From c3f844932196bf431f165b60b0320b1e04884126 Mon Sep 17 00:00:00 2001 From: Luke Brody Date: Sat, 7 Sep 2024 12:36:08 -0700 Subject: [PATCH] Add lint rules (except naming) (#321) Should also fix the data credit page --------- Co-authored-by: Luke Brody Co-authored-by: Kavi Gupta --- react/eslint.config.mjs | 127 +- react/package-lock.json | 4288 ++++++++++++++++++--- react/package.json | 9 +- react/src/about.tsx | 89 +- react/src/article.tsx | 40 +- react/src/comparison.tsx | 44 +- react/src/components/article-panel.tsx | 210 +- react/src/components/comparison-panel.tsx | 719 ++-- react/src/components/hamburger.tsx | 31 +- react/src/components/header.tsx | 241 +- react/src/components/load-article.ts | 148 +- react/src/components/map.tsx | 457 +-- react/src/components/mapper-panel.tsx | 402 +- react/src/components/plots.tsx | 452 +-- react/src/components/quiz-panel.tsx | 191 +- react/src/components/related-button.tsx | 158 +- react/src/components/screenshot.tsx | 214 +- react/src/components/search.tsx | 288 +- react/src/components/sidebar.tsx | 75 +- react/src/components/statistic-panel.tsx | 583 +-- react/src/components/table.tsx | 751 ++-- react/src/data-credit.tsx | 832 ++-- react/src/index.tsx | 96 +- react/src/load_json.ts | 173 +- react/src/mapper.tsx | 20 +- react/src/mapper/DataListSelector.tsx | 39 +- react/src/mapper/function.tsx | 640 +-- react/src/mapper/ramp-selector.tsx | 342 +- react/src/mapper/ramps.ts | 160 +- react/src/mapper/regression.ts | 70 +- react/src/mapper/settings.tsx | 326 +- react/src/mapper/style.ts | 12 +- react/src/navigation/links.ts | 110 +- react/src/navigation/random.ts | 59 +- react/src/page_template/settings.ts | 92 +- react/src/page_template/template.tsx | 142 +- react/src/quiz.tsx | 110 +- react/src/quiz/dates.ts | 108 +- react/src/quiz/quiz-components.tsx | 94 +- react/src/quiz/quiz-question.tsx | 251 +- react/src/quiz/quiz-result.tsx | 465 +-- react/src/quiz/quiz-statistics.tsx | 190 +- react/src/quiz/quiz.ts | 30 +- react/src/quiz/statistics.ts | 97 +- react/src/random.ts | 31 +- react/src/statistic.tsx | 85 +- react/src/universe.ts | 68 +- react/src/utils/DefaultMap.ts | 27 +- react/src/utils/color.ts | 78 +- react/src/utils/dom-to-image-more.d.ts | 8 +- react/src/utils/is_historical.ts | 7 +- react/src/utils/protos.js | 3296 ++++++++-------- react/src/utils/responsive.ts | 28 +- react/src/utils/text.ts | 18 +- react/src/utils/types.ts | 2 +- react/test/Dockerfile | 3 + react/test/article_test.ts | 399 +- react/test/ci_proxy.ts | 30 +- react/test/comparison_test.ts | 165 +- react/test/histogram_test.ts | 202 +- react/test/mapper_test.ts | 57 +- react/test/quiz_test.ts | 535 ++- react/test/random_test.ts | 20 +- react/test/search_test.ts | 69 +- react/test/settings_test.ts | 102 +- react/test/statistics_test.ts | 127 +- react/test/string_check_test.ts | 14 +- react/test/test_utils.ts | 157 +- react/tsconfig.json | 6 +- react/webpack.config.js | 32 +- 70 files changed, 11595 insertions(+), 7946 deletions(-) diff --git a/react/eslint.config.mjs b/react/eslint.config.mjs index 63bf1731..ef7b8bbe 100644 --- a/react/eslint.config.mjs +++ b/react/eslint.config.mjs @@ -1,25 +1,110 @@ // @ts-check -import eslint from '@eslint/js'; -import tseslint from 'typescript-eslint'; +import stylistic from '@stylistic/eslint-plugin' +import comments from 'eslint-plugin-eslint-comments' +import importPlugin from 'eslint-plugin-import' +import tseslint from 'typescript-eslint' +import reactPlugin from "eslint-plugin-react" +import preferFC from 'eslint-plugin-react-prefer-function-component/config' +import reactHooks from 'eslint-plugin-react-hooks' export default tseslint.config( - eslint.configs.recommended, - ...tseslint.configs.strict, - ...tseslint.configs.stylistic, - { - files: ['**/*.{ts,tsx,mts,cts}'], - rules: { - 'no-undef': 'off', - }, - }, - { - ignores: ['**/*.js', 'src/utils/protos.d.ts'], - }, - { - rules: { - "@typescript-eslint/no-require-imports": ["error", { allow: ['\\.json$']}], - "@typescript-eslint/no-non-null-assertion": "off" - } - } -); \ No newline at end of file + ...tseslint.configs.strictTypeChecked, + ...tseslint.configs.stylisticTypeChecked, + reactPlugin.configs.flat.recommended, + { + settings: { + react: { + version: 'detect', + }, + } + }, + preferFC.configs.recommended, + { + plugins: { + 'react-hooks': reactHooks + }, + rules: reactHooks.configs.recommended.rules + }, + stylistic.configs.customize({ + indent: 4, + }), + { + plugins: { + import: importPlugin, + }, + rules: { + ...importPlugin.configs.recommended.rules, + ...importPlugin.configs.recommended.typescript, + 'import/order': ['error', { + 'alphabetize': { + order: 'asc', + }, + 'newlines-between': 'always', + }], + 'import/no-unassigned-import': ['error', { + allow: ['**/*.css'], + }], + 'import/no-named-as-default-member': 'off', + 'import/namespace': 'off', + }, + settings: { + 'import/resolver': { + typescript: true, + node: true, + }, + 'import/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx'], + }, + }, + }, + { + plugins: { 'eslint-comments': comments }, + rules: comments.configs.recommended.rules, + }, + { + files: ['**/*.{ts,tsx,mts,cts}'], + rules: { + 'no-undef': 'off', + }, + }, + { + ignores: ['**/*.{js,mjs}', 'src/utils/protos.d.ts'], + }, + { + rules: { + '@typescript-eslint/no-require-imports': ['error', { allow: ['\\.json$'] }], + '@typescript-eslint/no-non-null-assertion': 'off', + 'prefer-template': 'error', + '@typescript-eslint/restrict-template-expressions': 'off', + 'eslint-comments/no-unused-disable': 'error', + 'no-console': ['error', { "allow": ["warn", "error"] }], + '@stylistic/max-statements-per-line': 'off', + '@typescript-eslint/no-unnecessary-condition': ['error', { allowConstantLoopConditions: true }], + '@typescript-eslint/explicit-function-return-type': ['error', { allowExpressions: true }], + '@typescript-eslint/no-misused-promises': ['error', { + checksVoidReturn: { + attributes: false, + arguments: false, + }, + }], + 'eqeqeq': 'error', + 'guard-for-in': 'error', + 'object-shorthand': 'error', + 'no-restricted-syntax': ['error', 'ExportNamedDeclaration:not([declaration])'], + 'react/prop-types': 'off', + 'no-shadow': 'error', + 'eslint-comments/require-description': ['error', { + ignore: ['eslint-enable'] + }], + }, + }, + { + languageOptions: { + parserOptions: { + parser: '@typescript-eslint/parser', + project: true, + }, + }, + }, +) diff --git a/react/package-lock.json b/react/package-lock.json index 2e4d8261..377c7c21 100644 --- a/react/package-lock.json +++ b/react/package-lock.json @@ -40,6 +40,7 @@ "@babel/preset-env": "^7.20.2", "@babel/preset-react": "^7.18.6", "@eslint/js": "^9.8.0", + "@stylistic/eslint-plugin": "^2.7.2", "@types/dom-to-image": "^2.6.7", "@types/eslint__js": "^8.42.3", "@types/express": "^4.17.21", @@ -52,7 +53,13 @@ "babel-plugin-import": "^1.13.8", "css-loader": "^6.8.1", "downloads-folder": "^3.0.3", - "eslint": "^9.8.0", + "eslint": "^8.57.0", + "eslint-import-resolver-typescript": "^3.6.3", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-react": "^7.35.2", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-prefer-function-component": "^3.3.0", "express": "^4.19.2", "express-http-proxy": "^2.1.1", "protobufjs-cli": "^1.1.1", @@ -2218,52 +2225,16 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/config-array": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.1.tgz", - "integrity": "sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==", - "dev": true, - "dependencies": { - "@eslint/object-schema": "^2.1.4", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@eslint/eslintrc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "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": "^10.0.1", - "globals": "^14.0.0", + "espree": "^9.6.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -2271,7 +2242,7 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -2303,42 +2274,16 @@ "concat-map": "0.0.1" } }, - "node_modules/@eslint/eslintrc/node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/espree": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", - "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { - "acorn": "^8.12.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "type-fest": "^0.20.2" }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, "engines": { - "node": ">=18" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2362,19 +2307,22 @@ "node": "*" } }, - "node_modules/@eslint/js": { - "version": "9.8.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.8.0.tgz", - "integrity": "sha512-MfluB7EUfxXtv3i/++oh89uzAr4PDI4nn201hsp+qaXqsjAWzinlZEHEfPgAX4doIlKvPG/i0A9dpKxOLII8yA==", + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "node_modules/@eslint/js": { + "version": "9.9.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.1.tgz", + "integrity": "sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2385,6 +2333,43 @@ "resolved": "https://registry.npmjs.org/@fontsource/jost/-/jost-5.0.18.tgz", "integrity": "sha512-NeFIj4DQ7Ll5VocGrH9yIZcgzyxXfpiPf01PeQORR3HR9NhcNQGpzOLtqAdpQKQnbjw5IcG6ACnucsFUFX9szw==" }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -2398,18 +2383,12 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", - "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", - "dev": true, - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", @@ -2521,6 +2500,15 @@ "node": ">= 8" } }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "engines": { + "node": ">=12.4.0" + } + }, "node_modules/@observablehq/plot": { "version": "0.6.16", "resolved": "https://registry.npmjs.org/@observablehq/plot/-/plot-0.6.16.tgz", @@ -2588,110 +2576,307 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true - }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "node_modules/@stylistic/eslint-plugin": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.7.2.tgz", + "integrity": "sha512-3DVLU5HEuk2pQoBmXJlzvrxbKNpu2mJ0SRqz5O/CJjyNCr12ZiPcYMEtuArTyPOk5i7bsAU44nywh1rGfe3gKQ==", "dev": true, "dependencies": { - "@types/connect": "*", - "@types/node": "*" + "@types/eslint": "^9.6.1", + "@typescript-eslint/utils": "^8.3.0", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" } }, - "node_modules/@types/bonjour": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", - "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.4.0.tgz", + "integrity": "sha512-n2jFxLeY0JmKfUqy3P70rs6vdoPjHK8P/w+zJcV3fk0b0BwRXC/zxRTEnAsgYT7MwdQDt/ZEbtdzdVC+hcpF0A==", "dev": true, "dependencies": { - "@types/node": "*" + "@typescript-eslint/types": "8.4.0", + "@typescript-eslint/visitor-keys": "8.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.4.0.tgz", + "integrity": "sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==", "dev": true, - "dependencies": { - "@types/node": "*" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", - "integrity": "sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig==", + "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.4.0.tgz", + "integrity": "sha512-kJ2OIP4dQw5gdI4uXsaxUZHRwWAGpREJ9Zq6D5L0BweyOrWsL6Sz0YcAZGWhvKnH7fm1J5YFE1JrQL0c9dd53A==", "dev": true, "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" + "@typescript-eslint/types": "8.4.0", + "@typescript-eslint/visitor-keys": "8.4.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@types/dom-to-image": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/@types/dom-to-image/-/dom-to-image-2.6.7.tgz", - "integrity": "sha512-me5VbCv+fcXozblWwG13krNBvuEOm6kA5xoa4RrjDJCNFOZSWR3/QLtOXimBHk1Fisq69Gx3JtOoXtg1N1tijg==", - "dev": true - }, - "node_modules/@types/eslint": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.1.tgz", - "integrity": "sha512-XpNDc4Z5Tb4x+SW1MriMVeIsMoONHCkWFMkR/aPJbzEsxqHy+4Glu/BqTdPrApfDeMaXbtNh6bseNgl5KaWrSg==", + "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.4.0.tgz", + "integrity": "sha512-swULW8n1IKLjRAgciCkTCafyTHHfwVQFt8DovmaF69sKbOxTSFMmIZaSHjqO9i/RV0wIblaawhzvtva8Nmm7lQ==", + "dev": true, "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.4.0", + "@typescript-eslint/types": "8.4.0", + "@typescript-eslint/typescript-estree": "8.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" } }, - "node_modules/@types/eslint__js": { - "version": "8.42.3", - "resolved": "https://registry.npmjs.org/@types/eslint__js/-/eslint__js-8.42.3.tgz", - "integrity": "sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==", + "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.4.0.tgz", + "integrity": "sha512-zTQD6WLNTre1hj5wp09nBIDiOc2U5r/qmzo7wxPn4ZgAjHql09EofqhF9WF+fZHzL5aCyaIpPcT2hyxl73kr9A==", "dev": true, "dependencies": { - "@types/eslint": "*" + "@typescript-eslint/types": "8.4.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" + "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "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/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==" - }, - "node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "dev": true, + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", + "integrity": "sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/dom-to-image": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/@types/dom-to-image/-/dom-to-image-2.6.7.tgz", + "integrity": "sha512-me5VbCv+fcXozblWwG13krNBvuEOm6kA5xoa4RrjDJCNFOZSWR3/QLtOXimBHk1Fisq69Gx3JtOoXtg1N1tijg==", + "dev": true + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint__js": { + "version": "8.42.3", + "resolved": "https://registry.npmjs.org/@types/eslint__js/-/eslint__js-8.42.3.tgz", + "integrity": "sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==", + "dev": true, + "dependencies": { + "@types/eslint": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==" + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dev": true, "dependencies": { "@types/body-parser": "*", @@ -2768,6 +2953,12 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==" }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, "node_modules/@types/leaflet": { "version": "1.9.12", "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.12.tgz", @@ -3129,17 +3320,11 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "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/@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/@webassemblyjs/ast": { "version": "1.11.6", @@ -3614,6 +3799,22 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-find": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-find/-/array-find-1.0.0.tgz", @@ -3626,6 +3827,26 @@ "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", "dev": true }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -3644,61 +3865,39 @@ "node": ">=0.10.0" } }, - "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/assert": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", - "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", - "dependencies": { - "es6-object-assign": "^1.1.0", - "is-nan": "^1.2.1", - "object-is": "^1.0.1", - "util": "^0.12.0" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, "engines": { - "node": "*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true - }, - "node_modules/async-exit-hook": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-1.1.2.tgz", - "integrity": "sha512-CeTSWB5Bou31xSHeO45ZKgLPRaJbV4I8csRcFYETDBehX7H+1GDO/v+v8G7fZmar1gOmYa6UTXn6d/WIiJbslw==", + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, "engines": { "node": ">= 0.4" }, @@ -3706,9 +3905,148 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-loader": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/assert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", + "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "dependencies": { + "es6-object-assign": "^1.1.0", + "is-nan": "^1.2.1", + "object-is": "^1.0.1", + "util": "^0.12.0" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, + "node_modules/async-exit-hook": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-1.1.2.tgz", + "integrity": "sha512-CeTSWB5Bou31xSHeO45ZKgLPRaJbV4I8csRcFYETDBehX7H+1GDO/v+v8G7fZmar1gOmYa6UTXn6d/WIiJbslw==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-loader": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", "dev": true, "dependencies": { @@ -4210,12 +4548,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5193,10 +5537,61 @@ "node": ">=12" } }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -5261,6 +5656,22 @@ "node": ">= 10" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -5271,10 +5682,11 @@ } }, "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dependencies": { + "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" }, @@ -5524,6 +5936,18 @@ "node": ">=6" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dom-to-image-more": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/dom-to-image-more/-/dom-to-image-more-3.3.0.tgz", @@ -5703,11 +6127,167 @@ "stackframe": "^1.3.4" } }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", + "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==" }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es6-object-assign": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", @@ -5775,58 +6355,337 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "engines": { - "node": ">=4.0" + "node": ">=4.0" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@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.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", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.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.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.3.tgz", + "integrity": "sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==", + "dev": true, + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.3.5", + "enhanced-resolve": "^5.15.0", + "eslint-module-utils": "^2.8.1", + "fast-glob": "^3.3.2", + "get-tsconfig": "^4.7.5", + "is-bun-module": "^1.0.2", + "is-glob": "^4.0.3" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.2.tgz", + "integrity": "sha512-3XnC5fDyc8M4J2E8pt8pmSVRX2M+5yWMCfI/kDZwauQeFgzQOuhcRBFKjTeJagqgk4sFKxe1mvNVnaWwImx/Tg==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-eslint-comments": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz", + "integrity": "sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5", + "ignore": "^5.0.5" + }, + "engines": { + "node": ">=6.5.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.35.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.2.tgz", + "integrity": "sha512-Rbj2R9zwP2GYNcIak4xoAMV57hrBh3hTaR0k7hVjwCQgryE/pw5px4b13EYjduOI0hfXyZhwBxaGpOTbWSGzKQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.19", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-prefer-function-component": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-prefer-function-component/-/eslint-plugin-react-prefer-function-component-3.3.0.tgz", + "integrity": "sha512-BJXHT8gn3tLd9pTrX7v9hozZA79p1s29mEPomg/laOK/1yt6KPFgIYWuxpq4I4C+x88APRVVBVtvW1rdCjeFxQ==", + "dev": true + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "node_modules/eslint": { - "version": "9.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.8.0.tgz", - "integrity": "sha512-K8qnZ/QJzT2dLKdZJVX6W4XOwBzutMYmt0lqUS+JdXgd+HTYFlonFgkJ8s44d/zMPPCnOOk0kMWCApCPhiOy9A==", + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.11.0", - "@eslint/config-array": "^0.17.1", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.8.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.0", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.2", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.1.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "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.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "resolve": "bin/resolve" }, "funding": { - "url": "https://eslint.org/donate" + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/eslint-scope": { @@ -5850,9 +6709,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz", - "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==", + "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" @@ -5861,6 +6720,15 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/eslint/node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -5949,45 +6817,16 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", - "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "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", "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/espree": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", - "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", - "dev": true, - "dependencies": { - "acorn": "^8.12.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -6021,6 +6860,21 @@ "node": ">=10.13.0" } }, + "node_modules/eslint/node_modules/globals": { + "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" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6174,6 +7028,18 @@ "node": ">= 0.8.0" } }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint/node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -6518,15 +7384,15 @@ } }, "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "dependencies": { - "flat-cache": "^4.0.0" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=16.0.0" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/file-saver": { @@ -6642,16 +7508,17 @@ } }, "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.4" + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, "engines": { - "node": ">=16" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { @@ -6766,9 +7633,39 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/gauge": { "version": "2.7.4", @@ -6844,14 +7741,18 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6890,6 +7791,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.0.tgz", + "integrity": "sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/getos": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", @@ -6951,6 +7881,22 @@ "node": ">=4" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -7013,15 +7959,13 @@ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-flag": { @@ -7034,20 +7978,20 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "engines": { "node": ">= 0.4" }, @@ -7067,11 +8011,11 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -7122,6 +8066,17 @@ "minimalistic-assert": "^1.0.1" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/highlight-es": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/highlight-es/-/highlight-es-1.0.3.tgz", @@ -7570,6 +8525,20 @@ "dev": true, "optional": true }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -7589,31 +8558,102 @@ }, "node_modules/interval-tree-1d": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/interval-tree-1d/-/interval-tree-1d-1.0.4.tgz", - "integrity": "sha512-wY8QJH+6wNI0uh4pDQzMvl+478Qh7Rl4qLmqiluxALlNvl+I+o5x38Pw3/z7mDPTPS1dQalZJXsmbvxx5gclhQ==", + "resolved": "https://registry.npmjs.org/interval-tree-1d/-/interval-tree-1d-1.0.4.tgz", + "integrity": "sha512-wY8QJH+6wNI0uh4pDQzMvl+478Qh7Rl4qLmqiluxALlNvl+I+o5x38Pw3/z7mDPTPS1dQalZJXsmbvxx5gclhQ==", + "dependencies": { + "binary-search-bounds": "^2.0.0" + } + }, + "node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, "dependencies": { - "binary-search-bounds": "^2.0.0" + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", - "dev": true - }, - "node_modules/ipaddr.js": { + "node_modules/is-binary-path": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", - "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, "engines": { - "node": ">= 10" + "node": ">=8" } }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -7625,16 +8665,25 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/is-bun-module": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.1.0.tgz", + "integrity": "sha512-4mTAVPlrXpaN3jtF0lsnPCMGnq4+qZjVIKq0HCpfcqf8OC1SM5oATCIAPM5V5FN05qp2NNnFndphmdZS9CV3hA==", "dev": true, "dependencies": { - "binary-extensions": "^2.0.0" + "semver": "^7.6.3" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=8" + "node": ">=10" } }, "node_modules/is-callable": { @@ -7661,12 +8710,45 @@ } }, "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "dev": true, "dependencies": { - "has": "^1.0.3" + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7702,6 +8784,18 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-finite": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", @@ -7759,6 +8853,18 @@ "integrity": "sha512-18toSebUVF7y717dgw/Dzn6djOCqrkiDp3MhB8P6TdKyCVkbD1ZwE7Uz8Hwx6hUPTvKjbyYH9ncXT4ts4qLaSA==", "dev": true }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-nan": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", @@ -7774,6 +8880,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -7783,6 +8901,21 @@ "node": ">=0.12.0" } }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-path-cwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", @@ -7852,6 +8985,49 @@ "node": ">=8" } }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -7864,12 +9040,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -7884,6 +9090,46 @@ "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", "dev": true }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -7922,6 +9168,19 @@ "resolved": "https://registry.npmjs.org/isoformat/-/isoformat-0.2.1.tgz", "integrity": "sha512-tFLRAygk9NqrRPhJSnNGh7g7oaVWDwR0wKh/GM2LgmPa50Eg4UfyaCO4I8k6EqJHl1/uh2RAD6g06n5ygEnrjQ==" }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, "node_modules/javascript-natural-sort": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", @@ -8090,6 +9349,21 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -8804,35 +10078,119 @@ "node": ">=0.10.0" } }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/obuf": { @@ -9281,6 +10639,14 @@ "node": ">=4.0.0" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.4.27", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz", @@ -9932,6 +11298,27 @@ "node": ">= 10.13.0" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -9964,6 +11351,24 @@ "@babel/runtime": "^7.8.4" } }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regexpu-core": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", @@ -10141,12 +11546,12 @@ "dev": true }, "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "dependencies": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -10176,6 +11581,15 @@ "node": ">=8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -10294,6 +11708,30 @@ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -10313,6 +11751,23 @@ } ] }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -10545,6 +12000,37 @@ "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", "dev": true }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -10611,13 +12097,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10874,6 +12364,91 @@ "node": ">=0.10.0" } }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -12171,6 +13746,39 @@ } } }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/tslib": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", @@ -12238,6 +13846,79 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typed-function": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.1.tgz", @@ -12318,6 +13999,21 @@ "node": ">=0.8.0" } }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/underscore": { "version": "1.13.6", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", @@ -12823,22 +14519,88 @@ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true, "engines": { - "node": ">=0.8.0" + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", + "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", + "dev": true, + "dependencies": { + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" }, "engines": { - "node": ">= 8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/which-pm-runs": { @@ -12896,15 +14658,15 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", - "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -14584,48 +16346,16 @@ "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "dev": true }, - "@eslint/config-array": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.1.tgz", - "integrity": "sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==", - "dev": true, - "requires": { - "@eslint/object-schema": "^2.1.4", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "dependencies": { - "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" - } - }, - "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" - } - } - } - }, "@eslint/eslintrc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", + "espree": "^9.6.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -14655,29 +16385,15 @@ "concat-map": "0.0.1" } }, - "eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", - "dev": true - }, - "espree": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", - "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "requires": { - "acorn": "^8.12.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.0.0" + "type-fest": "^0.20.2" } }, - "globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -14692,19 +16408,19 @@ "requires": { "brace-expansion": "^1.1.7" } + }, + "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 } } }, "@eslint/js": { - "version": "9.8.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.8.0.tgz", - "integrity": "sha512-MfluB7EUfxXtv3i/++oh89uzAr4PDI4nn201hsp+qaXqsjAWzinlZEHEfPgAX4doIlKvPG/i0A9dpKxOLII8yA==", - "dev": true - }, - "@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "version": "9.9.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.1.tgz", + "integrity": "sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==", "dev": true }, "@fontsource/jost": { @@ -14712,16 +16428,48 @@ "resolved": "https://registry.npmjs.org/@fontsource/jost/-/jost-5.0.18.tgz", "integrity": "sha512-NeFIj4DQ7Ll5VocGrH9yIZcgzyxXfpiPf01PeQORR3HR9NhcNQGpzOLtqAdpQKQnbjw5IcG6ACnucsFUFX9szw==" }, + "@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "dependencies": { + "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" + } + }, + "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" + } + } + } + }, "@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true }, - "@humanwhocodes/retry": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", - "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, "@jridgewell/gen-mapping": { @@ -14815,6 +16563,12 @@ "fastq": "^1.6.0" } }, + "@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true + }, "@observablehq/plot": { "version": "0.6.16", "resolved": "https://registry.npmjs.org/@observablehq/plot/-/plot-0.6.16.tgz", @@ -14879,6 +16633,122 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "@stylistic/eslint-plugin": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.7.2.tgz", + "integrity": "sha512-3DVLU5HEuk2pQoBmXJlzvrxbKNpu2mJ0SRqz5O/CJjyNCr12ZiPcYMEtuArTyPOk5i7bsAU44nywh1rGfe3gKQ==", + "dev": true, + "requires": { + "@types/eslint": "^9.6.1", + "@typescript-eslint/utils": "^8.3.0", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.4.0.tgz", + "integrity": "sha512-n2jFxLeY0JmKfUqy3P70rs6vdoPjHK8P/w+zJcV3fk0b0BwRXC/zxRTEnAsgYT7MwdQDt/ZEbtdzdVC+hcpF0A==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.4.0", + "@typescript-eslint/visitor-keys": "8.4.0" + } + }, + "@typescript-eslint/types": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.4.0.tgz", + "integrity": "sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.4.0.tgz", + "integrity": "sha512-kJ2OIP4dQw5gdI4uXsaxUZHRwWAGpREJ9Zq6D5L0BweyOrWsL6Sz0YcAZGWhvKnH7fm1J5YFE1JrQL0c9dd53A==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.4.0", + "@typescript-eslint/visitor-keys": "8.4.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + } + }, + "@typescript-eslint/utils": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.4.0.tgz", + "integrity": "sha512-swULW8n1IKLjRAgciCkTCafyTHHfwVQFt8DovmaF69sKbOxTSFMmIZaSHjqO9i/RV0wIblaawhzvtva8Nmm7lQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.4.0", + "@typescript-eslint/types": "8.4.0", + "@typescript-eslint/typescript-estree": "8.4.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.4.0.tgz", + "integrity": "sha512-zTQD6WLNTre1hj5wp09nBIDiOc2U5r/qmzo7wxPn4ZgAjHql09EofqhF9WF+fZHzL5aCyaIpPcT2hyxl73kr9A==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.4.0", + "eslint-visitor-keys": "^3.4.3" + }, + "dependencies": { + "eslint-visitor-keys": { + "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 + } + } + }, + "eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true + }, + "espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "dev": true, + "requires": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true + }, + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + } + } + }, "@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -14948,9 +16818,9 @@ "dev": true }, "@types/eslint": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.1.tgz", - "integrity": "sha512-XpNDc4Z5Tb4x+SW1MriMVeIsMoONHCkWFMkR/aPJbzEsxqHy+4Glu/BqTdPrApfDeMaXbtNh6bseNgl5KaWrSg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "requires": { "@types/estree": "*", "@types/json-schema": "*" @@ -15059,6 +16929,12 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==" }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, "@types/leaflet": { "version": "1.9.12", "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.12.tgz", @@ -15322,16 +17198,14 @@ "requires": { "@typescript-eslint/types": "8.0.1", "eslint-visitor-keys": "^3.4.3" - }, - "dependencies": { - "eslint-visitor-keys": { - "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 - } } }, + "@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 + }, "@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -15723,6 +17597,16 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "requires": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + } + }, "array-find": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-find/-/array-find-1.0.0.tgz", @@ -15735,6 +17619,20 @@ "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", "dev": true }, + "array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + } + }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -15747,6 +17645,87 @@ "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", "dev": true }, + "array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + } + }, + "array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + } + }, + "array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + } + }, + "arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + } + }, "asn1.js": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", @@ -15795,9 +17774,12 @@ "dev": true }, "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "requires": { + "possible-typed-array-names": "^1.0.0" + } }, "babel-loader": { "version": "9.1.3", @@ -16192,12 +18174,15 @@ "dev": true }, "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" } }, "callsite": { @@ -16926,10 +18911,43 @@ "d3-transition": "2 - 3" } }, + "data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + } + }, + "data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + } + }, + "data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "requires": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + } + }, "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dev": true, "requires": { "ms": "2.1.2" @@ -16977,6 +18995,16 @@ "execa": "^5.0.0" } }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, "define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -16984,10 +19012,11 @@ "dev": true }, "define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "requires": { + "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } @@ -17185,7 +19214,16 @@ "integrity": "sha512-rza3UH1LwdHh9qyPXp8lkwpjSNk/AMD3dPytUoRoqnypDUhY0xvbdmVhWOfxO68frEfV9BU8V12Ez7ZsHGZpCQ==", "dev": true, "requires": { - "@leichtgewicht/ip-codec": "^2.0.1" + "@leichtgewicht/ip-codec": "^2.0.1" + } + }, + "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-to-image-more": { @@ -17335,11 +19373,140 @@ "stackframe": "^1.3.4" } }, + "es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + } + }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-iterator-helpers": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", + "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.2" + } + }, "es-module-lexer": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==" }, + "es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "requires": { + "es-errors": "^1.3.0" + } + }, + "es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + } + }, + "es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "requires": { + "hasown": "^2.0.0" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "es6-object-assign": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", @@ -17395,37 +19562,41 @@ } }, "eslint": { - "version": "9.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.8.0.tgz", - "integrity": "sha512-K8qnZ/QJzT2dLKdZJVX6W4XOwBzutMYmt0lqUS+JdXgd+HTYFlonFgkJ8s44d/zMPPCnOOk0kMWCApCPhiOy9A==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.11.0", - "@eslint/config-array": "^0.17.1", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.8.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.0", "@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": "^8.0.2", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.1.0", - "esquery": "^1.5.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": "^8.0.0", + "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.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", @@ -17436,6 +19607,12 @@ "text-table": "^0.2.0" }, "dependencies": { + "@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -17499,32 +19676,15 @@ "dev": true }, "eslint-scope": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", - "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "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, "requires": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, - "eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", - "dev": true - }, - "espree": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", - "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", - "dev": true, - "requires": { - "acorn": "^8.12.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.0.0" - } - }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -17544,6 +19704,15 @@ "is-glob": "^4.0.3" } }, + "globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -17652,6 +19821,12 @@ "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 + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -17660,6 +19835,218 @@ } } }, + "eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-import-resolver-typescript": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.3.tgz", + "integrity": "sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==", + "dev": true, + "requires": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.3.5", + "enhanced-resolve": "^5.15.0", + "eslint-module-utils": "^2.8.1", + "fast-glob": "^3.3.2", + "get-tsconfig": "^4.7.5", + "is-bun-module": "^1.0.2", + "is-glob": "^4.0.3" + } + }, + "eslint-module-utils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.2.tgz", + "integrity": "sha512-3XnC5fDyc8M4J2E8pt8pmSVRX2M+5yWMCfI/kDZwauQeFgzQOuhcRBFKjTeJagqgk4sFKxe1mvNVnaWwImx/Tg==", + "dev": true, + "requires": { + "debug": "^3.2.7" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-plugin-eslint-comments": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz", + "integrity": "sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "ignore": "^5.0.5" + } + }, + "eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, + "requires": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "dependencies": { + "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" + } + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "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" + } + } + } + }, + "eslint-plugin-react": { + "version": "7.35.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.2.tgz", + "integrity": "sha512-Rbj2R9zwP2GYNcIak4xoAMV57hrBh3hTaR0k7hVjwCQgryE/pw5px4b13EYjduOI0hfXyZhwBxaGpOTbWSGzKQ==", + "dev": true, + "requires": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.19", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" + }, + "dependencies": { + "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" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "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" + } + }, + "resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + } + } + }, + "eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "requires": {} + }, + "eslint-plugin-react-prefer-function-component": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-prefer-function-component/-/eslint-plugin-react-prefer-function-component-3.3.0.tgz", + "integrity": "sha512-BJXHT8gn3tLd9pTrX7v9hozZA79p1s29mEPomg/laOK/1yt6KPFgIYWuxpq4I4C+x88APRVVBVtvW1rdCjeFxQ==", + "dev": true + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -17677,9 +20064,9 @@ } }, "eslint-visitor-keys": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz", - "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==", + "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 }, "esotope-hammerhead": { @@ -17956,12 +20343,12 @@ } }, "file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "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": "^4.0.0" + "flat-cache": "^3.0.4" } }, "file-saver": { @@ -18054,13 +20441,14 @@ } }, "flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "requires": { "flatted": "^3.2.9", - "keyv": "^4.5.4" + "keyv": "^4.5.3", + "rimraf": "^3.0.2" } }, "flatted": { @@ -18138,9 +20526,27 @@ "optional": true }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true }, "gauge": { "version": "2.7.4", @@ -18206,14 +20612,15 @@ "dev": true }, "get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, "get-os-info": { @@ -18240,6 +20647,26 @@ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true }, + "get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "requires": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + } + }, + "get-tsconfig": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.0.tgz", + "integrity": "sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==", + "dev": true, + "requires": { + "resolve-pkg-maps": "^1.0.0" + } + }, "getos": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", @@ -18289,6 +20716,16 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "requires": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + } + }, "globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -18342,13 +20779,11 @@ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true }, "has-flag": { "version": "3.0.0", @@ -18357,17 +20792,17 @@ "dev": true }, "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "requires": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" } }, "has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" }, "has-symbols": { "version": "1.0.3", @@ -18375,11 +20810,11 @@ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "requires": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" } }, "has-unicode": { @@ -18420,6 +20855,14 @@ "minimalistic-assert": "^1.0.1" } }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, "highlight-es": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/highlight-es/-/highlight-es-1.0.3.tgz", @@ -18756,6 +21199,17 @@ "dev": true, "optional": true }, + "internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + } + }, "internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -18792,10 +21246,38 @@ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", "requires": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + } + }, + "is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "requires": { "has-tostringtag": "^1.0.0" } }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -18805,6 +21287,33 @@ "binary-extensions": "^2.0.0" } }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-bun-module": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.1.0.tgz", + "integrity": "sha512-4mTAVPlrXpaN3jtF0lsnPCMGnq4+qZjVIKq0HCpfcqf8OC1SM5oATCIAPM5V5FN05qp2NNnFndphmdZS9CV3hA==", + "dev": true, + "requires": { + "semver": "^7.6.3" + }, + "dependencies": { + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + } + } + }, "is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -18820,12 +21329,30 @@ } }, "is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "requires": { + "hasown": "^2.0.2" + } + }, + "is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "requires": { + "is-typed-array": "^1.1.13" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "dev": true, "requires": { - "has": "^1.0.3" + "has-tostringtag": "^1.0.0" } }, "is-docker": { @@ -18846,6 +21373,15 @@ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, + "is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, "is-finite": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", @@ -18885,6 +21421,12 @@ "integrity": "sha512-18toSebUVF7y717dgw/Dzn6djOCqrkiDp3MhB8P6TdKyCVkbD1ZwE7Uz8Hwx6hUPTvKjbyYH9ncXT4ts4qLaSA==", "dev": true }, + "is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true + }, "is-nan": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", @@ -18894,12 +21436,27 @@ "define-properties": "^1.1.3" } }, + "is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-path-cwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", @@ -18945,18 +21502,61 @@ "integrity": "sha512-+5vbtF5FIg262iUa7gOIseIWTx0740RHiax7oSmJMhbfSoBIMQ/IacKKgfnGj65JGeH9lGEVQcdkDwhn1Em1mQ==", "dev": true }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true + }, + "is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "requires": { + "call-bind": "^1.0.7" + } + }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, "is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "requires": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" } }, "is-utf8": { @@ -18965,6 +21565,31 @@ "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", "dev": true }, + "is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + } + }, "is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -18997,6 +21622,19 @@ "resolved": "https://registry.npmjs.org/isoformat/-/isoformat-0.2.1.tgz", "integrity": "sha512-tFLRAygk9NqrRPhJSnNGh7g7oaVWDwR0wKh/GM2LgmPa50Eg4UfyaCO4I8k6EqJHl1/uh2RAD6g06n5ygEnrjQ==" }, + "iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "requires": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, "javascript-natural-sort": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", @@ -19131,6 +21769,18 @@ "universalify": "^2.0.0" } }, + "jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "requires": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + } + }, "keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -19694,9 +22344,9 @@ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" }, "object-is": { "version": "1.1.5", @@ -19712,6 +22362,63 @@ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, + "object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + } + }, + "object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + } + }, + "object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + } + }, + "object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + } + }, "obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -20043,6 +22750,11 @@ "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", "dev": true }, + "possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==" + }, "postcss": { "version": "8.4.27", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz", @@ -20530,6 +23242,21 @@ "resolve": "^1.20.0" } }, + "reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + } + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -20559,6 +23286,18 @@ "@babel/runtime": "^7.8.4" } }, + "regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "requires": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + } + }, "regexpu-core": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", @@ -20706,12 +23445,12 @@ "dev": true }, "resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "requires": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } @@ -20729,6 +23468,12 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" }, + "resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true + }, "retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -20813,11 +23558,42 @@ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, + "safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, "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==" }, + "safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "requires": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + } + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -21025,6 +23801,31 @@ "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", "dev": true }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, + "set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + } + }, "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -21076,13 +23877,14 @@ "dev": true }, "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" } }, "signal-exit": { @@ -21287,6 +24089,70 @@ } } }, + "string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + } + }, + "string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -22284,6 +25150,35 @@ "yn": "3.1.1" } }, + "tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + } + } + }, "tslib": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", @@ -22333,6 +25228,58 @@ "mime-types": "~2.1.24" } }, + "typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + } + }, + "typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + } + }, + "typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + } + }, + "typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + } + }, "typed-function": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.1.tgz", @@ -22372,6 +25319,18 @@ "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", "dev": true }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, "underscore": { "version": "1.13.6", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", @@ -22740,6 +25699,59 @@ "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-builtin-type": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", + "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", + "dev": true, + "requires": { + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, + "which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "requires": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + } + }, "which-pm-runs": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", @@ -22785,15 +25797,15 @@ } }, "which-typed-array": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", - "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.2" } }, "wide-align": { diff --git a/react/package.json b/react/package.json index d5ff074d..38b6e57f 100644 --- a/react/package.json +++ b/react/package.json @@ -19,6 +19,7 @@ "@babel/preset-env": "^7.20.2", "@babel/preset-react": "^7.18.6", "@eslint/js": "^9.8.0", + "@stylistic/eslint-plugin": "^2.7.2", "@types/dom-to-image": "^2.6.7", "@types/eslint__js": "^8.42.3", "@types/express": "^4.17.21", @@ -31,7 +32,13 @@ "babel-plugin-import": "^1.13.8", "css-loader": "^6.8.1", "downloads-folder": "^3.0.3", - "eslint": "^9.8.0", + "eslint": "^8.57.0", + "eslint-import-resolver-typescript": "^3.6.3", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-react": "^7.35.2", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-prefer-function-component": "^3.3.0", "express": "^4.19.2", "express-http-proxy": "^2.1.1", "protobufjs-cli": "^1.1.1", diff --git a/react/src/about.tsx b/react/src/about.tsx index f493e178..1cf0f26a 100644 --- a/react/src/about.tsx +++ b/react/src/about.tsx @@ -1,46 +1,51 @@ -import React from 'react'; - -import ReactDOM from 'react-dom/client'; -import "./style.css"; -import "./common.css"; -import { PageTemplate } from "./page_template/template"; -import { headerTextClass } from './utils/responsive'; - - -function AboutPanel() { - return - {() =>
-
About
- -

- Urban Stats is a database of various statistics, computed largely from Census Data for United States - data and the Global Human Settlement Layer for international data. The statistics are computed for - a variety of regions in the United States and abroad. The goal of this project is to provide a - resource for people to learn about the places they live, and to provide a resource for journalists - and researchers to find interesting statistics about places they are studying. -

- -

- The project is open source, and the code is available on  - GitHub. - Feel free to file an issue or pull request if you have any suggestions or find any bugs. -

- -

- The project is primarily developed by Kavi Gupta, a PhD student at MIT, and Luke Brody. - The primary contact for this project is urbanstats at kavigupta dot org. -

-

- Issues can be filed by emailing Kavi or by  - filing an issue on GitHub. -

-
} -
+import React, { ReactNode } from 'react' +import ReactDOM from 'react-dom/client' + +import './style.css' +import './common.css' +import { PageTemplate } from './page_template/template' +import { headerTextClass } from './utils/responsive' + +function AboutPanel(): ReactNode { + return ( + + {() => ( +
+
About
+ +

+ Urban Stats is a database of various statistics, computed largely from Census Data for United States + data and the Global Human Settlement Layer for international data. The statistics are computed for + a variety of regions in the United States and abroad. The goal of this project is to provide a + resource for people to learn about the places they live, and to provide a resource for journalists + and researchers to find interesting statistics about places they are studying. +

+ +

+ The project is open source, and the code is available on  + GitHub + . + Feel free to file an issue or pull request if you have any suggestions or find any bugs. +

+ +

+ The project is primarily developed by Kavi Gupta, a PhD student at MIT, and Luke Brody. + The primary contact for this project is urbanstats at kavigupta dot org. +

+

+ Issues can be filed by emailing Kavi or by  + filing an issue on GitHub + . +

+
+ )} +
+ ) } -async function loadPage() { - const root = ReactDOM.createRoot(document.getElementById("root")!); - root.render(); +function loadPage(): void { + const root = ReactDOM.createRoot(document.getElementById('root')!) + root.render() } -loadPage(); \ No newline at end of file +loadPage() diff --git a/react/src/article.tsx b/react/src/article.tsx index bca3ac90..ea8a9912 100644 --- a/react/src/article.tsx +++ b/react/src/article.tsx @@ -1,30 +1,28 @@ -import React from 'react'; +import React from 'react' +import ReactDOM from 'react-dom/client' +import './style.css' +import './common.css' -import ReactDOM from 'react-dom/client'; -import "./style.css"; -import "./common.css"; -import { data_link } from "./navigation/links"; +import { ArticlePanel } from './components/article-panel' +import { loadProtobuf } from './load_json' +import { data_link } from './navigation/links' +import { UNIVERSE_CONTEXT, default_article_universe, get_universe, remove_universe_if_default, remove_universe_if_not_in } from './universe' -import { ArticlePanel } from './components/article-panel'; -import { loadProtobuf } from './load_json'; -import { default_article_universe, get_universe, remove_universe_if_default, remove_universe_if_not_in, UNIVERSE_CONTEXT } from './universe'; +async function loadPage(): Promise { + const window_info = new URLSearchParams(window.location.search) - -async function loadPage() { - const window_info = new URLSearchParams(window.location.search); - - const longname = window_info.get("longname")!; - const data = await loadProtobuf(data_link(longname), "Article"); - document.title = data.shortname; - const root = ReactDOM.createRoot(document.getElementById("root")!); + const longname = window_info.get('longname')! + const data = await loadProtobuf(data_link(longname), 'Article') + document.title = data.shortname + const root = ReactDOM.createRoot(document.getElementById('root')!) remove_universe_if_not_in(data.universes) - const default_universe = default_article_universe(longname); - remove_universe_if_default(default_universe); + const default_universe = default_article_universe(longname) + remove_universe_if_default(default_universe) root.render( - - ); + , + ) } -loadPage(); \ No newline at end of file +void loadPage() diff --git a/react/src/comparison.tsx b/react/src/comparison.tsx index 7ab0c648..eb793bc5 100644 --- a/react/src/comparison.tsx +++ b/react/src/comparison.tsx @@ -1,34 +1,32 @@ -import React from 'react'; +import React from 'react' +import ReactDOM from 'react-dom/client' +import './style.css' +import './common.css' -import ReactDOM from 'react-dom/client'; -import "./style.css"; -import "./common.css"; -import { data_link } from "./navigation/links"; +import { ComparisonPanel } from './components/comparison-panel' +import { loadProtobuf } from './load_json' +import { data_link } from './navigation/links' +import { UNIVERSE_CONTEXT, default_comparison_universe, get_universe, remove_universe_if_default, remove_universe_if_not_in } from './universe' -import { loadProtobuf } from './load_json'; -import { ComparisonPanel } from './components/comparison-panel'; -import { default_comparison_universe, get_universe, remove_universe_if_default, remove_universe_if_not_in, UNIVERSE_CONTEXT } from './universe'; +async function loadPage(): Promise { + const window_info = new URLSearchParams(window.location.search) + const names = JSON.parse(window_info.get('longnames')!) as string[] + const datas = await Promise.all(names.map(name => loadProtobuf(data_link(name), 'Article'))) -async function loadPage() { - const window_info = new URLSearchParams(window.location.search); - - const names = JSON.parse(window_info.get("longnames")!) as string[]; - const datas = await Promise.all(names.map(name => loadProtobuf(data_link(name), "Article"))); - - const joined_string = datas.map(x => x.shortname).join(" vs "); - document.title = joined_string; - const root = ReactDOM.createRoot(document.getElementById("root")!); + const joined_string = datas.map(x => x.shortname).join(' vs ') + document.title = joined_string + const root = ReactDOM.createRoot(document.getElementById('root')!) // intersection of all the data.universes - const universes = datas.map(x => x.universes).reduce((a, b) => a.filter(c => b.includes(c))); + const universes = datas.map(x => x.universes).reduce((a, b) => a.filter(c => b.includes(c))) remove_universe_if_not_in(universes) - const default_universe = default_comparison_universe(names); - remove_universe_if_default(default_universe); + const default_universe = default_comparison_universe(names) + remove_universe_if_default(default_universe) root.render( - - ); + , + ) } -loadPage(); \ No newline at end of file +void loadPage() diff --git a/react/src/components/article-panel.tsx b/react/src/components/article-panel.tsx index 26b4dd95..8b27c731 100644 --- a/react/src/components/article-panel.tsx +++ b/react/src/components/article-panel.tsx @@ -1,119 +1,131 @@ -import React, { useRef } from 'react'; - -import { StatisticRowRaw } from "./table"; -import { Map } from "./map"; -import { Related } from "./related-button"; -import { PageTemplate } from "../page_template/template"; -import "../common.css"; -import "./article.css"; -import { load_article } from './load-article'; -import { comparisonHeadStyle, headerTextClass, subHeaderTextClass } from '../utils/responsive'; -import { SearchBox } from './search'; -import { article_link, comparison_link, sanitize } from '../navigation/links'; -import { longname_is_exclusively_american, useUniverse } from '../universe'; -import { useSetting, useTableCheckboxSettings } from '../page_template/settings'; -import { Article, IRelatedButtons } from "../utils/protos"; -import { NormalizeProto } from "../utils/types"; - -export function ArticlePanel({ article } : { article: Article }) { - - const headers_ref = useRef(null); - const table_ref = useRef(null); - const map_ref = useRef(null); - - if (article.articleType == undefined) { - throw new Error("articleType is undefined"); - } - - const screencap_elements = () => ({ - path: sanitize(article.longname) + ".png", +import '../common.css' +import './article.css' + +import React, { ReactNode, useRef } from 'react' + +import { article_link, comparison_link, sanitize } from '../navigation/links' +import { useSetting, useTableCheckboxSettings } from '../page_template/settings' +import { PageTemplate } from '../page_template/template' +import { longname_is_exclusively_american, useUniverse } from '../universe' +import { Article, IRelatedButtons } from '../utils/protos' +import { comparisonHeadStyle, headerTextClass, subHeaderTextClass } from '../utils/responsive' +import { NormalizeProto } from '../utils/types' + +import { load_article } from './load-article' +import { Map } from './map' +import { Related } from './related-button' +import { ScreencapElements } from './screenshot' +import { SearchBox } from './search' +import { StatisticRowRaw } from './table' + +export function ArticlePanel({ article }: { article: Article }): ReactNode { + const headers_ref = useRef(null) + const table_ref = useRef(null) + const map_ref = useRef(null) + + const screencap_elements = (): ScreencapElements => ({ + path: `${sanitize(article.longname)}.png`, overall_width: table_ref.current!.offsetWidth * 2, elements_to_render: [headers_ref.current!, table_ref.current!, map_ref.current!], }) - return {(template_info) => -
-
-
{article.shortname}
-
{article.longname}
-
-
- -
- - -
+ return ( + + {template_info => ( +
+
+
{article.shortname}
+
{article.longname}
+
+
+ +
+ + +
-

+

-
- []} - article_type={article.articleType} - basemap={{ type: "osm" }} - /> -
+
+ []} + article_type={article.articleType} + basemap={{ type: 'osm' }} + /> +
-
+
-
-
-
Compare to:
+
+
+
Compare to:
+
+
+ +
-
- -
-
- + - []} - article_type={article.articleType} - /> -
} + []} + article_type={article.articleType} + /> +
+ )}
+ ) } -function ComparisonSearchBox({ longname }: { longname: string }) { - const curr_universe = useUniverse(); - return { - document.location.href = comparison_link( - curr_universe, - [longname, x]); - }} - autoFocus={false} - /> +function ComparisonSearchBox({ longname }: { longname: string }): ReactNode { + const curr_universe = useUniverse() + return ( + { + document.location.href = comparison_link( + curr_universe, + [longname, x]) + }} + autoFocus={false} + /> + ) } -function StatisticRowHeader(props: { screenshot_mode : boolean}) { - const [simple_ordinals] = useSetting("simple_ordinals"); +function StatisticRowHeader(props: { screenshot_mode: boolean }): ReactNode { + const [simple_ordinals] = useSetting('simple_ordinals') return } -function ArticlePanelRows(props: { article_row: Article, longname: string, shortname: string, screenshot_mode: boolean }) { - const curr_universe = useUniverse(); - const settings = useTableCheckboxSettings(); - const [simple_ordinals] = useSetting("simple_ordinals"); +function ArticlePanelRows(props: { article_row: Article, longname: string, shortname: string, screenshot_mode: boolean }): ReactNode { + const curr_universe = useUniverse() + const settings = useTableCheckboxSettings() + const [simple_ordinals] = useSetting('simple_ordinals') const [filtered_rows] = load_article(curr_universe, props.article_row, settings, - longname_is_exclusively_american(props.longname)); - return <> - {filtered_rows.map((row, i) => - { document.location = article_link(curr_universe, x) }} - simple={simple_ordinals} - longname={props.longname} - shortname={props.shortname} - screenshot_mode={props.screenshot_mode} - />)} - -} \ No newline at end of file + longname_is_exclusively_american(props.longname)) + return ( + <> + {filtered_rows.map((row, i) => ( + { document.location = article_link(curr_universe, x) }} + simple={simple_ordinals} + longname={props.longname} + shortname={props.shortname} + screenshot_mode={props.screenshot_mode} + /> + ))} + + ) +} diff --git a/react/src/components/comparison-panel.tsx b/react/src/components/comparison-panel.tsx index ed4928fa..7c6e5577 100644 --- a/react/src/components/comparison-panel.tsx +++ b/react/src/components/comparison-panel.tsx @@ -1,231 +1,249 @@ -import React, { useRef } from 'react'; - -import { StatisticRowRawCellContents, StatisticRow } from "./table"; -import { MapGeneric, MapGenericProps } from "./map"; -import { PageTemplate } from "../page_template/template"; -import "../common.css"; -import "./article.css"; -import { ArticleRow, load_article } from './load-article'; -import { comparisonHeadStyle, headerTextClass, mobileLayout, subHeaderTextClass } from '../utils/responsive'; -import { SearchBox } from './search'; -import { article_link, sanitize } from '../navigation/links'; -import { lighten } from '../utils/color'; -import { longname_is_exclusively_american, useUniverse } from '../universe'; -import { row_expanded_key, useSetting, useTableCheckboxSettings } from '../page_template/settings'; -import { WithPlot } from './plots'; -import { Article } from "../utils/protos"; - -const main_columns = ["statval", "statval_unit", "statistic_ordinal", "statistic_percentile"]; -const main_columns_across_types = ["statval", "statval_unit"] -const left_bar_margin = 0.02; -const left_margin_pct = 0.18; -const bar_height = "5px"; +import '../common.css' +import './article.css' + +import React, { ReactNode, useRef } from 'react' + +import { article_link, sanitize } from '../navigation/links' +import { row_expanded_key, useSetting, useTableCheckboxSettings } from '../page_template/settings' +import { PageTemplate } from '../page_template/template' +import { longname_is_exclusively_american, useUniverse } from '../universe' +import { lighten } from '../utils/color' +import { Article } from '../utils/protos' +import { comparisonHeadStyle, headerTextClass, mobileLayout, subHeaderTextClass } from '../utils/responsive' + +import { ArticleRow, load_article } from './load-article' +import { MapGeneric, MapGenericProps, Polygons } from './map' +import { WithPlot } from './plots' +import { ScreencapElements } from './screenshot' +import { SearchBox } from './search' +import { StatisticRow, StatisticRowRawCellContents } from './table' + +const main_columns = ['statval', 'statval_unit', 'statistic_ordinal', 'statistic_percentile'] +const main_columns_across_types = ['statval', 'statval_unit'] +const left_bar_margin = 0.02 +const left_margin_pct = 0.18 +const bar_height = '5px' const COLOR_CYCLE = [ - "#5a7dc3", // blue - "#f7aa41", // orange - "#975ac3", // purple - "#f96d6d", // red - "#8e8e8e", // grey - "#c767b0", // pink - "#b8a32f", // yellow - "#8ac35a", // green -]; - -export function ComparisonPanel(props: { joined_string: string, universes: string[], names: string[], datas: Article[] }) { - const table_ref = useRef(null); - const map_ref = useRef(null); - - const screencap_elements = () => ({ - path: sanitize(props.joined_string) + ".png", - overall_width: table_ref.current!.offsetWidth * 2, - elements_to_render: [table_ref.current!, map_ref.current!], - }) - - if (props.names == undefined) { - throw new Error("ComparisonPanel: names not set"); + '#5a7dc3', // blue + '#f7aa41', // orange + '#975ac3', // purple + '#f96d6d', // red + '#8e8e8e', // grey + '#c767b0', // pink + '#b8a32f', // yellow + '#8ac35a', // green +] + +export function ComparisonPanel(props: { joined_string: string, universes: string[], names: string[], datas: Article[] }): ReactNode { + const table_ref = useRef(null) + const map_ref = useRef(null) + + const screencap_elements = (): ScreencapElements => ({ + path: `${sanitize(props.joined_string)}.png`, + overall_width: table_ref.current!.offsetWidth * 2, + elements_to_render: [table_ref.current!, map_ref.current!], + }) + + const left_margin = (): number => { + return 100 * left_margin_pct } - const left_margin = () => { - return 100 * left_margin_pct; - } - - const cell = (is_left: boolean, i: number, contents: React.ReactNode) => { + const cell = (is_left: boolean, i: number, contents: React.ReactNode): ReactNode => { if (is_left) { - return
+ return ( +
+ {contents} +
+ ) + } + const width = `${each(props.datas)}%` + return ( +
{contents}
- } - const width = each(props.datas) + "%"; - return
- {contents} -
+ ) } - const bars = () => { - return
- {cell(true, 0,
)} - {props.datas.map( - (data, i) =>
- )} -
+ const bars = (): ReactNode => { + return ( +
+ {cell(true, 0,
)} + {props.datas.map( + (data, i) => ( +
+ ), + )} +
+ ) } - const max_columns = () => { - return mobileLayout() ? 4 : 6; + const max_columns = (): number => { + return mobileLayout() ? 4 : 6 } - const width_columns = () => { - // 1.5 columns each if all data types are the same, otherwise 1 column each - // + 1 for the left margin - return (all_data_types_same(props.datas) ? 1.5 : 1) * props.datas.length + 1; + const width_columns = (): number => { + // 1.5 columns each if all data types are the same, otherwise 1 column each + // + 1 for the left margin + return (all_data_types_same(props.datas) ? 1.5 : 1) * props.datas.length + 1 } - const maybe_scroll = (contents: React.ReactNode) => { + const maybe_scroll = (contents: React.ReactNode): ReactNode => { if (width_columns() > max_columns()) { - return
-
- {contents} + return ( +
+
+ {contents} +
-
+ ) } - return contents; + return contents } - return { (template_info) => -
-
Comparison
-
{props.joined_string}
-
- -
-
-
-
Add another region:
-
-
- add_new(props.names, x)} - autoFocus={false} - /> -
-
- - -
- - {maybe_scroll( -
- {bars()} -
- {cell(true, 0,
)} - {props.datas.map( - (data, i) => cell(false, i,
- 1} - on_click={() => on_delete(props.names, i)} - on_change={(x) => on_change(props.names, i, x)} - screenshot_mode={template_info.screenshot_mode} - /> -
) - )} + return ( + + { template_info => ( +
+
Comparison
+
{props.joined_string}
+
+ +
+
+
+
Add another region:
+
+
+ { add_new(props.names, x) }} + autoFocus={false} + /> +
- {bars()} - +
+ + {maybe_scroll( +
+ {bars()} +
+ {cell(true, 0,
)} + {props.datas.map( + (data, i) => cell(false, i, +
+ 1} + on_click={() => { on_delete(props.names, i) }} + on_change={(x) => { on_change(props.names, i, x) }} + screenshot_mode={template_info.screenshot_mode} + /> +
, + ), + )} +
+ {bars()} + + +
, + )} +
+ +
+ x.longname)} + colors={props.datas.map((_, i) => color(i))} + basemap={{ type: 'osm' }} + /> +
)} -
- -
- x.longname)} - colors={props.datas.map((_, i) => color(i))} - basemap={{ type: "osm" }} - /> -
-
- }
+ + ) } -function color(i: number) { - return COLOR_CYCLE[i % COLOR_CYCLE.length]; +function color(i: number): string { + return COLOR_CYCLE[i % COLOR_CYCLE.length] } - -function on_change(names: string[] | undefined, i: number, x: string) { - if (names == undefined) { - throw new Error("names is undefined"); +function on_change(names: string[] | undefined, i: number, x: string): void { + if (names === undefined) { + throw new Error('names is undefined') } - const new_names = [...names]; - new_names[i] = x; - go(new_names); + const new_names = [...names] + new_names[i] = x + go(new_names) } -function on_delete(names: string[], i: number) { - const new_names = [...names]; - new_names.splice(i, 1); - go(new_names); +function on_delete(names: string[], i: number): void { + const new_names = [...names] + new_names.splice(i, 1) + go(new_names) } -function add_new(names: string[], x: string) { - const new_names = [...names]; - new_names.push(x); - go(new_names); +function add_new(names: string[], x: string): void { + const new_names = [...names] + new_names.push(x) + go(new_names) } -function go(names: string[]) { - const window_info = new URLSearchParams(window.location.search); - window_info.set("longnames", JSON.stringify(names)); - window.location.search = window_info.toString(); +function go(names: string[]): void { + const window_info = new URLSearchParams(window.location.search) + window_info.set('longnames', JSON.stringify(names)) + window.location.search = window_info.toString() } -function each(datas: Article[]) { - return 100 * (1 - left_margin_pct) / datas.length; +function each(datas: Article[]): number { + return 100 * (1 - left_margin_pct) / datas.length } -function all_data_types_same(datas: Article[]) { - return datas.every(x => x.articleType == datas[0].articleType) +function all_data_types_same(datas: Article[]): boolean { + return datas.every(x => x.articleType === datas[0].articleType) } - -function ComparsionPageRows({ names, datas, screenshot_mode }: { names: string[], datas: Article[], screenshot_mode: boolean }) { - const curr_universe = useUniverse(); - let rows: ArticleRow[][] = []; - const idxs: number[][] = []; - const exclusively_american = datas.every(x => longname_is_exclusively_american(x.longname)); - for (const i in datas) { - const [r, idx] = load_article(curr_universe, datas[i], useTableCheckboxSettings(), - exclusively_american); - rows.push(r); - idxs.push(idx); +function ComparsionPageRows({ names, datas, screenshot_mode }: { names: string[], datas: Article[], screenshot_mode: boolean }): ReactNode { + const curr_universe = useUniverse() + let rows: ArticleRow[][] = [] + const idxs: number[][] = [] + const exclusively_american = datas.every(x => longname_is_exclusively_american(x.longname)) + const settings = useTableCheckboxSettings() + for (const i of datas.keys()) { + const [r, idx] = load_article(curr_universe, datas[i], settings, + exclusively_american) + rows.push(r) + idxs.push(idx) } - rows = insert_missing(rows, idxs); + rows = insert_missing(rows, idxs) - const header_row = { return { is_header: true } }} - datas={datas} - names={names} - screenshot_mode={screenshot_mode} - />; + const header_row = ( + { return { is_header: true } }} + datas={datas} + names={names} + screenshot_mode={screenshot_mode} + /> + ) return ( <> { - rows[0].map((_, row_idx) => + rows[0].map((_, row_idx) => ( + ), ) } @@ -241,224 +260,246 @@ function ComparsionPageRows({ names, datas, screenshot_mode }: { names: string[] } function ComparisonRowBody({ rows, row_idx, datas, names, screenshot_mode }: { - rows: ArticleRow[][], - row_idx: number, - datas: Article[], - names: string[], + rows: ArticleRow[][] + row_idx: number + datas: Article[] + names: string[] screenshot_mode: boolean -}) { - const [expanded] = useSetting(row_expanded_key(rows[0][row_idx].statname)); - const contents = { - return { - key: row_idx, index: row_idx, ...rows[data_idx][row_idx], is_header: false - } - }} - datas={datas} - names={names} - screenshot_mode={screenshot_mode} - />; - const plot_props = rows.map((row, data_idx) => ({...row[row_idx], color: color(data_idx), shortname: datas[data_idx].shortname})); - return - - +}): ReactNode { + const [expanded] = useSetting(row_expanded_key(rows[0][row_idx].statname)) + const contents = ( + { + return { + key: row_idx, index: row_idx, ...rows[data_idx][row_idx], is_header: false, + } + }} + datas={datas} + names={names} + screenshot_mode={screenshot_mode} + /> + ) + const plot_props = rows.map((row, data_idx) => ({ ...row[row_idx], color: color(data_idx), shortname: datas[data_idx].shortname })) + return ( + + + + ) } function ComparisonRow({ names, params, datas, screenshot_mode }: { - names: string[], - params: (i: number) => { is_header: true } | ({ is_header: false, key: number, index: number } & ArticleRow), - datas: Article[], + names: string[] + params: (i: number) => { is_header: true } | ({ is_header: false, key: number, index: number } & ArticleRow) + datas: Article[] screenshot_mode: boolean -}) { - if (names == undefined) { - throw new Error("ComparisonRow: names is undefined"); - } - const row_overall = []; - const param_vals = Array.from(Array(datas.length).keys()).map(params); +}): ReactNode { + const row_overall = [] + const param_vals = Array.from(Array(datas.length).keys()).map(params) - const highlight_idx = param_vals.map(x => 'statval' in x ? x.statval: NaN).reduce((iMax, x, i, arr) => { + const highlight_idx = param_vals.map(x => 'statval' in x ? x.statval : NaN).reduce((iMax, x, i, arr) => { if (isNaN(x)) { - return iMax; + return iMax } - if (iMax == -1) { - return i; + if (iMax === -1) { + return i } return x > arr[iMax] ? i : iMax - }, -1); + }, -1) row_overall.push( -
+
-
+ backgroundColor: highlight_idx === -1 ? '#fff8f0' : color(highlight_idx), + height: '100%', + width: '50%', + margin: 'auto', + }} + /> +
, ) row_overall.push(...StatisticRowRawCellContents( { - ...param_vals[0], only_columns: ["statname"], _idx: -1, simple: true, longname: datas[0].longname, + ...param_vals[0], only_columns: ['statname'], _idx: -1, simple: true, longname: datas[0].longname, total_width: 100 * (left_margin_pct - left_bar_margin), - screenshot_mode: screenshot_mode - } - )); - const only_columns = all_data_types_same(datas) ? main_columns : main_columns_across_types; + screenshot_mode, + }, + )) + const only_columns = all_data_types_same(datas) ? main_columns : main_columns_across_types for (const i of datas.keys()) { row_overall.push(...StatisticRowRawCellContents( { - ...param_vals[i], only_columns: only_columns, _idx: i, simple: true, - statistic_style: highlight_idx == i ? { backgroundColor: lighten(color(i), 0.7) } : {}, - onReplace: x => on_change(names, i, x), + ...param_vals[i], only_columns, _idx: i, simple: true, + statistic_style: highlight_idx === i ? { backgroundColor: lighten(color(i), 0.7) } : {}, + onReplace: (x) => { on_change(names, i, x) }, total_width: each(datas), - screenshot_mode: screenshot_mode - } - )); + screenshot_mode, + }, + )) } - return row_overall; + return row_overall } -const manipulation_button_height = "24px"; - -function ManipulationButton({ color, on_click, text }: { color: string, on_click: () => void, text: React.ReactNode }) { - return
- {text} -
-} +const manipulation_button_height = '24px' -function HeadingDisplay({ longname, include_delete, on_click, on_change, screenshot_mode }: { longname: string, include_delete: boolean, on_click: () => void, on_change: (q: string) => void, screenshot_mode: boolean }) { +function ManipulationButton({ color: buttonColor, on_click, text }: { color: string, on_click: () => void, text: string }): ReactNode { + return ( +
+ {text} +
+ ) +} - const [is_editing, set_is_editing] = React.useState(false); - const curr_universe = useUniverse(); +function HeadingDisplay({ longname, include_delete, on_click, on_change: on_search_change, screenshot_mode }: { longname: string, include_delete: boolean, on_click: () => void, on_change: (q: string) => void, screenshot_mode: boolean }): ReactNode { + const [is_editing, set_is_editing] = React.useState(false) + const curr_universe = useUniverse() + + const manipulation_buttons = ( +
+
+ { set_is_editing(!is_editing) }} text="replace" /> + {!include_delete + ? null + : ( + <> +
+ + + )} +
+
+
+ ) - const manipulation_buttons =
-
- set_is_editing(!is_editing)} text="replace" /> - {!include_delete ? null : - <> -
- - - } -
+ return ( +
+ {screenshot_mode ? undefined : manipulation_buttons} +
+
{longname}
+ {is_editing + ? ( + + ) + : null}
-
- - return
- {screenshot_mode ? undefined : manipulation_buttons} -
-
{longname}
- {is_editing ? - - : null} -
+ ) } -function insert_missing(rows: ArticleRow[][], idxs: number[][]) { - const empty_row_example: Record = {}; - for (const data_i in rows) { - for (const row_i in rows[data_i]) { - const idx = idxs[data_i][row_i]; - empty_row_example[idx] = JSON.parse(JSON.stringify(rows[data_i][row_i])); +function insert_missing(rows: ArticleRow[][], idxs: number[][]): ArticleRow[][] { + const empty_row_example: Record = {} + for (const data_i of rows.keys()) { + for (const row_i of rows[data_i].keys()) { + const idx = idxs[data_i][row_i] + empty_row_example[idx] = JSON.parse(JSON.stringify(rows[data_i][row_i])) as typeof rows[number][number] for (const key of Object.keys(empty_row_example[idx]) as (keyof ArticleRow)[]) { - if (typeof empty_row_example[idx][key] === "number") { + if (typeof empty_row_example[idx][key] === 'number') { // @ts-expect-error Typescript is fucking up this assignment - empty_row_example[idx][key] = NaN; - } else if (key == "extra_stat") { - empty_row_example[idx][key] = undefined; + empty_row_example[idx][key] = NaN + } + else if (key === 'extra_stat') { + empty_row_example[idx][key] = undefined } } - empty_row_example[idx].article_type = "none"; // doesn't matter since we are using simple mode + empty_row_example[idx].article_type = 'none' // doesn't matter since we are using simple mode } } - const all_idxs = idxs.flat().filter((x, i, a) => a.indexOf(x) == i); + const all_idxs = idxs.flat().filter((x, i, a) => a.indexOf(x) === i) // sort all_idxs in ascending order numerically - all_idxs.sort((a, b) => a - b); + all_idxs.sort((a, b) => a - b) - const new_rows_all = []; - for (const data_i in rows) { - const new_rows = []; + const new_rows_all = [] + for (const data_i of rows.keys()) { + const new_rows = [] for (const idx of all_idxs) { if (idxs[data_i].includes(idx)) { - const index_to_pull = idxs[data_i].findIndex(x => x == idx); - new_rows.push(rows[data_i][index_to_pull]); - } else { - new_rows.push(empty_row_example[idx]); + const index_to_pull = idxs[data_i].findIndex(x => x === idx) + new_rows.push(rows[data_i][index_to_pull]) + } + else { + new_rows.push(empty_row_example[idx]) } } - new_rows_all.push(new_rows); + new_rows_all.push(new_rows) } - return new_rows_all; + return new_rows_all } +// eslint-disable-next-line prefer-function-component/prefer-function-component -- TODO: Maps don't support function components yet. class ComparisonMap extends MapGeneric { - - buttons() { - return
- Zoom to: -
- {this.zoom_button(-1, "black", () => this.zoom_to_all())} - {this.props.longnames.map((longname, i) => { - return this.zoom_button(i, this.props.colors[i], () => this.zoom_to(longname)) - })} -
+ override buttons(): ReactNode { + return ( +
+ Zoom to: +
+ {this.zoom_button(-1, 'black', () => { this.zoom_to_all() })} + {this.props.longnames.map((longname, i) => { + return this.zoom_button(i, this.props.colors[i], () => { this.zoom_to(longname) }) + })} +
+ ) } - zoom_button(i: number, color: string, onClick: () => void) { - return
+ zoom_button(i: number, buttonColor: string, onClick: () => void): ReactNode { + return ( +
+ ) } - async compute_polygons(): Promise[], Record[], number]>> { - const names = []; - const styles = []; + override compute_polygons(): Promise { + const names = [] + const styles = [] - for (const i in this.props.longnames) { - names.push(this.props.longnames[i]); - styles.push({ color: this.props.colors[i], fillColor: this.props.colors[i], "fillOpacity": 0.5, "weight": 1 }); + for (const i of this.props.longnames.keys()) { + names.push(this.props.longnames[i]) + styles.push({ color: this.props.colors[i], fillColor: this.props.colors[i], fillOpacity: 0.5, weight: 1 }) } - const zoom_index = -1; + const zoom_index = -1 - const metas = names.map(() => { return {} }); + const metas = names.map(() => { return {} }) - return [names, styles, metas, zoom_index]; + return Promise.resolve([names, styles, metas, zoom_index]) } - async mapDidRender() { - console.log("mapDidRender") - this.zoom_to_all(); + override mapDidRender(): Promise { + this.zoom_to_all() + return Promise.resolve() } -} \ No newline at end of file +} diff --git a/react/src/components/hamburger.tsx b/react/src/components/hamburger.tsx index 9a1d501f..f10973b6 100644 --- a/react/src/components/hamburger.tsx +++ b/react/src/components/hamburger.tsx @@ -1,14 +1,10 @@ // based on https://khuang159.medium.com/creating-a-hamburger-menu-in-react-f22e5ae442cb -export { Nav }; +import React, { ReactNode } from 'react' -import React from 'react'; +import '../common.css' -import "../common.css"; - - - -function Hamburger({ isOpen }: { isOpen: boolean }) { +function Hamburger({ isOpen }: { isOpen: boolean }): ReactNode { return ( <>
@@ -17,7 +13,8 @@ function Hamburger({ isOpen }: { isOpen: boolean }) {
- + `} + ) } -function Nav({ hamburger_open, set_hamburger_open }: { hamburger_open: boolean, set_hamburger_open: (newValue: boolean) => void }) { - - const toggleHamburger = () => { +export function Nav({ hamburger_open, set_hamburger_open }: { hamburger_open: boolean, set_hamburger_open: (newValue: boolean) => void }): ReactNode { + const toggleHamburger = (): void => { set_hamburger_open(!hamburger_open) } @@ -70,8 +67,8 @@ function Nav({ hamburger_open, set_hamburger_open }: { hamburger_open: boolean,
- - + `} +
) - -} \ No newline at end of file +} diff --git a/react/src/components/header.tsx b/react/src/components/header.tsx index cabf5317..888f3229 100644 --- a/react/src/components/header.tsx +++ b/react/src/components/header.tsx @@ -1,27 +1,28 @@ -import React from 'react'; +import React, { ReactNode } from 'react' -import "../common.css"; -import "./header.css"; -import { SearchBox } from './search'; -import { Nav } from './hamburger'; -import { mobileLayout } from '../utils/responsive'; -import { ScreenshotButton } from './screenshot'; -import { article_link, universe_path } from '../navigation/links'; -import { set_universe, useUniverse } from '../universe'; +import '../common.css' +import './header.css' +import { article_link, universe_path } from '../navigation/links' +import { set_universe, useUniverse } from '../universe' +import { mobileLayout } from '../utils/responsive' -export const HEADER_BAR_SIZE = "48px"; -const HEADER_BAR_SIZE_DESKTOP = "60px"; +import { Nav } from './hamburger' +import { ScreenshotButton } from './screenshot' +import { SearchBox } from './search' + +export const HEADER_BAR_SIZE = '48px' +const HEADER_BAR_SIZE_DESKTOP = '60px' export function Header(props: { - hamburger_open: boolean, - set_hamburger_open: (newValue: boolean) => void, - has_universe_selector: boolean, - all_universes: string[], - has_screenshot: boolean, - screenshot_mode: boolean, + hamburger_open: boolean + set_hamburger_open: (newValue: boolean) => void + has_universe_selector: boolean + all_universes: string[] + has_screenshot: boolean + screenshot_mode: boolean initiate_screenshot: (curr_universe: string) => void -}) { - const curr_universe = useUniverse(); +}): ReactNode { + const curr_universe = useUniverse() return (
{/* flex but stretch to fill */} -
+
{!mobileLayout() && props.has_universe_selector - ?
- -
+ ? ( +
+ +
+ ) : undefined} { - props.has_screenshot ? - props.initiate_screenshot(curr_universe)} - /> : undefined + props.has_screenshot + ? ( + { props.initiate_screenshot(curr_universe) }} + /> + ) + : undefined }
{ + (new_location) => { window.location.href = article_link( - curr_universe, new_location + curr_universe, new_location, ) } } placeholder="Search Urban Stats" style={{ - fontSize: "30px", - border: "1px solid #444", - paddingLeft: "1em", - width: "100%", - verticalAlign: "middle", + fontSize: '30px', + border: '1px solid #444', + paddingLeft: '1em', + width: '100%', + verticalAlign: 'middle', height: HEADER_BAR_SIZE, }} autoFocus={false} @@ -76,88 +82,109 @@ export function Header(props: { } function TopLeft(props: { - hamburger_open: boolean, - set_hamburger_open: (newValue: boolean) => void, - has_universe_selector: boolean, - all_universes: string[], -}) { + hamburger_open: boolean + set_hamburger_open: (newValue: boolean) => void + has_universe_selector: boolean + all_universes: string[] +}): ReactNode { if (mobileLayout()) { return (
- ); - } else { + ) + } + else { return (
- ); + ) } } -function HeaderImage() { - const path = mobileLayout() ? "/thumbnail.png" : "/banner.png"; +function HeaderImage(): ReactNode { + const path = mobileLayout() ? '/thumbnail.png' : '/banner.png' return ( - Urban Stats Logo + + Urban Stats Logo + ) } function UniverseSelector( - { all_universes } - : { all_universes: string[] } -) { - const curr_universe = useUniverse(); + { all_universes }: { all_universes: string[] }, +): ReactNode { + const curr_universe = useUniverse() // button to select universe. Image is icons/flags/${universe}.png // when clicked, a dropdown appears with all universes, labeled by their flags - const width = HEADER_BAR_SIZE; + const width = HEADER_BAR_SIZE - const [dropdown_open, set_dropdown_open] = React.useState(false); + const [dropdown_open, set_dropdown_open] = React.useState(false) - let dropdown = dropdown_open ? : undefined; + let dropdown = dropdown_open + ? ( + + ) + : undefined // wrap dropdown in a div to place it in front of everything else and let it spill out of the header // do NOT use class - dropdown =
{dropdown}
; + dropdown = ( +
+ {dropdown} +
+ ) return ( -
+
- {curr_universe} + {curr_universe} set_dropdown_open(!dropdown_open)} + onClick={() => { set_dropdown_open(!dropdown_open) }} />
{dropdown} @@ -166,43 +193,47 @@ function UniverseSelector( } function UniverseDropdown( - { all_universes, flag_size } - : { all_universes: string[], flag_size: string } -) { + { all_universes, flag_size }: { all_universes: string[], flag_size: string }, +): ReactNode { return (
-
Select universe for statistics
- {all_universes.map(alt_universe => { + {all_universes.map((alt_universe) => { return ( -
set_universe(alt_universe)}> -
{ set_universe(alt_universe) }}> +
- {alt_universe}
- {alt_universe == "world" ? "World" : alt_universe} + {alt_universe === 'world' ? 'World' : alt_universe}
) })} -
+
) -} \ No newline at end of file +} diff --git a/react/src/components/load-article.ts b/react/src/components/load-article.ts index 2d2b96fb..9fc19357 100644 --- a/react/src/components/load-article.ts +++ b/react/src/components/load-article.ts @@ -1,104 +1,103 @@ -import { TableCheckboxSettings } from "../page_template/settings"; -import { universe_is_american } from "../universe"; -import { Article, IExtraStatistic } from "../utils/protos"; +import { TableCheckboxSettings } from '../page_template/settings' +import { universe_is_american } from '../universe' +import { Article, IExtraStatistic } from '../utils/protos' export interface ExtraStat { - stat: IExtraStatistic, + stat: IExtraStatistic universe_total: number } export interface ArticleRow { - statval: number, - ordinal: number, - overallOrdinal: number, - percentile_by_population: number, - statistic_category: string, + statval: number + ordinal: number + overallOrdinal: number + percentile_by_population: number + statistic_category: string statcol: string - statname: string, - statpath: string, - explanation_page: string, - article_type: string, - total_count_in_class: number, - total_count_overall: number, - _index: number, - rendered_statname: string, + statname: string + statpath: string + explanation_page: string + article_type: string + total_count_in_class: number + total_count_overall: number + _index: number + rendered_statname: string extra_stat?: ExtraStat } -const index_list_info = require("../data/index_lists.json") as { +const index_list_info = require('../data/index_lists.json') as { index_lists: { - universal: number[]; - gpw: number[]; - usa: number[]; - }, + universal: number[] + gpw: number[] + usa: number[] + } type_to_has_gpw: Record } -function lookup_in_compressed_sequence(seq: [number, number][], idx: number) { +function lookup_in_compressed_sequence(seq: [number, number][], idx: number): number { // translation of produce_html_page.py::lookup_in_compressed_sequence for (const [value, length] of seq) { if (idx < length) { - return value; + return value } - idx -= length; + idx -= length } - throw new Error("Index out of bounds"); + throw new Error('Index out of bounds') } -export function for_type(universe: string, statcol: string, typ: string) : number { - const statnames = require("../data/statistic_list.json") as string[]; - const idx = statnames.indexOf(statcol); - const counts_by_universe = require("../data/counts_by_article_type.json") as Record>; - const counts_by_type = counts_by_universe[universe][typ]; +export function for_type(universe: string, statcol: string, typ: string): number { + const statnames = require('../data/statistic_list.json') as string[] + const idx = statnames.indexOf(statcol) + const counts_by_universe = require('../data/counts_by_article_type.json') as Record> + const counts_by_type = counts_by_universe[universe][typ] - return lookup_in_compressed_sequence(counts_by_type, idx); + return lookup_in_compressed_sequence(counts_by_type, idx) } -function compute_indices(longname: string, typ: string) { +function compute_indices(longname: string, typ: string): number[] { // translation of produce_html_page.py::indices - const lists = index_list_info["index_lists"]; - let result: number[] = []; - result = result.concat(lists["universal"]); - if (index_list_info["type_to_has_gpw"][typ]) { - result = result.concat(lists["gpw"]); + const lists = index_list_info.index_lists + let result: number[] = [] + result = result.concat(lists.universal) + if (index_list_info.type_to_has_gpw[typ]) { + result = result.concat(lists.gpw) } // else { - if (longname.includes("USA")) { - result = result.concat(lists["usa"]); + if (longname.includes('USA')) { + result = result.concat(lists.usa) } // sort result by numeric value - return result.sort((a, b) => a - b); + return result.sort((a, b) => a - b) } -export function load_article(universe: string, data: Article, settings: TableCheckboxSettings, exclusively_american: boolean) { - +export function load_article(universe: string, data: Article, settings: TableCheckboxSettings, exclusively_american: boolean): readonly [ArticleRow[], number[]] { // index of universe in data.universes - const universe_index = data.universes.indexOf(universe); - const article_type = data.articleType; + const universe_index = data.universes.indexOf(universe) + const article_type = data.articleType - const categories = require("../data/statistic_category_list.json") as string[]; - const names = require("../data/statistic_name_list.json") as string[]; - const paths = require("../data/statistic_path_list.json") as string[]; - const stats = require("../data/statistic_list.json") as string[]; - const explanation_page = require("../data/explanation_page.json") as string[]; + const categories = require('../data/statistic_category_list.json') as string[] + const names = require('../data/statistic_name_list.json') as string[] + const paths = require('../data/statistic_path_list.json') as string[] + const stats = require('../data/statistic_list.json') as string[] + const explanation_page = require('../data/explanation_page.json') as string[] - const extra_stats = require("../data/extra_stats.json") as [number, number][]; - const extra_stat_idx_to_col = extra_stats.map((xy) => xy[0]); + const extra_stats = require('../data/extra_stats.json') as [number, number][] + const extra_stat_idx_to_col = extra_stats.map(xy => xy[0]) - const indices = compute_indices(data.longname, article_type) as number[]; + const indices = compute_indices(data.longname, article_type) const modified_rows: ArticleRow[] = data.rows.map((row_original, row_index) => { - const i = indices[row_index]; + const i = indices[row_index] // fresh row object - let extra_stat: ExtraStat | undefined = undefined; + let extra_stat: ExtraStat | undefined = undefined if (extra_stat_idx_to_col.includes(i)) { - const extra_stat_idx = extra_stat_idx_to_col.indexOf(i); - const [, universe_total_idx] = extra_stats[extra_stat_idx]; + const extra_stat_idx = extra_stat_idx_to_col.indexOf(i) + const [, universe_total_idx] = extra_stats[extra_stat_idx] extra_stat = { stat: data.extraStats[extra_stat_idx], - universe_total: data.rows.filter((row, row_index) => indices[row_index] === universe_total_idx)[0].statval! - }; + universe_total: data.rows.find((_, universe_row_index) => indices[universe_row_index] === universe_total_idx)!.statval!, + } } return { statval: row_original.statval!, @@ -110,36 +109,37 @@ export function load_article(universe: string, data: Article, settings: TableChe statname: names[i], statpath: paths[i], explanation_page: explanation_page[i], - article_type: article_type, + article_type, total_count_in_class: for_type(universe, stats[i], article_type), - total_count_overall: for_type(universe, stats[i], "overall"), + total_count_overall: for_type(universe, stats[i], 'overall'), _index: i, rendered_statname: render_statname(i, names[i], exclusively_american), - extra_stat: extra_stat + extra_stat, } satisfies ArticleRow }) const filtered_rows = modified_rows.filter((row) => { if (universe_is_american(universe)) { - if (index_list_info["index_lists"]["gpw"].includes(indices[row._index])) { - return false; + if (index_list_info.index_lists.gpw.includes(indices[row._index])) { + return false } - } else { - if (index_list_info["index_lists"]["usa"].includes(indices[row._index])) { - return false; + } + else { + if (index_list_info.index_lists.usa.includes(indices[row._index])) { + return false } } - return settings[`show_statistic_${row.statistic_category}`]; - }); + return settings[`show_statistic_${row.statistic_category}`] + }) const filtered_indices = filtered_rows.map(x => x._index) - return [filtered_rows, filtered_indices] as const; + return [filtered_rows, filtered_indices] as const } export function render_statname(statindex: number, statname: string, exclusively_american: boolean): string { - const usa_stat = index_list_info["index_lists"]["usa"].includes(statindex); + const usa_stat = index_list_info.index_lists.usa.includes(statindex) if (!exclusively_american && usa_stat) { - return statname + " (USA only)"; + return `${statname} (USA only)` } - return statname; -} \ No newline at end of file + return statname +} diff --git a/react/src/components/map.tsx b/react/src/components/map.tsx index bc2c0d35..5a8848e8 100644 --- a/react/src/components/map.tsx +++ b/react/src/components/map.tsx @@ -1,89 +1,91 @@ -import React from 'react'; +import './map.css' -export { MapComponent as Map, MapGeneric }; - -import { shape_link, article_link } from "../navigation/links"; -import { random_color } from "../utils/color"; - -import "./map.css"; -import { is_historical_cd } from '../utils/is_historical'; -import { loadProtobuf } from '../load_json'; -import { GeoJSON2SVG } from 'geojson2svg'; +import { GeoJSON2SVG } from 'geojson2svg' import L from 'leaflet' -import { NormalizeProto } from "../utils/types"; -import { Feature, IRelatedButton, IRelatedButtons } from "../utils/protos"; -import { Basemap } from "../mapper/settings"; -import { relationship_key, useRelatedCheckboxSettings, useSetting } from '../page_template/settings'; -import { UNIVERSE_CONTEXT } from '../universe'; +import React, { ReactNode } from 'react' + +import { loadProtobuf } from '../load_json' +import { Basemap } from '../mapper/settings' +import { article_link, shape_link } from '../navigation/links' +import { relationship_key, useRelatedCheckboxSettings, useSetting } from '../page_template/settings' +import { UNIVERSE_CONTEXT } from '../universe' +import { random_color } from '../utils/color' +import { is_historical_cd } from '../utils/is_historical' +import { Feature, IRelatedButton, IRelatedButtons } from '../utils/protos' +import { NormalizeProto } from '../utils/types' export interface MapGenericProps { - height?: string, - basemap: Basemap, + height?: string + basemap: Basemap } -class MapGeneric

extends React.Component

{ - private polygon_by_name = new Map(); - private delta = 0.25; - private version = 0; - private last_modified = Date.now(); - private basemap_layer: null | L.TileLayer = null; - private basemap_props: null | Basemap = null; +export type Polygons = Readonly<[string[], Record[], Record[], number]> + +// eslint-disable-next-line prefer-function-component/prefer-function-component -- TODO: Maps don't support function components yet. +export class MapGeneric

extends React.Component

{ + private polygon_by_name = new Map() + private delta = 0.25 + private version = 0 + private last_modified = Date.now() + private basemap_layer: null | L.TileLayer = null + private basemap_props: null | Basemap = null protected map: L.Map | undefined = undefined private exist_this_time: string[] = [] - private id: string; + private id: string constructor(props: P) { - super(props); - this.id = "map-" + Math.random().toString(36).substring(2); + super(props) + this.id = `map-${Math.random().toString(36).substring(2)}` } - render() { + override render(): ReactNode { return (

-
+
{/* place this on the right of the map */}
+ { zIndex: 1000, position: 'absolute', right: 0, top: 0, padding: '1em' } + } + > {this.buttons()}
- ); + ) } - buttons() { + buttons(): ReactNode { return <> } - async compute_polygons(): Promise[], Record[], number]>> { + compute_polygons(): Promise { /** * Should return [names, styles, metas, zoom_index] * names: list of names of polygons to draw * styles: list of styles for each polygon * metas: list of metadata dictionaries for each polygon - * zoom_index: index of polygon to zoom to, or -1 if none + * zoom_index: index of polygon to zoom to, or -1 if none */ - throw "compute_polygons not implemented"; + throw new Error('compute_polygons not implemented') } - async mapDidRender() { - /** + async mapDidRender(): Promise { + /** * Called after the map is rendered */ } async loadShape(name: string): Promise> { - return await loadProtobuf(shape_link(name), "Feature") as NormalizeProto + return await loadProtobuf(shape_link(name), 'Feature') as NormalizeProto } - async componentDidMount() { + override async componentDidMount(): Promise { const map = new L.Map(this.id, { layers: [], center: new L.LatLng(0, 0), zoom: 0, - zoomSnap: this.delta, zoomDelta: this.delta, wheelPxPerZoomLevel: 60 / this.delta - }); - this.map = map; - await this.componentDidUpdate(); + zoomSnap: this.delta, zoomDelta: this.delta, wheelPxPerZoomLevel: 60 / this.delta, + }) + this.map = map + await this.componentDidUpdate() } /** @@ -91,57 +93,60 @@ class MapGeneric

extends React.Component

{ * * @returns string svg */ - async exportAsSvg() { - const [names, styles] = await this.compute_polygons(); - const map_bounds = this.map!.getBounds(); + async exportAsSvg(): Promise { + const [names, styles] = await this.compute_polygons() + const map_bounds = this.map!.getBounds() const bounds = { left: map_bounds.getWest(), right: map_bounds.getEast(), top: map_bounds.getNorth(), bottom: map_bounds.getSouth(), } - const width = 1000; - const height = width * (bounds.top - bounds.bottom) / (bounds.right - bounds.left); + const width = 1000 + const height = width * (bounds.top - bounds.bottom) / (bounds.right - bounds.left) const converter = new GeoJSON2SVG({ mapExtent: bounds, attributes: [{ property: 'style', type: 'dynamic', - key: 'style' + key: 'style', }], viewportSize: { - width: width, - height: height, + width, + height, }, - }); - - function toSvgStyle(style: Record & { weight?: number }) { - let svg_style = ""; - for (const key in style) { - if (key == "fillColor") { - svg_style += `fill:${style[key]};`; - continue; - } else if (key == "fillOpacity") { - svg_style += `fill-opacity:${style[key]};`; - continue; - } else if (key == "color") { - svg_style += `stroke:${style[key]};`; - continue; - } else if (key == "weight") { - svg_style += `stroke-width:${style[key]! / 10};`; - continue; + }) + + function toSvgStyle(style: Record & { weight?: number }): string { + let svg_style = '' + for (const key of Object.keys(style)) { + if (key === 'fillColor') { + svg_style += `fill:${style[key]};` + continue + } + else if (key === 'fillOpacity') { + svg_style += `fill-opacity:${style[key]};` + continue + } + else if (key === 'color') { + svg_style += `stroke:${style[key]};` + continue + } + else if (key === 'weight') { + svg_style += `stroke-width:${style[key]! / 10};` + continue } - svg_style += `${key}:${style[key]};`; + svg_style += `${key}:${style[key]};` } - return svg_style; + return svg_style } - const overall_svg = []; + const overall_svg = [] for (let i = 0; i < names.length; i++) { - const geojson = await this.polygon_geojson(names[i]); - const svg = converter.convert(geojson, { attributes: { style: toSvgStyle(styles[i]) } }); + const geojson = await this.polygon_geojson(names[i]) + const svg = converter.convert(geojson, { attributes: { style: toSvgStyle(styles[i]) } }) for (const elem of svg) { - overall_svg.push(elem); + overall_svg.push(elem) } } const header = ` @@ -154,264 +159,260 @@ class MapGeneric

extends React.Component

{ id="svg1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" - xmlns:svg="http://www.w3.org/2000/svg">`; - const footer = ""; - return header + overall_svg.join("") + footer; + xmlns:svg="http://www.w3.org/2000/svg">` + const footer = '' + return header + overall_svg.join('') + footer } - async exportAsGeoJSON() { - console.log("EXPORT AS GEOJSON") - const [names, , metas] = await this.compute_polygons(); + async exportAsGeoJSON(): Promise { + const [names, , metas] = await this.compute_polygons() const geojson: GeoJSON.FeatureCollection = { - "type": "FeatureCollection", - "features": [], - }; + type: 'FeatureCollection', + features: [], + } for (let i = 0; i < names.length; i++) { - let feature = await this.polygon_geojson(names[i]); - feature = JSON.parse(JSON.stringify(feature)); - for (const key in metas[i]) { - feature.properties![key] = metas[i][key]; + let feature = await this.polygon_geojson(names[i]) + feature = JSON.parse(JSON.stringify(feature)) as typeof feature + for (const key of Object.keys(metas[i])) { + feature.properties![key] = metas[i][key] } - geojson.features.push(feature); + geojson.features.push(feature) } - return JSON.stringify(geojson); + return JSON.stringify(geojson) } - async componentDidUpdate() { - await this.updateToVersion(this.version + 1); + override async componentDidUpdate(): Promise { + await this.updateToVersion(this.version + 1) } - async updateToVersion(version: number) { + async updateToVersion(version: number): Promise { if (version <= this.version) { - return; + return } // check if at least 1s has passed since last update const now = Date.now() - const delta = now - this.last_modified; + const delta = now - this.last_modified if (delta < 1000) { - setTimeout(() => this.updateToVersion(version), 1000 - delta); - return; + setTimeout(() => this.updateToVersion(version), 1000 - delta) + return } - this.version = version; - this.last_modified = now; - await this.updateFn(); + this.version = version + this.last_modified = now + await this.updateFn() } - async updateFn() { - const map = this.map!; - this.exist_this_time = []; + async updateFn(): Promise { + const map = this.map! + this.exist_this_time = [] - this.attachBasemap(); + this.attachBasemap() - const [names, styles, , zoom_index] = await this.compute_polygons(); + const [names, styles, , zoom_index] = await this.compute_polygons() - await this.add_polygons(map, names, styles, zoom_index); + await this.add_polygons(map, names, styles, zoom_index) - await this.mapDidRender(); + await this.mapDidRender() // Remove polygons that no longer exist for (const [name, polygon] of this.polygon_by_name.entries()) { if (!this.exist_this_time.includes(name)) { - map.removeLayer(polygon); - this.polygon_by_name.delete(name); + map.removeLayer(polygon) + this.polygon_by_name.delete(name) } } } - attachBasemap() { - if (JSON.stringify(this.props.basemap) == JSON.stringify(this.basemap_props)) { - return; + attachBasemap(): void { + if (JSON.stringify(this.props.basemap) === JSON.stringify(this.basemap_props)) { + return } - this.basemap_props = this.props.basemap; - if (this.basemap_layer != null) { - this.map!.removeLayer(this.basemap_layer); - this.basemap_layer = null; + this.basemap_props = this.props.basemap + if (this.basemap_layer !== null) { + this.map!.removeLayer(this.basemap_layer) + this.basemap_layer = null } - if (this.props.basemap.type == "none") { - return; + if (this.props.basemap.type === 'none') { + return } - const osmUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; - const osmAttrib = '© OpenStreetMap contributors'; - this.basemap_layer = L.tileLayer(osmUrl, { maxZoom: 20, attribution: osmAttrib }); - this.map!.addLayer(this.basemap_layer); + const osmUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' + const osmAttrib = '© OpenStreetMap contributors' + this.basemap_layer = L.tileLayer(osmUrl, { maxZoom: 20, attribution: osmAttrib }) + this.map!.addLayer(this.basemap_layer) } - async add_polygons(map: L.Map, names: string[], styles: Record[], zoom_to: number) { + async add_polygons(map: L.Map, names: string[], styles: Record[], zoom_to: number): Promise { for (let i = 0; i < names.length; i++) { - await this.add_polygon(map, names[i], i == zoom_to, styles[i]); + await this.add_polygon(map, names[i], i === zoom_to, styles[i]) } } async polygon_geojson(name: string): Promise { // https://stackoverflow.com/a/35970894/1549476 - const poly = await this.loadShape(name); + const poly = await this.loadShape(name) let geometry: GeoJSON.Geometry - if (poly.geometry == "multipolygon") { - const polys = poly.multipolygon.polygons; + if (poly.geometry === 'multipolygon') { + const polys = poly.multipolygon.polygons const coords = polys.map( - poly => poly.rings.map( + multiPoly => multiPoly.rings.map( ring => ring.coords.map( - coordinate => [coordinate.lon, coordinate.lat] - ) - ) - ); + coordinate => [coordinate.lon, coordinate.lat], + ), + ), + ) geometry = { - "type": "MultiPolygon", - "coordinates": coords, + type: 'MultiPolygon', + coordinates: coords, } - } else if (poly.geometry == "polygon") { + } + else { const coords = poly.polygon.rings.map( ring => ring.coords.map( - coordinate => [coordinate.lon, coordinate.lat] - ) - ); + coordinate => [coordinate.lon, coordinate.lat], + ), + ) geometry = { - "type": "Polygon", - "coordinates": coords, + type: 'Polygon', + coordinates: coords, } - } else { - throw "unknown shape type"; } const geojson = { - "type": "Feature" as const, - "properties": {}, - "geometry": geometry, + type: 'Feature' as const, + properties: {}, + geometry, } - return geojson; + return geojson } - async add_polygon(map: L.Map, name: string, fit_bounds: boolean, style: Record, add_callback = true, add_to_bottom = false) { - this.exist_this_time.push(name); + async add_polygon(map: L.Map, name: string, fit_bounds: boolean, style: Record, add_callback = true, add_to_bottom = false): Promise { + this.exist_this_time.push(name) if (this.polygon_by_name.has(name)) { - this.polygon_by_name.get(name)!.setStyle(style); - return; + this.polygon_by_name.get(name)!.setStyle(style) + return } - const geojson = await this.polygon_geojson(name); - const group = L.featureGroup(); + const geojson = await this.polygon_geojson(name) + const group = L.featureGroup() let polygon = L.geoJson(geojson, { - style: style, + style, // @ts-expect-error smoothFactor not included in library type definitions smoothFactor: 0.1, - className: "tag-" + name.replace(/ /g, "_") - }); + className: `tag-${name.replace(/ /g, '_')}`, + }) if (add_callback) { - polygon = polygon.on("click", () => { - window.location.href = article_link(this.context!, name); - }); + polygon = polygon.on('click', () => { + window.location.href = article_link(this.context, name) + }) } // @ts-expect-error Second parameter not included in library type definitions - group.addLayer(polygon, add_to_bottom); + group.addLayer(polygon, add_to_bottom) if (fit_bounds) { - map.fitBounds(group.getBounds(), { "animate": false }); + map.fitBounds(group.getBounds(), { animate: false }) } - map.addLayer(group); - this.polygon_by_name.set(name, group); + map.addLayer(group) + this.polygon_by_name.set(name, group) } - zoom_to_all() { - // zoom such that all polygons are visible - const bounds = new L.LatLngBounds([]); + zoom_to_all(): void { + // zoom such that all polygons are visible + const bounds = new L.LatLngBounds([]) for (const polygon of this.polygon_by_name.values()) { - bounds.extend(polygon.getBounds()); + bounds.extend(polygon.getBounds()) } - this.map!.fitBounds(bounds); + this.map!.fitBounds(bounds) } - zoom_to(name: string) { + zoom_to(name: string): void { // zoom to a specific polygon - console.log("zoom to", name); - this.map!.fitBounds(this.polygon_by_name.get(name)!.getBounds()); + this.map!.fitBounds(this.polygon_by_name.get(name)!.getBounds()) } - static contextType = UNIVERSE_CONTEXT; - - declare context: React.ContextType; + static override contextType = UNIVERSE_CONTEXT + + declare context: React.ContextType } interface MapProps extends MapGenericProps { - longname: string, - related: NormalizeProto[], - article_type: string, + longname: string + related: NormalizeProto[] + article_type: string } interface ArticleMapProps extends MapProps { - show_historical_cds: boolean, - settings: Record, + show_historical_cds: boolean + settings: Record } -function MapComponent(props: MapProps) { - const [show_historical_cds] = useSetting("show_historical_cds"); - const related_checkbox_settings = useRelatedCheckboxSettings(props.article_type); - return +// eslint-disable-next-line no-restricted-syntax -- Don't want to overwrite the JS Map +export { MapComponent as Map } +function MapComponent(props: MapProps): ReactNode { + const [show_historical_cds] = useSetting('show_historical_cds') + const related_checkbox_settings = useRelatedCheckboxSettings(props.article_type) + return ( + + ) } class ArticleMap extends MapGeneric { + private already_fit_bounds: string | undefined = undefined - private already_fit_bounds: string | undefined = undefined; - - async compute_polygons() { - const relateds = []; - relateds.push(...this.get_related("contained_by")); - relateds.push(...this.get_related("intersects")); - relateds.push(...this.get_related("borders")); - relateds.push(...this.get_related("contains")); - relateds.push(...this.get_related("same_geography")); + override compute_polygons(): Promise { + const relateds = [] + relateds.push(...this.get_related('contained_by')) + relateds.push(...this.get_related('intersects')) + relateds.push(...this.get_related('borders')) + relateds.push(...this.get_related('contains')) + relateds.push(...this.get_related('same_geography')) - const names = []; - const styles = []; + const names = [] + const styles = [] - names.push(this.props.longname); - styles.push({ "interactive": false, "fillOpacity": 0.5, "weight": 1, "color": "#5a7dc3", "fillColor": "#5a7dc3" }); + names.push(this.props.longname) + styles.push({ interactive: false, fillOpacity: 0.5, weight: 1, color: '#5a7dc3', fillColor: '#5a7dc3' }) - const [related_names, related_styles] = this.related_polygons(relateds); - names.push(...related_names); - styles.push(...related_styles); + const [related_names, related_styles] = this.related_polygons(relateds) + names.push(...related_names) + styles.push(...related_styles) - const zoom_index = this.already_fit_bounds != this.props.longname ? 0 : -1; + const zoom_index = this.already_fit_bounds !== this.props.longname ? 0 : -1 - const metas = names.map(() => ({})); + const metas = names.map(() => ({})) - return [names, styles, metas, zoom_index] as const; + return Promise.resolve([names, styles, metas, zoom_index] as const) } - async mapDidRender() { - this.already_fit_bounds = this.props.longname; + override mapDidRender(): Promise { + this.already_fit_bounds = this.props.longname + return Promise.resolve() } - get_related(key: string) { - if (this.props.related === undefined) { - return []; - } + get_related(key: string): NormalizeProto[] { const element = this.props.related.filter( - (x) => x.relationshipType == key) - .map((x) => x.buttons)[0]; - return element; + x => x.relationshipType === key) + .map(x => x.buttons)[0] + return element } - related_polygons(related: NormalizeProto[]) { - const names = []; - const styles = []; + related_polygons(related: NormalizeProto[]): [Polygons[0], Polygons[1]] { + const names = [] + const styles = [] for (let i = related.length - 1; i >= 0; i--) { if (!this.props.show_historical_cds && is_historical_cd(related[i].rowType)) { - continue; + continue } - const key = relationship_key(this.props.article_type, related[i].rowType); + const key = relationship_key(this.props.article_type, related[i].rowType) if (!this.props.settings[key]) { - continue; + continue } - - const color = random_color(related[i].longname); - const style = { color: color, weight: 1, fillColor: color, fillOpacity: 0.1 }; - names.push(related[i].longname); - styles.push(style); + const color = random_color(related[i].longname) + const style = { color, weight: 1, fillColor: color, fillOpacity: 0.1 } + names.push(related[i].longname) + styles.push(style) } - return [names, styles] as const; + return [names, styles] } - -} \ No newline at end of file +} diff --git a/react/src/components/mapper-panel.tsx b/react/src/components/mapper-panel.tsx index 78701551..83e8bc32 100644 --- a/react/src/components/mapper-panel.tsx +++ b/react/src/components/mapper-panel.tsx @@ -1,22 +1,22 @@ +import '../common.css' +import './article.css' -import React, { useEffect, useRef, useState } from 'react'; +import { gunzipSync, gzipSync } from 'zlib' -import { Statistic } from "./table"; -import { MapGeneric, MapGenericProps } from "./map"; -import { PageTemplate } from "../page_template/template"; -import "../common.css"; -import "./article.css"; -import { loadProtobuf } from '../load_json'; -import { consolidated_shape_link, consolidated_stats_link } from '../navigation/links'; -import { interpolate_color } from '../utils/color'; +import React, { ReactNode, useEffect, useRef, useState } from 'react' -import { Keypoints, parse_ramp, Ramp } from "../mapper/ramps"; -import { Basemap, ColorStat, ColorStatDescriptor, FilterSettings, LineStyle, MapSettings, MapperSettings, default_settings, parse_color_stat } from "../mapper/settings"; +import { loadProtobuf } from '../load_json' +import { Keypoints, Ramp, parse_ramp } from '../mapper/ramps' +import { Basemap, ColorStat, ColorStatDescriptor, FilterSettings, LineStyle, MapSettings, MapperSettings, default_settings, parse_color_stat } from '../mapper/settings' +import { consolidated_shape_link, consolidated_stats_link } from '../navigation/links' +import { PageTemplate } from '../page_template/template' +import { interpolate_color } from '../utils/color' +import { ConsolidatedShapes, ConsolidatedStatistics, Feature, IAllStats } from '../utils/protos' +import { headerTextClass } from '../utils/responsive' +import { NormalizeProto } from '../utils/types' -import { gunzipSync, gzipSync } from "zlib"; -import { headerTextClass } from '../utils/responsive'; -import { ConsolidatedShapes, ConsolidatedStatistics, Feature, IAllStats } from "../utils/protos"; -import { NormalizeProto } from "../utils/types"; +import { MapGeneric, MapGenericProps, Polygons } from './map' +import { Statistic } from './table' interface DisplayedMapProps extends MapGenericProps { color_stat: ColorStat @@ -24,7 +24,7 @@ interface DisplayedMapProps extends MapGenericProps { geography_kind: string underlying_shapes: Promise underlying_stats: Promise - ramp: Ramp, + ramp: Ramp ramp_callback: (newRamp: EmpiricalRamp) => void line_style: LineStyle basemap: Basemap @@ -32,47 +32,45 @@ interface DisplayedMapProps extends MapGenericProps { } class DisplayedMap extends MapGeneric { - name_to_index: undefined | Map + name_to_index: undefined | Map - async guarantee_name_to_index() { + async guarantee_name_to_index(): Promise { if (this.name_to_index === undefined) { - const result = (await this.props.underlying_shapes).longnames; - this.name_to_index = new Map(result.map((r, i) => [r, i])); + const result = (await this.props.underlying_shapes).longnames + this.name_to_index = new Map(result.map((r, i) => [r, i])) } } - async loadShape(name: string): Promise> { - await this.guarantee_name_to_index(); - const index = this.name_to_index!.get(name)!; - const data = (await this.props.underlying_shapes).shapes[index]; - return data as NormalizeProto; + override async loadShape(name: string): Promise> { + await this.guarantee_name_to_index() + const index = this.name_to_index!.get(name)! + const data = (await this.props.underlying_shapes).shapes[index] + return data as NormalizeProto } + override async compute_polygons(): Promise { + // reset index + this.name_to_index = undefined + await this.guarantee_name_to_index() - async compute_polygons() { - // reset index - this.name_to_index = undefined; - await this.guarantee_name_to_index(); - - const line_style = this.props.line_style; + const line_style = this.props.line_style - let stats: { stats: NormalizeProto[], longnames: string[] } = (await this.props.underlying_stats) as NormalizeProto; + let stats: { stats: NormalizeProto[], longnames: string[] } = (await this.props.underlying_stats) as NormalizeProto // TODO correct this! if (this.props.filter !== undefined) { - - const filter_vals = this.props.filter.compute(stats.stats); + const filter_vals = this.props.filter.compute(stats.stats) stats = { stats: stats.stats.filter((x, i) => filter_vals[i]), longnames: stats.longnames.filter((x, i) => filter_vals[i]), } } - const stat_vals = this.props.color_stat.compute(stats.stats); - const names = stats.longnames; - const [ramp, interpolations] = this.props.ramp.create_ramp(stat_vals); - this.props.ramp_callback({ ramp: ramp, interpolations: interpolations }); + const stat_vals = this.props.color_stat.compute(stats.stats) + const names = stats.longnames + const [ramp, interpolations] = this.props.ramp.create_ramp(stat_vals) + this.props.ramp_callback({ ramp, interpolations }) const colors = stat_vals.map( - val => interpolate_color(ramp, val) - ); + val => interpolate_color(ramp, val), + ) const styles = colors.map( // no outline, set color fill, alpha=1 color => ({ @@ -81,62 +79,65 @@ class DisplayedMap extends MapGeneric { color: line_style.color, opacity: 1, weight: line_style.weight, - }) - ); - const metas = stat_vals.map((x) => { return { statistic: x } }); - return [names, styles, metas, -1] as const; + }), + ) + const metas = stat_vals.map((x) => { return { statistic: x } }) + return [names, styles, metas, -1] as const } - async mapDidRender() { - // zoom map to fit united states - // do so instantly + override mapDidRender(): Promise { + // zoom map to fit united states + // do so instantly this.map!.fitBounds([ [49.3457868, -124.7844079], - [24.7433195, -66.9513812] - ], { animate: false }); + [24.7433195, -66.9513812], + ], { animate: false }) + return Promise.resolve() } } - -function Colorbar(props: { name: string, ramp: EmpiricalRamp | undefined }) { +function Colorbar(props: { name: string, ramp: EmpiricalRamp | undefined }): ReactNode { // do this as a table with 10 columns, each 10% wide and // 2 rows. Top one is the colorbar, bottom one is the // labels. if (props.ramp === undefined) { - return

; - } - const values = props.ramp.interpolations; - - - const create_value = (stat: number) => { - return
- - -
+ return
} + const values = props.ramp.interpolations + const create_value = (stat: number): ReactNode => { + return ( +
+ + +
+ ) + } return (
- +
{ values.map((x, i) => ( - )) } @@ -144,7 +145,7 @@ function Colorbar(props: { name: string, ramp: EmpiricalRamp | undefined }) { { values.map((x, i) => ( - )) @@ -156,7 +157,7 @@ function Colorbar(props: { name: string, ramp: EmpiricalRamp | undefined }) { {props.name} - ); + ) } interface MapComponentProps { @@ -173,24 +174,24 @@ interface MapComponentProps { } interface EmpiricalRamp { - ramp: Keypoints, + ramp: Keypoints interpolations: number[] } -function MapComponent(props: MapComponentProps) { - - const color_stat = parse_color_stat(name_to_index, props.color_stat); - const filter = props.filter.enabled ? parse_color_stat(name_to_index, props.filter.function) : undefined; +function MapComponent(props: MapComponentProps): ReactNode { + const color_stat = parse_color_stat(name_to_index, props.color_stat) + const filter = props.filter.enabled ? parse_color_stat(name_to_index, props.filter.function) : undefined - const [empirical_ramp, set_empirical_ramp] = useState(undefined); + const [empirical_ramp, set_empirical_ramp] = useState(undefined) return (
-
+ }} + > +
set_empirical_ramp(newRamp)} + ramp_callback={(newRamp) => { set_empirical_ramp(newRamp) }} ref={props.map_ref} line_style={props.line_style} basemap={props.basemap} height={props.height} />
-
+
}) { - const exportAsSvg = async () => { +function Export(props: { map_ref: React.RefObject }): ReactNode { + const exportAsSvg = async (): Promise => { if (props.map_ref.current === null) { - return; + return } - const svg = await props.map_ref.current.exportAsSvg(); - saveAsFile("map.svg", svg, "image/svg+xml"); + const svg = await props.map_ref.current.exportAsSvg() + saveAsFile('map.svg', svg, 'image/svg+xml') } - const exportAsGeoJSON = async () => { + const exportAsGeoJSON = async (): Promise => { if (props.map_ref.current === null) { - return; + return } - const geojson = await props.map_ref.current.exportAsGeoJSON(); - saveAsFile("map.geojson", geojson, "application/geo+json"); + const geojson = await props.map_ref.current.exportAsGeoJSON() + saveAsFile('map.geojson', geojson, 'application/geo+json') } - return
- - - -
+ return ( +
+ + + +
+ ) } function mapSettingsFromURLParams(): MapSettings { - const params = new URLSearchParams(window.location.search); - const encoded_settings = params.get("settings"); + const params = new URLSearchParams(window.location.search) + const encoded_settings = params.get('settings') let settings: Partial = {} if (encoded_settings !== null) { - const jsoned_settings = gunzipSync(Buffer.from(encoded_settings, 'base64')).toString(); - settings = JSON.parse(jsoned_settings); + const jsoned_settings = gunzipSync(Buffer.from(encoded_settings, 'base64')).toString() + settings = JSON.parse(jsoned_settings) as Partial } - return default_settings(settings); + return default_settings(settings) } -const names: string[] = require("../data/statistic_name_list.json") -const valid_geographies: string[] = require("../data/mapper/used_geographies.json"); +const valid_geographies = require('../data/mapper/used_geographies.json') as string[] +const names = require('../data/statistic_name_list.json') as string[] const name_to_index = new Map(names.map((name, i) => [name, i])) -export function MapperPanel() { - - const [map_settings, set_map_settings] = useState(mapSettingsFromURLParams()); +export function MapperPanel(): ReactNode { + const [map_settings, set_map_settings] = useState(mapSettingsFromURLParams()) - const [underlying_shapes, set_underlying_shapes] = useState | undefined>(undefined); - const [underlying_stats, set_underlying_stats] = useState | undefined>(undefined); + const [underlying_shapes, set_underlying_shapes] = useState | undefined>(undefined) + const [underlying_stats, set_underlying_stats] = useState | undefined>(undefined) useEffect(() => { if (valid_geographies.includes(map_settings.geography_kind)) { set_underlying_shapes(loadProtobuf( consolidated_shape_link(map_settings.geography_kind), - "ConsolidatedShapes" - )); + 'ConsolidatedShapes', + )) set_underlying_stats(loadProtobuf( consolidated_stats_link(map_settings.geography_kind), - "ConsolidatedStatistics" - )); + 'ConsolidatedStatistics', + )) } - }, [map_settings.geography_kind]); + }, [map_settings.geography_kind]) - const map_ref = useRef(null); + const map_ref = useRef(null) - const jsoned_settings = JSON.stringify(map_settings); + const jsoned_settings = JSON.stringify(map_settings) useEffect(() => { - // gzip then base64 encode - const encoded_settings = gzipSync(jsoned_settings).toString("base64"); + // gzip then base64 encode + const encoded_settings = gzipSync(jsoned_settings).toString('base64') // convert to parameters like ?settings=... - const params = new URLSearchParams(window.location.search); - params.set("settings", encoded_settings); + const params = new URLSearchParams(window.location.search) + params.set('settings', encoded_settings) // back button should work - window.history.pushState(null, "", "?" + params.toString()); - }, [jsoned_settings]); + window.history.pushState(null, '', `?${params.toString()}`) + }, [jsoned_settings]) useEffect(() => { - const listener = () => set_map_settings(mapSettingsFromURLParams()) - window.addEventListener('popstate', listener); - return () => window.removeEventListener('popstate', listener); - }, []); - - - const mapper_panel = (height: string | undefined) => { - const ramp = parse_ramp(map_settings.ramp); - const geography_kind = map_settings.geography_kind; - const color_stat = map_settings.color_stat; - const filter = map_settings.filter; - const valid = valid_geographies.includes(geography_kind); - - return !valid ?
Invalid geography kind
: + const listener = (): void => { set_map_settings(mapSettingsFromURLParams()) } + window.addEventListener('popstate', listener) + return () => { window.removeEventListener('popstate', listener) } + }, []) + + const mapper_panel = (height: string | undefined): ReactNode => { + const ramp = parse_ramp(map_settings.ramp) + const geography_kind = map_settings.geography_kind + const color_stat = map_settings.color_stat + const filter = map_settings.filter + const valid = valid_geographies.includes(geography_kind) + + return !valid + ?
Invalid geography kind
+ : ( + + ) } - if (new URLSearchParams(window.location.search).get("view") === "true") { - return mapper_panel("100%"); + if (new URLSearchParams(window.location.search).get('view') === 'true') { + return mapper_panel('100%') } - - return {() => -
-
Urban Stats Mapper (beta)
- - - { - mapper_panel(undefined) // use default height - } -
- }
-} + return ( + + {() => ( +
+
Urban Stats Mapper (beta)
+ + + { + mapper_panel(undefined) // use default height + } +
+ )} +
+ ) +} diff --git a/react/src/components/plots.tsx b/react/src/components/plots.tsx index ccfa9c48..3b2c2270 100644 --- a/react/src/components/plots.tsx +++ b/react/src/components/plots.tsx @@ -1,24 +1,24 @@ - -import React, { useEffect, useRef } from 'react'; -import { IHistogram } from '../utils/protos'; +import * as Plot from '@observablehq/plot' +import React, { ReactNode, useEffect, useRef } from 'react' // imort Observable plot -import * as Plot from "@observablehq/plot"; -import { HistogramType, useSetting } from '../page_template/settings'; -import { ExtraStat } from './load-article'; -import { CheckboxSetting } from './sidebar'; -import { create_screenshot } from './screenshot'; -import { useUniverse } from '../universe'; +import { HistogramType, useSetting } from '../page_template/settings' +import { useUniverse } from '../universe' +import { IHistogram } from '../utils/protos' + +import { ExtraStat } from './load-article' +import { create_screenshot } from './screenshot' +import { CheckboxSetting } from './sidebar' interface PlotProps { - shortname?: string; - extra_stat?: ExtraStat; - color: string; + shortname?: string + extra_stat?: ExtraStat + color: string } -const Y_PAD = 0.025; +const Y_PAD = 0.025 -export function WithPlot(props: { children: React.ReactNode, plot_props: PlotProps[], expanded: boolean, screenshot_mode: boolean }) { +export function WithPlot(props: { children: React.ReactNode, plot_props: PlotProps[], expanded: boolean, screenshot_mode: boolean }): ReactNode { return (
{props.children} @@ -27,189 +27,210 @@ export function WithPlot(props: { children: React.ReactNode, plot_props: PlotPro ) } -function RenderedPlot({ plot_props, screenshot_mode }: { plot_props: PlotProps[], screenshot_mode: boolean }) { - console.log("plot props", plot_props) +function RenderedPlot({ plot_props, screenshot_mode }: { plot_props: PlotProps[], screenshot_mode: boolean }): ReactNode { if (plot_props.some(p => p.extra_stat?.stat.histogram)) { - plot_props = plot_props.filter(p => p.extra_stat?.stat.histogram); - return ({ - shortname: props.shortname!, - histogram: props.extra_stat!.stat.histogram!, - color: props.color, - universe_total: props.extra_stat!.universe_total - }) - )} - screenshot_mode={screenshot_mode} - /> + plot_props = plot_props.filter(p => p.extra_stat?.stat.histogram) + return ( + ({ + shortname: props.shortname!, + histogram: props.extra_stat!.stat.histogram!, + color: props.color, + universe_total: props.extra_stat!.universe_total, + }), + )} + screenshot_mode={screenshot_mode} + /> + ) } - throw new Error("plot not recognized: " + JSON.stringify(plot_props)); + throw new Error(`plot not recognized: ${JSON.stringify(plot_props)}`) } interface HistogramProps { - shortname: string; - histogram: IHistogram; - color: string; - universe_total: number; + shortname: string + histogram: IHistogram + color: string + universe_total: number } -function Histogram(props: { histograms: HistogramProps[], screenshot_mode: boolean }) { - const [histogram_type] = useSetting("histogram_type"); - const [use_imperial] = useSetting("use_imperial"); - const [relative] = useSetting("histogram_relative"); +function Histogram(props: { histograms: HistogramProps[], screenshot_mode: boolean }): ReactNode { + const [histogram_type] = useSetting('histogram_type') + const [use_imperial] = useSetting('use_imperial') + const [relative] = useSetting('histogram_relative') // series for each histogram. Each series is a list of [x, y] pairs // x start at histogram.binMin and goes up by histogram.binSize // y is histogram.counts - console.log("HISTOGRAMS", props.histograms); // assert all the histograms have the same binMin and binSize - const binMin = props.histograms[0].histogram.binMin!; - const binSize = props.histograms[0].histogram.binSize!; + const binMin = props.histograms[0].histogram.binMin! + const binSize = props.histograms[0].histogram.binSize! for (const histogram of props.histograms) { if (histogram.histogram.binMin !== binMin || histogram.histogram.binSize !== binSize) { - throw new Error("histograms have different binMin or binSize"); + throw new Error('histograms have different binMin or binSize') } } // get the length of the x values // get the x values - const plot_ref = useRef(null); - const title = props.histograms.length === 1 ? props.histograms[0].shortname : ""; + const plot_ref = useRef(null) + const title = props.histograms.length === 1 ? props.histograms[0].shortname : '' useEffect(() => { if (plot_ref.current) { - const colors = props.histograms.map(h => h.color); - const shortnames = props.histograms.map(h => h.shortname); - const render_y = relative ? (y: number) => y.toFixed(2) + "%" : (y: number) => render_number_highly_rounded(y, 2); + const colors = props.histograms.map(h => h.color) + const shortnames = props.histograms.map(h => h.shortname) + const render_y = relative ? (y: number) => `${y.toFixed(2)}%` : (y: number) => render_number_highly_rounded(y, 2) - const [x_idx_start, x_idx_end] = histogramBounds(props.histograms); - const xidxs = Array.from({ length: x_idx_end - x_idx_start }, (_, i) => i + x_idx_start); - const [x_axis_marks, render_x] = x_axis(xidxs, binSize, binMin, use_imperial); - const [marks, max_value] = createHistogramMarks(props.histograms, xidxs, histogram_type, relative, render_x, render_y); + const [x_idx_start, x_idx_end] = histogramBounds(props.histograms) + const xidxs = Array.from({ length: x_idx_end - x_idx_start }, (_, i) => i + x_idx_start) + const [x_axis_marks, render_x] = x_axis(xidxs, binSize, binMin, use_imperial) + const [marks, max_value] = createHistogramMarks(props.histograms, xidxs, histogram_type, relative, render_x, render_y) marks.push( ...x_axis_marks, ...y_axis(max_value), - Plot.text([title], { frameAnchor: "top", dy: -40 }), - ); + Plot.text([title], { frameAnchor: 'top', dy: -40 }), + ) // y grid marks // marks.push(Plot.gridY([0, 25, 50, 75, 100])); const plot_config = { - marks: marks, + marks, x: { - label: `Density (/${use_imperial ? "mi" : "km"}²)`, + label: `Density (/${use_imperial ? 'mi' : 'km'}²)`, }, y: { - label: relative ? "% of total" : "Population", + label: relative ? '% of total' : 'Population', domain: [max_value * (-Y_PAD), max_value * (1 + Y_PAD)], }, grid: false, width: 1000, style: { - fontSize: "1em", - fontFamily: "Jost" + fontSize: '1em', + fontFamily: 'Jost', }, marginTop: 80, marginBottom: 40, marginLeft: 50, - color: props.histograms.length === 1 ? undefined : - { legend: true, range: colors, domain: shortnames }, - }; - const plot = Plot.plot(plot_config); - plot_ref.current.innerHTML = ""; - plot_ref.current.appendChild(plot); + color: props.histograms.length === 1 + ? undefined + : { legend: true, range: colors, domain: shortnames }, + } + const plot = Plot.plot(plot_config) + plot_ref.current.innerHTML = '' + plot_ref.current.appendChild(plot) } - }, [histogram_type, use_imperial, relative]); + }, [histogram_type, use_imperial, relative, binMin, binSize, props.histograms, title]) // put a button panel in the top right corner - return
-
- {props.screenshot_mode - ? undefined - :
- h.shortname)} /> + return ( +
+
- } -
+ {props.screenshot_mode + ? undefined + : ( +
+ h.shortname)} /> +
+ )} +
+ ) } function HistogramSettings(props: { - shortnames: string[]; + shortnames: string[] plot_ref: React.RefObject -}) { - const universe = useUniverse(); - const [histogram_type, setHistogramType] = useSetting("histogram_type"); +}): ReactNode { + const universe = useUniverse() + const [histogram_type, setHistogramType] = useSetting('histogram_type') // dropdown for histogram type - return
- { - if (props.plot_ref.current) { - create_screenshot( - { - path: props.shortnames.join("_") + "_histogram", - overall_width: props.plot_ref.current.offsetWidth * 2, - elements_to_render: [props.plot_ref.current], - height_multiplier: 1.2, - }, - universe - ) - } + return ( +
- - -
+ > + { + if (props.plot_ref.current) { + void create_screenshot( + { + path: `${props.shortnames.join('_')}_histogram`, + overall_width: props.plot_ref.current.offsetWidth * 2, + elements_to_render: [props.plot_ref.current], + height_multiplier: 1.2, + }, + universe, + ) + } + }} + width="20" + height="20" + /> + + +
+ ) } function histogramBounds(histograms: HistogramProps[]): [number, number] { - let x_idx_end = Math.max(...histograms.map(histogram => histogram.histogram.counts!.length)); - x_idx_end += 1; - const zeros_at_front = (arr: number[]) => { - let i = 0; + let x_idx_end = Math.max(...histograms.map(histogram => histogram.histogram.counts!.length)) + x_idx_end += 1 + const zeros_at_front = (arr: number[]): number => { + let i = 0 while (i < arr.length && arr[i] === 0) { - i++; + i++ } - return i; + return i } - let x_idx_start = Math.min(...histograms.map(histogram => zeros_at_front(histogram.histogram.counts!))); + let x_idx_start = Math.min(...histograms.map(histogram => zeros_at_front(histogram.histogram.counts!))) if (x_idx_start > 0) { - x_idx_start--; + x_idx_start-- } // round x_idx_start down to the nearest number which, when divided by 10, has a remainder of 0, 3, or 7 while (x_idx_start % 10 !== 0 && x_idx_start % 10 !== 3 && x_idx_start % 10 !== 7) { - x_idx_start--; + x_idx_start-- } // same for x_idx_end while (x_idx_end % 10 !== 0 && x_idx_end % 10 !== 3 && x_idx_end % 10 !== 7) { - x_idx_end++; + x_idx_end++ } - return [x_idx_start, x_idx_end]; + return [x_idx_start, x_idx_end] +} + +interface Series { + values: { name: string, xidx: number, y: number }[] + color: string } -function mulitipleSeriesConsistentLength(histograms: HistogramProps[], xidxs: number[], relative: boolean, is_cumulative: boolean) { +function mulitipleSeriesConsistentLength(histograms: HistogramProps[], xidxs: number[], relative: boolean, is_cumulative: boolean): Series[] { // Create a list of series, each with the same length - const sum = (arr: number[]) => arr.reduce((a, b) => a + b, 0); - const sum_each = histograms.map(histogram => sum(histogram.histogram.counts!)); + const sum = (arr: number[]): number => arr.reduce((a, b) => a + b, 0) + const sum_each = histograms.map(histogram => sum(histogram.histogram.counts!)) const series = histograms.map((histogram, histogram_idx) => { - const counts = [...histogram.histogram.counts!]; - const after_val = 0; + const counts = [...histogram.histogram.counts!] + const after_val = 0 if (is_cumulative) { for (let i = counts.length - 2; i >= 0; i--) { - counts[i] += counts[i + 1]; + counts[i] += counts[i + 1] } } return { @@ -217,81 +238,79 @@ function mulitipleSeriesConsistentLength(histograms: HistogramProps[], xidxs: nu name: histogram.shortname, xidx, y: ( - xidx >= counts.length ? - after_val - : - counts[xidx] / sum_each[histogram_idx] - ) * (relative ? 100 : histogram.universe_total) + xidx >= counts.length + ? after_val + : counts[xidx] / sum_each[histogram_idx] + ) * (relative ? 100 : histogram.universe_total), })), - color: histogram.color - }; - }); - return series; + color: histogram.color, + } + }) + return series } -function dovetailSequences(series: { values: { xidx: number, y: number, name: string }[], color: string }[]) { - const series_single: { xidx_left: number, xidx_right: number, y: number, color: string }[] = []; +function dovetailSequences(series: { values: { xidx: number, y: number, name: string }[], color: string }[]): { xidx_left: number, xidx_right: number, y: number, color: string }[] { + const series_single: { xidx_left: number, xidx_right: number, y: number, color: string }[] = [] for (let i = 0; i < series.length; i++) { - const s = series[i]; - const width = 1 / (series.length) * 0.8; - const off = (i - (series.length - 1) / 2) * width; - series_single.push(... - s.values - .map(v => ({ - xidx_left: v.xidx + off, xidx_right: v.xidx + off + width, - y: v.y, color: s.color, name: v.name - })) + const s = series[i] + const width = 1 / (series.length) * 0.8 + const off = (i - (series.length - 1) / 2) * width + series_single.push(...s.values + .map(v => ({ + xidx_left: v.xidx + off, xidx_right: v.xidx + off + width, + y: v.y, color: s.color, name: v.name, + })), ) } - return series_single; + return series_single } -function maxSequences(series: { values: { xidx: number, y: number, name: string }[] }[]) { - const series_max: { xidx: number, y: number, names: string[], ys: number[] }[] = []; +function maxSequences(series: { values: { xidx: number, y: number, name: string }[] }[]): { xidx: number, y: number, names: string[], ys: number[] }[] { + const series_max: { xidx: number, y: number, names: string[], ys: number[] }[] = [] for (let i = 0; i < series[0].values.length; i++) { series_max.push({ xidx: series[0].values[i].xidx, y: Math.max(...series.map(s => s.values[i].y)), names: series.map(s => s.values[i].name), - ys: series.map(s => s.values[i].y) - }); + ys: series.map(s => s.values[i].y), + }) } - return series_max; + return series_max } function x_axis(xidxs: number[], binSize: number, binMin: number, use_imperial: boolean): [Plot.Markish[], (x: number) => string] { - const x_keypoints: number[] = []; + const x_keypoints: number[] = [] for (const xidx of xidxs) { - let last_digit = xidx % 10; + let last_digit = xidx % 10 if (use_imperial) { - last_digit = (last_digit + 4) % 10; + last_digit = (last_digit + 4) % 10 } - if (last_digit == 0 || last_digit == 3 || last_digit == 7) { - x_keypoints.push(xidx); + if (last_digit === 0 || last_digit === 3 || last_digit === 7) { + x_keypoints.push(xidx) } } - const adjustment = use_imperial ? Math.log10(1.60934) * 2 : 0; + const adjustment = use_imperial ? Math.log10(1.60934) * 2 : 0 return [ [ Plot.axisX(x_keypoints, { tickFormat: d => render_pow10(d * binSize + binMin + adjustment) }), - Plot.gridX(x_keypoints) + Plot.gridX(x_keypoints), ], - x => render_number_highly_rounded(Math.pow(10, x * binSize + binMin + adjustment), 2) + "/" + (use_imperial ? "mi" : "km") + "²" + x => `${render_number_highly_rounded(Math.pow(10, x * binSize + binMin + adjustment), 2)}/${use_imperial ? 'mi' : 'km'}²`, ] } -function y_axis(max_value: number) { - const MIN_N_Y_TICKS = 5; - const ideal_tick_gap = max_value / MIN_N_Y_TICKS; - const log10_tick_gap_times_3 = Math.floor(Math.log10(ideal_tick_gap) * 3); - const tick_gap_oom = Math.pow(10, Math.floor(log10_tick_gap_times_3 / 3)); - const tick_gap_mantissa = log10_tick_gap_times_3 % 3 == 0 ? 1 : log10_tick_gap_times_3 % 3 == 1 ? 2 : 5; - const tick_gap = tick_gap_mantissa * tick_gap_oom; - const max_value_rounded = Math.ceil(max_value / tick_gap) * tick_gap; - const y_keypoints = Array.from({ length: Math.floor(max_value_rounded / tick_gap) + 1 }, (_, i) => i * tick_gap); +function y_axis(max_value: number): (Plot.CompoundMark | Plot.RuleY)[] { + const MIN_N_Y_TICKS = 5 + const ideal_tick_gap = max_value / MIN_N_Y_TICKS + const log10_tick_gap_times_3 = Math.floor(Math.log10(ideal_tick_gap) * 3) + const tick_gap_oom = Math.pow(10, Math.floor(log10_tick_gap_times_3 / 3)) + const tick_gap_mantissa = log10_tick_gap_times_3 % 3 === 0 ? 1 : log10_tick_gap_times_3 % 3 === 1 ? 2 : 5 + const tick_gap = tick_gap_mantissa * tick_gap_oom + const max_value_rounded = Math.ceil(max_value / tick_gap) * tick_gap + const y_keypoints = Array.from({ length: Math.floor(max_value_rounded / tick_gap) + 1 }, (_, i) => i * tick_gap) return [ - Plot.axisY(y_keypoints, { tickFormat: d => render_number_highly_rounded(d, 1) }), + Plot.axisY(y_keypoints, { tickFormat: (d: number) => render_number_highly_rounded(d, 1) }), Plot.gridY(y_keypoints), ] } @@ -299,93 +318,92 @@ function y_axis(max_value: number) { function pow10_moral(x: number): number { // 10 ** x, but "morally" so, i.e., 10 ** 0.3 = 2 if (x < 0) { - return 1 / pow10_moral(-x); + return 1 / pow10_moral(-x) } if (x >= 1) { - return 10 ** Math.floor(x) * pow10_moral(x - Math.floor(x)); + return 10 ** Math.floor(x) * pow10_moral(x - Math.floor(x)) } - const x10 = x * 10; - const error_round = Math.abs(x10 - Math.round(x10)); + const x10 = x * 10 + const error_round = Math.abs(x10 - Math.round(x10)) if (error_round > 0.2) { - return 10 ** x; + return 10 ** x } - if (Math.round(x10) == 0) { - return 1; + if (Math.round(x10) === 0) { + return 1 } - if (Math.round(x10) == 3) { - return 2; + if (Math.round(x10) === 3) { + return 2 } - if (Math.round(x10) == 7) { - return 5; + if (Math.round(x10) === 7) { + return 5 } - return 10 ** x; + return 10 ** x } -function render_pow10(x: number) { - const p10 = pow10_moral(x); +function render_pow10(x: number): string { + const p10 = pow10_moral(x) - return render_number_highly_rounded(p10); + return render_number_highly_rounded(p10) } -function render_number_highly_rounded(x: number, places = 0) { +function render_number_highly_rounded(x: number, places = 0): string { if (x < 1000) { - return x.toFixed(0); + return x.toFixed(0) } if (x < 1e6) { - return (x / 1e3).toFixed(places) + "k"; + return `${(x / 1e3).toFixed(places)}k` } if (x < 1e9) { - return (x / 1e6).toFixed(places) + "M"; + return `${(x / 1e6).toFixed(places)}M` } if (x < 1e12) { - return (x / 1e9).toFixed(places) + "B"; + return `${(x / 1e9).toFixed(places)}B` } - return x.toExponential(1); + return x.toExponential(1) } function createHistogramMarks( histograms: HistogramProps[], xidxs: number[], histogram_type: HistogramType, relative: boolean, render_x: (x: number) => string, - render_y: (y: number) => string + render_y: (y: number) => string, ): [Plot.Markish[], number] { - const series = mulitipleSeriesConsistentLength(histograms, xidxs, relative, histogram_type === "Line (cumulative)"); - const series_single = dovetailSequences(series); + const series = mulitipleSeriesConsistentLength(histograms, xidxs, relative, histogram_type === 'Line (cumulative)') + const series_single = dovetailSequences(series) - const max_value = Math.max(...series.map(s => Math.max(...s.values.map(v => v.y)))); + const max_value = Math.max(...series.map(s => Math.max(...s.values.map(v => v.y)))) const tip = Plot.tip(maxSequences(series), Plot.pointerX({ - x: "xidx", y: "y", - title: (d) => { - - let result = "Density: " + render_x(d.xidx) + "\n"; + x: 'xidx', y: 'y', + title: (d: { names: string[], xidx: number, ys: number[] }) => { + let result = `Density: ${render_x(d.xidx)}\n` if (d.names.length > 1) { - result += d.names.map((name: string, i: number) => `${name}: ${render_y(d.ys[i])}`).join("\n") - } else { - result += `Frequency: ${render_y(d.ys[0])}`; + result += d.names.map((name: string, i: number) => `${name}: ${render_y(d.ys[i])}`).join('\n') + } + else { + result += `Frequency: ${render_y(d.ys[0])}` } - return result; + return result }, })) - const color = histograms.length === 1 ? histograms[0].color : "name"; - const marks: Plot.Markish[] = []; - if (histogram_type === "Line" || histogram_type === "Line (cumulative)") { + const color = histograms.length === 1 ? histograms[0].color : 'name' + const marks: Plot.Markish[] = [] + if (histogram_type === 'Line' || histogram_type === 'Line (cumulative)') { marks.push( ...series.map(s => Plot.line(s.values, { - x: "xidx", y: "y", stroke: color, strokeWidth: 4 + x: 'xidx', y: 'y', stroke: color, strokeWidth: 4, })), - ); - } else if (histogram_type === "Bar") { + ) + } + else { marks.push( Plot.rectY(series_single, { - x1: "xidx_left", - x2: "xidx_right", - y: "y", + x1: 'xidx_left', + x2: 'xidx_right', + y: 'y', fill: color, - }) - ); - } else { - throw new Error("histogram_type not recognized: " + histogram_type); + }), + ) } - marks.push(tip); - return [marks, max_value]; -} \ No newline at end of file + marks.push(tip) + return [marks, max_value] +} diff --git a/react/src/components/quiz-panel.tsx b/react/src/components/quiz-panel.tsx index 8374a163..1b7fcc6f 100644 --- a/react/src/components/quiz-panel.tsx +++ b/react/src/components/quiz-panel.tsx @@ -1,120 +1,121 @@ -import React, { useState } from 'react'; +import React, { ReactNode, useState } from 'react' -import { PageTemplate } from "../page_template/template"; -import "../common.css"; -import "./quiz.css"; -import { History, reportToServer, reportToServerRetro } from '../quiz/statistics'; -import { QuizQuestionDispatch } from '../quiz/quiz-question'; -import { QuizResult } from '../quiz/quiz-result'; -import { a_correct, ENDPOINT, QuizDescriptor, QuizQuestion } from "../quiz/quiz"; +import { PageTemplate } from '../page_template/template' +import '../common.css' +import './quiz.css' +import { ENDPOINT, QuizDescriptor, QuizQuestion, a_correct } from '../quiz/quiz' +import { QuizQuestionDispatch } from '../quiz/quiz-question' +import { QuizResult } from '../quiz/quiz-result' +import { History, reportToServer, reportToServerRetro } from '../quiz/statistics' function loadQuizHistory(): History { - const history = JSON.parse(localStorage.getItem("quiz_history") ?? "{}") as History; + const history = JSON.parse(localStorage.getItem('quiz_history') ?? '{}') as History // set 42's correct_pattern's 0th element to true - if ("42" in history) { - if ("correct_pattern" in history["42"]) { - if (history["42"]["correct_pattern"].length > 0) { - history["42"]["correct_pattern"][0] = true; + if ('42' in history) { + if ('correct_pattern' in history['42']) { + if (history['42'].correct_pattern.length > 0) { + history['42'].correct_pattern[0] = true } } } - return history; + return history } -export function QuizPanel(props: { quizDescriptor: QuizDescriptor, today_name: string, todays_quiz: QuizQuestion[], parameters: string }) { - - const [quiz_history, set_quiz_history] = useState(loadQuizHistory()); - const [waiting, setWaiting] = useState(false); +export function QuizPanel(props: { quizDescriptor: QuizDescriptor, today_name: string, todays_quiz: QuizQuestion[], parameters: string }): ReactNode { + const [quiz_history, set_quiz_history] = useState(loadQuizHistory()) + const [waiting, setWaiting] = useState(false) const todays_quiz_history = quiz_history[props.quizDescriptor.name] ?? { choices: [], correct_pattern: [] } - const is_daily = typeof props.quizDescriptor.name == "number"; + const is_daily = typeof props.quizDescriptor.name === 'number' - const is_weekly = typeof props.quizDescriptor.name == "string" && props.quizDescriptor.name.match(/^W-?\d+$/); + const is_weekly = typeof props.quizDescriptor.name === 'string' && (/^W-?\d+$/.exec(props.quizDescriptor.name)) - const set_todays_quiz_history = (history_today: History[string]) => { + const set_todays_quiz_history = (history_today: History[string]): void => { const newHistory = { ...quiz_history, [props.quizDescriptor.name]: history_today } - set_quiz_history(newHistory); - setWaiting(true); + set_quiz_history(newHistory) + setWaiting(true) // if today is a number and not a string if (is_daily || is_weekly) { - localStorage.setItem("quiz_history", JSON.stringify(newHistory)); + localStorage.setItem('quiz_history', JSON.stringify(newHistory)) } } - const on_select = (selected: "A" | "B") => { + const on_select = (selected: 'A' | 'B'): void => { if (waiting) { - return; + return } - const history = todays_quiz_history; - const idx = history.correct_pattern.length; - const question = (props.todays_quiz)[idx]; - history.choices.push(selected); - history.correct_pattern.push((selected == "A") == a_correct(question)); - set_todays_quiz_history(history); - setTimeout(() => setWaiting(false), 500) + const history = todays_quiz_history + const idx = history.correct_pattern.length + const question = (props.todays_quiz)[idx] + history.choices.push(selected) + history.correct_pattern.push((selected === 'A') === a_correct(question)) + set_todays_quiz_history(history) + setTimeout(() => { setWaiting(false) }, 500) } - return {() => { - - const quiz = props.todays_quiz; - const history = todays_quiz_history; - - let index = history["choices"].length; - if (waiting) { - index -= 1; - } - - if (index == quiz.length) { - - let get_per_question; - if (is_daily) { - reportToServer(quiz_history); - // POST to endpoint /juxtastat/get_per_question_stats with the current day - get_per_question = fetch(ENDPOINT + "/juxtastat/get_per_question_stats", { - method: "POST", - body: JSON.stringify({ day: props.quizDescriptor.name }), - headers: { - "Content-Type": "application/json", - }, - }); - } - if (is_weekly && typeof props.quizDescriptor.name == "string") { - reportToServerRetro(quiz_history); - get_per_question = fetch(ENDPOINT + "/retrostat/get_per_question_stats", { - method: "POST", - body: JSON.stringify({ week: parseInt(props.quizDescriptor.name.substring(1)) }), - headers: { - "Content-Type": "application/json", - }, - }); - } - return ( - - ) - } - - return ( - - ); - }} -} \ No newline at end of file + return ( + + {() => { + const quiz = props.todays_quiz + const history = todays_quiz_history + + let index = history.choices.length + if (waiting) { + index -= 1 + } + + if (index === quiz.length) { + let get_per_question + if (is_daily) { + void reportToServer(quiz_history) + // POST to endpoint /juxtastat/get_per_question_stats with the current day + get_per_question = fetch(`${ENDPOINT}/juxtastat/get_per_question_stats`, { + method: 'POST', + body: JSON.stringify({ day: props.quizDescriptor.name }), + headers: { + 'Content-Type': 'application/json', + }, + }) + } + if (is_weekly && typeof props.quizDescriptor.name === 'string') { + void reportToServerRetro(quiz_history) + get_per_question = fetch(`${ENDPOINT}/retrostat/get_per_question_stats`, { + method: 'POST', + body: JSON.stringify({ week: parseInt(props.quizDescriptor.name.substring(1)) }), + headers: { + 'Content-Type': 'application/json', + }, + }) + } + return ( + + ) + } + + return ( + + ) + }} + + ) +} diff --git a/react/src/components/related-button.tsx b/react/src/components/related-button.tsx index fe0cbff4..e82a8974 100644 --- a/react/src/components/related-button.tsx +++ b/react/src/components/related-button.tsx @@ -1,87 +1,79 @@ +import React, { ReactNode } from 'react' -import React from 'react'; +import './related.css' +import { article_link } from '../navigation/links' +import { relationship_key, useSetting } from '../page_template/settings' +import { useUniverse } from '../universe' +import { lighten } from '../utils/color' +import { mobileLayout } from '../utils/responsive' -import { article_link } from "../navigation/links"; -import { CheckboxSetting } from "./sidebar"; - -import "./related.css"; -import { mobileLayout } from '../utils/responsive'; -import { lighten } from '../utils/color'; -import { useSetting, relationship_key } from '../page_template/settings'; -import { useUniverse } from '../universe'; - - -const type_ordering_idx = require("../data/type_ordering_idx.json"); -const type_to_type_category = require("../data/type_to_type_category.json"); +import { CheckboxSetting } from './sidebar' +const type_ordering_idx = require('../data/type_ordering_idx.json') as Record +const type_to_type_category = require('../data/type_to_type_category.json') as Record interface Region { rowType: string, longname: string, shortname: string } -const RED = "#f96d6d"; -const BLUE = "#5a7dc3"; -const ORANGE = "#af6707"; -const PURPLE = "#975ac3"; -const DARK_GRAY = "#4e525a"; -const PINK = "#c767b0"; -const GREEN = "#8ac35a"; -const YELLOW = "#b8a32f"; -const CYAN = "#07a5af"; +const RED = '#f96d6d' +const BLUE = '#5a7dc3' +const ORANGE = '#af6707' +const PURPLE = '#975ac3' +const DARK_GRAY = '#4e525a' +const PINK = '#c767b0' +const GREEN = '#8ac35a' +const YELLOW = '#b8a32f' +const CYAN = '#07a5af' const colorsEach: Record = { - "International": RED, - "US Subdivision": BLUE, - "Census": CYAN, - "Political": PURPLE, - "Oddball": DARK_GRAY, - "Kavi": ORANGE, - "School": YELLOW, - "Small": PINK, - "Native": GREEN, -}; - -function RelatedButton(props: { region: Region }) { + 'International': RED, + 'US Subdivision': BLUE, + 'Census': CYAN, + 'Political': PURPLE, + 'Oddball': DARK_GRAY, + 'Kavi': ORANGE, + 'School': YELLOW, + 'Small': PINK, + 'Native': GREEN, +} - const curr_universe = useUniverse(); - const type_category = type_to_type_category[props.region.rowType]; +function RelatedButton(props: { region: Region }): ReactNode { + const curr_universe = useUniverse() + const type_category = type_to_type_category[props.region.rowType] let classes = `serif button_related` if (mobileLayout()) { - classes += " button_related_mobile"; - } - const color = colorsEach[type_category]; - if (color === undefined) { - throw new Error("color is undefined; rowType is " + props.region.rowType + " and type_category is " + type_category); + classes += ' button_related_mobile' } + const color = colorsEach[type_category] return ( -
  • +
  • {props.region.shortname} + style={{ color: 'black', backgroundColor: lighten(color, 0.7) }} + href={article_link(curr_universe, props.region.longname)} + > + {props.region.shortname}
  • - ); + ) } -function RelatedList(props: { articleType: string, buttonType: string, regions: Record }) { - if (props.articleType == undefined) { - throw new Error("articleType is undefined; shoud be defined"); - } - const setting_key = relationship_key(props.articleType, props.buttonType); - function displayName(name: string) { - name = name.replace("_", " "); +function RelatedList(props: { articleType: string, buttonType: string, regions: Record }): ReactNode { + const setting_key = relationship_key(props.articleType, props.buttonType) + function displayName(name: string): string { + name = name.replace('_', ' ') // title case name = name.replace(/\w\S*/g, function (txt) { - return txt.charAt(0).toUpperCase() + txt.slice(1).toLowerCase(); - }); - return name; + return txt.charAt(0).toUpperCase() + txt.slice(1).toLowerCase() + }) + return name } return (
  • -
    +
    -
    +
    { Object.keys(props.regions).map((relationship_type, j) => { - const regions = props.regions[relationship_type]; + const regions = props.regions[relationship_type] return (
    • {displayName(relationship_type)}
    • { - regions.map((row, i) => + regions.map((row, i) => ( + ), ) }
    - ); - } + ) + }, ) }
  • - ); + ) } -export function Related(props: { article_type: string, related: { relationshipType: string, buttons: Region[] }[] }) { - +export function Related(props: { article_type: string, related: { relationshipType: string, buttons: Region[] }[] }): ReactNode { // buttons[rowType][relationshipType] = - const [showHistoricalCds] = useSetting("show_historical_cds"); - const buttons: Record> = {}; + const [showHistoricalCds] = useSetting('show_historical_cds') + const buttons: Record> = {} for (const relateds of props.related) { - const relationship_type = relateds.relationshipType; + const relationship_type = relateds.relationshipType for (const button of relateds.buttons) { - const row_type = button.rowType; + const row_type = button.rowType if (!(row_type in buttons)) { - buttons[row_type] = {}; + buttons[row_type] = {} } if (!(relationship_type in buttons[row_type])) { - buttons[row_type][relationship_type] = []; + buttons[row_type][relationship_type] = [] } - buttons[row_type][relationship_type].push(button); + buttons[row_type][relationship_type].push(button) } } // get a sorted list of keys of buttons const button_keys = Object.keys(buttons).sort((a, b) => - type_ordering_idx[a] - type_ordering_idx[b] - ); + type_ordering_idx[a] - type_ordering_idx[b], + ) - const elements = []; + const elements = [] for (const key of button_keys) { if (!showHistoricalCds) { - if (key == "Historical Congressional District") { - continue; + if (key === 'Historical Congressional District') { + continue } } elements.push( @@ -161,13 +153,13 @@ export function Related(props: { article_type: string, related: { relationshipTy buttonType={key} regions={buttons[key]} articleType={props.article_type} - /> - ); + />, + ) } return (
    {elements}
    - ); -} \ No newline at end of file + ) +} diff --git a/react/src/components/screenshot.tsx b/react/src/components/screenshot.tsx index 436ea10e..7b8006e8 100644 --- a/react/src/components/screenshot.tsx +++ b/react/src/components/screenshot.tsx @@ -1,148 +1,160 @@ -import React from 'react'; -import domtoimage from 'dom-to-image-more'; -import { saveAs } from 'file-saver'; -import { universe_path } from '../navigation/links'; - - -export function ScreenshotButton(props: { screenshot_mode: boolean, onClick: () => void }) { - const screencap_button =
    - Screenshot Button -
    +import domtoimage from 'dom-to-image-more' +import { saveAs } from 'file-saver' +import React, { ReactNode } from 'react' + +import { universe_path } from '../navigation/links' + +export function ScreenshotButton(props: { screenshot_mode: boolean, onClick: () => void }): ReactNode { + const screencap_button = ( +
    + Screenshot Button +
    + ) // if screenshot mode is on, put a loading circle over the image if (props.screenshot_mode) { - const pad = 10; // pct - const loading_circle =
    - const dim_filter =
    - return
    - {screencap_button} - {dim_filter} - {loading_circle} -
    + const pad = 10 // pct + const loading_circle = ( +
    +
    + ) + const dim_filter = ( +
    +
    + ) + return ( +
    + {screencap_button} + {dim_filter} + {loading_circle} +
    + ) } - return screencap_button; + return screencap_button } export interface ScreencapElements { - path: string, - overall_width: number, - elements_to_render: HTMLElement[], - height_multiplier?: number, + path: string + overall_width: number + elements_to_render: HTMLElement[] + height_multiplier?: number } -export async function create_screenshot(config: ScreencapElements, universe: string | undefined) { - const overall_width = config.overall_width; - const height_multiplier = config.height_multiplier ?? 1; +export async function create_screenshot(config: ScreencapElements, universe: string | undefined): Promise { + const overall_width = config.overall_width + const height_multiplier = config.height_multiplier ?? 1 async function screencap_element(ref: HTMLElement): Promise<[string, number]> { - console.log("Processing element", ref); - const scale_factor = overall_width / ref.offsetWidth; + const scale_factor = overall_width / ref.offsetWidth const link = await domtoimage.toPng(ref, { - bgcolor: "#fff8f0", + bgcolor: '#fff8f0', height: ref.offsetHeight * scale_factor * height_multiplier, width: ref.offsetWidth * scale_factor, style: { - transform: "scale(" + scale_factor + ")", - transformOrigin: "top left" - } - }); + transform: `scale(${scale_factor})`, + transformOrigin: 'top left', + }, + }) return [link, scale_factor * ref.offsetHeight * height_multiplier] } - const png_links = []; - const heights = []; + const png_links = [] + const heights = [] for (const ref of config.elements_to_render) { try { - const [png_link, height] = await screencap_element(ref); - png_links.push(png_link); - heights.push(height); - } catch (e) { - console.log("ERROR"); - console.error(e); + const [png_link, height] = await screencap_element(ref) + png_links.push(png_link) + heights.push(height) + } + catch (e) { + console.error(e) } } - const canvas = document.createElement("canvas"); + const canvas = document.createElement('canvas') - const pad_around = 100; - const pad_between = 50; + const pad_around = 100 + const pad_between = 50 - const banner = new Image(); + const banner = new Image() await new Promise((resolve) => { - banner.onload = () => resolve(); - banner.src = "/screenshot_footer.svg"; + banner.onload = () => { resolve() } + banner.src = '/screenshot_footer.svg' }) - const banner_scale = overall_width / banner.width; - const banner_height = banner.height * banner_scale; + const banner_scale = overall_width / banner.width + const banner_height = banner.height * banner_scale - canvas.width = pad_around * 2 + overall_width; - canvas.height = pad_around + pad_between * (png_links.length - 1) + heights.reduce((a, b) => a + b, 0) + banner_height; + canvas.width = pad_around * 2 + overall_width + canvas.height = pad_around + pad_between * (png_links.length - 1) + heights.reduce((a, b) => a + b, 0) + banner_height + + const ctx = canvas.getContext('2d')! + const imgs = [] - const ctx = canvas.getContext("2d")!; - const imgs = []; for (const png_link of png_links) { - const img = new Image(); - img.src = png_link; - imgs.push(img); + const img = new Image() + img.src = png_link + imgs.push(img) } - ctx.fillStyle = "#fff8f0"; - ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = '#fff8f0' + ctx.fillRect(0, 0, canvas.width, canvas.height) for (const img of imgs) { await new Promise((resolve) => { - img.onload = () => resolve(); + img.onload = () => { resolve() } }) } - let start = pad_around; + let start = pad_around for (const img of imgs) { - ctx.drawImage(img, pad_around, start); - start += img.height + pad_between; + ctx.drawImage(img, pad_around, start) + start += img.height + pad_between } - start -= pad_between; + start -= pad_between - ctx.drawImage(banner, pad_around, start, overall_width, banner_height); + ctx.drawImage(banner, pad_around, start, overall_width, banner_height) - if (universe != undefined) { - const flag = new Image(); - flag.src = universe_path(universe); + if (universe !== undefined) { + const flag = new Image() + flag.src = universe_path(universe) await new Promise((resolve) => { - flag.onload = () => resolve(); + flag.onload = () => { resolve() } }) // draw on bottom left, same height as banner - const flag_height = banner_height / 2; - const offset = flag_height / 2; - const flag_width = flag.width * flag_height / flag.height; - ctx.drawImage(flag, pad_around + offset, start + offset, flag_width, flag_height); + const flag_height = banner_height / 2 + const offset = flag_height / 2 + const flag_width = flag.width * flag_height / flag.height + ctx.drawImage(flag, pad_around + offset, start + offset, flag_width, flag_height) } canvas.toBlob(function (blob) { - saveAs(blob!, config.path); - }); -} \ No newline at end of file + saveAs(blob!, config.path) + }) +} diff --git a/react/src/components/search.tsx b/react/src/components/search.tsx index 10132f9e..250c376c 100644 --- a/react/src/components/search.tsx +++ b/react/src/components/search.tsx @@ -1,134 +1,134 @@ -import React from 'react'; +import React, { CSSProperties, ReactNode } from 'react' -import { loadProtobuf } from '../load_json'; -import { is_historical_cd } from '../utils/is_historical'; -import "../common.css"; -import { SearchIndex } from '../utils/protos'; -import { useSetting } from '../page_template/settings'; +import { loadProtobuf } from '../load_json' +import { useSetting } from '../page_template/settings' +import { is_historical_cd } from '../utils/is_historical' +import '../common.css' +import { SearchIndex } from '../utils/protos' export const SearchBox = (props: { - on_change: (inp: string) => void, autoFocus: boolean, placeholder: string, style: React.CSSProperties -}) => { - - const [show_historical_cds] = useSetting("show_historical_cds"); - const [matches, setMatches] = React.useState([]); - const [matchesStale, setMatchesStale] = React.useState(false); - const [indexCache, setIndexCache] = React.useState(undefined); - const [indexCacheUninitialized, setIndexCacheUninitialized] = React.useState(true); - const [firstCharacter, setFirstCharacter] = React.useState(undefined); - const [focused, setFocused] = React.useState(0); - - const form = React.useRef(null); - const textbox = React.useRef(null); - const dropdown = React.useRef(null); - - - const searchbox_dropdown_item_style = (idx: number) => { + on_change: (inp: string) => void + autoFocus: boolean + placeholder: string + style: React.CSSProperties +}): ReactNode => { + const [show_historical_cds] = useSetting('show_historical_cds') + const [matches, setMatches] = React.useState([]) + const [matchesStale, setMatchesStale] = React.useState(false) + const [indexCache, setIndexCache] = React.useState(undefined) + const [indexCacheUninitialized, setIndexCacheUninitialized] = React.useState(true) + const [firstCharacter, setFirstCharacter] = React.useState(undefined) + const [focused, setFocused] = React.useState(0) + + const form = React.useRef(null) + const textbox = React.useRef(null) + const dropdown = React.useRef(null) + + const searchbox_dropdown_item_style = (idx: number): CSSProperties => { return { - padding: "0.5em", - cursor: "pointer", - backgroundColor: (focused === idx) ? "#ffe0e0" : undefined - }; + padding: '0.5em', + cursor: 'pointer', + backgroundColor: (focused === idx) ? '#ffe0e0' : undefined, + } } - const onFormSubmit = (event: React.FormEvent) => { - event.preventDefault(); - const terms = matches; + const onFormSubmit = (event: React.FormEvent): boolean => { + event.preventDefault() + const terms = matches if (terms.length > 0) { props.on_change(terms[focused]) } - return false; + return false } - const get_input = () => { - let input = textbox.current!.value; - input = normalize(input); - return input; + const get_input = (): string => { + let input = textbox.current!.value + input = normalize(input) + return input } - const reload_cache = () => { - const input = get_input(); - if (input == '') { - setIndexCacheUninitialized(false); - setMatchesStale(false); - setMatches([]); - return; + const reload_cache = (): void => { + const input = get_input() + if (input === '') { + setIndexCacheUninitialized(false) + setMatchesStale(false) + setMatches([]) + return } - const currentFirstCharacter = input[0]; - if (firstCharacter != currentFirstCharacter) { - setFirstCharacter(currentFirstCharacter); - (async () => { - setFirstCharacter(currentFirstCharacter); - setIndexCache(await loadProtobuf(`/index/pages_${currentFirstCharacter}.gz`, "SearchIndex")); - setIndexCacheUninitialized(false); - setMatchesStale(true); - })(); - return; + const currentFirstCharacter = input[0] + if (firstCharacter !== currentFirstCharacter) { + setFirstCharacter(currentFirstCharacter) + void (async () => { + setFirstCharacter(currentFirstCharacter) + setIndexCache(await loadProtobuf(`/index/pages_${currentFirstCharacter}.gz`, 'SearchIndex')) + setIndexCacheUninitialized(false) + setMatchesStale(true) + })() + return } if (indexCacheUninitialized) { - return; + return } } - const onTextBoxKeyUp = (event: React.KeyboardEvent) => { - - reload_cache(); + const onTextBoxKeyUp = (event: React.KeyboardEvent): void => { + reload_cache() // this.setState({ matches_stale: true }); - setMatchesStale(true); + setMatchesStale(true) // if down arrow, then go to the next one - const dropdowns = document.getElementsByClassName("searchbox-dropdown-item"); + const dropdowns = document.getElementsByClassName('searchbox-dropdown-item') if (dropdowns.length > 0) { - if (event.key == "ArrowDown") { + if (event.key === 'ArrowDown') { setFocused((focused + 1) % dropdowns.length) } - if (event.key == "ArrowUp") { + if (event.key === 'ArrowUp') { setFocused((focused - 1) % dropdowns.length) } } } - const update_matches = async () => { - const input = get_input(); - if (input == '') { + const update_matches = (): void => { + const input = get_input() + if (input === '') { if (matches.length > 0) { - setMatches([]); + setMatches([]) } - return; + return } - const values = indexCache!.elements; - const priorities = indexCache!.priorities; - let matches_new = []; + const values = indexCache!.elements + const priorities = indexCache!.priorities + let matches_new = [] for (let i = 0; i < values.length; i++) { - const match_count = is_a_match(input, normalize(values[i])); - if (match_count == 0) { - continue; + const match_count = is_a_match(input, normalize(values[i])) + if (match_count === 0) { + continue } if (!show_historical_cds) { if (is_historical_cd(values[i])) { - continue; + continue } } if (is_international_duplicate(values[i])) { - continue; + continue } - matches_new.push([match_count, i, match_count - priorities[i] / 10]); + matches_new.push([match_count, i, match_count - priorities[i] / 10]) } - matches_new = top_10(matches_new); - matches_new = matches_new.map(idx => values[idx]); - setMatches(matches_new); - setMatchesStale(false); + matches_new = top_10(matches_new) + matches_new = matches_new.map(idx => values[idx]) + setMatches(matches_new) + setMatchesStale(false) } if (matchesStale && !indexCacheUninitialized) { - update_matches(); + update_matches() } return (
    -
    + > { matches.map((location, idx) => - ( -
    props.on_change(matches[idx])} - onMouseOver={() => - setFocused(idx) - } - > {location}
    - ) + ( +
    { props.on_change(matches[idx]) }} + onMouseOver={() => { setFocused(idx) }} + > + {' '} + {location} + {' '} + +
    + ), ) }
    - ); + ) } -function top_10(matches: number[][]) { - const num_prioritized = 3; +function top_10(matches: number[][]): number[] { + const num_prioritized = 3 const sort_key = (idx: number) => { return (a: number[], b: number[]) => { - if (a[idx] != b[idx]) { - return b[idx] - a[idx]; + if (a[idx] !== b[idx]) { + return b[idx] - a[idx] } - return a[1] - b[1]; + return a[1] - b[1] } - }; - matches.sort(sort_key(2)); - const overall_matches = []; + } + matches.sort(sort_key(2)) + const overall_matches = [] for (let i = 0; i < Math.min(num_prioritized, matches.length); i++) { - overall_matches.push(matches[i][1]); - matches[i][0] = -100; + overall_matches.push(matches[i][1]) + matches[i][0] = -100 } - matches.sort(sort_key(0)); + matches.sort(sort_key(0)) for (let i = 0; i < Math.min(10 - num_prioritized, matches.length); i++) { - if (matches[i][0] == -100) { - break; + if (matches[i][0] === -100) { + break } - overall_matches.push(matches[i][1]); + overall_matches.push(matches[i][1]) } - return overall_matches; + return overall_matches } - /* Check whether a is a substring of b (does not have to be contiguous) - + */ -function is_a_match(a: string, b: string) { - let i = 0; - let match_count = 0; - let prev_match = true; - // eslint-disable-next-line @typescript-eslint/prefer-for-of +function is_a_match(a: string, b: string): number { + let i = 0 + let match_count = 0 + let prev_match = true + // eslint-disable-next-line @typescript-eslint/prefer-for-of -- b is a string for (let j = 0; j < b.length; j++) { - if (a[i] == b[j]) { - i++; + if (a[i] === b[j]) { + i++ if (prev_match) { - match_count++; + match_count++ } - prev_match = true; - } else { - prev_match = false; + prev_match = true + } + else { + prev_match = false } - if (i == a.length) { - return match_count + 1; + if (i === a.length) { + return match_count + 1 } } - return 0; + return 0 } -function normalize(a: string) { - return a.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, ""); +function normalize(a: string): string { + return a.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '') } -function is_international_duplicate(x: string) { +function is_international_duplicate(x: string): boolean { // ends with [SN], USA - return x.endsWith(" [SN], USA"); -} \ No newline at end of file + return x.endsWith(' [SN], USA') +} diff --git a/react/src/components/sidebar.tsx b/react/src/components/sidebar.tsx index f4623fae..0dbdfcdb 100644 --- a/react/src/components/sidebar.tsx +++ b/react/src/components/sidebar.tsx @@ -1,20 +1,20 @@ -import React from 'react'; +import React, { ReactNode } from 'react' -import "../style.css"; -import "./sidebar.css"; -import { mobileLayout } from '../utils/responsive'; -import { SettingsDictionary, useSetting, useStatisticCategoryMetadataCheckboxes } from '../page_template/settings'; +import '../style.css' +import './sidebar.css' +import { SettingsDictionary, useSetting, useStatisticCategoryMetadataCheckboxes } from '../page_template/settings' +import { mobileLayout } from '../utils/responsive' -export function Sidebar() { - const statistic_category_metadata_checkboxes = useStatisticCategoryMetadataCheckboxes(); - let sidebar_section_content = "sidebar-section-content"; - let sidebar_section_title = "sidebar-section-title"; +export function Sidebar(): ReactNode { + const statistic_category_metadata_checkboxes = useStatisticCategoryMetadataCheckboxes() + let sidebar_section_content = 'sidebar-section-content' + let sidebar_section_title = 'sidebar-section-title' if (mobileLayout()) { - sidebar_section_content += " sidebar-section-content_mobile"; - sidebar_section_title += " sidebar-section-title_mobile"; + sidebar_section_content += ' sidebar-section-content_mobile' + sidebar_section_title += ' sidebar-section-title_mobile' } return ( -
    +
    Main Menu
      @@ -83,53 +83,56 @@ export function Sidebar() {
      Statistic Categories
        - {statistic_category_metadata_checkboxes.map((checkbox, i) => + {statistic_category_metadata_checkboxes.map((checkbox, i) => (
      • + ), )}
    - ); + ) } // type representing a key of SettingsDictionary that have boolean values -type BooleanSettingKey = keyof { [K in keyof SettingsDictionary as SettingsDictionary[K] extends boolean ? K : never]: boolean }; +type BooleanSettingKey = keyof { [K in keyof SettingsDictionary as SettingsDictionary[K] extends boolean ? K : never]: boolean } -export function CheckboxSetting(props: { name: string, setting_key: K, classNameToUse?: string }) { +export function CheckboxSetting(props: { name: string, setting_key: K, classNameToUse?: string }): ReactNode { + const [checked, setChecked] = useSetting(props.setting_key) - const [checked, setChecked] = useSetting(props.setting_key); - - return } - set_setting={(key, value) => { - if (key === props.setting_key) { - setChecked(value); - } else { - throw new Error("Invalid key: " + key); - } - }} - classNameToUse={props.classNameToUse} - />; + return ( + } + set_setting={(key, value) => { + if (key === props.setting_key) { + setChecked(value) + } + else { + throw new Error(`Invalid key: ${key}`) + } + }} + classNameToUse={props.classNameToUse} + /> + ) }; -export function CheckboxSettingCustom(props: { name: string, setting_key: K, settings: Record, set_setting: (key: K, value: boolean) => void, classNameToUse?: string }) { +export function CheckboxSettingCustom(props: { name: string, setting_key: K, settings: Record, set_setting: (key: K, value: boolean) => void, classNameToUse?: string }): ReactNode { // like CheckboxSetting, but doesn't use useSetting, instead using the callbacks return ( -
    +
    { props.set_setting(props.setting_key, e.target.checked) }} - style={{ accentColor: "#5a7dc3" }} + onChange={(e) => { props.set_setting(props.setting_key, e.target.checked) }} + style={{ accentColor: '#5a7dc3' }} />
    - ); + ) }; diff --git a/react/src/components/statistic-panel.tsx b/react/src/components/statistic-panel.tsx index 0b2b63a9..baf1f273 100644 --- a/react/src/components/statistic-panel.tsx +++ b/react/src/components/statistic-panel.tsx @@ -1,207 +1,219 @@ -import React, { CSSProperties, useMemo, useRef } from 'react'; +import React, { CSSProperties, ReactNode, useMemo, useRef } from 'react' -import { PageTemplate } from "../page_template/template"; -import "../common.css"; -import "./article.css"; -import { headerTextClass, subHeaderTextClass } from '../utils/responsive'; -import { article_link, explanation_page_link, sanitize, statistic_link } from '../navigation/links'; -import { Percentile, Statistic } from './table'; -import { display_type } from '../utils/text'; -import { useSetting } from '../page_template/settings'; -import { useUniverse } from '../universe'; +import { article_link, explanation_page_link, sanitize, statistic_link } from '../navigation/links' +import { useSetting } from '../page_template/settings' +import { PageTemplate } from '../page_template/template' +import '../common.css' +import './article.css' +import { useUniverse } from '../universe' +import { headerTextClass, subHeaderTextClass } from '../utils/responsive' +import { display_type } from '../utils/text' -const table_style = { display: "flex", flexDirection: "column", padding: "1px" } as const; -const column_names = ["Ordinal", "Name", "Value", "", "Percentile"]; -const column_widths = ["15%", "60%", "20%", "10%", "20%"]; +import { Percentile, Statistic } from './table' + +const table_style = { display: 'flex', flexDirection: 'column', padding: '1px' } as const +const column_names = ['Ordinal', 'Name', 'Value', '', 'Percentile'] +const column_widths = ['15%', '60%', '20%', '10%', '20%'] const column_styles = [ - { textAlign: "right", paddingRight: "1em" }, - { textAlign: "left" }, - { textAlign: "right" }, - { textAlign: "left" }, - { textAlign: "right" } -] as const; + { textAlign: 'right', paddingRight: '1em' }, + { textAlign: 'left' }, + { textAlign: 'right' }, + { textAlign: 'left' }, + { textAlign: 'right' }, +] as const -export function StatisticPanel(props: { - start: number, - amount: number, - count: number, - ordering: 'ascending' | 'descending', - joined_string: string, - statname: string, - article_type: string, - article_names: string[], - highlight: string | undefined, - rendered_statname: string, +export function StatisticPanel(props: { + start: number + amount: number + count: number + ordering: 'ascending' | 'descending' + joined_string: string + statname: string + article_type: string + article_names: string[] + highlight: string | undefined + rendered_statname: string data: { - value: number[], + value: number[] populationPercentile: number[] } explanation_page: string -}) { - - const headers_ref = useRef(null); - const table_ref = useRef(null); +}): ReactNode { + const headers_ref = useRef(null) + const table_ref = useRef(null) const is_ascending = props.ordering === 'ascending' const index_range = useMemo(() => { - const start = props.start - 1; - let end = start + props.amount; + const start = props.start - 1 + let end = start + props.amount if (end + props.amount >= props.count) { - end = props.count; + end = props.count } - const total = props.count; + const total = props.count const result = Array.from({ length: end - start }, (_, i) => { if (is_ascending) { - return total - i - 1; + return total - i - 1 } - return start + i; - }); - return result; - }, [props.start, props.amount, props.count, is_ascending]); + return start + i + }) + return result + }, [props.start, props.amount, props.count, is_ascending]) - const swap_ascending_descending = (curr_universe: string | undefined) => { - const new_order = is_ascending ? "ascending" : "descending"; + const swap_ascending_descending = (curr_universe: string | undefined): void => { + const new_order = is_ascending ? 'ascending' : 'descending' document.location = statistic_link( curr_universe, props.statname, props.article_type, 1, props.amount, new_order, - undefined - ); + undefined, + ) } - const background_color = (row_idx: number) => { + const background_color = (row_idx: number): string => { if (row_idx > 0) { - const name_at_idx = props.article_names[index_range[row_idx - 1]]; + const name_at_idx = props.article_names[index_range[row_idx - 1]] if (name_at_idx === props.highlight) { - return "#d4b5e2"; + return '#d4b5e2' } } if (row_idx % 2 === 1) { - return "#f7f1e8"; + return '#f7f1e8' } - return "#fff8f0"; + return '#fff8f0' } - const style = (col_idx: number, row_idx: number) => { - let style: CSSProperties = { ...table_style }; - if (row_idx == 0) { + const style = (col_idx: number, row_idx: number): CSSProperties => { + let result: CSSProperties = { ...table_style } + if (row_idx === 0) { // header, add a line at the bottom - style.borderBottom = "1px solid #000"; - style.fontWeight = 500; + result.borderBottom = '1px solid #000' + result.fontWeight = 500 } - style.backgroundColor = background_color(row_idx); - style.width = column_widths[col_idx]; - style = { ...style, ...column_styles[col_idx] }; - return style; + result.backgroundColor = background_color(row_idx) + result.width = column_widths[col_idx] + result = { ...result, ...column_styles[col_idx] } + return result } - return ({ - path: sanitize(props.joined_string) + ".png", - overall_width: table_ref.current!.offsetWidth * 2, - elements_to_render: [headers_ref.current!, table_ref.current!], - })} - has_universe_selector={true} - universes={require("../data/universes_ordered.json")} - >{() => -
    -
    -
    {props.rendered_statname}
    - {/* // TODO plural */} - -
    -
    -
    -
    - {column_names.map((name, i) => { - if (i === 0) { - return
    -
    {name}
    - swap_ascending_descending(curr_universe)} is_ascending={is_ascending} /> -
    + return ( + ({ + path: `${sanitize(props.joined_string)}.png`, + overall_width: table_ref.current!.offsetWidth * 2, + elements_to_render: [headers_ref.current!, table_ref.current!], + })} + has_universe_selector={true} + universes={require('../data/universes_ordered.json') as string[]} + > + {() => ( +
    +
    +
    {props.rendered_statname}
    + {/* // TODO plural */} + +
    +
    +
    +
    + {column_names.map((name, i) => { + if (i === 0) { + return ( +
    +
    {name}
    + { swap_ascending_descending(curr_universe) }} is_ascending={is_ascending} /> +
    + ) + } + return
    {name}
    + })} +
    + { + index_range.map((i, row_idx) => ( +
    +
    {i + 1}
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + )) } - return
    {name}
    - })} +
    +
    +
    - { - index_range.map((i, row_idx) =>
    -
    {i + 1}
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    )} -
    -
    - -
    - } - - + )} + + ) } -function Pagination(props: { - start: number, - count: number, - amount: number, - explanation_page: string, - statname: string, +function Pagination(props: { + start: number + count: number + amount: number + explanation_page: string + statname: string article_type: string ordering: string | undefined -}) { +}): ReactNode { // next and previous buttons, along with the current range (editable to jump to a specific page) // also a button to change the number of items per page - const change_start = (curr_universe: string | undefined, new_start: number) => { + const change_start = (curr_universe: string | undefined, new_start: number): void => { document.location.href = statistic_link( curr_universe, props.statname, props.article_type, - new_start, props.amount, props.ordering, undefined - ); + new_start, props.amount, props.ordering, undefined, + ) } - const change_amount = (curr_universe: string | undefined, new_amount: "All" | string | number) => { - let start = props.start; - let new_amount_num: number; - if (new_amount === "All") { - start = 1; - new_amount_num = props.count; - } else if (typeof new_amount === "string") { - new_amount_num = parseInt(new_amount); - } else { + const change_amount = (curr_universe: string | undefined, new_amount: string | number): void => { + let start = props.start + let new_amount_num: number + if (new_amount === 'All') { + start = 1 + new_amount_num = props.count + } + else if (typeof new_amount === 'string') { + new_amount_num = parseInt(new_amount) + } + else { new_amount_num = new_amount } if (start > props.count - new_amount_num) { - start = props.count - new_amount_num + 1; + start = props.count - new_amount_num + 1 } document.location.href = statistic_link( curr_universe, @@ -210,155 +222,192 @@ function Pagination(props: { start, new_amount === 'All' ? 'All' : new_amount_num, props.ordering, - undefined - ); + undefined, + ) } - const current = props.start; - const total = props.count; - const per_page = props.amount; - const prev = Math.max(1, current - per_page); - const max_pages = Math.floor(total / per_page); - const max_page_start = (max_pages - 1) * per_page + 1; - const next = Math.min(max_page_start, current + per_page); - const current_page = Math.ceil(current / per_page); - + const current = props.start + const total = props.count + const per_page = props.amount + const prev = Math.max(1, current - per_page) + const max_pages = Math.floor(total / per_page) + const max_page_start = (max_pages - 1) * per_page + 1 + const next = Math.min(max_page_start, current + per_page) + const current_page = Math.ceil(current / per_page) - const select_page = change_start(curr_universe, new_start)} - current_page={current_page} - max_pages={max_pages} - prev_page={prev} - next_page={next} - per_page={per_page} - /> + const select_page = ( + { change_start(curr_universe, new_start) }} + current_page={current_page} + max_pages={max_pages} + prev_page={prev} + next_page={next} + per_page={per_page} + /> + ) // align the entire div to the center. not flex. - return
    -
    -
    - Data Explanation and Credit + return ( + -
    -
    - {select_page} +
    +
    + {select_page} +
    +
    +
    + { change_amount(curr_universe, new_amount) }} + />
    -
    - change_amount(curr_universe, new_amount)} - /> -
    -
    + ) } -function PerPageSelector(props: { - per_page: number, - total: number, +function PerPageSelector(props: { + per_page: number + total: number change_amount: (curr_universe: string | undefined, targetValue: string) => void -}) { - const curr_universe = useUniverse(); - return
    - per page -
    +}): ReactNode { + const curr_universe = useUniverse() + return ( +
    + + + {' '} + per page + +
    + ) } -function SelectPage(props: { +function SelectPage(props: { prev_page: number current_page: number max_pages: number per_page: number change_start: (curr_universe: string | undefined, new_start: number) => void next_page: number -}) { +}): ReactNode { // low-key style for the buttons const button_style = { - backgroundColor: "#f7f1e8", - border: "1px solid #000", - padding: "0 0.5em", - margin: "0.5em" - }; + backgroundColor: '#f7f1e8', + border: '1px solid #000', + padding: '0 0.5em', + margin: '0.5em', + } - const curr_universe = useUniverse(); - return
    - -
    - Page: - { - if (e.key === "Enter") { - let new_page = parseInt((e.target as HTMLInputElement).value); - if (new_page < 1) { - new_page = 1; - } - if (new_page > props.max_pages) { - new_page = props.max_pages; + const curr_universe = useUniverse() + return ( +
    + +
    + Page: + { + if (e.key === 'Enter') { + let new_page = parseInt((e.target as HTMLInputElement).value) + if (new_page < 1) { + new_page = 1 + } + if (new_page > props.max_pages) { + new_page = props.max_pages + } + const new_start = (new_page - 1) * props.per_page + 1 + props.change_start(curr_universe, new_start) } - const new_start = (new_page - 1) * props.per_page + 1; - props.change_start(curr_universe, new_start); - } - }} /> - of {props.max_pages} + }} + /> + + {' of '} + {props.max_pages} + +
    +
    - -
    + ) } -function ArticleLink(props: { longname: string }) { - const curr_universe = useUniverse(); - return {props.longname} +function ArticleLink(props: { longname: string }): ReactNode { + const curr_universe = useUniverse() + return ( + + {props.longname} + + ) } -function StatisticPanelSubhead(props: { article_type: string, rendered_order: string }) { - const curr_universe = useUniverse(); - return
    - {display_type(curr_universe, props.article_type)} ({props.rendered_order}) -
    +function StatisticPanelSubhead(props: { article_type: string, rendered_order: string }): ReactNode { + const curr_universe = useUniverse() + return ( +
    + {display_type(curr_universe, props.article_type)} + {' '} + ( + {props.rendered_order} + ) +
    + ) } -function AutoPercentile(props: { - ordinal: number, - total_count_in_class: number, +function AutoPercentile(props: { + ordinal: number + total_count_in_class: number data: { populationPercentile: number[] } i: number -}) { - const [simple_ordinals] = useSetting("simple_ordinals"); - return +}): ReactNode { + const [simple_ordinals] = useSetting('simple_ordinals') + return ( + + ) } -function AscendingVsDescending({ on_click, is_ascending }: { on_click: (curr_universe: string | undefined) => void, is_ascending: boolean }) { - const curr_universe = useUniverse(); +function AscendingVsDescending({ on_click, is_ascending }: { on_click: (curr_universe: string | undefined) => void, is_ascending: boolean }): ReactNode { + const curr_universe = useUniverse() // either an up or down arrow, depending on the current ordering - return
    -
    on_click(curr_universe)}> - {is_ascending ? "▲" : "▼"} + return ( +
    +
    { on_click(curr_universe) }}> + {is_ascending ? '▲' : '▼'} +
    -
    -} \ No newline at end of file + ) +} diff --git a/react/src/components/table.tsx b/react/src/components/table.tsx index 296a8a03..d773e64a 100644 --- a/react/src/components/table.tsx +++ b/react/src/components/table.tsx @@ -1,431 +1,543 @@ -import React, { useState, useRef } from 'react'; +import React, { ReactNode, useRef, useState } from 'react' import ContentEditable, { ContentEditableEvent } from 'react-contenteditable' -import { article_link, statistic_link } from "../navigation/links"; -import { load_ordering } from '../load_json'; -import "./table.css"; -import { is_historical_cd } from '../utils/is_historical'; -import { display_type } from '../utils/text'; -import { ArticleRow } from './load-article'; -import { row_expanded_key, useSetting } from '../page_template/settings'; -import { useUniverse } from '../universe'; -import { WithPlot } from './plots'; +import { load_ordering } from '../load_json' +import { article_link, statistic_link } from '../navigation/links' +import './table.css' +import { row_expanded_key, useSetting } from '../page_template/settings' +import { useUniverse } from '../universe' +import { is_historical_cd } from '../utils/is_historical' +import { display_type } from '../utils/text' + +import { ArticleRow } from './load-article' +import { WithPlot } from './plots' const table_row_style: React.CSSProperties = { - display: "flex", - flexDirection: "row", -}; + display: 'flex', + flexDirection: 'row', +} export type StatisticRowRawProps = { simple: boolean only_columns?: string[] - _idx: number, + _idx: number statistic_style?: React.CSSProperties onReplace?: (newValue: string) => void } & ( ( { - is_header: false, simple: boolean + is_header: false + simple: boolean } & ArticleRow ) | { is_header: true } ) -export function StatisticRowRaw(props: StatisticRowRawProps & { index: number, longname?: string, shortname?: string, screenshot_mode: boolean }) { - - const [expanded] = useSetting(row_expanded_key(props.is_header ? "header" : props.statname)); - - const cell_contents = StatisticRowRawCellContents({ ...props, total_width: 100, screenshot_mode: props.screenshot_mode }); +export function StatisticRowRaw(props: StatisticRowRawProps & { index: number, longname?: string, shortname?: string, screenshot_mode: boolean }): ReactNode { + const [expanded] = useSetting(row_expanded_key(props.is_header ? 'header' : props.statname)) - return - - ; + const cell_contents = StatisticRowRawCellContents({ ...props, total_width: 100, screenshot_mode: props.screenshot_mode }) + return ( + + + + ) } export function StatisticRowRawCellContents(props: StatisticRowRawProps & { - total_width: number, longname?: string, screenshot_mode: boolean -}) { - const curr_universe = useUniverse(); - const alignStyle: React.CSSProperties = { textAlign: props.is_header ? "center" : "right" }; + total_width: number + longname?: string + screenshot_mode: boolean +}): React.JSX.Element[] { + const curr_universe = useUniverse() + const alignStyle: React.CSSProperties = { textAlign: props.is_header ? 'center' : 'right' } let value_columns: [number, string, React.ReactNode][] = [ [15, - "statval", -
    - { - props.is_header - ? "Value" - : } + 'statval', +
    + + { + props.is_header + ? 'Value' + : ( + + ) + } -
    +
    , ], [10, - "statval_unit", -
    - { - props.is_header - ? "" - : } + 'statval_unit', +
    + + { + props.is_header + ? '' + : ( + + ) + } -
    - ] +
    , + ], ] if (props.is_header) { - value_columns[0][0] += value_columns[1][0]; - value_columns = [value_columns[0]]; + value_columns[0][0] += value_columns[1][0] + value_columns = [value_columns[0]] } const cells: [number, string, React.ReactNode][] = [ [31, - "statname", - { - props.is_header ? "Statistic" : - - } - + 'statname', + + { + props.is_header + ? 'Statistic' + : ( + + ) + } + , ], ...value_columns, [ props.simple ? 7 : 17, - "statistic_percentile", - { - props.is_header - ? (props.simple ? right_align("%ile") : "Percentile") - : - } + 'statistic_percentile', + + { + props.is_header + ? (props.simple ? right_align('%ile') : 'Percentile') + : ( + + ) + } + , ], [ props.simple ? 8 : 25, - "statistic_ordinal", - { - props.is_header - ? (props.simple ? right_align("Ord") : "Ordinal") - : - } + 'statistic_ordinal', + + { + props.is_header + ? (props.simple ? right_align('Ord') : 'Ordinal') + : ( + + ) + } + , ], [8, - "pointer_in_class", + 'pointer_in_class', props.is_header ? Within Type - : - - + : ( + + + + ), ], [8, - "pointer_overall", + 'pointer_overall', props.is_header ? Overall - : - - - ] - ]; - const cell_percentages: number[] = []; - const cell_contents = []; - for (const i in cells) { - if (props.only_columns && !props.only_columns.includes(cells[i][1])) { - continue; + : ( + + + + ), + ], + ] + const cell_percentages: number[] = [] + const cell_contents = [] + for (const [percentage, column, contents] of cells) { + if (props.only_columns && !props.only_columns.includes(column)) { + continue } - cell_percentages.push(cells[i][0]); - cell_contents.push(cells[i][2]); + cell_percentages.push(percentage) + cell_contents.push(contents) } // normalize cell percentages - const sum = cell_percentages.reduce((a, b) => a + b, 0); - for (const i in cell_percentages) { - cell_percentages[i] = props.total_width * cell_percentages[i] / sum; + const sum = cell_percentages.reduce((a, b) => a + b, 0) + for (const i of cell_percentages.keys()) { + cell_percentages[i] = props.total_width * cell_percentages[i] / sum } const contents = cell_contents.map( (content, i) => { - const sty: React.CSSProperties = { width: cell_percentages[i] + "%", padding: "1px" }; + const sty: React.CSSProperties = { width: `${cell_percentages[i]}%`, padding: '1px' } if (props.is_header) { - sty.textAlign = "center"; + sty.textAlign = 'center' } - return
    - {content} -
    - } - ); - return contents; + return ( +
    + {content} +
    + ) + }, + ) + return contents } export function StatisticName(props: { - statname: string, article_type: string, ordinal: number, - longname: string, rendered_statname: string, - curr_universe: string, - use_toggle: boolean, + statname: string + article_type: string + ordinal: number + longname: string + rendered_statname: string + curr_universe: string + use_toggle: boolean screenshot_mode: boolean -}) { - const [expanded, setExpanded] = useSetting(row_expanded_key(props.statname)); - const link = {props.rendered_statname} +}): ReactNode { + const [expanded, setExpanded] = useSetting(row_expanded_key(props.statname)) + const link = ( + + {props.rendered_statname} + + ) if (props.use_toggle && !props.screenshot_mode) { - return - {link} -
    -
    setExpanded!(!expanded)} - style={{ - cursor: "pointer", border: "1px solid black", - padding: 0, borderRadius: "3px", fontSize: "75%", - minWidth: "1.5em", minHeight: "1.5em", textAlign: "center", - lineHeight: "1.2em", - }} + return ( + - {expanded ? "-" : "+"} -
    - + {link} +
    +
    { setExpanded(!expanded) }} + style={{ + cursor: 'pointer', border: '1px solid black', + padding: 0, borderRadius: '3px', fontSize: '75%', + minWidth: '1.5em', minHeight: '1.5em', textAlign: 'center', + lineHeight: '1.2em', + }} + > + {expanded ? '-' : '+'} +
    + + ) } - return link; + return link } export function StatisticRow({ is_header, index, contents }: { is_header: boolean, index: number, contents: React.ReactNode }): React.ReactNode { - return
    - {contents} -
    + return ( +
    + {contents} +
    + ) } -export function Statistic(props: { style?: React.CSSProperties, statname: string, value: number, is_unit: boolean }) { - const [use_imperial] = useSetting("use_imperial"); +export function Statistic(props: { style?: React.CSSProperties, statname: string, value: number, is_unit: boolean }): ReactNode { + const [use_imperial] = useSetting('use_imperial') const content = (() => { { - const name = props.statname; - let value = props.value; - const is_unit = props.is_unit; - if (name.includes("%") || name.includes("Change")) { + const name = props.statname + let value = props.value + const is_unit = props.is_unit + if (name.includes('%') || name.includes('Change')) { if (is_unit) { - return %; + return % } - return {(value * 100).toFixed(2)}; + return {(value * 100).toFixed(2)} } - else if (name.includes("Density")) { - const is_imperial = use_imperial; - let unit_name = "km"; + else if (name.includes('Density')) { + const is_imperial = use_imperial + let unit_name = 'km' if (is_imperial) { - unit_name = "mi"; - value *= 1.60934 * 1.60934; + unit_name = 'mi' + value *= 1.60934 * 1.60934 } - let places = 2; + let places = 2 if (value > 10) { - places = 0; - } else if (value > 1) { - places = 1; + places = 0 + } + else if (value > 1) { + places = 1 } if (is_unit) { - return / {unit_name}2; + return ( + + /  + {unit_name} + 2 + + ) } - return {value.toFixed(places)}; - } else if (name.startsWith("Population")) { + return {value.toFixed(places)} + } + else if (name.startsWith('Population')) { if (value > 1e6) { if (is_unit) { - return m; + return m } - return {(value / 1e6).toFixed(1)}; - } else if (value > 1e3) { + return {(value / 1e6).toFixed(1)} + } + else if (value > 1e3) { if (is_unit) { - return k; + return k } - return {(value / 1e3).toFixed(1)}; - } else { + return {(value / 1e3).toFixed(1)} + } + else { if (is_unit) { - return  ; + return   } - return {value.toFixed(0)}; + return {value.toFixed(0)} } - } else if (name == "Area") { - const is_imperial = use_imperial; - let unit: string | React.ReactElement = "null"; + } + else if (name === 'Area') { + const is_imperial = use_imperial + let unit: string | React.ReactElement = 'null' if (is_imperial) { - value /= 1.60934 * 1.60934; + value /= 1.60934 * 1.60934 if (value < 1) { unit = acres - value *= 640; - } else { - unit = mi2; + value *= 640 } - } else { + else { + unit = ( + + mi + 2 + + ) + } + } + else { if (value < 0.01) { - value *= 1000 * 1000; - unit = m2; - } else { - unit = km2; + value *= 1000 * 1000 + unit = ( + + m + 2 + + ) + } + else { + unit = ( + + km + 2 + + ) } } if (is_unit) { - return unit; - } else { + return unit + } + else { if (value > 100) { return {value.toFixed(0)} - } else if (value > 10) { + } + else if (value > 10) { return {value.toFixed(1)} - } else if (value > 1) { + } + else if (value > 1) { return {value.toFixed(2)} - } else { + } + else { return {value.toFixed(3)} } } - } else if (name.includes("Mean distance")) { - const is_imperial = use_imperial; - let unit = km; + } + else if (name.includes('Mean distance')) { + const is_imperial = use_imperial + let unit = km if (is_imperial) { unit = mi - value /= 1.60934; + value /= 1.60934 } if (is_unit) { - return unit; - } else { + return unit + } + else { return {value.toFixed(2)} } - } else if (name.includes("Election") || name.includes("Swing")) { + } + else if (name.includes('Election') || name.includes('Swing')) { if (is_unit) { - return %; + return % } - return ; - } else if (name.includes("high temp") || name.includes("high heat index") || name.includes("dewpt")) { + return + } + else if (name.includes('high temp') || name.includes('high heat index') || name.includes('dewpt')) { if (is_unit) { - return °F; + return °F } - return {value.toFixed(1)}; - } else if (name == "Mean sunny hours") { + return {value.toFixed(1)} + } + else if (name === 'Mean sunny hours') { if (is_unit) { - return  ; + return   } - const hours = Math.floor(value); - const minutes = Math.floor((value - hours) * 60); + const hours = Math.floor(value) + const minutes = Math.floor((value - hours) * 60) // e.g., 3:05 - return {hours}:{minutes.toString().padStart(2, "0")}; - } else if (name == "Rainfall" || name == "Snowfall [rain-equivalent]") { - const is_imperial = use_imperial; - value *= 100; - let unit = "cm"; + return ( + + {hours} + : + {minutes.toString().padStart(2, '0')} + + ) + } + else if (name === 'Rainfall' || name === 'Snowfall [rain-equivalent]') { + const is_imperial = use_imperial + value *= 100 + let unit = 'cm' if (is_imperial) { - unit = "in"; - value /= 2.54; + unit = 'in' + value /= 2.54 } if (is_unit) { - return {unit}/yr; + return ( + + {unit} + /yr + + ) } - return {value.toFixed(1)}; + return {value.toFixed(1)} } if (is_unit) { - return  ; + return   } - return {value.toFixed(3)}; + return {value.toFixed(3)} } })() if (props.style) { - return {content}; + return {content} } - return content; - + return content } -function ElectionResult(props: { value: number }) { +function ElectionResult(props: { value: number }): ReactNode { // check if value is NaN - if (props.value != props.value) { - return N/A; + if (props.value !== props.value) { + return N/A } - const value = Math.abs(props.value) * 100; - const places = value > 10 ? 1 : value > 1 ? 2 : value > 0.1 ? 3 : 4; - const text = value.toFixed(places); - const party = props.value > 0 ? "D" : "R"; - return {party}+{text}; + const value = Math.abs(props.value) * 100 + const places = value > 10 ? 1 : value > 1 ? 2 : value > 0.1 ? 3 : 4 + const text = value.toFixed(places) + const party = props.value > 0 ? 'D' : 'R' + return ( + + {party} + + + {text} + + ) } -export function Ordinal(props: { ordinal: number, total: number, type: string, statpath: string, onReplace?: (newValue: string) => void, simple: boolean }) { - const curr_universe = useUniverse(); - const onNewNumber = async (number: number) => { - let num = number; +export function Ordinal(props: { ordinal: number, total: number, type: string, statpath: string, onReplace?: (newValue: string) => void, simple: boolean }): ReactNode { + const curr_universe = useUniverse() + const onNewNumber = async (number: number): Promise => { + let num = number if (num < 0) { // -1 -> props.total, -2 -> props.total - 1, etc. - num = props.total + 1 + num; + num = props.total + 1 + num } if (num > props.total) { - num = props.total; + num = props.total } if (num <= 0) { - num = 1; + num = 1 } - const data = await load_ordering(curr_universe, props.statpath, props.type); + const data = await load_ordering(curr_universe, props.statpath, props.type) props.onReplace?.(data[num - 1]) } - const ordinal = props.ordinal; - const total = props.total; - const type = props.type; + const ordinal = props.ordinal + const total = props.total + const type = props.type if (ordinal > total) { return } - const en = ; + const en = ( + + ) if (props.simple) { - return right_align(en); + return right_align(en) } - return
    - {en} of {total} {display_type(curr_universe, type)} -
    ; + return ( +
    + {en} + {' of '} + {total} + {' '} + {display_type(curr_universe, type)} +
    + ) } -function EditableNumber(props: { number: number, onNewNumber: (number: number) => void }) { - const contentEditable: React.Ref = useRef(null); +function EditableNumber(props: { number: number, onNewNumber: (number: number) => void }): ReactNode { + const contentEditable: React.Ref = useRef(null) const [html, setHtml] = useState(props.number.toString()) - const handleChange = (evt: ContentEditableEvent) => { + const handleChange = (evt: ContentEditableEvent): void => { setHtml(evt.target.value) - }; + } return ( { - if (e.key == "Enter") { - const number = parseInt(contentEditable.current!.innerText || ""); + if (e.key === 'Enter') { + const number = parseInt(contentEditable.current!.innerText || '') if (!Number.isNaN(number)) { - props.onNewNumber(number); + props.onNewNumber(number) } - e.preventDefault(); + e.preventDefault() } }} - tagName='span' // Use a custom HTML tag (uses a div by default) + tagName="span" // Use a custom HTML tag (uses a div by default) /> ) }; -export function Percentile(props: { ordinal: number, total: number, percentile_by_population: number, simple: boolean }) { - const ordinal = props.ordinal; - const total = props.total; +export function Percentile(props: { ordinal: number, total: number, percentile_by_population: number, simple: boolean }): ReactNode { + const ordinal = props.ordinal + const total = props.total if (ordinal > total) { return } // percentile as an integer // used to be keyed by a setting, but now we always use percentile_by_population - const quantile = props.percentile_by_population; - const percentile = Math.floor(100 * quantile); + const quantile = props.percentile_by_population + const percentile = Math.floor(100 * quantile) if (props.simple) { - return right_align(percentile.toString() + "%"); + return right_align(`${percentile.toString()}%`) } // something like Xth percentile - let text = percentile + "th percentile"; - if (percentile % 10 == 1 && percentile % 100 != 11) { - text = percentile + "st percentile"; - } else if (percentile % 10 == 2 && percentile % 100 != 12) { - text = percentile + "nd percentile"; - } else if (percentile % 10 == 3 && percentile % 100 != 13) { - text = percentile + "rd percentile"; + let text = `${percentile}th percentile` + if (percentile % 10 === 1 && percentile % 100 !== 11) { + text = `${percentile}st percentile` } - return
    {text}
    ; + else if (percentile % 10 === 2 && percentile % 100 !== 12) { + text = `${percentile}nd percentile` + } + else if (percentile % 10 === 3 && percentile % 100 !== 13) { + text = `${percentile}rd percentile` + } + return
    {text}
    } -function PointerButtonsIndex(props: { ordinal: number, statpath: string, type: string, total: number }) { - const curr_universe = useUniverse(); - const get_data = async () => await load_ordering(curr_universe, props.statpath, props.type); - const [settings_show_historical_cds] = useSetting("show_historical_cds"); - const show_historical_cds = settings_show_historical_cds || is_historical_cd(props.type); +function PointerButtonsIndex(props: { ordinal: number, statpath: string, type: string, total: number }): ReactNode { + const curr_universe = useUniverse() + const get_data = async (): Promise => await load_ordering(curr_universe, props.statpath, props.type) + const [settings_show_historical_cds] = useSetting('show_historical_cds') + const show_historical_cds = settings_show_historical_cds return ( - + - ); + ) } -function PointerButtonIndex(props: { text: string, get_data: () => Promise, original_pos: number, direction: number, total: number, show_historical_cds: boolean }) { - const curr_universe = useUniverse(); - const out_of_bounds = (pos: number) => pos < 0 || pos >= props.total - const onClick = async (pos: number) => { +function PointerButtonIndex(props: { text: string, get_data: () => Promise, original_pos: number, direction: number, total: number, show_historical_cds: boolean }): ReactNode { + const curr_universe = useUniverse() + const out_of_bounds = (pos: number): boolean => pos < 0 || pos >= props.total + const onClick = async (pos: number): Promise => { { - const data = await props.get_data(); + const data = await props.get_data() while (!out_of_bounds(pos)) { - const name = data[pos]; + const name = data[pos] if (!props.show_historical_cds && is_historical_cd(name)) { - pos += props.direction; - continue; + pos += props.direction + continue } - document.location = article_link(curr_universe, name); - return; + document.location = article_link(curr_universe, name) + return } } } - const pos = props.original_pos - 1 + + props.direction; + const pos = props.original_pos - 1 + +props.direction if (out_of_bounds(pos) || props.original_pos > props.total) { return    - } else { + } + else { return ( onClick(pos)}>{props.text} - ); + ) } } -function right_align(value: React.ReactNode) { - return {value}; -} \ No newline at end of file +function right_align(value: React.ReactNode): ReactNode { + return ( + + {value} + + ) +} diff --git a/react/src/data-credit.tsx b/react/src/data-credit.tsx index 64981c94..10f9bfc7 100644 --- a/react/src/data-credit.tsx +++ b/react/src/data-credit.tsx @@ -1,426 +1,502 @@ -import React, { useEffect, useRef } from 'react'; - -import ReactDOM from 'react-dom/client'; -import "./style.css"; -import "./common.css"; -import { PageTemplate } from "./page_template/template"; -import { headerTextClass } from './utils/responsive'; - -const industry_occupation_table = require("./data/explanation_industry_occupation_table.json"); - -function ExplanationTable(props: { name: string, link: string, table: {name: string, description: string}[]}) { - return
    - Details on the {props.name} codes can be found here, - a summary is provided below: -
    -
    - { - props.table.map(({ name, description }, i) => -
    +import React, { ReactNode, useEffect, useRef } from 'react' +import ReactDOM from 'react-dom/client' + +import './style.css' +import './common.css' +import { PageTemplate } from './page_template/template' +import { headerTextClass } from './utils/responsive' + +const industry_occupation_table = require('./data/explanation_industry_occupation_table.json') as { industry: [string, string][], occupation: [string, string][] } + +function ExplanationTable(props: { name: string, link: string, table: [string, string][] }): ReactNode { + return ( +
    + Details on the + {' '} + {props.name} + {' '} + codes can be found + {' '} + here + , + a summary is provided below: +
    +
    + { + props.table.map(([name, description], i) => (
    - {name}
    -
    {description}
    -
    - ) - } +
    + {name} +
    +
    {description}
    +
    + ), + ) + } +
    -
    + ) } -function NRef({ children, name, h: Header = "h2" }: { children: React.ReactNode, name: string, h?: 'h1' | 'h2' }) { - const ref = useRef(null); +function NRef({ children, name, h: Header = 'h2' }: { children: React.ReactNode, name: string, h?: 'h1' | 'h2' }): ReactNode { + const ref = useRef(null) - const highlighted = window.location.hash?.substring(1) === `explanation_${name}`; + const highlighted = window.location.hash.substring(1) === `explanation_${name}` useEffect(() => { if (highlighted && ref.current !== null) { - ref.current.scrollIntoView(); + ref.current.scrollIntoView() } - }, [highlighted, ref.current]); + }, [highlighted]) - return
    {children}
    + return
    {children}
    } -function DataCreditPanel() { - - return {() => -
    -
    Credits
    - -

    Code contributors

    -

    - Special thanks to Luke Brody  - for helping with the build system (I'm hopeless with this stuff) and to  - glacialcascade  - for identifying and correcting a bug in the code. -

    - -

    Geography

    -
    -

    Shapefiles

    +function DataCreditPanel(): ReactNode { + return ( + + {() => ( +
    +
    Credits
    + +

    Code contributors

    +

    + Special thanks to + {' '} + Luke Brody +   + for helping with the build system (I'm hopeless with this stuff) and to  + glacialcascade +   + for identifying and correcting a bug in the code. +

    + +

    Geography

    -

    - Shapefiles on States, MSAs, CSAs, Counties, County subdivisions, Cities (CDPs), - Zip Codes (ZCTAs), Native Reservations, Native Reservation Subdivisions, - School Districts, Congressional Districts, and State Legislative Districts - are from the 2020 Census. USDA County Type shapefiles are aggregated from - county shapefiles, using the definitions from the USDA. -

    -

    - Shapefiles on Judicial Districts are from the HIFLD Open Data Portal. - Neighborhood shapefiles are from the 2017 Zillow Neighborhood Boundaries. - Shapefiles on Census Tracts, Census Block Groups, and Census Blocks are from the 2010 Census. - Shapefiles on historical congressional districts are mostly from UCLA with some - additions from thee Data Gov portal and the NC legislature. Media market - shapefiles are from Kenneth C Black. -

    -

    Shapefiles on Medicare regions (Hospital Referral Regions and Hospital Service Areas) come from the Dartmouth Atlas. -

    -

    - Subnational shapefiles are from ESRI. - National shapefiles are aggregated from subnational shapefiles. -

    -

    - Urban center shapefiles are sourced from the Global Human Settlement Layer's  - - Urban Centre Database v1.2 - .  - We filtered this dataset for urban centers with a quality code (QA2_1V) of 1, indicating a true - positive, and which are named. -

    -

    - The population circles were defined using the GHS-POP dataset, using an algorithm hand-coded - for the purpose of this website. Detailed maps and JSON files are available at  - - the GitHub repository - . -

    +

    Shapefiles

    +
    +

    + Shapefiles on States, MSAs, CSAs, Counties, County subdivisions, Cities (CDPs), + Zip Codes (ZCTAs), Native Reservations, Native Reservation Subdivisions, + School Districts, Congressional Districts, and State Legislative Districts + are from the 2020 Census. USDA County Type shapefiles are aggregated from + county shapefiles, using the definitions from + {' '} + the USDA + . +

    +

    + Shapefiles on Judicial Districts are from the HIFLD Open Data Portal. + Neighborhood shapefiles are from the 2017 Zillow Neighborhood Boundaries. + Shapefiles on Census Tracts, Census Block Groups, and Census Blocks are from the 2010 Census. + Shapefiles on historical congressional districts are mostly from UCLA with some + additions from thee Data Gov portal and the NC legislature. Media market + shapefiles are from + {' '} + Kenneth C Black + . +

    +

    + Shapefiles on Medicare regions (Hospital Referral Regions and Hospital Service Areas) come from + the Dartmouth Atlas + . +

    +

    + Subnational shapefiles are from + {' '} + ESRI + . + National shapefiles are aggregated from subnational shapefiles. +

    +

    + Urban center shapefiles are sourced from the Global Human Settlement Layer's  + + Urban Centre Database v1.2 + + .  + We filtered this dataset for urban centers with a quality code (QA2_1V) of 1, indicating a true + positive, and which are named. +

    +

    + The population circles were defined using the GHS-POP dataset, using an algorithm hand-coded + for the purpose of this website. Detailed maps and JSON files are available at  + + the GitHub repository + + . +

    +
    + Geography Metrics +
    +

    + We compute area using the projection + {' '} + CEA + . + This is a projection that preserves area, so we can compute area in square meters. +

    +

    + We compute compactness using the Polsby-Popper score, which is defined as + 4 * pi * area / perimeter^2. This is a standard measure of compactness, that + defines the circle as the most compact shape with a score of 1.0. +

    +
    - Geography Metrics +

    Census Data

    -

    - We compute area using the projection CEA. - This is a projection that preserves area, so we can compute area in square meters. -

    -

    - We compute compactness using the Polsby-Popper score, which is defined as - 4 * pi * area / perimeter^2. This is a standard measure of compactness, that - defines the circle as the most compact shape with a score of 1.0. -

    -
    -
    -

    Census Data

    -
    - All data listed in this section is collected from the 2020 US Census. + All data listed in this section is collected from the 2020 US Census. - Population -
    -

    - We compute population data as the column POP100. This is the total population - by census block. -

    -
    - Density Metrics -
    -

    - AW (area weighted) density is the standard Population/Area density. - PW (population weighted) density with a radius of X is the population-weighted density within - X miles of each census block's interior point, as defined by the census. For more information, - see this page. -

    -
    - Race -
    -

    - Race data is as defined by the census. Here, all the categories other than Hispanic are - specifically non-Hispanic. E.g., White is non-Hispanic White. -

    -
    + Population +
    +

    + We compute population data as the column POP100. This is the total population + by census block. +

    +
    + Density Metrics +
    +

    + AW (area weighted) density is the standard Population/Area density. + PW (population weighted) density with a radius of X is the population-weighted density within + X miles of each census block's interior point, as defined by the census. For more information, + see + {' '} + this page + . +

    +
    + Race +
    +

    + Race data is as defined by the census. Here, all the categories other than Hispanic are + specifically non-Hispanic. E.g., White is non-Hispanic White. +

    +
    - Vacancy and Units Per Adult -
    -

    - We compute vacancy as the percentage of housing units that are vacant. We compute - units per adult as the number of housing units divided by the number of adults. -

    + Vacancy and Units Per Adult +
    +

    + We compute vacancy as the percentage of housing units that are vacant. We compute + units per adult as the number of housing units divided by the number of adults. +

    +
    -
    - -

    American Community Survey Data

    -
    - All data listed in this section is collected from the 2021 American Community Survey 5-year estimates. - Citizenship -
    -

    - We analyze citizenship data by dividing the population into Citizen by Birth, Citizen by Naturalization, - and Not a Citizen. These will always add to 100%. This data is disaggregated from the tract - level to the block level using overall population as a weight. -

    -
    - Birthplace +

    American Community Survey Data

    + All data listed in this section is collected from the 2021 American Community Survey 5-year estimates. + + Citizenship +
    +

    + We analyze citizenship data by dividing the population into Citizen by Birth, Citizen by Naturalization, + and Not a Citizen. These will always add to 100%. This data is disaggregated from the tract + level to the block level using overall population as a weight. +

    +
    + Birthplace +
    + +

    + We analyze birthplace data by dividing the population into Born in State, Born in Other State, + and Born Outside the US. These will always add to 100%. This data is disaggregated from the tract + level to the block level using overall population as a weight. +

    +
    -

    - We analyze birthplace data by dividing the population into Born in State, Born in Other State, - and Born Outside the US. These will always add to 100%. This data is disaggregated from the tract - level to the block level using overall population as a weight. -

    -
    - - Language Spoken at Home -
    -

    - We analyze language data by dividing the population into English Only, Spanish, and Other. - These will always add to 100%. This data is disaggregated from the tract - level to the block level using overall population as a weight. -

    -
    + Language Spoken at Home +
    +

    + We analyze language data by dividing the population into English Only, Spanish, and Other. + These will always add to 100%. This data is disaggregated from the tract + level to the block level using overall population as a weight. +

    +
    - Education -
    -

    - We analyze education data by computing the percentage of the population over 25 with at least  - a high school degree, a bachelor's degree, or a graduate degree. These will not add to 100%. - This data is disaggregated from the block group level to the block level using adult population - as a weight. -

    -

    - We also compute as a percentage of the population the kind of degree the population has. - We group Science and Engineering related fields into STEM, and Education into the - Humanities. - These will not add to 100%. This data is disaggregated from the block group level to the block level - using adult population as a weight. -

    -
    + Education +
    +

    + We analyze education data by computing the percentage of the population over 25 with + {' '} + at least +   + a high school degree, a bachelor's degree, or a graduate degree. These will not add to 100%. + This data is disaggregated from the block group level to the block level using adult population + as a weight. +

    +

    + We also compute as a percentage of the population the kind of degree the population has. + We group Science and Engineering related fields into STEM, and Education into the + Humanities. + These will not add to 100%. This data is disaggregated from the block group level to the block level + using adult population as a weight. +

    +
    - Generation -
    - Generations are defined as follows: -
      -
    • Silent: up to 1946
    • -
    • Boomer: 1946-1966
    • -
    • GenX: 1967-1981
    • -
    • Millenial: 1982-1996
    • -
    • GenZ: 1997-2011
    • -
    • GenAlpha: 2012-2021
    • -
    - These will add to 100%. This data is disaggregated from the block group level to the block level - using overall population as a weight. -
    - Income -
    -

    - We use the census definition of poverty status, disaggregating from the tract level to the block level + Generation +

    + Generations are defined as follows: +
      +
    • Silent: up to 1946
    • +
    • Boomer: 1946-1966
    • +
    • GenX: 1967-1981
    • +
    • Millenial: 1982-1996
    • +
    • GenZ: 1997-2011
    • +
    • GenAlpha: 2012-2021
    • +
    + These will add to 100%. This data is disaggregated from the block group level to the block level using overall population as a weight. -

    -

    - We analyze income data by computing the percentage of the adult population with an income - between $0 and $50k, between $50k and $100k, and above $100k. These will add to 100%. - - We compute both individual and household income, disaggregating from the tract level to the block level - using adult population as a weight for indivividual income and occupied housing units as a weight for household income. -

    -
    +
    + Income +
    +

    + We use the census definition of poverty status, disaggregating from the tract level to the block level + using overall population as a weight. +

    +

    + We analyze income data by computing the percentage of the adult population with an income + between $0 and $50k, between $50k and $100k, and above $100k. These will add to 100%. + + We compute both individual and household income, disaggregating from the tract level to the block level + using adult population as a weight for indivividual income and occupied housing units as a weight for household income. +

    +
    - Transportation -
    -

    - All transportation data is computed using disaggregation from the block group level - to the block level, weighted by adult population. We consider taxi to be a form of - car transportation, and consider motorcycle to be a form of bike transportation. -

    -
    + Transportation +
    +

    + All transportation data is computed using disaggregation from the block group level + to the block level, weighted by adult population. We consider taxi to be a form of + car transportation, and consider motorcycle to be a form of bike transportation. +

    +
    - Health -
    -

    - Health data comes from the CDC's PLACES dataset  - version August 25, 2023, accessed June 1 2024. It is computed using disaggregation from the tract level to block level, using the 2010 census tracts - (I am not sure why the CDC uses 2010 tracts for 2023 data, but that's what they do). This data is inherently estimate based. -

    -
    + Health +
    +

    + Health data comes from the CDC's + {' '} + PLACES dataset +   + version August 25, 2023, accessed June 1 2024. It is computed using disaggregation from the tract level to block level, using the 2010 census tracts + (I am not sure why the CDC uses 2010 tracts for 2023 data, but that's what they do). This data is inherently estimate based. +

    +
    - Industry and Occupation -
    -

    - We disaggregate industry data from the block group level to the block level using population - over 18 as a weight. Numbers are percentages of the employed population. -

    - - - -
    + Industry and Occupation +
    +

    + We disaggregate industry data from the block group level to the block level using population + over 18 as a weight. Numbers are percentages of the employed population. +

    + + + +
    - Housing -
    -

    - All housing statistics are computed using disaggregation from the tract level to the block level, - weighted by occupied housing units. -

    -
    - Internet Access -
    -

    - We analyze internet access data by taking a percentage of households without - internet access, disaggregating from the block group level to the block level, - weighted by occupied housing units. -

    -
    - Insurance Access -
    -

    - We analyze insurance data by dividing the population into those with private insurance, - public insurance, and no insurance. These will always add to 100%. - - We group together Medicare, Medicaid, and other public insurance into public insurance, - and group together employer-based insurance, direct-purchase insurance, and other private - insurance into private insurance. This data is disaggregated from the tract level - to the block level using overall population as a weight. -

    -
    - Marriage -
    -

    - We analyze marriage data by dividing the over-15 population into those who are married, - never married, and divorced. These will always add to 100%. - This data is disaggregated from the tract level to the block level using adult population - as a weight. -

    -
    - Weather -
    -

    - Special thanks to OklahomaPerson for helping understand meterological data and help me - provide a list of statistics to present. -

    -

    - We collected weather data from ERA5, - a reanalysis dataset from the European Centre for - Medium-Range Weather Forecasts. We collect data over the time period 1991-2021 and - over the 0.25 degree grid. -

    -

    - For each grid point, we compute each of our weather statistics. - - We compute mean high temperatures by aggregating the daily high temperature for each day - in the time period, and then taking the mean of these values. We perform a similar - computation for mean high dew point. Using these values, we compute the mean high heat index. - - We compute mean rainfall and snowfall by aggregating the hourly rainfall and snowfall. - Codes 1 (rain) and 2 (freezing rain) are coded as rain, and codes 5 (snow), 6 (wet snow), - and 8 (ice pellets) are coded as snow. Code 7 (mixed) is coded as 50/50 rain/snow. -

    -

    - These estimates are then interpolated to the block level using the census block centroid - using bilinear interpolation. We then compute the population weighted average of these - statistics for each geography. -

    -
    - 2010 Census -
    -

    - 2010 Census data is treated the same way as 2020 Census data. -

    + Housing +
    +

    + All housing statistics are computed using disaggregation from the tract level to the block level, + weighted by occupied housing units. +

    +
    + Internet Access +
    +

    + We analyze internet access data by taking a percentage of households without + internet access, disaggregating from the block group level to the block level, + weighted by occupied housing units. +

    +
    + Insurance Access +
    +

    + We analyze insurance data by dividing the population into those with private insurance, + public insurance, and no insurance. These will always add to 100%. + + We group together Medicare, Medicaid, and other public insurance into public insurance, + and group together employer-based insurance, direct-purchase insurance, and other private + insurance into private insurance. This data is disaggregated from the tract level + to the block level using overall population as a weight. +

    +
    + Marriage +
    +

    + We analyze marriage data by dividing the over-15 population into those who are married, + never married, and divorced. These will always add to 100%. + This data is disaggregated from the tract level to the block level using adult population + as a weight. +

    +
    + Weather +
    +

    + Special thanks to + {' '} + OklahomaPerson + {' '} + for helping understand meterological data and help me + provide a list of statistics to present. +

    +

    + We collected weather data from + {' '} + ERA5 + , + a reanalysis dataset from the European Centre for + Medium-Range Weather Forecasts. We collect data over the time period 1991-2021 and + over the 0.25 degree grid. +

    +

    + For each grid point, we compute each of our weather statistics. + + We compute mean high temperatures by aggregating the daily high temperature for each day + in the time period, and then taking the mean of these values. We perform a similar + computation for mean high dew point. Using these values, we compute the mean high heat index. + + We compute mean rainfall and snowfall by aggregating the hourly rainfall and snowfall. + Codes 1 (rain) and 2 (freezing rain) are coded as rain, and codes 5 (snow), 6 (wet snow), + and 8 (ice pellets) are coded as snow. Code 7 (mixed) is coded as 50/50 rain/snow. +

    +

    + These estimates are then interpolated to the block level using the census block centroid + using bilinear interpolation. We then compute the population weighted average of these + statistics for each geography. +

    +
    + 2010 Census +
    +

    + 2010 Census data is treated the same way as 2020 Census data. +

    +
    -
    - Voting and Elections Science Team Data -
    - Election Data is from the US Elections Project's Voting and Elections Science Team - (VEST). - - Election Data is approximate and uses - VTD estimates when available. Data is precinct-level, disaggregated to the census block level - and then aggregated to the geography of interest based on the centroid. Results might not - match official results. Data is from the 2016 and 2020 US Presidential general elections. N/A - indicates that the statistic is not available for the given geography, possibly because the - precinct boundaries in the dataset are slightly inaccurate, or there are no results for - the precincts overlapping the geography. -
    - Parkland -
    - We compute the percentage of each 1km disc around each census block that is parkland. - We then compute the population weighted average of this statistic for each geography. - Data on parkland is from OSM. Thanks to Ellie for - helping process the park data. -
    -

    Distance from Features

    -
    - We compute two statistics for each census block: the distance to the nearest feature, and - whether the census block is within some distance of the feature. We then compute - the population weighted average of these statistics for each geography. - Hospitals + Voting and Elections Science Team Data
    - Hospital data is from HIFLD via Kaggle. - We pick 10km as the distance threshold for hospitals. + Election Data is from the US Elections Project's Voting and Elections Science Team + ( + VEST + ). + + Election Data is approximate and uses + VTD estimates when available. Data is precinct-level, disaggregated to the census block level + and then aggregated to the geography of interest based on the centroid. Results might not + match official results. Data is from the 2016 and 2020 US Presidential general elections. N/A + indicates that the statistic is not available for the given geography, possibly because the + precinct boundaries in the dataset are slightly inaccurate, or there are no results for + the precincts overlapping the geography.
    - Airports + Parkland
    - -

    - Airport data is from OurAirports via  - ArcGIS Hub -

    + We compute the percentage of each 1km disc around each census block that is parkland. + We then compute the population weighted average of this statistic for each geography. + Data on parkland is from OSM. Thanks to + {' '} + Ellie + {' '} + for + helping process the park data.
    - Transit Stops +

    Distance from Features

    - Train stop data is from OSM. Special thanks to Avery for helping process the train stop - data. + We compute two statistics for each census block: the distance to the nearest feature, and + whether the census block is within some distance of the feature. We then compute + the population weighted average of these statistics for each geography. + Hospitals +
    + Hospital data is from HIFLD via + {' '} + Kaggle + . + We pick 10km as the distance threshold for hospitals. +
    + Airports +
    + +

    + Airport data is from OurAirports via  + ArcGIS Hub +

    +
    + Transit Stops +
    + Train stop data is from OSM. Special thanks to + {' '} + Avery + {' '} + for helping process the train stop + data. +
    + Superfund Sites +
    + Superfund site data is from the EPA via  + + Data Gov + +
    + Schools +
    + School data is from NCES via  + HIFLD + . +
    - Superfund Sites + Gridded Population
    - Superfund site data is from the EPA via  - - Data Gov - + Gridded population data is from + {' '} + + Schiavina M., Freire S., Carioli A., MacManus K. (2023): GHS-POP R2023A - GHS population grid multitemporal (1975-2030).European Commission, Joint Research Centre (JRC) + {/* PID: http://data.europa.eu/89h/2ff68a52-5b5b-4a22-8f40-c41da8332cfe, doi:10.2905/2FF68A52-5B5B-4A22-8F40-C41DA8332CFE */} + PID: + {' '} + http://data.europa.eu/89h/2ff68a52-5b5b-4a22-8f40-c41da8332cfe + , + 10.2905/2FF68A52-5B5B-4A22-8F40-C41DA8332CFE + . + + We use the 2020 population estimates, which are + not perfectly accurate in all cases, but should be the best match to the 2020 Census numbers + we are using for the US data. To compute PW density, we treat each cell as effectively homogenous, + but since the cells are all smaller than 1 square kilometer, this should not be a major issue for + radii above 1km (which is the smallest radius we use for GHS-POP data).
    - Schools +

    Flags

    - School data is from NCES via  - HIFLD. + Every flag for the universe selector is from Wikipedia. All of them are free to use under + any circumstances, at least according to the Wikipedia page for the flag.
    - Gridded Population -
    - Gridded population data is from Schiavina M., Freire S., Carioli A., MacManus K. (2023): GHS-POP R2023A - GHS population grid multitemporal (1975-2030).European Commission, Joint Research Centre (JRC) - {/* PID: http://data.europa.eu/89h/2ff68a52-5b5b-4a22-8f40-c41da8332cfe, doi:10.2905/2FF68A52-5B5B-4A22-8F40-C41DA8332CFE */} - PID: http://data.europa.eu/89h/2ff68a52-5b5b-4a22-8f40-c41da8332cfe, - 10.2905/2FF68A52-5B5B-4A22-8F40-C41DA8332CFE. - We use the 2020 population estimates, which are - not perfectly accurate in all cases, but should be the best match to the 2020 Census numbers - we are using for the US data. To compute PW density, we treat each cell as effectively homogenous, - but since the cells are all smaller than 1 square kilometer, this should not be a major issue for - radii above 1km (which is the smallest radius we use for GHS-POP data). -
    -

    Flags

    -
    - Every flag for the universe selector is from Wikipedia. All of them are free to use under - any circumstances, at least according to the Wikipedia page for the flag. -
    -
    - }; + )} + + ) } -async function loadPage() { - const root = ReactDOM.createRoot(document.getElementById("root")!); - root.render(); +function loadPage(): void { + const root = ReactDOM.createRoot(document.getElementById('root')!) + root.render() } -loadPage(); \ No newline at end of file +loadPage() diff --git a/react/src/index.tsx b/react/src/index.tsx index 5fe24e42..a1ab3b5b 100644 --- a/react/src/index.tsx +++ b/react/src/index.tsx @@ -1,42 +1,62 @@ -import React from 'react'; - -import ReactDOM from 'react-dom/client'; -import "./style.css"; -import "./common.css"; - -import { PageTemplate } from './page_template/template'; - - -function IndexPanel() { - - return {() => -
    -
    - Urban Stats Logo -
    - -
    -

    The Urban Stats is a database of various statistics related to density, housing, and race - in the United States for a variety of regions. It is intended to be a resource for journalists, - researchers, and anyone else who is interested in these topics. The data is collected from the - US Census Bureau's 2020 census; and shapefiles for each region of interest are obtained from - the US Census Bureau's TIGER/Line database; except for the shapefiles for neighborhoods, which - are obtained from Zillow. - - Election Data is from the US Elections Project's - Voting and Elections Science Team - (VEST). -

    -

    Website by Kavi Gupta (kavigupta.org, @notkavi)

    -
    -
    - }
    +import React, { ReactNode } from 'react' +import ReactDOM from 'react-dom/client' +import './style.css' +import './common.css' + +import { PageTemplate } from './page_template/template' + +function IndexPanel(): ReactNode { + return ( + + {() => ( +
    +
    + Urban Stats Logo +
    + +
    +

    + The Urban Stats is a database of various statistics related to density, housing, and race + in the United States for a variety of regions. It is intended to be a resource for journalists, + researchers, and anyone else who is interested in these topics. The data is collected from the + US Census Bureau's 2020 census; and shapefiles for each region of interest are obtained from + the US Census Bureau's TIGER/Line database; except for the shapefiles for neighborhoods, which + are obtained from + {' '} + Zillow + . + + Election Data is from the + {' '} + US Elections Project's + {' '} + Voting and Elections Science Team + ( + VEST + ). +

    +

    + Website by Kavi Gupta ( + kavigupta.org + , + {' '} + + @notkavi + + ) +

    +
    +
    + )} +
    + ) } -async function loadPage() { - const root = ReactDOM.createRoot(document.getElementById("root")!); - root.render(); +function loadPage(): void { + const root = ReactDOM.createRoot(document.getElementById('root')!) + root.render() } -loadPage(); \ No newline at end of file +loadPage() diff --git a/react/src/load_json.ts b/react/src/load_json.ts index 0fde4fa8..88105dac 100644 --- a/react/src/load_json.ts +++ b/react/src/load_json.ts @@ -1,113 +1,112 @@ -export { loadJSON, loadProtobuf, load_ordering_protobuf, load_ordering }; +import { gunzipSync } from 'zlib' -import { gunzipSync } from 'zlib'; +import { index_link, ordering_data_link, ordering_link } from './navigation/links' import { - Article, Feature, StringList, ConsolidatedShapes, - ConsolidatedStatistics, DataLists, OrderLists, SearchIndex, - OrderList, - IDataList, - IOrderList -} from "./utils/protos"; -import { index_link, ordering_data_link, ordering_link } from './navigation/links'; + Article, ConsolidatedShapes, ConsolidatedStatistics, DataLists, + Feature, IDataList, IOrderList, OrderList, + OrderLists, + SearchIndex, + StringList, +} from './utils/protos' // from https://stackoverflow.com/a/4117299/1549476 // Load JSON text from server hosted file and return JSON parsed object -function loadJSON(filePath: string) { +export function loadJSON(filePath: string): unknown { // Load json file; - const json = loadTextFileAjaxSync(filePath, "application/json"); + const json = loadTextFileAjaxSync(filePath, 'application/json') // assert json is a string - if (typeof json !== "string") { - throw "file not found: " + filePath; + if (typeof json !== 'string') { + throw new Error(`file not found: ${filePath}`) } // Parse json - return JSON.parse(json); + return JSON.parse(json) } -// Load text with Ajax synchronously: takes path to file and optional MIME type -function loadTextFileAjaxSync(filePath: string, mimeType: string) { - const xmlhttp = new XMLHttpRequest(); - xmlhttp.open("GET", filePath, false); - if (mimeType != null) { - if (xmlhttp.overrideMimeType) { - xmlhttp.overrideMimeType(mimeType); - } - } - xmlhttp.send(); - if (xmlhttp.status == 200 && xmlhttp.readyState == 4) { - return xmlhttp.responseText; +function loadTextFileAjaxSync(filePath: string, mimeType: string): string | null { + const xmlhttp = new XMLHttpRequest() + xmlhttp.open('GET', filePath, false) + xmlhttp.overrideMimeType(mimeType) + xmlhttp.send() + if (xmlhttp.status === 200 && xmlhttp.readyState === 4) { + return xmlhttp.responseText } else { - // TODO Throw exception - return null; + // TODO Throw exception + return null } } - // Load a protobuf file from the server -async function loadProtobuf(filePath: string, name: "Article"): Promise
    -async function loadProtobuf(filePath: string, name: "Feature"): Promise -async function loadProtobuf(filePath: string, name: "StringList"): Promise -async function loadProtobuf(filePath: string, name: "OrderLists"): Promise -async function loadProtobuf(filePath: string, name: "DataLists"): Promise -async function loadProtobuf(filePath: string, name: "ConsolidatedShapes"): Promise -async function loadProtobuf(filePath: string, name: "ConsolidatedStatistics"): Promise -async function loadProtobuf(filePath: string, name: "SearchIndex"): Promise -async function loadProtobuf(filePath: string, name: string) { - const response = await fetch(filePath); - const compressed_buffer = await response.arrayBuffer(); - const buffer = gunzipSync(Buffer.from(compressed_buffer)); - const arr = new Uint8Array(buffer); - if (name == "Article") { - return Article.decode(arr); - } else if (name == "Feature") { - return Feature.decode(arr); - } else if (name == "StringList") { - return StringList.decode(arr); - } else if (name == "OrderLists") { - return OrderLists.decode(arr); - } else if (name == "DataLists") { - return DataLists.decode(arr); - } else if (name == "ConsolidatedShapes") { - return ConsolidatedShapes.decode(arr); - } else if (name == "ConsolidatedStatistics") { - return ConsolidatedStatistics.decode(arr); - } else if (name == "SearchIndex") { - return SearchIndex.decode(arr); - } else { - throw "protobuf type not recognized (see load_json.ts)"; +export async function loadProtobuf(filePath: string, name: 'Article'): Promise
    +export async function loadProtobuf(filePath: string, name: 'Feature'): Promise +export async function loadProtobuf(filePath: string, name: 'StringList'): Promise +export async function loadProtobuf(filePath: string, name: 'OrderLists'): Promise +export async function loadProtobuf(filePath: string, name: 'DataLists'): Promise +export async function loadProtobuf(filePath: string, name: 'ConsolidatedShapes'): Promise +export async function loadProtobuf(filePath: string, name: 'ConsolidatedStatistics'): Promise +export async function loadProtobuf(filePath: string, name: 'SearchIndex'): Promise +export async function loadProtobuf(filePath: string, name: string): Promise
    { + const response = await fetch(filePath) + const compressed_buffer = await response.arrayBuffer() + const buffer = gunzipSync(Buffer.from(compressed_buffer)) + const arr = new Uint8Array(buffer) + if (name === 'Article') { + return Article.decode(arr) + } + else if (name === 'Feature') { + return Feature.decode(arr) + } + else if (name === 'StringList') { + return StringList.decode(arr) + } + else if (name === 'OrderLists') { + return OrderLists.decode(arr) + } + else if (name === 'DataLists') { + return DataLists.decode(arr) + } + else if (name === 'ConsolidatedShapes') { + return ConsolidatedShapes.decode(arr) + } + else if (name === 'ConsolidatedStatistics') { + return ConsolidatedStatistics.decode(arr) + } + else if (name === 'SearchIndex') { + return SearchIndex.decode(arr) + } + else { + throw new Error('protobuf type not recognized (see load_json.ts)') } } -const order_links = require("./data/order_links.json"); -const data_links = require("./data/data_links.json"); +const order_links = require('./data/order_links.json') as Record +const data_links = require('./data/data_links.json') as Record -async function load_ordering_protobuf(universe: string, statpath: string, type: string, is_data: true): Promise -async function load_ordering_protobuf(universe: string, statpath: string, type: string, is_data: boolean): Promise -async function load_ordering_protobuf(universe: string, statpath: string, type: string, is_data: boolean) { - const links = is_data ? data_links : order_links; - const key = `${universe}__${type}__${statpath}`; - const idx = key in links ? links[key] : 0; - const order_link = is_data ? ordering_data_link(universe, type, idx) : ordering_link(universe, type, idx); +export async function load_ordering_protobuf(universe: string, statpath: string, type: string, is_data: true): Promise +export async function load_ordering_protobuf(universe: string, statpath: string, type: string, is_data: boolean): Promise +export async function load_ordering_protobuf(universe: string, statpath: string, type: string, is_data: boolean): Promise { + const links = is_data ? data_links : order_links + const key = `${universe}__${type}__${statpath}` + const idx = key in links ? links[key] : 0 + const order_link = is_data ? ordering_data_link(universe, type, idx) : ordering_link(universe, type, idx) if (is_data) { - const dataLists = await loadProtobuf(order_link, "DataLists"); - const index = dataLists.statnames.indexOf(statpath); - return dataLists.dataLists[index]; - } else { - const orderLists = await loadProtobuf(order_link, "OrderLists"); - const index = orderLists.statnames.indexOf(statpath); - return orderLists.orderLists[index]; + const dataLists = await loadProtobuf(order_link, 'DataLists') + const index = dataLists.statnames.indexOf(statpath) + return dataLists.dataLists[index] + } + else { + const orderLists = await loadProtobuf(order_link, 'OrderLists') + const index = orderLists.statnames.indexOf(statpath) + return orderLists.orderLists[index] } } -async function load_ordering(universe: string, statpath: string, type: string) { - if (universe == undefined) { - throw "universe is undefined"; - } - const idx_link = index_link(universe, type); - const data_promise = loadProtobuf(idx_link, "StringList"); - const ordering_promise = load_ordering_protobuf(universe, statpath, type, false); - const [data, ordering] = await Promise.all([data_promise, ordering_promise]); - const names_in_order = (ordering as OrderList).orderIdxs.map((i: number) => data.elements[i]); - return names_in_order; -} \ No newline at end of file +export async function load_ordering(universe: string, statpath: string, type: string): Promise { + const idx_link = index_link(universe, type) + const data_promise = loadProtobuf(idx_link, 'StringList') + const ordering_promise = load_ordering_protobuf(universe, statpath, type, false) + const [data, ordering] = await Promise.all([data_promise, ordering_promise]) + const names_in_order = (ordering as OrderList).orderIdxs.map((i: number) => data.elements[i]) + return names_in_order +} diff --git a/react/src/mapper.tsx b/react/src/mapper.tsx index 25af882b..ff5cd687 100644 --- a/react/src/mapper.tsx +++ b/react/src/mapper.tsx @@ -1,16 +1,14 @@ -import React from 'react'; - -import ReactDOM from 'react-dom/client'; -import "./style.css"; -import "./common.css"; +import React from 'react' +import ReactDOM from 'react-dom/client' +import './style.css' +import './common.css' // import { ArticlePanel } from './components/article-panel'; -import { MapperPanel } from './components/mapper-panel'; - +import { MapperPanel } from './components/mapper-panel' -async function loadPage() { - const root = ReactDOM.createRoot(document.getElementById("root")!); - root.render(); +function loadPage(): void { + const root = ReactDOM.createRoot(document.getElementById('root')!) + root.render() } -loadPage(); \ No newline at end of file +loadPage() diff --git a/react/src/mapper/DataListSelector.tsx b/react/src/mapper/DataListSelector.tsx index 0809cf38..42d6c2ef 100644 --- a/react/src/mapper/DataListSelector.tsx +++ b/react/src/mapper/DataListSelector.tsx @@ -1,28 +1,31 @@ -import React from "react"; -import { setting_name_style } from "./style"; +import React, { ReactNode } from 'react' -export function DataListSelector({ overall_name, initial_value, names, onChange, no_neutral, header_style }: { overall_name: string | undefined, initial_value: T | undefined, names: T[], onChange: (newValue: T) => void, no_neutral?: boolean, header_style?: React.CSSProperties}) { - const names_full = no_neutral ? names : ["", ...names]; - const set_initial = initial_value != undefined && names_full.includes(initial_value); - const actual_selector = ; - if (overall_name === undefined) return actual_selector; +import { setting_name_style } from './style' + +export function DataListSelector({ overall_name, initial_value, names, onChange, no_neutral, header_style }: { overall_name: string | undefined, initial_value: T | undefined, names: T[], onChange: (newValue: T) => void, no_neutral?: boolean, header_style?: React.CSSProperties }): ReactNode { + const names_full = no_neutral ? names : ['', ...names] + const set_initial = initial_value !== undefined && names_full.includes(initial_value) + const actual_selector = ( + + ) + if (overall_name === undefined) return actual_selector return (
    -
    +
    {overall_name}
    {actual_selector} -
    +
    - ); + ) } diff --git a/react/src/mapper/function.tsx b/react/src/mapper/function.tsx index 2f5732b3..ef232a97 100644 --- a/react/src/mapper/function.tsx +++ b/react/src/mapper/function.tsx @@ -1,68 +1,69 @@ -export { FilterSelector, FunctionSelector, FunctionColorStat }; +import { Parser, Value } from 'expr-eval' +import React, { ReactNode } from 'react' -import React from "react"; +import { CheckboxSettingCustom } from '../components/sidebar' -import { Parser, Value } from 'expr-eval'; -import { DataListSelector } from "./DataListSelector"; -import { CheckboxSettingCustom } from "../components/sidebar"; -import { Regression } from "./regression"; -import { ColorStat, ColorStatDescriptor, FilterSettings, RegressionDescriptor, StatisticsForGeography } from "./settings"; +import { DataListSelector } from './DataListSelector' +import { Regression } from './regression' +import { ColorStat, ColorStatDescriptor, FilterSettings, RegressionDescriptor, StatisticsForGeography } from './settings' interface VariableDescriptor { name: string expr: ColorStatDescriptor | undefined } - interface Variable { name: string expr: ColorStat } -class FunctionColorStat implements ColorStat { +export class FunctionColorStat implements ColorStat { constructor(private readonly _name: string | undefined, private readonly _variables: Variable[], private readonly _regressions: Regression[], private readonly _expr: string) { } - name() { - return this._name || "[Unnamed function]"; + + name(): string { + return this._name ?? '[Unnamed function]' } - compute(statistics_for_geography: StatisticsForGeography, vars: Record) { - let variables = { ...vars }; + compute(statistics_for_geography: StatisticsForGeography, vars: Record): number[] { + let variables = { ...vars } for (const variable of this._variables) { - variables[variable.name] = variable.expr.compute(statistics_for_geography); + variables[variable.name] = variable.expr.compute(statistics_for_geography) } - if (this._expr === "") { - return statistics_for_geography.map(() => 0); + if (this._expr === '') { + return statistics_for_geography.map(() => 0) } for (const regression of this._regressions) { - const out = regression.compute(statistics_for_geography, variables); - variables = { ...variables, ...out }; + const out = regression.compute(statistics_for_geography, variables) + variables = { ...variables, ...out } } return statistics_for_geography.map((_, i) => { - const expr = Parser.parse(this._expr); - const vars: Value = {}; - for (const key in variables) { - vars[key] = variables[key][i]; + const expr = Parser.parse(this._expr) + const statVars: Value = {} + for (const key of Object.keys(variables)) { + statVars[key] = variables[key][i] } - return expr.evaluate(vars); - }); + return expr.evaluate(statVars) as number + }) } } -const operator_style: React.CSSProperties = { width: "2em", minWidth: "2em", textAlign: "center" }; +const operator_style: React.CSSProperties = { width: '2em', minWidth: '2em', textAlign: 'center' } -function VariableNameSelector({ variable_name, set_variable_name, placeholder }: { variable_name: string, set_variable_name: (newValue: string) => void, placeholder: string }) { +function VariableNameSelector({ variable_name, set_variable_name, placeholder }: { variable_name: string, set_variable_name: (newValue: string) => void, placeholder: string }): ReactNode { // freeform input for variable name - return set_variable_name(e.target.value)} - />; + return ( + { set_variable_name(e.target.value) }} + /> + ) } -function RegressionSelector(props: { regression: RegressionDescriptor, set_regression: (newValue: RegressionDescriptor) => void, delete_regression: () => void, names: string[] }) { +function RegressionSelector(props: { regression: RegressionDescriptor, set_regression: (newValue: RegressionDescriptor) => void, delete_regression: () => void, names: string[] }): ReactNode { // Create several rows organized as // [stat selector] = [coefficient textbox] * [stat selector] // + [coefficient textbox] * [stat selector] @@ -70,76 +71,75 @@ function RegressionSelector(props: { regression: RegressionDescriptor, set_regre // + [coefficient textbox] (intercept) // + [coefficient textbox] (residue) - const set_coefficient_var = (i: number, value: string) => { - const coefficients = props.regression.var_coefficients; + const set_coefficient_var = (i: number, value: string): void => { + const coefficients = props.regression.var_coefficients props.set_regression({ ...props.regression, var_coefficients: coefficients.map((c, j) => i === j ? value : c), - }); + }) } - const set_intercept_var = (value: string) => { + const set_intercept_var = (value: string): void => { props.set_regression({ ...props.regression, var_intercept: value, - }); + }) } - const set_residue_var = (value: string) => { + const set_residue_var = (value: string): void => { props.set_regression({ ...props.regression, var_residue: value, - }); + }) } - const set_dependent_expr = (i: number, value: ColorStatDescriptor) => { - console.log("set coefficient expr", i, value) - const dependents = props.regression.dependents; + const set_dependent_expr = (i: number, value: ColorStatDescriptor): void => { + const dependents = props.regression.dependents props.set_regression({ ...props.regression, - dependents: dependents.map((c, j) => i == j ? value : c), - }); + dependents: dependents.map((c, j) => i === j ? value : c), + }) } - const remove_dependent_expr = (i: number) => { - const var_coefficients = props.regression.var_coefficients; - const dependents = props.regression.dependents; + const remove_dependent_expr = (i: number): void => { + const var_coefficients = props.regression.var_coefficients + const dependents = props.regression.dependents props.set_regression({ ...props.regression, - var_coefficients: var_coefficients.filter((_, j) => i != j), - dependents: dependents.filter((_, j) => i != j), - }); + var_coefficients: var_coefficients.filter((_, j) => i !== j), + dependents: dependents.filter((_, j) => i !== j), + }) } const rhs_params: { variable_name: string, set_variable_name: (newValue: string) => void, name: string, dependent?: ColorStatDescriptor, set_dependent: (newValue: ColorStatDescriptor) => void, descriptor?: string }[] = props.regression.dependents.map((dependent, i) => { return { variable_name: props.regression.var_coefficients[i], - set_variable_name: (value: string) => set_coefficient_var(i, value), + set_variable_name: (value: string) => { set_coefficient_var(i, value) }, name: `m_${i + 1}`, dependent: props.regression.dependents[i], - set_dependent: (value: ColorStatDescriptor) => set_dependent_expr(i, value), + set_dependent: (value: ColorStatDescriptor) => { set_dependent_expr(i, value) }, } }) rhs_params.push({ variable_name: props.regression.var_intercept, - set_variable_name: value => set_intercept_var(value), + set_variable_name: (value) => { set_intercept_var(value) }, name: `b`, descriptor: `[intercept]`, - set_dependent: () => { throw new Error("Intercept should not have a dependent") }, + set_dependent: () => { throw new Error('Intercept should not have a dependent') }, }) rhs_params.push({ variable_name: props.regression.var_residue, - set_variable_name: value => set_residue_var(value), + set_variable_name: (value) => { set_residue_var(value) }, name: `e`, descriptor: `[residue]`, - set_dependent: () => { throw new Error("Residue should not have a dependent") }, + set_dependent: () => { throw new Error('Residue should not have a dependent') }, }) const dependents = rhs_params.map((param, i) => ( -
    +
    {/* Text field!!! free enttry for variable entry */}
    - {i == 0 ? "=" : "+"} + {i === 0 ? '=' : '+'}
    -
    +
    - {param.dependent === undefined ? "" : ×} + {param.dependent === undefined ? '' : ×}
    { - param.dependent === undefined ? undefined : - <> - - - + param.dependent === undefined + ? undefined + : ( + <> + + + + ) } { - param.descriptor === undefined ? undefined : -
    - {param.descriptor} -
    + param.descriptor === undefined + ? undefined + : ( +
    + {param.descriptor} +
    + ) }
    - )); + )) - const rhs_stack =
    - {dependents} - {/*Add a dependent button */} -
    - + const rhs_stack = ( +
    + {dependents} + {/* Add a dependent button */} +
    + +
    -
    ; + ) - const lhs = props.set_regression({ - ...props.regression, - independent: stat, - })} - names={props.names} - simple={true} - />; + const lhs = ( + { + props.set_regression({ + ...props.regression, + independent: stat, + }) + }} + names={props.names} + simple={true} + /> + ) - const main =
    -
    - {lhs} -
    -
    - {rhs_stack} - props.set_regression({ - ...props.regression, - [key]: value, - }) - } - /> + const main = ( +
    +
    + {lhs} +
    +
    + {rhs_stack} + { + props.set_regression({ + ...props.regression, + [key]: value, + }) + } + } + /> +
    -
    ; + ) - return
    -
    - {main} -
    -
    - + return ( +
    +
    + {main} +
    +
    + +
    -
    ; + ) } -function VariableSelector(props: { variable: VariableDescriptor, set_variable: (newValue: VariableDescriptor) => void, delete_variable: () => void, names: string[] }) { - const variable = props.variable; - const names_full = ["", ...props.names]; - const initial_value = variable.expr != undefined && names_full.includes(variable.expr.value) ? variable.expr.value : ""; +function VariableSelector(props: { variable: VariableDescriptor, set_variable: (newValue: VariableDescriptor) => void, delete_variable: () => void, names: string[] }): ReactNode { + const variable = props.variable + const names_full = ['', ...props.names] + const initial_value = variable.expr !== undefined && names_full.includes(variable.expr.value) ? variable.expr.value : '' return ( -
    +
    {/* Button that deletes this variable */} - -
    -
    +
    +
    props.set_variable({ - ...variable, - name: e.target.value, - })} + onChange={(e) => { + props.set_variable({ + ...variable, + name: e.target.value, + }) + }} />
    -
    +
    - ); + ) } -function FunctionSelector(props: { function: ColorStatDescriptor, set_function: (newValue: ColorStatDescriptor) => void, names: string[], simple?: boolean, no_name_field?: boolean, placeholder?: string }) { - const func = props.function; +export function FunctionSelector(props: { function: ColorStatDescriptor, set_function: (newValue: ColorStatDescriptor) => void, names: string[], simple?: boolean, no_name_field?: boolean, placeholder?: string }): ReactNode { + const func = props.function if (func.variables === undefined) { - func.variables = []; + func.variables = [] } - const expression = props.set_function({ - ...func, - expression: e.target.value, - })} - />; + const expression = ( + { + props.set_function({ + ...func, + expression: e.target.value, + }) + }} + /> + ) if (props.simple) { - return expression; + return expression } return ( -
    +
    { - props.no_name_field ?
    : props.set_function({ - ...func, - name: e.target.value, - })} - /> + props.no_name_field + ?
    + : ( + { + props.set_function({ + ...func, + name: e.target.value, + }) + }} + /> + ) } func.variables || []} - set_variables={variables => props.set_function({ - ...func, - variables: variables || [], - })} + get_variables={() => func.variables ?? []} + set_variables={(variables) => { + props.set_function({ + ...func, + variables, + }) + }} names={props.names} /> -
    +
    func.regressions || []} - set_regressions={regressions => props.set_function({ - ...func, - regressions: regressions, - })} + get_regressions={() => func.regressions ?? []} + set_regressions={(regressions) => { + props.set_function({ + ...func, + regressions, + }) + }} names={props.names} /> -
    +
    {expression}
    - ); + ) } -function VariablesSelector({ get_variables, set_variables, names } - : { get_variables: () => VariableDescriptor[], set_variables: (newValue: VariableDescriptor[]) => void, names: string[] } -) { - return <> - { - get_variables().map((variable: VariableDescriptor, i: number) => ( - set_variables(get_variables().map((v2, j) => i == j ? v : v2))} - delete_variable={() => set_variables(get_variables().filter((v2, j) => i != j))} - names={names} - /> - )) - } - {/*Add a variable button */} -
    - -
    - +function VariablesSelector({ get_variables, set_variables, names }: { get_variables: () => VariableDescriptor[], set_variables: (newValue: VariableDescriptor[]) => void, names: string[] }, +): ReactNode { + return ( + <> + { + get_variables().map((variable: VariableDescriptor, i: number) => ( + { set_variables(get_variables().map((v2, j) => i === j ? v : v2)) }} + delete_variable={() => { set_variables(get_variables().filter((v2, j) => i !== j)) }} + names={names} + /> + )) + } + {/* Add a variable button */} +
    + +
    + + ) } -function RegressionsSelector({ get_regressions, set_regressions, names }: { get_regressions: () => RegressionDescriptor[], set_regressions: (newValue: RegressionDescriptor[]) => void, names: string[] }) { - const gr: () => RegressionDescriptor[] = () => get_regressions() || []; +function RegressionsSelector({ get_regressions, set_regressions, names }: { get_regressions: () => RegressionDescriptor[], set_regressions: (newValue: RegressionDescriptor[]) => void, names: string[] }): ReactNode { + const gr: () => RegressionDescriptor[] = () => get_regressions() - return <> - { - gr().map((regression, i) => ( - { - console.log("Setting regression", i, "to", r); - set_regressions(gr().map((r2, j) => i == j ? r : r2)); + return ( + <> + { + gr().map((regression, i) => ( + { + set_regressions(gr().map((r2, j) => i === j ? r : r2)) + }} + delete_regression={() => { set_regressions(gr().filter((r2, j) => i !== j)) }} + names={names} + /> + )) + } + {/* Add a regression button */} +
    + -
    - + > + Add Regression + +
    + + ) } -function FilterSelector(props: { filter: FilterSettings, set_filter: (newValue: FilterSettings) => void, names: string[] }) { - const filter = props.filter; +export function FilterSelector(props: { filter: FilterSettings, set_filter: (newValue: FilterSettings) => void, names: string[] }): ReactNode { + const filter = props.filter // like FunctionSelector, but has a checkmark for whether the filter is enabled return (
    @@ -408,54 +450,72 @@ function FilterSelector(props: { filter: FilterSettings, set_filter: (newValue: props.set_filter({ - ...filter, - enabled: e.target.checked, - })} - /> Enable Filter? + onChange={(e) => { + props.set_filter({ + ...filter, + enabled: e.target.checked, + }) + }} + /> + {' '} + Enable Filter? { - filter.enabled ? props.set_filter({ - ...filter, - function: f, - })} - names={props.names} - placeholder={'Filter expression, e.g., "(a > 0 and b < 0) or b > 10"'} - /> :
    + filter.enabled + ? ( + { + props.set_filter({ + ...filter, + function: f, + }) + }} + names={props.names} + placeholder={'Filter expression, e.g., "(a > 0 and b < 0) or b > 10"'} + /> + ) + :
    }
    - ); + ) } -export function StatisticSelector({ statistic, set_statistic, names, overall_name, simple }: { statistic: ColorStatDescriptor | undefined, set_statistic: (newValue: ColorStatDescriptor) => void, names: string[], overall_name: string | undefined, simple: boolean }) { - return
    - set_statistic(name != "Function" ? { - ...statistic, - type: "single", - value: name, - } : { - ...statistic, - type: "function", - value: "Function", - variables: [], - expression: "", - name: "", - } - )} /> - {statistic?.type == "function" ? - void, names: string[], overall_name: string | undefined, simple: boolean }): ReactNode { + return ( +
    + { + set_statistic(name !== 'Function' + ? { + ...statistic, + type: 'single', + value: name, + } + : { + ...statistic, + type: 'function', + value: 'Function', + variables: [], + expression: '', + name: '', + }, + ) + }} /> - : -
    } -
    ; + {statistic?.type === 'function' + ? ( + + ) + :
    } +
    + ) } diff --git a/react/src/mapper/ramp-selector.tsx b/react/src/mapper/ramp-selector.tsx index 95bc6014..36555fc7 100644 --- a/react/src/mapper/ramp-selector.tsx +++ b/react/src/mapper/ramp-selector.tsx @@ -1,176 +1,188 @@ -import React from "react"; -import { ColorMap, EncodedColorMap, RampDescriptor, RAMPS, parse_custom_colormap } from "./ramps"; +import React, { ReactNode } from 'react' -import { setting_sub_name_style } from "./style"; -import { interpolate_color } from "../utils/color"; +import { interpolate_color } from '../utils/color' -export function RampColormapSelector(props: { ramp: RampDescriptor, set_ramp: (newValue: RampDescriptor) => void, name?: string }) { +import { ColorMap, EncodedColorMap, RAMPS, RampDescriptor, parse_custom_colormap } from './ramps' +import { setting_sub_name_style } from './style' + +export function RampColormapSelector(props: { ramp: RampDescriptor, set_ramp: (newValue: RampDescriptor) => void, name?: string }): ReactNode { // dropdown selector for either a custom ramp or a ramp from a list of presets // if custom ramp, then add a text box for the ramp - const colormap = props.ramp.colormap; + const colormap = props.ramp.colormap - const set_colormap = (colormap: EncodedColorMap) => { + const set_colormap = (encodedColormap: EncodedColorMap): void => { props.set_ramp({ ...props.ramp, - colormap: colormap, - }); + colormap: encodedColormap, + }) } - const set_selected = (name: string) => { - if (name === "Custom") { - colormap.type = "custom"; - } else { - colormap.type = "preset"; - (colormap as { name: string }).name = name; + const set_selected = (name: string): void => { + if (name === 'Custom') { + colormap.type = 'custom' + } + else { + colormap.type = 'preset'; + (colormap as { name: string }).name = name } - set_colormap(colormap); + set_colormap(colormap) } - const set_custom_colormap = (custom_colormap: string) => { + const set_custom_colormap = (custom_colormap: string): void => { set_colormap({ ...colormap, type: 'custom', - custom_colormap: custom_colormap, - }); + custom_colormap, + }) } - const options = [ - "", - "Custom", - ...Object.keys(RAMPS), - ]; + const options = [ + '', + 'Custom', + ...Object.keys(RAMPS), + ] - let colormapSelection: string - if (colormap.type === 'none') { - colormapSelection = '' - } - else if (colormap.type === "preset") { - colormapSelection = colormap.name - } else { - colormapSelection = "Custom" - } + let colormapSelection: string + if (colormap.type === 'none') { + colormapSelection = '' + } + else if (colormap.type === 'preset') { + colormapSelection = colormap.name + } + else { + colormapSelection = 'Custom' + } - return ( -
    -
    - {props.name} -
    - + return ( +
    +
    + {props.name} +
    + + { + colormap.type === 'custom' + ? ( + + { set_custom_colormap(custom_colormap) }} + /> + + ) + :
    + } +
    + ) } -function SinglePointSelector({ value, color, cell, set_cell, remove_cell }: { value: number, color: string, cell: [number, string], set_cell: (newValue: [number, string]) => void, remove_cell: () => void }) { - return
    - - set_cell([ - cell[0], - e.target.value, - ]) - } - /> - - set_cell([ - parseFloat(e.target.value), - cell[1], - ]) - } - /> - -
    + { + set_cell([ + cell[0], + e.target.value, + ]) + }} + /> + { + set_cell([ + parseFloat(e.target.value), + cell[1], + ]) + }} + /> + +
    + ) } -function CustomColormapSelector(props: { colormap: string, set_colormap: (newValue: string) => void }) { - // flexbox containing several color tabs - // each color tab is a vertical flexbox containing a color picker and a text box - // at the end there is a plus button to add a new color tab - // each color tab has a minus button to remove itself - let colormap_text = props.colormap; - const parsed_colormap = parse_custom_colormap(colormap_text); - let colormap: ColorMap - if (parsed_colormap !== undefined) { - colormap = parsed_colormap.sort((a, b) => a[0] - b[0]); - colormap_text = JSON.stringify(colormap); - } else { - colormap = []; +function CustomColormapSelector(props: { colormap: string, set_colormap: (newValue: string) => void }): ReactNode { + // flexbox containing several color tabs + // each color tab is a vertical flexbox containing a color picker and a text box + // at the end there is a plus button to add a new color tab + // each color tab has a minus button to remove itself + let colormap_text = props.colormap + const parsed_colormap = parse_custom_colormap(colormap_text) + let colormap: ColorMap + if (parsed_colormap !== undefined) { + colormap = parsed_colormap.sort((a, b) => a[0] - b[0]) + colormap_text = JSON.stringify(colormap) + } + else { + colormap = [] + } + // colormap :: [[number, string]] + + const add_cell = (at_index: number): void => { + const new_colormap = colormap.slice() + let value = 0 + if (at_index === 0) { + value = colormap[0][0] - 1 } - // colormap :: [[number, string]] - console.log("cmap", colormap); - - const add_cell = (at_index: number) => { - const new_colormap = colormap.slice(); - let value = 0; - if (at_index == 0) { - value = colormap[0][0] - 1; - } else if (at_index == colormap.length) { - value = colormap[colormap.length - 1][0] + 1; - } else { - value = (colormap[at_index - 1][0] + colormap[at_index][0]) / 2; - } - const color = interpolate_color(colormap, value); - new_colormap.splice(at_index, 0, [value, color]); - props.set_colormap( - JSON.stringify(new_colormap) - ); + else if (at_index === colormap.length) { + value = colormap[colormap.length - 1][0] + 1 + } + else { + value = (colormap[at_index - 1][0] + colormap[at_index][0]) / 2 } + const color = interpolate_color(colormap, value) + new_colormap.splice(at_index, 0, [value, color]) + props.set_colormap( + JSON.stringify(new_colormap), + ) + } - function AddCellButton({ at_index }: { at_index: number }) { - return - } + ) + } - const color_tabs =
    @@ -181,44 +193,44 @@ function CustomColormapSelector(props: { colormap: string, set_colormap: (newVal value={value} color={color} cell={colormap[i]} - set_cell={cell => { - const new_colormap = colormap.slice(); - new_colormap[i] = cell; + set_cell={(cell) => { + const new_colormap = colormap.slice() + new_colormap[i] = cell props.set_colormap( - JSON.stringify(new_colormap) - ); + JSON.stringify(new_colormap), + ) }} remove_cell={() => { - const new_colormap = colormap.slice(); - new_colormap.splice(i, 1); + const new_colormap = colormap.slice() + new_colormap.splice(i, 1) props.set_colormap( - JSON.stringify(new_colormap) - ); - } - } + JSON.stringify(new_colormap), + ) + }} />, - + , ]) }
    - // then an input textbox - const input_textbox = - props.set_colormap(e.target.value) - } + onChange={(e) => { props.set_colormap(e.target.value) }} /> - return ( -
    -
    - Custom Colormap -
    - {color_tabs} - {input_textbox} + ) + return ( +
    +
    + Custom Colormap
    - ) - } \ No newline at end of file + {color_tabs} + {input_textbox} +
    + ) +} diff --git a/react/src/mapper/ramps.ts b/react/src/mapper/ramps.ts index 2e7401b2..00fbf48e 100644 --- a/react/src/mapper/ramps.ts +++ b/react/src/mapper/ramps.ts @@ -1,5 +1,4 @@ - -export const RAMPS = require("../data/mapper/ramps.json"); +export const RAMPS = require('../data/mapper/ramps.json') as Record export type EncodedColorMap = { type: 'none' } | { type: 'custom', custom_colormap: string } | { type: 'preset', name: string } @@ -8,92 +7,94 @@ export type ColorMap = [number, string][] export type Keypoints = Readonly<[number, string]>[] export interface Ramp { - create_ramp(values: number[]): Readonly<[Keypoints, number[]]>; + create_ramp(values: number[]): Readonly<[Keypoints, number[]]> } interface CommonRampDescriptor { - colormap: EncodedColorMap, reversed?: boolean + colormap: EncodedColorMap + reversed?: boolean } export interface ConstantRampDescriptor extends CommonRampDescriptor { - type: "constant", lower_bound?: string, upper_bound?: string + type: 'constant' + lower_bound?: string + upper_bound?: string } -export type RampDescriptor = ConstantRampDescriptor | CommonRampDescriptor & { type: "linear"} | CommonRampDescriptor & { type: "geometric" } +export type RampDescriptor = ConstantRampDescriptor | CommonRampDescriptor & { type: 'linear' } | CommonRampDescriptor & { type: 'geometric' } export function parse_custom_colormap(custom_colormap: string): ColorMap | undefined { try { - const result = JSON.parse(custom_colormap); + const result = JSON.parse(custom_colormap) as unknown if (result instanceof Array && result.every(x => x instanceof Array - && x.length === 2 - && typeof x[0] === "number" - && typeof x[1] === "string" - && x[1].match(/^#[0-9a-f]{6}$/i) !== null + && x.length === 2 + && typeof x[0] === 'number' + && typeof x[1] === 'string' + && (/^#[0-9a-f]{6}$/i.exec(x[1])) !== null, )) { - return result; + return result as ColorMap } - } catch (e) { - console.log("error! in_parse_custom_colormap", e); } - return undefined; + catch (e) { + console.error('error! in_parse_custom_colormap', e) + } + return undefined } function parse_colormap(cmap: EncodedColorMap): ColorMap { - console.log("C", cmap) - if (cmap.type === "none") { - // default - return RAMPS.Gray; - } else if (cmap.type === "custom") { - return parse_custom_colormap(cmap.custom_colormap) || RAMPS.Gray; - } else if (cmap.type === "preset") { - if (cmap.name == "") { - return RAMPS.Gray; + if (cmap.type === 'none') { + // default + return RAMPS.Gray + } + else if (cmap.type === 'custom') { + return parse_custom_colormap(cmap.custom_colormap) ?? RAMPS.Gray + } + else { + if (cmap.name === '') { + return RAMPS.Gray } - return RAMPS[cmap.name]; + return RAMPS[cmap.name] } - throw new Error("Invalid colormap type"); } -function parse_ramp_base(ramp: RampDescriptor) { - console.log("parse_ramp_base: ramp=", ramp) - const cmap = parse_colormap(ramp.colormap); - console.log("parse_ramp_base: cmap=", cmap); - if (ramp.type === "constant") { +function parse_ramp_base(ramp: RampDescriptor): ConstantRamp | LinearRamp | GeometricRamp { + const cmap = parse_colormap(ramp.colormap) + if (ramp.type === 'constant') { return new ConstantRamp(cmap, - ramp.lower_bound === undefined || ramp.lower_bound === "" ? 0 : parseFloat(ramp.lower_bound), - ramp.upper_bound === undefined || ramp.upper_bound === "" ? 1 : parseFloat(ramp.upper_bound), - ); - } else if (ramp.type === "linear") { - return new LinearRamp(cmap); - } else if (ramp.type === "geometric") { - return new GeometricRamp(cmap); - } - throw new Error("Invalid ramp type"); + ramp.lower_bound === undefined || ramp.lower_bound === '' ? 0 : parseFloat(ramp.lower_bound), + ramp.upper_bound === undefined || ramp.upper_bound === '' ? 1 : parseFloat(ramp.upper_bound), + ) + } + else if (ramp.type === 'linear') { + return new LinearRamp(cmap) + } + else { + return new GeometricRamp(cmap) + } } -export function parse_ramp(ramp: RampDescriptor) { +export function parse_ramp(ramp: RampDescriptor): Ramp { // handles modifiers like reversed - let base: Ramp = parse_ramp_base(ramp); + let base: Ramp = parse_ramp_base(ramp) if (ramp.reversed) { - base = new ReversedRamp(base); + base = new ReversedRamp(base) } - return base; + return base } class ConstantRamp implements Ramp { - private readonly values: Keypoints constructor(keypoints: ColorMap, a: number, b: number) { - const a0 = keypoints[0][0]; - const b0 = keypoints[keypoints.length - 1][0]; + const a0 = keypoints[0][0] + const b0 = keypoints[keypoints.length - 1][0] this.values = keypoints.map(([value, color]) => { - const new_value = (value - a0) / (b0 - a0) * (b - a) + a; - return [new_value, color]; + const new_value = (value - a0) / (b0 - a0) * (b - a) + a + return [new_value, color] }) } - create_ramp() { - return [this.values, linear_values(this.values[0][0], this.values[this.values.length - 1][0])] as const; + create_ramp(): Readonly<[Keypoints, number[]]> { + return [this.values, linear_values(this.values[0][0], this.values[this.values.length - 1][0])] as const } } @@ -101,52 +102,51 @@ class LinearRamp implements Ramp { constructor(private readonly values: Keypoints) { } - create_ramp(values: number[]) { - values = values.filter(x => !isNaN(x)); - const minimum = Math.min(...values); - const maximum = Math.max(...values); - const range = maximum - minimum; - const ramp_min = Math.min(...this.values.map(([value]) => value)); - const ramp_max = Math.max(...this.values.map(([value]) => value)); - const ramp_range = ramp_max - ramp_min; - const ramp: [number, string][] = this.values.map(x => [x[0], x[1]]); - for (const i in ramp) { - ramp[i][0] = (ramp[i][0] - ramp_min) / ramp_range * range + minimum; + create_ramp(values: number[]): Readonly<[Keypoints, number[]]> { + values = values.filter(x => !isNaN(x)) + const minimum = Math.min(...values) + const maximum = Math.max(...values) + const range = maximum - minimum + const ramp_min = Math.min(...this.values.map(([value]) => value)) + const ramp_max = Math.max(...this.values.map(([value]) => value)) + const ramp_range = ramp_max - ramp_min + const ramp: [number, string][] = this.values.map(x => [x[0], x[1]]) + for (const i of ramp.keys()) { + ramp[i][0] = (ramp[i][0] - ramp_min) / ramp_range * range + minimum } - return [ramp, linear_values(minimum, maximum)] as const; + return [ramp, linear_values(minimum, maximum)] as const } } function linear_values(minimum: number, maximum: number): number[] { - const steps = 10; - const range = maximum - minimum; - const values = Array(steps).fill(0).map((_, i) => minimum + range * i / (steps - 1)); - return values; + const steps = 10 + const range = maximum - minimum + const values = Array(steps).fill(0).map((_, i) => minimum + range * i / (steps - 1)) + return values } class GeometricRamp implements Ramp { constructor(private readonly values: Keypoints) { } - create_ramp(values: number[]) { - const log_values = values.map(x => Math.log(x)); - const [log_ramp, log_ramp_values] = new LinearRamp(this.values).create_ramp(log_values); - const ramp = log_ramp.map(x => [Math.exp(x[0]), x[1]] as const); - const ramp_values = log_ramp_values.map(x => Math.exp(x)); - return [ramp, ramp_values] as const; + create_ramp(values: number[]): Readonly<[Keypoints, number[]]> { + const log_values = values.map(x => Math.log(x)) + const [log_ramp, log_ramp_values] = new LinearRamp(this.values).create_ramp(log_values) + const ramp = log_ramp.map(x => [Math.exp(x[0]), x[1]] as const) + const ramp_values = log_ramp_values.map(x => Math.exp(x)) + return [ramp, ramp_values] as const } } - class ReversedRamp implements Ramp { constructor(private readonly base: Ramp) { } - create_ramp(values: number[]) { - const [ramp, ramp_values] = this.base.create_ramp(values); - const reversed_colors = ramp.map(x => x[1]).reverse(); - const ramp_reversed = reversed_colors.map((x, i) => [ramp[i][0], x] as const); - return [ramp_reversed, ramp_values] as const; + create_ramp(values: number[]): Readonly<[Keypoints, number[]]> { + const [ramp, ramp_values] = this.base.create_ramp(values) + const reversed_colors = ramp.map(x => x[1]).reverse() + const ramp_reversed = reversed_colors.map((x, i) => [ramp[i][0], x] as const) + return [ramp_reversed, ramp_values] as const } -} \ No newline at end of file +} diff --git a/react/src/mapper/regression.ts b/react/src/mapper/regression.ts index 3f2d5267..752f8162 100644 --- a/react/src/mapper/regression.ts +++ b/react/src/mapper/regression.ts @@ -1,24 +1,25 @@ -import { MathNumericType, dotMultiply, lusolve, multiply, transpose } from "mathjs"; -import { ColorStat, StatisticsForGeography } from "./settings"; +import { MathNumericType, dotMultiply, lusolve, multiply, transpose } from 'mathjs' + +import { ColorStat, StatisticsForGeography } from './settings' export class Regression { constructor( readonly independent_fn: ColorStat, readonly dependent_fns: ColorStat[], readonly dependent_names: string[], readonly intercept_name: string, readonly residual_name: string, - readonly weight_by_population: boolean, readonly population_idx: number + readonly weight_by_population: boolean, readonly population_idx: number, ) { } - compute(statistics_for_geography: StatisticsForGeography, variables: Record) { - const independent = this.independent_fn.compute(statistics_for_geography, variables); - const dependent = this.dependent_fns.map((fn) => fn.compute(statistics_for_geography, variables)); + compute(statistics_for_geography: StatisticsForGeography, variables: Record): Record { + const independent = this.independent_fn.compute(statistics_for_geography, variables) + const dependent = this.dependent_fns.map(fn => fn.compute(statistics_for_geography, variables)) // independent: (N,) // dependent: (K, N) - const y = independent.map(x => [x]); + const y = independent.map(x => [x]) // transpose dependent - const x = dependent[0].map((_, i) => dependent.map(row => row[i])); + const x = dependent[0].map((_, i) => dependent.map(row => row[i])) // x: (N, K) // y: (N, 1) @@ -26,51 +27,50 @@ export class Regression { // filter nans // is_nan: (N,) - const is_nan = y.map((yi, i) => isNaN(yi[0]) || x[i].some(xij => isNaN(xij))); - - const xfilt = x.filter((_, i) => !is_nan[i]); - const yfilt = y.filter((_, i) => !is_nan[i]); - const sfg_filt = statistics_for_geography.filter((_, i) => !is_nan[i]); + const is_nan = y.map((yi, i) => isNaN(yi[0]) || x[i].some(xij => isNaN(xij))) + const xfilt = x.filter((_, i) => !is_nan[i]) + const yfilt = y.filter((_, i) => !is_nan[i]) + const sfg_filt = statistics_for_geography.filter((_, i) => !is_nan[i]) - const Awofilt = x.map(row => [1, ...row]); - const A = xfilt.map(row => [1, ...row]); - let ATW = transpose(A); + const Awofilt = x.map(row => [1, ...row]) + const A = xfilt.map(row => [1, ...row]) + let ATW = transpose(A) if (this.weight_by_population) { - const W = sfg_filt.map(sfg => sfg.stats[this.population_idx]); - ATW = dotMultiply(ATW, W); + const W = sfg_filt.map(sfg => sfg.stats[this.population_idx]) + ATW = dotMultiply(ATW, W) } - const ata = multiply(ATW, A); + const ata = multiply(ATW, A) - const atb = multiply(ATW, yfilt); + const atb = multiply(ATW, yfilt) // solve for weights. weights = (ata)^-1 atb - const ws_col = lusolve(ata, atb); - const ws = ws_col.map(x => (x as MathNumericType[])[0]); + const ws_col = lusolve(ata, atb) + const ws = ws_col.map(col => (col as MathNumericType[])[0]) - const weights = ws.slice(1); - const intercept = ws[0]; + const weights = ws.slice(1) + const intercept = ws[0] - const preds = multiply(Awofilt, ws_col).map(x => (x as number[])[0]); + const preds = multiply(Awofilt, ws_col).map(pred => (pred as number[])[0]) - const result: Record = {}; + const result: Record = {} for (let i = 0; i < this.dependent_names.length; i++) { - if (this.dependent_names[i] == "") { - continue; + if (this.dependent_names[i] === '') { + continue } - result[this.dependent_names[i]] = preds.map(() => weights[i] as number); + result[this.dependent_names[i]] = preds.map(() => weights[i] as number) } - if (this.intercept_name != "") { - result[this.intercept_name] = preds.map(() => intercept as number); + if (this.intercept_name !== '') { + result[this.intercept_name] = preds.map(() => intercept as number) } - if (this.residual_name != "") { - result[this.residual_name] = preds.map((pred, i) => y[i][0] - pred); + if (this.residual_name !== '') { + result[this.residual_name] = preds.map((pred, i) => y[i][0] - pred) } - return result; + return result } -} \ No newline at end of file +} diff --git a/react/src/mapper/settings.tsx b/react/src/mapper/settings.tsx index 4f38cfd4..ce7324af 100644 --- a/react/src/mapper/settings.tsx +++ b/react/src/mapper/settings.tsx @@ -1,10 +1,11 @@ -import React from "react"; -import { FunctionColorStat, FilterSelector, StatisticSelector } from "./function"; -import { RampColormapSelector } from "./ramp-selector"; -import { setting_name_style, setting_sub_name_style } from "./style"; -import { DataListSelector } from "./DataListSelector"; -import { Regression } from "./regression"; -import { ConstantRampDescriptor, RampDescriptor } from "./ramps"; +import React, { ReactNode } from 'react' + +import { DataListSelector } from './DataListSelector' +import { FilterSelector, FunctionColorStat, StatisticSelector } from './function' +import { RampColormapSelector } from './ramp-selector' +import { ConstantRampDescriptor, RampDescriptor } from './ramps' +import { Regression } from './regression' +import { setting_name_style, setting_sub_name_style } from './style' export type StatisticsForGeography = { stats: number[] }[] @@ -14,97 +15,95 @@ export interface ColorStat { } export interface RegressionDescriptor { - var_coefficients: string[], - var_intercept: string, - independent: ColorStatDescriptor | undefined, - residual_name?: string, - var_residue: string, + var_coefficients: string[] + var_intercept: string + independent: ColorStatDescriptor | undefined + residual_name?: string + var_residue: string weight_by_population: boolean dependents: (ColorStatDescriptor | undefined)[] } export type ColorStatDescriptor = ( - { type: "single", value: string, variables?: { name: string, expr: (ColorStatDescriptor | undefined) }[], regressions?: RegressionDescriptor[], name?: string, expression?: string } + { type: 'single', value: string, variables?: { name: string, expr: (ColorStatDescriptor | undefined) }[], regressions?: RegressionDescriptor[], name?: string, expression?: string } | - { type: "function", value: "Function", variables: { name: string, expr: (ColorStatDescriptor | undefined) }[], regressions?: RegressionDescriptor[], name?: string, expression: string } + { type: 'function', value: 'Function', variables: { name: string, expr: (ColorStatDescriptor | undefined) }[], regressions?: RegressionDescriptor[], name?: string, expression: string } ) export interface LineStyle { - color: string, + color: string weight: number } export type Basemap = { - type: "osm" -} | { type: "none" } + type: 'osm' +} | { type: 'none' } export interface FilterSettings { - enabled: boolean, + enabled: boolean function: ColorStatDescriptor }; export interface MapSettings { - geography_kind: string, - filter: FilterSettings, + geography_kind: string + filter: FilterSettings color_stat: undefined | ColorStatDescriptor - ramp: RampDescriptor, - line_style: LineStyle, + ramp: RampDescriptor + line_style: LineStyle basemap: Basemap } export function default_settings(add_to: Partial): MapSettings { const defaults: MapSettings = { - geography_kind: "", + geography_kind: '', filter: { enabled: false, function: { - type: "function", - value: "Function", - expression: "", - variables: [] + type: 'function', + value: 'Function', + expression: '', + variables: [], }, }, color_stat: undefined, ramp: { - type: "linear", + type: 'linear', colormap: { - type: "none", - } + type: 'none', + }, }, line_style: { - color: "#000000", + color: '#000000', weight: 0.5, }, basemap: { - type: "osm", - } + type: 'osm', + }, } - return merge(add_to, defaults); + return merge(add_to, defaults) } function merge(add_to: Partial, add_from: T): T { let key: keyof T for (key in add_from) { if (add_to[key] === undefined) { - add_to[key] = add_from[key]; - } else if (typeof add_to[key] === "object") { - merge(add_to[key]!, add_from[key]); + add_to[key] = add_from[key] + } + else if (typeof add_to[key] === 'object') { + merge(add_to[key] as object, add_from[key]) } } - return add_to as T; + return add_to as T } -function parse_regression(name_to_index: ReadonlyMap, regr: RegressionDescriptor) { - console.log(regr); - const independent_fn = parse_color_stat(name_to_index, regr.independent); - const dependent_fns = regr.dependents.map(dependent => parse_color_stat(name_to_index, dependent)); - const dependent_names = regr.var_coefficients; - const intercept_name = regr.var_intercept; - const residual_name = regr.var_residue; - const weight_by_population = regr.weight_by_population; - - console.log(name_to_index); +function parse_regression(name_to_index: ReadonlyMap, regr: RegressionDescriptor): Regression { + const independent_fn = parse_color_stat(name_to_index, regr.independent) + const dependent_fns = regr.dependents.map(dependent => parse_color_stat(name_to_index, dependent)) + const dependent_names = regr.var_coefficients + const intercept_name = regr.var_intercept + const residual_name = regr.var_residue + const weight_by_population = regr.weight_by_population return new Regression( independent_fn, @@ -113,94 +112,94 @@ function parse_regression(name_to_index: ReadonlyMap, regr: Regr intercept_name, residual_name, weight_by_population, - name_to_index.get("Population")!, - ); + name_to_index.get('Population')!, + ) } export function parse_color_stat(name_to_index: ReadonlyMap, color_stat: ColorStatDescriptor | undefined): ColorStat { if (color_stat === undefined) { - return new InvalidColorStat(); + return new InvalidColorStat() } - const type = color_stat.type; - if (type === "single") { - const value = color_stat.value; + const type = color_stat.type + if (type === 'single') { + const value = color_stat.value if (name_to_index.has(value)) { - return new SingleColorStat(name_to_index.get(value)!, value); + return new SingleColorStat(name_to_index.get(value)!, value) } - return new InvalidColorStat(); + return new InvalidColorStat() } - if (type == "function") { - const variables = color_stat.variables.map(variable => { + else { + const variables = color_stat.variables.map((variable) => { return { name: variable.name, expr: parse_color_stat(name_to_index, variable.expr), } - }); - const regressions = (color_stat.regressions ?? []).map(regr => parse_regression(name_to_index, regr)); - console.log("regressions", regressions); - return new FunctionColorStat(color_stat.name, variables, regressions, color_stat.expression); + }) + const regressions = (color_stat.regressions ?? []).map(regr => parse_regression(name_to_index, regr)) + return new FunctionColorStat(color_stat.name, variables, regressions, color_stat.expression) } - return new InvalidColorStat(); } class SingleColorStat implements ColorStat { constructor(private readonly _index: number, private readonly _name: string) { } - name() { - return this._name; + name(): string { + return this._name } - compute(statistics_for_geography: StatisticsForGeography) { - return statistics_for_geography.map(statistics => statistics.stats[this._index]); + compute(statistics_for_geography: StatisticsForGeography): number[] { + return statistics_for_geography.map(statistics => statistics.stats[this._index]) } } class InvalidColorStat implements ColorStat { - name() { - return "[Invalid]"; + name(): string { + return '[Invalid]' } - compute(statistics_for_geography: StatisticsForGeography) { - return statistics_for_geography.map(() => 0); + compute(statistics_for_geography: StatisticsForGeography): number[] { + return statistics_for_geography.map(() => 0) } } -function ConstantParametersSelector({ ramp, set_ramp }: { ramp: ConstantRampDescriptor, set_ramp: (newValue: ConstantRampDescriptor) => void }) { +function ConstantParametersSelector({ ramp, set_ramp }: { ramp: ConstantRampDescriptor, set_ramp: (newValue: ConstantRampDescriptor) => void }): ReactNode { return ( -
    +
    Lower Bound:
    set_ramp({ - ...ramp, - lower_bound: e.target.value, - })} + onChange={(e) => { + set_ramp({ + ...ramp, + lower_bound: e.target.value, + }) + }} /> -
    +
    Upper Bound:
    set_ramp({ - ...ramp, - upper_bound: e.target.value, - })} + onChange={(e) => { + set_ramp({ + ...ramp, + upper_bound: e.target.value, + }) + }} />
    - ); + ) } - - -function RampSelector(props: { ramp: RampDescriptor, set_ramp: (newValue: RampDescriptor) => void }) { +function RampSelector(props: { ramp: RampDescriptor, set_ramp: (newValue: RampDescriptor) => void }): ReactNode { return (
    @@ -208,103 +207,117 @@ function RampSelector(props: { ramp: RampDescriptor, set_ramp: (newValue: RampDe
    props.set_ramp(ramp)} + set_ramp={(ramp) => { props.set_ramp(ramp) }} /> props.set_ramp({ - ...props.ramp, - type: name, - })} + onChange={(name) => { + props.set_ramp({ + ...props.ramp, + type: name, + }) + }} /> { - props.ramp.type === "constant" ? :
    + props.ramp.type === 'constant' + ? ( + + ) + :
    } -
    +
    Reversed:
    props.set_ramp({ - ...props.ramp, - reversed: e.target.checked, - })} + onChange={(e) => { + props.set_ramp({ + ...props.ramp, + reversed: e.target.checked, + }) + }} />
    ) } -function LineStyleSelector(props: { line_style: LineStyle, set_line_style: (newValue: LineStyle) => void }) { +function LineStyleSelector(props: { line_style: LineStyle, set_line_style: (newValue: LineStyle) => void }): ReactNode { return (
    Line Style:
    -
    +
    Color:
    props.set_line_style({ - ...props.line_style, - color: e.target.value, - })} + onChange={(e) => { + props.set_line_style({ + ...props.line_style, + color: e.target.value, + }) + }} />
    -
    +
    Weight:
    props.set_line_style({ - ...props.line_style, - weight: parseFloat(e.target.value), - })} + onChange={(e) => { + props.set_line_style({ + ...props.line_style, + weight: parseFloat(e.target.value), + }) + }} />
    ) } -function BaseMapSelector({ basemap, set_basemap }: { basemap: Basemap, set_basemap: (newValue: Basemap) => void }) { +function BaseMapSelector({ basemap, set_basemap }: { basemap: Basemap, set_basemap: (newValue: Basemap) => void }): ReactNode { // just a checkbox for now return (
    Basemap:
    -
    +
    OSM:
    set_basemap({ - type: e.target.checked ? "osm" : "none", - })} + checked={basemap.type !== 'none'} + onChange={(e) => { + set_basemap({ + type: e.target.checked ? 'osm' : 'none', + }) + }} />
    - ); + ) } -export function MapperSettings(props: { map_settings: MapSettings, valid_geographies: string[], set_map_settings: (newValue: MapSettings) => void, names: string[] }) { +export function MapperSettings(props: { map_settings: MapSettings, valid_geographies: string[], set_map_settings: (newValue: MapSettings) => void, names: string[] }): ReactNode { return (
    props.set_map_settings({ - ...props.map_settings, - geography_kind: name - }) + (name) => { + props.set_map_settings({ + ...props.map_settings, + geography_kind: name, + }) + } } />
    Filter
    props.set_map_settings({ - ...props.map_settings, - filter: filter, - })} + set_filter={(filter) => { + props.set_map_settings({ + ...props.map_settings, + filter, + }) + }} names={props.names} /> props.set_map_settings({ - ...props.map_settings, - color_stat: color_stat, - })} + set_statistic={(color_stat) => { + props.set_map_settings({ + ...props.map_settings, + color_stat, + }) + }} names={props.names} simple={false} /> props.set_map_settings({ - ...props.map_settings, - ramp: ramp, - })} + set_ramp={(ramp) => { + props.set_map_settings({ + ...props.map_settings, + ramp, + }) + }} /> - props.set_map_settings({ - ...props.map_settings, - line_style: line_style, - })} + { + props.set_map_settings({ + ...props.map_settings, + line_style, + }) + }} /> props.set_map_settings({ - ...props.map_settings, - basemap: basemap, - })} + set_basemap={(basemap) => { + props.set_map_settings({ + ...props.map_settings, + basemap, + }) + }} />
    ) -} \ No newline at end of file +} diff --git a/react/src/mapper/style.ts b/react/src/mapper/style.ts index 50e8c736..3dc42832 100644 --- a/react/src/mapper/style.ts +++ b/react/src/mapper/style.ts @@ -1,12 +1,12 @@ export const setting_name_style = { fontWeight: 500, - fontSize: "1.2em", - marginBottom: "0.125em", + fontSize: '1.2em', + marginBottom: '0.125em', } export const setting_sub_name_style = { fontWeight: 500, - fontSize: "1em", - marginBottom: "0.125em", - color: "#444", -} \ No newline at end of file + fontSize: '1em', + marginBottom: '0.125em', + color: '#444', +} diff --git a/react/src/navigation/links.ts b/react/src/navigation/links.ts index dc88b45d..b334eb75 100644 --- a/react/src/navigation/links.ts +++ b/react/src/navigation/links.ts @@ -1,118 +1,114 @@ -export function article_link(universe: string | undefined, longname: string) { +export function article_link(universe: string | undefined, longname: string): string { const params = new URLSearchParams() - params.set('longname', sanitize(longname)); - add_universe_to_params(universe, params); - return "/article.html?" + params.toString(); + params.set('longname', sanitize(longname)) + add_universe_to_params(universe, params) + return `/article.html?${params.toString()}` } function shard_bytes(longname: string): [string, string] { // as bytes, in utf-8 - const bytes = new TextEncoder().encode(longname); - const hash = new Uint32Array([0]); + const bytes = new TextEncoder().encode(longname) + const hash = new Uint32Array([0]) for (const byte of bytes) { - hash[0] = (hash[0] * 31 + byte) & 0xffffffff; + hash[0] = (hash[0] * 31 + byte) & 0xffffffff } // last 4 hex digits - let string = "" + let string = '' for (let i = 0; i < 4; i++) { - string += (hash[0] & 0xf).toString(16); - hash[0] = hash[0] >> 4; + string += (hash[0] & 0xf).toString(16) + hash[0] = hash[0] >> 4 } // get first two and last two return [ string.slice(0, 2), - string.slice(2, 3) + string.slice(2, 3), ] } - -export function sharded_name(longname: string) { - const sanitized_name = sanitize(longname); - const [a, b] = shard_bytes(sanitized_name); - return `${a}/${b}/${sanitized_name}`; +export function sharded_name(longname: string): string { + const sanitized_name = sanitize(longname) + const [a, b] = shard_bytes(sanitized_name) + return `${a}/${b}/${sanitized_name}` } -export function shape_link(longname: string) { - return "/shape/" + sharded_name(longname) + '.gz' +export function shape_link(longname: string): string { + return `/shape/${sharded_name(longname)}.gz` } -export function data_link(longname: string) { +export function data_link(longname: string): string { return `/data/${sharded_name(longname)}.gz` } -export function index_link(universe: string, typ: string) { +export function index_link(universe: string, typ: string): string { return `/index/${universe}/${sanitize(typ, false)}.gz` } -export function ordering_link(universe: string, type: string, idx: number) { +export function ordering_link(universe: string, type: string, idx: number): string { return `/order/${universe}/${sanitize(type, false)}_${idx}.gz` } -export function ordering_data_link(universe: string, type: string, idx: number) { +export function ordering_data_link(universe: string, type: string, idx: number): string { return `/order/${universe}/${sanitize(type, false)}_${idx}_data.gz` } -export function explanation_page_link(explanation: string) { +export function explanation_page_link(explanation: string): string { return `/data-credit.html#explanation_${sanitize(explanation)}` } -export function consolidated_shape_link(typ: string) { +export function consolidated_shape_link(typ: string): string { return `/consolidated/shapes__${sanitize(typ)}.gz` } -export function consolidated_stats_link(typ: string) { +export function consolidated_stats_link(typ: string): string { return `/consolidated/stats__${sanitize(typ)}.gz` } -export function comparison_link(universe: string, names: string[]) { +export function comparison_link(universe: string, names: string[]): string { const params = new URLSearchParams() - params.set('longnames', JSON.stringify(names.map(name => sanitize(name)))); - add_universe_to_params(universe, params); - return "/comparison.html?" + params.toString(); + params.set('longnames', JSON.stringify(names.map(name => sanitize(name)))) + add_universe_to_params(universe, params) + return `/comparison.html?${params.toString()}` } -export function statistic_link(universe: string | undefined, statname: string, article_type: string, start: number, amount: number | "All", order: string | undefined, highlight: string | undefined) { +export function statistic_link(universe: string | undefined, statname: string, article_type: string, start: number, amount: number | 'All', order: string | undefined, highlight: string | undefined): string { // make start % amount == 0 - if (amount != "All") { - start = start - 1; - start = start - (start % amount); - start = start + 1; + if (amount !== 'All') { + start = start - 1 + start = start - (start % amount) + start = start + 1 } const params = new URLSearchParams() - params.set('statname', statname); - params.set('article_type', article_type); - if (start !== undefined) { - params.set('start', start.toString()); - } - if (amount !== undefined) { - params.set('amount', `${amount}`) - } - if (order !== undefined && order !== null && order !== 'descending') { - params.set('order', order); + params.set('statname', statname) + params.set('article_type', article_type) + params.set('start', start.toString()) + params.set('amount', `${amount}`) + if (order !== undefined && order !== 'descending') { + params.set('order', order) } if (highlight !== undefined) { - params.set('highlight', highlight); + params.set('highlight', highlight) } - add_universe_to_params(universe, params); - return "/statistic.html?" + params.toString(); + add_universe_to_params(universe, params) + return `/statistic.html?${params.toString()}` } -export function sanitize(longname: string, spaces_around_slash = true) { - let x = longname; +export function sanitize(longname: string, spaces_around_slash = true): string { + let x = longname if (spaces_around_slash) { - x = x.replace("/", " slash "); - } else { - x = x.replace("/", "slash"); + x = x.replace('/', ' slash ') + } + else { + x = x.replace('/', 'slash') } - x = x.replace("%", "%25"); - return x; + x = x.replace('%', '%25') + return x } -export function universe_path(universe: string) { +export function universe_path(universe: string): string { return `/icons/flags/${universe}.png` } -export function add_universe_to_params(universe: string | undefined, params: URLSearchParams) { +export function add_universe_to_params(universe: string | undefined, params: URLSearchParams): void { if (universe !== undefined) - params.set("universe", universe) + params.set('universe', universe) } diff --git a/react/src/navigation/random.ts b/react/src/navigation/random.ts index 299ae8f4..ea1950d6 100644 --- a/react/src/navigation/random.ts +++ b/react/src/navigation/random.ts @@ -1,58 +1,57 @@ -export { uniform, by_population }; -import "../style.css"; -import "../common.css"; -import { article_link } from './links'; +import '../style.css' +import '../common.css' -import { loadJSON, loadProtobuf } from '../load_json'; -import { is_historical_cd } from "../utils/is_historical"; +import { loadJSON, loadProtobuf } from '../load_json' +import { is_historical_cd } from '../utils/is_historical' +import { article_link } from './links' -async function by_population(settings: { show_historical_cds: boolean }, domestic_only = false) { - const values = (await loadProtobuf("/index/pages.gz", "StringList")).elements; - const populations = loadJSON("/index/best_population_estimate.json") as number[]; - const totalWeight = populations.reduce((sum, x) => sum + x); +export async function by_population(settings: { show_historical_cds: boolean }, domestic_only = false): Promise { + const values = (await loadProtobuf('/index/pages.gz', 'StringList')).elements + const populations = loadJSON('/index/best_population_estimate.json') as number[] + const totalWeight = populations.reduce((sum, x) => sum + x) while (true) { - // Generate a random number between 0 and the total weight - const randomValue = Math.random() * totalWeight; + // Generate a random number between 0 and the total weight + const randomValue = Math.random() * totalWeight // Find the destination based on the random value - let x; - let cumulativeWeight = 0; + let x: string + let cumulativeWeight = 0 for (let i = 0; i < values.length; i++) { - cumulativeWeight += populations[i]; + cumulativeWeight += populations[i] if (randomValue < cumulativeWeight) { - x = values[i]; - break; + x = values[i] + break } } if (!settings.show_historical_cds && is_historical_cd(x!)) { - continue; + continue } // this is specifically looking for stuff that's only in the US. // so it makes sense. - if (domestic_only && (!x!.endsWith(", USA"))) { - continue; + if (domestic_only && (!x!.endsWith(', USA'))) { + continue } - document.location = article_link(undefined, x!); - break; + document.location = article_link(undefined, x!) + break } } -async function uniform(settings: { show_historical_cds: boolean }) { - const values = (await loadProtobuf("/index/pages.gz", "StringList")).elements; +export async function uniform(settings: { show_historical_cds: boolean }): Promise { + const values = (await loadProtobuf('/index/pages.gz', 'StringList')).elements while (true) { - const randomIndex = Math.floor(Math.random() * values.length); - const x = values[randomIndex]; + const randomIndex = Math.floor(Math.random() * values.length) + const x = values[randomIndex] if (!settings.show_historical_cds && is_historical_cd(x)) { - continue; + continue } - document.location = article_link(undefined, x); - break; + document.location = article_link(undefined, x) + break } -} \ No newline at end of file +} diff --git a/react/src/page_template/settings.ts b/react/src/page_template/settings.ts index e04a9512..173c63e7 100644 --- a/react/src/page_template/settings.ts +++ b/react/src/page_template/settings.ts @@ -1,75 +1,71 @@ -import { createContext, useContext, useEffect, useState } from "react"; -import { DefaultMap } from "../utils/DefaultMap"; +import { createContext, useContext, useEffect, useState } from 'react' + +import { DefaultMap } from '../utils/DefaultMap' export type StatisticSettingKey = `show_statistic_${string}` export type RelationshipKey = `related__${string}__${string}` export type RowExpandedKey = `expanded__${string}` -export type HistogramType = "Bar" | "Line" | "Line (cumulative)" +export type HistogramType = 'Bar' | 'Line' | 'Line (cumulative)' interface StatisticCategoryMetadataCheckbox { setting_key: StatisticSettingKey name: string } - export interface SettingsDictionary { - [relationshipKey: RelationshipKey]: boolean; - [showStatisticKey: StatisticSettingKey]: boolean; - [rowExpandedKey: RowExpandedKey]: boolean; - show_historical_cds: boolean, - simple_ordinals: boolean, - use_imperial: boolean, - histogram_type: HistogramType, - histogram_relative: boolean, + [relationshipKey: RelationshipKey]: boolean + [showStatisticKey: StatisticSettingKey]: boolean + [rowExpandedKey: RowExpandedKey]: boolean + show_historical_cds: boolean + simple_ordinals: boolean + use_imperial: boolean + histogram_type: HistogramType + histogram_relative: boolean } -export function relationship_key(article_type: string, other_type: string) { - return `related__${article_type}__${other_type}` as const; +export function relationship_key(article_type: string, other_type: string): RelationshipKey { + return `related__${article_type}__${other_type}` } -export function row_expanded_key(row_statname: string) { - return `expanded__${row_statname}` as const; +export function row_expanded_key(row_statname: string): RowExpandedKey { + return `expanded__${row_statname}` } -export function load_settings() { +export function load_settings(): [SettingsDictionary, StatisticCategoryMetadataCheckbox[]] { // backed by local storage - const settings = JSON.parse(localStorage.getItem("settings") ?? "{}") as Partial; - const map_relationship = require("../data/map_relationship.json"); - for (const i in map_relationship) { - const key = relationship_key(map_relationship[i][0], map_relationship[i][1]); + const settings = JSON.parse(localStorage.getItem('settings') ?? '{}') as Partial + const map_relationship = require('../data/map_relationship.json') as [string, string][] + for (const [article_type, other_type] of map_relationship) { + const key = relationship_key(article_type, other_type) if (!(key in settings)) { - settings[key] = true; + settings[key] = true } } - const statistic_category_metadata = require("../data/statistic_category_metadata.json") as { key: string, name: string, show_checkbox: boolean, default: boolean }[]; + const statistic_category_metadata = require('../data/statistic_category_metadata.json') as { key: string, name: string, show_checkbox: boolean, default: boolean }[] // list of {key, name, show_checkbox, default} - const statistic_category_metadata_checkboxes: StatisticCategoryMetadataCheckbox[] = []; - for (const i in statistic_category_metadata) { - const key = statistic_category_metadata[i]["key"]; - const setting_key = `show_statistic_${key}` as const; + const statistic_category_metadata_checkboxes: StatisticCategoryMetadataCheckbox[] = [] + for (const { key, default: defaultSetting, show_checkbox, name } of statistic_category_metadata) { + const setting_key = `show_statistic_${key}` as const if (!(setting_key in settings)) { - settings[setting_key] = statistic_category_metadata[i]["default"]; + settings[setting_key] = defaultSetting } - if (statistic_category_metadata[i]["show_checkbox"]) { - statistic_category_metadata_checkboxes.push({ - setting_key: setting_key, - name: statistic_category_metadata[i]["name"], - }); + if (show_checkbox) { + statistic_category_metadata_checkboxes.push({ setting_key, name }) } } settings.show_historical_cds = settings.show_historical_cds ?? false settings.simple_ordinals = settings.simple_ordinals ?? false settings.use_imperial = settings.use_imperial ?? false - settings.histogram_type = settings.histogram_type ?? "Line" + settings.histogram_type = settings.histogram_type ?? 'Line' settings.histogram_relative = settings.histogram_relative ?? true - return [settings as SettingsDictionary, statistic_category_metadata_checkboxes] as const; + return [settings as SettingsDictionary, statistic_category_metadata_checkboxes] } - export type BooleanSettings = { [K in keyof SettingsDictionary as SettingsDictionary[K] extends boolean ? K : never]: boolean } +/* eslint-disable react-hooks/rules-of-hooks -- We do kind of hacky things with hooks and iteration. But they mostly work because the keys don't change. */ export class Settings { private readonly settings: SettingsDictionary readonly statistic_category_metadata_checkboxes: StatisticCategoryMetadataCheckbox[] @@ -84,7 +80,7 @@ export class Settings { const [result, setResult] = useState(this.settings[key]) useEffect(() => { setResult(this.settings[key]) // So that if `key` changes we change our result immediately - const observer = () => setResult(this.settings[key]) + const observer = (): void => { setResult(this.settings[key]) } this.observers.get(key).add(observer) return () => { this.observers.get(key).delete(observer) @@ -95,8 +91,8 @@ export class Settings { setSetting(key: K, newValue: SettingsDictionary[K]): void { this.settings[key] = newValue - localStorage.setItem("settings", JSON.stringify(this.settings)) - this.observers.get(key).forEach(observer => observer()) + localStorage.setItem('settings', JSON.stringify(this.settings)) + this.observers.get(key).forEach((observer) => { observer() }) } get(key: K): SettingsDictionary[K] { @@ -109,33 +105,33 @@ export class Settings { export function useSetting(key: K): [SettingsDictionary[K], (newValue: SettingsDictionary[K]) => void] { const settings = useContext(Settings.Context) - return [settings.useSetting(key), (value) => settings.setSetting(key, value)] + return [settings.useSetting(key), (value) => { settings.setSetting(key, value) }] } - -export type TableCheckboxSettings = Record; +export type TableCheckboxSettings = Record export function useTableCheckboxSettings(): BooleanSettings { - const categories = require("../data/statistic_category_list.json"); + const categories = require('../data/statistic_category_list.json') as string[] const result = {} as BooleanSettings for (const category of categories) { - const key = `show_statistic_${category}` as StatisticSettingKey + const key = `show_statistic_${category}` as const result[key] = useSetting(key)[0] } return result } export function useRelatedCheckboxSettings(article_type_this: string): Record { - const article_types_other = require("../data/type_to_type_category.json"); + const article_types_other = require('../data/type_to_type_category.json') as Record const result = {} as Record - for (const article_type_other in article_types_other) { + for (const article_type_other of Object.keys(article_types_other)) { const key = relationship_key(article_type_this, article_type_other) result[key] = useSetting(key)[0] } return result } -export function useStatisticCategoryMetadataCheckboxes() { +export function useStatisticCategoryMetadataCheckboxes(): StatisticCategoryMetadataCheckbox[] { const settings = useContext(Settings.Context) return settings.statistic_category_metadata_checkboxes -} \ No newline at end of file +} +/* eslint-enable react-hooks/rules-of-hooks */ diff --git a/react/src/page_template/template.tsx b/react/src/page_template/template.tsx index 8ca7d600..508f31b0 100644 --- a/react/src/page_template/template.tsx +++ b/react/src/page_template/template.tsx @@ -1,21 +1,21 @@ -import "@fontsource/jost/100.css"; -import "@fontsource/jost/200.css"; -import "@fontsource/jost/300.css"; -import "@fontsource/jost/400.css"; -import "@fontsource/jost/500.css"; -import "@fontsource/jost/600.css"; -import "@fontsource/jost/700.css"; -import "@fontsource/jost/800.css"; -import "@fontsource/jost/900.css"; +import '@fontsource/jost/100.css' +import '@fontsource/jost/200.css' +import '@fontsource/jost/300.css' +import '@fontsource/jost/400.css' +import '@fontsource/jost/500.css' +import '@fontsource/jost/600.css' +import '@fontsource/jost/700.css' +import '@fontsource/jost/800.css' +import '@fontsource/jost/900.css' -import React, { Fragment, useState } from 'react'; +import React, { Fragment, ReactNode, useState } from 'react' -import { Header } from "../components/header"; -import { Sidebar } from "../components/sidebar"; -import "../common.css"; -import "../components/article.css"; -import { mobileLayout } from '../utils/responsive'; -import { create_screenshot, ScreencapElements } from '../components/screenshot'; +import { Header } from '../components/header' +import { ScreencapElements, create_screenshot } from '../components/screenshot' +import { Sidebar } from '../components/sidebar' +import '../common.css' +import '../components/article.css' +import { mobileLayout } from '../utils/responsive' export function PageTemplate({ screencap_elements = undefined, @@ -24,32 +24,31 @@ export function PageTemplate({ children, }: { screencap_elements?: () => ScreencapElements - has_universe_selector?: boolean, - universes?: string[], + has_universe_selector?: boolean + universes?: string[] children: ({ screenshot_mode }: { screenshot_mode: boolean }) => React.ReactNode -}) { - const [hamburger_open, set_hamburger_open] = useState(false); - const [screenshot_mode, set_screenshot_mode] = useState(false); +}): ReactNode { + const [hamburger_open, set_hamburger_open] = useState(false) + const [screenshot_mode, set_screenshot_mode] = useState(false) - const has_screenshot_button = screencap_elements != undefined; + const has_screenshot_button = screencap_elements !== undefined - const screencap = async (curr_universe: string) => { + const screencap = async (curr_universe: string): Promise => { if (screencap_elements === undefined) { - console.log("No screencap elements defined for this page."); - return; + return } try { - console.log("Creating screenshot..."); - await create_screenshot(screencap_elements(), has_universe_selector ? curr_universe : undefined); - } catch (e) { - console.error(e); + await create_screenshot(screencap_elements(), has_universe_selector ? curr_universe : undefined) + } + catch (e) { + console.error(e) } } - const initiate_screenshot = async (curr_universe: string) => { + const initiate_screenshot = (curr_universe: string): void => { set_screenshot_mode(true) setTimeout(async () => { - await screencap(curr_universe); + await screencap(curr_universe) set_screenshot_mode(false) }) } @@ -57,7 +56,7 @@ export function PageTemplate({ return ( -
    +
    initiate_screenshot(curr_universe)} + initiate_screenshot={(curr_universe) => { initiate_screenshot(curr_universe) }} /> -
    +
    - ); + ) } -function TemplateFooter() { - return
    - Urban Stats Version by . Last updated . Not for commercial use. -
    +function TemplateFooter(): ReactNode { + return ( +
    + {'Urban Stats Version '} + + {' by '} + + {'. Last updated '} + + {'. '} + + {' Not for commercial use. '} + +
    + ) } -function Version() { +function Version(): ReactNode { return 17.0.0 } -function LastUpdated() { +function LastUpdated(): ReactNode { return 2024-09-01 } -function MainCredits() { +function MainCredits(): ReactNode { return Kavi Gupta and Luke Brody } -function OtherCredits() { - return - Significant help with weather data from OklahomaPerson. - +function OtherCredits(): ReactNode { + return ( + + {'Significant help with weather data from '} + OklahomaPerson + . + + ) } -function BodyPanel({ hamburger_open, main_content }: { hamburger_open: boolean, main_content: React.ReactNode }) { +function BodyPanel({ hamburger_open, main_content }: { hamburger_open: boolean, main_content: React.ReactNode }): ReactNode { if (hamburger_open) { return } - return
    - {mobileLayout() ? undefined : } -
    - {main_content} -
    - + return ( +
    + {mobileLayout() ? undefined : } +
    + {main_content} +
    + +
    -
    + ) } -function LeftPanel() { +function LeftPanel(): ReactNode { return ( -
    +
    ) } -function Support() { - return - If you find urbanstats useful, please donate on kofi! - -} \ No newline at end of file +function Support(): ReactNode { + return ( + + {'If you find urbanstats useful, please donate on '} + kofi + ! + + ) +} diff --git a/react/src/quiz.tsx b/react/src/quiz.tsx index 7d059dac..a0eaa8db 100644 --- a/react/src/quiz.tsx +++ b/react/src/quiz.tsx @@ -1,78 +1,74 @@ -import React from 'react'; +import React from 'react' +import ReactDOM from 'react-dom/client' +import './style.css' +import './common.css' -import ReactDOM from 'react-dom/client'; -import "./style.css"; -import "./common.css"; +import { QuizPanel } from './components/quiz-panel' +import { loadJSON } from './load_json' +import { get_daily_offset_number, get_retrostat_offset_number } from './quiz/dates' +import { JuxtaQuestionJSON, QuizDescriptor, RetroQuestionJSON, load_juxta, load_retro } from './quiz/quiz' -import { loadJSON } from './load_json'; +const ENDPOINT = 'https://persistent.urbanstats.org' -import { QuizPanel } from './components/quiz-panel'; - -import { get_daily_offset_number, get_retrostat_offset_number } from './quiz/dates'; -import { JuxtaQuestionJSON, load_juxta, load_retro, QuizDescriptor, RetroQuestionJSON } from "./quiz/quiz"; - -const ENDPOINT = "https://persistent.urbanstats.org"; - -async function loadPage() { - document.title = "Juxtastat"; - const root = ReactDOM.createRoot(document.getElementById("root")!); +async function loadPage(): Promise { + document.title = 'Juxtastat' + const root = ReactDOM.createRoot(document.getElementById('root')!) // if there's a query, parse it - const params_string = window.location.search.substring(1) || window.location.hash.substring(1); - console.log(params_string) + const params_string = window.location.search.substring(1) || window.location.hash.substring(1) let urlParams = new URLSearchParams( - params_string - ); + params_string, + ) if (urlParams.has('short')) { - // look up short url - const short = urlParams.get('short'); + // look up short url + const short = urlParams.get('short') // POST to endpoint - let response = await fetch(ENDPOINT + "/lengthen", { - method: "POST", + const responseJson = await fetch(`${ENDPOINT}/lengthen`, { + method: 'POST', body: JSON.stringify({ shortened: short }), headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, - }).then((response) => response.json()); - console.log(response); - response = response["full_text"]; - console.log(response); - urlParams = new URLSearchParams(response); - console.log(urlParams); + }).then(response => response.json() as Promise<{ full_text: string }>) + urlParams = new URLSearchParams(responseJson.full_text) } - const mode = urlParams.get('mode'); - let todays_quiz; - let today_name: string; - let descriptor: QuizDescriptor; - if (mode == "retro") { - document.title = "Retrostat"; - let retro = get_retrostat_offset_number(); + const mode = urlParams.get('mode') + let todays_quiz + let today_name: string + let descriptor: QuizDescriptor + if (mode === 'retro') { + document.title = 'Retrostat' + let retro = get_retrostat_offset_number() if (urlParams.has('date')) { - retro = parseInt(urlParams.get('date')!); + retro = parseInt(urlParams.get('date')!) } descriptor = { kind: 'retrostat', - name: `W${retro}` + name: `W${retro}`, } - today_name = "Week " + retro; - todays_quiz = (loadJSON("/retrostat/" + retro) as RetroQuestionJSON[]).map(load_retro); - } else { - // daily quiz - let today: number; + today_name = `Week ${retro}` + todays_quiz = (loadJSON(`/retrostat/${retro}`) as RetroQuestionJSON[]).map(load_retro) + } + else { + // daily quiz + let today: number if (urlParams.has('date')) { - today = parseInt(urlParams.get('date')!); - } else { - today = get_daily_offset_number(); + today = parseInt(urlParams.get('date')!) + } + else { + today = get_daily_offset_number() } - todays_quiz = (loadJSON("/quiz/" + today) as JuxtaQuestionJSON[]).map(load_juxta); - today_name = today.toString(); - descriptor = { kind: "juxtastat", name: today } + todays_quiz = (loadJSON(`/quiz/${today}`) as JuxtaQuestionJSON[]).map(load_juxta) + today_name = today.toString() + descriptor = { kind: 'juxtastat', name: today } } - root.render(); + root.render( + , + ) } -loadPage(); \ No newline at end of file +void loadPage() diff --git a/react/src/quiz/dates.ts b/react/src/quiz/dates.ts index cda17631..9f4c0de2 100644 --- a/react/src/quiz/dates.ts +++ b/react/src/quiz/dates.ts @@ -1,94 +1,94 @@ -import { QuizDescriptor } from "./quiz"; +import { QuizDescriptor } from './quiz' -const reference = new Date(2023, 8, 2); // 2023-09-02. 8 is September, since months are 0-indexed for some fucking reason +const reference = new Date(2023, 8, 2) // 2023-09-02. 8 is September, since months are 0-indexed for some fucking reason -export function get_daily_offset_number() { +export function get_daily_offset_number(): number { // fractional days since reference // today's date without the time - const today = new Date(); - today.setHours(0, 0, 0, 0); - let offset = (today.valueOf() - reference.valueOf()) / (1000 * 60 * 60 * 24); + const today = new Date() + today.setHours(0, 0, 0, 0) + let offset = (today.valueOf() - reference.valueOf()) / (1000 * 60 * 60 * 24) // round to nearest day. this handles daylight savings time, since it's always a midnight-to-midnight comparison. // E.g., if it's 9/3 at 1am, the offset will be 9/3 at 0am - 9/2 at 0am = 1 day, which is correct. // Similarly, if it's 11/11 at 1am, the offset will be // 11/11 at 0am [NO DST] - 9/2 at 0am [DST] = (30 + 31 + 9) days + 1 hour = 70 days + 1 hour // which rounded to the nearest day is 70 days, which is correct. - offset = Math.round(offset); - return offset; + offset = Math.round(offset) + return offset } -export function get_retrostat_offset_number() { - const daily = get_daily_offset_number(); +export function get_retrostat_offset_number(): number { + const daily = get_daily_offset_number() // 78 through 84 --> 0 - return Math.floor((daily - 1) / 7) - 11; + return Math.floor((daily - 1) / 7) - 11 } -function day_start(offset: number) { - const date = new Date(reference); - date.setDate(date.getDate() + offset); - return date; +function day_start(offset: number): Date { + const date = new Date(reference) + date.setDate(date.getDate() + offset) + return date } -function day_end(offset: number) { - const start = day_start(offset); - start.setDate(start.getDate() + 1); - return start.valueOf(); +function day_end(offset: number): number { + const start = day_start(offset) + start.setDate(start.getDate() + 1) + return start.valueOf() } -function week_start(week_id: string) { +function week_start(week_id: string): Date { // check that week_id is W + number if (!week_id.startsWith('W')) { - throw new Error('week_id must start with W'); + throw new Error('week_id must start with W') } - const week_number = parseInt(week_id.slice(1)); - return day_start((week_number + 11) * 7 + 1); + const week_number = parseInt(week_id.slice(1)) + return day_start((week_number + 11) * 7 + 1) } -function week_end(week_id: string) { - const start = week_start(week_id); - start.setDate(start.getDate() + 7); - return start.valueOf(); +function week_end(week_id: string): number { + const start = week_start(week_id) + start.setDate(start.getDate() + 7) + return start.valueOf() } -function time_to_end_of_day(offset: number) { - return day_end(offset) - Date.now(); +function time_to_end_of_day(offset: number): number { + return day_end(offset) - Date.now() } -function time_to_end_of_week(week_id: string) { - return week_end(week_id) - Date.now(); +function time_to_end_of_week(week_id: string): number { + return week_end(week_id) - Date.now() } -function render_time_within_day(ms: number) { +function render_time_within_day(ms: number): string { // render HH:MM:SS from ms. Make sure to pad with 0s. - const seconds = Math.floor(ms / 1000); - const minutes = Math.floor(seconds / 60); - const hours = Math.floor(minutes / 60); - const seconds_remainder = seconds % 60; - const minutes_remainder = minutes % 60; - const hours_remainder = hours % 24; + const seconds = Math.floor(ms / 1000) + const minutes = Math.floor(seconds / 60) + const hours = Math.floor(minutes / 60) + const seconds_remainder = seconds % 60 + const minutes_remainder = minutes % 60 + const hours_remainder = hours % 24 - const seconds_string = seconds_remainder.toString().padStart(2, '0'); - const minutes_string = minutes_remainder.toString().padStart(2, '0'); - const hours_string = hours_remainder.toString().padStart(2, '0'); + const seconds_string = seconds_remainder.toString().padStart(2, '0') + const minutes_string = minutes_remainder.toString().padStart(2, '0') + const hours_string = hours_remainder.toString().padStart(2, '0') - return `${hours_string}:${minutes_string}:${seconds_string}`; + return `${hours_string}:${minutes_string}:${seconds_string}` } -function render_time_within_week(ms: number) { +function render_time_within_week(ms: number): string { // render X days, HH:MM:SS from ms. Make sure to pad with 0s. - const ms_per_day = 1000 * 60 * 60 * 24; - const days = Math.floor(ms / ms_per_day); - const without_days = ms % ms_per_day; - const time_string = render_time_within_day(without_days); + const ms_per_day = 1000 * 60 * 60 * 24 + const days = Math.floor(ms / ms_per_day) + const without_days = ms % ms_per_day + const time_string = render_time_within_day(without_days) // const s_if_plural = days === 1 ? '' : 's'; - return `${days}d ${time_string}`; + return `${days}d ${time_string}` } export function render_time_remaining({ kind, name }: QuizDescriptor): string { - switch(kind) { - case "juxtastat": - return render_time_within_day(time_to_end_of_day(name)); - case "retrostat": - return render_time_within_week(time_to_end_of_week(name)); + switch (kind) { + case 'juxtastat': + return render_time_within_day(time_to_end_of_day(name)) + case 'retrostat': + return render_time_within_week(time_to_end_of_week(name)) } -} \ No newline at end of file +} diff --git a/react/src/quiz/quiz-components.tsx b/react/src/quiz/quiz-components.tsx index 9f2bc7e6..948cc9a3 100644 --- a/react/src/quiz/quiz-components.tsx +++ b/react/src/quiz/quiz-components.tsx @@ -1,58 +1,70 @@ -import React from 'react'; +import React, { ReactNode } from 'react' -import "../common.css"; -import "../components/quiz.css"; -import { History } from "./statistics"; -import { nameOfQuizKind } from "./quiz"; -import { headerTextClass } from "../utils/responsive"; +import '../common.css' +import '../components/quiz.css' +import { headerTextClass } from '../utils/responsive' -export function Header({ quiz }: { quiz: { kind: "juxtastat" | "retrostat", name: string | number } }) { - let text = nameOfQuizKind(quiz.kind); - if (typeof quiz.name != "number") { - text += " " + quiz.name; +import { nameOfQuizKind } from './quiz' +import { History } from './statistics' + +export function Header({ quiz }: { quiz: { kind: 'juxtastat' | 'retrostat', name: string | number } }): ReactNode { + let text = nameOfQuizKind(quiz.kind) + if (typeof quiz.name !== 'number') { + text += ` ${quiz.name}` } - return (
    {text}
    ); + return (
    {text}
    ) } -export function Footer(props: { length: number, history: History[string] }) { +export function Footer(props: { length: number, history: History[string] }): ReactNode { const choices: `quiz_${'green' | 'red' | 'blank'}`[] = props.history.correct_pattern.map( - (correct) => correct ? "quiz_green" : "quiz_red" - ); + correct => correct ? 'quiz_green' : 'quiz_red', + ) while (choices.length < props.length) { - choices.push("quiz_blank"); + choices.push('quiz_blank') } - return
    + >
    + {create_value(x)}
    - - - {choices.map((x, i) => - - )} - - -
    + return ( + + + + {choices.map((x, i) => + , + )} + + +
    + ) } -export function Help(props: { quiz_kind: "juxtastat" | "retrostat" }) { - - const text = () => { - if (props.quiz_kind == "juxtastat") { - return "Select the geographical region answering the question. The questions get harder as you go on." - } else if (props.quiz_kind == "retrostat") { - return "Select the easier question. A question is considered easier if more people got it right." +export function Help(props: { quiz_kind: 'juxtastat' | 'retrostat' }): ReactNode { + const text = (): string => { + if (props.quiz_kind === 'juxtastat') { + return 'Select the geographical region answering the question. The questions get harder as you go on.' + } + else { + return 'Select the easier question. A question is considered easier if more people got it right.' } - throw new Error("Unknown quiz kind " + props.quiz_kind); } - return
    - {text()} {UserId()} -
    + return ( +
    + {text()} + {' '} + {UserId()} +
    + ) } -export function UserId() { - const user_id = localStorage.getItem("persistent_id"); +export function UserId(): ReactNode { + const user_id = localStorage.getItem('persistent_id') if (user_id === null) { - return ""; - } else { - return
    Your user id is {user_id}
    ; + return '' } -} \ No newline at end of file + else { + return ( +
    + {'Your user id is '} + {user_id} +
    + ) + } +} diff --git a/react/src/quiz/quiz-question.tsx b/react/src/quiz/quiz-question.tsx index b5c04ac3..d032c95c 100644 --- a/react/src/quiz/quiz-question.tsx +++ b/react/src/quiz/quiz-question.tsx @@ -1,38 +1,37 @@ +import '../common.css' +import '../components/quiz.css' -export { QuizQuestionDispatch }; +import React, { ReactNode } from 'react' +import { isFirefox } from 'react-device-detect' -import React, { ReactNode } from "react"; -import "../common.css"; -import "../components/quiz.css"; +import { MapGeneric, MapGenericProps, Polygons } from '../components/map' +import { mobileLayout } from '../utils/responsive' -import { isFirefox } from 'react-device-detect'; -import { Header, Footer, Help } from './quiz-components'; -import { MapGeneric, MapGenericProps } from '../components/map'; -import { mobileLayout } from "../utils/responsive"; -import { History } from "./statistics"; -import { a_correct, JuxtaQuestion, RetroQuestion } from "./quiz"; +import { JuxtaQuestion, RetroQuestion, a_correct } from './quiz' +import { Footer, Header, Help } from './quiz-components' +import { History } from './statistics' interface MapProps extends MapGenericProps { longname: string } class Map extends MapGeneric { - async compute_polygons(): Promise[], Record[], number]>> { - const style = { "interactive": false, "fillOpacity": 0.5, "weight": 1, "color": "#5a7dc3", "fillColor": "#5a7dc3" }; - return [[this.props.longname], [style], [{}], 0]; + override compute_polygons(): Promise { + const style = { interactive: false, fillOpacity: 0.5, weight: 1, color: '#5a7dc3', fillColor: '#5a7dc3' } + return Promise.resolve([[this.props.longname], [style], [{}], 0]) } } -function QuizQuestionDispatch(props: QuizQuestionProps & ( +export function QuizQuestionDispatch(props: QuizQuestionProps & ( { question: JuxtaQuestion | RetroQuestion } -)) { +)): ReactNode { switch (props.question.kind) { - case "retrostat": - return ; - case "juxtastat": - return ; + case 'retrostat': + return + case 'juxtastat': + return default: - throw new Error("Invalid question kind"); + throw new Error('Invalid question kind') } } @@ -42,70 +41,70 @@ interface QuizQuestionProps { nested: boolean no_header: boolean no_footer: boolean - quiz: { kind: "juxtastat" | "retrostat", name: number | string} - on_select: (letter: "A" | "B") => void - length: number, + quiz: { kind: 'juxtastat' | 'retrostat', name: number | string } + on_select: (letter: 'A' | 'B') => void + length: number } function QuizQuestion(props: QuizQuestionProps & { get_question: () => ReactNode - get_option: (letter: "a" | "b") => ReactNode - get_demo: (letter: "a" | "b") => ReactNode -}) { - - let button_a = "quiz_clickable"; - let button_b = "quiz_clickable"; + get_option: (letter: 'a' | 'b') => ReactNode + get_demo: (letter: 'a' | 'b') => ReactNode +}): ReactNode { + let button_a = 'quiz_clickable' + let button_b = 'quiz_clickable' if (props.waiting) { - const choices = props.history.choices; - const pattern = props.history.correct_pattern; - const choice = choices[choices.length - 1]; - const correct = pattern[pattern.length - 1]; - const css_class = correct ? " quiz_correct" : " quiz_incorrect"; - if (choice == "A") { - button_a += css_class; - } else { - button_b += css_class; + const choices = props.history.choices + const pattern = props.history.correct_pattern + const choice = choices[choices.length - 1] + const correct = pattern[pattern.length - 1] + const css_class = correct ? ' quiz_correct' : ' quiz_incorrect' + if (choice === 'A') { + button_a += css_class + } + else { + button_b += css_class } } - const question = props.get_question(); + const question = props.get_question() const button_style: React.CSSProperties = { - display: "flex", - justifyContent: "center", - alignItems: "center", - flexDirection: "column", - padding: "0.5em", + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + flexDirection: 'column', + padding: '0.5em', } - const row_style = { display: "flex", justifyContent: "center", width: "90%", margin: "auto" }; + const row_style = { display: 'flex', justifyContent: 'center', width: '90%', margin: 'auto' } - let quiztext_css = mobileLayout() ? "quiztext_mobile" : "quiztext"; + let quiztext_css = mobileLayout() ? 'quiztext_mobile' : 'quiztext' if (props.nested) { - quiztext_css += "_nested"; + quiztext_css += '_nested' } return (
    {props.no_header ? undefined :
    } -
    +
    {question}
    -
    -
    props.on_select("A")} style={button_style}> - -
    +
    +
    { props.on_select('A') }} style={button_style}> + +
    {props.get_option('a')}
    -
    -
    props.on_select("B")} style={button_style}> - -
    +
    +
    { props.on_select('B') }} style={button_style}> + +
    {props.get_option('b')}
    @@ -113,88 +112,110 @@ function QuizQuestion(props: QuizQuestionProps & {
    -
    +
    {props.get_demo('a')}
    -
    +
    {props.get_demo('b')}
    - {props.no_footer ? undefined : <> -
    - - } + {props.no_footer + ? undefined + : ( + <> +
    + + + )}
    ) } -function RetroQuizQuestion(props: QuizQuestionProps & { question: RetroQuestion }) { - - const get_demo = (key: 'a' | 'b') => { - const key_upper = a_correct(props.question[key]) ? "A" : "B"; - console.log(key_upper); - return
    - undefined} - waiting={true} - quiz={{ kind: 'juxtastat', name: "demo" }} - no_header={true} - no_footer={true} - nested={isFirefox} // Firefox doesn't support zoom so we use special CSS for nested questions - /> -
    +function RetroQuizQuestion(props: QuizQuestionProps & { question: RetroQuestion }): ReactNode { + const get_demo = (key: 'a' | 'b'): ReactNode => { + const key_upper = a_correct(props.question[key]) ? 'A' : 'B' + return ( +
    + undefined} + waiting={true} + quiz={{ kind: 'juxtastat', name: 'demo' }} + no_header={true} + no_footer={true} + nested={isFirefox} // Firefox doesn't support zoom so we use special CSS for nested questions + /> +
    + ) } - return "Which question was easier?"} - get_option={(letter) => `Question ${letter.toUpperCase()}`} - get_demo={get_demo} - /> - - + return ( + 'Which question was easier?'} + get_option={letter => `Question ${letter.toUpperCase()}`} + get_demo={get_demo} + /> + ) } function JuxtastatQuizQuestion(props: QuizQuestionProps & { question: JuxtaQuestion -}) { - return render_question(props.question.question)} - get_option={(letter) => props.question[`longname_${letter}`]} - get_demo={(letter) => } - /> +}): ReactNode { + return ( + render_question(props.question.question)} + get_option={letter => props.question[`longname_${letter}`]} + get_demo={letter => ( + + )} + /> + ) } -export function question_string(question: string) { - if (question.startsWith("!FULL ")) { - return question.slice(6); +export function question_string(question: string): string { + if (question.startsWith('!FULL ')) { + return question.slice(6) } return `Which has a ${question}?` } -export function render_question(question_text: string) { - if (question_text.includes("!TOOLTIP")) { - const [question, tooltip] = question_text.split("!TOOLTIP "); - return {question_string(question)}; +export function render_question(question_text: string): ReactNode { + if (question_text.includes('!TOOLTIP')) { + const [question, tooltip] = question_text.split('!TOOLTIP ') + return ( + + {question_string(question)} + + + ) } - const q = question_string(question_text); - return q; + const q = question_string(question_text) + return q } -export function Tooltip(props: { content: ReactNode }) { +export function Tooltip(props: { content: ReactNode }): ReactNode { // create an image that looks like a little [?] text superscript that when you click on it // shows the tooltip - const [show, setShow] = React.useState(false); - return - setShow(!show)}>ℹ️ - {show ?
    ({props.content})
    : undefined - } -
    -} \ No newline at end of file + const [show, setShow] = React.useState(false) + return ( + + { setShow(!show) }}>ℹ️ + {show + ? ( +
    + ( + {props.content} + ) +
    + ) + : undefined} +
    + ) +} diff --git a/react/src/quiz/quiz-result.tsx b/react/src/quiz/quiz-result.tsx index e6da5545..068b3dd4 100644 --- a/react/src/quiz/quiz-result.tsx +++ b/react/src/quiz/quiz-result.tsx @@ -1,51 +1,47 @@ -import React, { ReactNode, useEffect, useRef, useState } from 'react'; -import { isMobile, isFirefox } from 'react-device-detect'; +import React, { ReactNode, useEffect, useRef, useState } from 'react' +import { isFirefox, isMobile } from 'react-device-detect' -import { Statistic } from "../components/table"; -import { article_link } from "../navigation/links"; - - -import { AudienceStatistics, QuizStatistics } from './quiz-statistics'; -import { render_question } from './quiz-question'; -import { render_time_remaining } from './dates'; -import { QuizDescriptor, ENDPOINT, a_correct, QuizQuestion, JuxtaQuestion, RetroQuestion, nameOfQuizKind } from "./quiz"; -import { History } from "./statistics"; -import { Header, UserId } from "./quiz-components"; +import { Statistic } from '../components/table' +import { article_link } from '../navigation/links' +import { render_time_remaining } from './dates' +import { ENDPOINT, JuxtaQuestion, QuizDescriptor, QuizQuestion, RetroQuestion, a_correct, nameOfQuizKind } from './quiz' +import { Header, UserId } from './quiz-components' +import { render_question } from './quiz-question' +import { AudienceStatistics, QuizStatistics } from './quiz-statistics' +import { History } from './statistics' interface QuizResultProps { quizDescriptor: QuizDescriptor - get_per_question: Promise | undefined; - today_name: string; + get_per_question: Promise | undefined + today_name: string history: { - correct_pattern: boolean[]; - choices: ("A" | "B")[] - }; - parameters: string, - whole_history: History; - quiz: QuizQuestion[]; + correct_pattern: boolean[] + choices: ('A' | 'B')[] + } + parameters: string + whole_history: History + quiz: QuizQuestion[] } -export function QuizResult(props: QuizResultProps) { - - const button = useRef(null); - const [total, setTotal] = useState(0); - const [per_question, set_per_question] = useState([0, 0, 0, 0, 0]); +export function QuizResult(props: QuizResultProps): ReactNode { + const button = useRef(null) + const [total, setTotal] = useState(0) + const [per_question, set_per_question] = useState([0, 0, 0, 0, 0]) useEffect(() => { - (async () => { + void (async () => { if (props.get_per_question !== undefined) { - const response = await props.get_per_question.then((response) => response.json()); - setTotal(response.total); - set_per_question(response.per_question) + const responseJson = await props.get_per_question.then(response => response.json() as Promise<{ total: number, per_question: number[] }>) + setTotal(responseJson.total) + set_per_question(responseJson.per_question) } - })(); - }, []) + })() + }, [props.get_per_question]) - - const today_name = props.today_name; - const correct_pattern = props.history.correct_pattern; - const total_correct = correct_pattern.reduce((partialSum, a) => partialSum + (a ? 1 : 0), 0); + const today_name = props.today_name + const correct_pattern = props.history.correct_pattern + const total_correct = correct_pattern.reduce((partialSum, a) => partialSum + (a ? 1 : 0), 0) return (
    @@ -63,16 +59,20 @@ export function QuizResult(props: QuizResultProps) { />
    - {total > 30 ?
    - -
    -
    -
    : undefined} + {total > 30 + ? ( +
    + +
    +
    +
    + ) + : undefined}
    - Details (spoilers, don't share!) + Details (spoilers, don't share!)
    {props.quiz.map( (quiz, index) => ( @@ -83,13 +83,13 @@ export function QuizResult(props: QuizResultProps) { choice={props.history.choices[index]} correct={correct_pattern[index]} /> - ) + ), )}
    - ); + ) } interface ShareButtonProps { @@ -98,173 +98,184 @@ interface ShareButtonProps { today_name: string correct_pattern: boolean[] total_correct: number - quiz_kind: "juxtastat" | "retrostat" + quiz_kind: 'juxtastat' | 'retrostat' } -function ShareButton({ button_ref, parameters, today_name, correct_pattern, total_correct, quiz_kind }: ShareButtonProps) { - const can_share = navigator.canShare && navigator.canShare({ url: "https://juxtastat.org", text: "test" }); - const is_share = isMobile && can_share && !isFirefox; - console.log("ismobile, canshare, isfirefox", isMobile, can_share, isFirefox); - - return ; + return ( + + ) } -function Timer({ quiz }: { quiz: QuizDescriptor }) { - const [, setTime] = useState(0); +function Timer({ quiz }: { quiz: QuizDescriptor }): ReactNode { + const [, setTime] = useState(0) useEffect(() => { - const interval = setInterval(() => setTime(time => time + 1), 1000); - return () => clearInterval(interval); + const interval = setInterval(() => { setTime(time => time + 1) }, 1000) + return () => { clearInterval(interval) } }) - const w = quiz.kind == "juxtastat" ? "5em" : "6.5em"; - return
    - {render_time_remaining(quiz)} -
    + const w = quiz.kind === 'juxtastat' ? '5em' : '6.5em' + return ( +
    + {render_time_remaining(quiz)} +
    + ) } -function TimeToNextQuiz({ quiz }: { quiz: QuizDescriptor }) { +function TimeToNextQuiz({ quiz }: { quiz: QuizDescriptor }): ReactNode { return ( -
    +
    -
    Next quiz in
    + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'flex-center', + flexWrap: 'wrap', + gap: '1em', + }} + > +
    Next quiz in
    - ); + ) } -export function Summary(props: { total_correct: number, total: number, correct_pattern: boolean[] }) { - let show = "error"; +export function Summary(props: { total_correct: number, total: number, correct_pattern: boolean[] }): ReactNode { + let show = 'error' // let frac = this.props.total_correct / this.props.total_correct; - const correct = props.total_correct; - const incorrect = props.total - props.total_correct; - - if (correct == 0) { - show = "Impressively Bad Job! 🤷"; - } else if (incorrect == 0) { - show = "Perfect! 🔥"; - } else if (correct == 1) { - show = "No! No!! 😠"; - } else if (incorrect == 1) { - show = "Excellent! 😊"; - } else if (incorrect == 2) { - show = "Good! 🙃"; - } else { - show = "Better luck next time! 🫤"; + const correct = props.total_correct + const incorrect = props.total - props.total_correct + + if (correct === 0) { + show = 'Impressively Bad Job! 🤷' + } + else if (incorrect === 0) { + show = 'Perfect! 🔥' + } + else if (correct === 1) { + show = 'No! No!! 😠' + } + else if (incorrect === 1) { + show = 'Excellent! 😊' + } + else if (incorrect === 2) { + show = 'Good! 🙃' + } + else { + show = 'Better luck next time! 🫤' } - show = show + " " + correct + "/" + props.total; - return
    - {show} - {red_and_green_squares(props.correct_pattern)} -
    ; + show = `${show} ${correct}/${props.total}` + return ( +
    + {show} + {red_and_green_squares(props.correct_pattern)} +
    + ) } -export async function summary(today_name: string, correct_pattern: boolean[], total_correct: number, parameters: string, quiz_kind: "juxtastat" | "retrostat") { +export async function summary(today_name: string, correct_pattern: boolean[], total_correct: number, parameters: string, quiz_kind: 'juxtastat' | 'retrostat'): Promise<[string, string]> { // wordle-style summary - let text = nameOfQuizKind(quiz_kind) + " " + today_name + " " + total_correct + "/" + correct_pattern.length; + let text = `${nameOfQuizKind(quiz_kind)} ${today_name} ${total_correct}/${correct_pattern.length}` - text += "\n"; - text += "\n"; + text += '\n' + text += '\n' - text += red_and_green_squares(correct_pattern); + text += red_and_green_squares(correct_pattern) - text += "\n"; + text += '\n' - - let url = "https://juxtastat.org"; - if (parameters != "") { - console.log(parameters); + let url = 'https://juxtastat.org' + if (parameters !== '') { if (parameters.length > 100) { // POST to endpoint - const response = await fetch(ENDPOINT + "/shorten", { - method: "POST", + const responseJson = await fetch(`${ENDPOINT}/shorten`, { + method: 'POST', body: JSON.stringify({ full_text: parameters }), headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, - }).then((response) => response.json()); + }).then(response => response.json() as Promise<{ shortened: string }>) // get short url - const short = response["shortened"]; - parameters = "short=" + short; - + const short = responseJson.shortened + parameters = `short=${short}` } - url += "/#" + parameters; + url += `/#${parameters}` } - return [text, url]; + return [text, url] } -function QuizResultRow(props: QuizResultRowProps & { question: QuizQuestion }) { - switch(props.question.kind) { - case "juxtastat": - return ; - case "retrostat": - return ; +function QuizResultRow(props: QuizResultRowProps & { question: QuizQuestion }): ReactNode { + switch (props.question.kind) { + case 'juxtastat': + return + case 'retrostat': + return } } interface QuizResultRowProps { question: QuizQuestion - choice: "A" | "B" + choice: 'A' | 'B' correct: boolean index: number } interface GenericQuizResultRowProps extends QuizResultRowProps { get_label: () => ReactNode - get_option: (letter: "a" | "b") => ReactNode - get_stat: (letter: "a" | "b") => ReactNode + get_option: (letter: 'a' | 'b') => ReactNode + get_stat: (letter: 'a' | 'b') => ReactNode } -export function GenericQuizResultRow(props: GenericQuizResultRowProps) { - - const comparison = a_correct(props.question) ? - (>) : (<); - let first = "serif quiz_result_name_left"; - let second = "serif quiz_result_name_right"; +export function GenericQuizResultRow(props: GenericQuizResultRowProps): ReactNode { + const comparison = a_correct(props.question) + ? (>) + : (<) + let first = 'serif quiz_result_name_left' + let second = 'serif quiz_result_name_right' - if (props.choice == "A") { - first += " quiz_selected"; - } else { - second += " quiz_selected"; + if (props.choice === 'A') { + first += ' quiz_selected' } - const result = props.correct ? "🟩" : "🟥"; + else { + second += ' quiz_selected' + } + const result = props.correct ? '🟩' : '🟥' return (
    @@ -273,19 +284,19 @@ export function GenericQuizResultRow(props: GenericQuizResultRowProps) { - {props.get_option("a")} + {props.get_option('a')} - {props.get_stat("a")} + {props.get_stat('a')} {comparison} - {props.get_stat("b")} + {props.get_stat('b')} - {props.get_option("b")} + {props.get_option('b')} {result} @@ -296,71 +307,91 @@ export function GenericQuizResultRow(props: GenericQuizResultRowProps) {
    - ); + ) } -function Value({ stat, stat_column }: { stat: number, stat_column: string }) { - return - - - ; +function Value({ stat, stat_column }: { stat: number, stat_column: string }): ReactNode { + return ( + + + + + ) } -function JuxtastatQuizResultRow(props: QuizResultRowProps & { question: JuxtaQuestion }) { - return - {props.question.stat_column} - } - get_option={(letter) => } - get_stat={(stat) => } - /> +function JuxtastatQuizResultRow(props: QuizResultRowProps & { question: JuxtaQuestion }): ReactNode { + return ( + ( + + {props.question.stat_column} + + )} + get_option={letter => } + get_stat={stat => } + /> + ) } -function RetrostatQuizResultRow(props: QuizResultRowProps & { question: RetroQuestion }) { - - return - Juxtastat Users Who Got This Question Right % - } - get_option={(letter) => { - const style = letter == "a" ? { marginLeft: "20%" } : { marginRight: "20%" }; - const q = props.question[letter]; - return
    -
    {render_question(q.question)}
    -
    -
    -
    -
    -
    - }} - get_stat={(stat) => } - /> +function RetrostatQuizResultRow(props: QuizResultRowProps & { question: RetroQuestion }): ReactNode { + return ( + ( + + Juxtastat Users Who Got This Question Right % + + )} + get_option={(letter) => { + const style = letter === 'a' ? { marginLeft: '20%' } : { marginRight: '20%' } + const q = props.question[letter] + return ( +
    +
    {render_question(q.question)}
    +
    +
    + + {' '} + +
    +
    + + {' '} + +
    +
    +
    + ) + }} + get_stat={stat => } + /> + ) } -export function Clickable({ longname }: { longname: string }) { +export function Clickable({ longname }: { longname: string }): ReactNode { // return {longname} // same without any link formatting - return - {longname} - ; + return ( + + {longname} + + ) } -export function red_and_green_squares(correct_pattern: boolean[]) { +export function red_and_green_squares(correct_pattern: boolean[]): string { return correct_pattern.map(function (x) { - // red square emoji for wrong, green for right - return x ? "🟩" : "🟥"; - }).join(""); + // red square emoji for wrong, green for right + return x ? '🟩' : '🟥' + }).join('') } - diff --git a/react/src/quiz/quiz-statistics.tsx b/react/src/quiz/quiz-statistics.tsx index 6102d0a0..ac19dfdd 100644 --- a/react/src/quiz/quiz-statistics.tsx +++ b/react/src/quiz/quiz-statistics.tsx @@ -1,125 +1,149 @@ -import React from 'react'; -import { parse_time_identifier, History } from './statistics'; -import { QuizDescriptor } from "./quiz"; +import React, { ReactNode } from 'react' + +import { QuizDescriptor } from './quiz' +import { History, parse_time_identifier } from './statistics' interface QuizStatisticsProps { quiz: QuizDescriptor whole_history: History } -export function QuizStatistics(props: QuizStatisticsProps) { - +export function QuizStatistics(props: QuizStatisticsProps): ReactNode { const history = (i: number): History[string] | undefined => { switch (props.quiz.kind) { - case "juxtastat": - return props.whole_history[i]; - case "retrostat": + case 'juxtastat': + return props.whole_history[i] + case 'retrostat': return props.whole_history[`W${i}`] } } - const today = parse_time_identifier(props.quiz.kind, props.quiz.name.toString()); - const historical_correct = new Array(today + 1).fill(-1); - const frequencies = new Array(6).fill(0); - const played_games = []; + const today = parse_time_identifier(props.quiz.kind, props.quiz.name.toString()) + const historical_correct = new Array(today + 1).fill(-1) + const frequencies = new Array(6).fill(0) + const played_games = [] for (let i = 0; i <= today; i++) { - const hist_i = history(i); + const hist_i = history(i) if (hist_i === undefined) { - continue; - } else { - const amount = hist_i.correct_pattern.reduce((partialSum, a) => partialSum + (a ? 1 : 0), 0); - historical_correct[i] = amount; - frequencies[amount] += 1; - played_games.push(amount); + continue + } + else { + const amount = hist_i.correct_pattern.reduce((partialSum, a) => partialSum + (a ? 1 : 0), 0) + historical_correct[i] = amount + frequencies[amount] += 1 + played_games.push(amount) } } - const max_streaks = new Array(historical_correct.length).fill(0); + const max_streaks = new Array(historical_correct.length).fill(0) for (let val = 0; val < max_streaks.length; val++) { if (historical_correct[val] >= 3) { - max_streaks[val] = (val > 0 ? max_streaks[val - 1] : 0) + 1; + max_streaks[val] = (val > 0 ? max_streaks[val - 1] : 0) + 1 } } - const max_streak = Math.max(...max_streaks); - const current_streak = max_streaks[today]; - const total_freq = frequencies.reduce((partialSum, a) => partialSum + a, 0); + const max_streak = Math.max(...max_streaks) + const current_streak = max_streaks[today] + const total_freq = frequencies.reduce((partialSum, a) => partialSum + a, 0) const statistics = [ { - name: "Played", - value: played_games.length, + name: 'Played', + value: played_games.length.toString(), }, { - name: "Mean score", + name: 'Mean score', value: ( played_games.reduce((partialSum, a) => partialSum + a, 0) / played_games.length ).toFixed(2), }, { - name: "Win Rate (3+)", - value: ( - played_games.filter((x) => x >= 3).length + name: 'Win Rate (3+)', + value: `${( + played_games.filter(x => x >= 3).length / played_games.length * 100 - ).toFixed(0) + "%", + ).toFixed(0)}%`, }, { - name: "Current Streak (3+)", - value: current_streak, + name: 'Current Streak (3+)', + value: current_streak.toString(), }, { - name: "Max Streak (3+)", - value: max_streak, + name: 'Max Streak (3+)', + value: max_streak.toString(), }, - ]; - return
    -
    Your Statistics
    - -
    - - - {frequencies.map((amt, i) => - - - - )} - -
    - {i}/5 - - - - {amt > 0 ? ({amt} ({(amt / total_freq * 100).toFixed(1)}%)) : undefined} -
    -
    ; + ] + return ( +
    +
    Your Statistics
    + +
    + + + {frequencies.map((amt, i) => ( + + + + + ), + )} + +
    + {i} + /5 + + + + {amt > 0 + ? ( + + {amt} + {' '} + ( + {(amt / total_freq * 100).toFixed(1)} + %) + + ) + : undefined} +
    +
    + ) } -export function AudienceStatistics({ total, per_question } : { total: number, per_question: number[] }) { +export function AudienceStatistics({ total, per_question }: { total: number, per_question: number[] }): ReactNode { // two flexboxes of the scores for each - return
    -
    Question Difficulty
    - { - return { - name: "Q" + (i + 1) + " Correct", - value: (x / total * 100).toFixed(0) + "%", - addtl_class: (x / total > 0.5 ? "text_quiz_correct" : "text_quiz_incorrect") + " quiz-audience-statistics-displayed", - }; - } - )} /> -
    ; + return ( +
    +
    Question Difficulty
    + { + return { + name: `Q${i + 1} Correct`, + value: `${(x / total * 100).toFixed(0)}%`, + addtl_class: `${x / total > 0.5 ? 'text_quiz_correct' : 'text_quiz_incorrect'} quiz-audience-statistics-displayed`, + } + }, + )} + /> +
    + ) } -export function DisplayedStats({ statistics }: { statistics: { value: string, name: string, addtl_class?: string }[]}) { - return
    - {statistics.map((stat, i) => - )} -
    ; +export function DisplayedStats({ statistics }: { statistics: { value: string, name: string, addtl_class?: string }[] }): ReactNode { + return ( +
    + {statistics.map((stat, i) => , + )} +
    + ) } -export function DisplayedStat({ number, name, addtl_class }: { number: string, name: string, addtl_class?: string }) { +export function DisplayedStat({ number, name, addtl_class }: { number: string, name: string, addtl_class?: string }): ReactNode { // large font for numbers, small for names. Center-aligned using flexbox - return
    -
    {number}
    -
    {name}
    -
    ; - + return ( +
    +
    {number}
    +
    {name}
    +
    + ) } - diff --git a/react/src/quiz/quiz.ts b/react/src/quiz/quiz.ts index 7a56cf30..f4216462 100644 --- a/react/src/quiz/quiz.ts +++ b/react/src/quiz/quiz.ts @@ -1,35 +1,35 @@ -export type QuizDescriptor = { kind: "juxtastat", name: number } | { kind: "retrostat", name: string } +export type QuizDescriptor = { kind: 'juxtastat', name: number } | { kind: 'retrostat', name: string } -export const ENDPOINT = "https://persistent.urbanstats.org"; +export const ENDPOINT = 'https://persistent.urbanstats.org' export interface JuxtaQuestionJSON { stat_a: number, stat_b: number, question: string, longname_a: string, longname_b: string, stat_column: string }; -export interface JuxtaQuestion extends JuxtaQuestionJSON { kind: "juxtastat" } +export interface JuxtaQuestion extends JuxtaQuestionJSON { kind: 'juxtastat' } export interface RetroQuestionJSON { a_ease: number, b_ease: number, a: JuxtaQuestionJSON, b: JuxtaQuestionJSON }; -export interface RetroQuestion { kind: "retrostat", a_ease: number, b_ease: number, a: JuxtaQuestion, b: JuxtaQuestion } +export interface RetroQuestion { kind: 'retrostat', a_ease: number, b_ease: number, a: JuxtaQuestion, b: JuxtaQuestion } export type QuizQuestion = JuxtaQuestion | RetroQuestion export function a_correct(quiz: QuizQuestion): boolean { switch (quiz.kind) { - case "juxtastat": - return quiz.stat_a > quiz.stat_b; - case "retrostat": - return quiz.a_ease > quiz.b_ease; + case 'juxtastat': + return quiz.stat_a > quiz.stat_b + case 'retrostat': + return quiz.a_ease > quiz.b_ease } } -export function nameOfQuizKind(quiz_kind: "juxtastat" | "retrostat") { +export function nameOfQuizKind(quiz_kind: 'juxtastat' | 'retrostat'): string { return quiz_kind.replace( /\w\S*/g, function (txt) { - return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); - } - ); + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() + }, + ) } export function load_juxta(quiz: JuxtaQuestionJSON): JuxtaQuestion { - return { kind: "juxtastat", ...quiz }; + return { kind: 'juxtastat', ...quiz } } export function load_retro(quiz: RetroQuestionJSON): RetroQuestion { - return { kind: "retrostat", a: load_juxta(quiz.a), b: load_juxta(quiz.b), a_ease: quiz.a_ease, b_ease: quiz.b_ease }; -} \ No newline at end of file + return { kind: 'retrostat', a: load_juxta(quiz.a), b: load_juxta(quiz.b), a_ease: quiz.a_ease, b_ease: quiz.b_ease } +} diff --git a/react/src/quiz/statistics.ts b/react/src/quiz/statistics.ts index 1c5648e7..1e78abcb 100644 --- a/react/src/quiz/statistics.ts +++ b/react/src/quiz/statistics.ts @@ -1,97 +1,88 @@ +const ENDPOINT = 'https://persistent.urbanstats.org' -export { reportToServer, reportToServerRetro, parse_time_identifier }; - -const ENDPOINT = "https://persistent.urbanstats.org"; - -async function unique_persistent_id() { +async function unique_persistent_id(): Promise { // (domain name, id stored in local storage) // random 60 bit hex number // (15 hex digits) - if (localStorage.getItem("persistent_id") == undefined) { - let random_hex = ""; + if (localStorage.getItem('persistent_id') === null) { + let random_hex = '' for (let i = 0; i < 15; i++) { - random_hex += Math.floor(Math.random() * 16).toString(16)[0]; + random_hex += Math.floor(Math.random() * 16).toString(16)[0] } // register via server - await fetch(ENDPOINT + "/juxtastat/register_user", { - method: "POST", + await fetch(`${ENDPOINT}/juxtastat/register_user`, { + method: 'POST', body: JSON.stringify({ user: random_hex, domain: window.location.hostname }), headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, - }); + }) // register - localStorage.setItem("persistent_id", random_hex); + localStorage.setItem('persistent_id', random_hex) } - return localStorage.getItem("persistent_id"); + return localStorage.getItem('persistent_id')! } -export type History = Record +export type History = Record -async function reportToServerGeneric(whole_history: History, endpoint_latest: string, endpoint_store: string, parse_day: (day: string) => number) { - const user = await unique_persistent_id(); - console.log("USER", user); - console.log("whole history", whole_history); +async function reportToServerGeneric(whole_history: History, endpoint_latest: string, endpoint_store: string, parse_day: (day: string) => number): Promise { + const user = await unique_persistent_id() // fetch from latest_day endpoint const latest_day_response = await fetch(ENDPOINT + endpoint_latest, { - method: "POST", - body: JSON.stringify({ user: user }), + method: 'POST', + body: JSON.stringify({ user }), headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, - }); - const latest_day_json = await latest_day_response.json(); - console.log("latest day", latest_day_json); - const latest_day = latest_day_json["latest_day"]; - const filtered_days = Object.keys(whole_history).filter((day) => parse_day(day) > latest_day); + }) + const latest_day_json = await latest_day_response.json() as { latest_day: number } + const latest_day = latest_day_json.latest_day + const filtered_days = Object.keys(whole_history).filter(day => parse_day(day) > latest_day) const update = filtered_days.map((day) => { return [ parse_day(day), - whole_history[day]["correct_pattern"], + whole_history[day].correct_pattern, ] - }); - console.log(update); + }) // store user stats await fetch(ENDPOINT + endpoint_store, { - method: "POST", - body: JSON.stringify({ user: user, day_stats: JSON.stringify(update) }), + method: 'POST', + body: JSON.stringify({ user, day_stats: JSON.stringify(update) }), headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, - }); + }) } -function parse_time_identifier(quiz_kind: "juxtastat" | "retrostat", today: string) { +export function parse_time_identifier(quiz_kind: 'juxtastat' | 'retrostat', today: string): number { switch (quiz_kind) { - case "juxtastat": - return parse_juxtastat_day(today); - case "retrostat": - return parse_retrostat_week(today); + case 'juxtastat': + return parse_juxtastat_day(today) + case 'retrostat': + return parse_retrostat_week(today) } } -function parse_juxtastat_day(day: string) { +function parse_juxtastat_day(day: string): number { // return -10000 if day doesn't match -?[0-9]+ - if (/^-?[0-9]+$/.test(day) == false) { - return -10000; + if (!/^-?[0-9]+$/.test(day)) { + return -10000 } - return parseInt(day); + return parseInt(day) } -function parse_retrostat_week(day: string) { +function parse_retrostat_week(day: string): number { // return -10000 if day doesn't match W-?[0-9]+ - if (/^W-?[0-9]+$/.test(day) == false) { - return -10000; + if (!/^W-?[0-9]+$/.test(day)) { + return -10000 } - return parseInt(day.substring(1)); + return parseInt(day.substring(1)) } - - -async function reportToServer(whole_history: History) { - await reportToServerGeneric(whole_history, "/juxtastat/latest_day", "/juxtastat/store_user_stats", parse_juxtastat_day); +export async function reportToServer(whole_history: History): Promise { + await reportToServerGeneric(whole_history, '/juxtastat/latest_day', '/juxtastat/store_user_stats', parse_juxtastat_day) } -async function reportToServerRetro(whole_history: History) { - await reportToServerGeneric(whole_history, "/retrostat/latest_week", "/retrostat/store_user_stats", parse_retrostat_week); +export async function reportToServerRetro(whole_history: History): Promise { + await reportToServerGeneric(whole_history, '/retrostat/latest_week', '/retrostat/store_user_stats', parse_retrostat_week) } diff --git a/react/src/random.ts b/react/src/random.ts index 5ddbfa4b..cd13c10c 100644 --- a/react/src/random.ts +++ b/react/src/random.ts @@ -1,25 +1,22 @@ -import "./style.css"; -import "./common.css"; +import './style.css' +import './common.css' -import { by_population, uniform } from './navigation/random'; -import { load_settings } from './page_template/settings'; +import { by_population, uniform } from './navigation/random' +import { load_settings } from './page_template/settings' +async function main(): Promise { + const window_info = new URLSearchParams(window.location.search) -async function main() { - const window_info = new URLSearchParams(window.location.search); + const [settings] = load_settings() - const [settings] = load_settings(); + const sampleby = window_info.get('sampleby') - console.log(settings); - - const sampleby = window_info.get("sampleby"); - - console.log(sampleby); - if (sampleby == "uniform" || sampleby === null) { - uniform(settings); - } else if (sampleby == "population") { - by_population(settings, window_info.get("us_only")!.toLowerCase() == "true"); + if (sampleby === 'uniform' || sampleby === null) { + await uniform(settings) + } + else if (sampleby === 'population') { + await by_population(settings, window_info.get('us_only')!.toLowerCase() === 'true') } } -main(); \ No newline at end of file +void main() diff --git a/react/src/statistic.tsx b/react/src/statistic.tsx index 499810ef..fac6fd8a 100644 --- a/react/src/statistic.tsx +++ b/react/src/statistic.tsx @@ -1,50 +1,49 @@ -import React from 'react'; +import React from 'react' +import ReactDOM from 'react-dom/client' +import './style.css' +import './common.css' -import ReactDOM from 'react-dom/client'; -import "./style.css"; -import "./common.css"; +import { for_type, render_statname } from './components/load-article' +import { StatisticPanel } from './components/statistic-panel' +import { load_ordering, load_ordering_protobuf } from './load_json' +import { UNIVERSE_CONTEXT, get_universe, longname_is_exclusively_american, remove_universe_if_default } from './universe' +import { IDataList } from './utils/protos' +import { NormalizeProto } from './utils/types' -import { load_ordering_protobuf, load_ordering } from './load_json'; -import { StatisticPanel } from './components/statistic-panel'; -import { for_type, render_statname } from './components/load-article'; -import { get_universe, longname_is_exclusively_american, remove_universe_if_default, UNIVERSE_CONTEXT } from './universe'; -import { NormalizeProto } from "./utils/types"; -import { IDataList } from "./utils/protos"; - - -async function loadPage() { - const window_info = new URLSearchParams(window.location.search); +async function loadPage(): Promise { + const window_info = new URLSearchParams(window.location.search) // TODO: Use zod to better parse these - const article_type = window_info.get("article_type")!; - const statname = window_info.get("statname")!; - const start = parseInt(window_info.get("start") || "1"); - const amount = window_info.get("amount"); - const order = (window_info.get("order") ?? 'descending') as 'ascending' | 'descending'; - const highlight = window_info.get("highlight") ?? undefined; + const article_type = window_info.get('article_type')! + const statname = window_info.get('statname')! + const start = parseInt(window_info.get('start') ?? '1') + const amount = window_info.get('amount') + const order = (window_info.get('order') ?? 'descending') as 'ascending' | 'descending' + const highlight = window_info.get('highlight') ?? undefined // delete highlight then replaceState - window_info.delete("highlight"); - window.history.replaceState({}, "", "?" + window_info.toString()); - const names = require("./data/statistic_name_list.json"); - const paths = require("./data/statistic_path_list.json"); - const explanation_pages = require("./data/explanation_page.json"); - const stats = require("./data/statistic_list.json"); - const statpath = paths[names.indexOf(statname)]; - const explanation_page = explanation_pages[names.indexOf(statname)]; - const statcol = stats[names.indexOf(statname)]; - remove_universe_if_default("world"); - const universe = get_universe("world"); - const article_names = await load_ordering(universe, statpath, article_type); - const data = await load_ordering_protobuf(universe, statpath, article_type, true) as NormalizeProto; + window_info.delete('highlight') + window.history.replaceState({}, '', `?${window_info.toString()}`) + const names = require('./data/statistic_name_list.json') as string[] + const paths = require('./data/statistic_path_list.json') as string[] + const explanation_pages = require('./data/explanation_page.json') as string[] + const stats = require('./data/statistic_list.json') as string[] + const statpath = paths[names.indexOf(statname)] + const explanation_page = explanation_pages[names.indexOf(statname)] + const statcol = stats[names.indexOf(statname)] + remove_universe_if_default('world') + const universe = get_universe('world') + const article_names = await load_ordering(universe, statpath, article_type) + const data = await load_ordering_protobuf(universe, statpath, article_type, true) as NormalizeProto let parsedAmount: number - if (amount == "All") { - parsedAmount = article_names.length; - } else { - parsedAmount = parseInt(amount ?? "10"); + if (amount === 'All') { + parsedAmount = article_names.length + } + else { + parsedAmount = parseInt(amount ?? '10') } - document.title = statname; - const root = ReactDOM.createRoot(document.getElementById("root")!); - const exclusively_american = article_names.every(longname_is_exclusively_american); + document.title = statname + const root = ReactDOM.createRoot(document.getElementById('root')!) + const exclusively_american = article_names.every(longname_is_exclusively_american) root.render( - - ); + , + ) } -loadPage(); \ No newline at end of file +void loadPage() diff --git a/react/src/universe.ts b/react/src/universe.ts index fb112dfd..7de15e34 100644 --- a/react/src/universe.ts +++ b/react/src/universe.ts @@ -1,65 +1,63 @@ -import { createContext, useContext } from "react"; +import { createContext, useContext } from 'react' -export const UNIVERSE_CONTEXT = createContext(undefined); +export const UNIVERSE_CONTEXT = createContext(undefined) export function useUniverse(): string { - return useContext(UNIVERSE_CONTEXT)!; + return useContext(UNIVERSE_CONTEXT)! } export function get_universe(default_universe: Default): string | Default { - return new URLSearchParams(window.location.search).get("universe") ?? default_universe + return new URLSearchParams(window.location.search).get('universe') ?? default_universe } -export function set_universe(universe: string) { - const params = new URLSearchParams(window.location.search); - params.set("universe", universe); - window.location.search = params.toString(); +export function set_universe(universe: string): void { + const params = new URLSearchParams(window.location.search) + params.set('universe', universe) + window.location.search = params.toString() } -export function remove_universe_if_not_in(universes: string[]) { - const universe = get_universe(undefined); - if (universe == undefined || !universes.includes(universe)) { - // clear universe without actually reloading the page - const params = new URLSearchParams(window.location.search); - params.delete("universe"); - window.history.replaceState({}, "", window.location.pathname + "?" + params.toString()); +export function remove_universe_if_not_in(universes: string[]): void { + const universe = get_universe(undefined) + if (universe === undefined || !universes.includes(universe)) { + // clear universe without actually reloading the page + const params = new URLSearchParams(window.location.search) + params.delete('universe') + window.history.replaceState({}, '', `${window.location.pathname}?${params.toString()}`) } } -export function remove_universe_if_default(default_universe: string) { +export function remove_universe_if_default(default_universe: string): void { if (get_universe(undefined) === default_universe) { - // clear universe without actually reloading the page - const params = new URLSearchParams(window.location.search); - params.delete("universe"); - window.history.replaceState({}, "", window.location.pathname + "?" + params.toString()); + // clear universe without actually reloading the page + const params = new URLSearchParams(window.location.search) + params.delete('universe') + window.history.replaceState({}, '', `${window.location.pathname}?${params.toString()}`) } } -export function default_article_universe(longname: string) { - console.log(longname); +export function default_article_universe(longname: string): 'USA' | 'world' { // if longname contains USA, then default to USA - if (longname.includes("USA")) { - console.log("USA!!") - return "USA"; + if (longname.includes('USA')) { + return 'USA' } - return "world"; + return 'world' } -export function default_comparison_universe(longnames: string[]) { +export function default_comparison_universe(longnames: string[]): 'USA' | 'world' { // if all longnames are the same universe, default to that universe - const universes = longnames.map(x => default_article_universe(x)); + const universes = longnames.map(x => default_article_universe(x)) if (universes.every(x => x === universes[0])) { - return universes[0]; + return universes[0] } - return "world"; + return 'world' } -export function universe_is_american(universe: string) { +export function universe_is_american(universe: string): boolean { // if universe ends with USA, then it's American - return universe.includes("USA"); + return universe.includes('USA') } -export function longname_is_exclusively_american(universe: string) { +export function longname_is_exclusively_american(universe: string): boolean { // if longname ends with ", USA", then it's exclusively American - return universe.endsWith(", USA"); -} \ No newline at end of file + return universe.endsWith(', USA') +} diff --git a/react/src/utils/DefaultMap.ts b/react/src/utils/DefaultMap.ts index 4bbc4ac7..baa40b14 100644 --- a/react/src/utils/DefaultMap.ts +++ b/react/src/utils/DefaultMap.ts @@ -1,19 +1,18 @@ export interface ReadonlyDefaultMap extends ReadonlyMap { - get(key: K): V + get(key: K): V } export class DefaultMap extends Map implements ReadonlyDefaultMap { + override get(key: K): V { + let result = super.get(key) + if (result === undefined) { + result = this.makeDefault(key) + this.set(key, result) + } + return result + } - get(key: K): V { - let result = super.get(key) - if (result === undefined) { - result = this.makeDefault(key) - this.set(key, result) - } - return result - } - - constructor(private readonly makeDefault: (key: K) => V) { - super() - } -} \ No newline at end of file + constructor(private readonly makeDefault: (key: K) => V) { + super() + } +} diff --git a/react/src/utils/color.ts b/react/src/utils/color.ts index a73a1a83..c1073f2c 100644 --- a/react/src/utils/color.ts +++ b/react/src/utils/color.ts @@ -1,67 +1,67 @@ -import { Keypoints } from "../mapper/ramps"; +import { Keypoints } from '../mapper/ramps' -export function random_color(name: string) { +export function random_color(name: string): string { // randomly choose a color hex code where H is between 0 and 360, // S is between 50 and 100, and L is between 20 and 50 // seed random with the hash of longname - let seed = 0; + let seed = 0 for (let j = 0; j < name.length; j++) { - seed += name.charCodeAt(j); - seed *= 31; - seed %= 1000000007; + seed += name.charCodeAt(j) + seed *= 31 + seed %= 1000000007 } - function random() { - const x = Math.sin(seed++) * 10000; - return x - Math.floor(x); + function random(): number { + const x = Math.sin(seed++) * 10000 + return x - Math.floor(x) } - return `hsl(${random() * 360}, ${50 + random() * 50}%, ${20 + random() * 30}%)`; + return `hsl(${random() * 360}, ${50 + random() * 50}%, ${20 + random() * 30}%)` } -export function interpolate_color(ramp: Keypoints, item: number) { +export function interpolate_color(ramp: Keypoints, item: number): string { // ramp is a list of [value, color] pairs // item is a value // interpolates in RGB space between the two closest colors in the ramp if (isNaN(item)) { - return "#000000"; + return '#000000' } - if (ramp.length == 0) { - return "#000000"; + if (ramp.length === 0) { + return '#000000' } - let i = 0; + let i = 0 while (i < ramp.length && item > ramp[i][0]) { - i++; + i++ } - if (i == 0) { - return ramp[0][1]; + if (i === 0) { + return ramp[0][1] } - if (i == ramp.length) { - return ramp[ramp.length - 1][1]; + if (i === ramp.length) { + return ramp[ramp.length - 1][1] } - const [value1, color1] = ramp[i - 1]; - const [value2, color2] = ramp[i]; - const fraction = (item - value1) / (value2 - value1); - const r1 = parseInt(color1.slice(1, 3), 16); - const g1 = parseInt(color1.slice(3, 5), 16); - const b1 = parseInt(color1.slice(5, 7), 16); - const r2 = parseInt(color2.slice(1, 3), 16); - const g2 = parseInt(color2.slice(3, 5), 16); - const b2 = parseInt(color2.slice(5, 7), 16); - const r = Math.round(r1 + fraction * (r2 - r1)); - const g = Math.round(g1 + fraction * (g2 - g1)); - const b = Math.round(b1 + fraction * (b2 - b1)); - return "#" + r.toString(16).padStart(2, "0") + g.toString(16).padStart(2, "0") + b.toString(16).padStart(2, "0"); + const [value1, color1] = ramp[i - 1] + const [value2, color2] = ramp[i] + const fraction = (item - value1) / (value2 - value1) + const r1 = parseInt(color1.slice(1, 3), 16) + const g1 = parseInt(color1.slice(3, 5), 16) + const b1 = parseInt(color1.slice(5, 7), 16) + const r2 = parseInt(color2.slice(1, 3), 16) + const g2 = parseInt(color2.slice(3, 5), 16) + const b2 = parseInt(color2.slice(5, 7), 16) + const r = Math.round(r1 + fraction * (r2 - r1)) + const g = Math.round(g1 + fraction * (g2 - g1)) + const b = Math.round(b1 + fraction * (b2 - b1)) + return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}` } -export function lighten(color: string, fraction: number) { - if (!(typeof color === "string")) { - throw new Error("color is not a string"); +export function lighten(color: string, fraction: number): string { + if (!(typeof color === 'string')) { + throw new Error('color is not a string') } - const ramp: [number, string][] = [[0, color], [1, "#ffffff"]]; - return interpolate_color(ramp, fraction); -} \ No newline at end of file + const ramp: [number, string][] = [[0, color], [1, '#ffffff']] + return interpolate_color(ramp, fraction) +} diff --git a/react/src/utils/dom-to-image-more.d.ts b/react/src/utils/dom-to-image-more.d.ts index a167ee82..29140e4a 100644 --- a/react/src/utils/dom-to-image-more.d.ts +++ b/react/src/utils/dom-to-image-more.d.ts @@ -1,8 +1,8 @@ declare module 'dom-to-image-more' { function toPng(e: HTMLElement, options: { - bgcolor: string, - height: number, - width: number, + bgcolor: string + height: number + width: number style: React.CSSProperties - }): string; + }): Promise } diff --git a/react/src/utils/is_historical.ts b/react/src/utils/is_historical.ts index b844e962..31ab8979 100644 --- a/react/src/utils/is_historical.ts +++ b/react/src/utils/is_historical.ts @@ -1,5 +1,4 @@ - -export function is_historical_cd(name: string) { +export function is_historical_cd(name: string): boolean { // if name starts with "Historical Congressional District" - return name.startsWith("Historical Congressional District"); -} \ No newline at end of file + return name.startsWith('Historical Congressional District') +} diff --git a/react/src/utils/protos.js b/react/src/utils/protos.js index c59b049c..aed3684a 100644 --- a/react/src/utils/protos.js +++ b/react/src/utils/protos.js @@ -1,16 +1,15 @@ -/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars*/ -"use strict"; +/* eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars */ +'use strict' -var $protobuf = require("protobufjs/minimal"); +var $protobuf = require('protobufjs/minimal') // Common aliases -var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; +var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util // Exported root namespace -var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); - -$root.StatisticRow = (function() { +var $root = $protobuf.roots.default || ($protobuf.roots.default = {}) +$root.StatisticRow = (function () { /** * Properties of a StatisticRow. * @exports IStatisticRow @@ -30,13 +29,13 @@ $root.StatisticRow = (function() { * @param {IStatisticRow=} [properties] Properties to set */ function StatisticRow(properties) { - this.ordinalByUniverse = []; - this.overallOrdinalByUniverse = []; - this.percentileByPopulationByUniverse = []; + this.ordinalByUniverse = [] + this.overallOrdinalByUniverse = [] + this.percentileByPopulationByUniverse = [] if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -45,7 +44,7 @@ $root.StatisticRow = (function() { * @memberof StatisticRow * @instance */ - StatisticRow.prototype.statval = 0; + StatisticRow.prototype.statval = 0 /** * StatisticRow ordinalByUniverse. @@ -53,7 +52,7 @@ $root.StatisticRow = (function() { * @memberof StatisticRow * @instance */ - StatisticRow.prototype.ordinalByUniverse = $util.emptyArray; + StatisticRow.prototype.ordinalByUniverse = $util.emptyArray /** * StatisticRow overallOrdinalByUniverse. @@ -61,7 +60,7 @@ $root.StatisticRow = (function() { * @memberof StatisticRow * @instance */ - StatisticRow.prototype.overallOrdinalByUniverse = $util.emptyArray; + StatisticRow.prototype.overallOrdinalByUniverse = $util.emptyArray /** * StatisticRow percentileByPopulationByUniverse. @@ -69,7 +68,7 @@ $root.StatisticRow = (function() { * @memberof StatisticRow * @instance */ - StatisticRow.prototype.percentileByPopulationByUniverse = $util.emptyArray; + StatisticRow.prototype.percentileByPopulationByUniverse = $util.emptyArray /** * Creates a new StatisticRow instance using the specified properties. @@ -80,8 +79,8 @@ $root.StatisticRow = (function() { * @returns {StatisticRow} StatisticRow instance */ StatisticRow.create = function create(properties) { - return new StatisticRow(properties); - }; + return new StatisticRow(properties) + } /** * Encodes the specified StatisticRow message. Does not implicitly {@link StatisticRow.verify|verify} messages. @@ -94,29 +93,29 @@ $root.StatisticRow = (function() { */ StatisticRow.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.statval != null && Object.hasOwnProperty.call(message, "statval")) - writer.uint32(/* id 1, wireType 5 =*/13).float(message.statval); - if (message.ordinalByUniverse != null && message.ordinalByUniverse.length) { - writer.uint32(/* id 2, wireType 2 =*/18).fork(); + writer = $Writer.create() + if (message.statval != null && Object.hasOwnProperty.call(message, 'statval')) + writer.uint32(/* id 1, wireType 5 = */13).float(message.statval) + if (message.ordinalByUniverse?.length) { + writer.uint32(/* id 2, wireType 2 = */18).fork() for (var i = 0; i < message.ordinalByUniverse.length; ++i) - writer.int32(message.ordinalByUniverse[i]); - writer.ldelim(); + writer.int32(message.ordinalByUniverse[i]) + writer.ldelim() } - if (message.overallOrdinalByUniverse != null && message.overallOrdinalByUniverse.length) { - writer.uint32(/* id 3, wireType 2 =*/26).fork(); + if (message.overallOrdinalByUniverse?.length) { + writer.uint32(/* id 3, wireType 2 = */26).fork() for (var i = 0; i < message.overallOrdinalByUniverse.length; ++i) - writer.int32(message.overallOrdinalByUniverse[i]); - writer.ldelim(); + writer.int32(message.overallOrdinalByUniverse[i]) + writer.ldelim() } - if (message.percentileByPopulationByUniverse != null && message.percentileByPopulationByUniverse.length) { - writer.uint32(/* id 4, wireType 2 =*/34).fork(); + if (message.percentileByPopulationByUniverse?.length) { + writer.uint32(/* id 4, wireType 2 = */34).fork() for (var i = 0; i < message.percentileByPopulationByUniverse.length; ++i) - writer.float(message.percentileByPopulationByUniverse[i]); - writer.ldelim(); + writer.float(message.percentileByPopulationByUniverse[i]) + writer.ldelim() } - return writer; - }; + return writer + } /** * Encodes the specified StatisticRow message, length delimited. Does not implicitly {@link StatisticRow.verify|verify} messages. @@ -128,8 +127,8 @@ $root.StatisticRow = (function() { * @returns {$protobuf.Writer} Writer */ StatisticRow.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes a StatisticRow message from the specified reader or buffer. @@ -144,55 +143,58 @@ $root.StatisticRow = (function() { */ StatisticRow.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StatisticRow(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StatisticRow() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - message.statval = reader.float(); - break; + case 1: { + message.statval = reader.float() + break } - case 2: { - if (!(message.ordinalByUniverse && message.ordinalByUniverse.length)) - message.ordinalByUniverse = []; + case 2: { + if (!(message.ordinalByUniverse?.length)) + message.ordinalByUniverse = [] if ((tag & 7) === 2) { - var end2 = reader.uint32() + reader.pos; + var end2 = reader.uint32() + reader.pos while (reader.pos < end2) - message.ordinalByUniverse.push(reader.int32()); - } else - message.ordinalByUniverse.push(reader.int32()); - break; + message.ordinalByUniverse.push(reader.int32()) + } + else + message.ordinalByUniverse.push(reader.int32()) + break } - case 3: { - if (!(message.overallOrdinalByUniverse && message.overallOrdinalByUniverse.length)) - message.overallOrdinalByUniverse = []; + case 3: { + if (!(message.overallOrdinalByUniverse?.length)) + message.overallOrdinalByUniverse = [] if ((tag & 7) === 2) { - var end2 = reader.uint32() + reader.pos; + var end2 = reader.uint32() + reader.pos while (reader.pos < end2) - message.overallOrdinalByUniverse.push(reader.int32()); - } else - message.overallOrdinalByUniverse.push(reader.int32()); - break; + message.overallOrdinalByUniverse.push(reader.int32()) + } + else + message.overallOrdinalByUniverse.push(reader.int32()) + break } - case 4: { - if (!(message.percentileByPopulationByUniverse && message.percentileByPopulationByUniverse.length)) - message.percentileByPopulationByUniverse = []; + case 4: { + if (!(message.percentileByPopulationByUniverse?.length)) + message.percentileByPopulationByUniverse = [] if ((tag & 7) === 2) { - var end2 = reader.uint32() + reader.pos; + var end2 = reader.uint32() + reader.pos while (reader.pos < end2) - message.percentileByPopulationByUniverse.push(reader.float()); - } else - message.percentileByPopulationByUniverse.push(reader.float()); - break; + message.percentileByPopulationByUniverse.push(reader.float()) + } + else + message.percentileByPopulationByUniverse.push(reader.float()) + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes a StatisticRow message from the specified reader or buffer, length delimited. @@ -206,9 +208,9 @@ $root.StatisticRow = (function() { */ StatisticRow.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies a StatisticRow message. @@ -219,34 +221,34 @@ $root.StatisticRow = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ StatisticRow.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.statval != null && message.hasOwnProperty("statval")) - if (typeof message.statval !== "number") - return "statval: number expected"; - if (message.ordinalByUniverse != null && message.hasOwnProperty("ordinalByUniverse")) { + if (typeof message !== 'object' || message === null) + return 'object expected' + if (message.statval != null && message.hasOwnProperty('statval')) + if (typeof message.statval !== 'number') + return 'statval: number expected' + if (message.ordinalByUniverse != null && message.hasOwnProperty('ordinalByUniverse')) { if (!Array.isArray(message.ordinalByUniverse)) - return "ordinalByUniverse: array expected"; + return 'ordinalByUniverse: array expected' for (var i = 0; i < message.ordinalByUniverse.length; ++i) if (!$util.isInteger(message.ordinalByUniverse[i])) - return "ordinalByUniverse: integer[] expected"; + return 'ordinalByUniverse: integer[] expected' } - if (message.overallOrdinalByUniverse != null && message.hasOwnProperty("overallOrdinalByUniverse")) { + if (message.overallOrdinalByUniverse != null && message.hasOwnProperty('overallOrdinalByUniverse')) { if (!Array.isArray(message.overallOrdinalByUniverse)) - return "overallOrdinalByUniverse: array expected"; + return 'overallOrdinalByUniverse: array expected' for (var i = 0; i < message.overallOrdinalByUniverse.length; ++i) if (!$util.isInteger(message.overallOrdinalByUniverse[i])) - return "overallOrdinalByUniverse: integer[] expected"; + return 'overallOrdinalByUniverse: integer[] expected' } - if (message.percentileByPopulationByUniverse != null && message.hasOwnProperty("percentileByPopulationByUniverse")) { + if (message.percentileByPopulationByUniverse != null && message.hasOwnProperty('percentileByPopulationByUniverse')) { if (!Array.isArray(message.percentileByPopulationByUniverse)) - return "percentileByPopulationByUniverse: array expected"; + return 'percentileByPopulationByUniverse: array expected' for (var i = 0; i < message.percentileByPopulationByUniverse.length; ++i) - if (typeof message.percentileByPopulationByUniverse[i] !== "number") - return "percentileByPopulationByUniverse: number[] expected"; + if (typeof message.percentileByPopulationByUniverse[i] !== 'number') + return 'percentileByPopulationByUniverse: number[] expected' } - return null; - }; + return null + } /** * Creates a StatisticRow message from a plain object. Also converts values to their respective internal types. @@ -258,33 +260,33 @@ $root.StatisticRow = (function() { */ StatisticRow.fromObject = function fromObject(object) { if (object instanceof $root.StatisticRow) - return object; - var message = new $root.StatisticRow(); + return object + var message = new $root.StatisticRow() if (object.statval != null) - message.statval = Number(object.statval); + message.statval = Number(object.statval) if (object.ordinalByUniverse) { if (!Array.isArray(object.ordinalByUniverse)) - throw TypeError(".StatisticRow.ordinalByUniverse: array expected"); - message.ordinalByUniverse = []; + throw TypeError('.StatisticRow.ordinalByUniverse: array expected') + message.ordinalByUniverse = [] for (var i = 0; i < object.ordinalByUniverse.length; ++i) - message.ordinalByUniverse[i] = object.ordinalByUniverse[i] | 0; + message.ordinalByUniverse[i] = object.ordinalByUniverse[i] | 0 } if (object.overallOrdinalByUniverse) { if (!Array.isArray(object.overallOrdinalByUniverse)) - throw TypeError(".StatisticRow.overallOrdinalByUniverse: array expected"); - message.overallOrdinalByUniverse = []; + throw TypeError('.StatisticRow.overallOrdinalByUniverse: array expected') + message.overallOrdinalByUniverse = [] for (var i = 0; i < object.overallOrdinalByUniverse.length; ++i) - message.overallOrdinalByUniverse[i] = object.overallOrdinalByUniverse[i] | 0; + message.overallOrdinalByUniverse[i] = object.overallOrdinalByUniverse[i] | 0 } if (object.percentileByPopulationByUniverse) { if (!Array.isArray(object.percentileByPopulationByUniverse)) - throw TypeError(".StatisticRow.percentileByPopulationByUniverse: array expected"); - message.percentileByPopulationByUniverse = []; + throw TypeError('.StatisticRow.percentileByPopulationByUniverse: array expected') + message.percentileByPopulationByUniverse = [] for (var i = 0; i < object.percentileByPopulationByUniverse.length; ++i) - message.percentileByPopulationByUniverse[i] = Number(object.percentileByPopulationByUniverse[i]); + message.percentileByPopulationByUniverse[i] = Number(object.percentileByPopulationByUniverse[i]) } - return message; - }; + return message + } /** * Creates a plain object from a StatisticRow message. Also converts values to other types if specified. @@ -297,34 +299,34 @@ $root.StatisticRow = (function() { */ StatisticRow.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.arrays || options.defaults) { - object.ordinalByUniverse = []; - object.overallOrdinalByUniverse = []; - object.percentileByPopulationByUniverse = []; + object.ordinalByUniverse = [] + object.overallOrdinalByUniverse = [] + object.percentileByPopulationByUniverse = [] } if (options.defaults) - object.statval = 0; - if (message.statval != null && message.hasOwnProperty("statval")) - object.statval = options.json && !isFinite(message.statval) ? String(message.statval) : message.statval; + object.statval = 0 + if (message.statval != null && message.hasOwnProperty('statval')) + object.statval = options.json && !isFinite(message.statval) ? String(message.statval) : message.statval if (message.ordinalByUniverse && message.ordinalByUniverse.length) { - object.ordinalByUniverse = []; + object.ordinalByUniverse = [] for (var j = 0; j < message.ordinalByUniverse.length; ++j) - object.ordinalByUniverse[j] = message.ordinalByUniverse[j]; + object.ordinalByUniverse[j] = message.ordinalByUniverse[j] } if (message.overallOrdinalByUniverse && message.overallOrdinalByUniverse.length) { - object.overallOrdinalByUniverse = []; + object.overallOrdinalByUniverse = [] for (var j = 0; j < message.overallOrdinalByUniverse.length; ++j) - object.overallOrdinalByUniverse[j] = message.overallOrdinalByUniverse[j]; + object.overallOrdinalByUniverse[j] = message.overallOrdinalByUniverse[j] } if (message.percentileByPopulationByUniverse && message.percentileByPopulationByUniverse.length) { - object.percentileByPopulationByUniverse = []; + object.percentileByPopulationByUniverse = [] for (var j = 0; j < message.percentileByPopulationByUniverse.length; ++j) - object.percentileByPopulationByUniverse[j] = options.json && !isFinite(message.percentileByPopulationByUniverse[j]) ? String(message.percentileByPopulationByUniverse[j]) : message.percentileByPopulationByUniverse[j]; + object.percentileByPopulationByUniverse[j] = options.json && !isFinite(message.percentileByPopulationByUniverse[j]) ? String(message.percentileByPopulationByUniverse[j]) : message.percentileByPopulationByUniverse[j] } - return object; - }; + return object + } /** * Converts this StatisticRow to JSON. @@ -334,8 +336,8 @@ $root.StatisticRow = (function() { * @returns {Object.} JSON object */ StatisticRow.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for StatisticRow @@ -347,16 +349,15 @@ $root.StatisticRow = (function() { */ StatisticRow.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/StatisticRow"; - }; - - return StatisticRow; -})(); + return `${typeUrlPrefix}/StatisticRow` + } -$root.RelatedButton = (function() { + return StatisticRow +})() +$root.RelatedButton = (function () { /** * Properties of a RelatedButton. * @exports IRelatedButton @@ -378,7 +379,7 @@ $root.RelatedButton = (function() { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -387,7 +388,7 @@ $root.RelatedButton = (function() { * @memberof RelatedButton * @instance */ - RelatedButton.prototype.longname = ""; + RelatedButton.prototype.longname = '' /** * RelatedButton shortname. @@ -395,7 +396,7 @@ $root.RelatedButton = (function() { * @memberof RelatedButton * @instance */ - RelatedButton.prototype.shortname = ""; + RelatedButton.prototype.shortname = '' /** * RelatedButton rowType. @@ -403,7 +404,7 @@ $root.RelatedButton = (function() { * @memberof RelatedButton * @instance */ - RelatedButton.prototype.rowType = ""; + RelatedButton.prototype.rowType = '' /** * Creates a new RelatedButton instance using the specified properties. @@ -414,8 +415,8 @@ $root.RelatedButton = (function() { * @returns {RelatedButton} RelatedButton instance */ RelatedButton.create = function create(properties) { - return new RelatedButton(properties); - }; + return new RelatedButton(properties) + } /** * Encodes the specified RelatedButton message. Does not implicitly {@link RelatedButton.verify|verify} messages. @@ -428,15 +429,15 @@ $root.RelatedButton = (function() { */ RelatedButton.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.longname != null && Object.hasOwnProperty.call(message, "longname")) - writer.uint32(/* id 1, wireType 2 =*/10).string(message.longname); - if (message.shortname != null && Object.hasOwnProperty.call(message, "shortname")) - writer.uint32(/* id 2, wireType 2 =*/18).string(message.shortname); - if (message.rowType != null && Object.hasOwnProperty.call(message, "rowType")) - writer.uint32(/* id 3, wireType 2 =*/26).string(message.rowType); - return writer; - }; + writer = $Writer.create() + if (message.longname != null && Object.hasOwnProperty.call(message, 'longname')) + writer.uint32(/* id 1, wireType 2 = */10).string(message.longname) + if (message.shortname != null && Object.hasOwnProperty.call(message, 'shortname')) + writer.uint32(/* id 2, wireType 2 = */18).string(message.shortname) + if (message.rowType != null && Object.hasOwnProperty.call(message, 'rowType')) + writer.uint32(/* id 3, wireType 2 = */26).string(message.rowType) + return writer + } /** * Encodes the specified RelatedButton message, length delimited. Does not implicitly {@link RelatedButton.verify|verify} messages. @@ -448,8 +449,8 @@ $root.RelatedButton = (function() { * @returns {$protobuf.Writer} Writer */ RelatedButton.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes a RelatedButton message from the specified reader or buffer. @@ -464,30 +465,30 @@ $root.RelatedButton = (function() { */ RelatedButton.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.RelatedButton(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.RelatedButton() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - message.longname = reader.string(); - break; + case 1: { + message.longname = reader.string() + break } - case 2: { - message.shortname = reader.string(); - break; + case 2: { + message.shortname = reader.string() + break } - case 3: { - message.rowType = reader.string(); - break; + case 3: { + message.rowType = reader.string() + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes a RelatedButton message from the specified reader or buffer, length delimited. @@ -501,9 +502,9 @@ $root.RelatedButton = (function() { */ RelatedButton.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies a RelatedButton message. @@ -514,19 +515,19 @@ $root.RelatedButton = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ RelatedButton.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.longname != null && message.hasOwnProperty("longname")) + if (typeof message !== 'object' || message === null) + return 'object expected' + if (message.longname != null && message.hasOwnProperty('longname')) if (!$util.isString(message.longname)) - return "longname: string expected"; - if (message.shortname != null && message.hasOwnProperty("shortname")) + return 'longname: string expected' + if (message.shortname != null && message.hasOwnProperty('shortname')) if (!$util.isString(message.shortname)) - return "shortname: string expected"; - if (message.rowType != null && message.hasOwnProperty("rowType")) + return 'shortname: string expected' + if (message.rowType != null && message.hasOwnProperty('rowType')) if (!$util.isString(message.rowType)) - return "rowType: string expected"; - return null; - }; + return 'rowType: string expected' + return null + } /** * Creates a RelatedButton message from a plain object. Also converts values to their respective internal types. @@ -538,16 +539,16 @@ $root.RelatedButton = (function() { */ RelatedButton.fromObject = function fromObject(object) { if (object instanceof $root.RelatedButton) - return object; - var message = new $root.RelatedButton(); + return object + var message = new $root.RelatedButton() if (object.longname != null) - message.longname = String(object.longname); + message.longname = String(object.longname) if (object.shortname != null) - message.shortname = String(object.shortname); + message.shortname = String(object.shortname) if (object.rowType != null) - message.rowType = String(object.rowType); - return message; - }; + message.rowType = String(object.rowType) + return message + } /** * Creates a plain object from a RelatedButton message. Also converts values to other types if specified. @@ -560,21 +561,21 @@ $root.RelatedButton = (function() { */ RelatedButton.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.defaults) { - object.longname = ""; - object.shortname = ""; - object.rowType = ""; - } - if (message.longname != null && message.hasOwnProperty("longname")) - object.longname = message.longname; - if (message.shortname != null && message.hasOwnProperty("shortname")) - object.shortname = message.shortname; - if (message.rowType != null && message.hasOwnProperty("rowType")) - object.rowType = message.rowType; - return object; - }; + object.longname = '' + object.shortname = '' + object.rowType = '' + } + if (message.longname != null && message.hasOwnProperty('longname')) + object.longname = message.longname + if (message.shortname != null && message.hasOwnProperty('shortname')) + object.shortname = message.shortname + if (message.rowType != null && message.hasOwnProperty('rowType')) + object.rowType = message.rowType + return object + } /** * Converts this RelatedButton to JSON. @@ -584,8 +585,8 @@ $root.RelatedButton = (function() { * @returns {Object.} JSON object */ RelatedButton.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for RelatedButton @@ -597,16 +598,15 @@ $root.RelatedButton = (function() { */ RelatedButton.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/RelatedButton"; - }; - - return RelatedButton; -})(); + return `${typeUrlPrefix}/RelatedButton` + } -$root.RelatedButtons = (function() { + return RelatedButton +})() +$root.RelatedButtons = (function () { /** * Properties of a RelatedButtons. * @exports IRelatedButtons @@ -624,11 +624,11 @@ $root.RelatedButtons = (function() { * @param {IRelatedButtons=} [properties] Properties to set */ function RelatedButtons(properties) { - this.buttons = []; + this.buttons = [] if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -637,7 +637,7 @@ $root.RelatedButtons = (function() { * @memberof RelatedButtons * @instance */ - RelatedButtons.prototype.relationshipType = ""; + RelatedButtons.prototype.relationshipType = '' /** * RelatedButtons buttons. @@ -645,7 +645,7 @@ $root.RelatedButtons = (function() { * @memberof RelatedButtons * @instance */ - RelatedButtons.prototype.buttons = $util.emptyArray; + RelatedButtons.prototype.buttons = $util.emptyArray /** * Creates a new RelatedButtons instance using the specified properties. @@ -656,8 +656,8 @@ $root.RelatedButtons = (function() { * @returns {RelatedButtons} RelatedButtons instance */ RelatedButtons.create = function create(properties) { - return new RelatedButtons(properties); - }; + return new RelatedButtons(properties) + } /** * Encodes the specified RelatedButtons message. Does not implicitly {@link RelatedButtons.verify|verify} messages. @@ -670,14 +670,14 @@ $root.RelatedButtons = (function() { */ RelatedButtons.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.relationshipType != null && Object.hasOwnProperty.call(message, "relationshipType")) - writer.uint32(/* id 1, wireType 2 =*/10).string(message.relationshipType); - if (message.buttons != null && message.buttons.length) + writer = $Writer.create() + if (message.relationshipType != null && Object.hasOwnProperty.call(message, 'relationshipType')) + writer.uint32(/* id 1, wireType 2 = */10).string(message.relationshipType) + if (message.buttons?.length) for (var i = 0; i < message.buttons.length; ++i) - $root.RelatedButton.encode(message.buttons[i], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim(); - return writer; - }; + $root.RelatedButton.encode(message.buttons[i], writer.uint32(/* id 2, wireType 2 = */18).fork()).ldelim() + return writer + } /** * Encodes the specified RelatedButtons message, length delimited. Does not implicitly {@link RelatedButtons.verify|verify} messages. @@ -689,8 +689,8 @@ $root.RelatedButtons = (function() { * @returns {$protobuf.Writer} Writer */ RelatedButtons.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes a RelatedButtons message from the specified reader or buffer. @@ -705,28 +705,28 @@ $root.RelatedButtons = (function() { */ RelatedButtons.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.RelatedButtons(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.RelatedButtons() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - message.relationshipType = reader.string(); - break; + case 1: { + message.relationshipType = reader.string() + break } - case 2: { - if (!(message.buttons && message.buttons.length)) - message.buttons = []; - message.buttons.push($root.RelatedButton.decode(reader, reader.uint32())); - break; + case 2: { + if (!(message.buttons?.length)) + message.buttons = [] + message.buttons.push($root.RelatedButton.decode(reader, reader.uint32())) + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes a RelatedButtons message from the specified reader or buffer, length delimited. @@ -740,9 +740,9 @@ $root.RelatedButtons = (function() { */ RelatedButtons.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies a RelatedButtons message. @@ -753,22 +753,22 @@ $root.RelatedButtons = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ RelatedButtons.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.relationshipType != null && message.hasOwnProperty("relationshipType")) + if (typeof message !== 'object' || message === null) + return 'object expected' + if (message.relationshipType != null && message.hasOwnProperty('relationshipType')) if (!$util.isString(message.relationshipType)) - return "relationshipType: string expected"; - if (message.buttons != null && message.hasOwnProperty("buttons")) { + return 'relationshipType: string expected' + if (message.buttons != null && message.hasOwnProperty('buttons')) { if (!Array.isArray(message.buttons)) - return "buttons: array expected"; + return 'buttons: array expected' for (var i = 0; i < message.buttons.length; ++i) { - var error = $root.RelatedButton.verify(message.buttons[i]); + var error = $root.RelatedButton.verify(message.buttons[i]) if (error) - return "buttons." + error; + return `buttons.${error}` } } - return null; - }; + return null + } /** * Creates a RelatedButtons message from a plain object. Also converts values to their respective internal types. @@ -780,22 +780,22 @@ $root.RelatedButtons = (function() { */ RelatedButtons.fromObject = function fromObject(object) { if (object instanceof $root.RelatedButtons) - return object; - var message = new $root.RelatedButtons(); + return object + var message = new $root.RelatedButtons() if (object.relationshipType != null) - message.relationshipType = String(object.relationshipType); + message.relationshipType = String(object.relationshipType) if (object.buttons) { if (!Array.isArray(object.buttons)) - throw TypeError(".RelatedButtons.buttons: array expected"); - message.buttons = []; + throw TypeError('.RelatedButtons.buttons: array expected') + message.buttons = [] for (var i = 0; i < object.buttons.length; ++i) { - if (typeof object.buttons[i] !== "object") - throw TypeError(".RelatedButtons.buttons: object expected"); - message.buttons[i] = $root.RelatedButton.fromObject(object.buttons[i]); + if (typeof object.buttons[i] !== 'object') + throw TypeError('.RelatedButtons.buttons: object expected') + message.buttons[i] = $root.RelatedButton.fromObject(object.buttons[i]) } } - return message; - }; + return message + } /** * Creates a plain object from a RelatedButtons message. Also converts values to other types if specified. @@ -808,21 +808,21 @@ $root.RelatedButtons = (function() { */ RelatedButtons.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.arrays || options.defaults) - object.buttons = []; + object.buttons = [] if (options.defaults) - object.relationshipType = ""; - if (message.relationshipType != null && message.hasOwnProperty("relationshipType")) - object.relationshipType = message.relationshipType; + object.relationshipType = '' + if (message.relationshipType != null && message.hasOwnProperty('relationshipType')) + object.relationshipType = message.relationshipType if (message.buttons && message.buttons.length) { - object.buttons = []; + object.buttons = [] for (var j = 0; j < message.buttons.length; ++j) - object.buttons[j] = $root.RelatedButton.toObject(message.buttons[j], options); + object.buttons[j] = $root.RelatedButton.toObject(message.buttons[j], options) } - return object; - }; + return object + } /** * Converts this RelatedButtons to JSON. @@ -832,8 +832,8 @@ $root.RelatedButtons = (function() { * @returns {Object.} JSON object */ RelatedButtons.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for RelatedButtons @@ -845,16 +845,15 @@ $root.RelatedButtons = (function() { */ RelatedButtons.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/RelatedButtons"; - }; - - return RelatedButtons; -})(); + return `${typeUrlPrefix}/RelatedButtons` + } -$root.Histogram = (function() { + return RelatedButtons +})() +$root.Histogram = (function () { /** * Properties of a Histogram. * @exports IHistogram @@ -873,11 +872,11 @@ $root.Histogram = (function() { * @param {IHistogram=} [properties] Properties to set */ function Histogram(properties) { - this.counts = []; + this.counts = [] if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -886,7 +885,7 @@ $root.Histogram = (function() { * @memberof Histogram * @instance */ - Histogram.prototype.binMin = 0; + Histogram.prototype.binMin = 0 /** * Histogram binSize. @@ -894,7 +893,7 @@ $root.Histogram = (function() { * @memberof Histogram * @instance */ - Histogram.prototype.binSize = 0; + Histogram.prototype.binSize = 0 /** * Histogram counts. @@ -902,7 +901,7 @@ $root.Histogram = (function() { * @memberof Histogram * @instance */ - Histogram.prototype.counts = $util.emptyArray; + Histogram.prototype.counts = $util.emptyArray /** * Creates a new Histogram instance using the specified properties. @@ -913,8 +912,8 @@ $root.Histogram = (function() { * @returns {Histogram} Histogram instance */ Histogram.create = function create(properties) { - return new Histogram(properties); - }; + return new Histogram(properties) + } /** * Encodes the specified Histogram message. Does not implicitly {@link Histogram.verify|verify} messages. @@ -927,19 +926,19 @@ $root.Histogram = (function() { */ Histogram.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.binMin != null && Object.hasOwnProperty.call(message, "binMin")) - writer.uint32(/* id 1, wireType 5 =*/13).float(message.binMin); - if (message.binSize != null && Object.hasOwnProperty.call(message, "binSize")) - writer.uint32(/* id 2, wireType 5 =*/21).float(message.binSize); - if (message.counts != null && message.counts.length) { - writer.uint32(/* id 3, wireType 2 =*/26).fork(); + writer = $Writer.create() + if (message.binMin != null && Object.hasOwnProperty.call(message, 'binMin')) + writer.uint32(/* id 1, wireType 5 = */13).float(message.binMin) + if (message.binSize != null && Object.hasOwnProperty.call(message, 'binSize')) + writer.uint32(/* id 2, wireType 5 = */21).float(message.binSize) + if (message.counts?.length) { + writer.uint32(/* id 3, wireType 2 = */26).fork() for (var i = 0; i < message.counts.length; ++i) - writer.int32(message.counts[i]); - writer.ldelim(); + writer.int32(message.counts[i]) + writer.ldelim() } - return writer; - }; + return writer + } /** * Encodes the specified Histogram message, length delimited. Does not implicitly {@link Histogram.verify|verify} messages. @@ -951,8 +950,8 @@ $root.Histogram = (function() { * @returns {$protobuf.Writer} Writer */ Histogram.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes a Histogram message from the specified reader or buffer. @@ -967,37 +966,38 @@ $root.Histogram = (function() { */ Histogram.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.Histogram(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.Histogram() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - message.binMin = reader.float(); - break; + case 1: { + message.binMin = reader.float() + break } - case 2: { - message.binSize = reader.float(); - break; + case 2: { + message.binSize = reader.float() + break } - case 3: { - if (!(message.counts && message.counts.length)) - message.counts = []; + case 3: { + if (!(message.counts?.length)) + message.counts = [] if ((tag & 7) === 2) { - var end2 = reader.uint32() + reader.pos; + var end2 = reader.uint32() + reader.pos while (reader.pos < end2) - message.counts.push(reader.int32()); - } else - message.counts.push(reader.int32()); - break; + message.counts.push(reader.int32()) + } + else + message.counts.push(reader.int32()) + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes a Histogram message from the specified reader or buffer, length delimited. @@ -1011,9 +1011,9 @@ $root.Histogram = (function() { */ Histogram.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies a Histogram message. @@ -1024,23 +1024,23 @@ $root.Histogram = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ Histogram.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.binMin != null && message.hasOwnProperty("binMin")) - if (typeof message.binMin !== "number") - return "binMin: number expected"; - if (message.binSize != null && message.hasOwnProperty("binSize")) - if (typeof message.binSize !== "number") - return "binSize: number expected"; - if (message.counts != null && message.hasOwnProperty("counts")) { + if (typeof message !== 'object' || message === null) + return 'object expected' + if (message.binMin != null && message.hasOwnProperty('binMin')) + if (typeof message.binMin !== 'number') + return 'binMin: number expected' + if (message.binSize != null && message.hasOwnProperty('binSize')) + if (typeof message.binSize !== 'number') + return 'binSize: number expected' + if (message.counts != null && message.hasOwnProperty('counts')) { if (!Array.isArray(message.counts)) - return "counts: array expected"; + return 'counts: array expected' for (var i = 0; i < message.counts.length; ++i) if (!$util.isInteger(message.counts[i])) - return "counts: integer[] expected"; + return 'counts: integer[] expected' } - return null; - }; + return null + } /** * Creates a Histogram message from a plain object. Also converts values to their respective internal types. @@ -1052,21 +1052,21 @@ $root.Histogram = (function() { */ Histogram.fromObject = function fromObject(object) { if (object instanceof $root.Histogram) - return object; - var message = new $root.Histogram(); + return object + var message = new $root.Histogram() if (object.binMin != null) - message.binMin = Number(object.binMin); + message.binMin = Number(object.binMin) if (object.binSize != null) - message.binSize = Number(object.binSize); + message.binSize = Number(object.binSize) if (object.counts) { if (!Array.isArray(object.counts)) - throw TypeError(".Histogram.counts: array expected"); - message.counts = []; + throw TypeError('.Histogram.counts: array expected') + message.counts = [] for (var i = 0; i < object.counts.length; ++i) - message.counts[i] = object.counts[i] | 0; + message.counts[i] = object.counts[i] | 0 } - return message; - }; + return message + } /** * Creates a plain object from a Histogram message. Also converts values to other types if specified. @@ -1079,25 +1079,25 @@ $root.Histogram = (function() { */ Histogram.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.arrays || options.defaults) - object.counts = []; + object.counts = [] if (options.defaults) { - object.binMin = 0; - object.binSize = 0; + object.binMin = 0 + object.binSize = 0 } - if (message.binMin != null && message.hasOwnProperty("binMin")) - object.binMin = options.json && !isFinite(message.binMin) ? String(message.binMin) : message.binMin; - if (message.binSize != null && message.hasOwnProperty("binSize")) - object.binSize = options.json && !isFinite(message.binSize) ? String(message.binSize) : message.binSize; + if (message.binMin != null && message.hasOwnProperty('binMin')) + object.binMin = options.json && !isFinite(message.binMin) ? String(message.binMin) : message.binMin + if (message.binSize != null && message.hasOwnProperty('binSize')) + object.binSize = options.json && !isFinite(message.binSize) ? String(message.binSize) : message.binSize if (message.counts && message.counts.length) { - object.counts = []; + object.counts = [] for (var j = 0; j < message.counts.length; ++j) - object.counts[j] = message.counts[j]; + object.counts[j] = message.counts[j] } - return object; - }; + return object + } /** * Converts this Histogram to JSON. @@ -1107,8 +1107,8 @@ $root.Histogram = (function() { * @returns {Object.} JSON object */ Histogram.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for Histogram @@ -1120,16 +1120,15 @@ $root.Histogram = (function() { */ Histogram.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/Histogram"; - }; - - return Histogram; -})(); + return `${typeUrlPrefix}/Histogram` + } -$root.ExtraStatistic = (function() { + return Histogram +})() +$root.ExtraStatistic = (function () { /** * Properties of an ExtraStatistic. * @exports IExtraStatistic @@ -1149,7 +1148,7 @@ $root.ExtraStatistic = (function() { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -1158,10 +1157,10 @@ $root.ExtraStatistic = (function() { * @memberof ExtraStatistic * @instance */ - ExtraStatistic.prototype.histogram = null; + ExtraStatistic.prototype.histogram = null // OneOf field names bound to virtual getters and setters - var $oneOfFields; + var $oneOfFields /** * ExtraStatistic _histogram. @@ -1169,10 +1168,10 @@ $root.ExtraStatistic = (function() { * @memberof ExtraStatistic * @instance */ - Object.defineProperty(ExtraStatistic.prototype, "_histogram", { - get: $util.oneOfGetter($oneOfFields = ["histogram"]), - set: $util.oneOfSetter($oneOfFields) - }); + Object.defineProperty(ExtraStatistic.prototype, '_histogram', { + get: $util.oneOfGetter($oneOfFields = ['histogram']), + set: $util.oneOfSetter($oneOfFields), + }) /** * Creates a new ExtraStatistic instance using the specified properties. @@ -1183,8 +1182,8 @@ $root.ExtraStatistic = (function() { * @returns {ExtraStatistic} ExtraStatistic instance */ ExtraStatistic.create = function create(properties) { - return new ExtraStatistic(properties); - }; + return new ExtraStatistic(properties) + } /** * Encodes the specified ExtraStatistic message. Does not implicitly {@link ExtraStatistic.verify|verify} messages. @@ -1197,11 +1196,11 @@ $root.ExtraStatistic = (function() { */ ExtraStatistic.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.histogram != null && Object.hasOwnProperty.call(message, "histogram")) - $root.Histogram.encode(message.histogram, writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim(); - return writer; - }; + writer = $Writer.create() + if (message.histogram != null && Object.hasOwnProperty.call(message, 'histogram')) + $root.Histogram.encode(message.histogram, writer.uint32(/* id 1, wireType 2 = */10).fork()).ldelim() + return writer + } /** * Encodes the specified ExtraStatistic message, length delimited. Does not implicitly {@link ExtraStatistic.verify|verify} messages. @@ -1213,8 +1212,8 @@ $root.ExtraStatistic = (function() { * @returns {$protobuf.Writer} Writer */ ExtraStatistic.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes an ExtraStatistic message from the specified reader or buffer. @@ -1229,22 +1228,22 @@ $root.ExtraStatistic = (function() { */ ExtraStatistic.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.ExtraStatistic(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.ExtraStatistic() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - message.histogram = $root.Histogram.decode(reader, reader.uint32()); - break; + case 1: { + message.histogram = $root.Histogram.decode(reader, reader.uint32()) + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes an ExtraStatistic message from the specified reader or buffer, length delimited. @@ -1258,9 +1257,9 @@ $root.ExtraStatistic = (function() { */ ExtraStatistic.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies an ExtraStatistic message. @@ -1271,19 +1270,19 @@ $root.ExtraStatistic = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ ExtraStatistic.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - var properties = {}; - if (message.histogram != null && message.hasOwnProperty("histogram")) { - properties._histogram = 1; + if (typeof message !== 'object' || message === null) + return 'object expected' + var properties = {} + if (message.histogram != null && message.hasOwnProperty('histogram')) { + properties._histogram = 1 { - var error = $root.Histogram.verify(message.histogram); + var error = $root.Histogram.verify(message.histogram) if (error) - return "histogram." + error; + return `histogram.${error}` } } - return null; - }; + return null + } /** * Creates an ExtraStatistic message from a plain object. Also converts values to their respective internal types. @@ -1295,15 +1294,15 @@ $root.ExtraStatistic = (function() { */ ExtraStatistic.fromObject = function fromObject(object) { if (object instanceof $root.ExtraStatistic) - return object; - var message = new $root.ExtraStatistic(); + return object + var message = new $root.ExtraStatistic() if (object.histogram != null) { - if (typeof object.histogram !== "object") - throw TypeError(".ExtraStatistic.histogram: object expected"); - message.histogram = $root.Histogram.fromObject(object.histogram); + if (typeof object.histogram !== 'object') + throw TypeError('.ExtraStatistic.histogram: object expected') + message.histogram = $root.Histogram.fromObject(object.histogram) } - return message; - }; + return message + } /** * Creates a plain object from an ExtraStatistic message. Also converts values to other types if specified. @@ -1316,15 +1315,15 @@ $root.ExtraStatistic = (function() { */ ExtraStatistic.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; - if (message.histogram != null && message.hasOwnProperty("histogram")) { - object.histogram = $root.Histogram.toObject(message.histogram, options); + options = {} + var object = {} + if (message.histogram != null && message.hasOwnProperty('histogram')) { + object.histogram = $root.Histogram.toObject(message.histogram, options) if (options.oneofs) - object._histogram = "histogram"; + object._histogram = 'histogram' } - return object; - }; + return object + } /** * Converts this ExtraStatistic to JSON. @@ -1334,8 +1333,8 @@ $root.ExtraStatistic = (function() { * @returns {Object.} JSON object */ ExtraStatistic.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for ExtraStatistic @@ -1347,16 +1346,15 @@ $root.ExtraStatistic = (function() { */ ExtraStatistic.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/ExtraStatistic"; - }; - - return ExtraStatistic; -})(); + return `${typeUrlPrefix}/ExtraStatistic` + } -$root.Article = (function() { + return ExtraStatistic +})() +$root.Article = (function () { /** * Properties of an Article. * @exports IArticle @@ -1380,14 +1378,14 @@ $root.Article = (function() { * @param {IArticle=} [properties] Properties to set */ function Article(properties) { - this.rows = []; - this.related = []; - this.universes = []; - this.extraStats = []; + this.rows = [] + this.related = [] + this.universes = [] + this.extraStats = [] if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -1396,7 +1394,7 @@ $root.Article = (function() { * @memberof Article * @instance */ - Article.prototype.shortname = ""; + Article.prototype.shortname = '' /** * Article longname. @@ -1404,7 +1402,7 @@ $root.Article = (function() { * @memberof Article * @instance */ - Article.prototype.longname = ""; + Article.prototype.longname = '' /** * Article source. @@ -1412,7 +1410,7 @@ $root.Article = (function() { * @memberof Article * @instance */ - Article.prototype.source = ""; + Article.prototype.source = '' /** * Article articleType. @@ -1420,7 +1418,7 @@ $root.Article = (function() { * @memberof Article * @instance */ - Article.prototype.articleType = ""; + Article.prototype.articleType = '' /** * Article rows. @@ -1428,7 +1426,7 @@ $root.Article = (function() { * @memberof Article * @instance */ - Article.prototype.rows = $util.emptyArray; + Article.prototype.rows = $util.emptyArray /** * Article related. @@ -1436,7 +1434,7 @@ $root.Article = (function() { * @memberof Article * @instance */ - Article.prototype.related = $util.emptyArray; + Article.prototype.related = $util.emptyArray /** * Article universes. @@ -1444,7 +1442,7 @@ $root.Article = (function() { * @memberof Article * @instance */ - Article.prototype.universes = $util.emptyArray; + Article.prototype.universes = $util.emptyArray /** * Article extraStats. @@ -1452,7 +1450,7 @@ $root.Article = (function() { * @memberof Article * @instance */ - Article.prototype.extraStats = $util.emptyArray; + Article.prototype.extraStats = $util.emptyArray /** * Creates a new Article instance using the specified properties. @@ -1463,8 +1461,8 @@ $root.Article = (function() { * @returns {Article} Article instance */ Article.create = function create(properties) { - return new Article(properties); - }; + return new Article(properties) + } /** * Encodes the specified Article message. Does not implicitly {@link Article.verify|verify} messages. @@ -1477,29 +1475,29 @@ $root.Article = (function() { */ Article.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.shortname != null && Object.hasOwnProperty.call(message, "shortname")) - writer.uint32(/* id 1, wireType 2 =*/10).string(message.shortname); - if (message.longname != null && Object.hasOwnProperty.call(message, "longname")) - writer.uint32(/* id 2, wireType 2 =*/18).string(message.longname); - if (message.source != null && Object.hasOwnProperty.call(message, "source")) - writer.uint32(/* id 3, wireType 2 =*/26).string(message.source); - if (message.articleType != null && Object.hasOwnProperty.call(message, "articleType")) - writer.uint32(/* id 4, wireType 2 =*/34).string(message.articleType); - if (message.rows != null && message.rows.length) + writer = $Writer.create() + if (message.shortname != null && Object.hasOwnProperty.call(message, 'shortname')) + writer.uint32(/* id 1, wireType 2 = */10).string(message.shortname) + if (message.longname != null && Object.hasOwnProperty.call(message, 'longname')) + writer.uint32(/* id 2, wireType 2 = */18).string(message.longname) + if (message.source != null && Object.hasOwnProperty.call(message, 'source')) + writer.uint32(/* id 3, wireType 2 = */26).string(message.source) + if (message.articleType != null && Object.hasOwnProperty.call(message, 'articleType')) + writer.uint32(/* id 4, wireType 2 = */34).string(message.articleType) + if (message.rows?.length) for (var i = 0; i < message.rows.length; ++i) - $root.StatisticRow.encode(message.rows[i], writer.uint32(/* id 5, wireType 2 =*/42).fork()).ldelim(); - if (message.related != null && message.related.length) + $root.StatisticRow.encode(message.rows[i], writer.uint32(/* id 5, wireType 2 = */42).fork()).ldelim() + if (message.related?.length) for (var i = 0; i < message.related.length; ++i) - $root.RelatedButtons.encode(message.related[i], writer.uint32(/* id 6, wireType 2 =*/50).fork()).ldelim(); - if (message.universes != null && message.universes.length) + $root.RelatedButtons.encode(message.related[i], writer.uint32(/* id 6, wireType 2 = */50).fork()).ldelim() + if (message.universes?.length) for (var i = 0; i < message.universes.length; ++i) - writer.uint32(/* id 7, wireType 2 =*/58).string(message.universes[i]); - if (message.extraStats != null && message.extraStats.length) + writer.uint32(/* id 7, wireType 2 = */58).string(message.universes[i]) + if (message.extraStats?.length) for (var i = 0; i < message.extraStats.length; ++i) - $root.ExtraStatistic.encode(message.extraStats[i], writer.uint32(/* id 8, wireType 2 =*/66).fork()).ldelim(); - return writer; - }; + $root.ExtraStatistic.encode(message.extraStats[i], writer.uint32(/* id 8, wireType 2 = */66).fork()).ldelim() + return writer + } /** * Encodes the specified Article message, length delimited. Does not implicitly {@link Article.verify|verify} messages. @@ -1511,8 +1509,8 @@ $root.Article = (function() { * @returns {$protobuf.Writer} Writer */ Article.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes an Article message from the specified reader or buffer. @@ -1527,58 +1525,58 @@ $root.Article = (function() { */ Article.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.Article(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.Article() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - message.shortname = reader.string(); - break; + case 1: { + message.shortname = reader.string() + break } - case 2: { - message.longname = reader.string(); - break; + case 2: { + message.longname = reader.string() + break } - case 3: { - message.source = reader.string(); - break; + case 3: { + message.source = reader.string() + break } - case 4: { - message.articleType = reader.string(); - break; + case 4: { + message.articleType = reader.string() + break } - case 5: { - if (!(message.rows && message.rows.length)) - message.rows = []; - message.rows.push($root.StatisticRow.decode(reader, reader.uint32())); - break; + case 5: { + if (!(message.rows?.length)) + message.rows = [] + message.rows.push($root.StatisticRow.decode(reader, reader.uint32())) + break } - case 6: { - if (!(message.related && message.related.length)) - message.related = []; - message.related.push($root.RelatedButtons.decode(reader, reader.uint32())); - break; + case 6: { + if (!(message.related?.length)) + message.related = [] + message.related.push($root.RelatedButtons.decode(reader, reader.uint32())) + break } - case 7: { - if (!(message.universes && message.universes.length)) - message.universes = []; - message.universes.push(reader.string()); - break; + case 7: { + if (!(message.universes?.length)) + message.universes = [] + message.universes.push(reader.string()) + break } - case 8: { - if (!(message.extraStats && message.extraStats.length)) - message.extraStats = []; - message.extraStats.push($root.ExtraStatistic.decode(reader, reader.uint32())); - break; + case 8: { + if (!(message.extraStats?.length)) + message.extraStats = [] + message.extraStats.push($root.ExtraStatistic.decode(reader, reader.uint32())) + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes an Article message from the specified reader or buffer, length delimited. @@ -1592,9 +1590,9 @@ $root.Article = (function() { */ Article.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies an Article message. @@ -1605,56 +1603,56 @@ $root.Article = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ Article.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.shortname != null && message.hasOwnProperty("shortname")) + if (typeof message !== 'object' || message === null) + return 'object expected' + if (message.shortname != null && message.hasOwnProperty('shortname')) if (!$util.isString(message.shortname)) - return "shortname: string expected"; - if (message.longname != null && message.hasOwnProperty("longname")) + return 'shortname: string expected' + if (message.longname != null && message.hasOwnProperty('longname')) if (!$util.isString(message.longname)) - return "longname: string expected"; - if (message.source != null && message.hasOwnProperty("source")) + return 'longname: string expected' + if (message.source != null && message.hasOwnProperty('source')) if (!$util.isString(message.source)) - return "source: string expected"; - if (message.articleType != null && message.hasOwnProperty("articleType")) + return 'source: string expected' + if (message.articleType != null && message.hasOwnProperty('articleType')) if (!$util.isString(message.articleType)) - return "articleType: string expected"; - if (message.rows != null && message.hasOwnProperty("rows")) { + return 'articleType: string expected' + if (message.rows != null && message.hasOwnProperty('rows')) { if (!Array.isArray(message.rows)) - return "rows: array expected"; + return 'rows: array expected' for (var i = 0; i < message.rows.length; ++i) { - var error = $root.StatisticRow.verify(message.rows[i]); + var error = $root.StatisticRow.verify(message.rows[i]) if (error) - return "rows." + error; + return `rows.${error}` } } - if (message.related != null && message.hasOwnProperty("related")) { + if (message.related != null && message.hasOwnProperty('related')) { if (!Array.isArray(message.related)) - return "related: array expected"; + return 'related: array expected' for (var i = 0; i < message.related.length; ++i) { - var error = $root.RelatedButtons.verify(message.related[i]); + var error = $root.RelatedButtons.verify(message.related[i]) if (error) - return "related." + error; + return `related.${error}` } } - if (message.universes != null && message.hasOwnProperty("universes")) { + if (message.universes != null && message.hasOwnProperty('universes')) { if (!Array.isArray(message.universes)) - return "universes: array expected"; + return 'universes: array expected' for (var i = 0; i < message.universes.length; ++i) if (!$util.isString(message.universes[i])) - return "universes: string[] expected"; + return 'universes: string[] expected' } - if (message.extraStats != null && message.hasOwnProperty("extraStats")) { + if (message.extraStats != null && message.hasOwnProperty('extraStats')) { if (!Array.isArray(message.extraStats)) - return "extraStats: array expected"; + return 'extraStats: array expected' for (var i = 0; i < message.extraStats.length; ++i) { - var error = $root.ExtraStatistic.verify(message.extraStats[i]); + var error = $root.ExtraStatistic.verify(message.extraStats[i]) if (error) - return "extraStats." + error; + return `extraStats.${error}` } } - return null; - }; + return null + } /** * Creates an Article message from a plain object. Also converts values to their respective internal types. @@ -1666,55 +1664,55 @@ $root.Article = (function() { */ Article.fromObject = function fromObject(object) { if (object instanceof $root.Article) - return object; - var message = new $root.Article(); + return object + var message = new $root.Article() if (object.shortname != null) - message.shortname = String(object.shortname); + message.shortname = String(object.shortname) if (object.longname != null) - message.longname = String(object.longname); + message.longname = String(object.longname) if (object.source != null) - message.source = String(object.source); + message.source = String(object.source) if (object.articleType != null) - message.articleType = String(object.articleType); + message.articleType = String(object.articleType) if (object.rows) { if (!Array.isArray(object.rows)) - throw TypeError(".Article.rows: array expected"); - message.rows = []; + throw TypeError('.Article.rows: array expected') + message.rows = [] for (var i = 0; i < object.rows.length; ++i) { - if (typeof object.rows[i] !== "object") - throw TypeError(".Article.rows: object expected"); - message.rows[i] = $root.StatisticRow.fromObject(object.rows[i]); + if (typeof object.rows[i] !== 'object') + throw TypeError('.Article.rows: object expected') + message.rows[i] = $root.StatisticRow.fromObject(object.rows[i]) } } if (object.related) { if (!Array.isArray(object.related)) - throw TypeError(".Article.related: array expected"); - message.related = []; + throw TypeError('.Article.related: array expected') + message.related = [] for (var i = 0; i < object.related.length; ++i) { - if (typeof object.related[i] !== "object") - throw TypeError(".Article.related: object expected"); - message.related[i] = $root.RelatedButtons.fromObject(object.related[i]); + if (typeof object.related[i] !== 'object') + throw TypeError('.Article.related: object expected') + message.related[i] = $root.RelatedButtons.fromObject(object.related[i]) } } if (object.universes) { if (!Array.isArray(object.universes)) - throw TypeError(".Article.universes: array expected"); - message.universes = []; + throw TypeError('.Article.universes: array expected') + message.universes = [] for (var i = 0; i < object.universes.length; ++i) - message.universes[i] = String(object.universes[i]); + message.universes[i] = String(object.universes[i]) } if (object.extraStats) { if (!Array.isArray(object.extraStats)) - throw TypeError(".Article.extraStats: array expected"); - message.extraStats = []; + throw TypeError('.Article.extraStats: array expected') + message.extraStats = [] for (var i = 0; i < object.extraStats.length; ++i) { - if (typeof object.extraStats[i] !== "object") - throw TypeError(".Article.extraStats: object expected"); - message.extraStats[i] = $root.ExtraStatistic.fromObject(object.extraStats[i]); + if (typeof object.extraStats[i] !== 'object') + throw TypeError('.Article.extraStats: object expected') + message.extraStats[i] = $root.ExtraStatistic.fromObject(object.extraStats[i]) } } - return message; - }; + return message + } /** * Creates a plain object from an Article message. Also converts values to other types if specified. @@ -1727,50 +1725,50 @@ $root.Article = (function() { */ Article.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.arrays || options.defaults) { - object.rows = []; - object.related = []; - object.universes = []; - object.extraStats = []; + object.rows = [] + object.related = [] + object.universes = [] + object.extraStats = [] } if (options.defaults) { - object.shortname = ""; - object.longname = ""; - object.source = ""; - object.articleType = ""; - } - if (message.shortname != null && message.hasOwnProperty("shortname")) - object.shortname = message.shortname; - if (message.longname != null && message.hasOwnProperty("longname")) - object.longname = message.longname; - if (message.source != null && message.hasOwnProperty("source")) - object.source = message.source; - if (message.articleType != null && message.hasOwnProperty("articleType")) - object.articleType = message.articleType; + object.shortname = '' + object.longname = '' + object.source = '' + object.articleType = '' + } + if (message.shortname != null && message.hasOwnProperty('shortname')) + object.shortname = message.shortname + if (message.longname != null && message.hasOwnProperty('longname')) + object.longname = message.longname + if (message.source != null && message.hasOwnProperty('source')) + object.source = message.source + if (message.articleType != null && message.hasOwnProperty('articleType')) + object.articleType = message.articleType if (message.rows && message.rows.length) { - object.rows = []; + object.rows = [] for (var j = 0; j < message.rows.length; ++j) - object.rows[j] = $root.StatisticRow.toObject(message.rows[j], options); + object.rows[j] = $root.StatisticRow.toObject(message.rows[j], options) } if (message.related && message.related.length) { - object.related = []; + object.related = [] for (var j = 0; j < message.related.length; ++j) - object.related[j] = $root.RelatedButtons.toObject(message.related[j], options); + object.related[j] = $root.RelatedButtons.toObject(message.related[j], options) } if (message.universes && message.universes.length) { - object.universes = []; + object.universes = [] for (var j = 0; j < message.universes.length; ++j) - object.universes[j] = message.universes[j]; + object.universes[j] = message.universes[j] } if (message.extraStats && message.extraStats.length) { - object.extraStats = []; + object.extraStats = [] for (var j = 0; j < message.extraStats.length; ++j) - object.extraStats[j] = $root.ExtraStatistic.toObject(message.extraStats[j], options); + object.extraStats[j] = $root.ExtraStatistic.toObject(message.extraStats[j], options) } - return object; - }; + return object + } /** * Converts this Article to JSON. @@ -1780,8 +1778,8 @@ $root.Article = (function() { * @returns {Object.} JSON object */ Article.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for Article @@ -1793,16 +1791,15 @@ $root.Article = (function() { */ Article.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/Article"; - }; - - return Article; -})(); + return `${typeUrlPrefix}/Article` + } -$root.Coordinate = (function() { + return Article +})() +$root.Coordinate = (function () { /** * Properties of a Coordinate. * @exports ICoordinate @@ -1823,7 +1820,7 @@ $root.Coordinate = (function() { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -1832,7 +1829,7 @@ $root.Coordinate = (function() { * @memberof Coordinate * @instance */ - Coordinate.prototype.lon = 0; + Coordinate.prototype.lon = 0 /** * Coordinate lat. @@ -1840,7 +1837,7 @@ $root.Coordinate = (function() { * @memberof Coordinate * @instance */ - Coordinate.prototype.lat = 0; + Coordinate.prototype.lat = 0 /** * Creates a new Coordinate instance using the specified properties. @@ -1851,8 +1848,8 @@ $root.Coordinate = (function() { * @returns {Coordinate} Coordinate instance */ Coordinate.create = function create(properties) { - return new Coordinate(properties); - }; + return new Coordinate(properties) + } /** * Encodes the specified Coordinate message. Does not implicitly {@link Coordinate.verify|verify} messages. @@ -1865,13 +1862,13 @@ $root.Coordinate = (function() { */ Coordinate.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.lon != null && Object.hasOwnProperty.call(message, "lon")) - writer.uint32(/* id 1, wireType 5 =*/13).float(message.lon); - if (message.lat != null && Object.hasOwnProperty.call(message, "lat")) - writer.uint32(/* id 2, wireType 5 =*/21).float(message.lat); - return writer; - }; + writer = $Writer.create() + if (message.lon != null && Object.hasOwnProperty.call(message, 'lon')) + writer.uint32(/* id 1, wireType 5 = */13).float(message.lon) + if (message.lat != null && Object.hasOwnProperty.call(message, 'lat')) + writer.uint32(/* id 2, wireType 5 = */21).float(message.lat) + return writer + } /** * Encodes the specified Coordinate message, length delimited. Does not implicitly {@link Coordinate.verify|verify} messages. @@ -1883,8 +1880,8 @@ $root.Coordinate = (function() { * @returns {$protobuf.Writer} Writer */ Coordinate.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes a Coordinate message from the specified reader or buffer. @@ -1899,26 +1896,26 @@ $root.Coordinate = (function() { */ Coordinate.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.Coordinate(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.Coordinate() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - message.lon = reader.float(); - break; + case 1: { + message.lon = reader.float() + break } - case 2: { - message.lat = reader.float(); - break; + case 2: { + message.lat = reader.float() + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes a Coordinate message from the specified reader or buffer, length delimited. @@ -1932,9 +1929,9 @@ $root.Coordinate = (function() { */ Coordinate.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies a Coordinate message. @@ -1945,16 +1942,16 @@ $root.Coordinate = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ Coordinate.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.lon != null && message.hasOwnProperty("lon")) - if (typeof message.lon !== "number") - return "lon: number expected"; - if (message.lat != null && message.hasOwnProperty("lat")) - if (typeof message.lat !== "number") - return "lat: number expected"; - return null; - }; + if (typeof message !== 'object' || message === null) + return 'object expected' + if (message.lon != null && message.hasOwnProperty('lon')) + if (typeof message.lon !== 'number') + return 'lon: number expected' + if (message.lat != null && message.hasOwnProperty('lat')) + if (typeof message.lat !== 'number') + return 'lat: number expected' + return null + } /** * Creates a Coordinate message from a plain object. Also converts values to their respective internal types. @@ -1966,14 +1963,14 @@ $root.Coordinate = (function() { */ Coordinate.fromObject = function fromObject(object) { if (object instanceof $root.Coordinate) - return object; - var message = new $root.Coordinate(); + return object + var message = new $root.Coordinate() if (object.lon != null) - message.lon = Number(object.lon); + message.lon = Number(object.lon) if (object.lat != null) - message.lat = Number(object.lat); - return message; - }; + message.lat = Number(object.lat) + return message + } /** * Creates a plain object from a Coordinate message. Also converts values to other types if specified. @@ -1986,18 +1983,18 @@ $root.Coordinate = (function() { */ Coordinate.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.defaults) { - object.lon = 0; - object.lat = 0; - } - if (message.lon != null && message.hasOwnProperty("lon")) - object.lon = options.json && !isFinite(message.lon) ? String(message.lon) : message.lon; - if (message.lat != null && message.hasOwnProperty("lat")) - object.lat = options.json && !isFinite(message.lat) ? String(message.lat) : message.lat; - return object; - }; + object.lon = 0 + object.lat = 0 + } + if (message.lon != null && message.hasOwnProperty('lon')) + object.lon = options.json && !isFinite(message.lon) ? String(message.lon) : message.lon + if (message.lat != null && message.hasOwnProperty('lat')) + object.lat = options.json && !isFinite(message.lat) ? String(message.lat) : message.lat + return object + } /** * Converts this Coordinate to JSON. @@ -2007,8 +2004,8 @@ $root.Coordinate = (function() { * @returns {Object.} JSON object */ Coordinate.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for Coordinate @@ -2020,16 +2017,15 @@ $root.Coordinate = (function() { */ Coordinate.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/Coordinate"; - }; - - return Coordinate; -})(); + return `${typeUrlPrefix}/Coordinate` + } -$root.Ring = (function() { + return Coordinate +})() +$root.Ring = (function () { /** * Properties of a Ring. * @exports IRing @@ -2046,11 +2042,11 @@ $root.Ring = (function() { * @param {IRing=} [properties] Properties to set */ function Ring(properties) { - this.coords = []; + this.coords = [] if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -2059,7 +2055,7 @@ $root.Ring = (function() { * @memberof Ring * @instance */ - Ring.prototype.coords = $util.emptyArray; + Ring.prototype.coords = $util.emptyArray /** * Creates a new Ring instance using the specified properties. @@ -2070,8 +2066,8 @@ $root.Ring = (function() { * @returns {Ring} Ring instance */ Ring.create = function create(properties) { - return new Ring(properties); - }; + return new Ring(properties) + } /** * Encodes the specified Ring message. Does not implicitly {@link Ring.verify|verify} messages. @@ -2084,12 +2080,12 @@ $root.Ring = (function() { */ Ring.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.coords != null && message.coords.length) + writer = $Writer.create() + if (message.coords?.length) for (var i = 0; i < message.coords.length; ++i) - $root.Coordinate.encode(message.coords[i], writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim(); - return writer; - }; + $root.Coordinate.encode(message.coords[i], writer.uint32(/* id 1, wireType 2 = */10).fork()).ldelim() + return writer + } /** * Encodes the specified Ring message, length delimited. Does not implicitly {@link Ring.verify|verify} messages. @@ -2101,8 +2097,8 @@ $root.Ring = (function() { * @returns {$protobuf.Writer} Writer */ Ring.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes a Ring message from the specified reader or buffer. @@ -2117,24 +2113,24 @@ $root.Ring = (function() { */ Ring.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.Ring(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.Ring() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - if (!(message.coords && message.coords.length)) - message.coords = []; - message.coords.push($root.Coordinate.decode(reader, reader.uint32())); - break; + case 1: { + if (!(message.coords?.length)) + message.coords = [] + message.coords.push($root.Coordinate.decode(reader, reader.uint32())) + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes a Ring message from the specified reader or buffer, length delimited. @@ -2148,9 +2144,9 @@ $root.Ring = (function() { */ Ring.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies a Ring message. @@ -2161,19 +2157,19 @@ $root.Ring = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ Ring.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.coords != null && message.hasOwnProperty("coords")) { + if (typeof message !== 'object' || message === null) + return 'object expected' + if (message.coords != null && message.hasOwnProperty('coords')) { if (!Array.isArray(message.coords)) - return "coords: array expected"; + return 'coords: array expected' for (var i = 0; i < message.coords.length; ++i) { - var error = $root.Coordinate.verify(message.coords[i]); + var error = $root.Coordinate.verify(message.coords[i]) if (error) - return "coords." + error; + return `coords.${error}` } } - return null; - }; + return null + } /** * Creates a Ring message from a plain object. Also converts values to their respective internal types. @@ -2185,20 +2181,20 @@ $root.Ring = (function() { */ Ring.fromObject = function fromObject(object) { if (object instanceof $root.Ring) - return object; - var message = new $root.Ring(); + return object + var message = new $root.Ring() if (object.coords) { if (!Array.isArray(object.coords)) - throw TypeError(".Ring.coords: array expected"); - message.coords = []; + throw TypeError('.Ring.coords: array expected') + message.coords = [] for (var i = 0; i < object.coords.length; ++i) { - if (typeof object.coords[i] !== "object") - throw TypeError(".Ring.coords: object expected"); - message.coords[i] = $root.Coordinate.fromObject(object.coords[i]); + if (typeof object.coords[i] !== 'object') + throw TypeError('.Ring.coords: object expected') + message.coords[i] = $root.Coordinate.fromObject(object.coords[i]) } } - return message; - }; + return message + } /** * Creates a plain object from a Ring message. Also converts values to other types if specified. @@ -2211,17 +2207,17 @@ $root.Ring = (function() { */ Ring.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.arrays || options.defaults) - object.coords = []; + object.coords = [] if (message.coords && message.coords.length) { - object.coords = []; + object.coords = [] for (var j = 0; j < message.coords.length; ++j) - object.coords[j] = $root.Coordinate.toObject(message.coords[j], options); + object.coords[j] = $root.Coordinate.toObject(message.coords[j], options) } - return object; - }; + return object + } /** * Converts this Ring to JSON. @@ -2231,8 +2227,8 @@ $root.Ring = (function() { * @returns {Object.} JSON object */ Ring.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for Ring @@ -2244,16 +2240,15 @@ $root.Ring = (function() { */ Ring.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/Ring"; - }; - - return Ring; -})(); + return `${typeUrlPrefix}/Ring` + } -$root.Polygon = (function() { + return Ring +})() +$root.Polygon = (function () { /** * Properties of a Polygon. * @exports IPolygon @@ -2270,11 +2265,11 @@ $root.Polygon = (function() { * @param {IPolygon=} [properties] Properties to set */ function Polygon(properties) { - this.rings = []; + this.rings = [] if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -2283,7 +2278,7 @@ $root.Polygon = (function() { * @memberof Polygon * @instance */ - Polygon.prototype.rings = $util.emptyArray; + Polygon.prototype.rings = $util.emptyArray /** * Creates a new Polygon instance using the specified properties. @@ -2294,8 +2289,8 @@ $root.Polygon = (function() { * @returns {Polygon} Polygon instance */ Polygon.create = function create(properties) { - return new Polygon(properties); - }; + return new Polygon(properties) + } /** * Encodes the specified Polygon message. Does not implicitly {@link Polygon.verify|verify} messages. @@ -2308,12 +2303,12 @@ $root.Polygon = (function() { */ Polygon.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.rings != null && message.rings.length) + writer = $Writer.create() + if (message.rings?.length) for (var i = 0; i < message.rings.length; ++i) - $root.Ring.encode(message.rings[i], writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim(); - return writer; - }; + $root.Ring.encode(message.rings[i], writer.uint32(/* id 1, wireType 2 = */10).fork()).ldelim() + return writer + } /** * Encodes the specified Polygon message, length delimited. Does not implicitly {@link Polygon.verify|verify} messages. @@ -2325,8 +2320,8 @@ $root.Polygon = (function() { * @returns {$protobuf.Writer} Writer */ Polygon.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes a Polygon message from the specified reader or buffer. @@ -2341,24 +2336,24 @@ $root.Polygon = (function() { */ Polygon.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.Polygon(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.Polygon() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - if (!(message.rings && message.rings.length)) - message.rings = []; - message.rings.push($root.Ring.decode(reader, reader.uint32())); - break; + case 1: { + if (!(message.rings?.length)) + message.rings = [] + message.rings.push($root.Ring.decode(reader, reader.uint32())) + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes a Polygon message from the specified reader or buffer, length delimited. @@ -2372,9 +2367,9 @@ $root.Polygon = (function() { */ Polygon.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies a Polygon message. @@ -2385,19 +2380,19 @@ $root.Polygon = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ Polygon.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.rings != null && message.hasOwnProperty("rings")) { + if (typeof message !== 'object' || message === null) + return 'object expected' + if (message.rings != null && message.hasOwnProperty('rings')) { if (!Array.isArray(message.rings)) - return "rings: array expected"; + return 'rings: array expected' for (var i = 0; i < message.rings.length; ++i) { - var error = $root.Ring.verify(message.rings[i]); + var error = $root.Ring.verify(message.rings[i]) if (error) - return "rings." + error; + return `rings.${error}` } } - return null; - }; + return null + } /** * Creates a Polygon message from a plain object. Also converts values to their respective internal types. @@ -2409,20 +2404,20 @@ $root.Polygon = (function() { */ Polygon.fromObject = function fromObject(object) { if (object instanceof $root.Polygon) - return object; - var message = new $root.Polygon(); + return object + var message = new $root.Polygon() if (object.rings) { if (!Array.isArray(object.rings)) - throw TypeError(".Polygon.rings: array expected"); - message.rings = []; + throw TypeError('.Polygon.rings: array expected') + message.rings = [] for (var i = 0; i < object.rings.length; ++i) { - if (typeof object.rings[i] !== "object") - throw TypeError(".Polygon.rings: object expected"); - message.rings[i] = $root.Ring.fromObject(object.rings[i]); + if (typeof object.rings[i] !== 'object') + throw TypeError('.Polygon.rings: object expected') + message.rings[i] = $root.Ring.fromObject(object.rings[i]) } } - return message; - }; + return message + } /** * Creates a plain object from a Polygon message. Also converts values to other types if specified. @@ -2435,17 +2430,17 @@ $root.Polygon = (function() { */ Polygon.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.arrays || options.defaults) - object.rings = []; + object.rings = [] if (message.rings && message.rings.length) { - object.rings = []; + object.rings = [] for (var j = 0; j < message.rings.length; ++j) - object.rings[j] = $root.Ring.toObject(message.rings[j], options); + object.rings[j] = $root.Ring.toObject(message.rings[j], options) } - return object; - }; + return object + } /** * Converts this Polygon to JSON. @@ -2455,8 +2450,8 @@ $root.Polygon = (function() { * @returns {Object.} JSON object */ Polygon.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for Polygon @@ -2468,16 +2463,15 @@ $root.Polygon = (function() { */ Polygon.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/Polygon"; - }; - - return Polygon; -})(); + return `${typeUrlPrefix}/Polygon` + } -$root.MultiPolygon = (function() { + return Polygon +})() +$root.MultiPolygon = (function () { /** * Properties of a MultiPolygon. * @exports IMultiPolygon @@ -2494,11 +2488,11 @@ $root.MultiPolygon = (function() { * @param {IMultiPolygon=} [properties] Properties to set */ function MultiPolygon(properties) { - this.polygons = []; + this.polygons = [] if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -2507,7 +2501,7 @@ $root.MultiPolygon = (function() { * @memberof MultiPolygon * @instance */ - MultiPolygon.prototype.polygons = $util.emptyArray; + MultiPolygon.prototype.polygons = $util.emptyArray /** * Creates a new MultiPolygon instance using the specified properties. @@ -2518,8 +2512,8 @@ $root.MultiPolygon = (function() { * @returns {MultiPolygon} MultiPolygon instance */ MultiPolygon.create = function create(properties) { - return new MultiPolygon(properties); - }; + return new MultiPolygon(properties) + } /** * Encodes the specified MultiPolygon message. Does not implicitly {@link MultiPolygon.verify|verify} messages. @@ -2532,12 +2526,12 @@ $root.MultiPolygon = (function() { */ MultiPolygon.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.polygons != null && message.polygons.length) + writer = $Writer.create() + if (message.polygons?.length) for (var i = 0; i < message.polygons.length; ++i) - $root.Polygon.encode(message.polygons[i], writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim(); - return writer; - }; + $root.Polygon.encode(message.polygons[i], writer.uint32(/* id 1, wireType 2 = */10).fork()).ldelim() + return writer + } /** * Encodes the specified MultiPolygon message, length delimited. Does not implicitly {@link MultiPolygon.verify|verify} messages. @@ -2549,8 +2543,8 @@ $root.MultiPolygon = (function() { * @returns {$protobuf.Writer} Writer */ MultiPolygon.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes a MultiPolygon message from the specified reader or buffer. @@ -2565,24 +2559,24 @@ $root.MultiPolygon = (function() { */ MultiPolygon.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.MultiPolygon(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.MultiPolygon() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - if (!(message.polygons && message.polygons.length)) - message.polygons = []; - message.polygons.push($root.Polygon.decode(reader, reader.uint32())); - break; + case 1: { + if (!(message.polygons?.length)) + message.polygons = [] + message.polygons.push($root.Polygon.decode(reader, reader.uint32())) + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes a MultiPolygon message from the specified reader or buffer, length delimited. @@ -2596,9 +2590,9 @@ $root.MultiPolygon = (function() { */ MultiPolygon.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies a MultiPolygon message. @@ -2609,19 +2603,19 @@ $root.MultiPolygon = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ MultiPolygon.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.polygons != null && message.hasOwnProperty("polygons")) { + if (typeof message !== 'object' || message === null) + return 'object expected' + if (message.polygons != null && message.hasOwnProperty('polygons')) { if (!Array.isArray(message.polygons)) - return "polygons: array expected"; + return 'polygons: array expected' for (var i = 0; i < message.polygons.length; ++i) { - var error = $root.Polygon.verify(message.polygons[i]); + var error = $root.Polygon.verify(message.polygons[i]) if (error) - return "polygons." + error; + return `polygons.${error}` } } - return null; - }; + return null + } /** * Creates a MultiPolygon message from a plain object. Also converts values to their respective internal types. @@ -2633,20 +2627,20 @@ $root.MultiPolygon = (function() { */ MultiPolygon.fromObject = function fromObject(object) { if (object instanceof $root.MultiPolygon) - return object; - var message = new $root.MultiPolygon(); + return object + var message = new $root.MultiPolygon() if (object.polygons) { if (!Array.isArray(object.polygons)) - throw TypeError(".MultiPolygon.polygons: array expected"); - message.polygons = []; + throw TypeError('.MultiPolygon.polygons: array expected') + message.polygons = [] for (var i = 0; i < object.polygons.length; ++i) { - if (typeof object.polygons[i] !== "object") - throw TypeError(".MultiPolygon.polygons: object expected"); - message.polygons[i] = $root.Polygon.fromObject(object.polygons[i]); + if (typeof object.polygons[i] !== 'object') + throw TypeError('.MultiPolygon.polygons: object expected') + message.polygons[i] = $root.Polygon.fromObject(object.polygons[i]) } } - return message; - }; + return message + } /** * Creates a plain object from a MultiPolygon message. Also converts values to other types if specified. @@ -2659,17 +2653,17 @@ $root.MultiPolygon = (function() { */ MultiPolygon.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.arrays || options.defaults) - object.polygons = []; + object.polygons = [] if (message.polygons && message.polygons.length) { - object.polygons = []; + object.polygons = [] for (var j = 0; j < message.polygons.length; ++j) - object.polygons[j] = $root.Polygon.toObject(message.polygons[j], options); + object.polygons[j] = $root.Polygon.toObject(message.polygons[j], options) } - return object; - }; + return object + } /** * Converts this MultiPolygon to JSON. @@ -2679,8 +2673,8 @@ $root.MultiPolygon = (function() { * @returns {Object.} JSON object */ MultiPolygon.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for MultiPolygon @@ -2692,16 +2686,15 @@ $root.MultiPolygon = (function() { */ MultiPolygon.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/MultiPolygon"; - }; - - return MultiPolygon; -})(); + return `${typeUrlPrefix}/MultiPolygon` + } -$root.Feature = (function() { + return MultiPolygon +})() +$root.Feature = (function () { /** * Properties of a Feature. * @exports IFeature @@ -2721,11 +2714,11 @@ $root.Feature = (function() { * @param {IFeature=} [properties] Properties to set */ function Feature(properties) { - this.zones = []; + this.zones = [] if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -2734,7 +2727,7 @@ $root.Feature = (function() { * @memberof Feature * @instance */ - Feature.prototype.polygon = null; + Feature.prototype.polygon = null /** * Feature multipolygon. @@ -2742,7 +2735,7 @@ $root.Feature = (function() { * @memberof Feature * @instance */ - Feature.prototype.multipolygon = null; + Feature.prototype.multipolygon = null /** * Feature zones. @@ -2750,7 +2743,7 @@ $root.Feature = (function() { * @memberof Feature * @instance */ - Feature.prototype.zones = $util.emptyArray; + Feature.prototype.zones = $util.emptyArray /** * Feature centerLon. @@ -2758,10 +2751,10 @@ $root.Feature = (function() { * @memberof Feature * @instance */ - Feature.prototype.centerLon = 0; + Feature.prototype.centerLon = 0 // OneOf field names bound to virtual getters and setters - var $oneOfFields; + var $oneOfFields /** * Feature geometry. @@ -2769,10 +2762,10 @@ $root.Feature = (function() { * @memberof Feature * @instance */ - Object.defineProperty(Feature.prototype, "geometry", { - get: $util.oneOfGetter($oneOfFields = ["polygon", "multipolygon"]), - set: $util.oneOfSetter($oneOfFields) - }); + Object.defineProperty(Feature.prototype, 'geometry', { + get: $util.oneOfGetter($oneOfFields = ['polygon', 'multipolygon']), + set: $util.oneOfSetter($oneOfFields), + }) /** * Creates a new Feature instance using the specified properties. @@ -2783,8 +2776,8 @@ $root.Feature = (function() { * @returns {Feature} Feature instance */ Feature.create = function create(properties) { - return new Feature(properties); - }; + return new Feature(properties) + } /** * Encodes the specified Feature message. Does not implicitly {@link Feature.verify|verify} messages. @@ -2797,21 +2790,21 @@ $root.Feature = (function() { */ Feature.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.polygon != null && Object.hasOwnProperty.call(message, "polygon")) - $root.Polygon.encode(message.polygon, writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim(); - if (message.multipolygon != null && Object.hasOwnProperty.call(message, "multipolygon")) - $root.MultiPolygon.encode(message.multipolygon, writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim(); - if (message.zones != null && message.zones.length) { - writer.uint32(/* id 3, wireType 2 =*/26).fork(); + writer = $Writer.create() + if (message.polygon != null && Object.hasOwnProperty.call(message, 'polygon')) + $root.Polygon.encode(message.polygon, writer.uint32(/* id 1, wireType 2 = */10).fork()).ldelim() + if (message.multipolygon != null && Object.hasOwnProperty.call(message, 'multipolygon')) + $root.MultiPolygon.encode(message.multipolygon, writer.uint32(/* id 2, wireType 2 = */18).fork()).ldelim() + if (message.zones?.length) { + writer.uint32(/* id 3, wireType 2 = */26).fork() for (var i = 0; i < message.zones.length; ++i) - writer.int32(message.zones[i]); - writer.ldelim(); + writer.int32(message.zones[i]) + writer.ldelim() } - if (message.centerLon != null && Object.hasOwnProperty.call(message, "centerLon")) - writer.uint32(/* id 4, wireType 5 =*/37).float(message.centerLon); - return writer; - }; + if (message.centerLon != null && Object.hasOwnProperty.call(message, 'centerLon')) + writer.uint32(/* id 4, wireType 5 = */37).float(message.centerLon) + return writer + } /** * Encodes the specified Feature message, length delimited. Does not implicitly {@link Feature.verify|verify} messages. @@ -2823,8 +2816,8 @@ $root.Feature = (function() { * @returns {$protobuf.Writer} Writer */ Feature.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes a Feature message from the specified reader or buffer. @@ -2839,41 +2832,42 @@ $root.Feature = (function() { */ Feature.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.Feature(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.Feature() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - message.polygon = $root.Polygon.decode(reader, reader.uint32()); - break; + case 1: { + message.polygon = $root.Polygon.decode(reader, reader.uint32()) + break } - case 2: { - message.multipolygon = $root.MultiPolygon.decode(reader, reader.uint32()); - break; + case 2: { + message.multipolygon = $root.MultiPolygon.decode(reader, reader.uint32()) + break } - case 3: { - if (!(message.zones && message.zones.length)) - message.zones = []; + case 3: { + if (!(message.zones?.length)) + message.zones = [] if ((tag & 7) === 2) { - var end2 = reader.uint32() + reader.pos; + var end2 = reader.uint32() + reader.pos while (reader.pos < end2) - message.zones.push(reader.int32()); - } else - message.zones.push(reader.int32()); - break; + message.zones.push(reader.int32()) + } + else + message.zones.push(reader.int32()) + break } - case 4: { - message.centerLon = reader.float(); - break; + case 4: { + message.centerLon = reader.float() + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes a Feature message from the specified reader or buffer, length delimited. @@ -2887,9 +2881,9 @@ $root.Feature = (function() { */ Feature.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies a Feature message. @@ -2900,39 +2894,39 @@ $root.Feature = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ Feature.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - var properties = {}; - if (message.polygon != null && message.hasOwnProperty("polygon")) { - properties.geometry = 1; + if (typeof message !== 'object' || message === null) + return 'object expected' + var properties = {} + if (message.polygon != null && message.hasOwnProperty('polygon')) { + properties.geometry = 1 { - var error = $root.Polygon.verify(message.polygon); + var error = $root.Polygon.verify(message.polygon) if (error) - return "polygon." + error; + return `polygon.${error}` } } - if (message.multipolygon != null && message.hasOwnProperty("multipolygon")) { + if (message.multipolygon != null && message.hasOwnProperty('multipolygon')) { if (properties.geometry === 1) - return "geometry: multiple values"; - properties.geometry = 1; + return 'geometry: multiple values' + properties.geometry = 1 { - var error = $root.MultiPolygon.verify(message.multipolygon); + var error = $root.MultiPolygon.verify(message.multipolygon) if (error) - return "multipolygon." + error; + return `multipolygon.${error}` } } - if (message.zones != null && message.hasOwnProperty("zones")) { + if (message.zones != null && message.hasOwnProperty('zones')) { if (!Array.isArray(message.zones)) - return "zones: array expected"; + return 'zones: array expected' for (var i = 0; i < message.zones.length; ++i) if (!$util.isInteger(message.zones[i])) - return "zones: integer[] expected"; + return 'zones: integer[] expected' } - if (message.centerLon != null && message.hasOwnProperty("centerLon")) - if (typeof message.centerLon !== "number") - return "centerLon: number expected"; - return null; - }; + if (message.centerLon != null && message.hasOwnProperty('centerLon')) + if (typeof message.centerLon !== 'number') + return 'centerLon: number expected' + return null + } /** * Creates a Feature message from a plain object. Also converts values to their respective internal types. @@ -2944,29 +2938,29 @@ $root.Feature = (function() { */ Feature.fromObject = function fromObject(object) { if (object instanceof $root.Feature) - return object; - var message = new $root.Feature(); + return object + var message = new $root.Feature() if (object.polygon != null) { - if (typeof object.polygon !== "object") - throw TypeError(".Feature.polygon: object expected"); - message.polygon = $root.Polygon.fromObject(object.polygon); + if (typeof object.polygon !== 'object') + throw TypeError('.Feature.polygon: object expected') + message.polygon = $root.Polygon.fromObject(object.polygon) } if (object.multipolygon != null) { - if (typeof object.multipolygon !== "object") - throw TypeError(".Feature.multipolygon: object expected"); - message.multipolygon = $root.MultiPolygon.fromObject(object.multipolygon); + if (typeof object.multipolygon !== 'object') + throw TypeError('.Feature.multipolygon: object expected') + message.multipolygon = $root.MultiPolygon.fromObject(object.multipolygon) } if (object.zones) { if (!Array.isArray(object.zones)) - throw TypeError(".Feature.zones: array expected"); - message.zones = []; + throw TypeError('.Feature.zones: array expected') + message.zones = [] for (var i = 0; i < object.zones.length; ++i) - message.zones[i] = object.zones[i] | 0; + message.zones[i] = object.zones[i] | 0 } if (object.centerLon != null) - message.centerLon = Number(object.centerLon); - return message; - }; + message.centerLon = Number(object.centerLon) + return message + } /** * Creates a plain object from a Feature message. Also converts values to other types if specified. @@ -2979,31 +2973,31 @@ $root.Feature = (function() { */ Feature.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.arrays || options.defaults) - object.zones = []; + object.zones = [] if (options.defaults) - object.centerLon = 0; - if (message.polygon != null && message.hasOwnProperty("polygon")) { - object.polygon = $root.Polygon.toObject(message.polygon, options); + object.centerLon = 0 + if (message.polygon != null && message.hasOwnProperty('polygon')) { + object.polygon = $root.Polygon.toObject(message.polygon, options) if (options.oneofs) - object.geometry = "polygon"; + object.geometry = 'polygon' } - if (message.multipolygon != null && message.hasOwnProperty("multipolygon")) { - object.multipolygon = $root.MultiPolygon.toObject(message.multipolygon, options); + if (message.multipolygon != null && message.hasOwnProperty('multipolygon')) { + object.multipolygon = $root.MultiPolygon.toObject(message.multipolygon, options) if (options.oneofs) - object.geometry = "multipolygon"; + object.geometry = 'multipolygon' } if (message.zones && message.zones.length) { - object.zones = []; + object.zones = [] for (var j = 0; j < message.zones.length; ++j) - object.zones[j] = message.zones[j]; + object.zones[j] = message.zones[j] } - if (message.centerLon != null && message.hasOwnProperty("centerLon")) - object.centerLon = options.json && !isFinite(message.centerLon) ? String(message.centerLon) : message.centerLon; - return object; - }; + if (message.centerLon != null && message.hasOwnProperty('centerLon')) + object.centerLon = options.json && !isFinite(message.centerLon) ? String(message.centerLon) : message.centerLon + return object + } /** * Converts this Feature to JSON. @@ -3013,8 +3007,8 @@ $root.Feature = (function() { * @returns {Object.} JSON object */ Feature.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for Feature @@ -3026,16 +3020,15 @@ $root.Feature = (function() { */ Feature.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/Feature"; - }; - - return Feature; -})(); + return `${typeUrlPrefix}/Feature` + } -$root.StringList = (function() { + return Feature +})() +$root.StringList = (function () { /** * Properties of a StringList. * @exports IStringList @@ -3052,11 +3045,11 @@ $root.StringList = (function() { * @param {IStringList=} [properties] Properties to set */ function StringList(properties) { - this.elements = []; + this.elements = [] if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -3065,7 +3058,7 @@ $root.StringList = (function() { * @memberof StringList * @instance */ - StringList.prototype.elements = $util.emptyArray; + StringList.prototype.elements = $util.emptyArray /** * Creates a new StringList instance using the specified properties. @@ -3076,8 +3069,8 @@ $root.StringList = (function() { * @returns {StringList} StringList instance */ StringList.create = function create(properties) { - return new StringList(properties); - }; + return new StringList(properties) + } /** * Encodes the specified StringList message. Does not implicitly {@link StringList.verify|verify} messages. @@ -3090,12 +3083,12 @@ $root.StringList = (function() { */ StringList.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.elements != null && message.elements.length) + writer = $Writer.create() + if (message.elements?.length) for (var i = 0; i < message.elements.length; ++i) - writer.uint32(/* id 1, wireType 2 =*/10).string(message.elements[i]); - return writer; - }; + writer.uint32(/* id 1, wireType 2 = */10).string(message.elements[i]) + return writer + } /** * Encodes the specified StringList message, length delimited. Does not implicitly {@link StringList.verify|verify} messages. @@ -3107,8 +3100,8 @@ $root.StringList = (function() { * @returns {$protobuf.Writer} Writer */ StringList.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes a StringList message from the specified reader or buffer. @@ -3123,24 +3116,24 @@ $root.StringList = (function() { */ StringList.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StringList(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StringList() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - if (!(message.elements && message.elements.length)) - message.elements = []; - message.elements.push(reader.string()); - break; + case 1: { + if (!(message.elements?.length)) + message.elements = [] + message.elements.push(reader.string()) + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes a StringList message from the specified reader or buffer, length delimited. @@ -3154,9 +3147,9 @@ $root.StringList = (function() { */ StringList.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies a StringList message. @@ -3167,17 +3160,17 @@ $root.StringList = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ StringList.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.elements != null && message.hasOwnProperty("elements")) { + if (typeof message !== 'object' || message === null) + return 'object expected' + if (message.elements != null && message.hasOwnProperty('elements')) { if (!Array.isArray(message.elements)) - return "elements: array expected"; + return 'elements: array expected' for (var i = 0; i < message.elements.length; ++i) if (!$util.isString(message.elements[i])) - return "elements: string[] expected"; + return 'elements: string[] expected' } - return null; - }; + return null + } /** * Creates a StringList message from a plain object. Also converts values to their respective internal types. @@ -3189,17 +3182,17 @@ $root.StringList = (function() { */ StringList.fromObject = function fromObject(object) { if (object instanceof $root.StringList) - return object; - var message = new $root.StringList(); + return object + var message = new $root.StringList() if (object.elements) { if (!Array.isArray(object.elements)) - throw TypeError(".StringList.elements: array expected"); - message.elements = []; + throw TypeError('.StringList.elements: array expected') + message.elements = [] for (var i = 0; i < object.elements.length; ++i) - message.elements[i] = String(object.elements[i]); + message.elements[i] = String(object.elements[i]) } - return message; - }; + return message + } /** * Creates a plain object from a StringList message. Also converts values to other types if specified. @@ -3212,17 +3205,17 @@ $root.StringList = (function() { */ StringList.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.arrays || options.defaults) - object.elements = []; + object.elements = [] if (message.elements && message.elements.length) { - object.elements = []; + object.elements = [] for (var j = 0; j < message.elements.length; ++j) - object.elements[j] = message.elements[j]; + object.elements[j] = message.elements[j] } - return object; - }; + return object + } /** * Converts this StringList to JSON. @@ -3232,8 +3225,8 @@ $root.StringList = (function() { * @returns {Object.} JSON object */ StringList.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for StringList @@ -3245,16 +3238,15 @@ $root.StringList = (function() { */ StringList.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/StringList"; - }; - - return StringList; -})(); + return `${typeUrlPrefix}/StringList` + } -$root.SearchIndex = (function() { + return StringList +})() +$root.SearchIndex = (function () { /** * Properties of a SearchIndex. * @exports ISearchIndex @@ -3272,12 +3264,12 @@ $root.SearchIndex = (function() { * @param {ISearchIndex=} [properties] Properties to set */ function SearchIndex(properties) { - this.elements = []; - this.priorities = []; + this.elements = [] + this.priorities = [] if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -3286,7 +3278,7 @@ $root.SearchIndex = (function() { * @memberof SearchIndex * @instance */ - SearchIndex.prototype.elements = $util.emptyArray; + SearchIndex.prototype.elements = $util.emptyArray /** * SearchIndex priorities. @@ -3294,7 +3286,7 @@ $root.SearchIndex = (function() { * @memberof SearchIndex * @instance */ - SearchIndex.prototype.priorities = $util.emptyArray; + SearchIndex.prototype.priorities = $util.emptyArray /** * Creates a new SearchIndex instance using the specified properties. @@ -3305,8 +3297,8 @@ $root.SearchIndex = (function() { * @returns {SearchIndex} SearchIndex instance */ SearchIndex.create = function create(properties) { - return new SearchIndex(properties); - }; + return new SearchIndex(properties) + } /** * Encodes the specified SearchIndex message. Does not implicitly {@link SearchIndex.verify|verify} messages. @@ -3319,18 +3311,18 @@ $root.SearchIndex = (function() { */ SearchIndex.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.elements != null && message.elements.length) + writer = $Writer.create() + if (message.elements?.length) for (var i = 0; i < message.elements.length; ++i) - writer.uint32(/* id 1, wireType 2 =*/10).string(message.elements[i]); - if (message.priorities != null && message.priorities.length) { - writer.uint32(/* id 2, wireType 2 =*/18).fork(); + writer.uint32(/* id 1, wireType 2 = */10).string(message.elements[i]) + if (message.priorities?.length) { + writer.uint32(/* id 2, wireType 2 = */18).fork() for (var i = 0; i < message.priorities.length; ++i) - writer.uint32(message.priorities[i]); - writer.ldelim(); + writer.uint32(message.priorities[i]) + writer.ldelim() } - return writer; - }; + return writer + } /** * Encodes the specified SearchIndex message, length delimited. Does not implicitly {@link SearchIndex.verify|verify} messages. @@ -3342,8 +3334,8 @@ $root.SearchIndex = (function() { * @returns {$protobuf.Writer} Writer */ SearchIndex.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes a SearchIndex message from the specified reader or buffer. @@ -3358,35 +3350,36 @@ $root.SearchIndex = (function() { */ SearchIndex.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.SearchIndex(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.SearchIndex() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - if (!(message.elements && message.elements.length)) - message.elements = []; - message.elements.push(reader.string()); - break; + case 1: { + if (!(message.elements?.length)) + message.elements = [] + message.elements.push(reader.string()) + break } - case 2: { - if (!(message.priorities && message.priorities.length)) - message.priorities = []; + case 2: { + if (!(message.priorities?.length)) + message.priorities = [] if ((tag & 7) === 2) { - var end2 = reader.uint32() + reader.pos; + var end2 = reader.uint32() + reader.pos while (reader.pos < end2) - message.priorities.push(reader.uint32()); - } else - message.priorities.push(reader.uint32()); - break; + message.priorities.push(reader.uint32()) + } + else + message.priorities.push(reader.uint32()) + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes a SearchIndex message from the specified reader or buffer, length delimited. @@ -3400,9 +3393,9 @@ $root.SearchIndex = (function() { */ SearchIndex.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies a SearchIndex message. @@ -3413,24 +3406,24 @@ $root.SearchIndex = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ SearchIndex.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.elements != null && message.hasOwnProperty("elements")) { + if (typeof message !== 'object' || message === null) + return 'object expected' + if (message.elements != null && message.hasOwnProperty('elements')) { if (!Array.isArray(message.elements)) - return "elements: array expected"; + return 'elements: array expected' for (var i = 0; i < message.elements.length; ++i) if (!$util.isString(message.elements[i])) - return "elements: string[] expected"; + return 'elements: string[] expected' } - if (message.priorities != null && message.hasOwnProperty("priorities")) { + if (message.priorities != null && message.hasOwnProperty('priorities')) { if (!Array.isArray(message.priorities)) - return "priorities: array expected"; + return 'priorities: array expected' for (var i = 0; i < message.priorities.length; ++i) if (!$util.isInteger(message.priorities[i])) - return "priorities: integer[] expected"; + return 'priorities: integer[] expected' } - return null; - }; + return null + } /** * Creates a SearchIndex message from a plain object. Also converts values to their respective internal types. @@ -3442,24 +3435,24 @@ $root.SearchIndex = (function() { */ SearchIndex.fromObject = function fromObject(object) { if (object instanceof $root.SearchIndex) - return object; - var message = new $root.SearchIndex(); + return object + var message = new $root.SearchIndex() if (object.elements) { if (!Array.isArray(object.elements)) - throw TypeError(".SearchIndex.elements: array expected"); - message.elements = []; + throw TypeError('.SearchIndex.elements: array expected') + message.elements = [] for (var i = 0; i < object.elements.length; ++i) - message.elements[i] = String(object.elements[i]); + message.elements[i] = String(object.elements[i]) } if (object.priorities) { if (!Array.isArray(object.priorities)) - throw TypeError(".SearchIndex.priorities: array expected"); - message.priorities = []; + throw TypeError('.SearchIndex.priorities: array expected') + message.priorities = [] for (var i = 0; i < object.priorities.length; ++i) - message.priorities[i] = object.priorities[i] >>> 0; + message.priorities[i] = object.priorities[i] >>> 0 } - return message; - }; + return message + } /** * Creates a plain object from a SearchIndex message. Also converts values to other types if specified. @@ -3472,24 +3465,24 @@ $root.SearchIndex = (function() { */ SearchIndex.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.arrays || options.defaults) { - object.elements = []; - object.priorities = []; + object.elements = [] + object.priorities = [] } if (message.elements && message.elements.length) { - object.elements = []; + object.elements = [] for (var j = 0; j < message.elements.length; ++j) - object.elements[j] = message.elements[j]; + object.elements[j] = message.elements[j] } if (message.priorities && message.priorities.length) { - object.priorities = []; + object.priorities = [] for (var j = 0; j < message.priorities.length; ++j) - object.priorities[j] = message.priorities[j]; + object.priorities[j] = message.priorities[j] } - return object; - }; + return object + } /** * Converts this SearchIndex to JSON. @@ -3499,8 +3492,8 @@ $root.SearchIndex = (function() { * @returns {Object.} JSON object */ SearchIndex.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for SearchIndex @@ -3512,16 +3505,15 @@ $root.SearchIndex = (function() { */ SearchIndex.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/SearchIndex"; - }; - - return SearchIndex; -})(); + return `${typeUrlPrefix}/SearchIndex` + } -$root.OrderList = (function() { + return SearchIndex +})() +$root.OrderList = (function () { /** * Properties of an OrderList. * @exports IOrderList @@ -3538,11 +3530,11 @@ $root.OrderList = (function() { * @param {IOrderList=} [properties] Properties to set */ function OrderList(properties) { - this.orderIdxs = []; + this.orderIdxs = [] if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -3551,7 +3543,7 @@ $root.OrderList = (function() { * @memberof OrderList * @instance */ - OrderList.prototype.orderIdxs = $util.emptyArray; + OrderList.prototype.orderIdxs = $util.emptyArray /** * Creates a new OrderList instance using the specified properties. @@ -3562,8 +3554,8 @@ $root.OrderList = (function() { * @returns {OrderList} OrderList instance */ OrderList.create = function create(properties) { - return new OrderList(properties); - }; + return new OrderList(properties) + } /** * Encodes the specified OrderList message. Does not implicitly {@link OrderList.verify|verify} messages. @@ -3576,15 +3568,15 @@ $root.OrderList = (function() { */ OrderList.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.orderIdxs != null && message.orderIdxs.length) { - writer.uint32(/* id 1, wireType 2 =*/10).fork(); + writer = $Writer.create() + if (message.orderIdxs?.length) { + writer.uint32(/* id 1, wireType 2 = */10).fork() for (var i = 0; i < message.orderIdxs.length; ++i) - writer.int32(message.orderIdxs[i]); - writer.ldelim(); + writer.int32(message.orderIdxs[i]) + writer.ldelim() } - return writer; - }; + return writer + } /** * Encodes the specified OrderList message, length delimited. Does not implicitly {@link OrderList.verify|verify} messages. @@ -3596,8 +3588,8 @@ $root.OrderList = (function() { * @returns {$protobuf.Writer} Writer */ OrderList.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes an OrderList message from the specified reader or buffer. @@ -3612,29 +3604,30 @@ $root.OrderList = (function() { */ OrderList.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.OrderList(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.OrderList() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - if (!(message.orderIdxs && message.orderIdxs.length)) - message.orderIdxs = []; + case 1: { + if (!(message.orderIdxs?.length)) + message.orderIdxs = [] if ((tag & 7) === 2) { - var end2 = reader.uint32() + reader.pos; + var end2 = reader.uint32() + reader.pos while (reader.pos < end2) - message.orderIdxs.push(reader.int32()); - } else - message.orderIdxs.push(reader.int32()); - break; + message.orderIdxs.push(reader.int32()) + } + else + message.orderIdxs.push(reader.int32()) + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes an OrderList message from the specified reader or buffer, length delimited. @@ -3648,9 +3641,9 @@ $root.OrderList = (function() { */ OrderList.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies an OrderList message. @@ -3661,17 +3654,17 @@ $root.OrderList = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ OrderList.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.orderIdxs != null && message.hasOwnProperty("orderIdxs")) { + if (typeof message !== 'object' || message === null) + return 'object expected' + if (message.orderIdxs != null && message.hasOwnProperty('orderIdxs')) { if (!Array.isArray(message.orderIdxs)) - return "orderIdxs: array expected"; + return 'orderIdxs: array expected' for (var i = 0; i < message.orderIdxs.length; ++i) if (!$util.isInteger(message.orderIdxs[i])) - return "orderIdxs: integer[] expected"; + return 'orderIdxs: integer[] expected' } - return null; - }; + return null + } /** * Creates an OrderList message from a plain object. Also converts values to their respective internal types. @@ -3683,17 +3676,17 @@ $root.OrderList = (function() { */ OrderList.fromObject = function fromObject(object) { if (object instanceof $root.OrderList) - return object; - var message = new $root.OrderList(); + return object + var message = new $root.OrderList() if (object.orderIdxs) { if (!Array.isArray(object.orderIdxs)) - throw TypeError(".OrderList.orderIdxs: array expected"); - message.orderIdxs = []; + throw TypeError('.OrderList.orderIdxs: array expected') + message.orderIdxs = [] for (var i = 0; i < object.orderIdxs.length; ++i) - message.orderIdxs[i] = object.orderIdxs[i] | 0; + message.orderIdxs[i] = object.orderIdxs[i] | 0 } - return message; - }; + return message + } /** * Creates a plain object from an OrderList message. Also converts values to other types if specified. @@ -3706,17 +3699,17 @@ $root.OrderList = (function() { */ OrderList.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.arrays || options.defaults) - object.orderIdxs = []; + object.orderIdxs = [] if (message.orderIdxs && message.orderIdxs.length) { - object.orderIdxs = []; + object.orderIdxs = [] for (var j = 0; j < message.orderIdxs.length; ++j) - object.orderIdxs[j] = message.orderIdxs[j]; + object.orderIdxs[j] = message.orderIdxs[j] } - return object; - }; + return object + } /** * Converts this OrderList to JSON. @@ -3726,8 +3719,8 @@ $root.OrderList = (function() { * @returns {Object.} JSON object */ OrderList.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for OrderList @@ -3739,16 +3732,15 @@ $root.OrderList = (function() { */ OrderList.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/OrderList"; - }; - - return OrderList; -})(); + return `${typeUrlPrefix}/OrderList` + } -$root.DataList = (function() { + return OrderList +})() +$root.DataList = (function () { /** * Properties of a DataList. * @exports IDataList @@ -3766,12 +3758,12 @@ $root.DataList = (function() { * @param {IDataList=} [properties] Properties to set */ function DataList(properties) { - this.value = []; - this.populationPercentile = []; + this.value = [] + this.populationPercentile = [] if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -3780,7 +3772,7 @@ $root.DataList = (function() { * @memberof DataList * @instance */ - DataList.prototype.value = $util.emptyArray; + DataList.prototype.value = $util.emptyArray /** * DataList populationPercentile. @@ -3788,7 +3780,7 @@ $root.DataList = (function() { * @memberof DataList * @instance */ - DataList.prototype.populationPercentile = $util.emptyArray; + DataList.prototype.populationPercentile = $util.emptyArray /** * Creates a new DataList instance using the specified properties. @@ -3799,8 +3791,8 @@ $root.DataList = (function() { * @returns {DataList} DataList instance */ DataList.create = function create(properties) { - return new DataList(properties); - }; + return new DataList(properties) + } /** * Encodes the specified DataList message. Does not implicitly {@link DataList.verify|verify} messages. @@ -3813,21 +3805,21 @@ $root.DataList = (function() { */ DataList.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.value != null && message.value.length) { - writer.uint32(/* id 1, wireType 2 =*/10).fork(); + writer = $Writer.create() + if (message.value?.length) { + writer.uint32(/* id 1, wireType 2 = */10).fork() for (var i = 0; i < message.value.length; ++i) - writer.float(message.value[i]); - writer.ldelim(); + writer.float(message.value[i]) + writer.ldelim() } - if (message.populationPercentile != null && message.populationPercentile.length) { - writer.uint32(/* id 2, wireType 2 =*/18).fork(); + if (message.populationPercentile?.length) { + writer.uint32(/* id 2, wireType 2 = */18).fork() for (var i = 0; i < message.populationPercentile.length; ++i) - writer.float(message.populationPercentile[i]); - writer.ldelim(); + writer.float(message.populationPercentile[i]) + writer.ldelim() } - return writer; - }; + return writer + } /** * Encodes the specified DataList message, length delimited. Does not implicitly {@link DataList.verify|verify} messages. @@ -3839,8 +3831,8 @@ $root.DataList = (function() { * @returns {$protobuf.Writer} Writer */ DataList.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes a DataList message from the specified reader or buffer. @@ -3855,40 +3847,42 @@ $root.DataList = (function() { */ DataList.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.DataList(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.DataList() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - if (!(message.value && message.value.length)) - message.value = []; + case 1: { + if (!(message.value?.length)) + message.value = [] if ((tag & 7) === 2) { - var end2 = reader.uint32() + reader.pos; + var end2 = reader.uint32() + reader.pos while (reader.pos < end2) - message.value.push(reader.float()); - } else - message.value.push(reader.float()); - break; + message.value.push(reader.float()) + } + else + message.value.push(reader.float()) + break } - case 2: { - if (!(message.populationPercentile && message.populationPercentile.length)) - message.populationPercentile = []; + case 2: { + if (!(message.populationPercentile?.length)) + message.populationPercentile = [] if ((tag & 7) === 2) { - var end2 = reader.uint32() + reader.pos; + var end2 = reader.uint32() + reader.pos while (reader.pos < end2) - message.populationPercentile.push(reader.float()); - } else - message.populationPercentile.push(reader.float()); - break; + message.populationPercentile.push(reader.float()) + } + else + message.populationPercentile.push(reader.float()) + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes a DataList message from the specified reader or buffer, length delimited. @@ -3902,9 +3896,9 @@ $root.DataList = (function() { */ DataList.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies a DataList message. @@ -3915,24 +3909,24 @@ $root.DataList = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ DataList.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.value != null && message.hasOwnProperty("value")) { + if (typeof message !== 'object' || message === null) + return 'object expected' + if (message.value != null && message.hasOwnProperty('value')) { if (!Array.isArray(message.value)) - return "value: array expected"; + return 'value: array expected' for (var i = 0; i < message.value.length; ++i) - if (typeof message.value[i] !== "number") - return "value: number[] expected"; + if (typeof message.value[i] !== 'number') + return 'value: number[] expected' } - if (message.populationPercentile != null && message.hasOwnProperty("populationPercentile")) { + if (message.populationPercentile != null && message.hasOwnProperty('populationPercentile')) { if (!Array.isArray(message.populationPercentile)) - return "populationPercentile: array expected"; + return 'populationPercentile: array expected' for (var i = 0; i < message.populationPercentile.length; ++i) - if (typeof message.populationPercentile[i] !== "number") - return "populationPercentile: number[] expected"; + if (typeof message.populationPercentile[i] !== 'number') + return 'populationPercentile: number[] expected' } - return null; - }; + return null + } /** * Creates a DataList message from a plain object. Also converts values to their respective internal types. @@ -3944,24 +3938,24 @@ $root.DataList = (function() { */ DataList.fromObject = function fromObject(object) { if (object instanceof $root.DataList) - return object; - var message = new $root.DataList(); + return object + var message = new $root.DataList() if (object.value) { if (!Array.isArray(object.value)) - throw TypeError(".DataList.value: array expected"); - message.value = []; + throw TypeError('.DataList.value: array expected') + message.value = [] for (var i = 0; i < object.value.length; ++i) - message.value[i] = Number(object.value[i]); + message.value[i] = Number(object.value[i]) } if (object.populationPercentile) { if (!Array.isArray(object.populationPercentile)) - throw TypeError(".DataList.populationPercentile: array expected"); - message.populationPercentile = []; + throw TypeError('.DataList.populationPercentile: array expected') + message.populationPercentile = [] for (var i = 0; i < object.populationPercentile.length; ++i) - message.populationPercentile[i] = Number(object.populationPercentile[i]); + message.populationPercentile[i] = Number(object.populationPercentile[i]) } - return message; - }; + return message + } /** * Creates a plain object from a DataList message. Also converts values to other types if specified. @@ -3974,24 +3968,24 @@ $root.DataList = (function() { */ DataList.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.arrays || options.defaults) { - object.value = []; - object.populationPercentile = []; + object.value = [] + object.populationPercentile = [] } if (message.value && message.value.length) { - object.value = []; + object.value = [] for (var j = 0; j < message.value.length; ++j) - object.value[j] = options.json && !isFinite(message.value[j]) ? String(message.value[j]) : message.value[j]; + object.value[j] = options.json && !isFinite(message.value[j]) ? String(message.value[j]) : message.value[j] } if (message.populationPercentile && message.populationPercentile.length) { - object.populationPercentile = []; + object.populationPercentile = [] for (var j = 0; j < message.populationPercentile.length; ++j) - object.populationPercentile[j] = options.json && !isFinite(message.populationPercentile[j]) ? String(message.populationPercentile[j]) : message.populationPercentile[j]; + object.populationPercentile[j] = options.json && !isFinite(message.populationPercentile[j]) ? String(message.populationPercentile[j]) : message.populationPercentile[j] } - return object; - }; + return object + } /** * Converts this DataList to JSON. @@ -4001,8 +3995,8 @@ $root.DataList = (function() { * @returns {Object.} JSON object */ DataList.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for DataList @@ -4014,16 +4008,15 @@ $root.DataList = (function() { */ DataList.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/DataList"; - }; - - return DataList; -})(); + return `${typeUrlPrefix}/DataList` + } -$root.OrderLists = (function() { + return DataList +})() +$root.OrderLists = (function () { /** * Properties of an OrderLists. * @exports IOrderLists @@ -4041,12 +4034,12 @@ $root.OrderLists = (function() { * @param {IOrderLists=} [properties] Properties to set */ function OrderLists(properties) { - this.statnames = []; - this.orderLists = []; + this.statnames = [] + this.orderLists = [] if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -4055,7 +4048,7 @@ $root.OrderLists = (function() { * @memberof OrderLists * @instance */ - OrderLists.prototype.statnames = $util.emptyArray; + OrderLists.prototype.statnames = $util.emptyArray /** * OrderLists orderLists. @@ -4063,7 +4056,7 @@ $root.OrderLists = (function() { * @memberof OrderLists * @instance */ - OrderLists.prototype.orderLists = $util.emptyArray; + OrderLists.prototype.orderLists = $util.emptyArray /** * Creates a new OrderLists instance using the specified properties. @@ -4074,8 +4067,8 @@ $root.OrderLists = (function() { * @returns {OrderLists} OrderLists instance */ OrderLists.create = function create(properties) { - return new OrderLists(properties); - }; + return new OrderLists(properties) + } /** * Encodes the specified OrderLists message. Does not implicitly {@link OrderLists.verify|verify} messages. @@ -4088,15 +4081,15 @@ $root.OrderLists = (function() { */ OrderLists.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.statnames != null && message.statnames.length) + writer = $Writer.create() + if (message.statnames?.length) for (var i = 0; i < message.statnames.length; ++i) - writer.uint32(/* id 1, wireType 2 =*/10).string(message.statnames[i]); - if (message.orderLists != null && message.orderLists.length) + writer.uint32(/* id 1, wireType 2 = */10).string(message.statnames[i]) + if (message.orderLists?.length) for (var i = 0; i < message.orderLists.length; ++i) - $root.OrderList.encode(message.orderLists[i], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim(); - return writer; - }; + $root.OrderList.encode(message.orderLists[i], writer.uint32(/* id 2, wireType 2 = */18).fork()).ldelim() + return writer + } /** * Encodes the specified OrderLists message, length delimited. Does not implicitly {@link OrderLists.verify|verify} messages. @@ -4108,8 +4101,8 @@ $root.OrderLists = (function() { * @returns {$protobuf.Writer} Writer */ OrderLists.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes an OrderLists message from the specified reader or buffer. @@ -4124,30 +4117,30 @@ $root.OrderLists = (function() { */ OrderLists.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.OrderLists(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.OrderLists() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - if (!(message.statnames && message.statnames.length)) - message.statnames = []; - message.statnames.push(reader.string()); - break; + case 1: { + if (!(message.statnames?.length)) + message.statnames = [] + message.statnames.push(reader.string()) + break } - case 2: { - if (!(message.orderLists && message.orderLists.length)) - message.orderLists = []; - message.orderLists.push($root.OrderList.decode(reader, reader.uint32())); - break; + case 2: { + if (!(message.orderLists?.length)) + message.orderLists = [] + message.orderLists.push($root.OrderList.decode(reader, reader.uint32())) + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes an OrderLists message from the specified reader or buffer, length delimited. @@ -4161,9 +4154,9 @@ $root.OrderLists = (function() { */ OrderLists.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies an OrderLists message. @@ -4174,26 +4167,26 @@ $root.OrderLists = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ OrderLists.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.statnames != null && message.hasOwnProperty("statnames")) { + if (typeof message !== 'object' || message === null) + return 'object expected' + if (message.statnames != null && message.hasOwnProperty('statnames')) { if (!Array.isArray(message.statnames)) - return "statnames: array expected"; + return 'statnames: array expected' for (var i = 0; i < message.statnames.length; ++i) if (!$util.isString(message.statnames[i])) - return "statnames: string[] expected"; + return 'statnames: string[] expected' } - if (message.orderLists != null && message.hasOwnProperty("orderLists")) { + if (message.orderLists != null && message.hasOwnProperty('orderLists')) { if (!Array.isArray(message.orderLists)) - return "orderLists: array expected"; + return 'orderLists: array expected' for (var i = 0; i < message.orderLists.length; ++i) { - var error = $root.OrderList.verify(message.orderLists[i]); + var error = $root.OrderList.verify(message.orderLists[i]) if (error) - return "orderLists." + error; + return `orderLists.${error}` } } - return null; - }; + return null + } /** * Creates an OrderLists message from a plain object. Also converts values to their respective internal types. @@ -4205,27 +4198,27 @@ $root.OrderLists = (function() { */ OrderLists.fromObject = function fromObject(object) { if (object instanceof $root.OrderLists) - return object; - var message = new $root.OrderLists(); + return object + var message = new $root.OrderLists() if (object.statnames) { if (!Array.isArray(object.statnames)) - throw TypeError(".OrderLists.statnames: array expected"); - message.statnames = []; + throw TypeError('.OrderLists.statnames: array expected') + message.statnames = [] for (var i = 0; i < object.statnames.length; ++i) - message.statnames[i] = String(object.statnames[i]); + message.statnames[i] = String(object.statnames[i]) } if (object.orderLists) { if (!Array.isArray(object.orderLists)) - throw TypeError(".OrderLists.orderLists: array expected"); - message.orderLists = []; + throw TypeError('.OrderLists.orderLists: array expected') + message.orderLists = [] for (var i = 0; i < object.orderLists.length; ++i) { - if (typeof object.orderLists[i] !== "object") - throw TypeError(".OrderLists.orderLists: object expected"); - message.orderLists[i] = $root.OrderList.fromObject(object.orderLists[i]); + if (typeof object.orderLists[i] !== 'object') + throw TypeError('.OrderLists.orderLists: object expected') + message.orderLists[i] = $root.OrderList.fromObject(object.orderLists[i]) } } - return message; - }; + return message + } /** * Creates a plain object from an OrderLists message. Also converts values to other types if specified. @@ -4238,24 +4231,24 @@ $root.OrderLists = (function() { */ OrderLists.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.arrays || options.defaults) { - object.statnames = []; - object.orderLists = []; + object.statnames = [] + object.orderLists = [] } if (message.statnames && message.statnames.length) { - object.statnames = []; + object.statnames = [] for (var j = 0; j < message.statnames.length; ++j) - object.statnames[j] = message.statnames[j]; + object.statnames[j] = message.statnames[j] } if (message.orderLists && message.orderLists.length) { - object.orderLists = []; + object.orderLists = [] for (var j = 0; j < message.orderLists.length; ++j) - object.orderLists[j] = $root.OrderList.toObject(message.orderLists[j], options); + object.orderLists[j] = $root.OrderList.toObject(message.orderLists[j], options) } - return object; - }; + return object + } /** * Converts this OrderLists to JSON. @@ -4265,8 +4258,8 @@ $root.OrderLists = (function() { * @returns {Object.} JSON object */ OrderLists.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for OrderLists @@ -4278,16 +4271,15 @@ $root.OrderLists = (function() { */ OrderLists.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/OrderLists"; - }; - - return OrderLists; -})(); + return `${typeUrlPrefix}/OrderLists` + } -$root.DataLists = (function() { + return OrderLists +})() +$root.DataLists = (function () { /** * Properties of a DataLists. * @exports IDataLists @@ -4305,12 +4297,12 @@ $root.DataLists = (function() { * @param {IDataLists=} [properties] Properties to set */ function DataLists(properties) { - this.statnames = []; - this.dataLists = []; + this.statnames = [] + this.dataLists = [] if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -4319,7 +4311,7 @@ $root.DataLists = (function() { * @memberof DataLists * @instance */ - DataLists.prototype.statnames = $util.emptyArray; + DataLists.prototype.statnames = $util.emptyArray /** * DataLists dataLists. @@ -4327,7 +4319,7 @@ $root.DataLists = (function() { * @memberof DataLists * @instance */ - DataLists.prototype.dataLists = $util.emptyArray; + DataLists.prototype.dataLists = $util.emptyArray /** * Creates a new DataLists instance using the specified properties. @@ -4338,8 +4330,8 @@ $root.DataLists = (function() { * @returns {DataLists} DataLists instance */ DataLists.create = function create(properties) { - return new DataLists(properties); - }; + return new DataLists(properties) + } /** * Encodes the specified DataLists message. Does not implicitly {@link DataLists.verify|verify} messages. @@ -4352,15 +4344,15 @@ $root.DataLists = (function() { */ DataLists.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.statnames != null && message.statnames.length) + writer = $Writer.create() + if (message.statnames?.length) for (var i = 0; i < message.statnames.length; ++i) - writer.uint32(/* id 1, wireType 2 =*/10).string(message.statnames[i]); - if (message.dataLists != null && message.dataLists.length) + writer.uint32(/* id 1, wireType 2 = */10).string(message.statnames[i]) + if (message.dataLists?.length) for (var i = 0; i < message.dataLists.length; ++i) - $root.DataList.encode(message.dataLists[i], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim(); - return writer; - }; + $root.DataList.encode(message.dataLists[i], writer.uint32(/* id 2, wireType 2 = */18).fork()).ldelim() + return writer + } /** * Encodes the specified DataLists message, length delimited. Does not implicitly {@link DataLists.verify|verify} messages. @@ -4372,8 +4364,8 @@ $root.DataLists = (function() { * @returns {$protobuf.Writer} Writer */ DataLists.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes a DataLists message from the specified reader or buffer. @@ -4388,30 +4380,30 @@ $root.DataLists = (function() { */ DataLists.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.DataLists(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.DataLists() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - if (!(message.statnames && message.statnames.length)) - message.statnames = []; - message.statnames.push(reader.string()); - break; + case 1: { + if (!(message.statnames?.length)) + message.statnames = [] + message.statnames.push(reader.string()) + break } - case 2: { - if (!(message.dataLists && message.dataLists.length)) - message.dataLists = []; - message.dataLists.push($root.DataList.decode(reader, reader.uint32())); - break; + case 2: { + if (!(message.dataLists?.length)) + message.dataLists = [] + message.dataLists.push($root.DataList.decode(reader, reader.uint32())) + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes a DataLists message from the specified reader or buffer, length delimited. @@ -4425,9 +4417,9 @@ $root.DataLists = (function() { */ DataLists.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies a DataLists message. @@ -4438,26 +4430,26 @@ $root.DataLists = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ DataLists.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.statnames != null && message.hasOwnProperty("statnames")) { + if (typeof message !== 'object' || message === null) + return 'object expected' + if (message.statnames != null && message.hasOwnProperty('statnames')) { if (!Array.isArray(message.statnames)) - return "statnames: array expected"; + return 'statnames: array expected' for (var i = 0; i < message.statnames.length; ++i) if (!$util.isString(message.statnames[i])) - return "statnames: string[] expected"; + return 'statnames: string[] expected' } - if (message.dataLists != null && message.hasOwnProperty("dataLists")) { + if (message.dataLists != null && message.hasOwnProperty('dataLists')) { if (!Array.isArray(message.dataLists)) - return "dataLists: array expected"; + return 'dataLists: array expected' for (var i = 0; i < message.dataLists.length; ++i) { - var error = $root.DataList.verify(message.dataLists[i]); + var error = $root.DataList.verify(message.dataLists[i]) if (error) - return "dataLists." + error; + return `dataLists.${error}` } } - return null; - }; + return null + } /** * Creates a DataLists message from a plain object. Also converts values to their respective internal types. @@ -4469,27 +4461,27 @@ $root.DataLists = (function() { */ DataLists.fromObject = function fromObject(object) { if (object instanceof $root.DataLists) - return object; - var message = new $root.DataLists(); + return object + var message = new $root.DataLists() if (object.statnames) { if (!Array.isArray(object.statnames)) - throw TypeError(".DataLists.statnames: array expected"); - message.statnames = []; + throw TypeError('.DataLists.statnames: array expected') + message.statnames = [] for (var i = 0; i < object.statnames.length; ++i) - message.statnames[i] = String(object.statnames[i]); + message.statnames[i] = String(object.statnames[i]) } if (object.dataLists) { if (!Array.isArray(object.dataLists)) - throw TypeError(".DataLists.dataLists: array expected"); - message.dataLists = []; + throw TypeError('.DataLists.dataLists: array expected') + message.dataLists = [] for (var i = 0; i < object.dataLists.length; ++i) { - if (typeof object.dataLists[i] !== "object") - throw TypeError(".DataLists.dataLists: object expected"); - message.dataLists[i] = $root.DataList.fromObject(object.dataLists[i]); + if (typeof object.dataLists[i] !== 'object') + throw TypeError('.DataLists.dataLists: object expected') + message.dataLists[i] = $root.DataList.fromObject(object.dataLists[i]) } } - return message; - }; + return message + } /** * Creates a plain object from a DataLists message. Also converts values to other types if specified. @@ -4502,24 +4494,24 @@ $root.DataLists = (function() { */ DataLists.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.arrays || options.defaults) { - object.statnames = []; - object.dataLists = []; + object.statnames = [] + object.dataLists = [] } if (message.statnames && message.statnames.length) { - object.statnames = []; + object.statnames = [] for (var j = 0; j < message.statnames.length; ++j) - object.statnames[j] = message.statnames[j]; + object.statnames[j] = message.statnames[j] } if (message.dataLists && message.dataLists.length) { - object.dataLists = []; + object.dataLists = [] for (var j = 0; j < message.dataLists.length; ++j) - object.dataLists[j] = $root.DataList.toObject(message.dataLists[j], options); + object.dataLists[j] = $root.DataList.toObject(message.dataLists[j], options) } - return object; - }; + return object + } /** * Converts this DataLists to JSON. @@ -4529,8 +4521,8 @@ $root.DataLists = (function() { * @returns {Object.} JSON object */ DataLists.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for DataLists @@ -4542,16 +4534,15 @@ $root.DataLists = (function() { */ DataLists.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/DataLists"; - }; - - return DataLists; -})(); + return `${typeUrlPrefix}/DataLists` + } -$root.AllStats = (function() { + return DataLists +})() +$root.AllStats = (function () { /** * Properties of an AllStats. * @exports IAllStats @@ -4568,11 +4559,11 @@ $root.AllStats = (function() { * @param {IAllStats=} [properties] Properties to set */ function AllStats(properties) { - this.stats = []; + this.stats = [] if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -4581,7 +4572,7 @@ $root.AllStats = (function() { * @memberof AllStats * @instance */ - AllStats.prototype.stats = $util.emptyArray; + AllStats.prototype.stats = $util.emptyArray /** * Creates a new AllStats instance using the specified properties. @@ -4592,8 +4583,8 @@ $root.AllStats = (function() { * @returns {AllStats} AllStats instance */ AllStats.create = function create(properties) { - return new AllStats(properties); - }; + return new AllStats(properties) + } /** * Encodes the specified AllStats message. Does not implicitly {@link AllStats.verify|verify} messages. @@ -4606,15 +4597,15 @@ $root.AllStats = (function() { */ AllStats.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.stats != null && message.stats.length) { - writer.uint32(/* id 1, wireType 2 =*/10).fork(); + writer = $Writer.create() + if (message.stats?.length) { + writer.uint32(/* id 1, wireType 2 = */10).fork() for (var i = 0; i < message.stats.length; ++i) - writer.float(message.stats[i]); - writer.ldelim(); + writer.float(message.stats[i]) + writer.ldelim() } - return writer; - }; + return writer + } /** * Encodes the specified AllStats message, length delimited. Does not implicitly {@link AllStats.verify|verify} messages. @@ -4626,8 +4617,8 @@ $root.AllStats = (function() { * @returns {$protobuf.Writer} Writer */ AllStats.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes an AllStats message from the specified reader or buffer. @@ -4642,29 +4633,30 @@ $root.AllStats = (function() { */ AllStats.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.AllStats(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.AllStats() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - if (!(message.stats && message.stats.length)) - message.stats = []; + case 1: { + if (!(message.stats?.length)) + message.stats = [] if ((tag & 7) === 2) { - var end2 = reader.uint32() + reader.pos; + var end2 = reader.uint32() + reader.pos while (reader.pos < end2) - message.stats.push(reader.float()); - } else - message.stats.push(reader.float()); - break; + message.stats.push(reader.float()) + } + else + message.stats.push(reader.float()) + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes an AllStats message from the specified reader or buffer, length delimited. @@ -4678,9 +4670,9 @@ $root.AllStats = (function() { */ AllStats.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies an AllStats message. @@ -4691,17 +4683,17 @@ $root.AllStats = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ AllStats.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.stats != null && message.hasOwnProperty("stats")) { + if (typeof message !== 'object' || message === null) + return 'object expected' + if (message.stats != null && message.hasOwnProperty('stats')) { if (!Array.isArray(message.stats)) - return "stats: array expected"; + return 'stats: array expected' for (var i = 0; i < message.stats.length; ++i) - if (typeof message.stats[i] !== "number") - return "stats: number[] expected"; + if (typeof message.stats[i] !== 'number') + return 'stats: number[] expected' } - return null; - }; + return null + } /** * Creates an AllStats message from a plain object. Also converts values to their respective internal types. @@ -4713,17 +4705,17 @@ $root.AllStats = (function() { */ AllStats.fromObject = function fromObject(object) { if (object instanceof $root.AllStats) - return object; - var message = new $root.AllStats(); + return object + var message = new $root.AllStats() if (object.stats) { if (!Array.isArray(object.stats)) - throw TypeError(".AllStats.stats: array expected"); - message.stats = []; + throw TypeError('.AllStats.stats: array expected') + message.stats = [] for (var i = 0; i < object.stats.length; ++i) - message.stats[i] = Number(object.stats[i]); + message.stats[i] = Number(object.stats[i]) } - return message; - }; + return message + } /** * Creates a plain object from an AllStats message. Also converts values to other types if specified. @@ -4736,17 +4728,17 @@ $root.AllStats = (function() { */ AllStats.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.arrays || options.defaults) - object.stats = []; + object.stats = [] if (message.stats && message.stats.length) { - object.stats = []; + object.stats = [] for (var j = 0; j < message.stats.length; ++j) - object.stats[j] = options.json && !isFinite(message.stats[j]) ? String(message.stats[j]) : message.stats[j]; + object.stats[j] = options.json && !isFinite(message.stats[j]) ? String(message.stats[j]) : message.stats[j] } - return object; - }; + return object + } /** * Converts this AllStats to JSON. @@ -4756,8 +4748,8 @@ $root.AllStats = (function() { * @returns {Object.} JSON object */ AllStats.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for AllStats @@ -4769,16 +4761,15 @@ $root.AllStats = (function() { */ AllStats.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/AllStats"; - }; - - return AllStats; -})(); + return `${typeUrlPrefix}/AllStats` + } -$root.ConsolidatedShapes = (function() { + return AllStats +})() +$root.ConsolidatedShapes = (function () { /** * Properties of a ConsolidatedShapes. * @exports IConsolidatedShapes @@ -4796,12 +4787,12 @@ $root.ConsolidatedShapes = (function() { * @param {IConsolidatedShapes=} [properties] Properties to set */ function ConsolidatedShapes(properties) { - this.longnames = []; - this.shapes = []; + this.longnames = [] + this.shapes = [] if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -4810,7 +4801,7 @@ $root.ConsolidatedShapes = (function() { * @memberof ConsolidatedShapes * @instance */ - ConsolidatedShapes.prototype.longnames = $util.emptyArray; + ConsolidatedShapes.prototype.longnames = $util.emptyArray /** * ConsolidatedShapes shapes. @@ -4818,7 +4809,7 @@ $root.ConsolidatedShapes = (function() { * @memberof ConsolidatedShapes * @instance */ - ConsolidatedShapes.prototype.shapes = $util.emptyArray; + ConsolidatedShapes.prototype.shapes = $util.emptyArray /** * Creates a new ConsolidatedShapes instance using the specified properties. @@ -4829,8 +4820,8 @@ $root.ConsolidatedShapes = (function() { * @returns {ConsolidatedShapes} ConsolidatedShapes instance */ ConsolidatedShapes.create = function create(properties) { - return new ConsolidatedShapes(properties); - }; + return new ConsolidatedShapes(properties) + } /** * Encodes the specified ConsolidatedShapes message. Does not implicitly {@link ConsolidatedShapes.verify|verify} messages. @@ -4843,15 +4834,15 @@ $root.ConsolidatedShapes = (function() { */ ConsolidatedShapes.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.longnames != null && message.longnames.length) + writer = $Writer.create() + if (message.longnames?.length) for (var i = 0; i < message.longnames.length; ++i) - writer.uint32(/* id 1, wireType 2 =*/10).string(message.longnames[i]); - if (message.shapes != null && message.shapes.length) + writer.uint32(/* id 1, wireType 2 = */10).string(message.longnames[i]) + if (message.shapes?.length) for (var i = 0; i < message.shapes.length; ++i) - $root.Feature.encode(message.shapes[i], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim(); - return writer; - }; + $root.Feature.encode(message.shapes[i], writer.uint32(/* id 2, wireType 2 = */18).fork()).ldelim() + return writer + } /** * Encodes the specified ConsolidatedShapes message, length delimited. Does not implicitly {@link ConsolidatedShapes.verify|verify} messages. @@ -4863,8 +4854,8 @@ $root.ConsolidatedShapes = (function() { * @returns {$protobuf.Writer} Writer */ ConsolidatedShapes.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes a ConsolidatedShapes message from the specified reader or buffer. @@ -4879,30 +4870,30 @@ $root.ConsolidatedShapes = (function() { */ ConsolidatedShapes.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.ConsolidatedShapes(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.ConsolidatedShapes() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - if (!(message.longnames && message.longnames.length)) - message.longnames = []; - message.longnames.push(reader.string()); - break; + case 1: { + if (!(message.longnames?.length)) + message.longnames = [] + message.longnames.push(reader.string()) + break } - case 2: { - if (!(message.shapes && message.shapes.length)) - message.shapes = []; - message.shapes.push($root.Feature.decode(reader, reader.uint32())); - break; + case 2: { + if (!(message.shapes?.length)) + message.shapes = [] + message.shapes.push($root.Feature.decode(reader, reader.uint32())) + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes a ConsolidatedShapes message from the specified reader or buffer, length delimited. @@ -4916,9 +4907,9 @@ $root.ConsolidatedShapes = (function() { */ ConsolidatedShapes.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies a ConsolidatedShapes message. @@ -4929,26 +4920,26 @@ $root.ConsolidatedShapes = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ ConsolidatedShapes.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.longnames != null && message.hasOwnProperty("longnames")) { + if (typeof message !== 'object' || message === null) + return 'object expected' + if (message.longnames != null && message.hasOwnProperty('longnames')) { if (!Array.isArray(message.longnames)) - return "longnames: array expected"; + return 'longnames: array expected' for (var i = 0; i < message.longnames.length; ++i) if (!$util.isString(message.longnames[i])) - return "longnames: string[] expected"; + return 'longnames: string[] expected' } - if (message.shapes != null && message.hasOwnProperty("shapes")) { + if (message.shapes != null && message.hasOwnProperty('shapes')) { if (!Array.isArray(message.shapes)) - return "shapes: array expected"; + return 'shapes: array expected' for (var i = 0; i < message.shapes.length; ++i) { - var error = $root.Feature.verify(message.shapes[i]); + var error = $root.Feature.verify(message.shapes[i]) if (error) - return "shapes." + error; + return `shapes.${error}` } } - return null; - }; + return null + } /** * Creates a ConsolidatedShapes message from a plain object. Also converts values to their respective internal types. @@ -4960,27 +4951,27 @@ $root.ConsolidatedShapes = (function() { */ ConsolidatedShapes.fromObject = function fromObject(object) { if (object instanceof $root.ConsolidatedShapes) - return object; - var message = new $root.ConsolidatedShapes(); + return object + var message = new $root.ConsolidatedShapes() if (object.longnames) { if (!Array.isArray(object.longnames)) - throw TypeError(".ConsolidatedShapes.longnames: array expected"); - message.longnames = []; + throw TypeError('.ConsolidatedShapes.longnames: array expected') + message.longnames = [] for (var i = 0; i < object.longnames.length; ++i) - message.longnames[i] = String(object.longnames[i]); + message.longnames[i] = String(object.longnames[i]) } if (object.shapes) { if (!Array.isArray(object.shapes)) - throw TypeError(".ConsolidatedShapes.shapes: array expected"); - message.shapes = []; + throw TypeError('.ConsolidatedShapes.shapes: array expected') + message.shapes = [] for (var i = 0; i < object.shapes.length; ++i) { - if (typeof object.shapes[i] !== "object") - throw TypeError(".ConsolidatedShapes.shapes: object expected"); - message.shapes[i] = $root.Feature.fromObject(object.shapes[i]); + if (typeof object.shapes[i] !== 'object') + throw TypeError('.ConsolidatedShapes.shapes: object expected') + message.shapes[i] = $root.Feature.fromObject(object.shapes[i]) } } - return message; - }; + return message + } /** * Creates a plain object from a ConsolidatedShapes message. Also converts values to other types if specified. @@ -4993,24 +4984,24 @@ $root.ConsolidatedShapes = (function() { */ ConsolidatedShapes.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.arrays || options.defaults) { - object.longnames = []; - object.shapes = []; + object.longnames = [] + object.shapes = [] } if (message.longnames && message.longnames.length) { - object.longnames = []; + object.longnames = [] for (var j = 0; j < message.longnames.length; ++j) - object.longnames[j] = message.longnames[j]; + object.longnames[j] = message.longnames[j] } if (message.shapes && message.shapes.length) { - object.shapes = []; + object.shapes = [] for (var j = 0; j < message.shapes.length; ++j) - object.shapes[j] = $root.Feature.toObject(message.shapes[j], options); + object.shapes[j] = $root.Feature.toObject(message.shapes[j], options) } - return object; - }; + return object + } /** * Converts this ConsolidatedShapes to JSON. @@ -5020,8 +5011,8 @@ $root.ConsolidatedShapes = (function() { * @returns {Object.} JSON object */ ConsolidatedShapes.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for ConsolidatedShapes @@ -5033,16 +5024,15 @@ $root.ConsolidatedShapes = (function() { */ ConsolidatedShapes.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/ConsolidatedShapes"; - }; - - return ConsolidatedShapes; -})(); + return `${typeUrlPrefix}/ConsolidatedShapes` + } -$root.ConsolidatedStatistics = (function() { + return ConsolidatedShapes +})() +$root.ConsolidatedStatistics = (function () { /** * Properties of a ConsolidatedStatistics. * @exports IConsolidatedStatistics @@ -5061,13 +5051,13 @@ $root.ConsolidatedStatistics = (function() { * @param {IConsolidatedStatistics=} [properties] Properties to set */ function ConsolidatedStatistics(properties) { - this.longnames = []; - this.shortnames = []; - this.stats = []; + this.longnames = [] + this.shortnames = [] + this.stats = [] if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; + this[keys[i]] = properties[keys[i]] } /** @@ -5076,7 +5066,7 @@ $root.ConsolidatedStatistics = (function() { * @memberof ConsolidatedStatistics * @instance */ - ConsolidatedStatistics.prototype.longnames = $util.emptyArray; + ConsolidatedStatistics.prototype.longnames = $util.emptyArray /** * ConsolidatedStatistics shortnames. @@ -5084,7 +5074,7 @@ $root.ConsolidatedStatistics = (function() { * @memberof ConsolidatedStatistics * @instance */ - ConsolidatedStatistics.prototype.shortnames = $util.emptyArray; + ConsolidatedStatistics.prototype.shortnames = $util.emptyArray /** * ConsolidatedStatistics stats. @@ -5092,7 +5082,7 @@ $root.ConsolidatedStatistics = (function() { * @memberof ConsolidatedStatistics * @instance */ - ConsolidatedStatistics.prototype.stats = $util.emptyArray; + ConsolidatedStatistics.prototype.stats = $util.emptyArray /** * Creates a new ConsolidatedStatistics instance using the specified properties. @@ -5103,8 +5093,8 @@ $root.ConsolidatedStatistics = (function() { * @returns {ConsolidatedStatistics} ConsolidatedStatistics instance */ ConsolidatedStatistics.create = function create(properties) { - return new ConsolidatedStatistics(properties); - }; + return new ConsolidatedStatistics(properties) + } /** * Encodes the specified ConsolidatedStatistics message. Does not implicitly {@link ConsolidatedStatistics.verify|verify} messages. @@ -5117,18 +5107,18 @@ $root.ConsolidatedStatistics = (function() { */ ConsolidatedStatistics.encode = function encode(message, writer) { if (!writer) - writer = $Writer.create(); - if (message.longnames != null && message.longnames.length) + writer = $Writer.create() + if (message.longnames?.length) for (var i = 0; i < message.longnames.length; ++i) - writer.uint32(/* id 1, wireType 2 =*/10).string(message.longnames[i]); - if (message.shortnames != null && message.shortnames.length) + writer.uint32(/* id 1, wireType 2 = */10).string(message.longnames[i]) + if (message.shortnames?.length) for (var i = 0; i < message.shortnames.length; ++i) - writer.uint32(/* id 2, wireType 2 =*/18).string(message.shortnames[i]); - if (message.stats != null && message.stats.length) + writer.uint32(/* id 2, wireType 2 = */18).string(message.shortnames[i]) + if (message.stats?.length) for (var i = 0; i < message.stats.length; ++i) - $root.AllStats.encode(message.stats[i], writer.uint32(/* id 3, wireType 2 =*/26).fork()).ldelim(); - return writer; - }; + $root.AllStats.encode(message.stats[i], writer.uint32(/* id 3, wireType 2 = */26).fork()).ldelim() + return writer + } /** * Encodes the specified ConsolidatedStatistics message, length delimited. Does not implicitly {@link ConsolidatedStatistics.verify|verify} messages. @@ -5140,8 +5130,8 @@ $root.ConsolidatedStatistics = (function() { * @returns {$protobuf.Writer} Writer */ ConsolidatedStatistics.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; + return this.encode(message, writer).ldelim() + } /** * Decodes a ConsolidatedStatistics message from the specified reader or buffer. @@ -5156,36 +5146,36 @@ $root.ConsolidatedStatistics = (function() { */ ConsolidatedStatistics.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.ConsolidatedStatistics(); + reader = $Reader.create(reader) + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.ConsolidatedStatistics() while (reader.pos < end) { - var tag = reader.uint32(); + var tag = reader.uint32() switch (tag >>> 3) { - case 1: { - if (!(message.longnames && message.longnames.length)) - message.longnames = []; - message.longnames.push(reader.string()); - break; + case 1: { + if (!(message.longnames?.length)) + message.longnames = [] + message.longnames.push(reader.string()) + break } - case 2: { - if (!(message.shortnames && message.shortnames.length)) - message.shortnames = []; - message.shortnames.push(reader.string()); - break; + case 2: { + if (!(message.shortnames?.length)) + message.shortnames = [] + message.shortnames.push(reader.string()) + break } - case 3: { - if (!(message.stats && message.stats.length)) - message.stats = []; - message.stats.push($root.AllStats.decode(reader, reader.uint32())); - break; + case 3: { + if (!(message.stats?.length)) + message.stats = [] + message.stats.push($root.AllStats.decode(reader, reader.uint32())) + break } - default: - reader.skipType(tag & 7); - break; + default: + reader.skipType(tag & 7) + break } } - return message; - }; + return message + } /** * Decodes a ConsolidatedStatistics message from the specified reader or buffer, length delimited. @@ -5199,9 +5189,9 @@ $root.ConsolidatedStatistics = (function() { */ ConsolidatedStatistics.decodeDelimited = function decodeDelimited(reader) { if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; + reader = new $Reader(reader) + return this.decode(reader, reader.uint32()) + } /** * Verifies a ConsolidatedStatistics message. @@ -5212,33 +5202,33 @@ $root.ConsolidatedStatistics = (function() { * @returns {string|null} `null` if valid, otherwise the reason why it is not */ ConsolidatedStatistics.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.longnames != null && message.hasOwnProperty("longnames")) { + if (typeof message !== 'object' || message === null) + return 'object expected' + if (message.longnames != null && message.hasOwnProperty('longnames')) { if (!Array.isArray(message.longnames)) - return "longnames: array expected"; + return 'longnames: array expected' for (var i = 0; i < message.longnames.length; ++i) if (!$util.isString(message.longnames[i])) - return "longnames: string[] expected"; + return 'longnames: string[] expected' } - if (message.shortnames != null && message.hasOwnProperty("shortnames")) { + if (message.shortnames != null && message.hasOwnProperty('shortnames')) { if (!Array.isArray(message.shortnames)) - return "shortnames: array expected"; + return 'shortnames: array expected' for (var i = 0; i < message.shortnames.length; ++i) if (!$util.isString(message.shortnames[i])) - return "shortnames: string[] expected"; + return 'shortnames: string[] expected' } - if (message.stats != null && message.hasOwnProperty("stats")) { + if (message.stats != null && message.hasOwnProperty('stats')) { if (!Array.isArray(message.stats)) - return "stats: array expected"; + return 'stats: array expected' for (var i = 0; i < message.stats.length; ++i) { - var error = $root.AllStats.verify(message.stats[i]); + var error = $root.AllStats.verify(message.stats[i]) if (error) - return "stats." + error; + return `stats.${error}` } } - return null; - }; + return null + } /** * Creates a ConsolidatedStatistics message from a plain object. Also converts values to their respective internal types. @@ -5250,34 +5240,34 @@ $root.ConsolidatedStatistics = (function() { */ ConsolidatedStatistics.fromObject = function fromObject(object) { if (object instanceof $root.ConsolidatedStatistics) - return object; - var message = new $root.ConsolidatedStatistics(); + return object + var message = new $root.ConsolidatedStatistics() if (object.longnames) { if (!Array.isArray(object.longnames)) - throw TypeError(".ConsolidatedStatistics.longnames: array expected"); - message.longnames = []; + throw TypeError('.ConsolidatedStatistics.longnames: array expected') + message.longnames = [] for (var i = 0; i < object.longnames.length; ++i) - message.longnames[i] = String(object.longnames[i]); + message.longnames[i] = String(object.longnames[i]) } if (object.shortnames) { if (!Array.isArray(object.shortnames)) - throw TypeError(".ConsolidatedStatistics.shortnames: array expected"); - message.shortnames = []; + throw TypeError('.ConsolidatedStatistics.shortnames: array expected') + message.shortnames = [] for (var i = 0; i < object.shortnames.length; ++i) - message.shortnames[i] = String(object.shortnames[i]); + message.shortnames[i] = String(object.shortnames[i]) } if (object.stats) { if (!Array.isArray(object.stats)) - throw TypeError(".ConsolidatedStatistics.stats: array expected"); - message.stats = []; + throw TypeError('.ConsolidatedStatistics.stats: array expected') + message.stats = [] for (var i = 0; i < object.stats.length; ++i) { - if (typeof object.stats[i] !== "object") - throw TypeError(".ConsolidatedStatistics.stats: object expected"); - message.stats[i] = $root.AllStats.fromObject(object.stats[i]); + if (typeof object.stats[i] !== 'object') + throw TypeError('.ConsolidatedStatistics.stats: object expected') + message.stats[i] = $root.AllStats.fromObject(object.stats[i]) } } - return message; - }; + return message + } /** * Creates a plain object from a ConsolidatedStatistics message. Also converts values to other types if specified. @@ -5290,30 +5280,30 @@ $root.ConsolidatedStatistics = (function() { */ ConsolidatedStatistics.toObject = function toObject(message, options) { if (!options) - options = {}; - var object = {}; + options = {} + var object = {} if (options.arrays || options.defaults) { - object.longnames = []; - object.shortnames = []; - object.stats = []; + object.longnames = [] + object.shortnames = [] + object.stats = [] } if (message.longnames && message.longnames.length) { - object.longnames = []; + object.longnames = [] for (var j = 0; j < message.longnames.length; ++j) - object.longnames[j] = message.longnames[j]; + object.longnames[j] = message.longnames[j] } if (message.shortnames && message.shortnames.length) { - object.shortnames = []; + object.shortnames = [] for (var j = 0; j < message.shortnames.length; ++j) - object.shortnames[j] = message.shortnames[j]; + object.shortnames[j] = message.shortnames[j] } if (message.stats && message.stats.length) { - object.stats = []; + object.stats = [] for (var j = 0; j < message.stats.length; ++j) - object.stats[j] = $root.AllStats.toObject(message.stats[j], options); + object.stats[j] = $root.AllStats.toObject(message.stats[j], options) } - return object; - }; + return object + } /** * Converts this ConsolidatedStatistics to JSON. @@ -5323,8 +5313,8 @@ $root.ConsolidatedStatistics = (function() { * @returns {Object.} JSON object */ ConsolidatedStatistics.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + return this.constructor.toObject(this, $protobuf.util.toJSONOptions) + } /** * Gets the default type url for ConsolidatedStatistics @@ -5336,12 +5326,12 @@ $root.ConsolidatedStatistics = (function() { */ ConsolidatedStatistics.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com"; + typeUrlPrefix = 'type.googleapis.com' } - return typeUrlPrefix + "/ConsolidatedStatistics"; - }; + return `${typeUrlPrefix}/ConsolidatedStatistics` + } - return ConsolidatedStatistics; -})(); + return ConsolidatedStatistics +})() -module.exports = $root; +module.exports = $root diff --git a/react/src/utils/responsive.ts b/react/src/utils/responsive.ts index 7e4a6ab8..b215e99b 100644 --- a/react/src/utils/responsive.ts +++ b/react/src/utils/responsive.ts @@ -1,28 +1,26 @@ - - -export function mobileLayout() { - return window.innerWidth <= 1100; +export function mobileLayout(): boolean { + return window.innerWidth <= 1100 } -export function headerTextClass() { - return "centered_text " + (mobileLayout() ? "headertext_mobile" : "headertext") +export function headerTextClass(): string { + return `centered_text ${mobileLayout() ? 'headertext_mobile' : 'headertext'}` } -export function subHeaderTextClass() { - return "centered_text " + (mobileLayout() ? "subheadertext_mobile" : "subheadertext"); +export function subHeaderTextClass(): string { + return `centered_text ${mobileLayout() ? 'subheadertext_mobile' : 'subheadertext'}` } export function comparisonHeadStyle( - align: React.CSSProperties['textAlign'] = "center" + align: React.CSSProperties['textAlign'] = 'center', ): React.CSSProperties { // bold return { - fontSize: mobileLayout() ? "15px" : "20px", + fontSize: mobileLayout() ? '15px' : '20px', fontWeight: 500, - margin: "0", - padding: "0", + margin: '0', + padding: '0', textAlign: align, - verticalAlign: "bottom", - color: "#000000", + verticalAlign: 'bottom', + color: '#000000', } -} \ No newline at end of file +} diff --git a/react/src/utils/text.ts b/react/src/utils/text.ts index f13feff6..5f0b94d7 100644 --- a/react/src/utils/text.ts +++ b/react/src/utils/text.ts @@ -1,14 +1,14 @@ -import { universe_is_american } from "../universe"; +import { universe_is_american } from '../universe' -const american_to_international = require("..//data/american_to_international.json"); +const american_to_international = require('..//data/american_to_international.json') as Record -export function display_type(universe: string, type: string) { - const american_to_international_reversed = Object.fromEntries(Object.entries(american_to_international).map(([a, b]) => [b, a])); +export function display_type(universe: string, type: string): string { + const american_to_international_reversed = Object.fromEntries(Object.entries(american_to_international).map(([a, b]) => [b, a])) if (type in american_to_international_reversed && universe_is_american(universe)) { - type = american_to_international_reversed[type]; + type = american_to_international_reversed[type] } - if (type.endsWith("y")) { - return type.slice(0, -1) + "ies"; + if (type.endsWith('y')) { + return `${type.slice(0, -1)}ies` } - return type + "s"; -} \ No newline at end of file + return `${type}s` +} diff --git a/react/src/utils/types.ts b/react/src/utils/types.ts index f1253e4b..f82a22c7 100644 --- a/react/src/utils/types.ts +++ b/react/src/utils/types.ts @@ -1 +1 @@ -export type NormalizeProto = { [K in keyof T]-?: Exclude, null> } \ No newline at end of file +export type NormalizeProto = { [K in keyof T]-?: Exclude, null> } diff --git a/react/test/Dockerfile b/react/test/Dockerfile index 95444c1a..0daaedc3 100644 --- a/react/test/Dockerfile +++ b/react/test/Dockerfile @@ -19,6 +19,9 @@ RUN apt-get -y install libc6:i386 RUN apt-get -y install libx11-6:i386 RUN apt-get -y install fluxbox +# needed for downloads directory in TestCafe +RUN apt-get -y install xdg-user-dirs + COPY ./requirements.txt ./ RUN pip3 install --break-system-packages -r requirements.txt diff --git a/react/test/article_test.ts b/react/test/article_test.ts index ac5d2d91..9fd09cc4 100644 --- a/react/test/article_test.ts +++ b/react/test/article_test.ts @@ -1,338 +1,331 @@ -import { Selector } from 'testcafe'; +import { Selector } from 'testcafe' -import { - TARGET, SEARCH_FIELD, getLocation, comparison_page, check_textboxes, check_all_category_boxes, - screencap, download_image -} from './test_utils'; +import { + SEARCH_FIELD, TARGET, check_all_category_boxes, check_textboxes, comparison_page, download_image, + getLocation, screencap, +} from './test_utils' fixture('longer article test') - .page(TARGET + '/article.html?longname=California%2C+USA') - // no local storage - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); + .page(`${TARGET}/article.html?longname=California%2C+USA`) +// no local storage + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) -test('california-article-test', async t => { +test('california-article-test', async (t) => { // screenshot path: images/first_test.png - await screencap(t, "article/california"); -}); - + await screencap(t, 'article/california') +}) -test('neighboring-state-test', async t => { - await t.wait(1000); - await screencap(t, "article/california-with-neighbors"); +test('neighboring-state-test', async (t) => { + await t.wait(1000) + await screencap(t, 'article/california-with-neighbors') await t - .click(Selector('path').withAttribute('class', /tag-Arizona,_USA/)); + .click(Selector('path').withAttribute('class', /tag-Arizona,_USA/)) await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=Arizona%2C+USA'); -}); - - + .eql(`${TARGET}/article.html?longname=Arizona%2C+USA`) +}) fixture('shorter article test') - .page(TARGET + '/article.html?longname=San+Marino+city%2C+California%2C+USA') - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); - + .page(`${TARGET}/article.html?longname=San+Marino+city%2C+California%2C+USA`) + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) -test('san-marino-article-test', async t => { - await screencap(t, "article/san-marino"); -}); +test('san-marino-article-test', async (t) => { + await screencap(t, 'article/san-marino') +}) -test('editable-number', async t => { +test('editable-number', async (t) => { // span with class editable_number - const editableNumber = Selector('span').withAttribute('class', 'editable_number').nth(0); + const editableNumber = Selector('span').withAttribute('class', 'editable_number').nth(0) await t .click(editableNumber) - // select all and delete + // select all and delete .pressKey('ctrl+a') .typeText(editableNumber, '3') - .pressKey('enter'); - await t.expect(editableNumber.innerText).eql('3'); + .pressKey('enter') + await t.expect(editableNumber.innerText).eql('3') await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=Chicago+city%2C+Illinois%2C+USA'); + .eql(`${TARGET}/article.html?longname=Chicago+city%2C+Illinois%2C+USA`) }) -test('lr-buttons', async t => { +test('lr-buttons', async (t) => { // button with a < on it - const prev = Selector('a').withText('<').nth(0); - const next = Selector('a').withText('>').nth(0); - const prev_overall = Selector('a').withText('<').nth(1); - const next_overall = Selector('a').withText('>').nth(1); + const prev = Selector('a').withText('<').nth(0) + const next = Selector('a').withText('>').nth(0) + const prev_overall = Selector('a').withText('<').nth(1) + const next_overall = Selector('a').withText('>').nth(1) await t - .click(prev); + .click(prev) await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=Fortuna+city%2C+California%2C+USA'); + .eql(`${TARGET}/article.html?longname=Fortuna+city%2C+California%2C+USA`) await t - .click(next); + .click(next) await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=San+Marino+city%2C+California%2C+USA'); + .eql(`${TARGET}/article.html?longname=San+Marino+city%2C+California%2C+USA`) await t - .click(next); + .click(next) await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=Lakewood+Park+CDP%2C+Florida%2C+USA'); + .eql(`${TARGET}/article.html?longname=Lakewood+Park+CDP%2C+Florida%2C+USA`) await t .click(prev) await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=San+Marino+city%2C+California%2C+USA'); + .eql(`${TARGET}/article.html?longname=San+Marino+city%2C+California%2C+USA`) - await t.click(prev_overall); + await t.click(prev_overall) await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=Havre+High+School+District%2C+Montana%2C+USA'); - await t.click(next_overall); + .eql(`${TARGET}/article.html?longname=Havre+High+School+District%2C+Montana%2C+USA`) + await t.click(next_overall) await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=San+Marino+city%2C+California%2C+USA'); - await t.click(next_overall); + .eql(`${TARGET}/article.html?longname=San+Marino+city%2C+California%2C+USA`) + await t.click(next_overall) await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=78225%2C+USA'); + .eql(`${TARGET}/article.html?longname=78225%2C+USA`) }) -test('san-marino-2010-health', async t => { - await check_textboxes(t, ['2010 Census', 'Health']); - await screencap(t, "article/san-marino-2010-health"); - -}); +test('san-marino-2010-health', async (t) => { + await check_textboxes(t, ['2010 Census', 'Health']) + await screencap(t, 'article/san-marino-2010-health') +}) -test('uncheck-box-mobile', async t => { +test('uncheck-box-mobile', async (t) => { // Find div with class checkbox-setting containing a label with text "Race" // and a checkbox, then find the checkbox - await t.resizeWindow(400, 800); + await t.resizeWindow(400, 800) // refresh - await t.eval(() => location.reload()); - await t.wait(1000); - await check_textboxes(t, ['Race']); + await t.eval(() => { location.reload() }) + await t.wait(1000) + await check_textboxes(t, ['Race']) - await screencap(t, "article/remove_race_initial_mobile"); + await screencap(t, 'article/remove_race_initial_mobile') // refresh - await t.eval(() => location.reload()); - await screencap(t, "article/remove_race_refresh_mobile"); + await t.eval(() => { location.reload() }) + await screencap(t, 'article/remove_race_refresh_mobile') }) -test('uncheck-box-desktop', async t => { - await t.resizeWindow(1400, 800); +test('uncheck-box-desktop', async (t) => { + await t.resizeWindow(1400, 800) // refresh - await t.eval(() => location.reload()); - await t.wait(1000); - await check_textboxes(t, ['Race']); + await t.eval(() => { location.reload() }) + await t.wait(1000) + await check_textboxes(t, ['Race']) - await screencap(t, "article/remove_race_initial_desktop"); + await screencap(t, 'article/remove_race_initial_desktop') // refresh - await t.eval(() => location.reload()); - await screencap(t, "article/remove_race_refresh_desktop"); + await t.eval(() => { location.reload() }) + await screencap(t, 'article/remove_race_refresh_desktop') }) -test('simple', async t => { - await t.resizeWindow(1400, 800); +test('simple', async (t) => { + await t.resizeWindow(1400, 800) // refresh - await t.eval(() => location.reload()); - await t.wait(1000); - await check_textboxes(t, ['Simple Ordinals']); + await t.eval(() => { location.reload() }) + await t.wait(1000) + await check_textboxes(t, ['Simple Ordinals']) - await screencap(t, "article/simple-ordinals"); + await screencap(t, 'article/simple-ordinals') }) -test('download-article', async t => { - await download_image(t, "article/download-article"); +test('download-article', async (t) => { + await download_image(t, 'article/download-article') }) -test('create-comparison-from-article', async t => { - const otherRegion = Selector('input').withAttribute('placeholder', 'Other region...'); +test('create-comparison-from-article', async (t) => { + const otherRegion = Selector('input').withAttribute('placeholder', 'Other region...') await t .click(otherRegion) - .typeText(otherRegion, "pasadena city california") - .pressKey('enter'); + .typeText(otherRegion, 'pasadena city california') + .pressKey('enter') await t.expect(getLocation()) - .eql(comparison_page(["San Marino city, California, USA", "Pasadena city, California, USA"])); + .eql(comparison_page(['San Marino city, California, USA', 'Pasadena city, California, USA'])) }) fixture('article universe selector test') - .page(TARGET + '/article.html?longname=San+Marino+city%2C+California%2C+USA') - // no local storage - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); + .page(`${TARGET}/article.html?longname=San+Marino+city%2C+California%2C+USA`) +// no local storage + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) -test('article-universe-selector-test', async t => { +test('article-universe-selector-test', async (t) => { await t - .click(Selector('img').withAttribute('class', 'universe-selector')); - await screencap(t, "article-dropped-down-universe-selector"); + .click(Selector('img').withAttribute('class', 'universe-selector')) + await screencap(t, 'article-dropped-down-universe-selector') await t .click( Selector('img') .withAttribute('class', 'universe-selector-option') - .withAttribute('alt', 'California, USA')); + .withAttribute('alt', 'California, USA')) await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=San+Marino+city%2C+California%2C+USA&universe=California%2C+USA'); -}); + .eql(`${TARGET}/article.html?longname=San+Marino+city%2C+California%2C+USA&universe=California%2C+USA`) +}) fixture('article universe selector test international') - .page(TARGET + '/article.html?longname=Delhi+%5BNew+Delhi%5D+Urban+Center%2C+India') - // no local storage - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); + .page(`${TARGET}/article.html?longname=Delhi+%5BNew+Delhi%5D+Urban+Center%2C+India`) +// no local storage + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) -test('article-universe-selector-test', async t => { +test('article-universe-selector-test', async (t) => { await t - .click(Selector('img').withAttribute('class', 'universe-selector')); - await screencap(t, "article-dropped-down-universe-selector-international"); + .click(Selector('img').withAttribute('class', 'universe-selector')) + await screencap(t, 'article-dropped-down-universe-selector-international') await t .click( Selector('img') .withAttribute('class', 'universe-selector-option') - .withAttribute('alt', 'India')); + .withAttribute('alt', 'India')) await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=Delhi+%5BNew+Delhi%5D+Urban+Center%2C+India&universe=India'); - await screencap(t, "article/delhi-india"); -}); + .eql(`${TARGET}/article.html?longname=Delhi+%5BNew+Delhi%5D+Urban+Center%2C+India&universe=India`) + await screencap(t, 'article/delhi-india') +}) fixture('article universe navigation test') - .page(TARGET + '/article.html?longname=San+Marino+city%2C+California%2C+USA&universe=California%2C+USA') - // no local storage - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); + .page(`${TARGET}/article.html?longname=San+Marino+city%2C+California%2C+USA&universe=California%2C+USA`) +// no local storage + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) -test('article-universe-right-arrow', async t => { +test('article-universe-right-arrow', async (t) => { // click right population arrow await t - .click(Selector('a').withText('>')); + .click(Selector('a').withText('>')) await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=Camp+Pendleton+South+CDP%2C+California%2C+USA&universe=California%2C+USA'); -}); + .eql(`${TARGET}/article.html?longname=Camp+Pendleton+South+CDP%2C+California%2C+USA&universe=California%2C+USA`) +}) -test("article-universe-ordinal", async t => { +test('article-universe-ordinal', async (t) => { // click the ordinal for the universe - const editableNumber = Selector('span').withAttribute('class', 'editable_number').nth(0); + const editableNumber = Selector('span').withAttribute('class', 'editable_number').nth(0) await t .click(editableNumber) - // select all and delete + // select all and delete .pressKey('ctrl+a') .typeText(editableNumber, '3') - .pressKey('enter'); + .pressKey('enter') await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=San+Jose+city%2C+California%2C+USA&universe=California%2C+USA'); -}); - + .eql(`${TARGET}/article.html?longname=San+Jose+city%2C+California%2C+USA&universe=California%2C+USA`) +}) -test("article-universe-statistic-page", async t => { +test('article-universe-statistic-page', async (t) => { // click the link for Area await t - .click(Selector('a').withText(/^Area$/)); + .click(Selector('a').withText(/^Area$/)) await t.expect(getLocation()) - .eql(TARGET + '/statistic.html?statname=Area&article_type=City&start=801&amount=20&universe=California%2C+USA'); - await screencap(t, "statistics/universe-statistic-page"); -}); + .eql(`${TARGET}/statistic.html?statname=Area&article_type=City&start=801&amount=20&universe=California%2C+USA`) + await screencap(t, 'statistics/universe-statistic-page') +}) -test("article-universe-related-button", async t => { +test('article-universe-related-button', async (t) => { await t - .click(Selector('a').withText('Los Angeles County')); + .click(Selector('a').withText('Los Angeles County')) await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=Los+Angeles+County%2C+California%2C+USA&universe=California%2C+USA'); -}); + .eql(`${TARGET}/article.html?longname=Los+Angeles+County%2C+California%2C+USA&universe=California%2C+USA`) +}) -test("article-universe-search", async t => { +test('article-universe-search', async (t) => { await t .click(SEARCH_FIELD) - .typeText(SEARCH_FIELD, "Chino"); + .typeText(SEARCH_FIELD, 'Chino') await t - .pressKey('enter'); + .pressKey('enter') await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=Chino+city%2C+California%2C+USA&universe=California%2C+USA'); -}); + .eql(`${TARGET}/article.html?longname=Chino+city%2C+California%2C+USA&universe=California%2C+USA`) +}) -test("article-universe-compare", async t => { +test('article-universe-compare', async (t) => { // compare to San Francisco await t .click(Selector('input').withAttribute('placeholder', 'Other region...')) - .typeText(Selector('input').withAttribute('placeholder', 'Other region...'), "San Francisco city california") - .pressKey('enter'); + .typeText(Selector('input').withAttribute('placeholder', 'Other region...'), 'San Francisco city california') + .pressKey('enter') await t.expect(getLocation()) .eql( - TARGET + '/comparison.html?longnames=%5B%22San+Marino+city%2C+California%2C+USA%22%2C%22San+Francisco+city%2C+California%2C+USA%22%5D&universe=California%2C+USA' - ); - await screencap(t, "comparison/universe-compare"); -}); + `${TARGET}/comparison.html?longnames=%5B%22San+Marino+city%2C+California%2C+USA%22%2C%22San+Francisco+city%2C+California%2C+USA%22%5D&universe=California%2C+USA`, + ) + await screencap(t, 'comparison/universe-compare') +}) -test("article-universe-compare-different", async t => { +test('article-universe-compare-different', async (t) => { // compare to Chicago await t .click(Selector('input').withAttribute('placeholder', 'Other region...')) - .typeText(Selector('input').withAttribute('placeholder', 'Other region...'), "Chicago city illinois") - .pressKey('enter'); + .typeText(Selector('input').withAttribute('placeholder', 'Other region...'), 'Chicago city illinois') + .pressKey('enter') await t.expect(getLocation()) .eql( - TARGET + '/comparison.html?longnames=%5B%22San+Marino+city%2C+California%2C+USA%22%2C%22Chicago+city%2C+Illinois%2C+USA%22%5D' - ); - await screencap(t, "comparison/universe-compare-different"); -}); + `${TARGET}/comparison.html?longnames=%5B%22San+Marino+city%2C+California%2C+USA%22%2C%22Chicago+city%2C+Illinois%2C+USA%22%5D`, + ) + await screencap(t, 'comparison/universe-compare-different') +}) fixture('article universe state test') - .page(TARGET + '/article.html?longname=California%2C+USA') - // no local storage - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); + .page(`${TARGET}/article.html?longname=California%2C+USA`) +// no local storage + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) -test("article-universe-state-world", async t => { +test('article-universe-state-world', async (t) => { // go to the world await t - .click(Selector('img').withAttribute('class', 'universe-selector')); + .click(Selector('img').withAttribute('class', 'universe-selector')) await t .click( Selector('img') .withAttribute('class', 'universe-selector-option') - .withAttribute('alt', 'world')); + .withAttribute('alt', 'world')) await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=California%2C+USA&universe=world'); + .eql(`${TARGET}/article.html?longname=California%2C+USA&universe=world`) // screenshot - await screencap(t, "article/california-world"); -}); + await screencap(t, 'article/california-world') +}) fixture('article universe state from subnational test') - .page(TARGET + '/article.html?longname=Kerala%2C+India') - // no local storage - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); - - -test("article-universe-state-from-subnational", async t => { - await screencap(t, "article/kerala-india"); + .page(`${TARGET}/article.html?longname=Kerala%2C+India`) +// no local storage + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) + +test('article-universe-state-from-subnational', async (t) => { + await screencap(t, 'article/kerala-india') // click the > button await t - .click(Selector('a').withText('>')); + .click(Selector('a').withText('>')) await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=California%2C+USA&universe=world'); - await screencap(t, "article/california-world-from-kerala"); -}); + .eql(`${TARGET}/article.html?longname=California%2C+USA&universe=world`) + await screencap(t, 'article/california-world-from-kerala') +}) fixture('all stats test') - .page(TARGET + '/article.html?longname=California%2C+USA') - // no local storage - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); - -test('california-all-stats', async t => { - await t.resizeWindow(1400, 800); - await t.eval(() => location.reload()); - await check_all_category_boxes(t); - await screencap(t, "article/california-all-stats"); -}); + .page(`${TARGET}/article.html?longname=California%2C+USA`) +// no local storage + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) + +test('california-all-stats', async (t) => { + await t.resizeWindow(1400, 800) + await t.eval(() => { location.reload() }) + await check_all_category_boxes(t) + await screencap(t, 'article/california-all-stats') +}) // selected because the gz changed in statistic classes fixture('all stats test regression') - .page(TARGET + '/article.html?longname=Charlotte%2C+Maine%2C+USA') - // no local storage - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); - -test('charlotte-all-stats', async t => { - await t.resizeWindow(1400, 800); - await t.eval(() => location.reload()); - await check_all_category_boxes(t); - await screencap(t, "article/charlotte-all-stats"); -}); + .page(`${TARGET}/article.html?longname=Charlotte%2C+Maine%2C+USA`) +// no local storage + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) + +test('charlotte-all-stats', async (t) => { + await t.resizeWindow(1400, 800) + await t.eval(() => { location.reload() }) + await check_all_category_boxes(t) + await screencap(t, 'article/charlotte-all-stats') +}) diff --git a/react/test/ci_proxy.ts b/react/test/ci_proxy.ts index ea7f63e3..5c1ca1ed 100644 --- a/react/test/ci_proxy.ts +++ b/react/test/ci_proxy.ts @@ -1,43 +1,43 @@ /** * On the test CI, we want to have multiple test runners in parallel. - * + * * However, pulling down the densitydb repo to run the tests against each time is expensive. - * - * So, our strategy will be to serve the local files generated by generate_site.py, + * + * So, our strategy will be to serve the local files generated by generate_site.py, * and then any files not generated, we'll proxy the request to densitydb on Github. */ -import express from 'express'; -import proxy from 'express-http-proxy'; +import express from 'express' +import proxy from 'express-http-proxy' /** * If the user is using a branch that also exists on densitydb, we should use it as well. - * + * * Otherwise, use `master` */ -const branch = fetch(`https://github.com/densitydb/densitydb.github.io/tree/${process.env.BRANCH_NAME}`, { method: 'HEAD' }).then(response => { +const branch = fetch(`https://github.com/densitydb/densitydb.github.io/tree/${process.env.BRANCH_NAME}`, { method: 'HEAD' }).then((response) => { switch (response.status) { case 200: - return process.env.BRANCH_NAME!; + return process.env.BRANCH_NAME! case 404: - return 'master'; + return 'master' default: throw new Error(`Unknown response code for branch check: ${response.status}`) } }) -const app = express(); +const app = express() app.use(express.static('test/density-db'), proxy('https://raw.githubusercontent.com', { async proxyReqPathResolver(req) { return `/densitydb/densitydb.github.io/${await branch}${req.path}` }, userResHeaderDecorator(headers, userReq) { - const fileExtension = userReq.path.match(/\.(.+)$/)?.[1] - const mimeType = fileExtension ? { 'html': 'text/html', 'js': 'text/javascript' }[fileExtension] : undefined - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { 'content-security-policy': _, ...filteredHeaders } = headers; + const fileExtension = (/\.(.+)$/.exec(userReq.path))?.[1] + const mimeType = fileExtension ? { html: 'text/html', js: 'text/javascript' }[fileExtension] : undefined + // eslint-disable-next-line @typescript-eslint/no-unused-vars -- We're removing the context-security-policy header via destructuring + const { 'content-security-policy': _, ...filteredHeaders } = headers return { ...filteredHeaders, 'content-type': mimeType ?? headers['content-type'], @@ -45,4 +45,4 @@ app.use(express.static('test/density-db'), proxy('https://raw.githubusercontent. }, })) -app.listen(8000); +app.listen(8000) diff --git a/react/test/comparison_test.ts b/react/test/comparison_test.ts index 144955e1..c2b2cb94 100644 --- a/react/test/comparison_test.ts +++ b/react/test/comparison_test.ts @@ -1,130 +1,127 @@ +import { Selector } from 'testcafe' +import { TARGET, comparison_page, download_image, getLocation, screencap } from './test_utils' -import { Selector } from 'testcafe'; -import { TARGET, getLocation, comparison_page, screencap, download_image } from './test_utils'; - -export const upper_sgv = "Upper San Gabriel Valley CCD [CCD], Los Angeles County, California, USA" -export const pasadena = "Pasadena CCD [CCD], Los Angeles County, California, USA" -export const sw_sgv = "Southwest San Gabriel Valley CCD [CCD], Los Angeles County, California, USA" -export const east_sgv = "East San Gabriel Valley CCD [CCD], Los Angeles County, California, USA" -export const chicago = "Chicago city [CCD], Cook County, Illinois, USA" +export const upper_sgv = 'Upper San Gabriel Valley CCD [CCD], Los Angeles County, California, USA' +export const pasadena = 'Pasadena CCD [CCD], Los Angeles County, California, USA' +export const sw_sgv = 'Southwest San Gabriel Valley CCD [CCD], Los Angeles County, California, USA' +export const east_sgv = 'East San Gabriel Valley CCD [CCD], Los Angeles County, California, USA' +export const chicago = 'Chicago city [CCD], Cook County, Illinois, USA' fixture('comparison test heterogenous') - .page(comparison_page(["San Marino city, California, USA", pasadena, sw_sgv])) - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); - -test('comparison-3-desktop-heterogenous', async t => { - await t.resizeWindow(1400, 800); - await t.eval(() => location.reload()); - await screencap(t, "comparison/heterogenous-comparison-desktop"); + .page(comparison_page(['San Marino city, California, USA', pasadena, sw_sgv])) + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) + +test('comparison-3-desktop-heterogenous', async (t) => { + await t.resizeWindow(1400, 800) + await t.eval(() => { location.reload() }) + await screencap(t, 'comparison/heterogenous-comparison-desktop') }) -test('comparison-3-mobile-heterogenous', async t => { - await t.resizeWindow(400, 800); - await t.eval(() => location.reload()); - await screencap(t, "comparison/heterogenous-comparison-mobile"); +test('comparison-3-mobile-heterogenous', async (t) => { + await t.resizeWindow(400, 800) + await t.eval(() => { location.reload() }) + await screencap(t, 'comparison/heterogenous-comparison-mobile') }) fixture('comparison test homogenous (2)') .page(comparison_page([upper_sgv, sw_sgv])) - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); - -test('comparison-2-mobile', async t => { - await t.resizeWindow(400, 800); - await t.eval(() => location.reload()); - await screencap(t, "comparison/basic-comparison-2-mobile"); + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) + +test('comparison-2-mobile', async (t) => { + await t.resizeWindow(400, 800) + await t.eval(() => { location.reload() }) + await screencap(t, 'comparison/basic-comparison-2-mobile') }) fixture('comparison test homogenous (3)') .page(comparison_page([upper_sgv, pasadena, sw_sgv])) - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); - - -test('comparison-3-desktop', async t => { - await t.resizeWindow(1400, 800); - await t.eval(() => location.reload()); - await screencap(t, "comparison/basic-comparison-desktop"); + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) + +test('comparison-3-desktop', async (t) => { + await t.resizeWindow(1400, 800) + await t.eval(() => { location.reload() }) + await screencap(t, 'comparison/basic-comparison-desktop') }) -test('comparison-3-mobile', async t => { - await t.resizeWindow(400, 800); - await t.eval(() => location.reload()); - await screencap(t, "comparison/basic-comparison-mobile"); +test('comparison-3-mobile', async (t) => { + await t.resizeWindow(400, 800) + await t.eval(() => { location.reload() }) + await screencap(t, 'comparison/basic-comparison-mobile') }) -test('comparison-3-download', async t => { - await t.resizeWindow(1400, 800); - await t.eval(() => location.reload()); - await download_image(t, "comparison/download-comparison"); +test('comparison-3-download', async (t) => { + await t.resizeWindow(1400, 800) + await t.eval(() => { location.reload() }) + await download_image(t, 'comparison/download-comparison') }) -test('comparison-3-add', async t => { - const otherRegion = Selector('input').withAttribute('placeholder', 'Name'); +test('comparison-3-add', async (t) => { + const otherRegion = Selector('input').withAttribute('placeholder', 'Name') await t .click(otherRegion) - .typeText(otherRegion, "san marino city california") - .pressKey('enter'); + .typeText(otherRegion, 'san marino city california') + .pressKey('enter') await t.expect(getLocation()) - .eql(comparison_page([upper_sgv, pasadena, sw_sgv, "San Marino city, California, USA"])); + .eql(comparison_page([upper_sgv, pasadena, sw_sgv, 'San Marino city, California, USA'])) }) -test('comparison-3-remove-first', async t => { - const remove = Selector('div').withAttribute('class', 'serif manipulation-button-delete').nth(0); +test('comparison-3-remove-first', async (t) => { + const remove = Selector('div').withAttribute('class', 'serif manipulation-button-delete').nth(0) await t - .click(remove); + .click(remove) await t.expect(getLocation()) - .eql(comparison_page([pasadena, sw_sgv])); + .eql(comparison_page([pasadena, sw_sgv])) }) -test('comparison-3-remove-second', async t => { - const remove = Selector('div').withAttribute('class', 'serif manipulation-button-delete').nth(1); +test('comparison-3-remove-second', async (t) => { + const remove = Selector('div').withAttribute('class', 'serif manipulation-button-delete').nth(1) await t - .click(remove); + .click(remove) await t.expect(getLocation()) - .eql(comparison_page([upper_sgv, sw_sgv])); + .eql(comparison_page([upper_sgv, sw_sgv])) }) -test('comparison-3-replace-second', async t => { - const replace = Selector('div').withAttribute('class', 'serif manipulation-button-replace').nth(1); +test('comparison-3-replace-second', async (t) => { + const replace = Selector('div').withAttribute('class', 'serif manipulation-button-replace').nth(1) await t - .click(replace); + .click(replace) // already focused on the input - const otherRegion = Selector('input').withAttribute('placeholder', 'Replacement'); + const otherRegion = Selector('input').withAttribute('placeholder', 'Replacement') await t - .typeText(otherRegion, "East San Gabriel Valley") - .pressKey('enter'); + .typeText(otherRegion, 'East San Gabriel Valley') + .pressKey('enter') await t.expect(getLocation()) - .eql(comparison_page([upper_sgv, east_sgv, sw_sgv])); -}); + .eql(comparison_page([upper_sgv, east_sgv, sw_sgv])) +}) -test('comparison-3-editable-number-third', async t => { - const editableNumber = Selector('span').withAttribute('class', 'editable_number').nth(2); +test('comparison-3-editable-number-third', async (t) => { + const editableNumber = Selector('span').withAttribute('class', 'editable_number').nth(2) await t .click(editableNumber) - // select all and delete + // select all and delete .pressKey('ctrl+a') .typeText(editableNumber, '3') - .pressKey('enter'); - await t.expect(editableNumber.innerText).eql('3'); + .pressKey('enter') + await t.expect(editableNumber.innerText).eql('3') await t.expect(getLocation()) - .eql(comparison_page([upper_sgv, pasadena, chicago])); + .eql(comparison_page([upper_sgv, pasadena, chicago])) }) fixture('plotted-across-180') - .page(TARGET + '/comparison.html?longnames=%5B%22England%2C+United+Kingdom%22%2C%22Alaska%2C+USA%22%2C%22Chukotskiy+avtonomnyy+okrug%2C+Russian+Federation%22%5D') - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); - -test('comparison-3-plotted-across-180', async t => { - await t.resizeWindow(1400, 800); - await t.eval(() => location.reload()); - await screencap(t, "comparison/plotted-across-180"); -}); - + .page(`${TARGET}/comparison.html?longnames=%5B%22England%2C+United+Kingdom%22%2C%22Alaska%2C+USA%22%2C%22Chukotskiy+avtonomnyy+okrug%2C+Russian+Federation%22%5D`) + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) + +test('comparison-3-plotted-across-180', async (t) => { + await t.resizeWindow(1400, 800) + await t.eval(() => { location.reload() }) + await screencap(t, 'comparison/plotted-across-180') +}) diff --git a/react/test/histogram_test.ts b/react/test/histogram_test.ts index 9d5d0334..8d986bc5 100644 --- a/react/test/histogram_test.ts +++ b/react/test/histogram_test.ts @@ -1,124 +1,120 @@ +import { Selector } from 'testcafe' -import { Selector } from 'testcafe'; -import { TARGET, check_textboxes, comparison_page, download_histogram, download_image, download_or_check_string, screencap } from './test_utils'; +import { TARGET, check_textboxes, comparison_page, download_histogram, download_image, download_or_check_string, screencap } from './test_utils' -export const upper_sgv = "Upper San Gabriel Valley CCD [CCD], Los Angeles County, California, USA" -export const pasadena = "Pasadena CCD [CCD], Los Angeles County, California, USA" -export const sw_sgv = "Southwest San Gabriel Valley CCD [CCD], Los Angeles County, California, USA" -export const east_sgv = "East San Gabriel Valley CCD [CCD], Los Angeles County, California, USA" -export const chicago = "Chicago city [CCD], Cook County, Illinois, USA" +export const upper_sgv = 'Upper San Gabriel Valley CCD [CCD], Los Angeles County, California, USA' +export const pasadena = 'Pasadena CCD [CCD], Los Angeles County, California, USA' +export const sw_sgv = 'Southwest San Gabriel Valley CCD [CCD], Los Angeles County, California, USA' +export const east_sgv = 'East San Gabriel Valley CCD [CCD], Los Angeles County, California, USA' +export const chicago = 'Chicago city [CCD], Cook County, Illinois, USA' -async function download_or_check_histogram(t: TestController, name: string) { +async function download_or_check_histogram(t: TestController, name: string): Promise { const output = await t.eval(() => { - return document.getElementsByClassName("histogram-svg-panel")[0].innerHTML; - }); - await download_or_check_string(t, output, name); + return document.getElementsByClassName('histogram-svg-panel')[0].innerHTML + }) as string + await download_or_check_string(t, output, name) } fixture('article check and uncheck test') - .page(TARGET + "/article.html?longname=New+York+Urban+Center%2C+USA&universe=world") - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); - -test("histogram-article-check-uncheck", async t => { - await t.resizeWindow(800, 800); - await t.eval(() => location.reload()); + .page(`${TARGET}/article.html?longname=New+York+Urban+Center%2C+USA&universe=world`) + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) + +test('histogram-article-check-uncheck', async (t) => { + await t.resizeWindow(800, 800) + await t.eval(() => { location.reload() }) // count the number of `histogram-svg-panel` elements - await t.expect(Selector('.histogram-svg-panel').count).eql(0); - await t.click(Selector('.expand-toggle')); - await t.expect(Selector('.histogram-svg-panel').count).eql(1); - await t.click(Selector('.expand-toggle')); - await t.expect(Selector('.histogram-svg-panel').count).eql(0); -}); + await t.expect(Selector('.histogram-svg-panel').count).eql(0) + await t.click(Selector('.expand-toggle')) + await t.expect(Selector('.histogram-svg-panel').count).eql(1) + await t.click(Selector('.expand-toggle')) + await t.expect(Selector('.histogram-svg-panel').count).eql(0) +}) fixture('article test') - .page(TARGET + "/article.html?longname=Germany&universe=world") - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); - -test('histogram-basic-article', async t => { - await t.resizeWindow(800, 800); - await t.eval(() => location.reload()); - await t.click(Selector('.expand-toggle')); - await download_or_check_histogram(t, 'histogram-basic-article'); - await screencap(t, "histogram/histogram-basic-article"); -}); - - -test('histogram-basic-article-multi', async t => { - await t.resizeWindow(800, 800); - await t.eval(() => location.reload()); - await check_textboxes(t, ["Other Density Metrics"]); - await t.eval(() => location.reload()); - await t.wait(1000); - Selector('.expand-toggle').count.then(async count => { - for (let i = 0; i < count; i++) { - await t.click(Selector('.expand-toggle').nth(i)); - } - }); - await screencap(t, "histogram/histogram-basic-article-multi"); - await download_image(t, "histogram/histogram-basic-article-multi-screenshot"); - await download_histogram(t, "histogram/histogram-basic-article-multi-histogram-0", 0); - await download_histogram(t, "histogram/histogram-basic-article-multi-histogram-1", 1); -}); + .page(`${TARGET}/article.html?longname=Germany&universe=world`) + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) + +test('histogram-basic-article', async (t) => { + await t.resizeWindow(800, 800) + await t.eval(() => { location.reload() }) + await t.click(Selector('.expand-toggle')) + await download_or_check_histogram(t, 'histogram-basic-article') + await screencap(t, 'histogram/histogram-basic-article') +}) + +test('histogram-basic-article-multi', async (t) => { + await t.resizeWindow(800, 800) + await t.eval(() => { location.reload() }) + await check_textboxes(t, ['Other Density Metrics']) + await t.eval(() => { location.reload() }) + await t.wait(1000) + const count = await Selector('.expand-toggle').count + for (let i = 0; i < count; i++) { + await t.click(Selector('.expand-toggle').nth(i)) + } + await screencap(t, 'histogram/histogram-basic-article-multi') + await download_image(t, 'histogram/histogram-basic-article-multi-screenshot') + await download_histogram(t, 'histogram/histogram-basic-article-multi-histogram-0', 0) + await download_histogram(t, 'histogram/histogram-basic-article-multi-histogram-1', 1) +}) fixture('comparison test heterogenous') - .page(comparison_page(["San Marino city, California, USA", pasadena, sw_sgv])) - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); - -test('histogram-basic-comparison', async t => { - await t.resizeWindow(800, 800); - await t.eval(() => location.reload()); + .page(comparison_page(['San Marino city, California, USA', pasadena, sw_sgv])) + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) + +test('histogram-basic-comparison', async (t) => { + await t.resizeWindow(800, 800) + await t.eval(() => { location.reload() }) // select element with class name `expand-toggle` - await t.click(Selector('.expand-toggle')); - await download_or_check_histogram(t, 'histogram-basic-comparison'); - await screencap(t, "histogram/histogram-basic-comparison"); -}); + await t.click(Selector('.expand-toggle')) + await download_or_check_histogram(t, 'histogram-basic-comparison') + await screencap(t, 'histogram/histogram-basic-comparison') +}) fixture('comparison test heterogenous with nan') - .page(comparison_page(["India", "China", pasadena])) - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); - - -test('histogram-basic-comparison-nan', async t => { - await t.resizeWindow(800, 800); - await t.eval(() => location.reload()); + .page(comparison_page(['India', 'China', pasadena])) + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) + +test('histogram-basic-comparison-nan', async (t) => { + await t.resizeWindow(800, 800) + await t.eval(() => { location.reload() }) // select element with class name `expand-toggle` - await t.click(Selector('.expand-toggle')); - await download_or_check_histogram(t, 'histogram-basic-comparison-nan'); - await screencap(t, "histogram/histogram-basic-comparison-nan"); -}); + await t.click(Selector('.expand-toggle')) + await download_or_check_histogram(t, 'histogram-basic-comparison-nan') + await screencap(t, 'histogram/histogram-basic-comparison-nan') +}) fixture('comparison test heterogenous with nan in the middle') - .page(comparison_page(["India", pasadena, "China"])) - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); - - -test('histogram-basic-comparison-nan-middle', async t => { - await t.resizeWindow(800, 800); - await t.eval(() => location.reload()); + .page(comparison_page(['India', pasadena, 'China'])) + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) + +test('histogram-basic-comparison-nan-middle', async (t) => { + await t.resizeWindow(800, 800) + await t.eval(() => { location.reload() }) // select element with class name `expand-toggle` - await t.click(Selector('.expand-toggle')); - await download_or_check_histogram(t, 'histogram-basic-comparison-nan-middle'); - await screencap(t, "histogram/histogram-basic-comparison-nan-middle"); -}); + await t.click(Selector('.expand-toggle')) + await download_or_check_histogram(t, 'histogram-basic-comparison-nan-middle') + await screencap(t, 'histogram/histogram-basic-comparison-nan-middle') +}) fixture('comparison ordering test') - .page(TARGET + "/comparison.html?longnames=%5B%22USA%22%2C%22United+Kingdom%22%5D") - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); - -test('histogram-ordering', async t => { - await t.click(Selector('.expand-toggle')); - await download_or_check_histogram(t, 'histogram-ordering'); - await screencap(t, "histogram/histogram-ordering"); -}); \ No newline at end of file + .page(`${TARGET}/comparison.html?longnames=%5B%22USA%22%2C%22United+Kingdom%22%5D`) + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) + +test('histogram-ordering', async (t) => { + await t.click(Selector('.expand-toggle')) + await download_or_check_histogram(t, 'histogram-ordering') + await screencap(t, 'histogram/histogram-ordering') +}) diff --git a/react/test/mapper_test.ts b/react/test/mapper_test.ts index 2b8286ad..4b072151 100644 --- a/react/test/mapper_test.ts +++ b/react/test/mapper_test.ts @@ -1,40 +1,41 @@ +import fs from 'fs' -import { Selector } from 'testcafe'; -import { TARGET, download_or_check_string, most_recent_download_path, screencap } from './test_utils'; -import fs from 'fs'; +import { Selector } from 'testcafe' -async function check_geojson(t: TestController, path: string) { +import { TARGET, download_or_check_string, most_recent_download_path, screencap } from './test_utils' + +async function check_geojson(t: TestController, path: string): Promise { // download the geojson by clicking the button - await t.click(Selector('button').withText('Export as GeoJSON')); - await t.wait(3000); - const mrdp = most_recent_download_path(); - const most_recent_download = fs.readFileSync(mrdp, 'utf8'); - await download_or_check_string(t, most_recent_download, path); + await t.click(Selector('button').withText('Export as GeoJSON')) + await t.wait(3000) + const mrdp = most_recent_download_path() + const most_recent_download = fs.readFileSync(mrdp, 'utf8') + await download_or_check_string(t, most_recent_download, path) } fixture('mapping') - .page(TARGET + '/mapper.html?settings=H4sIAAAAAAAAA1WOzQ6CQAyEX8XUeCOGixeO%2BggejSEFy7Kh%2B5PdRSWEd7dLjMHe2plvpjMociqg76d60PYBFVwTJoICOs2JAlQzkMWGSbQOOZIoo22TdjZrafIk0O9UwBODzv4I1e2%2BLAW0jl2oo8RugKitYlrtPObDmbEddgcQIKDxGytrSxjgG2Rwq%2FlAkZJoFk3eL2NDPbF%2BQ27OpBRPUiTIiotnX64j0Iu06uWr8ngSd4OR%2FtNdNJLzAd2YY7skAQAA') - // no local storage - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); + .page(`${TARGET}/mapper.html?settings=H4sIAAAAAAAAA1WOzQ6CQAyEX8XUeCOGixeO%2BggejSEFy7Kh%2B5PdRSWEd7dLjMHe2plvpjMociqg76d60PYBFVwTJoICOs2JAlQzkMWGSbQOOZIoo22TdjZrafIk0O9UwBODzv4I1e2%2BLAW0jl2oo8RugKitYlrtPObDmbEddgcQIKDxGytrSxjgG2Rwq%2FlAkZJoFk3eL2NDPbF%2BQ27OpBRPUiTIiotnX64j0Iu06uWr8ngSd4OR%2FtNdNJLzAd2YY7skAQAA`) +// no local storage + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) -test("state-map", async t => { - await screencap(t, "state-map"); - await check_geojson(t, "state-map-geojson"); +test('state-map', async (t) => { + await screencap(t, 'state-map') + await check_geojson(t, 'state-map-geojson') }) fixture('mapping-more-complex') - .page(TARGET + '/mapper.html?settings=H4sIAAAAAAAAA5WSwW6DMAyGXwV5l3ZqJ8ax10o797DbVCFDDUQLSeSErqjqu88BRpm0wyokhH7bn3%2FbXKEmWzO6ps8%2FlTnBDva2M6GHDVRKB2LYXYEMFpokWKH2JJHOlEFZE2OhdyRVs3S7baC02nLuA4a%2FMjZwRt1F6W0psYpNPOw%2BrmCwjfGLBOjieEHxytSaFoyDdZ3GSEn2DZqaklWWvqbbLM3SdXQz09xjtAETCcexjLwfRgZKtsnqkjwnbi2VE7xR8s1UT2njGLJQciQvE%2F7XFGR5c8nIeMDpcVhjLhbUacgZ8zlXRg5ZkhMXUExaaamqVKmmRtCClH%2BRqpuQF33u7p7Gq9%2BO4o2xdYtJtDKEDNPBW1zG4sIo3Bf0TsyohgmZzsQ%2B%2Fk%2BBOxIhcuR36WVAAQwwqXgq0vjAjy2R0pcsAgr09Lub9a1c6htvmskEzgIAAA%3D%3D') - // no local storage - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); + .page(`${TARGET}/mapper.html?settings=H4sIAAAAAAAAA5WSwW6DMAyGXwV5l3ZqJ8ax10o797DbVCFDDUQLSeSErqjqu88BRpm0wyokhH7bn3%2FbXKEmWzO6ps8%2FlTnBDva2M6GHDVRKB2LYXYEMFpokWKH2JJHOlEFZE2OhdyRVs3S7baC02nLuA4a%2FMjZwRt1F6W0psYpNPOw%2BrmCwjfGLBOjieEHxytSaFoyDdZ3GSEn2DZqaklWWvqbbLM3SdXQz09xjtAETCcexjLwfRgZKtsnqkjwnbi2VE7xR8s1UT2njGLJQciQvE%2F7XFGR5c8nIeMDpcVhjLhbUacgZ8zlXRg5ZkhMXUExaaamqVKmmRtCClH%2BRqpuQF33u7p7Gq9%2BO4o2xdYtJtDKEDNPBW1zG4sIo3Bf0TsyohgmZzsQ%2B%2Fk%2BBOxIhcuR36WVAAQwwqXgq0vjAjy2R0pcsAgr09Lub9a1c6htvmskEzgIAAA%3D%3D`) +// no local storage + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) -test("mapping-more-complex", async t => { - await t.resizeWindow(1400, 800); - await t.eval(() => location.reload()); - await t.wait(5000); - await screencap(t, "mapping-more-complex"); - await check_geojson(t, "mapping-more-complex-geojson"); +test('mapping-more-complex', async (t) => { + await t.resizeWindow(1400, 800) + await t.eval(() => { location.reload() }) + await t.wait(5000) + await screencap(t, 'mapping-more-complex') + await check_geojson(t, 'mapping-more-complex-geojson') }) diff --git a/react/test/quiz_test.ts b/react/test/quiz_test.ts index 8ef333b0..98316068 100644 --- a/react/test/quiz_test.ts +++ b/react/test/quiz_test.ts @@ -1,177 +1,175 @@ -import { Selector, RequestHook } from 'testcafe'; -import { TARGET, screencap } from './test_utils'; -import { exec } from 'child_process'; -import { writeFileSync } from 'fs'; -import { promisify } from 'util'; +import { exec } from 'child_process' +import { writeFileSync } from 'fs' +import { promisify } from 'util' -async function quiz_screencap(t: TestController, name: string) { +import { RequestHook, Selector } from 'testcafe' + +import { TARGET, screencap } from './test_utils' + +async function quiz_screencap(t: TestController, name: string): Promise { await t.eval(() => { - const elem = document.getElementById("quiz-timer"); + const elem = document.getElementById('quiz-timer') if (elem) { - elem.remove(); + elem.remove() } - }); - await t.wait(1000); - await screencap(t, name); + }) + await t.wait(1000) + await screencap(t, name) } export class ProxyPersistent extends RequestHook { - - async onRequest(e: { requestOptions: RequestMockOptions }) { - if (e.requestOptions.hostname == "persistent.urbanstats.org") { - e.requestOptions.hostname = "localhost"; - e.requestOptions.port = 54579; - e.requestOptions.protocol = "http:"; - e.requestOptions.path = e.requestOptions.path.replace("https://persistent.urbanstats.org", "localhost:54579"); - e.requestOptions.host = "localhost:54579"; + override onRequest(e: { requestOptions: RequestMockOptions }): void { + if (e.requestOptions.hostname === 'persistent.urbanstats.org') { + e.requestOptions.hostname = 'localhost' + e.requestOptions.port = 54579 + e.requestOptions.protocol = 'http:' + e.requestOptions.path = e.requestOptions.path.replace('https://persistent.urbanstats.org', 'localhost:54579') + e.requestOptions.host = 'localhost:54579' // console.log(e) } } - // TestCafe complains if we don't have this - // eslint-disable-next-line @typescript-eslint/no-empty-function - async onResponse() {} + // eslint-disable-next-line @typescript-eslint/no-empty-function -- TestCafe complains if we don't have this + override onResponse(): void {} } -async function run_query(query: string) { +async function run_query(query: string): Promise { // dump given query to a string - const command_line = `sqlite3 ../urbanstats-persistent-data/db.sqlite3 "${query}"`; - const result = await promisify(exec)(command_line); - return result.stdout; + const command_line = `sqlite3 ../urbanstats-persistent-data/db.sqlite3 "${query}"` + const result = await promisify(exec)(command_line) + return result.stdout } -function juxtastat_table() { - return run_query("SELECT user, day, corrects from JuxtaStatIndividualStats"); +function juxtastat_table(): Promise { + return run_query('SELECT user, day, corrects from JuxtaStatIndividualStats') } -function retrostat_table() { - return run_query("SELECT user, week, corrects from JuxtaStatIndividualStatsRetrostat"); +function retrostat_table(): Promise { + return run_query('SELECT user, week, corrects from JuxtaStatIndividualStatsRetrostat') } -function quiz_fixture(fix_name: string, url: string, new_localstorage: Record, sql_statements: string) { +function quiz_fixture(fix_name: string, url: string, new_localstorage: Record, sql_statements: string): void { fixture(fix_name) .page(url) - // no local storage - .beforeEach(async t => { + // no local storage + .beforeEach(async (t) => { // create a temporary file - const tempfile = "/tmp/quiz_test_" + Math.floor(Math.random() * 1000000) + ".sql"; + const tempfile = `/tmp/quiz_test_${Math.floor(Math.random() * 1000000)}.sql` // write the sql statements to the temporary file - writeFileSync(tempfile, sql_statements); - await promisify(exec)(`rm -f ../urbanstats-persistent-data/db.sqlite3; cd ../urbanstats-persistent-data; cat ${tempfile} | sqlite3 db.sqlite3; cd -`); - exec("bash ../urbanstats-persistent-data/run_for_test.sh"); - await t.wait(2000); + writeFileSync(tempfile, sql_statements) + await promisify(exec)(`rm -f ../urbanstats-persistent-data/db.sqlite3; cd ../urbanstats-persistent-data; cat ${tempfile} | sqlite3 db.sqlite3; cd -`) + exec('bash ../urbanstats-persistent-data/run_for_test.sh') + await t.wait(2000) await t.eval(() => { localStorage.clear() - for (const k in new_localstorage) { - localStorage.setItem(k, new_localstorage[k]); + for (const k of Object.keys(new_localstorage)) { + localStorage.setItem(k, new_localstorage[k]) } - }, { dependencies: { new_localstorage } }); + }, { dependencies: { new_localstorage } }) }) - .afterEach(async t => { - exec("killall gunicorn"); - await t.wait(1000); + .afterEach(async (t) => { + exec('killall gunicorn') + await t.wait(1000) }) - .requestHooks(new ProxyPersistent()); + .requestHooks(new ProxyPersistent()) } // click the kth button with id quiz-answer-button-$which -function click_button(t: TestController, which: string) { - return t.click(Selector("div").withAttribute("id", "quiz-answer-button-" + which)); +function click_button(t: TestController, which: string): TestControllerPromise { + return t.click(Selector('div').withAttribute('id', `quiz-answer-button-${which}`)) } -async function click_buttons(t: TestController, whichs: string[]) { +async function click_buttons(t: TestController, whichs: string[]): Promise { for (const which of whichs) { - await click_button(t, which); - await t.wait(500); + await click_button(t, which) + await t.wait(500) } - await t.wait(2000); + await t.wait(2000) } -function example_quiz_history(min_quiz: number, max_quiz: number, min_retro?: number, max_retro?: number) { - const quiz_history: Record = {}; +function example_quiz_history(min_quiz: number, max_quiz: number, min_retro?: number, max_retro?: number): Record { + const quiz_history: Record = {} for (let i = min_quiz; i <= max_quiz; i++) { quiz_history[i] = { - "choices": ["A", "A", "A", "A", "A"], - "correct_pattern": [true, true, true, i % 3 == 1, i % 4 == 1] + choices: ['A', 'A', 'A', 'A', 'A'], + correct_pattern: [true, true, true, i % 3 === 1, i % 4 === 1], } } if (min_quiz <= 62 && max_quiz >= 62) { quiz_history[62] = { - "choices": ["A", "A", "A", "A", "A"], - "correct_pattern": [false, false, false, false, false] + choices: ['A', 'A', 'A', 'A', 'A'], + correct_pattern: [false, false, false, false, false], } } if (min_retro && max_retro) { for (let i = min_retro; i <= max_retro; i++) { - quiz_history["W" + i] = { - "choices": ["A", "A", "A", "A", "A"], - "correct_pattern": [true, true, true, i % 3 == 1, i % 4 == 1] + quiz_history[`W${i}`] = { + choices: ['A', 'A', 'A', 'A', 'A'], + correct_pattern: [true, true, true, i % 3 === 1, i % 4 === 1], } } } - return quiz_history; + return quiz_history } - - quiz_fixture( 'quiz clickthrough test on empty background', - TARGET + '/quiz.html?date=99', - { "persistent_id": "000000000000007" }, - "" -); - -test('quiz-clickthrough-test', async t => { - await click_button(t, "a"); - await t.wait(2000); - await quiz_screencap(t, "quiz/clickthrough-1"); - await click_button(t, "b"); - await t.wait(2000); - await quiz_screencap(t, "quiz/clickthrough-2"); - await click_button(t, "a"); - await t.wait(2000); - await quiz_screencap(t, "quiz/clickthrough-3"); - await click_button(t, "b"); - await t.wait(2000); - await quiz_screencap(t, "quiz/clickthrough-4"); - await click_button(t, "a"); - await t.wait(2000); - await t.eval(() => document.getElementById("quiz-timer")!.remove()); - await t.wait(3000); - await quiz_screencap(t, "quiz/clickthrough-5"); - const quiz_history = await t.eval(() => { - return JSON.stringify(JSON.parse(localStorage.getItem("quiz_history")!)); - }); - await t.expect(quiz_history).eql('{"99":{"choices":["A","B","A","B","A"],"correct_pattern":[true,false,true,false,false]}}'); - await t.expect(await juxtastat_table()).eql("7|99|5\n"); -}); + `${TARGET}/quiz.html?date=99`, + { persistent_id: '000000000000007' }, + '', +) + +test('quiz-clickthrough-test', async (t) => { + await click_button(t, 'a') + await t.wait(2000) + await quiz_screencap(t, 'quiz/clickthrough-1') + await click_button(t, 'b') + await t.wait(2000) + await quiz_screencap(t, 'quiz/clickthrough-2') + await click_button(t, 'a') + await t.wait(2000) + await quiz_screencap(t, 'quiz/clickthrough-3') + await click_button(t, 'b') + await t.wait(2000) + await quiz_screencap(t, 'quiz/clickthrough-4') + await click_button(t, 'a') + await t.wait(2000) + await t.eval(() => { document.getElementById('quiz-timer')!.remove() }) + await t.wait(3000) + await quiz_screencap(t, 'quiz/clickthrough-5') + const quiz_history: unknown = await t.eval(() => { + return JSON.stringify(JSON.parse(localStorage.getItem('quiz_history')!)) + }) + await t.expect(quiz_history).eql('{"99":{"choices":["A","B","A","B","A"],"correct_pattern":[true,false,true,false,false]}}') + await t.expect(await juxtastat_table()).eql('7|99|5\n') +}) quiz_fixture( 'report old quiz results too', - TARGET + '/quiz.html?date=99', - { "persistent_id": "000000000000007", "quiz_history": JSON.stringify(example_quiz_history(87, 90)) }, - "" -); - -test('quiz-report-old-results', async t => { - await t.eval(() => location.reload()); - await click_buttons(t, ["a", "a", "a", "a", "a"]); - const quiz_history = await t.eval(() => { - return JSON.parse(localStorage.getItem("quiz_history")!); - }); - const expected_quiz_history = example_quiz_history(87, 90); + `${TARGET}/quiz.html?date=99`, + { persistent_id: '000000000000007', quiz_history: JSON.stringify(example_quiz_history(87, 90)) }, + '', +) + +test('quiz-report-old-results', async (t) => { + await t.eval(() => { location.reload() }) + await click_buttons(t, ['a', 'a', 'a', 'a', 'a']) + const quiz_history: unknown = await t.eval(() => { + return JSON.parse(localStorage.getItem('quiz_history')!) as unknown + }) + const expected_quiz_history = example_quiz_history(87, 90) expected_quiz_history[99] = { - "choices": ["A", "A", "A", "A", "A"], - "correct_pattern": [true, true, true, true, false] + choices: ['A', 'A', 'A', 'A', 'A'], + correct_pattern: [true, true, true, true, false], } - await t.expect(quiz_history).eql(expected_quiz_history); - await t.expect(await juxtastat_table()).eql("7|87|7\n7|88|15\n7|89|23\n7|90|7\n7|99|15\n"); -}); + await t.expect(quiz_history).eql(expected_quiz_history) + await t.expect(await juxtastat_table()).eql('7|87|7\n7|88|15\n7|89|23\n7|90|7\n7|99|15\n') +}) quiz_fixture( 'do not report stale quiz results', - TARGET + '/quiz.html?date=99', - { "persistent_id": "000000000000007", "quiz_history": JSON.stringify(example_quiz_history(87, 92)) }, + `${TARGET}/quiz.html?date=99`, + { persistent_id: '000000000000007', quiz_history: JSON.stringify(example_quiz_history(87, 92)) }, ` CREATE TABLE IF NOT EXISTS JuxtaStatIndividualStats (user integer, day integer, corrects integer, time integer, PRIMARY KEY (user, day)); @@ -179,29 +177,28 @@ quiz_fixture( INSERT INTO JuxtaStatIndividualStats VALUES (7, 88, 0, 0); INSERT INTO JuxtaStatIndividualStats VALUES (7, 89, 0, 0); INSERT INTO JuxtaStatIndividualStats VALUES (7, 90, 0, 0); - ` -); - -test('quiz-do-not-report-stale-results', async t => { - await t.eval(() => location.reload()); - await click_buttons(t, ["a", "a", "a", "a", "a"]); - const quiz_history = await t.eval(() => { - return JSON.parse(localStorage.getItem("quiz_history")!); - }); - const expected_quiz_history = example_quiz_history(87, 92); + `, +) + +test('quiz-do-not-report-stale-results', async (t) => { + await t.eval(() => { location.reload() }) + await click_buttons(t, ['a', 'a', 'a', 'a', 'a']) + const quiz_history: unknown = await t.eval(() => { + return JSON.parse(localStorage.getItem('quiz_history')!) as unknown + }) + const expected_quiz_history = example_quiz_history(87, 92) expected_quiz_history[99] = { - "choices": ["A", "A", "A", "A", "A"], - "correct_pattern": [true, true, true, true, false] + choices: ['A', 'A', 'A', 'A', 'A'], + correct_pattern: [true, true, true, true, false], } - await t.expect(quiz_history).eql(expected_quiz_history); - await t.expect(await juxtastat_table()).eql("7|87|0\n7|88|0\n7|89|0\n7|90|0\n7|91|15\n7|92|7\n7|99|15\n"); -}); - + await t.expect(quiz_history).eql(expected_quiz_history) + await t.expect(await juxtastat_table()).eql('7|87|0\n7|88|0\n7|89|0\n7|90|0\n7|91|15\n7|92|7\n7|99|15\n') +}) quiz_fixture( 'percentage correct test', - TARGET + '/quiz.html?date=99', - { "persistent_id": "000000000000007" }, + `${TARGET}/quiz.html?date=99`, + { persistent_id: '000000000000007' }, ` CREATE TABLE IF NOT EXISTS JuxtaStatIndividualStats (user integer, day integer, corrects integer, time integer, PRIMARY KEY (user, day)); @@ -210,70 +207,70 @@ quiz_fixture( INSERT INTO JuxtastatUserDomain VALUES (7, 'urbanstats.org'); INSERT INTO JuxtastatUserDomain VALUES (8, 'urbanstats.org'); - ` + Array.from(Array(30).keys()).map( - i => `INSERT INTO JuxtaStatIndividualStats VALUES(${i + 30}, 99, 101, 0); INSERT INTO JuxtaStatUserDomain VALUES(${i + 30}, 'urbanstats.org');` - ).join("\n") -); - -test('quiz-percentage-correct', async t => { - await t.eval(() => location.reload()); - await click_buttons(t, ["a", "a", "a", "a", "a"]); - await quiz_screencap(t, "quiz/percentage-correct"); + ${Array.from(Array(30).keys()).map( + i => `INSERT INTO JuxtaStatIndividualStats VALUES(${i + 30}, 99, 101, 0); INSERT INTO JuxtaStatUserDomain VALUES(${i + 30}, 'urbanstats.org');`, + ).join('\n')}`, +) + +test('quiz-percentage-correct', async (t) => { + await t.eval(() => { location.reload() }) + await click_buttons(t, ['a', 'a', 'a', 'a', 'a']) + await quiz_screencap(t, 'quiz/percentage-correct') await t.expect(await juxtastat_table()).eql( - Array.from(Array(30).keys()).map(i => `${i + 30}|99|101`).join("\n") + "\n" + "7|99|15\n" - ); + `${Array.from(Array(30).keys()).map(i => `${i + 30}|99|101`).join('\n')}\n` + `7|99|15\n`, + ) // assert no element with id quiz-audience-statistics - await t.expect(Selector("#quiz-audience-statistics").exists).notOk(); + await t.expect(Selector('#quiz-audience-statistics').exists).notOk() // now become user 8 await t.eval(() => { - localStorage.clear(); - localStorage.setItem("persistent_id", "000000000000008"); - }); - await t.eval(() => location.reload()); - await click_buttons(t, ["a", "a", "a", "a", "a"]); - await quiz_screencap(t, "quiz/percentage-correct-2"); + localStorage.clear() + localStorage.setItem('persistent_id', '000000000000008') + }) + await t.eval(() => { location.reload() }) + await click_buttons(t, ['a', 'a', 'a', 'a', 'a']) + await quiz_screencap(t, 'quiz/percentage-correct-2') await t.expect(await juxtastat_table()).eql( - Array.from(Array(30).keys()).map(i => `${i + 30}|99|101`).join("\n") + "\n" + "7|99|15\n" + "8|99|15\n" - ); + `${Array.from(Array(30).keys()).map(i => `${i + 30}|99|101`).join('\n')}\n` + `7|99|15\n` + `8|99|15\n`, + ) // assert element with id quiz-audience-statistics exists - await t.expect(Selector("#quiz-audience-statistics").exists).ok(); - const stats = await Selector("#quiz-audience-statistics").innerText; - await t.expect(stats).eql('Question Difficulty\n100%\nQ1 Correct\n3%\nQ2 Correct\n100%\nQ3 Correct\n3%\nQ4 Correct\n0%\nQ5 Correct'); -}); + await t.expect(Selector('#quiz-audience-statistics').exists).ok() + const stats = await Selector('#quiz-audience-statistics').innerText + await t.expect(stats).eql('Question Difficulty\n100%\nQ1 Correct\n3%\nQ2 Correct\n100%\nQ3 Correct\n3%\nQ4 Correct\n0%\nQ5 Correct') +}) quiz_fixture( 'new user', - TARGET + '/quiz.html?date=99', + `${TARGET}/quiz.html?date=99`, {}, - "", -); + '', +) -function hex_to_dec(hex: string) { +function hex_to_dec(hex: string): string { // https://stackoverflow.com/a/53751162/1549476 - if (hex.length % 2) { hex = '0' + hex; } + if (hex.length % 2) { hex = `0${hex}` } - const bn = BigInt('0x' + hex); + const bn = BigInt(`0x${hex}`) - const d = bn.toString(10); - return d; + const d = bn.toString(10) + return d } -test('quiz-new-user', async t => { - await click_buttons(t, ["a", "a", "a", "a", "a"]); +test('quiz-new-user', async (t) => { + await click_buttons(t, ['a', 'a', 'a', 'a', 'a']) const user_id = await t.eval(() => { - return localStorage.getItem("persistent_id"); - }); - await t.expect(user_id).notEql(null); - const user_id_int = hex_to_dec(user_id); - const juxta_table = await juxtastat_table(); - await t.expect(juxta_table).eql(`${user_id_int}|99|15\n`); - await t.expect(await run_query("SELECT user from JuxtastatUserDomain")).eql(`${user_id_int}\n`); -}); + return localStorage.getItem('persistent_id') + }) as string | null + await t.expect(user_id).notEql(null) + const user_id_int = hex_to_dec(user_id!) + const juxta_table = await juxtastat_table() + await t.expect(juxta_table).eql(`${user_id_int}|99|15\n`) + await t.expect(await run_query('SELECT user from JuxtastatUserDomain')).eql(`${user_id_int}\n`) +}) quiz_fixture( 'retrostat', - TARGET + '/quiz.html?date=99', - { "persistent_id": "000000000000007", "quiz_history": JSON.stringify(example_quiz_history(87, 93, 27, 33)) }, + `${TARGET}/quiz.html?date=99`, + { persistent_id: '000000000000007', quiz_history: JSON.stringify(example_quiz_history(87, 93, 27, 33)) }, ` CREATE TABLE IF NOT EXISTS JuxtaStatIndividualStats (user integer, day integer, corrects integer, time integer, PRIMARY KEY (user, day)); @@ -283,128 +280,128 @@ quiz_fixture( INSERT INTO JuxtaStatIndividualStats VALUES (7, 90, 0, 0); INSERT INTO JuxtaStatIndividualStatsRetrostat VALUES (7, 30, 0, 0); - ` -); - -test('quiz-retrostat-regular-quiz-reporting', async t => { - await t.eval(() => location.reload()); - await click_buttons(t, ["a", "a", "a", "a", "a"]); - const quiz_history = await t.eval(() => { - return JSON.parse(localStorage.getItem("quiz_history")!); - }); - const expected_quiz_history = example_quiz_history(87, 93, 27, 33); + `, +) + +test('quiz-retrostat-regular-quiz-reporting', async (t) => { + await t.eval(() => { location.reload() }) + await click_buttons(t, ['a', 'a', 'a', 'a', 'a']) + const quiz_history: unknown = await t.eval(() => { + return JSON.parse(localStorage.getItem('quiz_history')!) as unknown + }) + const expected_quiz_history = example_quiz_history(87, 93, 27, 33) expected_quiz_history[99] = { - "choices": ["A", "A", "A", "A", "A"], - "correct_pattern": [true, true, true, true, false] + choices: ['A', 'A', 'A', 'A', 'A'], + correct_pattern: [true, true, true, true, false], } - await t.expect(quiz_history).eql(expected_quiz_history); - await t.expect(await juxtastat_table()).eql("7|90|0\n7|91|15\n7|92|7\n7|93|23\n7|99|15\n"); - await t.expect(await retrostat_table()).eql("7|30|0\n"); + await t.expect(quiz_history).eql(expected_quiz_history) + await t.expect(await juxtastat_table()).eql('7|90|0\n7|91|15\n7|92|7\n7|93|23\n7|99|15\n') + await t.expect(await retrostat_table()).eql('7|30|0\n') }) -test('quiz-retrostat-retrostat-reporting', async t => { - const url = TARGET + '/quiz.html?mode=retro&date=38'; - await t.navigateTo(url); - await t.eval(() => location.reload()); - await click_buttons(t, ["a", "a", "a", "a", "a"]); - const quiz_history = await t.eval(() => { - return JSON.parse(localStorage.getItem("quiz_history")!); - }); - const expected_quiz_history = example_quiz_history(87, 93, 27, 33); - expected_quiz_history["W38"] = { - "choices": ["A", "A", "A", "A", "A"], - "correct_pattern": [false, false, true, false, true] +test('quiz-retrostat-retrostat-reporting', async (t) => { + const url = `${TARGET}/quiz.html?mode=retro&date=38` + await t.navigateTo(url) + await t.eval(() => { location.reload() }) + await click_buttons(t, ['a', 'a', 'a', 'a', 'a']) + const quiz_history: unknown = await t.eval(() => { + return JSON.parse(localStorage.getItem('quiz_history')!) as unknown + }) + const expected_quiz_history = example_quiz_history(87, 93, 27, 33) + expected_quiz_history.W38 = { + choices: ['A', 'A', 'A', 'A', 'A'], + correct_pattern: [false, false, true, false, true], } - await t.expect(quiz_history).eql(expected_quiz_history); - await t.expect(await juxtastat_table()).eql("7|90|0\n"); - await t.expect(await retrostat_table()).eql("7|30|0\n7|31|15\n7|32|7\n7|33|23\n7|38|20\n"); -}); + await t.expect(quiz_history).eql(expected_quiz_history) + await t.expect(await juxtastat_table()).eql('7|90|0\n') + await t.expect(await retrostat_table()).eql('7|30|0\n7|31|15\n7|32|7\n7|33|23\n7|38|20\n') +}) fixture('quiz result test') - .page(TARGET + '/quiz.html?date=100') - // very specific local storage - .beforeEach(async t => { + .page(`${TARGET}/quiz.html?date=100`) +// very specific local storage + .beforeEach(async (t) => { await t.eval(() => { localStorage.clear() - localStorage.setItem("quiz_history", JSON.stringify(example_quiz_history(2, 100))); - }, { dependencies: { example_quiz_history } }); - }); - -async function check_text(t: TestController, words: string, emoji: string) { - const text = await Selector("#quiz-result-summary-words").innerText; - await t.expect(text).eql(words); - const emoji_text = await Selector("#quiz-result-summary-emoji").innerText; - await t.expect(emoji_text).eql(emoji); + localStorage.setItem('quiz_history', JSON.stringify(example_quiz_history(2, 100))) + }, { dependencies: { example_quiz_history } }) + }) + +async function check_text(t: TestController, words: string, emoji: string): Promise { + const text = await Selector('#quiz-result-summary-words').innerText + await t.expect(text).eql(words) + const emoji_text = await Selector('#quiz-result-summary-emoji').innerText + await t.expect(emoji_text).eql(emoji) } -test('quiz-results-test', async t => { - await t.resizeWindow(1400, 800); - await t.eval(() => location.reload()); - await t.wait(1000); - await t.eval(() => location.reload()); - await quiz_screencap(t, "quiz/results-page"); - await check_text(t, "Excellent! 😊 4/5", "🟩🟩🟩🟩🟥"); -}); +test('quiz-results-test', async (t) => { + await t.resizeWindow(1400, 800) + await t.eval(() => { location.reload() }) + await t.wait(1000) + await t.eval(() => { location.reload() }) + await quiz_screencap(t, 'quiz/results-page') + await check_text(t, 'Excellent! 😊 4/5', '🟩🟩🟩🟩🟥') +}) fixture('several quiz results') - .page(TARGET + '/quiz.html?date=90') - // very specific local storage - .beforeEach(async t => { + .page(`${TARGET}/quiz.html?date=90`) +// very specific local storage + .beforeEach(async (t) => { await t.eval(() => { localStorage.clear() - localStorage.setItem("quiz_history", JSON.stringify({ - "90": { - "choices": ["A", "A", "A", "A", "A"], - "correct_pattern": [true, true, true, true, false] + localStorage.setItem('quiz_history', JSON.stringify({ + 90: { + choices: ['A', 'A', 'A', 'A', 'A'], + correct_pattern: [true, true, true, true, false], }, - "91": { - "choices": ["A", "A", "A", "A", "A"], - "correct_pattern": [true, false, true, false, true], + 91: { + choices: ['A', 'A', 'A', 'A', 'A'], + correct_pattern: [true, false, true, false, true], }, - "92": { - "choices": ["A", "A", "A", "A", "A"], - "correct_pattern": [true, true, true, true, true], + 92: { + choices: ['A', 'A', 'A', 'A', 'A'], + correct_pattern: [true, true, true, true, true], }, - "93": { - "choices": ["A", "A", "A", "A", "A"], - "correct_pattern": [false, false, false, false, false], + 93: { + choices: ['A', 'A', 'A', 'A', 'A'], + correct_pattern: [false, false, false, false, false], }, - "94": { - "choices": ["A", "A", "A", "A", "A"], - "correct_pattern": [false, false, false, true, true], + 94: { + choices: ['A', 'A', 'A', 'A', 'A'], + correct_pattern: [false, false, false, true, true], }, - "95": { - "choices": ["A", "A", "A", "A", "A"], - "correct_pattern": [true, true, true, true, false], + 95: { + choices: ['A', 'A', 'A', 'A', 'A'], + correct_pattern: [true, true, true, true, false], }, - })); - }, { dependencies: { example_quiz_history } }); - }); + })) + }, { dependencies: { example_quiz_history } }) + }) -test('several-quiz-results-test', async t => { - await t.eval(() => location.reload()); - await quiz_screencap(t, "quiz/results-page-several"); +test('several-quiz-results-test', async (t) => { + await t.eval(() => { location.reload() }) + await quiz_screencap(t, 'quiz/results-page-several') // true true true true false - await check_text(t, "Excellent! 😊 4/5", "🟩🟩🟩🟩🟥"); + await check_text(t, 'Excellent! 😊 4/5', '🟩🟩🟩🟩🟥') // go to the next quiz via changing the href await t.eval(() => { - document.location.href = "/quiz.html?date=91"; - }); - await check_text(t, "Good! 🙃 3/5", "🟩🟥🟩🟥🟩"); + document.location.href = '/quiz.html?date=91' + }) + await check_text(t, 'Good! 🙃 3/5', '🟩🟥🟩🟥🟩') await t.eval(() => { - document.location.href = "/quiz.html?date=92"; - }); - await check_text(t, "Perfect! 🔥 5/5", "🟩🟩🟩🟩🟩"); + document.location.href = '/quiz.html?date=92' + }) + await check_text(t, 'Perfect! 🔥 5/5', '🟩🟩🟩🟩🟩') await t.eval(() => { - document.location.href = "/quiz.html?date=93"; - }); - await check_text(t, "Impressively Bad Job! 🤷 0/5", "🟥🟥🟥🟥🟥"); + document.location.href = '/quiz.html?date=93' + }) + await check_text(t, 'Impressively Bad Job! 🤷 0/5', '🟥🟥🟥🟥🟥') await t.eval(() => { - document.location.href = "/quiz.html?date=94"; - }); - await check_text(t, "Better luck next time! 🫤 2/5", "🟥🟥🟥🟩🟩"); + document.location.href = '/quiz.html?date=94' + }) + await check_text(t, 'Better luck next time! 🫤 2/5', '🟥🟥🟥🟩🟩') await t.eval(() => { - document.location.href = "/quiz.html?date=95"; - }); - await check_text(t, "Excellent! 😊 4/5", "🟩🟩🟩🟩🟥"); -}); + document.location.href = '/quiz.html?date=95' + }) + await check_text(t, 'Excellent! 😊 4/5', '🟩🟩🟩🟩🟥') +}) diff --git a/react/test/random_test.ts b/react/test/random_test.ts index 24910e25..522c8bdd 100644 --- a/react/test/random_test.ts +++ b/react/test/random_test.ts @@ -1,19 +1,19 @@ -import { TARGET, getLocation } from './test_utils'; +import { TARGET, getLocation } from './test_utils' fixture('random') - .page(TARGET + "/random.html?sampleby=population&us_only=true") - // no local storage - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); + .page(`${TARGET}/random.html?sampleby=population&us_only=true`) +// no local storage + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) -test("random-usa", async t => { +test('random-usa', async (t) => { // wait for load - await t.wait(1000); + await t.wait(1000) // contains article await t.expect(getLocation()) - .contains('/article.html?longname='); + .contains('/article.html?longname=') // location should not include &universe= await t.expect(getLocation()) - .notContains('&universe='); + .notContains('&universe=') }) diff --git a/react/test/search_test.ts b/react/test/search_test.ts index 0ea6c1f4..65afcc94 100644 --- a/react/test/search_test.ts +++ b/react/test/search_test.ts @@ -1,59 +1,58 @@ -import { - TARGET, SEARCH_FIELD, getLocation, screencap -} from './test_utils'; +import { + SEARCH_FIELD, TARGET, getLocation, screencap, +} from './test_utils' fixture('shorter article test') - .page(TARGET + '/article.html?longname=San+Marino+city%2C+California%2C+USA') - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); + .page(`${TARGET}/article.html?longname=San+Marino+city%2C+California%2C+USA`) + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) - -test('search-test', async t => { +test('search-test', async (t) => { await t .click(SEARCH_FIELD) - .typeText(SEARCH_FIELD, "Pasadena"); - await screencap(t, "search/san-marino-search-pasadena"); + .typeText(SEARCH_FIELD, 'Pasadena') + await screencap(t, 'search/san-marino-search-pasadena') await t - .pressKey('enter'); + .pressKey('enter') await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=Pasadena+city%2C+Texas%2C+USA'); -}); + .eql(`${TARGET}/article.html?longname=Pasadena+city%2C+Texas%2C+USA`) +}) -test('search-test-with-extra-char', async t => { +test('search-test-with-extra-char', async (t) => { await t .click(SEARCH_FIELD) - .typeText(SEARCH_FIELD, "Pasadena c"); - await screencap(t, "search/san-marino-search-pasadena-c"); -}); + .typeText(SEARCH_FIELD, 'Pasadena c') + await screencap(t, 'search/san-marino-search-pasadena-c') +}) -test('search-test-with-special-chars', async t => { +test('search-test-with-special-chars', async (t) => { await t .click(SEARCH_FIELD) - .typeText(SEARCH_FIELD, "Utt"); - await screencap(t, "search/san-marino-search-Utt"); -}); + .typeText(SEARCH_FIELD, 'Utt') + await screencap(t, 'search/san-marino-search-Utt') +}) -test('search-test-different-first-char', async t => { +test('search-test-different-first-char', async (t) => { await t .click(SEARCH_FIELD) - .typeText(SEARCH_FIELD, "hina"); - await screencap(t, "search/san-marino-search-hina"); -}); + .typeText(SEARCH_FIELD, 'hina') + await screencap(t, 'search/san-marino-search-hina') +}) -test('search-test-arrows', async t => { +test('search-test-arrows', async (t) => { await t - .click(SEARCH_FIELD); - await t.wait(1000); + .click(SEARCH_FIELD) + await t.wait(1000) await t - .typeText(SEARCH_FIELD, "Pasadena"); - await t.wait(1000); + .typeText(SEARCH_FIELD, 'Pasadena') + await t.wait(1000) await t .pressKey('down') - .pressKey('down'); - await screencap(t, "search/san-marino-search-pasadena-down-down"); + .pressKey('down') + await screencap(t, 'search/san-marino-search-pasadena-down-down') await t - .pressKey('enter'); + .pressKey('enter') await t.expect(getLocation()) - .eql(TARGET + '/article.html?longname=Pasadena+CDP%2C+Maryland%2C+USA'); + .eql(`${TARGET}/article.html?longname=Pasadena+CDP%2C+Maryland%2C+USA`) }) diff --git a/react/test/settings_test.ts b/react/test/settings_test.ts index 49acbde0..ad8fb5a0 100644 --- a/react/test/settings_test.ts +++ b/react/test/settings_test.ts @@ -1,81 +1,81 @@ -import { Selector } from 'testcafe'; +import { Selector } from 'testcafe' import { - TARGET, SEARCH_FIELD, getLocation, check_textboxes, - screencap -} from './test_utils'; + SEARCH_FIELD, TARGET, check_textboxes, getLocation, + screencap, +} from './test_utils' fixture('settings regression test') - .page(TARGET + '/article.html?longname=San+Marino+city%2C+California%2C+USA') - // no local storage - .beforeEach(async t => { + .page(`${TARGET}/article.html?longname=San+Marino+city%2C+California%2C+USA`) +// no local storage + .beforeEach(async (t) => { await t.eval(() => { - localStorage.clear(); - const EG_SETTINGS = '{"related__State__County":true,"related__Native Area__Native Subdivision":true,"related__Native Statistical Area__Native Subdivision":true,"related__CSA__MSA":true,"related__MSA__County":true,"related__County__City":true,"related__CCD__City":true,"related__City__Neighborhood":true,"related__School District__Neighborhood":true,"related__ZIP__Neighborhood":false,"related__Urban Area__City":true,"related__Judicial Circuit__Judicial District":true,"related__County__County":true,"related__MSA__MSA":true,"related__CSA__CSA":true,"related__Urban Area__Urban Area":true,"related__ZIP__ZIP":true,"related__CCD__CCD":true,"related__City__City":false,"related__Neighborhood__Neighborhood":true,"related__Congressional District__Congressional District":true,"related__State House District__State House District":true,"related__State Senate District__State Senate District":true,"related__Historical Congressional District__Historical Congressional District":true,"related__Native Area__Native Area":true,"related__Native Statistical Area__Native Statistical Area":true,"related__Native Subdivision__Native Subdivision":true,"related__School District__School District":true,"related__Judicial District__Judicial District":true,"related__Judicial Circuit__Judicial Circuit":true,"related__County Cross CD__County Cross CD":true,"related__USDA County Type__USDA County Type":true,"related__Hospital Referral Region__Hospital Referral Region":true,"related__Hospital Service Area__Hospital Service Area":true,"related__Media Market__Media Market":true,"related__Continent__Continent":true,"related__Country__Country":true,"related__Subnational Region__Subnational Region":true,"related__Urban Center__Urban Center":true,"related__State__State":true,"related__5M Person Circle__5M Person Circle":true,"related__US 5M Person Circle__US 5M Person Circle":true,"related__10M Person Circle__10M Person Circle":true,"related__US 10M Person Circle__US 10M Person Circle":true,"related__20M Person Circle__20M Person Circle":true,"related__US 20M Person Circle__US 20M Person Circle":true,"related__50M Person Circle__50M Person Circle":true,"related__US 50M Person Circle__US 50M Person Circle":true,"related__100M Person Circle__100M Person Circle":true,"related__US 100M Person Circle__US 100M Person Circle":true,"related__200M Person Circle__200M Person Circle":true,"related__US 200M Person Circle__US 200M Person Circle":true,"related__500M Person Circle__500M Person Circle":true,"related__US 500M Person Circle__US 500M Person Circle":true,"related__1B Person Circle__1B Person Circle":true,"related__US 1B Person Circle__US 1B Person Circle":true,"show_statistic_main":true,"show_statistic_race":false,"show_statistic_national_origin":false,"show_statistic_education":false,"show_statistic_generation":false,"show_statistic_income":false,"show_statistic_housing":false,"show_statistic_transportation":false,"show_statistic_health":false,"show_statistic_climate":true,"show_statistic_industry":false,"show_statistic_occupation":false,"show_statistic_election":false,"show_statistic_feature":false,"show_statistic_weather":false,"show_statistic_misc":false,"show_statistic_other_densities":true,"show_statistic_2010":false,"use_imperial":true,"simple_ordinals":true,"related__ZIP__City":true}'; - localStorage.setItem('settings', EG_SETTINGS); - location.reload(); - }); - }); + localStorage.clear() + const EG_SETTINGS = '{"related__State__County":true,"related__Native Area__Native Subdivision":true,"related__Native Statistical Area__Native Subdivision":true,"related__CSA__MSA":true,"related__MSA__County":true,"related__County__City":true,"related__CCD__City":true,"related__City__Neighborhood":true,"related__School District__Neighborhood":true,"related__ZIP__Neighborhood":false,"related__Urban Area__City":true,"related__Judicial Circuit__Judicial District":true,"related__County__County":true,"related__MSA__MSA":true,"related__CSA__CSA":true,"related__Urban Area__Urban Area":true,"related__ZIP__ZIP":true,"related__CCD__CCD":true,"related__City__City":false,"related__Neighborhood__Neighborhood":true,"related__Congressional District__Congressional District":true,"related__State House District__State House District":true,"related__State Senate District__State Senate District":true,"related__Historical Congressional District__Historical Congressional District":true,"related__Native Area__Native Area":true,"related__Native Statistical Area__Native Statistical Area":true,"related__Native Subdivision__Native Subdivision":true,"related__School District__School District":true,"related__Judicial District__Judicial District":true,"related__Judicial Circuit__Judicial Circuit":true,"related__County Cross CD__County Cross CD":true,"related__USDA County Type__USDA County Type":true,"related__Hospital Referral Region__Hospital Referral Region":true,"related__Hospital Service Area__Hospital Service Area":true,"related__Media Market__Media Market":true,"related__Continent__Continent":true,"related__Country__Country":true,"related__Subnational Region__Subnational Region":true,"related__Urban Center__Urban Center":true,"related__State__State":true,"related__5M Person Circle__5M Person Circle":true,"related__US 5M Person Circle__US 5M Person Circle":true,"related__10M Person Circle__10M Person Circle":true,"related__US 10M Person Circle__US 10M Person Circle":true,"related__20M Person Circle__20M Person Circle":true,"related__US 20M Person Circle__US 20M Person Circle":true,"related__50M Person Circle__50M Person Circle":true,"related__US 50M Person Circle__US 50M Person Circle":true,"related__100M Person Circle__100M Person Circle":true,"related__US 100M Person Circle__US 100M Person Circle":true,"related__200M Person Circle__200M Person Circle":true,"related__US 200M Person Circle__US 200M Person Circle":true,"related__500M Person Circle__500M Person Circle":true,"related__US 500M Person Circle__US 500M Person Circle":true,"related__1B Person Circle__1B Person Circle":true,"related__US 1B Person Circle__US 1B Person Circle":true,"show_statistic_main":true,"show_statistic_race":false,"show_statistic_national_origin":false,"show_statistic_education":false,"show_statistic_generation":false,"show_statistic_income":false,"show_statistic_housing":false,"show_statistic_transportation":false,"show_statistic_health":false,"show_statistic_climate":true,"show_statistic_industry":false,"show_statistic_occupation":false,"show_statistic_election":false,"show_statistic_feature":false,"show_statistic_weather":false,"show_statistic_misc":false,"show_statistic_other_densities":true,"show_statistic_2010":false,"use_imperial":true,"simple_ordinals":true,"related__ZIP__City":true}' + localStorage.setItem('settings', EG_SETTINGS) + location.reload() + }) + }) -test('check-settings-loaded', async t => { +test('check-settings-loaded', async (t) => { // screenshot path: images/first_test.png - await screencap(t, "settings/check-settings-loaded"); + await screencap(t, 'settings/check-settings-loaded') // check there's an element containing class Huntington_Library - await t.expect(Selector('path').withAttribute('class', /tag-Huntington_Library/).exists).ok(); + await t.expect(Selector('path').withAttribute('class', /tag-Huntington_Library/).exists).ok() // check that there's no element Pasadena_city or 91101 - await t.expect(Selector('path').withAttribute('class', /tag-Pasadena_city/).exists).notOk(); - await t.expect(Selector('path').withAttribute('class', /tag-91101/).exists).notOk(); -}); + await t.expect(Selector('path').withAttribute('class', /tag-Pasadena_city/).exists).notOk() + await t.expect(Selector('path').withAttribute('class', /tag-91101/).exists).notOk() +}) -test('check-settings-loaded-desktop', async t => { +test('check-settings-loaded-desktop', async (t) => { // screenshot path: images/first_test.png - await t.resizeWindow(1400, 800); - await t.eval(() => location.reload()); - await screencap(t, "settings/check-settings-loaded-desktop"); -}); + await t.resizeWindow(1400, 800) + await t.eval(() => { location.reload() }) + await screencap(t, 'settings/check-settings-loaded-desktop') +}) -test('check-settings-persistent', async t => { - await t.expect(Selector('span').withText('mi').exists).ok(); +test('check-settings-persistent', async (t) => { + await t.expect(Selector('span').withText('mi').exists).ok() // navigate to Pasadena via search - await t.typeText(SEARCH_FIELD, "Pasadena, CA, USA"); - await t.pressKey('enter'); - await t.expect(getLocation()).eql(TARGET + '/article.html?longname=Pasadena+city%2C+California%2C+USA'); + await t.typeText(SEARCH_FIELD, 'Pasadena, CA, USA') + await t.pressKey('enter') + await t.expect(getLocation()).eql(`${TARGET}/article.html?longname=Pasadena+city%2C+California%2C+USA`) // check box "Imperial" - await check_textboxes(t, ["Use Imperial Units"]); + await check_textboxes(t, ['Use Imperial Units']) // assert mi not in page - await t.expect(Selector('span').withText('mi').exists).notOk(); + await t.expect(Selector('span').withText('mi').exists).notOk() // go back to San Marino - await t.eval(() => location.reload()); - await t.expect(Selector('span').withText('mi').exists).notOk(); + await t.eval(() => { location.reload() }) + await t.expect(Selector('span').withText('mi').exists).notOk() }) -test('check-related-button-checkboxes-page-specific', async t => { +test('check-related-button-checkboxes-page-specific', async (t) => { // navigate to 91108 - await t.typeText(SEARCH_FIELD, "91108"); - await t.pressKey('enter'); - await t.expect(getLocation()).eql(TARGET + '/article.html?longname=91108%2C+USA'); + await t.typeText(SEARCH_FIELD, '91108') + await t.pressKey('enter') + await t.expect(getLocation()).eql(`${TARGET}/article.html?longname=91108%2C+USA`) // this should not be page specific - await t.expect(Selector('span').withText('mi').exists).ok(); + await t.expect(Selector('span').withText('mi').exists).ok() // San Marino should be present - await t.expect(Selector('path').withAttribute('class', /tag-San_Marino_city/).exists).ok(); + await t.expect(Selector('path').withAttribute('class', /tag-San_Marino_city/).exists).ok() // neighborhoods should not be present (Huntington Library) - await t.expect(Selector('path').withAttribute('class', /tag-Huntington_Library/).exists).notOk(); -}); + await t.expect(Selector('path').withAttribute('class', /tag-Huntington_Library/).exists).notOk() +}) -test('checkboxes-can-be-checked', async t => { +test('checkboxes-can-be-checked', async (t) => { // check that Pasadena CCD is not present - await t.expect(Selector('path').withAttribute('class', /tag-Pasadena_CCD/).exists).notOk(); + await t.expect(Selector('path').withAttribute('class', /tag-Pasadena_CCD/).exists).notOk() const pasadena_ccd = Selector('li').withAttribute('class', 'list_of_lists') .withText('Pasadena CCD') - // find a checkbox inside it - .find('input'); + // find a checkbox inside it + .find('input') await t - .click(pasadena_ccd); + .click(pasadena_ccd) // check that Pasadena CCD is now present - await t.expect(Selector('path').withAttribute('class', /tag-Pasadena_CCD/).exists).ok(); + await t.expect(Selector('path').withAttribute('class', /tag-Pasadena_CCD/).exists).ok() // check that this is persistent by going to Berkeley and checking that Briones CCD is present - await t.typeText(SEARCH_FIELD, "Berkeley, CA, USA"); - await t.pressKey('enter'); - await t.expect(getLocation()).eql(TARGET + '/article.html?longname=Berkeley+city%2C+California%2C+USA'); - await t.expect(Selector('path').withAttribute('class', /tag-Briones_CCD/).exists).ok(); + await t.typeText(SEARCH_FIELD, 'Berkeley, CA, USA') + await t.pressKey('enter') + await t.expect(getLocation()).eql(`${TARGET}/article.html?longname=Berkeley+city%2C+California%2C+USA`) + await t.expect(Selector('path').withAttribute('class', /tag-Briones_CCD/).exists).ok() }) diff --git a/react/test/statistics_test.ts b/react/test/statistics_test.ts index cf79d62a..f5d5aeae 100644 --- a/react/test/statistics_test.ts +++ b/react/test/statistics_test.ts @@ -1,118 +1,115 @@ -import { Selector } from 'testcafe'; +import { Selector } from 'testcafe' -import { TARGET, getLocation, screencap } from './test_utils'; +import { TARGET, getLocation, screencap } from './test_utils' fixture('statistics') - .page(TARGET + '/article.html?longname=Indianapolis+IN+HRR%2C+USA') - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); - -test('statistics-page', async t => { - await t.resizeWindow(1400, 800); - await t.eval(() => location.reload()); + .page(`${TARGET}/article.html?longname=Indianapolis+IN+HRR%2C+USA`) + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) + +test('statistics-page', async (t) => { + await t.resizeWindow(1400, 800) + await t.eval(() => { location.reload() }) // click the link labeled "Population" await t - .click(Selector('a').withText(/^Population$/)); + .click(Selector('a').withText(/^Population$/)) // assert url is https://urbanstats.org/statistic.html?statname=Population&article_type=Hospital+Referral+Region&start=21&amount=20 await t.expect(getLocation()) - .eql(TARGET + '/statistic.html?statname=Population&article_type=Hospital+Referral+Region&start=21&amount=20&universe=USA'); - await screencap(t, "statistics/population"); + .eql(`${TARGET}/statistic.html?statname=Population&article_type=Hospital+Referral+Region&start=21&amount=20&universe=USA`) + await screencap(t, 'statistics/population') const count = Selector('div').withAttribute('style', /background-color: rgb\(212, 181, 226\);/) - .withText(/Indianapolis IN HRR, USA/); - await t.expect(count.count).gte(1, "Need highlighting"); + .withText(/Indianapolis IN HRR, USA/) + await t.expect(count.count).gte(1, 'Need highlighting') // click link "Data Explanation and Credit" await t - .click(Selector('a').withText(/^Data Explanation and Credit$/)); + .click(Selector('a').withText(/^Data Explanation and Credit$/)) await t.expect(getLocation()) - .eql(TARGET + '/data-credit.html#explanation_population'); -}); + .eql(`${TARGET}/data-credit.html#explanation_population`) +}) fixture('statistics-navigation') - .page(TARGET + '/statistic.html?statname=Population&article_type=Hospital+Referral+Region&start=21&amount=20') - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); + .page(`${TARGET}/statistic.html?statname=Population&article_type=Hospital+Referral+Region&start=21&amount=20`) + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) -test('statistics-navigation-left', async t => { +test('statistics-navigation-left', async (t) => { await t - .click(Selector('button').withText('<')); - const url = TARGET + '/statistic.html?statname=Population&article_type=Hospital+Referral+Region&start=1&amount=20'; + .click(Selector('button').withText('<')) + const url = `${TARGET}/statistic.html?statname=Population&article_type=Hospital+Referral+Region&start=1&amount=20` await t.expect(getLocation()) - .eql(url); + .eql(url) // going left again does nothing await t - .click(Selector('button').withText('<')); + .click(Selector('button').withText('<')) await t.expect(getLocation()) - .eql(url); -}); + .eql(url) +}) -test('statistics-navigation-right', async t => { +test('statistics-navigation-right', async (t) => { await t - .click(Selector('button').withText('>')); + .click(Selector('button').withText('>')) await t.expect(getLocation()) - .eql(TARGET + '/statistic.html?statname=Population&article_type=Hospital+Referral+Region&start=41&amount=20'); -}); + .eql(`${TARGET}/statistic.html?statname=Population&article_type=Hospital+Referral+Region&start=41&amount=20`) +}) -test('statistics-navigation-amount', async t => { +test('statistics-navigation-amount', async (t) => { // take the select field that currently says 20 and make it say 50 - const amount = Selector('select').nth(0); + const amount = Selector('select').nth(0) await t .click(amount) - .click(Selector('option').withText('50')); + .click(Selector('option').withText('50')) await t.expect(getLocation()) - .eql(TARGET + '/statistic.html?statname=Population&article_type=Hospital+Referral+Region&start=1&amount=50'); - await screencap(t, "statistics/amount-50"); + .eql(`${TARGET}/statistic.html?statname=Population&article_type=Hospital+Referral+Region&start=1&amount=50`) + await screencap(t, 'statistics/amount-50') // set to All await t .click(amount) - .click(Selector('option').withText('All')); + .click(Selector('option').withText('All')) await t.expect(getLocation()) - .eql(TARGET + '/statistic.html?statname=Population&article_type=Hospital+Referral+Region&start=1&amount=All'); - await screencap(t, "statistics/amount-all"); -}); - + .eql(`${TARGET}/statistic.html?statname=Population&article_type=Hospital+Referral+Region&start=1&amount=All`) + await screencap(t, 'statistics/amount-all') +}) -test('statistics-navigation-last-page', async t => { +test('statistics-navigation-last-page', async (t) => { // find input with value 2, then replace it with 15 - const page = Selector('input').withAttribute('value', '2'); + const page = Selector('input').withAttribute('value', '2') await t .click(page) .pressKey('ctrl+a') .typeText(page, '15') - .pressKey('enter'); + .pressKey('enter') - const url = TARGET + '/statistic.html?statname=Population&article_type=Hospital+Referral+Region&start=281&amount=20'; + const url = `${TARGET}/statistic.html?statname=Population&article_type=Hospital+Referral+Region&start=281&amount=20` await t.expect(getLocation()) - .eql(url); + .eql(url) - await screencap(t, "statistics/last-page"); + await screencap(t, 'statistics/last-page') // going right again does nothing await t - .click(Selector('button').withText('>')); + .click(Selector('button').withText('>')) await t.expect(getLocation()) - .eql(url); -}); - + .eql(url) +}) fixture('statistic universe selector test') - .page(TARGET + '/statistic.html?statname=Population&article_type=City&start=3461&amount=20') - // no local storage - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); - + .page(`${TARGET}/statistic.html?statname=Population&article_type=City&start=3461&amount=20`) +// no local storage + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) -test('statistic-universe-selector-test', async t => { +test('statistic-universe-selector-test', async (t) => { await t - .click(Selector('img').withAttribute('class', 'universe-selector')); - await screencap(t, "statistic-dropped-down-universe-selector"); + .click(Selector('img').withAttribute('class', 'universe-selector')) + await screencap(t, 'statistic-dropped-down-universe-selector') await t .click( Selector('img') .withAttribute('class', 'universe-selector-option') - .withAttribute('alt', 'Puerto Rico, USA')); + .withAttribute('alt', 'Puerto Rico, USA')) await t.expect(getLocation()) - .eql(TARGET + '/statistic.html?statname=Population&article_type=City&start=3461&amount=20&universe=Puerto+Rico%2C+USA'); -}); + .eql(`${TARGET}/statistic.html?statname=Population&article_type=City&start=3461&amount=20&universe=Puerto+Rico%2C+USA`) +}) diff --git a/react/test/string_check_test.ts b/react/test/string_check_test.ts index 79b8465e..0f017cb6 100644 --- a/react/test/string_check_test.ts +++ b/react/test/string_check_test.ts @@ -1,12 +1,12 @@ -import { IS_TESTING, TARGET } from "./test_utils"; +import { IS_TESTING, TARGET } from './test_utils' fixture('mapping') .page(TARGET) - // no local storage - .beforeEach(async t => { - await t.eval(() => localStorage.clear()); - }); +// no local storage + .beforeEach(async (t) => { + await t.eval(() => { localStorage.clear() }) + }) -test("state-map", async t => { - await t.expect(IS_TESTING).ok("String tests are in overwrite mode. Set IS_TESTING to true to run tests.") +test('state-map', async (t) => { + await t.expect(IS_TESTING).ok('String tests are in overwrite mode. Set IS_TESTING to true to run tests.') }) diff --git a/react/test/test_utils.ts b/react/test/test_utils.ts index bbf7fc9b..41c132df 100644 --- a/react/test/test_utils.ts +++ b/react/test/test_utils.ts @@ -1,133 +1,132 @@ +import fs from 'fs' +import path from 'path' -import { Selector, ClientFunction } from 'testcafe'; -import fs from 'fs'; -import path from 'path'; -import downloadsFolder from 'downloads-folder'; +import downloadsFolder from 'downloads-folder' +import { ClientFunction, Selector } from 'testcafe' -export const TARGET = process.env.URBANSTATS_TEST_TARGET ?? "http://localhost:8000" -export const SEARCH_FIELD = Selector('input').withAttribute('placeholder', 'Search Urban Stats'); -export const getLocation = ClientFunction(() => document.location.href); +export const TARGET = process.env.URBANSTATS_TEST_TARGET ?? 'http://localhost:8000' +export const SEARCH_FIELD = Selector('input').withAttribute('placeholder', 'Search Urban Stats') +export const getLocation = ClientFunction(() => document.location.href) -export const IS_TESTING = true; +export const IS_TESTING = true -export function comparison_page(locations: string[]) { - const params = new URLSearchParams(); - params.set('longnames', JSON.stringify(locations)); - return TARGET + '/comparison.html?' + params.toString(); +export function comparison_page(locations: string[]): string { + const params = new URLSearchParams() + params.set('longnames', JSON.stringify(locations)) + return `${TARGET}/comparison.html?${params.toString()}` } -export async function check_textboxes(t: TestController, txts: string[]) { - const hamburgerMenu = Selector('div').withAttribute('class', 'hamburgermenu'); +export async function check_textboxes(t: TestController, txts: string[]): Promise { + const hamburgerMenu = Selector('div').withAttribute('class', 'hamburgermenu') if (await hamburgerMenu.exists) { - await t.click(hamburgerMenu); + await t.click(hamburgerMenu) } for (const txt of txts) { const checkbox = Selector('div').withAttribute('class', 'checkbox-setting') - // filter for label + // filter for label .filter(node => node.querySelector('label')!.innerText === txt, { txt }) - // find checkbox - .find('input'); - await t.click(checkbox); + // find checkbox + .find('input') + await t.click(checkbox) } if (await hamburgerMenu.exists) { - await t.click(hamburgerMenu); + await t.click(hamburgerMenu) } } -export async function check_all_category_boxes(t: TestController) { - const hamburgerMenu = Selector('div').withAttribute('class', 'hamburgermenu'); +export async function check_all_category_boxes(t: TestController): Promise { + const hamburgerMenu = Selector('div').withAttribute('class', 'hamburgermenu') if (await hamburgerMenu.exists) { - await t.click(hamburgerMenu); + await t.click(hamburgerMenu) } const checkboxes = Selector('div').withAttribute('class', 'checkbox-setting') - .filter(node => { - const label = node.querySelector('label')!.innerText; + .filter((node) => { + const label = node.querySelector('label')!.innerText return ( - label !== "Use Imperial Units" - && label !== "Include Historical Districts" - && label !== "Simple Ordinals" - && label !== "Race" - && label !== "Election" - ); - }).find('input'); + label !== 'Use Imperial Units' + && label !== 'Include Historical Districts' + && label !== 'Simple Ordinals' + && label !== 'Race' + && label !== 'Election' + ) + }).find('input') for (let i = 0; i < await checkboxes.count; i++) { - await t.click(checkboxes.nth(i)); + await t.click(checkboxes.nth(i)) } if (await hamburgerMenu.exists) { - await t.click(hamburgerMenu); + await t.click(hamburgerMenu) } // reload - await t.eval(() => location.reload()); + await t.eval(() => { location.reload() }) } - -async function prep_for_image(t: TestController) { - await t.wait(1000); +async function prep_for_image(t: TestController): Promise { + await t.wait(1000) await t.eval(() => { - // disable the leaflet map - for (const x of Array.from(document.getElementsByClassName("leaflet-tile-pane"))) { - x.remove(); + // disable the leaflet map + for (const x of Array.from(document.getElementsByClassName('leaflet-tile-pane'))) { + x.remove() } - for (const x of Array.from(document.getElementsByClassName("map-container-for-testing"))) { - const style = "border-style: solid; border-color: #abcdef"; - x.setAttribute("style", style); + for (const x of Array.from(document.getElementsByClassName('map-container-for-testing'))) { + const style = 'border-style: solid; border-color: #abcdef' + x.setAttribute('style', style) } - document.getElementById("current-version")!.innerHTML = "<VERSION>"; - document.getElementById("last-updated")!.innerHTML = "<LAST UPDATED>"; - for (const x of Array.from(document.getElementsByClassName("juxtastat-user-id"))) { - x.innerHTML = "<USER ID>"; + document.getElementById('current-version')!.innerHTML = '<VERSION>' + document.getElementById('last-updated')!.innerHTML = '<LAST UPDATED>' + for (const x of Array.from(document.getElementsByClassName('juxtastat-user-id'))) { + x.innerHTML = '<USER ID>' } - }); + }) } -export async function screencap(t: TestController, name: string) { +export async function screencap(t: TestController, name: string): Promise { await prep_for_image(t) - return await t.takeScreenshot({ - // include the browser name in the screenshot path - path: name + '_' + t.browser.name + '.png', + return t.takeScreenshot({ + // include the browser name in the screenshot path + path: `${name}_${t.browser.name}.png`, fullPage: true, }) } -export async function grab_download(t: TestController, name: string, button: Selector) { - await prep_for_image(t); +export async function grab_download(t: TestController, name: string, button: Selector): Promise { + await prep_for_image(t) await t - .click(button); - await t.wait(3000); - await copy_most_recent_file(t, name); + .click(button) + await t.wait(3000) + copy_most_recent_file(t, name) } -export async function download_image(t: TestController, name: string) { - const download = Selector('img').withAttribute('src', '/screenshot.png'); - await grab_download(t, name, download); +export async function download_image(t: TestController, name: string): Promise { + const download = Selector('img').withAttribute('src', '/screenshot.png') + await grab_download(t, name, download) } -export async function download_histogram(t: TestController, name: string, nth: number) { - const download = Selector('img').withAttribute('src', '/download.png').nth(nth); - await grab_download(t, name, download); +export async function download_histogram(t: TestController, name: string, nth: number): Promise { + const download = Selector('img').withAttribute('src', '/download.png').nth(nth) + await grab_download(t, name, download) } -export function most_recent_download_path() { +export function most_recent_download_path(): string { // get the most recent file in the downloads folder - const files = fs.readdirSync(downloadsFolder()); - const sorted = files.map(x => path.join(downloadsFolder(), x)).sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs); - - console.log("Most recent download: " + sorted[0]); - return sorted[0]; + const files = fs.readdirSync(downloadsFolder()) + const sorted = files.map(x => path.join(downloadsFolder(), x)).sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs) + return sorted[0] } -async function copy_most_recent_file(t: TestController, name: string) { +function copy_most_recent_file(t: TestController, name: string): void { // copy the file to the screenshots folder - const screenshotsFolder = path.join(__dirname, '..', 'screenshots'); - fs.copyFileSync(most_recent_download_path(), path.join(screenshotsFolder, name + '_' + t.browser.name + '.png')); + const screenshotsFolder = path.join(__dirname, '..', 'screenshots') + fs.copyFileSync(most_recent_download_path(), path.join(screenshotsFolder, `${name}_${t.browser.name}.png`)) } -export async function download_or_check_string(t: TestController, string: string, name: string) { - const path_to_file = path.join(__dirname, '..', "..", "tests", 'reference_strings', name + '.txt'); +export async function download_or_check_string(t: TestController, string: string, name: string): Promise { + const path_to_file = path.join(__dirname, '..', '..', 'tests', 'reference_strings', `${name}.txt`) + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- We might want to change this variable if (IS_TESTING) { - const expected = fs.readFileSync(path_to_file, 'utf8'); - await t.expect(string).eql(expected); - } else { - fs.writeFileSync(path_to_file, string); + const expected = fs.readFileSync(path_to_file, 'utf8') + await t.expect(string).eql(expected) + } + else { + fs.writeFileSync(path_to_file, string) } } diff --git a/react/tsconfig.json b/react/tsconfig.json index e5f742c2..b6415f37 100644 --- a/react/tsconfig.json +++ b/react/tsconfig.json @@ -9,6 +9,10 @@ "esModuleInterop": true, "target": "ES2022", "module": "CommonJS", - "resolveJsonModule": true + "resolveJsonModule": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "allowUnreachableCode": false } } \ No newline at end of file diff --git a/react/webpack.config.js b/react/webpack.config.js index f3c94357..f03c8831 100644 --- a/react/webpack.config.js +++ b/react/webpack.config.js @@ -1,35 +1,35 @@ -const path = require('path'); +const path = require('path') const NodePolyfillPlugin = require('node-polyfill-webpack-plugin') module.exports = env => ({ entry: { - "article": ['./src/article.tsx'], - "quiz": ['./src/quiz.tsx'], - "index": ['./src/index.tsx'], - "random": ["./src/random.ts"], - "about": ['./src/about.tsx'], - "data-credit": ['./src/data-credit.tsx'], - "mapper": ['./src/mapper.tsx'], - "comparison": ['./src/comparison.tsx'], - "statistic": ['./src/statistic.tsx'] + 'article': ['./src/article.tsx'], + 'quiz': ['./src/quiz.tsx'], + 'index': ['./src/index.tsx'], + 'random': ['./src/random.ts'], + 'about': ['./src/about.tsx'], + 'data-credit': ['./src/data-credit.tsx'], + 'mapper': ['./src/mapper.tsx'], + 'comparison': ['./src/comparison.tsx'], + 'statistic': ['./src/statistic.tsx'], }, output: { filename: '[name].js', path: path.resolve(__dirname, '..', 'dist'), - clean: true + clean: true, }, resolve: { // Add '.ts' and '.tsx' as resolvable extensions. - extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"], + extensions: ['', '.webpack.js', '.web.js', '.ts', '.tsx', '.js'], }, module: { rules: [ - { test: /\.tsx?$/, loader: "ts-loader" }, - { test: /\.js$/, loader: "babel-loader" }, + { test: /\.tsx?$/, loader: 'ts-loader' }, + { test: /\.js$/, loader: 'babel-loader' }, { test: /\.css$/i, - use: ["style-loader", "css-loader"] + use: ['style-loader', 'css-loader'], }, ], @@ -48,4 +48,4 @@ module.exports = env => ({ writeToDisk: true, }, }, -}); +})