From 121d17a09893e39ebdbf4f11c98ba0ae5327d3af Mon Sep 17 00:00:00 2001 From: inker Date: Sun, 24 Nov 2019 00:42:22 +0000 Subject: [PATCH] react change extension init update removed 'dummy' update split view update update update update update . gradients + colors circular transfers transfer slight shift mouse on transfers update . fix stadium split components optimize rerendering . . refactor . optimize rerendering font size further optimization . . . refactor stadium angle fix stadium . update . detailed E colors . . platform offset single platform per station . by route offset -> slot . restructure update fix update makeShouldSwapFunc refactor getPositions . patches optimization update update replaced slot positions with slots bang operator fix . fix overlapped points moved optimization, span sorting & cost func to separate files moved span sorting & cost func to separate files . intersecting a parallel batch reduced penalty . . update comparePropsLevel update upd . . dummy fix . . upd upd . update update fixes ref -> innerRef osi update initSlots remove distance factor update algos getDerivedStateFromProps update update update fix comparisonLevel fix stadium for firefox tooltip font update naming PositioningEngine . mounting update memoize-one memoization of styles update hooks remove withRefs update update transfers update update object memoization update update Modal -> Portal update update update dependencies update dependencies update config update --- .eslintrc.js | 11 + package-lock.json | 1716 ++++++++++++++--- package.json | 16 +- src/Config.ts | 1 + src/Metro/DummyContainer.tsx | 50 + src/Metro/MapContainer.tsx | 209 ++ src/Metro/Platforms.tsx | 130 ++ src/Metro/PositioningEngine.ts | 317 +++ src/Metro/Spans.tsx | 227 +++ src/Metro/Transfers.tsx | 220 +++ src/Metro/index.tsx | 137 ++ src/Metro/optimization/costFunction.ts | 169 ++ src/Metro/optimization/optimizeSlots.ts | 236 +++ src/Metro/optimization/sortSpans.ts | 19 + src/Metro/utils/cartesian.ts | 7 + src/Metro/utils/getPlatformBranches.ts | 21 + src/Metro/utils/getPositions.ts | 22 + src/Metro/utils/getSegments.ts | 39 + src/Metro/utils/initSlots.ts | 48 + src/Metro/utils/makeAcceptanceFunc.ts | 13 + src/Metro/utils/makeCircumCircles.ts | 19 + .../utils/makeGetPlatformPositionFunc.ts | 77 + src/Metro/utils/makeWhiskers.ts | 132 ++ src/Metro/utils/optimize.ts | 32 + src/Metro/utils/types.ts | 14 + src/MetroMap.ts | 895 --------- src/MetroMap.tsx | 280 +++ src/components/Platform.tsx | 87 + src/components/Portal.ts | 55 + src/components/Tooltip.tsx | 137 ++ src/components/Transfer/Gradient.tsx | 53 + src/components/Transfer/index.tsx | 138 ++ src/components/filters/BlackGlow.tsx | 25 + src/components/filters/Gray.tsx | 17 + src/components/filters/Opacity.tsx | 14 + src/components/filters/Shadow.tsx | 39 + src/components/primitives/Arc.tsx | 71 + src/components/primitives/Bezier.tsx | 52 + src/components/primitives/Circle.tsx | 24 + src/components/primitives/Line.tsx | 28 + src/components/primitives/Stadium.tsx | 44 + src/css/map.css | 29 +- src/{main.ts => index.ts} | 2 +- src/mapconfig.json | 1 + src/network/Platform.ts | 46 +- src/network/Span.ts | 67 +- src/network/index.ts | 28 +- src/utils/algorithm/findCycle.ts | 39 +- src/utils/collections.ts | 17 + src/utils/comparisonLevel.ts | 21 + src/utils/equalsByLevel.ts | 31 + src/utils/math/index.ts | 4 +- src/utils/math/vector.ts | 4 +- src/utils/memoizeObject.ts | 10 + src/utils/shallowEqual.ts | 4 + src/utils/svg/filters.ts | 99 - src/utils/types.ts | 3 + tsconfig.json | 6 +- webpack/rules.js | 3 +- webpack/webpack.config.js | 2 +- 60 files changed, 4979 insertions(+), 1278 deletions(-) create mode 100644 src/Metro/DummyContainer.tsx create mode 100644 src/Metro/MapContainer.tsx create mode 100644 src/Metro/Platforms.tsx create mode 100644 src/Metro/PositioningEngine.ts create mode 100644 src/Metro/Spans.tsx create mode 100644 src/Metro/Transfers.tsx create mode 100644 src/Metro/index.tsx create mode 100644 src/Metro/optimization/costFunction.ts create mode 100644 src/Metro/optimization/optimizeSlots.ts create mode 100644 src/Metro/optimization/sortSpans.ts create mode 100644 src/Metro/utils/cartesian.ts create mode 100644 src/Metro/utils/getPlatformBranches.ts create mode 100644 src/Metro/utils/getPositions.ts create mode 100644 src/Metro/utils/getSegments.ts create mode 100644 src/Metro/utils/initSlots.ts create mode 100644 src/Metro/utils/makeAcceptanceFunc.ts create mode 100644 src/Metro/utils/makeCircumCircles.ts create mode 100644 src/Metro/utils/makeGetPlatformPositionFunc.ts create mode 100644 src/Metro/utils/makeWhiskers.ts create mode 100644 src/Metro/utils/optimize.ts create mode 100644 src/Metro/utils/types.ts delete mode 100644 src/MetroMap.ts create mode 100644 src/MetroMap.tsx create mode 100644 src/components/Platform.tsx create mode 100644 src/components/Portal.ts create mode 100644 src/components/Tooltip.tsx create mode 100644 src/components/Transfer/Gradient.tsx create mode 100644 src/components/Transfer/index.tsx create mode 100644 src/components/filters/BlackGlow.tsx create mode 100644 src/components/filters/Gray.tsx create mode 100644 src/components/filters/Opacity.tsx create mode 100644 src/components/filters/Shadow.tsx create mode 100644 src/components/primitives/Arc.tsx create mode 100644 src/components/primitives/Bezier.tsx create mode 100644 src/components/primitives/Circle.tsx create mode 100644 src/components/primitives/Line.tsx create mode 100644 src/components/primitives/Stadium.tsx rename src/{main.ts => index.ts} (92%) create mode 100644 src/utils/comparisonLevel.ts create mode 100644 src/utils/equalsByLevel.ts create mode 100644 src/utils/memoizeObject.ts create mode 100644 src/utils/shallowEqual.ts create mode 100644 src/utils/types.ts diff --git a/.eslintrc.js b/.eslintrc.js index b2735e9de..bbfe932dd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,6 @@ module.exports = { extends: [ + 'eslint-config-airbnb', '@inker/eslint-config-typescript', // 'plugin:import/errors', // 'plugin:import/warnings', @@ -50,6 +51,16 @@ module.exports = { allowAllPropertiesOnSameLine: false, }], + 'react/prop-types': 0, + 'react/jsx-one-expression-per-line': 0, + 'react/jsx-props-no-spreading': 0, + 'react/jsx-filename-extension': [2, { + extensions: [ + '.jsx', + '.tsx', + ], + }], + '@typescript-eslint/no-unused-vars': [2, { vars: 'all', args: 'after-used', diff --git a/package-lock.json b/package-lock.json index 54806cd57..72c71bd29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,11 +16,17 @@ "download.js": "^1.0.0", "github-api": "^3.4.0", "hammerjs": "^2.0.8", + "history": "^5.0.0", "html-tags": "^3.1.0", "leaflet": "^1.7.1", "localforage": "^1.9.0", "lodash": "^4.17.21", + "memoize-one": "^5.1.1", "normalize.css": "^8.0.1", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-router-dom": "^5.1.2", + "styled-components": "^5.0.1", "svgio": "^0.4.0", "tryfunc": "^3.1.0", "tslib": "^2.4.0", @@ -31,9 +37,17 @@ "@types/hammerjs": "^2.0.40", "@types/leaflet": "^1.7.4", "@types/lodash": "^4.14.171", + "@types/memoize-one": "^5.1.2", + "@types/react": "^17.0.14", + "@types/react-dom": "^17.0.9", + "@types/react-router-dom": "^5.1.3", + "@types/styled-components": "^5.1.25", "copy-webpack-plugin": "^9.0.1", - "css-loader": "^6.2.0", + "css-loader": "^6.7.1", "eslint": "^8.21.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-plugin-jsx-a11y": "^6.6.1", + "eslint-plugin-react": "^7.30.1", "html-webpack-plugin": "^5.3.1", "mini-css-extract-plugin": "^2.1.0", "npm-run-all": "^4.1.5", @@ -59,7 +73,6 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, "dependencies": { "@babel/highlight": "^7.14.5" }, @@ -171,7 +184,6 @@ "version": "7.14.8", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz", "integrity": "sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg==", - "dev": true, "dependencies": { "@babel/types": "^7.14.8", "jsesc": "^2.5.1", @@ -181,6 +193,17 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-compilation-targets": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz", @@ -212,7 +235,6 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", - "dev": true, "dependencies": { "@babel/helper-get-function-arity": "^7.14.5", "@babel/template": "^7.14.5", @@ -226,7 +248,6 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", - "dev": true, "dependencies": { "@babel/types": "^7.14.5" }, @@ -238,7 +259,6 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", - "dev": true, "dependencies": { "@babel/types": "^7.14.5" }, @@ -259,12 +279,11 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", - "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", - "dev": true, + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", "dependencies": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -332,7 +351,6 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", - "dev": true, "dependencies": { "@babel/types": "^7.14.5" }, @@ -340,11 +358,18 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", + "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -376,7 +401,6 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", @@ -390,7 +414,6 @@ "version": "7.14.8", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.8.tgz", "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==", - "dev": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -398,11 +421,34 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", + "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.18.9.tgz", + "integrity": "sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==", + "dev": true, + "dependencies": { + "core-js-pure": "^3.20.2", + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.14.5", "@babel/parser": "^7.14.5", @@ -416,7 +462,6 @@ "version": "7.14.8", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.8.tgz", "integrity": "sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.14.5", "@babel/generator": "^7.14.8", @@ -436,7 +481,6 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -452,16 +496,15 @@ "node_modules/@babel/traverse/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/@babel/types": { - "version": "7.14.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", - "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", - "dev": true, + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz", + "integrity": "sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==", "dependencies": { - "@babel/helper-validator-identifier": "^7.14.8", + "@babel/helper-string-parser": "^7.18.10", + "@babel/helper-validator-identifier": "^7.18.6", "to-fast-properties": "^2.0.0" }, "engines": { @@ -486,6 +529,29 @@ "node": ">=10.0.0" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "dependencies": { + "@emotion/memoize": "^0.8.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "node_modules/@eslint/eslintrc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", @@ -767,6 +833,22 @@ "integrity": "sha512-VbjwR1fhsn2h2KXAY4oy1fm7dCxaKy0D+deTb8Ilc3Eo3rc5+5eA4rfYmZaHgNJKxVyI0f6WIXzO2zLkVmQPHA==", "dev": true }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dev": true, + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz", @@ -810,6 +892,16 @@ "@types/unist": "*" } }, + "node_modules/@types/memoize-one": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/memoize-one/-/memoize-one-5.1.2.tgz", + "integrity": "sha512-9lItlM8Bf1DPvm8p4zE0vUn7YoTktEYgLd6Eva/PT0its200xmhaYrCMG/Y8615/f62SXOrDWMIEPk/xV+DQpw==", + "deprecated": "This is a stub types definition. memoize-one provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "memoize-one": "*" + } + }, "node_modules/@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", @@ -840,6 +932,70 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "dev": true + }, + "node_modules/@types/react": { + "version": "17.0.48", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.48.tgz", + "integrity": "sha512-zJ6IYlJ8cYYxiJfUaZOQee4lh99mFihBoqkOSEGV+dFi9leROW6+PgstzQ+w3gWTnUfskALtQPGHK6dYmPj+2A==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "17.0.17", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.17.tgz", + "integrity": "sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg==", + "dev": true, + "dependencies": { + "@types/react": "^17" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.18", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.18.tgz", + "integrity": "sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g==", + "dev": true, + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dev": true, + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, + "node_modules/@types/styled-components": { + "version": "5.1.25", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.25.tgz", + "integrity": "sha512-fgwl+0Pa8pdkwXRoVPP9JbqF0Ivo9llnmsm+7TCI330kbPIFd9qv1Lrhr37shf4tnxCOSu+/IgqM7uJXLWZZNQ==", + "dev": true, + "dependencies": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, "node_modules/@types/unist": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", @@ -1501,7 +1657,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -1515,6 +1670,19 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + }, + "engines": { + "node": ">=6.0" + } + }, "node_modules/arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -1553,7 +1721,6 @@ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -1614,6 +1781,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", + "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -1632,6 +1817,12 @@ "node": ">=0.10.0" } }, + "node_modules/ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", + "dev": true + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -1674,6 +1865,15 @@ "node": ">= 4.5.0" } }, + "node_modules/axe-core": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.3.tgz", + "integrity": "sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/axios": { "version": "0.21.4", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", @@ -1682,6 +1882,32 @@ "follow-redirects": "^1.14.0" } }, + "node_modules/axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true + }, + "node_modules/babel-plugin-styled-components": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", + "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11", + "picomatch": "^2.3.0" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, + "node_modules/babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, "node_modules/bail": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", @@ -2003,6 +2229,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha512-W2lPwkBkMZwFlPCXhIlYgxu+7gC/NUlCtdK652DAJ1JdgV0sTrvuPFshNPrFa1TY2JOkLhgdeEBplB4ezEa+xg==" + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -2029,7 +2260,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -2221,7 +2451,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -2229,8 +2458,7 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "node_modules/colord": { "version": "2.4.0", @@ -2308,8 +2536,7 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/connect-history-api-fallback": { "version": "1.6.0", @@ -2411,6 +2638,17 @@ "node": ">=10.13.0" } }, + "node_modules/core-js-pure": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.24.1.tgz", + "integrity": "sha512-r1nJk41QLLPyozHUUPmILCEMtMw24NG4oWK6RbsDdjzQgg9ZvrUsPBj1MnG0wXXp1DCDU6j+wUvEmBSrtRbLXg==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -2528,6 +2766,14 @@ "node": ">=6" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, "node_modules/css-color-names": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-1.0.1.tgz", @@ -2634,18 +2880,18 @@ } }, "node_modules/css-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.2.0.tgz", - "integrity": "sha512-/rvHfYRjIpymZblf49w8jYcRo2y9gj6rV8UroHGmBxKrIyGLokpycyKzp9OkitvqT29ZSpzJ0Ic7SpnJX3sC8g==", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", + "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.2.15", + "postcss": "^8.4.7", "postcss-modules-extract-imports": "^3.0.0", "postcss-modules-local-by-default": "^4.0.0", "postcss-modules-scope": "^3.0.0", "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.1.0", + "postcss-value-parser": "^4.2.0", "semver": "^7.3.5" }, "engines": { @@ -2659,24 +2905,6 @@ "webpack": "^5.0.0" } }, - "node_modules/css-loader/node_modules/postcss": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz", - "integrity": "sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==", - "dev": true, - "dependencies": { - "colorette": "^1.2.2", - "nanoid": "^3.1.23", - "source-map-js": "^0.6.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, "node_modules/css-loader/node_modules/semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -2802,6 +3030,16 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", @@ -2943,6 +3181,18 @@ "node": ">=8.0.0" } }, + "node_modules/csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", + "dev": true + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3497,7 +3747,6 @@ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", "dev": true, - "peer": true, "dependencies": { "has": "^1.0.3" } @@ -3538,7 +3787,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, "engines": { "node": ">=0.8.0" } @@ -3599,12 +3847,32 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-config-airbnb": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", + "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "dev": true, + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" + }, + "engines": { + "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.28.0", + "eslint-plugin-react-hooks": "^4.3.0" + } + }, "node_modules/eslint-config-airbnb-base": { "version": "15.0.0", "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", "dev": true, - "peer": true, "dependencies": { "confusing-browser-globals": "^1.0.10", "object.assign": "^4.1.2", @@ -3624,7 +3892,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true, - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -3858,6 +4125,48 @@ "lodash": "^4.17.21" } }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz", + "integrity": "sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.18.9", + "aria-query": "^4.2.2", + "array-includes": "^3.1.5", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.4.3", + "axobject-query": "^2.2.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.2", + "language-tags": "^1.0.5", + "minimatch": "^3.1.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/eslint-plugin-lodash": { "version": "7.4.0", "resolved": "https://registry.npmjs.org/eslint-plugin-lodash/-/eslint-plugin-lodash-7.4.0.tgz", @@ -3887,6 +4196,94 @@ "eslint": "^7.0.0 || ^8.0.0" } }, + "node_modules/eslint-plugin-react": { + "version": "7.30.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.30.1.tgz", + "integrity": "sha512-NbEvI9jtqO46yJA3wcRF9Mo0lF9T/jhdHqhCHXiXtD+Zcb98812wvokjWpU7Q4QH5edo6dmqrukxVvWWXHlsUg==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.5", + "array.prototype.flatmap": "^1.3.0", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.5", + "object.fromentries": "^2.0.5", + "object.hasown": "^1.1.1", + "object.values": "^1.1.5", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.3", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "peer": 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/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/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/eslint-plugin-sonarjs": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.13.0.tgz", @@ -5090,7 +5487,6 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, "engines": { "node": ">=4" } @@ -5211,7 +5607,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, "engines": { "node": ">=4" } @@ -5309,6 +5704,27 @@ "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", "dev": true }, + "node_modules/history": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", + "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", + "dependencies": { + "@babel/runtime": "^7.7.6" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -6381,8 +6797,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -6400,7 +6815,6 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, "bin": { "jsesc": "bin/jsesc" }, @@ -6453,6 +6867,19 @@ "node": ">=6" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.2.tgz", + "integrity": "sha512-4ZCADZHRkno244xlNnn4AOG6sRQ7iBZ5BbgZ4vW4y5IZw7cVUD1PPeblm1xx/nfmMxPdt/LHsXZW8z/j58+l9Q==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.2" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/kebab-case": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/kebab-case/-/kebab-case-1.0.1.tgz", @@ -6490,6 +6917,21 @@ "integrity": "sha512-sZLUnTqimCkvkgRS+kbPlYW5o8q5w1cu+uIisKpEWkj31I8mx8kNG162DwRav8Zirkva6N5uoFsm9kzK4mUXjw==", "dev": true }, + "node_modules/language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "dev": true, + "dependencies": { + "language-subtag-registry": "~0.3.2" + } + }, "node_modules/last-call-webpack-plugin": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz", @@ -6646,6 +7088,17 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -6770,6 +7223,11 @@ "node": ">= 0.6" } }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -7007,6 +7465,19 @@ "node": ">=4" } }, + "node_modules/mini-create-react-context": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", + "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "tiny-warning": "^1.0.3" + }, + "peerDependencies": { + "prop-types": "^15.0.0", + "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, "node_modules/mini-css-extract-plugin": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.1.0.tgz", @@ -7133,9 +7604,9 @@ "optional": true }, "node_modules/nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" @@ -7397,7 +7868,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -7509,7 +7979,6 @@ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -7519,6 +7988,36 @@ "node": ">= 0.4" } }, + "node_modules/object.fromentries": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", + "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.hasown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", + "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", @@ -7536,7 +8035,6 @@ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -7620,24 +8118,6 @@ "webpack": "^4.0.0" } }, - "node_modules/optimize-css-assets-webpack-plugin/node_modules/postcss": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz", - "integrity": "sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==", - "dev": true, - "dependencies": { - "colorette": "^1.2.2", - "nanoid": "^3.1.23", - "source-map-js": "^0.6.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -7878,11 +8358,16 @@ "node": ">=4" } }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "node_modules/picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -7981,21 +8466,27 @@ } }, "node_modules/postcss": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz", - "integrity": "sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==", + "version": "8.4.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], "dependencies": { - "colorette": "^1.2.2", - "nanoid": "^3.1.23", - "source-map-js": "^0.6.2" + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" }, "engines": { "node": "^10 || ^12 || >=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" } }, "node_modules/postcss-calc": { @@ -10002,10 +10493,9 @@ } }, "node_modules/postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", - "dev": true + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/prelude-ls": { "version": "1.2.1", @@ -10032,6 +10522,21 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -10157,6 +10662,118 @@ "node": ">= 0.8" } }, + "node_modules/react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", + "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" + }, + "peerDependencies": { + "react": "17.0.2" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "peer": true + }, + "node_modules/react-router": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.3.tgz", + "integrity": "sha512-mzQGUvS3bM84TnbtMYR8ZjKnuPJ71IjSzR+DE6UkUqvN4czWIqEs17yLL8xkAycv4ev0AiN+IGrWu88vJs/p2w==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.4.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-Ov0tGPMBgqmbu5CDmN++tv2HQ9HlWDuWIIqn4b88gjlAN5IHI+4ZUZRcpz9Hl0azFIwihbLDYw1OiHGRo7ZIng==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.3.3", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-dom/node_modules/history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "dependencies": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "node_modules/react-router/node_modules/history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "dependencies": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "node_modules/react-router/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/react-router/node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/react-router/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -10340,6 +10957,11 @@ "node": ">=8" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, "node_modules/regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -10645,6 +11267,11 @@ "node": ">=4" } }, + "node_modules/resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, "node_modules/resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -10752,6 +11379,15 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "node_modules/scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, "node_modules/schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -10944,6 +11580,11 @@ "node": ">=8" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -11266,15 +11907,14 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/source-map-js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", - "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "dev": true, "engines": { "node": ">=0.10.0" @@ -11554,6 +12194,25 @@ "node": ">=6" } }, + "node_modules/string.prototype.matchall": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", + "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.1", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/string.prototype.padend": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.2.tgz", @@ -11675,6 +12334,36 @@ "integrity": "sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI=", "dev": true }, + "node_modules/styled-components": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.5.tgz", + "integrity": "sha512-ndETJ9RKaaL6q41B69WudeqLzOpY1A/ET/glXkNZ2T7dPjPqpPCXXQjDFYZWwNnE5co0wX+gTCqx9mfxTmSIPg==", + "hasInstallScript": true, + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, "node_modules/stylehacks": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.0.1.tgz", @@ -12226,7 +12915,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -12555,11 +13243,20 @@ "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "dev": true }, + "node_modules/tiny-invariant": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz", + "integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, "engines": { "node": ">=4" } @@ -13259,6 +13956,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -14369,7 +15071,6 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, "requires": { "@babel/highlight": "^7.14.5" } @@ -14451,13 +15152,20 @@ "version": "7.14.8", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz", "integrity": "sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg==", - "dev": true, "requires": { "@babel/types": "^7.14.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, + "@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "requires": { + "@babel/types": "^7.18.6" + } + }, "@babel/helper-compilation-targets": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz", @@ -14482,7 +15190,6 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", - "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.14.5", "@babel/template": "^7.14.5", @@ -14493,7 +15200,6 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", - "dev": true, "requires": { "@babel/types": "^7.14.5" } @@ -14502,7 +15208,6 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", - "dev": true, "requires": { "@babel/types": "^7.14.5" } @@ -14517,12 +15222,11 @@ } }, "@babel/helper-module-imports": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", - "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", - "dev": true, + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.18.6" } }, "@babel/helper-module-transforms": { @@ -14575,16 +15279,19 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", - "dev": true, "requires": { "@babel/types": "^7.14.5" } }, + "@babel/helper-string-parser": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", + "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==" + }, "@babel/helper-validator-identifier": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", - "dev": true + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==" }, "@babel/helper-validator-option": { "version": "7.14.5", @@ -14607,7 +15314,6 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", @@ -14617,14 +15323,30 @@ "@babel/parser": { "version": "7.14.8", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.8.tgz", - "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==", - "dev": true + "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==" + }, + "@babel/runtime": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", + "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/runtime-corejs3": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.18.9.tgz", + "integrity": "sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==", + "dev": true, + "requires": { + "core-js-pure": "^3.20.2", + "regenerator-runtime": "^0.13.4" + } }, "@babel/template": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", - "dev": true, "requires": { "@babel/code-frame": "^7.14.5", "@babel/parser": "^7.14.5", @@ -14635,7 +15357,6 @@ "version": "7.14.8", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.8.tgz", "integrity": "sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg==", - "dev": true, "requires": { "@babel/code-frame": "^7.14.5", "@babel/generator": "^7.14.8", @@ -14652,7 +15373,6 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -14660,18 +15380,17 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, "@babel/types": { - "version": "7.14.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", - "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", - "dev": true, + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz", + "integrity": "sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==", "requires": { - "@babel/helper-validator-identifier": "^7.14.8", + "@babel/helper-string-parser": "^7.18.10", + "@babel/helper-validator-identifier": "^7.18.6", "to-fast-properties": "^2.0.0" } }, @@ -14687,6 +15406,29 @@ "integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==", "dev": true }, + "@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "requires": { + "@emotion/memoize": "^0.8.0" + } + }, + "@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "@eslint/eslintrc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", @@ -14896,6 +15638,22 @@ "integrity": "sha512-VbjwR1fhsn2h2KXAY4oy1fm7dCxaKy0D+deTb8Ilc3Eo3rc5+5eA4rfYmZaHgNJKxVyI0f6WIXzO2zLkVmQPHA==", "dev": true }, + "@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true + }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dev": true, + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "@types/html-minifier-terser": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz", @@ -14939,6 +15697,15 @@ "@types/unist": "*" } }, + "@types/memoize-one": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/memoize-one/-/memoize-one-5.1.2.tgz", + "integrity": "sha512-9lItlM8Bf1DPvm8p4zE0vUn7YoTktEYgLd6Eva/PT0its200xmhaYrCMG/Y8615/f62SXOrDWMIEPk/xV+DQpw==", + "dev": true, + "requires": { + "memoize-one": "*" + } + }, "@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", @@ -14969,6 +15736,70 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "dev": true + }, + "@types/react": { + "version": "17.0.48", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.48.tgz", + "integrity": "sha512-zJ6IYlJ8cYYxiJfUaZOQee4lh99mFihBoqkOSEGV+dFi9leROW6+PgstzQ+w3gWTnUfskALtQPGHK6dYmPj+2A==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "17.0.17", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.17.tgz", + "integrity": "sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg==", + "dev": true, + "requires": { + "@types/react": "^17" + } + }, + "@types/react-router": { + "version": "5.1.18", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.18.tgz", + "integrity": "sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g==", + "dev": true, + "requires": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dev": true, + "requires": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, + "@types/styled-components": { + "version": "5.1.25", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.25.tgz", + "integrity": "sha512-fgwl+0Pa8pdkwXRoVPP9JbqF0Ivo9llnmsm+7TCI330kbPIFd9qv1Lrhr37shf4tnxCOSu+/IgqM7uJXLWZZNQ==", + "dev": true, + "requires": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, "@types/unist": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", @@ -15458,7 +16289,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -15469,6 +16299,16 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -15498,7 +16338,6 @@ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -15525,12 +16364,24 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, - "array.prototype.flat": { + "array.prototype.flat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", + "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "dev": true, + "peer": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.flatmap": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", - "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", + "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -15550,6 +16401,12 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", + "dev": true + }, "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -15583,6 +16440,12 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, + "axe-core": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.3.tgz", + "integrity": "sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==", + "dev": true + }, "axios": { "version": "0.21.4", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", @@ -15591,6 +16454,29 @@ "follow-redirects": "^1.14.0" } }, + "axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true + }, + "babel-plugin-styled-components": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", + "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11", + "picomatch": "^2.3.0" + } + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, "bail": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", @@ -15856,6 +16742,11 @@ "quick-lru": "^4.0.1" } }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha512-W2lPwkBkMZwFlPCXhIlYgxu+7gC/NUlCtdK652DAJ1JdgV0sTrvuPFshNPrFa1TY2JOkLhgdeEBplB4ezEa+xg==" + }, "caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -15878,7 +16769,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -16028,7 +16918,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -16036,8 +16925,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colord": { "version": "2.4.0", @@ -16105,8 +16993,7 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "dev": true, - "peer": true + "dev": true }, "connect-history-api-fallback": { "version": "1.6.0", @@ -16182,6 +17069,12 @@ } } }, + "core-js-pure": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.24.1.tgz", + "integrity": "sha512-r1nJk41QLLPyozHUUPmILCEMtMw24NG4oWK6RbsDdjzQgg9ZvrUsPBj1MnG0wXXp1DCDU6j+wUvEmBSrtRbLXg==", + "dev": true + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -16269,6 +17162,11 @@ } } }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" + }, "css-color-names": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-1.0.1.tgz", @@ -16340,32 +17238,21 @@ } }, "css-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.2.0.tgz", - "integrity": "sha512-/rvHfYRjIpymZblf49w8jYcRo2y9gj6rV8UroHGmBxKrIyGLokpycyKzp9OkitvqT29ZSpzJ0Ic7SpnJX3sC8g==", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", + "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", "dev": true, "requires": { "icss-utils": "^5.1.0", - "postcss": "^8.2.15", + "postcss": "^8.4.7", "postcss-modules-extract-imports": "^3.0.0", "postcss-modules-local-by-default": "^4.0.0", "postcss-modules-scope": "^3.0.0", "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.1.0", + "postcss-value-parser": "^4.2.0", "semver": "^7.3.5" }, "dependencies": { - "postcss": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz", - "integrity": "sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==", - "dev": true, - "requires": { - "colorette": "^1.2.2", - "nanoid": "^3.1.23", - "source-map-js": "^0.6.2" - } - }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -16457,6 +17344,16 @@ } } }, + "css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "css-tree": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", @@ -16558,6 +17455,18 @@ "css-tree": "^1.1.2" } }, + "csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", + "dev": true + }, + "damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -17015,7 +17924,6 @@ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", "dev": true, - "peer": true, "requires": { "has": "^1.0.3" } @@ -17046,8 +17954,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { "version": "8.21.0", @@ -17214,12 +18121,22 @@ } } }, + "eslint-config-airbnb": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", + "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" + } + }, "eslint-config-airbnb-base": { "version": "15.0.0", "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", "dev": true, - "peer": true, "requires": { "confusing-browser-globals": "^1.0.10", "object.assign": "^4.1.2", @@ -17231,8 +18148,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "peer": true + "dev": true } } }, @@ -17425,6 +18341,41 @@ "lodash": "^4.17.21" } }, + "eslint-plugin-jsx-a11y": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz", + "integrity": "sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==", + "dev": true, + "requires": { + "@babel/runtime": "^7.18.9", + "aria-query": "^4.2.2", + "array-includes": "^3.1.5", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.4.3", + "axobject-query": "^2.2.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.2", + "language-tags": "^1.0.5", + "minimatch": "^3.1.2", + "semver": "^6.3.0" + }, + "dependencies": { + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "eslint-plugin-lodash": { "version": "7.4.0", "resolved": "https://registry.npmjs.org/eslint-plugin-lodash/-/eslint-plugin-lodash-7.4.0.tgz", @@ -17443,6 +18394,70 @@ "peer": true, "requires": {} }, + "eslint-plugin-react": { + "version": "7.30.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.30.1.tgz", + "integrity": "sha512-NbEvI9jtqO46yJA3wcRF9Mo0lF9T/jhdHqhCHXiXtD+Zcb98812wvokjWpU7Q4QH5edo6dmqrukxVvWWXHlsUg==", + "dev": true, + "requires": { + "array-includes": "^3.1.5", + "array.prototype.flatmap": "^1.3.0", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.5", + "object.fromentries": "^2.0.5", + "object.hasown": "^1.1.1", + "object.values": "^1.1.5", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.3", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.7" + }, + "dependencies": { + "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" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "peer": true, + "requires": {} + }, "eslint-plugin-sonarjs": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.13.0.tgz", @@ -18237,8 +19252,7 @@ "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "globby": { "version": "11.1.0", @@ -18325,8 +19339,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-property-descriptors": { "version": "1.0.0", @@ -18396,6 +19409,29 @@ "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", "dev": true }, + "history": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", + "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", + "requires": { + "@babel/runtime": "^7.7.6" + } + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, "hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -19192,8 +20228,7 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { "version": "4.1.0", @@ -19207,8 +20242,7 @@ "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, "json-parse-better-errors": { "version": "1.0.2", @@ -19249,6 +20283,16 @@ "minimist": "^1.2.5" } }, + "jsx-ast-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.2.tgz", + "integrity": "sha512-4ZCADZHRkno244xlNnn4AOG6sRQ7iBZ5BbgZ4vW4y5IZw7cVUD1PPeblm1xx/nfmMxPdt/LHsXZW8z/j58+l9Q==", + "dev": true, + "requires": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.2" + } + }, "kebab-case": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/kebab-case/-/kebab-case-1.0.1.tgz", @@ -19280,6 +20324,21 @@ "integrity": "sha512-sZLUnTqimCkvkgRS+kbPlYW5o8q5w1cu+uIisKpEWkj31I8mx8kNG162DwRav8Zirkva6N5uoFsm9kzK4mUXjw==", "dev": true }, + "language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "dev": true, + "requires": { + "language-subtag-registry": "~0.3.2" + } + }, "last-call-webpack-plugin": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz", @@ -19407,6 +20466,14 @@ "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", "dev": true }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -19497,6 +20564,11 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, + "memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -19666,6 +20738,15 @@ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true }, + "mini-create-react-context": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", + "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", + "requires": { + "@babel/runtime": "^7.12.1", + "tiny-warning": "^1.0.3" + } + }, "mini-css-extract-plugin": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.1.0.tgz", @@ -19766,9 +20847,9 @@ "optional": true }, "nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", "dev": true }, "nanomatch": { @@ -19974,8 +21055,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", @@ -20056,13 +21136,33 @@ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", "es-abstract": "^1.19.1" } }, + "object.fromentries": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", + "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "object.hasown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", + "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", + "dev": true, + "requires": { + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", @@ -20077,7 +21177,6 @@ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -20138,19 +21237,6 @@ "cssnano": "^5.0.2", "last-call-webpack-plugin": "^3.0.0", "postcss": "^8.2.1" - }, - "dependencies": { - "postcss": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz", - "integrity": "sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==", - "dev": true, - "requires": { - "colorette": "^1.2.2", - "nanoid": "^3.1.23", - "source-map-js": "^0.6.2" - } - } } }, "optionator": { @@ -20340,11 +21426,16 @@ "pify": "^3.0.0" } }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" }, "pidtree": { "version": "0.3.1", @@ -20415,14 +21506,14 @@ "dev": true }, "postcss": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz", - "integrity": "sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==", + "version": "8.4.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", "dev": true, "requires": { - "colorette": "^1.2.2", - "nanoid": "^3.1.23", - "source-map-js": "^0.6.2" + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" } }, "postcss-calc": { @@ -21882,10 +22973,9 @@ } }, "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", - "dev": true + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "prelude-ls": { "version": "1.2.1", @@ -21909,6 +22999,23 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -21998,6 +23105,110 @@ "unpipe": "1.0.0" } }, + "react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "react-dom": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", + "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "peer": true + }, + "react-router": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.3.tgz", + "integrity": "sha512-mzQGUvS3bM84TnbtMYR8ZjKnuPJ71IjSzR+DE6UkUqvN4czWIqEs17yLL8xkAycv4ev0AiN+IGrWu88vJs/p2w==", + "requires": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.4.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "dependencies": { + "history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "requires": { + "isarray": "0.0.1" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, + "react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-Ov0tGPMBgqmbu5CDmN++tv2HQ9HlWDuWIIqn4b88gjlAN5IHI+4ZUZRcpz9Hl0azFIwihbLDYw1OiHGRo7ZIng==", + "requires": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.3.3", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "dependencies": { + "history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + } + } + }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -22137,6 +23348,11 @@ "strip-indent": "^3.0.0" } }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -22362,6 +23578,11 @@ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "dev": true }, + "resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -22438,6 +23659,15 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -22605,6 +23835,11 @@ "kind-of": "^6.0.2" } }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -22869,13 +24104,12 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", - "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "dev": true }, "source-map-resolve": { @@ -23110,6 +24344,22 @@ } } }, + "string.prototype.matchall": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", + "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.1", + "side-channel": "^1.0.4" + } + }, "string.prototype.padend": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.2.tgz", @@ -23192,6 +24442,23 @@ "integrity": "sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI=", "dev": true }, + "styled-components": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.5.tgz", + "integrity": "sha512-ndETJ9RKaaL6q41B69WudeqLzOpY1A/ET/glXkNZ2T7dPjPqpPCXXQjDFYZWwNnE5co0wX+gTCqx9mfxTmSIPg==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + } + }, "stylehacks": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.0.1.tgz", @@ -23607,7 +24874,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -23860,11 +25126,20 @@ "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "dev": true }, + "tiny-invariant": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz", + "integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" }, "to-object-path": { "version": "0.3.0", @@ -24405,6 +25680,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 633f8235d..3139685f9 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,17 @@ "download.js": "^1.0.0", "github-api": "^3.4.0", "hammerjs": "^2.0.8", + "history": "^5.0.0", "html-tags": "^3.1.0", "leaflet": "^1.7.1", "localforage": "^1.9.0", "lodash": "^4.17.21", + "memoize-one": "^5.1.1", "normalize.css": "^8.0.1", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-router-dom": "^5.1.2", + "styled-components": "^5.0.1", "svgio": "^0.4.0", "tryfunc": "^3.1.0", "tslib": "^2.4.0", @@ -25,9 +31,17 @@ "@types/hammerjs": "^2.0.40", "@types/leaflet": "^1.7.4", "@types/lodash": "^4.14.171", + "@types/memoize-one": "^5.1.2", + "@types/react": "^17.0.14", + "@types/react-dom": "^17.0.9", + "@types/react-router-dom": "^5.1.3", + "@types/styled-components": "^5.1.25", "copy-webpack-plugin": "^9.0.1", - "css-loader": "^6.2.0", + "css-loader": "^6.7.1", "eslint": "^8.21.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-plugin-jsx-a11y": "^6.6.1", + "eslint-plugin-react": "^7.30.1", "html-webpack-plugin": "^5.3.1", "mini-css-extract-plugin": "^2.1.0", "npm-run-all": "^4.1.5", diff --git a/src/Config.ts b/src/Config.ts index 8503f3012..a8f3838a3 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -5,6 +5,7 @@ export default interface Config { minZoom: number, maxZoom: number, detailedZoom: number, + detailedE: boolean, url: { [resource: string]: string, }, diff --git a/src/Metro/DummyContainer.tsx b/src/Metro/DummyContainer.tsx new file mode 100644 index 000000000..6ef0e91f3 --- /dev/null +++ b/src/Metro/DummyContainer.tsx @@ -0,0 +1,50 @@ +import React, { memo } from 'react' +import styled from 'styled-components' + +import memoizeObject from 'utils/memoizeObject' + +const memoDummyTransfersStyle = memoizeObject() +const memoDummyPlatformsStyle = memoizeObject() + +const DummyPlatforms = styled.g` + opacity: 0; + /* stroke: blue; */ + /* stroke-width: 0.5px; */ +` + +const DummyTransfers = styled.g` + opacity: 0; + + & path { + fill: none; + pointer-events: stroke; + } +` + +interface Props { + dummyTransfersStrokeWidth: number, + dummyPlatformsStrokeWidth: number, + mountDummyTransfers: (g: SVGGElement) => void, + mountDummyPlatforms: (g: SVGGElement) => void, +} + +const DummyContainer = (props: Props) => ( + <> + + + + +) + +export default memo(DummyContainer) diff --git a/src/Metro/MapContainer.tsx b/src/Metro/MapContainer.tsx new file mode 100644 index 000000000..05d41f246 --- /dev/null +++ b/src/Metro/MapContainer.tsx @@ -0,0 +1,209 @@ +import React, { + memo, + useState, + useCallback, + useMemo, +} from 'react' + +import { + Point, + LatLng, +} from 'leaflet' + +import { meanColor } from 'utils/color' + +import { + tryGetFromMap, +} from 'utils/collections' + +import Network, { + Platform, +} from '../network' + +import Config from '../Config' + +import makeWhiskers from './utils/makeWhiskers' +import makeCircumcircles from './utils/makeCircumcircles' + +import PositioningEngine from './PositioningEngine' +import Platforms from './Platforms' +import Transfers from './Transfers' +import Spans from './Spans' + +import { Containers as MetroContainers } from '.' + +const GRAY = '#999' +const BLACK = '#000' + +interface Props { + config: Config, + zoom: number, + lineRules: Map, + network: Network, + svgSizes: any, + containers: MetroContainers, + featuredPlatforms: Platform[] | null, + getPlatformPosition: (platform: Platform) => Point, + setFeaturedPlatforms: (platforms: Platform[] | null) => void, + latLngToOverlayPoint: (latLng: LatLng) => Point, +} + +function linesToColors(lines: Set, lineRules: Map): string[] { + const rgbs: string[] = [] + for (const line of lines) { + const { stroke } = tryGetFromMap(lineRules, line[0] === 'M' ? line : line[0]) + if (stroke) { + rgbs.push(stroke) + } + } + return rgbs +} + +const MapContainer = ({ + config, + zoom, + network, + lineRules, + svgSizes: { + lineWidth, + lightLineWidth, + circleBorder, + circleRadius, + transferWidth, + transferBorder, + fullCircleRadius, + }, + getPlatformPosition, + featuredPlatforms, + setFeaturedPlatforms, + containers: { + dummyTransfers, + dummyPlatforms, + defs, + }, +}: Props) => { + const [pathsInner, setPathsInner] = useState(null) + const [transfersInner, setTransfersInner] = useState(null) + + const isDetailed = useMemo(() => zoom >= config.detailedZoom, [zoom]) + const stationCircumpoints = useMemo(() => makeCircumcircles(network), [zoom]) + const whiskers = useMemo(() => makeWhiskers(network.stations, getPlatformPosition), [zoom]) + + const unsetFeaturedPlatforms = useCallback(() => { + setFeaturedPlatforms(null) + }, [setFeaturedPlatforms]) + + const getPlatformWhiskers = useCallback( + (platform: Platform) => tryGetFromMap(whiskers, platform), + [whiskers], + ) + + const getPlatformColor = useCallback( + (platform: Platform) => { + const passingLines = platform.passingLines() + + if (!isDetailed) { + // return BLACK + return config.detailedE && !platform.passingLines().has('E') ? GRAY : BLACK + } + + if (!config.detailedE) { + return meanColor(linesToColors(passingLines, lineRules)) + } + if (!passingLines.has('E')) { + return GRAY + } + // TODO: temp + return BLACK + // const line = passingLines.values().next().value + // return passingLines.size === 1 && tryGetFromMap(lineRules, line).stroke || BLACK + }, + [config, lineRules, isDetailed], + ) + + // const lineWidth = 2 ** (zoom / 4 - 1.75); + + const lightRailPathStyle = tryGetFromMap(lineRules, 'L') + lightRailPathStyle.strokeWidth = `${lightLineWidth}px` + + return ( + + {({ + getSpanSlotsScaled, + getSpanOffset, + getPlatformSlotPoints, + }) => ( + <> + {pathsInner && network.spans + && ( + + )} + + + + {transfersInner && dummyTransfers && defs && network.transfers + && ( + + )} + + {dummyPlatforms && network.platforms + && ( + + )} + + + + + )} + + ) +} + +export default memo(MapContainer) diff --git a/src/Metro/Platforms.tsx b/src/Metro/Platforms.tsx new file mode 100644 index 000000000..c1d03f37d --- /dev/null +++ b/src/Metro/Platforms.tsx @@ -0,0 +1,130 @@ +import React, { + memo, + useCallback, + useMemo, +} from 'react' +import styled from 'styled-components' +import { Point } from 'leaflet' + +import memoizeObject from 'utils/memoizeObject' + +import PlatformReact from 'components/Platform' + +import { + Platform, +} from '../network' + +const PlatformCircles = styled.g` + fill: white; + stroke: black; +` + +const emptyArray = [] as const + +const memoPlatformCirclesStyle = memoizeObject() + +const platformHasELines = (platform: Platform) => + Array.from(platform.passingLines()).some(l => l.startsWith('E')) + +function getDisplayedPlatforms(platforms: Platform[], isDetailed: boolean) { + const map = new WeakMap() + + if (isDetailed) { + // show all platforms + for (const p of platforms) { + map.set(p, [p]) + } + } else { + const stations = Array.from(new Set(platforms.map(p => p.station))) + for (const s of stations) { + const names = new Set(s.platforms.map(p => p.name)) + for (const name of names) { + const namesakes = s.platforms.filter(p => p.name === name) + + const firstEPlatform = namesakes.find(platformHasELines) + const platform = firstEPlatform || namesakes[0] + if (!platform) { + throw new Error('could not find platform, wtf') + } + + map.set(platform, namesakes) + } + } + } + + return map +} + +function getFeatured(platforms: Platform[]) { + const platform = platforms[0] + const { name } = platform + return platform.station.platforms.filter(p => p.name === name) +} + +interface Props { + platforms: Platform[], + isDetailed: boolean, + strokeWidth: number, + circleRadius: number, + dummyPlatforms: SVGGElement, + featuredPlatforms: Platform[] | null, + getPlatformSlotPoints: (platform: Platform) => Point | Point[], + getPlatformColor: (platform: Platform) => string, + setFeaturedPlatforms: (platform: Platform[]) => void, + unsetFeaturedPlatforms: () => void, +} + +const Platforms = ({ + platforms, + isDetailed, + strokeWidth, + circleRadius, + dummyPlatforms, + featuredPlatforms, + getPlatformSlotPoints, + getPlatformColor, + setFeaturedPlatforms, + unsetFeaturedPlatforms, +}: Props) => { + const displayedMap = useMemo( + () => getDisplayedPlatforms(platforms, isDetailed), + [platforms, isDetailed], + ) + const featuredSet = featuredPlatforms && new Set(featuredPlatforms) + + const setFeaturedPlatformsCb = useCallback((pls: Platform[]) => { + const featureds = isDetailed ? getFeatured(pls) : pls + setFeaturedPlatforms(featureds) + }, [isDetailed, setFeaturedPlatforms]) + + return ( + + {dummyPlatforms && platforms.map(platform => { + const slotPoints = getPlatformSlotPoints(platform) + const representedPlatforms = displayedMap.get(platform) + const radius = representedPlatforms ? circleRadius : 0 + const isFeatured = featuredSet?.has(platform) + + return ( + + ) + })} + + ) +} + +export default memo(Platforms) diff --git a/src/Metro/PositioningEngine.ts b/src/Metro/PositioningEngine.ts new file mode 100644 index 000000000..b8ef33c44 --- /dev/null +++ b/src/Metro/PositioningEngine.ts @@ -0,0 +1,317 @@ +import React, { PureComponent } from 'react' +import { Point } from 'leaflet' +import { + mean, +} from 'lodash' + +import { + zero as zeroVec, + normalize, + orthogonal, +} from 'utils/math/vector' + +import { + tryGetFromMap, +} from 'utils/collections' + +import Network, { + Platform, + Span, + Route, +} from '../network' + +import initSlots from './utils/initSlots' +import getPositions from './utils/getPositions' +import { + SlotPoints, +} from './utils/types' + +import optimizeSlots from './optimization/optimizeSlots' +import sortSpans from './optimization/sortSpans' +import costFunction from './optimization/costFunction' + +const GAP_BETWEEN_PARALLEL = 0 // 0 - none, 1 - line width + +const SOURCE_TARGET = ['source', 'target'] as const + +interface ChildrenFuncParams { + getSpanSlotsScaled: PositioningEngine['getSpanSlotsScaled'], + getSpanOffset: PositioningEngine['getSpanOffset'], + getPlatformSlotPoints: PositioningEngine['getPlatformSlotPoints'], +} + +export type ChildrenFunc = (o: ChildrenFuncParams) => React.ReactNode + +interface Props { + children: ChildrenFunc, + detailedE: boolean, + lineWidth: number, + network: Network, + getPlatformPosition: (platform: Platform) => Point, + getPlatformWhiskers: (platform: Platform) => Map, +} + +class PositioningEngine extends PureComponent { + private readonly platformSlots: WeakMap + + private readonly spanBatches = new Map() + private readonly parallelSpans: Span[][] = [] + + constructor(props: Props) { + super(props) + + this.platformSlots = initSlots(props.network.platforms) + this.updateBatches() + this.optimize() + } + + // center is 0, (-1, 0, 1 or -0.5, 0.5 ...) + private getPlatformSlotPosition(platform: Platform, route: Route) { + const map = this.platformSlots.get(platform) + if (!map) { + return 0 + } + + const index = map.indexOf(route) + if (index < 0) { + return 0 + } + + const routes = platform.passingRoutes() + + const leftShift = (routes.size - 1) / 2 + return index - leftShift + } + + private getSpanSlots = (span: Span) => { + const { + source, + target, + routes, + } = span + + if (this.props.detailedE && routes.length > 1) { + throw new Error('more routes per span than 1') + } + + const firstRoute = routes[0] + + return { + source: this.getPlatformSlotPosition(source, firstRoute), + target: this.getPlatformSlotPosition(target, firstRoute), + } + } + + private getSpanSlotPoints = (span: Span): SlotPoints => { + const { + getPlatformWhiskers, + } = this.props + + const slots = this.getSpanSlotsScaled(span) + + const [source, target] = SOURCE_TARGET.map((prop, i) => { + const platform = span[prop] + + const pos = this.props.getPlatformPosition(platform) + const controlPoint = tryGetFromMap(getPlatformWhiskers(platform), span) + const vec = controlPoint.subtract(pos) + + const ortho = orthogonal(vec)[i] + const normal = ortho.equals(zeroVec) ? ortho : normalize(ortho) + return normal.multiplyBy(slots[prop]).add(pos) + }) + + return { + source, + target, + } + } + + private updateBatches = () => { + // console.time('batches') + + const { + platformSlots, + } = this + + const { + network, + } = this.props + + this.spanBatches.clear() + this.parallelSpans.length = 0 + + const remainingSpans = new Set(network.spans) + for (const span of network.spans) { + if (!remainingSpans.has(span)) { + continue + } + + const sourceMap = platformSlots.get(span.source) + const targetMap = platformSlots.get(span.target) + + const parallelSpans = span.parallelSpans() + parallelSpans.push(span) + + const spanToSourceSlot = new Map() + + const sameDiffSpans = new Map() + for (const s of parallelSpans) { + remainingSpans.delete(s) + + const route = s.routes[0] + // TODO: check this shit + const sourceSlot = sourceMap ? sourceMap.indexOf(route) : 0 + const targetSlot = targetMap ? targetMap.indexOf(route) : 0 + + if (sourceSlot < 0 || targetSlot < 0) { + console.error('in span:', span) + throw new Error('route not found') + } + + const diff = targetSlot - sourceSlot + + spanToSourceSlot.set(s, sourceSlot) + + const ss = sameDiffSpans.get(diff) + if (ss) { + ss.push(s) + } else { + sameDiffSpans.set(diff, [s]) + } + } + + const entries = Array.from(sameDiffSpans.values()) + + for (const ss of entries) { + if (ss.length < 2) { + continue + } + const sourceSlots = ss.map(s => tryGetFromMap(spanToSourceSlot, s)) + const avgSourceSlot = mean(sourceSlots) + for (const [i, s] of ss.entries()) { + // normalize + this.spanBatches.set(s, sourceSlots[i] - avgSourceSlot) + } + this.parallelSpans.push(ss) + } + } + + // console.timeEnd('batches') + } + + private costFunction = (log = false) => { + const { + parallelSpans, + getSpanSlots, + getSpanSlotPoints, + } = this + + const { + network, + } = this.props + + return costFunction({ + network, + parallelSpans, + getSpanSlots, + getSpanSlotPoints, + log, + }) + } + + private optimize() { + const { + platformSlots, + spanBatches, + parallelSpans, + } = this + + const { + network, + } = this.props + + console.time('loops') + const cost = this.costFunction() + console.log('initial cost', cost) + + optimizeSlots({ + network, + platformSlots, + costFunc: this.costFunction, + updateBatches: this.updateBatches, + }) + + console.timeEnd('loops') + + console.log('batches', spanBatches) + console.log('slots', platformSlots) + + // TODO: how to save optimized state? + + console.log('total cost', this.costFunction(true)) + + sortSpans(network.spans, parallelSpans) + + this.updateBatches() // may be redundant + } + + private getSpanSlotsScaled = (span: Span) => { + const { lineWidth } = this.props + const lineWidthPlusGapPx = (GAP_BETWEEN_PARALLEL + 1) * lineWidth + const slots = this.getSpanSlots(span) + return { + source: slots.source * lineWidthPlusGapPx, + target: slots.target * lineWidthPlusGapPx, + } + } + + private getSpanOffset = (span: Span) => { + const offset = this.spanBatches.get(span) + if (!offset) { + return 0 + } + + const { lineWidth } = this.props + const lineWidthPlusGapPx = (GAP_BETWEEN_PARALLEL + 1) * lineWidth + return offset * lineWidthPlusGapPx + } + + private getPlatformSlotPoints = (platform: Platform) => { + const pos = this.props.getPlatformPosition(platform) + // get first whisker + const { value } = this.props.getPlatformWhiskers(platform).values().next() + if (!value || pos.equals(value)) { + // TODO WTF + return pos + } + + const slots = this.platformSlots.get(platform) + if (!slots) { + return pos + } + + const { lineWidth } = this.props + const lineWidthPlusGapPx = (GAP_BETWEEN_PARALLEL + 1) * lineWidth + const leftShift = (slots.length - 1) / 2 + const maxSlot = leftShift * lineWidthPlusGapPx + + return getPositions(pos, value, -maxSlot, maxSlot) + } + + render() { + const { + getSpanSlotsScaled, + getSpanOffset, + getPlatformSlotPoints, + } = this + + return this.props.children({ + getSpanSlotsScaled, + getSpanOffset, + getPlatformSlotPoints, + }) + } +} + +export default PositioningEngine diff --git a/src/Metro/Spans.tsx b/src/Metro/Spans.tsx new file mode 100644 index 000000000..e0e03be5a --- /dev/null +++ b/src/Metro/Spans.tsx @@ -0,0 +1,227 @@ +import React, { + useState, + memo, +} from 'react' +import styled from 'styled-components' +import { Point } from 'leaflet' + +import Portal from 'components/Portal' +import Bezier from 'components/primitives/Bezier' + +import * as math from 'utils/math' +import memoizeObject from 'utils/memoizeObject' + +import { + tryGetFromMap, +} from 'utils/collections' + +import { + Span, + Platform, +} from '../network' + +const E_COLORS = [ + '#000', + '#f00', // 1 + '#4af', // 2 + '#00f', // 3 + '#840', // 4 + '#0a0', // 5 + '#ff0', // 6 + '#c0c', // 7 + '#fc0', // 8 + '#ac0', // 9 +] + +const CURVE_SPLIT_NUM = 8 + +const Paths = styled.g` + fill: none; +` + +const Inner = styled(Paths)` +` + +const Outer = styled(Paths)` +` + +const PathsOuter = styled(Outer)` +` + +const PathsInner = styled(Inner)` +` + +const memoPathsOuterStyle = memoizeObject() +const memoPathsInnerStyle = memoizeObject() + +interface Props { + spans: Span[], + lineWidth: number, + lineRules: Map, + detailedE: boolean, + pathsInnerWrapper: Element, + getPlatformPosition: (platform: Platform) => Point, + getPlatformWhiskers: (platform: Platform) => Map, + getSpanSlotsScaled: (span: Span) => { source: number, target: number }, + getSpanOffset: (span: Span) => number, +} + +const Spans = ({ + spans, + lineWidth, + pathsInnerWrapper, + lineRules, + detailedE, + getPlatformPosition, + getPlatformWhiskers, + getSpanSlotsScaled, + getSpanOffset, +}: Props) => { + const [pathsInner, setPathsInner] = useState(null) + + const getControlPoints = (span: Span) => { + const { source, target } = span + const sourcePos = getPlatformPosition(source) + const targetPos = getPlatformPosition(target) + + const sourceControlPoint = tryGetFromMap(getPlatformWhiskers(source), span) + const targetControlPoint = tryGetFromMap(getPlatformWhiskers(target), span) + + const controlPoints = [ + sourcePos, + sourceControlPoint, + targetControlPoint, + targetPos, + ] + + const { + source: sourceSlot, + target: targetSlot, + } = getSpanSlotsScaled(span) + + const offset = getSpanOffset(span) + + if (sourceSlot === 0 && targetSlot === 0 && offset === 0) { + return [controlPoints] + } + + const sourceLine = controlPoints.slice(0, 2) + const targetLine = controlPoints.slice(2, 4) + + // TODO: figure out what to do with zero lines + const offsetSourceLine = sourceLine[0].equals(sourceLine[1]) + ? sourceLine + : math.offsetLine(sourceLine, sourceSlot - offset) + + const offsetTargetLine = targetLine[0].equals(targetLine[1]) + ? targetLine + : math.offsetLine(targetLine, targetSlot - offset) + + controlPoints[0] = offsetSourceLine[0] + controlPoints[1] = offsetSourceLine[1] + controlPoints[2] = offsetTargetLine[0] + controlPoints[3] = offsetTargetLine[1] + + if (offset === 0) { + return [controlPoints] + } + + const curves = math.split(controlPoints, CURVE_SPLIT_NUM) + const [head, ...tail] = curves.map(pa => math.offsetPath(pa, offset)) + return [head, ...tail.map(arr => arr.slice(1))] + } + + const makePath = (span: Span) => { + const { + routes, + source, + target, + } = span + + if (routes.length === 0) { + console.error(span, 'span has no routes!') + throw new Error('span has no routes!') + } + const tokens = routes[0].line.match(/([ELM])(\d{0,2})/) + if (!tokens) { + throw new Error(`match failed for ${source.name}-${target.name}`) + } + const [lineId, lineType] = tokens + + const controlPoints = getControlPoints(span) + + // drawBezierHints(this.overlay.origin, controlPoints, get(this.lineRules.get(lineId), 'stroke') as string) + const lineStyle = lineRules.get(lineType === 'M' ? routes[0].line : lineType) + const color = detailedE + ? (lineType === 'E' ? E_COLORS[routes[0].branch] : '#999') + : lineStyle && lineStyle.stroke + const bezier = ( + + ) + // bezier.id = 'op-' + spanIndex; + if (lineType === 'E') { + // bezier.classList.add('E') + // const { branch } = span.routes[0]; + // if (branch !== undefined) { + // bezier.classList.add('E' + branch); + // } + const inner = ( + + ) + // inner.id = 'ip-' + spanIndex; + // pool.outerEdgeBindings.set(span, bezier) + // pool.innerEdgeBindings.set(span, inner) + return [bezier, inner] + } + // if (lineId !== undefined) { + // bezier.classList.add(lineId) + // } + // if (lineType !== undefined) { + // bezier.classList.add(lineType) + // } else { + // bezier.style.stroke = 'black' + // } + // pool.outerEdgeBindings.set(span, bezier) + return [bezier] + } + + const outerStrokeWidth = lineWidth + const innerStrokeWidth = lineWidth / 2 + + return ( + <> + + {pathsInner && spans.map(span => { + const [outer] = makePath(span) + return outer + })} + + + + + + + ) +} + +export default memo(Spans) diff --git a/src/Metro/Transfers.tsx b/src/Metro/Transfers.tsx new file mode 100644 index 000000000..5b6601579 --- /dev/null +++ b/src/Metro/Transfers.tsx @@ -0,0 +1,220 @@ +import React, { + useState, + useCallback, + memo, +} from 'react' +import styled from 'styled-components' +import { Point } from 'leaflet' +import { + castArray, + difference, + uniq, + minBy, +} from 'lodash' + +import Portal from 'components/Portal' +import TransferReact from 'components/Transfer' + +import { getCircumcenter } from 'utils/math' +import memoizeObject from 'utils/memoizeObject' + +import { + Platform, + Station, + Transfer, +} from '../network' + +import cartesian from './utils/cartesian' + +type Pair = [T, T] +type Triple = [T, T, T] + +const Paths = styled.g` + fill: none; +` + +const Inner = styled(Paths)` +` + +const Outer = styled(Paths)` +` + +const TransfersOuter = styled(Outer)` + stroke: #404040; +` + +const TransfersInner = styled(Inner)` + stroke: #FFFFFF; +` + +const memoTransfersOuterStyle = memoizeObject() +const memoTransfersInnerStyle = memoizeObject() + +function getFeaturedPlatforms(transfer: Transfer) { + const { source, target } = transfer + const sourceName = source.name + const targetName = target.name + const sourceFeaturedPlatforms = source.station.platforms.filter(p => p.name === sourceName) + const targetFeaturedPlatforms = target.station.platforms.filter(p => p.name === targetName) + return uniq([...sourceFeaturedPlatforms, ...targetFeaturedPlatforms]) +} + +function getBestPair(sourcePositionArr: Point[], targetPositionArr: Point[]) { + const combos = cartesian(sourcePositionArr, targetPositionArr) as Pair[] + return minBy(combos, ([p1, p2]) => p1.distanceTo(p2))! +} + +function getBestTriplet(sourcePositionArr: Point[], targetPositionArr: Point[], thirdPos: Point) { + const thirdPositions = [thirdPos] + const combos = cartesian(sourcePositionArr, targetPositionArr, thirdPositions) as Triple[] + return minBy(combos, combo => { + const c = getCircumcenter(combo) + return c ? c.distanceTo(combo[0]) : Infinity + })! +} + +function getBestCombo(sourcePos: Point | Point[], targetPos: Point | Point[], thirdPos: Point | null) { + if (!Array.isArray(sourcePos) && !Array.isArray(targetPos)) { + return [sourcePos, targetPos, thirdPos] as const + } + + const sourcePositionArr = castArray(sourcePos) + const targetPositionArr = castArray(targetPos) + const bestCombo = thirdPos + ? getBestTriplet(sourcePositionArr, targetPositionArr, thirdPos) + : getBestPair(sourcePositionArr, targetPositionArr) + + if (!bestCombo) { + throw new Error('somehow best combo sucks') + } + return bestCombo +} + +interface Props { + transfers: Transfer[], + isDetailed: boolean, + stationCircumpoints: WeakMap, + featuredPlatforms: Platform[] | null, + transferWidth: number, + transferBorder: number, + fullCircleRadius: number, + transfersInnerWrapper: Element, + dummyTransfers: Element, + defs: Element, + getPlatformPosition: (platform: Platform) => Point, + getPlatformSlotPoints: (platform: Platform) => Point | Point[], + getPlatformColor: (platform: Platform) => string, + setFeaturedPlatforms: (platforms: Platform[]) => void, + unsetFeaturedPlatforms: () => void, +} + +const Transfers = ({ + transfers, + isDetailed, + stationCircumpoints, + featuredPlatforms, + transferWidth, + transferBorder, + fullCircleRadius, + transfersInnerWrapper, + dummyTransfers, + defs, + getPlatformPosition, + getPlatformSlotPoints, + getPlatformColor, + setFeaturedPlatforms, + unsetFeaturedPlatforms, +}: Props) => { + const [transfersInner, setTransfersInner] = useState(null) + + const getThirdPosition = (transfer: Transfer) => { + const { source, target } = transfer + const scp = stationCircumpoints.get(source.station) + const includes = scp && scp.includes(source) && scp.includes(target) + if (!includes) { + return null + } + const third = difference(scp, [source, target])[0] || undefined + return getPlatformPosition(third) + } + + const getPositions = (transfer: Transfer) => { + const { source, target } = transfer + const sourcePos = getPlatformSlotPoints(source) + const targetPos = getPlatformSlotPoints(target) + const thirdPos = getThirdPosition(transfer) + return getBestCombo(sourcePos, targetPos, thirdPos) + } + + const setFeaturedPlatformsMem = useCallback((transfer: Transfer) => { + const feat = getFeaturedPlatforms(transfer) + setFeaturedPlatforms(feat) + }, [setFeaturedPlatforms]) + + const outerStrokeWidth = transferWidth + transferBorder / 2 + const innerStrokeWidth = transferWidth - transferBorder / 2 + + const featuredPlatformsSet = featuredPlatforms && new Set(featuredPlatforms) + + return ( + <> + + {transfersInner && dummyTransfers && defs && transfers.map(transfer => { + const [ + sourcePos, + targetPos, + thirdPos, + ] = getPositions(transfer) + const { source, target } = transfer + + const isFeatured = featuredPlatformsSet + && featuredPlatformsSet.has(source) + && featuredPlatformsSet.has(target) + + const strokeWidth = transfer.type === 'osi' + ? outerStrokeWidth / 1.5 + : isFeatured + ? outerStrokeWidth * 1.25 + : undefined + + return ( + + ) + })} + + + + + + ) +} + +export default memo(Transfers) diff --git a/src/Metro/index.tsx b/src/Metro/index.tsx new file mode 100644 index 000000000..b1017cf1c --- /dev/null +++ b/src/Metro/index.tsx @@ -0,0 +1,137 @@ +import React, { + memo, + useState, + useMemo, +} from 'react' + +import { + Point, + point, + LatLng, +} from 'leaflet' + +import { + maxBy, + meanBy, +} from 'lodash' + +import TooltipReact from 'components/Tooltip' + +import ShadowFilter from 'components/filters/Shadow' +import BlackGlowFilter from 'components/filters/BlackGlow' +import GrayFilter from 'components/filters/Gray' +import OpacityFilter from 'components/filters/Opacity' + +import getSvgSizesByZoom from 'ui/getSvgSizesByZoom' + +import memoizeObject from 'utils/memoizeObject' +import { getPlatformNamesZipped } from 'utils/misc' + +import Network, { + Platform, +} from '../network' + +import Config from '../Config' + +import makeGetPlatformPositionFunc from './utils/makeGetPlatformPositionFunc' +import { SvgGContainer } from './utils/types' + +import DummyContainer from './DummyContainer' +import MapContainer from './MapContainer' + +export type Containers = SvgGContainer<'defs' | 'dummyTransfers' | 'dummyPlatforms'> + +interface Props { + config: Config, + zoom: number, + lineRules: Map, + network: Network, + latLngToOverlayPoint: (latLng: LatLng) => Point, +} + +const memoMapContainers = memoizeObject() + +function getTooltipPosition(featuredPlatforms: Platform[] | null, getPlatformPosition: (platform: Platform) => Point) { + if (!featuredPlatforms || featuredPlatforms.length === 0) { + return null + } + + const topmostPlatform = maxBy(featuredPlatforms, p => p.location.lat) + const topmostPosition = topmostPlatform && getPlatformPosition(topmostPlatform) + + const x = meanBy(featuredPlatforms, p => getPlatformPosition(p).x) + return topmostPosition ? point(x, topmostPosition.y) : null +} + +const Metro = ({ + config, + zoom, + lineRules, + network, + latLngToOverlayPoint, +}: Props) => { + const [defs, setDefs] = useState(null) + const [dummyTransfers, setDummyTransfers] = useState(null) + const [dummyPlatforms, setDummyPlatforms] = useState(null) + const [featuredPlatforms, setFeaturedPlatforms] = useState(null) + + const getPlatformPosition = useMemo(() => makeGetPlatformPositionFunc( + zoom >= config.detailedZoom, + network, + latLngToOverlayPoint, + ), [zoom]) + + const tooltipPos = getTooltipPosition(featuredPlatforms, getPlatformPosition) + const tooltipStrings = featuredPlatforms && getPlatformNamesZipped(featuredPlatforms) + + const svgSizes = getSvgSizesByZoom(zoom, config.detailedZoom) + + const { + circleBorder, + transferWidth, + transferBorder, + } = svgSizes + + return ( + <> + + + + + + + + + + + + + + ) +} + +export default memo(Metro) diff --git a/src/Metro/optimization/costFunction.ts b/src/Metro/optimization/costFunction.ts new file mode 100644 index 000000000..092f07c23 --- /dev/null +++ b/src/Metro/optimization/costFunction.ts @@ -0,0 +1,169 @@ +import { Point } from 'leaflet' + +import { + sumBy, + meanBy, + castArray, +} from 'lodash' + +import { + segmentsIntersect, +} from 'utils/math/vector' + +import { + tryGetFromMap, + iteratePairwise, +} from 'utils/collections' + +import Network, { + Span, +} from '../../network' + +import { + Slots, + SlotPoints, +} from '../utils/types' + +interface Options { + network: Network, + parallelSpans: Span[][], + getSpanSlots: (span: Span) => Slots, + getSpanSlotPoints: (span: Span) => SlotPoints, + log: boolean, +} + +export default ({ + network, + parallelSpans, + getSpanSlots, + getSpanSlotPoints, + log, +}: Options) => { + // distances (less) + // crossings (less) + + const spans = network.spans.filter(s => s.routes[0].line === 'E') + + let numCrossings = 0 + let numParallelCrossings = 0 + let numParallelMisallignments = 0 + + const misalignments: string[] = [] + + const numSpans = spans.length + + const map = new WeakMap() + + for (const span of spans) { + const slots = getSpanSlotPoints(span) + map.set(span, slots) + } + + const intersections: { [key: string]: boolean | undefined } = {} + const distances: { [id: string]: number } = {} + + for (let i = 0; i < numSpans; ++i) { + const span = spans[i] + + const { + source: sourcePoint, + target: targetPoint, + } = tryGetFromMap(map, span) + + distances[span.id] = sourcePoint.distanceTo(targetPoint) + + const arr = [sourcePoint, targetPoint] as const + + for (let j = i + 1; j < numSpans; ++j) { + const otherSpan = spans[j] + + const areParallel = span.isParallel(otherSpan) + + if (areParallel) { + const pt1 = getSpanSlots(span) + const pt2 = getSpanSlots(otherSpan) + const sourceDiff = pt2.source - pt1.source + const targetDiff = pt2.target - pt1.target + const diff = Math.abs(targetDiff - sourceDiff) + if (diff > Number.EPSILON) { + ++numParallelMisallignments + if (log) { + misalignments.push([span.source.name, span.target.name, span.routes[0].branch, otherSpan.routes[0].branch, +sourceDiff.toFixed(3), +targetDiff.toFixed(3)]) + } + } + } + + const { + source: otherSourcePoint, + target: otherTargetPoint, + } = tryGetFromMap(map, otherSpan) + + const doIntersect = !span.isContinuous(otherSpan) + && segmentsIntersect(arr, [otherSourcePoint, otherTargetPoint]) + + if (doIntersect) { + // console.log('intersection') + // console.log(span.source.name, span.target.name, span.routes[0].branch) + // console.log(otherSpan.source.name, otherSpan.target.name, otherSpan.routes[0].branch) + if (areParallel) { + if (log) { + console.log('intersection') + console.log(span.source.name, span.target.name, span.routes[0].branch) + console.log(otherSpan.source.name, otherSpan.target.name, otherSpan.routes[0].branch) + } + ++numParallelCrossings + } + intersections[`${span.id}:${otherSpan.id}`] = doIntersect + intersections[`${otherSpan.id}:${span.id}`] = doIntersect + } + } + } + + const remaining = new Set(spans) + for (const parallels of parallelSpans) { + for (const s of parallels) { + remaining.delete(s) + } + } + const augmentedParallelSpans = [...parallelSpans, ...Array.from(remaining).map(castArray)] + + iteratePairwise(augmentedParallelSpans, (a, b) => { + for (const s1 of a) { + for (const s2 of b) { + const doIntersect = intersections[`${s1.id}:${s2.id}`] + if (doIntersect) { + numCrossings += (a.length * b.length) ** 0.1 + return + } + } + } + }) + + const avgDist = meanBy(spans, s => distances[s.id]) + + const sum = sumBy( + parallelSpans, + ps => ps.length ** 8 * meanBy(ps, s => distances[s.id]) / avgDist, + ) + const parallelBatches = sum / numSpans + // console.log(spans.length, entries.length) + + // TODO: treat only adjacent parallel as parallel + + const totalCost = 150000 + + numParallelCrossings * 2000 + + numParallelMisallignments * 500 + // - neighborPoints * 300 + + numCrossings * 1 + - parallelBatches * 10 + + avgDist * 0 + + if (log) { + console.log('avg distance', avgDist) + console.log('num crossings', numCrossings) + console.log('num parallel crossings', numParallelCrossings) + console.log('misallignments', numParallelMisallignments, misalignments) + } + + return totalCost +} diff --git a/src/Metro/optimization/optimizeSlots.ts b/src/Metro/optimization/optimizeSlots.ts new file mode 100644 index 000000000..7378d0c77 --- /dev/null +++ b/src/Metro/optimization/optimizeSlots.ts @@ -0,0 +1,236 @@ +import { + clamp, + intersection, + lte, + random, + sample, + shuffle, +} from 'lodash' + +import { + tryGetFromMap, + swapArrayElements, +} from 'utils/collections' + +import Network, { + Platform, + Route, +} from '../../network' + +import getSegments from '../utils/getSegments' +import getPlatformBranches from '../utils/getPlatformBranches' +import makeAcceptanceFunc from '../utils/makeAcceptanceFunc' +import optimize from '../utils/optimize' + +function onAccept(newCost: number, prevCost: number, iteration: number) { + if (newCost !== prevCost) { + console.log(iteration, newCost) + } +} + +interface Options { + network: Network, + platformSlots: WeakMap, + costFunc: () => number, + updateBatches: () => void, +} + +export default ({ + network, + platformSlots, + costFunc, + updateBatches, +}: Options) => { + const platforms = network.platforms.filter(p => platformSlots.has(p)) + const segments = getSegments(platforms) + console.log('segments', segments) + + // initial primitive optimization (straigtening of segments) + console.log('straightening segments') + for (const segment of segments) { + const firstPlatform = segment[0] + const slots = tryGetFromMap(platformSlots, firstPlatform) + const shuffledSlots = shuffle(slots) + for (const p of segment) { + const pSlots = tryGetFromMap(platformSlots, p) + pSlots.splice(0, pSlots.length, ...shuffledSlots) + } + } + updateBatches() + + let cost = costFunc() + console.log('initial cost', cost) + const TOTAL_ITERATIONS = 100 + + const swapSpansOptions = { + costFunc, + shouldAccept: makeAcceptanceFunc(TOTAL_ITERATIONS, 20, 50), + onAccept, + move: () => { + const segment = sample(segments)! + const firstPlatform = segment[0] + const routes = tryGetFromMap(platformSlots, firstPlatform) + const max = routes.length - 1 + const slot1 = random(0, max) + const slot2 = random(0, max) + for (const p of segment) { + const slots = tryGetFromMap(platformSlots, p) + swapArrayElements(slots, slot1, slot2) + } + updateBatches() + return { + segment, + slot1, + slot2, + } + }, + restore: ({ segment, slot1, slot2 }) => { + for (const p of segment) { + const slots = tryGetFromMap(platformSlots, p) + swapArrayElements(slots, slot1, slot2) + } + updateBatches() + }, + } + + // cost = optimize(TOTAL_ITERATIONS, cost, swapSpansOptions) + + // move whole routes around + const platformBranches = getPlatformBranches(platforms) + const routeEntries = shuffle(Array.from(platformBranches)) + + console.log('swap routes') + + const swapRoutesOptions = { + costFunc, + shouldAccept: lte, + onAccept, + move: (i) => { + const [r1, ps1] = sample(routeEntries)! + const [r2, ps2] = sample(routeEntries)! + const commonPlatforms = intersection(ps1, ps2) + for (const p of commonPlatforms) { + const slots = tryGetFromMap(platformSlots, p) + const slot1 = slots.indexOf(r1) + const slot2 = slots.indexOf(r2) + swapArrayElements(slots, slot1, slot2) + } + updateBatches() + return { + commonPlatforms, + r1, + r2, + } + }, + restore: ({ commonPlatforms, r1, r2 }) => { + for (const p of commonPlatforms) { + const slots = tryGetFromMap(platformSlots, p) + const slot1 = slots.indexOf(r1) + const slot2 = slots.indexOf(r2) + swapArrayElements(slots, slot1, slot2) + } + updateBatches() + }, + } + + cost = optimize(TOTAL_ITERATIONS / 2, cost, swapRoutesOptions) + + const clampingOptions = { + costFunc, + shouldAccept: lte, + onAccept, + move: (i) => { + const [route, ps] = routeEntries[i % routeEntries.length] + const down = Math.random() < 0.5 + const swappedPlatforms = new Map() + for (const p of ps) { + const slots = tryGetFromMap(platformSlots, p) + const slot = slots.indexOf(route) + const newSlot = slot + (down ? 1 : -1) + const otherSlot = clamp(newSlot, 0, slots.length - 1) + if (slot === otherSlot) { + continue + } + const otherRoute = slots[otherSlot] + swapArrayElements(slots, slot, otherSlot) + swappedPlatforms.set(p, otherRoute) + } + updateBatches() + return { + route, + swappedPlatforms, + } + }, + restore: ({ route, swappedPlatforms }) => { + for (const [p, otherRoute] of swappedPlatforms) { + const slots = tryGetFromMap(platformSlots, p) + const slot = slots.indexOf(otherRoute) + const otherSlot = slots.indexOf(route) + swapArrayElements(slots, slot, otherSlot) + } + updateBatches() + }, + } + + console.log('moving routes (with clamping)', routeEntries) + + cost = optimize(TOTAL_ITERATIONS * 2, cost, clampingOptions) + + // console.log('swapping routes again') + // cost = optimize(TOTAL_ITERATIONS / 3, cost, swapRoutesOptions) + const minThreeRoutePlatforms = segments.filter(pa => pa[0].passingRoutes().size > 2) + + if (minThreeRoutePlatforms.length > 0) { + console.log('rotating routes') + cost = optimize(TOTAL_ITERATIONS / 3, cost, { + costFunc, + shouldAccept: lte, + onAccept, + move: (i) => { + const down = Math.random() < 0.5 + const segment = sample(minThreeRoutePlatforms)! + + // rotate + for (const p of segment) { + const slots = tryGetFromMap(platformSlots, p) + if (down) { + const last = slots.pop()! + slots.unshift(last) + } else { + const first = slots.shift()! + slots.push(first) + } + } + updateBatches() + return { + segment, + down, + } + }, + restore: ({ segment, down }) => { + for (const p of segment) { + const slots = tryGetFromMap(platformSlots, p) + if (!down) { + const last = slots.pop()! + slots.unshift(last) + } else { + const first = slots.shift()! + slots.push(first) + } + } + updateBatches() + }, + }) + } + + console.log('finally') + + cost = optimize(TOTAL_ITERATIONS / 2, cost, { + ...swapSpansOptions, + shouldAccept: lte, + }) + + console.log('moving routes (with clamping) again', routeEntries) + + cost = optimize(TOTAL_ITERATIONS / 2, cost, clampingOptions) +} diff --git a/src/Metro/optimization/sortSpans.ts b/src/Metro/optimization/sortSpans.ts new file mode 100644 index 000000000..f7d92e5f8 --- /dev/null +++ b/src/Metro/optimization/sortSpans.ts @@ -0,0 +1,19 @@ +import { + orderBy, +} from 'lodash' + +import Span from '../../network/Span' + +const makeIteratee = (parallelSpans: Span[][]) => + (span: Span) => { + const parallels = parallelSpans.find(ss => ss.includes(span)) + const numParallels = parallels ? parallels.length : 1 + const eFactor = span.routes[0].line === 'E' ? 0 : Infinity + return eFactor + numParallels + } + +export default (spans: Span[], parallelSpans: Span[][]) => { + const iteratee = makeIteratee(parallelSpans) + const sortedSpans = orderBy(spans, iteratee, 'desc') + spans.splice(0, spans.length, ...sortedSpans) +} diff --git a/src/Metro/utils/cartesian.ts b/src/Metro/utils/cartesian.ts new file mode 100644 index 000000000..4874a1840 --- /dev/null +++ b/src/Metro/utils/cartesian.ts @@ -0,0 +1,7 @@ +const f = (a, b) => + [].concat(...a.map(d => b.map(e => [].concat(d, e)))) + +const cartesian = (a: any[] | null, b: any[] | null, ...c: (any[] | null)[]): any[][] => + (b ? cartesian(f(a, b), ...c) : a) + +export default cartesian diff --git a/src/Metro/utils/getPlatformBranches.ts b/src/Metro/utils/getPlatformBranches.ts new file mode 100644 index 000000000..f2d44c6f2 --- /dev/null +++ b/src/Metro/utils/getPlatformBranches.ts @@ -0,0 +1,21 @@ +import Platform from 'network/Platform' +import Route from 'network/Route' + +import { + getOrMakeInMap, +} from 'utils/collections' + +/** + * route -> platforms + */ +export default (platforms: Platform[]): Map => { + const map = new Map() + for (const platform of platforms) { + const passingRoutes = platform.passingRoutes() + for (const route of passingRoutes) { + const arr = getOrMakeInMap(map, route, []) + arr.push(platform) + } + } + return map +} diff --git a/src/Metro/utils/getPositions.ts b/src/Metro/utils/getPositions.ts new file mode 100644 index 000000000..c1c2a3ae9 --- /dev/null +++ b/src/Metro/utils/getPositions.ts @@ -0,0 +1,22 @@ +import { Point } from 'leaflet' +import { memoize } from 'lodash' + +import { + orthogonal, + normalize, +} from 'utils/math/vector' + +function getPositions(pos: Point, value: Point, minOffset: number, maxOffset: number) { + const vec = value.subtract(pos) + const ortho = orthogonal(vec)[0] + const normal = normalize(ortho) + return [ + normal.multiplyBy(minOffset).add(pos), + normal.multiplyBy(maxOffset).add(pos), + ] +} + +export default memoize( + getPositions, + (pos, value, minOffset, maxOffset) => `${pos.x};${pos.y};${value.x};${value.y};${minOffset};${maxOffset}`, +) diff --git a/src/Metro/utils/getSegments.ts b/src/Metro/utils/getSegments.ts new file mode 100644 index 000000000..ed34732f8 --- /dev/null +++ b/src/Metro/utils/getSegments.ts @@ -0,0 +1,39 @@ +import { + xor, +} from 'lodash' + +import Platform from 'network/Platform' + +function walk(platform: Platform, patches: Set): Set { + patches.add(platform) + const passingRoutes = Array.from(platform.passingRoutes()) + const neighbors = platform.adjacentPlatformsBySpans() + for (const p of neighbors) { + if (patches.has(p) || xor(passingRoutes, Array.from(p.passingRoutes())).length !== 0) { + continue + } + walk(p, patches) + } + return patches +} + +const getCloud = (platform: Platform) => walk(platform, new Set()) + +/** + * patches of platforms with the same routes (exactly) + */ +export default (platforms: Platform[]): Platform[][] => { + const remainingPlatforms = new Set(platforms) + const patches: Platform[][] = [] + for (const platform of platforms) { + if (!remainingPlatforms.has(platform)) { + continue + } + const patch = Array.from(getCloud(platform)) + for (const p of patch) { + remainingPlatforms.delete(p) + } + patches.push(patch) + } + return patches +} diff --git a/src/Metro/utils/initSlots.ts b/src/Metro/utils/initSlots.ts new file mode 100644 index 000000000..a896823cb --- /dev/null +++ b/src/Metro/utils/initSlots.ts @@ -0,0 +1,48 @@ +import { + shuffle, +} from 'lodash' + +import { + Platform, + Route, +} from '../../network' + +export default (platforms: Iterable) => { + const platformSlots = new WeakMap() + + for (const platform of platforms) { + const routeSet = platform.passingRoutes() + if (routeSet.size === 1) { + continue + } + const routes = Array.from(routeSet) + + // if (!config.detailedE) { + // const lineSet = platform.passingLines() + // if (lineSet.size === 1) { + // continue + // } + // const lines = Array.from(lineSet) + // const leftShift = (routes.length - 1) / 2 + + // for (let i = 0; i < lines.length; ++i) { + // const slot = (i - leftShift) * lineWidthPlusGapPx + // const map = getOrMakeInMap(platformSlots, platform, () => new Map()) + // const line = lines[i] + // for (const route of routes) { + // if (route.line !== line) { + // continue + // } + // map.set(route, slot) + // } + // const route = routes[i] + // map.set(route, slot) + // } + // return + // } + + platformSlots.set(platform, shuffle(routes)) + } + + return platformSlots +} diff --git a/src/Metro/utils/makeAcceptanceFunc.ts b/src/Metro/utils/makeAcceptanceFunc.ts new file mode 100644 index 000000000..0285ffbfa --- /dev/null +++ b/src/Metro/utils/makeAcceptanceFunc.ts @@ -0,0 +1,13 @@ +const MIN = 1 + +export default (numIterations: number, max: number, power: number) => { + const diff = max - MIN + return (newCost: number, prevCost: number, i: number) => { + const temperature = 1 - (i / numIterations) + const b = temperature ** power + const threshold = MIN + (b * diff) + + const ratio = newCost / prevCost + return ratio <= threshold + } +} diff --git a/src/Metro/utils/makeCircumCircles.ts b/src/Metro/utils/makeCircumCircles.ts new file mode 100644 index 000000000..a3380efaa --- /dev/null +++ b/src/Metro/utils/makeCircumCircles.ts @@ -0,0 +1,19 @@ +import findCycle from 'utils/algorithm/findCycle' + +import Network, { + Platform, + Station, +} from '../../network' + +export default (network: Network) => { + const stationCircumpoints = new WeakMap() + + for (const station of network.stations) { + const circular = findCycle(network, station) + if (circular.length > 0) { + stationCircumpoints.set(station, circular) + } + } + + return stationCircumpoints +} diff --git a/src/Metro/utils/makeGetPlatformPositionFunc.ts b/src/Metro/utils/makeGetPlatformPositionFunc.ts new file mode 100644 index 000000000..552d28ed7 --- /dev/null +++ b/src/Metro/utils/makeGetPlatformPositionFunc.ts @@ -0,0 +1,77 @@ +import { + Point, + LatLng, +} from 'leaflet' + +import getCenter from 'utils/geo/getCenter' +import { + tryGetFromMap, +} from 'utils/collections' + +import Network, { + Platform, +} from '../../network' + +type LatLngToOverlayPoint = (latLng: LatLng) => Point + +function getPlatformsPositionOnOverlayDetailed(network: Network, latLngToOverlayPoint: LatLngToOverlayPoint) { + const positions = new WeakMap() + + // all platforms are in their place + for (const station of network.stations) { + for (const platform of station.platforms) { + const pos = latLngToOverlayPoint(platform.location) + positions.set(platform, pos) + } + } + return positions +} + +function getPlatformsPositionOnOverlayNonDetailed(network: Network, latLngToOverlayPoint: LatLngToOverlayPoint) { + const positions = new WeakMap() + + for (const station of network.stations) { + const nameSet = new Set() + const center = latLngToOverlayPoint(station.getCenter()) + const { platforms } = station + for (const platform of platforms) { + nameSet.add(platform.name) + positions.set(platform, center) + } + if (nameSet.size === 1) { + continue + } + // unless... + if (nameSet.size < 1) { + console.error(station) + throw new Error('station has no names') + } + const posByName = new Map() + for (const name of nameSet) { + const locations = platforms.filter(p => p.name === name).map(p => p.location) + const geoCenter = getCenter(locations) + posByName.set(name, latLngToOverlayPoint(geoCenter)) + } + for (const platform of platforms) { + const pos = tryGetFromMap(posByName, platform.name) + positions.set(platform, pos) + } + } + + return positions +} + +export default ( + isDetailed: boolean, + network: Network, + latLngToOverlayPoint: LatLngToOverlayPoint, +) => { + const func = isDetailed + ? getPlatformsPositionOnOverlayDetailed + : getPlatformsPositionOnOverlayNonDetailed + + const positions = func(network, latLngToOverlayPoint) + + return (platform: Platform) => + tryGetFromMap(positions, platform) +} diff --git a/src/Metro/utils/makeWhiskers.ts b/src/Metro/utils/makeWhiskers.ts new file mode 100644 index 000000000..f8c80f177 --- /dev/null +++ b/src/Metro/utils/makeWhiskers.ts @@ -0,0 +1,132 @@ +import { Point } from 'leaflet' +import { + xor, +} from 'lodash' + +import { tryGetFromMap } from 'utils/collections' +import { makeWings } from 'utils/math' +import { + mean as meanPoint, + normalize, +} from 'utils/math/vector' + +import { + Station, + Platform, + Span, +} from '../../network' + +type Bound = 'inbound' | 'outbound' +const SPAN_PROPS = ['inbound', 'outbound'] as const + +type Positions = { + [P in Bound]: Point +} + +// for termini with multiple converging routes +function getImaginaryPosition( + platform: Platform, + getPlatformPosition: (platform: Platform) => Point, +) { + const pos = getPlatformPosition(platform) + const neighbors = platform.adjacentPlatformsBySpans() + const neighborsPositions = neighbors.map(getPlatformPosition) + const cofficient = -0.1 // somehow any negative number works + return meanPoint(neighborsPositions) + .subtract(pos) + .multiplyBy(cofficient) + .add(pos) +} + +function makeWhiskersForPlatform( + platform: Platform, + getPlatformPosition: (platform: Platform) => Point, +): Map { + const PART = 0.5 + const pos = getPlatformPosition(platform) + const whiskers = new Map() + const allSpans = platform.getAllSpans() + + if (allSpans.length === 0) { + return whiskers + } + + if (allSpans.length === 1) { + return whiskers.set(allSpans[0], pos) + } + + if (allSpans.length === 2) { + // TODO: fix source/target in graph + // const { inbound, outbound } = spans + // const boundSpans = inbound.length === 2 ? inbound : outbound.length === 2 ? outbound : null + // if (boundSpans) { + // return whiskers.set(boundSpans[0], pos).set(boundSpans[1], pos) + // } + + // const inboundSpan = inbound[0] + // const outboundSpan = outbound[0] + + const [inboundSpan, outboundSpan] = allSpans + const areSameBound = xor(inboundSpan.routes, outboundSpan.routes).length > 0 + if (!areSameBound) { + const prevPos = getPlatformPosition(inboundSpan.other(platform)) + const nextPos = getPlatformPosition(outboundSpan.other(platform)) + const wings = makeWings(prevPos, pos, nextPos, 1) + const t = Math.min(pos.distanceTo(prevPos), pos.distanceTo(nextPos)) * PART + whiskers.set(inboundSpan, wings[0].multiplyBy(t).add(pos)) + whiskers.set(outboundSpan, wings[1].multiplyBy(t).add(pos)) + return whiskers + } + } + + const positions: Positions = {} as any + const distances = new WeakMap() + + for (const bound of SPAN_PROPS) { + const boundSpans = platform.spans[bound] + const normals: Point[] = [] + for (const span of boundSpans) { + const neighbor = span.other(platform) + const neighborPos = getPlatformPosition(neighbor) + const normal = normalize(neighborPos.subtract(pos)) + normals.push(normal) + distances.set(span, pos.distanceTo(neighborPos)) + } + positions[bound] = normals.length > 0 + ? meanPoint(normals).add(pos) + : getImaginaryPosition(platform, getPlatformPosition) + } + + const wings = makeWings(positions.inbound, pos, positions.outbound, 1) + const wObj: Positions = { + inbound: wings[0], + outbound: wings[1], + } + + for (const bound of SPAN_PROPS) { + const boundSpans = platform.spans[bound] + const wing = wObj[bound] + for (const span of boundSpans) { + const t = tryGetFromMap(distances, span) * PART + const end = wing.multiplyBy(t).add(pos) + whiskers.set(span, end) + } + } + return whiskers +} + +export default ( + stations: Iterable, + getPlatformPosition: (platform: Platform) => Point, +) => { + const whiskers = new WeakMap>() + + for (const station of stations) { + for (const platform of station.platforms) { + const wh = makeWhiskersForPlatform(platform, getPlatformPosition) + whiskers.set(platform, wh) + } + } + + return whiskers +} diff --git a/src/Metro/utils/optimize.ts b/src/Metro/utils/optimize.ts new file mode 100644 index 000000000..bdd7c478e --- /dev/null +++ b/src/Metro/utils/optimize.ts @@ -0,0 +1,32 @@ +interface Options { + move: (iteration: number) => T, + costFunc: () => number, + shouldAccept: (newCost: number, prevCost: number, iteration: number) => boolean, + onAccept?: (newCost: number, prevCost: number, iteration: number) => void, + restore?: (snapshot: T, iteration: number) => void, +} + +export default (totalIterations: number, initialCost: number, { + costFunc, + shouldAccept, + move, + restore, + onAccept, +}: Options) => { + let prevCost = initialCost + for (let i = 0; i < totalIterations; ++i) { + const snapshot = move(i) + const newCost = costFunc() + if (shouldAccept(newCost, prevCost, i)) { + if (onAccept) { + onAccept(newCost, prevCost, i) + } + prevCost = newCost + continue + } + if (restore) { + restore(snapshot, i) + } + } + return prevCost +} diff --git a/src/Metro/utils/types.ts b/src/Metro/utils/types.ts new file mode 100644 index 000000000..19e0f35b1 --- /dev/null +++ b/src/Metro/utils/types.ts @@ -0,0 +1,14 @@ +import { Point } from 'leaflet' + +export type SvgGContainer = { + [P in PropName]: SVGGraphicsElement | null +} + +export type SourceOrTarget = 'source' | 'target' + +export type BySourceOrTarget = { + [P in SourceOrTarget]: T +} + +export type Slots = BySourceOrTarget +export type SlotPoints = BySourceOrTarget diff --git a/src/MetroMap.ts b/src/MetroMap.ts deleted file mode 100644 index a4233ea8d..000000000 --- a/src/MetroMap.ts +++ /dev/null @@ -1,895 +0,0 @@ -import L from 'leaflet' -import unblur from 'unblur' -import { - difference, - intersection, - uniqueId, - maxBy, -} from 'lodash' - -import getSvgSizesByZoom from 'ui/getSvgSizesByZoom' -import addLayerSwitcher from 'ui/addLayerSwitcher' -import DistanceMeasure from 'ui/DistanceMeasure' -import SvgOverlay from 'ui/SvgOverlay' -import ContextMenu from 'ui/ContextMenu' -import RoutePlanner from 'ui/RoutePlanner' -import Tooltip from 'ui/Tooltip' -import FAQ from 'ui/FAQ' -// import drawZones from 'ui/drawZones' - -import { - // mapbox, - // mapbox2, - mapnik, - osmFrance, - openMapSurfer, - cartoDBNoLabels, - wikimapia, -} from 'ui/tilelayers' - -import { - getPlatformNames, - // getPlatformNamesZipped, - // midPointsToEnds, -} from 'utils/misc' - -import { - byId, - removeAllChildren, -} from 'utils/dom' - -import * as math from 'utils/math' -import * as svg from 'utils/svg' -import { getJSON } from 'utils/http' -import getCenter from 'utils/geo/getCenter' -// import geometricMedian from 'utils/geo/geometricMedian' -import { meanColor } from 'utils/color' -import MetroMapEventMap from 'utils/MetroMapEventMap' -import Mediator from 'utils/Mediator' - -import { - tryGetFromMap, - tryGetKeyFromBiMap, - getOrMakeInMap, -} from 'utils/collections' - -import * as scale from 'utils/sfx/scale' -import findCycle from 'utils/algorithm/findCycle' -import * as gradients from 'utils/svg/gradients' -import { create as createBezier } from 'utils/svg/bezier' -import { appendAll as appendAllFilters } from 'utils/svg/filters' -// import drawBezierHints from 'utils/dev/bezierHints' - -import { - mean, - normalize, - unit, - angle, -} from 'utils/math/vector' - -import 'leaflet/dist/leaflet.css' - -import Config from './Config' -import pool from './ObjectPool' -import getLineRules from './getLineRules' - -import Network, { - Platform, - Station, - Span, - Transfer, - GraphJSON, -} from './network' - -const alertifyPromise = import(/* webpackChunkName: "alertify" */ 'ui/alertify') - -const GAP_BETWEEN_PARALLEL = 0 // 0 - none, 1 - line width -const CURVE_SPLIT_NUM = 10 - -const groupIds = [ - 'paths-outer', - 'paths-inner', - 'transfers-outer', - 'station-circles', - 'transfers-inner', - 'dummy-circles', -] - -const contextMenuArray = [ - { - event: 'routefrom', - text: 'Route from here', - }, - { - event: 'routeto', - text: 'Route to here', - }, - { - event: 'clearroute', - text: 'Clear route', - }, - { - event: 'showheatmap', - text: 'Show heatmap', - extra: { - disabled: true, - }, - }, -] - -export default class { - readonly mediator = new Mediator() - protected readonly config: Config - protected map: L.Map - private moving = false - protected overlay: SvgOverlay - protected readonly contextMenu = new ContextMenu(contextMenuArray as any) - - protected network: Network - private lineRules: Map - protected readonly whiskers = new WeakMap>() - private readonly platformOffsets = new Map>() - protected readonly platformsOnSVG = new WeakMap() - - protected readonly tooltip = new Tooltip() - - // private routeWorker = new Worker('js/routeworker.js'); - - getMap(): L.Map { - return this.map - } - - getNetwork(): Network { - return this.network - } - - constructor(config: Config) { - this.config = config - this.makeMap() - } - - protected async makeMap() { - try { - const { config } = this - const networkPromise = this.getGraph() - const lineRulesPromise = getLineRules(config.url.scheme) - const dataPromise = getJSON(config.url.data) - - const tileLoadPromise = new Promise(resolve => { - cartoDBNoLabels.once('load', resolve) - setTimeout(resolve, 5000) - }) - - // wait.textContent = 'making map...'; - - config.center = [0, 0] - const mapOptions = { ...config } - const scaleControl = L.control.scale({ - imperial: false, - }) - this.map = L.map(config.containerId, mapOptions).addControl(scaleControl) - const mapPaneStyle = this.map.getPanes().mapPane.style - mapPaneStyle.visibility = 'hidden' - - addLayerSwitcher(this.map, [ - cartoDBNoLabels, - // mapbox, - mapnik, - osmFrance, - // mapbox2, - openMapSurfer, - wikimapia, - ]) - - // wait.textContent = 'loading graph...'; - this.addContextMenu() - - const json = await networkPromise - this.network = new Network(json) - const platformLocations = this.network.platforms.map(p => p.location) - const center = getCenter(platformLocations) - config.center = [center.lat, center.lng] - const bounds = L.latLngBounds(platformLocations) - this.overlay = new SvgOverlay(bounds).addTo(this.map) - const { defs } = this.overlay - appendAllFilters(defs) - - const { - default: alertify, - confirm, - } = await alertifyPromise - window.addEventListener('keydown', async e => { - if (!e.shiftKey || !e.ctrlKey || e.key !== 'r' || !await confirm('Reset network?')) { - return - } - const graph = await this.getGraph() - this.resetNetwork(graph) - }) - - const { textContent } = defs - if (!textContent) { - alertify.alert(` - Your browser doesn't seem to have capabilities to display some features of the map. - Consider using Chrome or Firefox for the best experience. - `) - } - - this.lineRules = await lineRulesPromise - // wait.textContent = 'adding content...'; - this.resetMapView() - this.map.addLayer(cartoDBNoLabels) - this.map.on('overlayupdate', () => { - this.moving = true - this.redrawNetwork() - this.moving = false - // console.time('conversion'); - // file.svgToPicture(document.getElementById('overlay') as any).then(img => { - // document.body.appendChild(img); - // console.timeEnd('conversion'); - // }); - }) - this.initNetwork() - // TODO: fix the kludge making the grey area disappear - this.map.invalidateSize(false) - this.addMapListeners() - new RoutePlanner().addTo(this) - new DistanceMeasure().addTo(this.map) - // this.routeWorker.postMessage(this.network); - // drawZones(this.map, this.network.platforms); - - dataPromise.then(data => new FAQ(data).addTo(this.map)) - // wait.textContent = 'loading tiles...'; - - await tileLoadPromise - // wait.parentElement.removeChild(wait); - mapPaneStyle.visibility = '' - // const img = file.svgToImg(document.getElementById('overlay') as any, true); - // file.svgToCanvas(document.getElementById('overlay') as any) - // .then(canvas => fFile.downloadText('svg.txt', canvas.toDataURL('image/png'))); - // file.downloadText('img.txt', img.src); - this.runUnblur() - } catch (err) { - console.error(err) - } - } - - runUnblur() { - this.map - .on('movestart', () => { - this.moving = true - }) - .on('moveend', () => { - this.moving = false - }) - unblur({ - skipIf: () => this.moving, - interval: 250, - }) - } - - subscribe(type: K, listener: (e: MetroMapEventMap[K]) => void) { - this.mediator.subscribe(type, listener) - // forwarding map event to mediator - this.map.on(type, this.mediator.publish) - } - - private addContextMenu() { - for (const el of contextMenuArray) { - this.map.on(el.event, this.mediator.publish) - } - this.contextMenu.addTo(this.map) - } - - protected addMapListeners() { - const { map, contextMenu } = this - - map.on('zoomstart', () => { - this.tooltip.hide() - }) - - map.on('distancemeasureinit', () => { - contextMenu.insertItem('measuredistance', 'Measure distance') - - map.on('clearmeasurements', () => { - contextMenu.removeItem('clearmeasurements') - contextMenu.insertItem('measuredistance', 'Measure distance') - }) - - this.subscribe('measuredistance', () => { - contextMenu.removeItem('measuredistance') - contextMenu.insertItem('clearmeasurements', 'Clear measurements') - }) - }) - } - - /** call only once! */ - private initNetwork() { - const { origin } = this.overlay - - for (const groupId of groupIds) { - const g = svg.createSVGElement('g') - g.id = groupId - origin.appendChild(g) - } - - origin.insertBefore(this.tooltip.element, document.getElementById('dummy-circles')) - this.redrawNetwork() - this.addStationListeners() - } - - private resetMapView() { - // const fitness = (points, pt) => points.reduce((prev, cur) => this.bounds., 0); - // const center = geometricMedian(this.network.platforms.map(p => p.location), fitness, 0.1); - const { center, zoom } = this.config - const options = { - animate: false, - } - if (!center) { - console.error('cannot set map to center') - return - } - this.map.setView(center, zoom + 1, options) - this.map.setView(center, zoom, options) - } - - private getGraph(): Promise { - return getJSON(this.config.url.graph) - } - - protected resetNetwork(json: GraphJSON) { - this.network = new Network(json) - this.redrawNetwork() - } - - private cleanElements() { - const { overlay, tooltip } = this - const { element } = tooltip - for (const child of overlay.origin.childNodes) { - if (child !== element) { - removeAllChildren(child) - } - } - } - - protected redrawNetwork() { - console.time('pre') - this.cleanElements() - this.updatePlatformsPositionOnOverlay() - console.timeEnd('pre') - console.time('preparation') - const { detailedZoom } = this.config - const zoom = this.map.getZoom() - // const lineWidth = 2 ** (zoom / 4 - 1.75); - const { - lineWidth, - lightLineWidth, - circleBorder, - dummyCircleRadius, - transferWidth, - transferBorder, - } = this.getSvgSizes() - - const strokeWidths = { - 'station-circles': circleBorder, - 'dummy-circles': 0, - 'transfers-outer': transferWidth + transferBorder / 2, - 'transfers-inner': transferWidth - transferBorder / 2, - 'paths-outer': lineWidth, - 'paths-inner': lineWidth / 2, - } - - const docFrags = new Map() - for (const [id, width] of Object.entries(strokeWidths)) { - docFrags.set(id, document.createDocumentFragment()) - byId(id).style.strokeWidth = `${width}px` - } - - const lightRailPathStyle = tryGetFromMap(this.lineRules, 'L') - lightRailPathStyle.strokeWidth = `${lightLineWidth}px` - - // 11 - 11, 12 - 11.5, 13 - 12, 14 - 12.5 - this.tooltip.setFontSize(Math.max((zoom + 10) * 0.5, 11)) - - const stationCircumpoints = new Map() - - console.timeEnd('preparation') - - // station circles - - console.time('circle preparation') - - const stationCirclesFrag = tryGetFromMap(docFrags, 'station-circles') - const dummyCirclesFrag = tryGetFromMap(docFrags, 'dummy-circles') - - const isDetailed = zoom >= detailedZoom - - this.platformOffsets.clear() - const lineWidthPlusGapPx = (GAP_BETWEEN_PARALLEL + 1) * lineWidth - for (const span of this.network.spans) { - const { source, target, routes } = span - const parallel = this.network.spans.filter(s => s.isOf(source, target)) - if (parallel.length === 1) { - continue - } - if (parallel.length === 0) { - throw new Error(`some error with span ${source.name}-${target.name}: it probably does not exist`) - } - - const i = parallel.indexOf(span) - if (i === -1) { - throw new Error(`some error with span ${source.name}-${target.name}`) - } - const leftShift = (parallel.length - 1) / 2 - const totalOffset = (i - leftShift) * lineWidthPlusGapPx - for (const p of [source, target]) { - const pos = tryGetFromMap(this.platformsOnSVG, p) - const spanRouteSpans = p.spans.filter(s => intersection(s.routes, routes).length > 0) - for (const s of spanRouteSpans) { - const map = getOrMakeInMap(this.platformOffsets, pos, () => new Map()) - map.set(s, totalOffset) - } - } - } - - for (const station of this.network.stations) { - const circumpoints: L.Point[] = [] - // const stationMeanColor: string - // if (zoom < 12) { - // stationMeanColor = color.mean(this.linesToColors(this.passingLinesStation(station))); - // } - for (const platform of station.platforms) { - const pos = tryGetFromMap(this.platformsOnSVG, platform) - // const posOnSVG = this.overlay.latLngToSvgPoint(platform.location); - const whiskers = this.makeWhiskers(platform) - this.whiskers.set(platform, whiskers) - - if (zoom > 9) { - const ci = this.makePlatformElement(platform) - if (platform.type === 'dummy') { - ci.style.display = 'none' - } - // ci.id = 'p-' + platformIndex; - - if (isDetailed) { - this.colorizePlatformElement(ci, platform.passingLines()) - } - // else { - // ci.style.stroke = stationMeanColor; - // } - const dummyCircle = svg.makeCircle(pos, dummyCircleRadius) - // dummyCircle.id = 'd-' + platformIndex; - - stationCirclesFrag.appendChild(ci) - pool.platformBindings.set(platform, ci) - dummyCirclesFrag.appendChild(dummyCircle) - pool.dummyBindings.set(platform, dummyCircle) - } - } - - const circular = findCycle(this.network, station) - if (circular.length > 0) { - for (const platform of station.platforms) { - if (circular.includes(platform)) { - const pos = tryGetFromMap(this.platformsOnSVG, platform) - circumpoints.push(pos) - } - } - stationCircumpoints.set(station, circular) - } - } - - console.timeEnd('circle preparation') - console.time('transfer preparation') - - // transfers - - const transfersOuterFrag = tryGetFromMap(docFrags, 'transfers-outer') - const transfersInnerFrag = tryGetFromMap(docFrags, 'transfers-inner') - for (const transfer of this.network.transfers) { - const { source, target } = transfer - if (!isDetailed && source.name === target.name) { - continue - } - const scp = stationCircumpoints.get(source.station) - const paths = scp !== undefined - && scp.includes(source) - && scp.includes(target) ? this.makeTransferArc( - transfer, - scp, - ) : svg.makeTransferLine( - tryGetFromMap(this.platformsOnSVG, source), - tryGetFromMap(this.platformsOnSVG, target), - ) - pool.outerEdgeBindings.set(transfer, paths[0]) - pool.innerEdgeBindings.set(transfer, paths[1]) - if (transfer.type === 'osi') { - paths[1].style.display = 'none' - const outer = paths[0] - const h = transferWidth / 1.5 - outer.style.strokeDasharray = `${transferWidth} ${h}` - outer.style.strokeWidth = `${transferWidth}px` - } else { - paths[0].style.stroke = isDetailed ? this.makeGradient(transfer) : '#000' - } - transfersOuterFrag.appendChild(paths[0]) - transfersInnerFrag.appendChild(paths[1]) - // this.transferToModel(transfer, paths); - } - - console.timeEnd('transfer preparation') - console.time('span preparation') - // paths - - const pathsOuterFrag = tryGetFromMap(docFrags, 'paths-outer') - const pathsInnerFrag = tryGetFromMap(docFrags, 'paths-inner') - - for (const span of this.network.spans) { - const [outer, inner] = this.makePath(span) - pathsOuterFrag.appendChild(outer) - if (inner) { - pathsInnerFrag.appendChild(inner) - } - } - - console.timeEnd('span preparation') - - console.time('appending') - for (const [key, val] of docFrags) { - byId(key).appendChild(val) - } - console.timeEnd('appending') - } - - private getSvgSizes() { - return getSvgSizesByZoom(this.map.getZoom(), this.config.detailedZoom) - } - - private makePlatformElement(platform: Platform) { - const pos = tryGetFromMap(this.platformsOnSVG, platform) - const { circleRadius } = this.getSvgSizes() - const offsetsMap = this.platformOffsets.get(pos) - if (!offsetsMap) { - return svg.makeCircle(pos, circleRadius) - } - - const offsets = Array.from(offsetsMap).map(([k, v]) => v) - const width = Math.max(...offsets) - Math.min(...offsets) - - const stadium = svg.makeStadium(pos, width, circleRadius) - const { value } = tryGetFromMap(this.whiskers, platform).values().next() - const rotationAngle = angle(value.subtract(pos), unit) - const deg = rotationAngle * 180 / Math.PI - stadium.setAttribute('transform', `rotate(${deg})`) - return stadium - } - - private makeGradient(transfer: Transfer) { - const { source, target } = transfer - const pos1 = tryGetFromMap(this.platformsOnSVG, source) - const pos2 = tryGetFromMap(this.platformsOnSVG, target) - // paths[0].id = 'ot-' + transferIndex; - // paths[1].id = 'it-' + transferIndex; - const gradientColors = [ - this.getPlatformColor(source), - this.getPlatformColor(target), - ] - // const colors = [ - // source, - // target, - // ].map(i => getComputedStyle(stationCirclesFrag.childNodes[i] as Element, null).stroke) - // console.log(colors); - const { fullCircleRadius } = this.getSvgSizes() - const circlePortion = fullCircleRadius / pos1.distanceTo(pos2) - const gradientVector = pos2.subtract(pos1) - let gradient = pool.gradientBindings.get(transfer) - if (gradient === undefined) { - gradient = gradients.makeLinear(gradientVector, gradientColors, circlePortion) - gradient.id = uniqueId('gradient-') - pool.gradientBindings.set(transfer, gradient) - this.overlay.defs.appendChild(gradient) - } else { - gradients.setDirection(gradient, gradientVector) - gradients.setOffset(gradient, circlePortion) - } - return `url(#${gradient.id})` - } - - private updatePlatformsPositionOnOverlay(zoom = this.map.getZoom()) { - const { - config, network, overlay, platformsOnSVG, - } = this - // all platforms are in their place - if (zoom >= config.detailedZoom) { - for (const station of network.stations) { - for (const platform of station.platforms) { - const pos = overlay.latLngToOverlayPoint(platform.location) - platformsOnSVG.set(platform, pos) - } - } - return - } - for (const station of network.stations) { - const nameSet = new Set() - const center = overlay.latLngToOverlayPoint(station.getCenter()) - const { platforms } = station - for (const platform of platforms) { - nameSet.add(platform.name) - platformsOnSVG.set(platform, center) - } - if (nameSet.size === 1) { - continue - } - // unless... - if (nameSet.size < 1) { - console.error(station) - throw new Error('station has no names') - } - const posByName = new Map() - for (const name of nameSet) { - const locations = platforms.filter(p => p.name === name).map(p => p.location) - const geoCenter = getCenter(locations) - posByName.set(name, overlay.latLngToOverlayPoint(geoCenter)) - } - for (const platform of platforms) { - const pos = tryGetFromMap(posByName, platform.name) - platformsOnSVG.set(platform, pos) - } - } - } - - private getPlatformColor(platform: Platform): string { - return meanColor(this.linesToColors(platform.passingLines())) - } - - private linesToColors(lines: Set): string[] { - const rgbs: string[] = [] - for (const line of lines) { - const { stroke } = tryGetFromMap(this.lineRules, line[0] === 'M' ? line : line[0]) - if (stroke) { - rgbs.push(stroke) - } - } - return rgbs - } - - private colorizePlatformElement(ci: SVGElement, lines: Set) { - if (lines.size === 0) { - return - } - if (lines.size === 1) { - const line = lines.values().next().value - ci.classList.add(line[0] === 'M' ? line : line[0]) - return - } - ci.style.stroke = meanColor(this.linesToColors(lines)) - } - - protected makeWhiskers(platform: Platform): Map { - const PART = 0.5 - const pos = tryGetFromMap(this.platformsOnSVG, platform) - const whiskers = new Map() - const { spans } = platform - if (spans.length === 0) { - return whiskers - } - if (spans.length === 1) { - return whiskers.set(spans[0], pos) - } - if (spans.length === 2) { - if (platform.passingLines().size === 2) { - return whiskers.set(spans[0], pos).set(spans[1], pos) - } - const neighborPositions = spans.map(span => tryGetFromMap(this.platformsOnSVG, span.other(platform))) - const [prevPos, nextPos] = neighborPositions - const wings = math.makeWings(prevPos, pos, nextPos, 1) - const t = Math.min(pos.distanceTo(prevPos), pos.distanceTo(nextPos)) * PART - for (let i = 0; i < 2; ++i) { - // const t = pos.distanceTo(neighborPositions[i]) * PART - const end = wings[i].multiplyBy(t).add(pos) - whiskers.set(spans[i], end) - } - return whiskers - } - - const normals: [L.Point[], L.Point[]] = [[], []] - const sortedSpans: [Span[], Span[]] = [[], []] - const distances = new WeakMap() - for (const span of spans) { - const neighbor = span.other(platform) - const neighborPos = tryGetFromMap(this.platformsOnSVG, neighbor) - const dirIdx = span.source === platform ? 0 : 1 - normals[dirIdx].push(normalize(neighborPos.subtract(pos))) - sortedSpans[dirIdx].push(span) - distances.set(span, pos.distanceTo(neighborPos)) - } - const [prevPos, nextPos] = normals.map(ns => mean(ns).add(pos)) - const wings = math.makeWings(prevPos, pos, nextPos, 1) - for (let i = 0; i < 2; ++i) { - const wing = wings[i] - for (const span of sortedSpans[i]) { - const t = tryGetFromMap(distances, span) * PART - const end = wing.multiplyBy(t).add(pos) - whiskers.set(span, end) - } - } - return whiskers - } - - private makeTransferArc(transfer: Transfer, cluster: Platform[]): SVGLineElement[] | SVGPathElement[] { - const { network, platformsOnSVG } = this - const { source, target } = transfer - const pos1 = tryGetFromMap(platformsOnSVG, source) - const pos2 = tryGetFromMap(platformsOnSVG, target) - const makeArc = (thirdPlatform: Platform) => - svg.makeTransferArc(pos1, pos2, tryGetFromMap(platformsOnSVG, thirdPlatform)) - if (cluster.length === 3) { - const third = difference(cluster, [source, target])[0] - return makeArc(third) - } - if (source === cluster[2] && target === cluster[3] || source === cluster[3] && target === cluster[2]) { - return svg.makeTransferLine(pos1, pos2) - } - // const s = transfer.source; - // const pl1neighbors = network.transfers.filter(t => t.source === s || t.target === s); - // const pl1deg = pl1neighbors.length; - const rarr: Platform[] = [] - for (const t of network.transfers) { - if (t === transfer) { - continue - } - if (transfer.has(t.source)) { - rarr.push(t.target) - } else if (transfer.has(t.target)) { - rarr.push(t.source) - } - } - let third: Platform - if (rarr.length === 2) { - if (rarr[0] !== rarr[1]) { - throw new Error('FFFFUC') - } - third = rarr[0] - } else if (rarr.length === 3) { - third = rarr[0] === rarr[1] ? rarr[2] : rarr[0] === rarr[2] ? rarr[1] : rarr[0] - } else { - throw new Error('111FUUFF') - } - return makeArc(third) - } - - private makePath(span: Span) { - const { routes, source, target } = span - if (routes.length === 0) { - console.error(span, 'span has no routes!') - throw new Error('span has no routes!') - } - const tokens = routes[0].line.match(/([ELM])(\d{0,2})/) - if (!tokens) { - throw new Error(`match failed for ${source.name}-${target.name}`) - } - const [lineId, lineType] = tokens - - const controlPoints = this.getControlPoints(span) - - // drawBezierHints(this.overlay.origin, controlPoints, get(this.lineRules.get(lineId), 'stroke') as string) - - const bezier = createBezier(controlPoints[0], ...controlPoints.slice(1)) - // bezier.id = 'op-' + spanIndex; - if (lineType === 'E') { - bezier.classList.add('E') - // const { branch } = span.routes[0]; - // if (branch !== undefined) { - // bezier.classList.add('E' + branch); - // } - const inner = bezier.cloneNode(true) as typeof bezier - // inner.id = 'ip-' + spanIndex; - pool.outerEdgeBindings.set(span, bezier) - pool.innerEdgeBindings.set(span, inner) - return [bezier, inner] - } - if (lineId !== undefined) { - bezier.classList.add(lineId) - } - if (lineType !== undefined) { - bezier.classList.add(lineType) - } else { - bezier.style.stroke = 'black' - } - pool.outerEdgeBindings.set(span, bezier) - return [bezier] - } - - private getControlPoints(span: Span) { - const { source, target } = span - const sourcePos = tryGetFromMap(this.platformsOnSVG, source) - const targetPos = tryGetFromMap(this.platformsOnSVG, target) - - const controlPoints = [ - sourcePos, - tryGetFromMap(tryGetFromMap(this.whiskers, source), span), - tryGetFromMap(tryGetFromMap(this.whiskers, target), span), - targetPos, - ] - - const sourceMap = this.platformOffsets.get(sourcePos) - const targetMap = this.platformOffsets.get(targetPos) - if (sourceMap) { - const offset = sourceMap.get(span) - if (!offset) { - return [controlPoints] - } - if (targetMap) { - const curves = math.split(controlPoints, CURVE_SPLIT_NUM) - const [head, ...tail] = curves.map(pa => math.offsetPath(pa, offset)) - return [head, ...tail.map(arr => arr.slice(1))] - } - const lineO = math.offsetLine(controlPoints.slice(0, 2), offset) - controlPoints[0] = lineO[0] - controlPoints[1] = lineO[1] - return [controlPoints] - } - if (targetMap) { - const offset = targetMap.get(span) - if (!offset) { - return [controlPoints] - } - const lineO = math.offsetLine(controlPoints.slice(2, 4), offset) - controlPoints[2] = lineO[0] - controlPoints[3] = lineO[1] - } - return [controlPoints] - } - - private addStationListeners() { - const onMouseOut = (e: MouseEvent) => { - this.tooltip.hide() - scale.unscaleAll() - } - const dummyCircles = byId('dummy-circles') - dummyCircles.addEventListener('mouseover', e => { - const dummy = e.target as SVGCircleElement - const platform = tryGetKeyFromBiMap(pool.dummyBindings, dummy) - this.highlightStation(platform.station, getPlatformNames(platform), [platform.name]) - }) - dummyCircles.addEventListener('mouseout', onMouseOut) - - // TODO: replace with rect - // const onTransferOver = (e: MouseEvent) => { - // const el = e.target as SVGPathElement | SVGLineElement - // const { source, target } = pool.outerEdgeBindings.getKey(el) - // || tryGetKeyFromBiMap(pool.innerEdgeBindings, el) - // const names = getPlatformNamesZipped([source, target]) - // this.highlightStation(source.station, names, [source.name, target.name]) - // } - // const transfersOuter = byId('transfers-outer') - // const transfersInner = byId('transfers-inner') - // transfersOuter.addEventListener('mouseover', onTransferOver) - // transfersInner.addEventListener('mouseover', onTransferOver) - // transfersOuter.addEventListener('mouseout', onMouseOut) - // transfersInner.addEventListener('mouseout', onMouseOut) - } - - private highlightStation(station: Station, namesOnPlate: string[], filteredNames: string[]) { - const scaleFactor = 1.25 - const platforms = station.platforms.filter(p => filteredNames.includes(p.name)) - for (const platform of platforms) { - const circle = tryGetFromMap(pool.platformBindings, platform) - scale.scaleElement(circle, scaleFactor, true) - } - if (this.map.getZoom() >= this.config.detailedZoom) { - for (const transfer of this.network.transfers) { - const shouldScale = platforms.some(p => transfer.has(p)) - && filteredNames.includes(transfer.source.name) - && filteredNames.includes(transfer.target.name) - if (shouldScale) { - scale.scaleTransfer(transfer, scaleFactor) - } - } - } - const topmostPlatform = maxBy(platforms, p => p.location.lat) - const topmostCircle = tryGetFromMap(pool.platformBindings, topmostPlatform) - if (platforms.some(p => p.type !== 'dummy')) { - this.tooltip.show(svg.getElementOffset(topmostCircle), namesOnPlate) - } - } -} diff --git a/src/MetroMap.tsx b/src/MetroMap.tsx new file mode 100644 index 000000000..5c3f11ce6 --- /dev/null +++ b/src/MetroMap.tsx @@ -0,0 +1,280 @@ +import React from 'react' +import ReactDom from 'react-dom' +import L from 'leaflet' +import unblur from 'unblur' + +import { + mapbox2, + mapnik, + osmFrance, + openMapSurfer, + cartoDBNoLabels, + wikimapia, +} from 'ui/tilelayers' + +import { getJSON } from 'utils/http' + +import getCenter from 'utils/geo/getCenter' + +import MetroMapEventMap from 'utils/MetroMapEventMap' + +import Mediator from 'utils/Mediator' + +import Config from './Config' +import getLineRules from './getLineRules' + +import Network, { GraphJSON } from './network' + +import addLayerSwitcher from './ui/addLayerSwitcher' +import DistanceMeasure from './ui/DistanceMeasure' +import SvgOverlay from './ui/SvgOverlay' +import ContextMenu from './ui/ContextMenu' +import FAQ from './ui/FAQ' +// import drawZones from './ui/drawZones' + +// import geometricMedian from 'utils/geo/geometricMedian' + +import Metro from './Metro' + +import 'leaflet/dist/leaflet.css' + +const alertifyPromise = import(/* webpackChunkName: "alertify" */ 'ui/alertify') + +const contextMenuArray = [ + { + event: 'routefrom', + text: 'Route from here', + }, + { + event: 'routeto', + text: 'Route to here', + }, + { + event: 'clearroute', + text: 'Clear route', + }, + { + event: 'showheatmap', + text: 'Show heatmap', + extra: { + disabled: true, + }, + }, +] + +export default class { + readonly mediator = new Mediator() + protected readonly config: Config + protected map: L.Map + private moving = false + protected overlay: SvgOverlay + protected readonly contextMenu = new ContextMenu(contextMenuArray as any) + + protected network: Network + private lineRules: Map + + // private routeWorker = new Worker('js/routeworker.js'); + + getMap(): L.Map { + return this.map + } + + getNetwork(): Network { + return this.network + } + + constructor(config: Config) { + this.config = config + this.makeMap() + } + + protected async makeMap() { + try { + const { config } = this + const networkPromise = this.getGraph() + const lineRulesPromise = getLineRules(config.url.scheme) + const dataPromise = getJSON(config.url.data) + + const tileLoadPromise = new Promise(resolve => { + cartoDBNoLabels.once('load', resolve) + setTimeout(resolve, 5000) + }) + + // wait.textContent = 'making map...'; + + config.center = [0, 0] + const mapOptions = { ...config } + const scaleControl = L.control.scale({ + imperial: false, + }) + this.map = L.map(config.containerId, mapOptions).addControl(scaleControl) + const mapPaneStyle = this.map.getPanes().mapPane.style + mapPaneStyle.visibility = 'hidden' + + addLayerSwitcher(this.map, [ + cartoDBNoLabels, + mapnik, + osmFrance, + mapbox2, + openMapSurfer, + wikimapia, + ]) + + // wait.textContent = 'loading graph...'; + this.addContextMenu() + + const json = await networkPromise + this.network = new Network(json, config.detailedE) + const platformLocations = this.network.platforms.map(p => p.location) + const center = getCenter(platformLocations) + config.center = [center.lat, center.lng] + const bounds = L.latLngBounds(platformLocations) + this.overlay = new SvgOverlay(bounds, L.point(200, 200)).addTo(this.map) + const { defs } = this.overlay + + const { + default: alertify, + confirm, + } = await alertifyPromise + // addEventListener('keydown', async e => { + // if (!e.shiftKey || !e.ctrlKey || e.keyCode !== 82 || !(await confirm('Reset network?'))) { + // return + // } + // const graph = await this.getGraph() + // this.resetNetwork(graph) + // }) + + // const { textContent } = defs + // if (!textContent) { + // alertify.alert(` + // Your browser doesn't seem to have capabilities to display some features of the map. + // Consider using Chrome or Firefox for the best experience. + // `) + // } + + this.lineRules = await lineRulesPromise + // wait.textContent = 'adding content...'; + this.resetMapView() + this.map.addLayer(cartoDBNoLabels) + this.map.on('overlayupdate', e => { + this.moving = true + this.render() + this.moving = false + // console.time('conversion'); + // file.svgToPicture(document.getElementById('overlay') as any).then(img => { + // document.body.appendChild(img); + // console.timeEnd('conversion'); + // }); + }) + this.initNetwork() + // TODO: fix the kludge making the grey area disappear + this.map.invalidateSize(false) + this.addMapListeners() + // new RoutePlanner().addTo(this) + new DistanceMeasure().addTo(this.map) + // this.routeWorker.postMessage(this.network); + // drawZones(this.map, this.network.platforms); + + dataPromise.then(data => new FAQ(data).addTo(this.map)) + // wait.textContent = 'loading tiles...'; + + await tileLoadPromise + // wait.parentElement.removeChild(wait); + mapPaneStyle.visibility = '' + // const img = file.svgToImg(document.getElementById('overlay') as any, true); + // file.svgToCanvas(document.getElementById('overlay') as any) + // .then(canvas => fFile.downloadText('svg.txt', canvas.toDataURL('image/png'))); + // file.downloadText('img.txt', img.src); + this.runUnblur() + } catch (err) { + console.error(err) + } + } + + runUnblur() { + this.map + .on('movestart', e => this.moving = true) + .on('moveend', e => this.moving = false) + unblur({ + interval: 250, + skipIf: () => this.moving, + // onUnblur: (els) => console.log('unblurred:', els.length), + // onSkip: () => console.log('skipped'), + }) + } + + subscribe(type: K, listener: (e: MetroMapEventMap[K]) => void) { + this.mediator.subscribe(type, listener) + // forwarding map event to mediator + this.map.on(type, this.mediator.publish) + } + + private addContextMenu() { + for (const el of contextMenuArray) { + this.map.on(el.event, this.mediator.publish) + } + this.contextMenu.addTo(this.map) + } + + protected addMapListeners() { + const { map, contextMenu } = this + + map.on('distancemeasureinit', e => { + contextMenu.insertItem('measuredistance', 'Measure distance') + + map.on('clearmeasurements', () => { + contextMenu.removeItem('clearmeasurements') + contextMenu.insertItem('measuredistance', 'Measure distance') + }) + + this.subscribe('measuredistance', () => { + contextMenu.removeItem('measuredistance') + contextMenu.insertItem('clearmeasurements', 'Clear measurements') + }) + }) + } + + /** call only once! */ + private initNetwork() { + this.render() + } + + private resetMapView() { + // const fitness = (points, pt) => points.reduce((prev, cur) => this.bounds., 0); + // const center = geometricMedian(this.network.platforms.map(p => p.location), fitness, 0.1); + const { center, zoom } = this.config + const options = { + animate: false, + } + if (!center) { + console.error('cannot set map to center') + return + } + this.map.setView(center, zoom + 1, options) + this.map.setView(center, zoom, options) + } + + private getGraph(): Promise { + return getJSON(this.config.url.graph) + } + + protected resetNetwork(json: GraphJSON) { + this.network = new Network(json) + this.render() + } + + private latLngToOverlayPoint = (latLng: L.LatLng) => + this.overlay.latLngToOverlayPoint(latLng) + + render() { + ReactDom.render(( + + ), this.overlay.origin) + } +} diff --git a/src/components/Platform.tsx b/src/components/Platform.tsx new file mode 100644 index 000000000..9d6c235aa --- /dev/null +++ b/src/components/Platform.tsx @@ -0,0 +1,87 @@ +import React, { + memo, + useCallback, +} from 'react' +import { Point } from 'leaflet' + +import Platform from 'network/Platform' + +import Circle from './primitives/Circle' +import Stadium from './primitives/Stadium' + +import Portal from './Portal' + +interface Props { + position: Point | Point[], + radius: number, + color?: string, + isFeatured?: boolean, + platforms: readonly Platform[], // platforms it represents + dummyParent: Element | null, + onMouseOver?: (platforms: readonly Platform[]) => void, + onMouseOut?: () => void, +} + +const PlatformReact = ({ + position, + platforms, + radius, + color, + isFeatured, + dummyParent, + onMouseOver, + onMouseOut, +}: Props) => { + // console.log('rend', position) + const onMouseOverCb = useCallback(() => { + if (!onMouseOver || platforms.every(p => p.type === 'dummy')) { + return + } + onMouseOver(platforms) + }, [platforms, onMouseOver]) + + const El = useCallback( + (props) => + Array.isArray(position) ? ( + + ) : ( + + ), + [position], + ) + + const realRadius = isFeatured ? radius * 1.25 : radius + const dummyRadius = radius * 2 + + return ( + <> + p.type === 'dummy') ? 'none' : undefined} + /> + {dummyParent + && ( + + + + )} + + ) +} + +export default memo(PlatformReact) diff --git a/src/components/Portal.ts b/src/components/Portal.ts new file mode 100644 index 000000000..a87a7a40f --- /dev/null +++ b/src/components/Portal.ts @@ -0,0 +1,55 @@ +import { + useRef, + useEffect, + useMemo, + memo, + FC, +} from 'react' + +import ReactDOM from 'react-dom' +import htmlTags from 'html-tags' + +const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml' +const SVG_NAMESPACE = 'http://www.w3.org/2000/svg' + +type TagName = keyof ElementTagNameMap + +interface Props { + tagName: TagName, + modalRoot: Element, +} + +const Portal: FC = ({ + tagName, + modalRoot, + children, +}) => { + const el = useMemo(() => { + const ns = htmlTags.includes(tagName) && tagName !== 'svg' ? HTML_NAMESPACE : SVG_NAMESPACE + return document.createElementNS(ns, tagName) as HTMLElement + }, [tagName, modalRoot]) + + const elRef = useRef(el) + + useEffect(() => { + // The portal element is inserted in the DOM tree after + // the Modal's children are mounted, meaning that children + // will be mounted on a detached DOM node. If a child + // component requires to be attached to the DOM tree + // immediately when mounted, for example to measure a + // DOM node, or uses 'autoFocus' in a descendant, add + // state to Modal and only render the children when Modal + // is inserted in the DOM tree. + modalRoot.appendChild(el) + return () => { + modalRoot.removeChild(el) + } + }, []) + + return ReactDOM.createPortal( + children, + elRef.current!, // this.props.modalRoot is possible + ) +} + +export default memo(Portal) as typeof Portal diff --git a/src/components/Tooltip.tsx b/src/components/Tooltip.tsx new file mode 100644 index 000000000..bcd249300 --- /dev/null +++ b/src/components/Tooltip.tsx @@ -0,0 +1,137 @@ +import React, { memo } from 'react' +import styled, { createGlobalStyle } from 'styled-components' +import { Point } from 'leaflet' + +import memoizeObject from 'utils/memoizeObject' + +const WIDTH = 300 +const HALF_WIDTH = WIDTH / 2 + +const GAP = 10 + +const Fonts = createGlobalStyle` + /* latin-ext */ + @font-face { + font-family: 'Ubuntu'; + font-style: normal; + font-weight: 400; + src: local('Ubuntu Regular'), local('Ubuntu-Regular'), url(https://fonts.gstatic.com/s/ubuntu/v12/4iCs6KVjbNBYlgoKcQ72nU6AF7xm.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; + } + /* latin */ + @font-face { + font-family: 'Ubuntu'; + font-style: normal; + font-weight: 400; + src: local('Ubuntu Regular'), local('Ubuntu-Regular'), url(https://fonts.gstatic.com/s/ubuntu/v12/4iCs6KVjbNBYlgoKfw72nU6AFw.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + } + + /* latin-ext */ + @font-face { + font-family: 'Ubuntu-Medium'; + font-style: normal; + /* font-weight: 500; */ + font-weight: 700; + src: local('Ubuntu Medium'), local('Ubuntu-Medium'), url(https://fonts.gstatic.com/s/ubuntu/v12/4iCv6KVjbNBYlgoCjC3jvmyNPYZvg7UI.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; + } + /* latin */ + @font-face { + font-family: 'Ubuntu-Medium'; + font-style: normal; + /* font-weight: 500; */ + font-weight: 700; + src: local('Ubuntu Medium'), local('Ubuntu-Medium'), url(https://fonts.gstatic.com/s/ubuntu/v12/4iCv6KVjbNBYlgoCjC3jsGyNPYZvgw.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + } + + /* latin-ext */ + @font-face { + font-family: 'Ubuntu'; + font-style: normal; + font-weight: 700; + src: local('Ubuntu Bold'), local('Ubuntu-Bold'), url(https://fonts.gstatic.com/s/ubuntu/v12/4iCv6KVjbNBYlgoCxCvjvmyNPYZvg7UI.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; + } + /* latin */ + @font-face { + font-family: 'Ubuntu'; + font-style: normal; + font-weight: 700; + src: local('Ubuntu Bold'), local('Ubuntu-Bold'), url(https://fonts.gstatic.com/s/ubuntu/v12/4iCv6KVjbNBYlgoCxCvjsGyNPYZvgw.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + } +` + +const PlateBoxWrapper = styled.div` + display: flex; + justify-content: center; + height: 100%; + align-items: flex-end; +` + +const PlateBox = styled.div` + color: #000000; + text-align: center; + font-family: Corbel, 'Ubuntu-Medium', Candara, Calibri, 'Trebuchet MS', sans-serif; + font-weight: bold; + opacity: 1; + user-select: none; + + white-space: nowrap; + /*border-radius: 2px;*/ + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); + /*filter: url(#shadow);*/ + background-color: white; /* for div */ + padding: 1px 3px 1px 3px; + line-height: 1.3em; + display: ${props => props.display ? 'inline-block' : 'none'}; + margin-bottom: ${GAP}px; + /*float: left;*/ +` + +const Textling = styled.div` + font-weight: ${props => props.primary && 'bold'}; +` + +const memoizeTextLingStyle = memoizeObject() + +interface Props { + names: string[] | null, + position: Point | null, + fontSize: number, +} + +const Tooltip = ({ + position, + names, + fontSize, +}: Props) => ( + + + + + {names && names.map((name, i) => ( + + {name} + + ))} + + + +) + +export default memo(Tooltip) diff --git a/src/components/Transfer/Gradient.tsx b/src/components/Transfer/Gradient.tsx new file mode 100644 index 000000000..e3cfbe2fd --- /dev/null +++ b/src/components/Transfer/Gradient.tsx @@ -0,0 +1,53 @@ +import React, { memo } from 'react' + +import { Point } from 'leaflet' + +import memoizeObject from 'utils/memoizeObject' + +const memoStop1Style = memoizeObject() +const memoStop2Style = memoizeObject() + +interface Props { + id: string, + start: Point, + end: Point, + startColor: string, + endColor: string, + fullCircleRadius: number, +} + +const Gradient = ({ + id, + start, + end, + startColor, + endColor, + fullCircleRadius, +}: Props) => { + const vector = end.subtract(start) + + const transferLength = start.distanceTo(end) + const circlePortion = transferLength === 0 ? 0 : fullCircleRadius / transferLength + + return ( + + + + + ) +} + +export default memo(Gradient) diff --git a/src/components/Transfer/index.tsx b/src/components/Transfer/index.tsx new file mode 100644 index 000000000..99d81b8c9 --- /dev/null +++ b/src/components/Transfer/index.tsx @@ -0,0 +1,138 @@ +import React, { + useCallback, + memo, +} from 'react' +import { Point } from 'leaflet' + +import memoizeObject from 'utils/memoizeObject' + +import Transfer from '../../network/Transfer' +import Platform from '../../network/Platform' + +import Line from '../primitives/Line' +import Arc from '../primitives/Arc' + +import Portal from '../Portal' + +import Gradient from './Gradient' + +const memoPathStyle = memoizeObject() + +const getId = (transfer: Transfer) => + `gradient-${transfer.id}` + +interface Props { + start: Point, + end: Point, + third?: Point | null, + transfer: Transfer, + fullCircleRadius: number, + strokeWidth?: number, + defs: SVGDefsElement | null, + innerParent: Element | null, + dummyParent: Element | null, + getPlatformColor: (platform: Platform) => string, + onMouseOver?: (transfer: Transfer) => void, + onMouseOut?: () => void, +} + +const TransferReact = ({ + start, + end, + third, + transfer, + fullCircleRadius, + strokeWidth, + defs, + innerParent, + dummyParent, + getPlatformColor, + onMouseOver, + onMouseOut, +}: Props) => { + const onMouseOverMem = useCallback(() => { + if (!onMouseOver) { + return + } + onMouseOver(transfer) + }, [transfer, onMouseOver]) + + const Path = third ? Arc : Line + const newEnd = end.clone() + if (!third) { + if (start.x === end.x) { + newEnd.x += 0.01 + } + if (start.y === end.y) { + newEnd.y += 0.01 + } + } + + const osiStyle = strokeWidth && transfer.type === 'osi' && { + strokeDasharray: `0 ${strokeWidth * 1.5}`, + strokeLinecap: 'round', + } + + return ( + <> + + {innerParent && transfer.type !== 'osi' + && ( + + + + )} + {defs + && ( + + + + )} + {dummyParent + && ( + + + + )} + + ) +} + +export default memo(TransferReact) diff --git a/src/components/filters/BlackGlow.tsx b/src/components/filters/BlackGlow.tsx new file mode 100644 index 000000000..a454e486b --- /dev/null +++ b/src/components/filters/BlackGlow.tsx @@ -0,0 +1,25 @@ +import React, { memo } from 'react' + +const BlackGlowFilter = () => ( + + + + + + + + +) + +export default memo(BlackGlowFilter) diff --git a/src/components/filters/Gray.tsx b/src/components/filters/Gray.tsx new file mode 100644 index 000000000..ca20f8caf --- /dev/null +++ b/src/components/filters/Gray.tsx @@ -0,0 +1,17 @@ +import React, { memo } from 'react' + +const GrayFilter = () => ( + + + +) + +export default memo(GrayFilter) diff --git a/src/components/filters/Opacity.tsx b/src/components/filters/Opacity.tsx new file mode 100644 index 000000000..f06b402bd --- /dev/null +++ b/src/components/filters/Opacity.tsx @@ -0,0 +1,14 @@ +import React, { memo } from 'react' + +const OpacityFilter = () => ( + + + + + +) + +export default memo(OpacityFilter) diff --git a/src/components/filters/Shadow.tsx b/src/components/filters/Shadow.tsx new file mode 100644 index 000000000..46f8304ed --- /dev/null +++ b/src/components/filters/Shadow.tsx @@ -0,0 +1,39 @@ +import React, { memo } from 'react' + +const ShadowFilter = () => ( + + + + + + +) + +export default memo(ShadowFilter) diff --git a/src/components/primitives/Arc.tsx b/src/components/primitives/Arc.tsx new file mode 100644 index 000000000..16f596919 --- /dev/null +++ b/src/components/primitives/Arc.tsx @@ -0,0 +1,71 @@ +import React, { memo } from 'react' +import styled from 'styled-components' +import { Point } from 'leaflet' + +import { getCircumcenter } from 'utils/math' +import { dot, det } from 'utils/math/vector' + +interface ArcArgs { + radius: number, + large?: number, + clockwise?: number, +} + +const Root = styled.path` +` + +function getArgs(a: Point, b: Point, controlPoint: Point): ArcArgs { + const center = getCircumcenter([a, b, controlPoint]) + if (center === null) { + return { + radius: Infinity, + } + } + const A = a.subtract(controlPoint) + const B = b.subtract(controlPoint) + const thirdIsBetween = dot(A, B) < 0 + const u = a.subtract(center) + const v = b.subtract(center) + // the distance is shorter when moving from start to end clockwise + const isClockwise = det(u, v) >= 0 + return { + radius: center.distanceTo(a), + large: thirdIsBetween ? 1 : 0, + clockwise: isClockwise && !thirdIsBetween || thirdIsBetween && !isClockwise ? 1 : 0, + } +} + +interface Props extends React.SVGProps { + a: Point, + b: Point, + third: Point, + [prop: string]: any, +} + +const Arc = ({ + a: start, + b: end, + third, + ...otherProps +}: Props) => { + const { + radius, + large, + clockwise, + } = getArgs(start, end, third) + + const d = [ + 'M', start.x, start.y, + ...(radius === Infinity ? ['L'] : ['A', radius, radius, 0, large, clockwise]), + end.x, end.y, + ].join(' ') + + return ( + + ) +} + +export default memo(Arc) diff --git a/src/components/primitives/Bezier.tsx b/src/components/primitives/Bezier.tsx new file mode 100644 index 000000000..8452229ef --- /dev/null +++ b/src/components/primitives/Bezier.tsx @@ -0,0 +1,52 @@ +import React, { memo } from 'react' +import styled from 'styled-components' +import { Point } from 'leaflet' + +const curveTypeLetters = ['', 'L', 'Q', 'C'] + +function tailToString(tail: Point[]) { + const { length } = tail + if (length > 3) { + throw new Error(`the tail should consist of 1-3 elements, but got ${length} instead`) + } + const letter = curveTypeLetters[length] + const coords = tail.map(pt => `${pt.x} ${pt.y}`).join(' ') + return `${letter} ${coords}` +} + +function makePath(controlPoints: Point[], tails?: Point[][]) { + if (controlPoints.length < 2) { + throw new Error(`there should be at least 2 control points, but got ${controlPoints.length} instead`) + } + const [start, ...tail] = controlPoints + const str = `M ${start.x} ${start.y}` + if (!tails) { + return str + } + // tails.unshift(tail) // tails can be mutable + const tailStr = [tail, ...tails].map(tailToString).join(' ') + return `${str} ${tailStr}` +} + +const Path = styled.path` + stroke: ${props => props.color || '#000'}; +` + +interface Props { + controlPoints: Point[], + tails?: Point[][], + color?: string | null, +} + +const Bezier = ({ + controlPoints, + tails, + color, +}: Props) => ( + +) + +export default memo(Bezier) diff --git a/src/components/primitives/Circle.tsx b/src/components/primitives/Circle.tsx new file mode 100644 index 000000000..9aa2c98f6 --- /dev/null +++ b/src/components/primitives/Circle.tsx @@ -0,0 +1,24 @@ +import React, { memo } from 'react' +import { Point } from 'leaflet' + +interface Props extends React.SVGProps { + center: Point, + radius: number, + [prop: string]: any, +} + +const Circle = ({ + center, + radius, + ...otherProps +}: Props) => ( + +) + +export default memo(Circle) diff --git a/src/components/primitives/Line.tsx b/src/components/primitives/Line.tsx new file mode 100644 index 000000000..1a7649790 --- /dev/null +++ b/src/components/primitives/Line.tsx @@ -0,0 +1,28 @@ +import React, { memo } from 'react' +import styled from 'styled-components' +import { Point } from 'leaflet' + +const Root = styled.line` +` + +interface Props extends React.SVGProps { + a: Point, + b: Point, + [prop: string]: any, +} + +const Line = ({ + a, + b, + ...otherProps +}: Props) => ( + +) + +export default memo(Line) diff --git a/src/components/primitives/Stadium.tsx b/src/components/primitives/Stadium.tsx new file mode 100644 index 000000000..07220d5ca --- /dev/null +++ b/src/components/primitives/Stadium.tsx @@ -0,0 +1,44 @@ +import React, { memo } from 'react' +import { Point } from 'leaflet' + +interface Props extends React.SVGProps { + c1: Point, + c2: Point, + radius: number, + [prop: string]: any, +} + +const Stadium = ({ + c1, + c2, + radius, + ...otherProps +}: Props) => { + const diameter = radius * 2 + + const center = c1.add(c2).divideBy(2) + const distance = c1.distanceTo(c2) + + const vec = c2.subtract(c1) + const rotation = Math.atan2(vec.y, vec.x) + const rotationDeg = rotation * 180 / Math.PI + + return ( + + ) +} + +export default memo(Stadium) diff --git a/src/css/map.css b/src/css/map.css index e74a24324..2b8c5065a 100644 --- a/src/css/map.css +++ b/src/css/map.css @@ -10,31 +10,8 @@ circle { } } -#station-circles { - fill: white; - stroke: black; -} - -#dummy-circles { - opacity: 0; - stroke: blue; - stroke-width: 0.5px; -} - -#paths-outer, #paths-inner, #transfers-outer, #transfers-inner { - fill: none; -} - -#paths-outer, #transfers-outer { - & > path { - pointer-events: stroke; +#map-container { + & path { + pointer-events: initial; } } - -#transfers-outer { - stroke: #404040; -} - -#transfers-inner { - stroke: #FFFFFF; -} diff --git a/src/main.ts b/src/index.ts similarity index 92% rename from src/main.ts rename to src/index.ts index 315b2ef8b..d45d0caee 100644 --- a/src/main.ts +++ b/src/index.ts @@ -17,7 +17,7 @@ if (Browser.ie) { const mapPromise = Browser.mobile ? import(/* webpackChunkName: "MetroMap" */ './MetroMap') - : import(/* webpackChunkName: "EditableMetroMap" */ './EditableMetroMap') + : import(/* webpackChunkName: "EditableMetroMap" */ './MetroMap') const tokens = location.search.match(/city=(\w+)/) const city = tokens ? tokens[1] : 'spb' diff --git a/src/mapconfig.json b/src/mapconfig.json index 910fe3d5e..220f13a74 100644 --- a/src/mapconfig.json +++ b/src/mapconfig.json @@ -4,6 +4,7 @@ "maxZoom": 18, "zoom": 11, "detailedZoom": 12, + "detailedE": true, "url": { "graph": "https://raw.githubusercontent.com/metrofan/metronetworks/master/{city}/graph.json", "data": "res/faq.json", diff --git a/src/network/Platform.ts b/src/network/Platform.ts index 0513d99d5..a1acd5356 100644 --- a/src/network/Platform.ts +++ b/src/network/Platform.ts @@ -7,6 +7,11 @@ import Transfer from './Transfer' import Station from './Station' import Route from './Route' +interface Spans { + inbound: Span[], + outbound: Span[], +} + type PlatformType = 'normal' | 'dummy' export default class Platform { @@ -15,7 +20,11 @@ export default class Platform { name: string altNames: AltNames location: LatLng - readonly spans: Span[] = [] + readonly spans: Spans = { + inbound: [], + outbound: [], + } + readonly transfers: Transfer[] = [] private _station: Station elevation?: number @@ -32,9 +41,24 @@ export default class Platform { this.elevation = elevation } + hasSpan(span: Span) { + const { spans } = this + return spans.inbound.includes(span) || spans.outbound.includes(span) + } + + getAllSpans() { + const { spans } = this + return [...spans.inbound, ...spans.outbound] + } + + getNumSpans() { + const { spans } = this + return spans.inbound.length + spans.outbound.length + } + passingRoutes(): Set { const routes = new Set() - for (const span of this.spans) { + for (const span of this.getAllSpans()) { for (const route of span.routes) { routes.add(route) } @@ -44,11 +68,27 @@ export default class Platform { passingLines(): Set { const lines = new Set() - for (const span of this.spans) { + for (const span of this.getAllSpans()) { for (const route of span.routes) { lines.add(route.line) } } return lines } + + adjacentPlatformsBySpans() { + return this.getAllSpans().map(s => s.other(this)) + } + + adjacentPlatformsByTransfers() { + return this.transfers.map(t => t.other(this)) + } + + isAdjacentBySpan(platform: Platform) { + return this.getAllSpans().some(s => s.has(platform)) + } + + isAdjacentByTransfer(platform: Platform) { + return this.transfers.some(t => t.has(platform)) + } } diff --git a/src/network/Span.ts b/src/network/Span.ts index 083747263..1d983efc6 100644 --- a/src/network/Span.ts +++ b/src/network/Span.ts @@ -1,4 +1,7 @@ -import { pull } from 'lodash' +import { + intersection, + pull, +} from 'lodash' import Platform from './Platform' import Edge from './Edge' @@ -9,8 +12,21 @@ export default class Span extends Edge { constructor(source: Platform, target: Platform, routes: Route[]) { super(source, target) - source.spans.push(this) - target.spans.push(this) + // TODO + source.spans.outbound.push(this) + target.spans.inbound.push(this) + // if (source.spans.outbound.find(s => intersection(s.routes, routes).length > 0)) { + // console.log('inverting span 1', this) + // source.spans.inbound.push(this) + // } else { + // source.spans.outbound.push(this) + // } + // if (target.spans.inbound.find(s => intersection(s.routes, routes).length > 0)) { + // console.log('inverting span 2', this) + // target.spans.outbound.push(this) + // } else { + // target.spans.inbound.push(this) + // } this.routes = routes } @@ -20,10 +36,10 @@ export default class Span extends Edge { set source(vertex: Platform) { if (this._source !== undefined) { - pull(this._source.spans, this) + pull(this._source.spans.outbound, this) } this._source = vertex - vertex.spans.push(this) + vertex.spans.outbound.push(this) } get target() { @@ -32,9 +48,46 @@ export default class Span extends Edge { set target(vertex: Platform) { if (this._target !== undefined) { - pull(this._target.spans, this) + pull(this._target.spans.inbound, this) } this._target = vertex - vertex.spans.push(this) + vertex.spans.inbound.push(this) + } + + isAdjacentSpan(other: Span) { + // but not parallel! + return (this.has(other.source) || this.has(other.target)) + && !this.isOf(other.source, other.target) + } + + isContinuous(other: Span) { + // kinda like 'next' + return this.isAdjacentSpan(other) && intersection(this.routes, other.routes).length > 0 + } + + parallelSpans() { + const { + _source, + _target, + } = this + const spans = new Set([..._source.getAllSpans(), ..._target.getAllSpans()]) + spans.delete(this) + return Array.from(spans).filter(s => s.isOf(_source, _target)) + } + + invert() { + const { + _source, + _target, + } = this + + _source.spans.outbound.splice(_source.spans.outbound.indexOf(this), 1) + _source.spans.inbound.push(this) + + _target.spans.inbound.splice(_target.spans.inbound.indexOf(this), 1) + _target.spans.outbound.push(this) + + this._source = _target + this._target = _source } } diff --git a/src/network/index.ts b/src/network/index.ts index eb9afce14..209bf3d53 100644 --- a/src/network/index.ts +++ b/src/network/index.ts @@ -1,5 +1,8 @@ import { latLng } from 'leaflet' -import { pull } from 'lodash' +import { + // orderBy, + pull, +} from 'lodash' import Platform from './Platform' import Station from './Station' @@ -34,7 +37,8 @@ export default class { readonly transfers: Transfer[] readonly spans: Span[] readonly routes: Route[] - constructor(json: GraphJSON) { + constructor(json: GraphJSON, detailedE?: boolean) { + console.time('restore') this.platforms = json.platforms .map(p => new Platform(p.name, objectifyLatLng(p.location), p.altNames, p.type as any)) this.stations = [] @@ -43,9 +47,25 @@ export default class { this.transfers = json.transfers .map(s => new Transfer(this.platforms[s.source], this.platforms[s.target], s.type)) const spanRoutes = (s: SpanJSON) => s.routes.map(i => this.routes[i]) - this.spans = json.spans.map(s => new Span(this.platforms[s.source], this.platforms[s.target], spanRoutes(s))) - console.time('restore') + if (detailedE) { + this.spans = [] + for (const s of json.spans) { + const pSource = this.platforms[s.source] + const pTarget = this.platforms[s.target] + const isE = s.routes.every(r => json.routes[r].line === 'E') + if (!isE) { + this.spans.push(new Span(pSource, pTarget, spanRoutes(s))) + continue + } + const spans = s.routes.map(r => new Span(pSource, pTarget, [json.routes[r]])) + this.spans.push(...spans) + } + // this.spans = orderBy(this.spans, a => a.source.passingLines().has('E') ? -1 : 0) + } else { + this.spans = json.spans.map(s => new Span(this.platforms[s.source], this.platforms[s.target], spanRoutes(s))) + } + const transferSet = new Set(this.transfers) const platformsCopy = new Set(this.platforms) while (transferSet.size > 0) { diff --git a/src/utils/algorithm/findCycle.ts b/src/utils/algorithm/findCycle.ts index d49a99640..7ec09f35c 100644 --- a/src/utils/algorithm/findCycle.ts +++ b/src/utils/algorithm/findCycle.ts @@ -1,5 +1,3 @@ -import { isEqual } from 'lodash' - import Network, { Platform, Station, @@ -15,26 +13,33 @@ export default (network: Network, station: Station): Platform[] => { return [] } // TODO: if n=3, leave as it is; if n=4, metro has priority + // TODO: get all combos const { transfers } = network if (platforms.length === 3) { const eachHasTwoTransfers = platforms.every(p => incidentEdges(transfers, p).length === 2) return eachHasTwoTransfers ? platforms : [] } - if (platforms.length === 4) { - const psAndDegs = platforms - .map(platform => ({ - platform, - degree: transfers.filter(t => t.has(platform)).length, - })) - .sort((a, b) => a.degree - b.degree) - const degs = psAndDegs.map(i => i.degree) - const ps = psAndDegs.map(i => i.platform) - if (isEqual(degs, [2, 2, 3, 3])) { - return ps - } - if (isEqual(degs, [1, 2, 2, 3])) { - return ps.slice(1) - } + const filtered = platforms.filter(p => !p.passingLines().has('E')) + if (filtered.length === 3) { + const [a, b, c] = filtered + const eachIsAdjacent = a.isAdjacentByTransfer(b) && b.isAdjacentByTransfer(c) && c.isAdjacentByTransfer(a) + return eachIsAdjacent ? filtered : [] } + // if (platforms.length === 4) { + // const psAndDegs = platforms + // .map(platform => ({ + // platform, + // degree: transfers.filter(t => t.has(platform)).length, + // })) + // .sort((a, b) => a.degree - b.degree) + // const degs = psAndDegs.map(i => i.degree) + // const ps = psAndDegs.map(i => i.platform) + // if (isEqual(degs, [2, 2, 3, 3])) { + // return ps + // } + // if (isEqual(degs, [1, 2, 2, 3])) { + // return ps.slice(1) + // } + // } return [] } diff --git a/src/utils/collections.ts b/src/utils/collections.ts index 3aabe3bb7..659ecfcb6 100644 --- a/src/utils/collections.ts +++ b/src/utils/collections.ts @@ -48,3 +48,20 @@ export function tryGetKeyFromBiMap(map: IBiMap, val: V): K { } return key } + +export function swapArrayElements(arr: T[], a: number, b: number) { + const t = arr[a] + arr[a] = arr[b] + arr[b] = t +} + +export function iteratePairwise(arr: T[], iteration: (a: T, b: T) => void) { + const len = arr.length + const lenMinusOne = len - 1 + for (let i = 0; i < lenMinusOne; ++i) { + const el = arr[i] + for (let j = i + 1; j < len; ++j) { + iteration(el, arr[j]) + } + } +} diff --git a/src/utils/comparisonLevel.ts b/src/utils/comparisonLevel.ts new file mode 100644 index 000000000..e5bb15d37 --- /dev/null +++ b/src/utils/comparisonLevel.ts @@ -0,0 +1,21 @@ +import { Component, ComponentClass } from 'react' + +import equalsByLevel from './equalsByLevel' + +const comparisonLevel = (level: number) => + >(Class: C) => { + Class.prototype.shouldComponentUpdate = function (this: Component, nextProps: P, nextState: S) { + return !equalsByLevel(this.props, nextProps, level) + || !equalsByLevel(this.state, nextState, level) + } + + const composedComponentName = Class.displayName + || Class.name + || 'Component' + + Class.displayName = `comparisonLevel(${level})(${composedComponentName})` + + return Class + } + +export default comparisonLevel diff --git a/src/utils/equalsByLevel.ts b/src/utils/equalsByLevel.ts new file mode 100644 index 000000000..1409608f6 --- /dev/null +++ b/src/utils/equalsByLevel.ts @@ -0,0 +1,31 @@ +const sameContent = (arr1: T[], arr2: T[]) => + arr1.length === arr2.length && arr1.every(item => arr2.includes(item)) + +function objectsAreTheSame(o1: object, o2: object, currentLevel: number): boolean { + const keys1 = Object.keys(o1) + const keys2 = Object.keys(o2) + + return sameContent(keys1, keys2) + && keys1.every(k => equalsByLevel(o1[k], o2[k], currentLevel - 1)) +} + +const equalsByLevel = (o1: any, o2: any, currentLevel: number) => + o1 === o2 + || currentLevel > 0 + && o1 + && o2 + && typeof o1 === 'object' + && typeof o2 === 'object' + && objectsAreTheSame(o1, o2, currentLevel) + +/** + * 0: === + * 1: shallow + * Infinity: deep + */ +export default (o1: any, o2: any, level: number) => { + if (!Number.isSafeInteger(level) || level < 0) { + throw new Error(`level must be a non-negative safe integer (i.e. from 0 to 9007199254740991 inclusive) or Infinity, but got ${level} instead`) + } + return equalsByLevel(o1, o2, level) +} diff --git a/src/utils/math/index.ts b/src/utils/math/index.ts index 967ad916a..fe28b0e07 100644 --- a/src/utils/math/index.ts +++ b/src/utils/math/index.ts @@ -57,7 +57,9 @@ export function offsetLine(points: Point[], d: number): Point[] { throw new Error('line must have 2 points') } if (points[0].equals(points[1])) { - throw new Error('points are overlapped') + console.error('points are overlapped:') + return points + // throw new Error('points are overlapped') } const vec = points[1].subtract(points[0]) const [o] = orthogonal(vec) diff --git a/src/utils/math/vector.ts b/src/utils/math/vector.ts index 639aaaa4a..d4bcdd204 100644 --- a/src/utils/math/vector.ts +++ b/src/utils/math/vector.ts @@ -2,8 +2,8 @@ import { Point, point } from 'leaflet' import { isArbitrarilySmall as isNumberSmall } from '.' -export type Ray = [Point, Point] -type Segment = [Point, Point] +export type Ray = Readonly<[Point, Point]> +type Segment = Readonly<[Point, Point]> enum Orientation { COLLINEAR, diff --git a/src/utils/memoizeObject.ts b/src/utils/memoizeObject.ts new file mode 100644 index 000000000..fa56da41b --- /dev/null +++ b/src/utils/memoizeObject.ts @@ -0,0 +1,10 @@ +import memoizeOne from 'memoize-one' +import { identity } from 'lodash' + +import shallowEqual from './shallowEqual' + +const comparator = (newArgs: [any], oldArgs: [any]) => + shallowEqual(newArgs[0], oldArgs[0]) + +export default () => + memoizeOne(identity, comparator) diff --git a/src/utils/shallowEqual.ts b/src/utils/shallowEqual.ts new file mode 100644 index 000000000..aa297b934 --- /dev/null +++ b/src/utils/shallowEqual.ts @@ -0,0 +1,4 @@ +import equalsByLevel from './equalsByLevel' + +export default (a: any, b: any) => + equalsByLevel(a, b, 1) diff --git a/src/utils/svg/filters.ts b/src/utils/svg/filters.ts index d57fc74c4..bec97e485 100644 --- a/src/utils/svg/filters.ts +++ b/src/utils/svg/filters.ts @@ -1,104 +1,5 @@ -import { createSVGElement } from '.' - const GLOW_FILTER_ID = 'black-glow' -export function makeDrop(): SVGFilterElement { - const filter = createSVGElement('filter') - filter.id = 'shadow' - filter.setAttribute('width', '200%') - filter.setAttribute('height', '200%') - filter.innerHTML = ` - - - - - ` - return filter -} - -export function makeGlow(): SVGFilterElement { - const filter = createSVGElement('filter') - filter.id = GLOW_FILTER_ID - filter.innerHTML = ` - - - - - - - ` - return filter -} - -export function makeOpacity(): SVGFilterElement { - const filter = createSVGElement('filter') - filter.id = 'opacity' - filter.innerHTML = ` - - - - ` - return filter -} - -export function makeGray(): SVGFilterElement { - const filter = createSVGElement('filter') - filter.id = 'gray' - filter.innerHTML = ` - - ` - return filter -} - -export function appendAll(defs: SVGDefsElement) { - defs.appendChild(makeDrop()) - defs.appendChild(makeGlow()) - defs.appendChild(makeOpacity()) - defs.appendChild(makeGray()) -} - export function applyDrop(path: SVGPathElement | SVGLineElement) { // fixing disappearing lines const box = path.getBoundingClientRect() diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 000000000..6fee95131 --- /dev/null +++ b/src/utils/types.ts @@ -0,0 +1,3 @@ +export type Omit = Pick> + +export type Subtract = Omit diff --git a/tsconfig.json b/tsconfig.json index d48aa00af..7b86bd186 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,10 +11,12 @@ "alwaysStrict": true, "strictBindCallApply": true, "experimentalDecorators": true, + "jsx": "react", "strictNullChecks": true, + "esModuleInterop": true, "noImplicitThis": true, - "noUnusedLocals": true, - // "downlevelIteration": true, + "noImplicitAny": true, + "downlevelIteration": true, "resolveJsonModule": true, "noEmitHelpers": true, "importHelpers": true, diff --git a/webpack/rules.js b/webpack/rules.js index 9ceb9a34b..932dcc11c 100644 --- a/webpack/rules.js +++ b/webpack/rules.js @@ -16,6 +16,7 @@ const tsOptions = (isDev) => isDev ? {} : { const getCssLoader = global => global ? 'css-loader' : { loader: 'css-loader', options: { + esModule: false, modules: { localIdentName: '[path]___[name]__[local]___[hash:base64:5]', }, @@ -36,7 +37,7 @@ module.exports = (isDev) => compact([ // use: 'source-map-loader', // }, { - test: /\.ts$/, + test: /\.tsx?$/, use: { loader: 'ts-loader', options: tsOptions(isDev), diff --git a/webpack/webpack.config.js b/webpack/webpack.config.js index b58239ede..df925bd71 100644 --- a/webpack/webpack.config.js +++ b/webpack/webpack.config.js @@ -27,7 +27,7 @@ module.exports = (env) => { mode: isDev ? 'development' : 'production', target: 'web', entry: { - app: './src/main.ts', + app: './src/index.ts', }, output: { clean: true,