diff --git a/.env b/.env index 3f191e1fdfb47f26281eeb4f6ea52ea423ed0d8b..470be3d78ae1c38d2951c67acae288685d447b9c 100644 --- a/.env +++ b/.env @@ -1,5 +1,6 @@ NEXT_PUBLIC_BASE_API_URL = 'https://corsproxy.io/?https://lux1.atcomp.pl/minerva/api' NEXT_PUBLIC_BASE_NEW_API_URL = 'https://corsproxy.io/?https://lux1.atcomp.pl/minerva/new_api/' +BASE_MAP_IMAGES_URL = 'https://lux1.atcomp.pl/' NEXT_PUBLIC_PROJECT_ID = 'pdmap_appu_test' ZOD_SEED = 997 diff --git a/.eslintrc.json b/.eslintrc.json index e60a21f06faa469391143c2f16c8d14f11ee5e97..3ed8a0dac26c9f37e4753346fdf8856d5351cabe 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -10,6 +10,7 @@ "plugin:react/recommended", "plugin:jsx-a11y/recommended", "plugin:@next/next/recommended", + "plugin:prettier/recommended", "prettier", "next" ], @@ -87,7 +88,8 @@ "callees": ["twMerge"], "config": "./tailwind.config.ts" } - ] + ], + "prettier/prettier": "error" }, "overrides": [ { diff --git a/package-lock.json b/package-lock.json index cbe12b20544abcdb8444e4320827acbf4ece6cee..f41b112587042b8d19ebbab8add5288dcd762146 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,6 +50,7 @@ "eslint-plugin-import": "^2.28.1", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-n": "^16.1.0", + "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", @@ -1885,6 +1886,26 @@ "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.8.4.tgz", "integrity": "sha512-kB+NJ5Br56ZhElKsf0pM7/PQfrDdDVMRz8f0JM6eVOGE+L89z9hwcst9QvWBBnazzuqGTGtPsJNZoQ1JdNiGSQ==" }, + "node_modules/@pkgr/utils": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", + "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "fast-glob": "^3.3.0", + "is-glob": "^4.0.3", + "open": "^9.1.0", + "picocolors": "^1.0.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@reduxjs/toolkit": { "version": "1.9.6", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.6.tgz", @@ -3208,6 +3229,15 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -3239,6 +3269,18 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, + "node_modules/bplist-parser": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", + "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.44" + }, + "engines": { + "node": ">= 5.10.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3347,6 +3389,21 @@ "semver": "^7.0.0" } }, + "node_modules/bundle-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", + "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", + "dev": true, + "dependencies": { + "run-applescript": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -4379,6 +4436,150 @@ "node": ">=0.10.0" } }, + "node_modules/default-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", + "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", + "dev": true, + "dependencies": { + "bundle-name": "^3.0.0", + "default-browser-id": "^3.0.0", + "execa": "^7.1.1", + "titleize": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", + "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", + "dev": true, + "dependencies": { + "bplist-parser": "^0.2.0", + "untildify": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/default-browser/node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/default-browser/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -4404,6 +4605,18 @@ "node": ">= 0.4" } }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-properties": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", @@ -5211,6 +5424,35 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-prettier": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz", + "integrity": "sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.5" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-plugin-promise": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", @@ -5730,6 +5972,12 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -6882,6 +7130,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6944,6 +7207,24 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-installed-globally": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", @@ -7206,6 +7487,33 @@ "node": ">=0.10.0" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-wsl/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -9784,6 +10092,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", + "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "dev": true, + "dependencies": { + "default-browser": "^4.0.0", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -10282,6 +10608,18 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/prettier-plugin-tailwindcss": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.6.tgz", @@ -11077,6 +11415,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/run-applescript": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", + "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -11720,6 +12073,22 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/synckit": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", + "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "dev": true, + "dependencies": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/tailwind-merge": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz", @@ -11841,6 +12210,18 @@ "readable-stream": "3" } }, + "node_modules/titleize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", + "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", diff --git a/package.json b/package.json index c359766c7f78ada52b5de72261d4290a64af0c10..c6c19cd69978bfb0f35c523f619b33883299f8d2 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "eslint-plugin-import": "^2.28.1", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-n": "^16.1.0", + "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", diff --git a/prettier.config.js b/prettier.config.js index fd41c7ea28115342ff628216fd2269ac7fb37e53..eec5dca5c09d171184a2d30ee3bd9e029b5e32c2 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -6,6 +6,7 @@ const config = { plugins: [import('prettier-plugin-tailwindcss')], tailwindConfig: './tailwind.config.ts', tailwindFunctions: ['twMerge'], + tabWidth: 2, }; module.exports = config; diff --git a/src/components/FunctionalArea/FunctionalArea.component.tsx b/src/components/FunctionalArea/FunctionalArea.component.tsx index 784d7fdeb3f64fa5ee1e14e942fb3a556f558054..d38bcd195aa44fcd1c7f18c3dd0c2f43dc912797 100644 --- a/src/components/FunctionalArea/FunctionalArea.component.tsx +++ b/src/components/FunctionalArea/FunctionalArea.component.tsx @@ -1,6 +1,6 @@ -import { TopBar } from '@/components/FunctionalArea/TopBar'; -import { NavBar } from '@/components/FunctionalArea/NavBar'; import { MapNavigation } from '@/components/FunctionalArea/MapNavigation'; +import { NavBar } from '@/components/FunctionalArea/NavBar'; +import { TopBar } from '@/components/FunctionalArea/TopBar'; export const FunctionalArea = (): JSX.Element => ( <> diff --git a/src/components/Map/MapViewer/utils/config/getMapTileUrl.test.ts b/src/components/Map/MapViewer/utils/config/getMapTileUrl.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..91d8919adcef11d7f6b19d9291487a7eac13315d --- /dev/null +++ b/src/components/Map/MapViewer/utils/config/getMapTileUrl.test.ts @@ -0,0 +1,34 @@ +import { BASE_MAP_IMAGES_URL } from '@/constants'; +import { getMapTileUrl } from './getMapTileUrl'; + +describe('getMapTileUrl - util', () => { + describe('when projectDirectory is empty', () => { + it('should return empty value', () => { + const projectDirectory = undefined; + const currentBackgroundImagePath = 'currentBackgroundImagePath'; + const result = ''; + + expect( + getMapTileUrl({ + projectDirectory, + currentBackgroundImagePath, + }), + ).toBe(result); + }); + }); + + describe('when all args are valid', () => { + it('should return correct value', () => { + const projectDirectory = 'directory'; + const currentBackgroundImagePath = 'currentBackgroundImagePath'; + const result = `${BASE_MAP_IMAGES_URL}/map_images/${projectDirectory}/${currentBackgroundImagePath}/{z}/{x}/{y}.PNG`; + + expect( + getMapTileUrl({ + projectDirectory, + currentBackgroundImagePath, + }), + ).toBe(result); + }); + }); +}); diff --git a/src/components/Map/MapViewer/utils/config/getMapTileUrl.ts b/src/components/Map/MapViewer/utils/config/getMapTileUrl.ts new file mode 100644 index 0000000000000000000000000000000000000000..0ba5eed4032787a6c9b5d2b9d377a4b6248b821b --- /dev/null +++ b/src/components/Map/MapViewer/utils/config/getMapTileUrl.ts @@ -0,0 +1,15 @@ +import { BASE_MAP_IMAGES_URL } from '@/constants'; + +export const getMapTileUrl = ({ + projectDirectory, + currentBackgroundImagePath, +}: { + projectDirectory?: string; + currentBackgroundImagePath: string; +}): string => { + if (!projectDirectory) { + return ''; + } + + return `${BASE_MAP_IMAGES_URL}/map_images/${projectDirectory}/${currentBackgroundImagePath}/{z}/{x}/{y}.PNG`; +}; diff --git a/src/components/Map/MapViewer/utils/useOlMapConfig.test.ts b/src/components/Map/MapViewer/utils/config/useOlMapConfig.test.ts similarity index 100% rename from src/components/Map/MapViewer/utils/useOlMapConfig.test.ts rename to src/components/Map/MapViewer/utils/config/useOlMapConfig.test.ts diff --git a/src/components/Map/MapViewer/utils/useOlMapConfig.ts b/src/components/Map/MapViewer/utils/config/useOlMapConfig.ts similarity index 73% rename from src/components/Map/MapViewer/utils/useOlMapConfig.ts rename to src/components/Map/MapViewer/utils/config/useOlMapConfig.ts index 9ac87292dec1aea948fa26dc151d15aee6c2ca14..2a584d8764593a4cfaa69b8d98e33cfe3b172159 100644 --- a/src/components/Map/MapViewer/utils/useOlMapConfig.ts +++ b/src/components/Map/MapViewer/utils/config/useOlMapConfig.ts @@ -1,6 +1,8 @@ /* eslint-disable no-magic-numbers */ import { OPTIONS } from '@/constants/map'; +import { currentBackgroundImagePathSelector } from '@/redux/backgrounds/background.selectors'; import { mapDataPositionSelector, mapDataSizeSelector } from '@/redux/map/map.selectors'; +import { projectDataSelector } from '@/redux/project/project.selectors'; import { Point } from '@/types/map'; import { usePointToProjection } from '@/utils/map/usePointToProjection'; import { View } from 'ol'; @@ -9,6 +11,7 @@ import TileLayer from 'ol/layer/Tile'; import { XYZ } from 'ol/source'; import { useMemo } from 'react'; import { useSelector } from 'react-redux'; +import { getMapTileUrl } from './getMapTileUrl'; interface UseOlMapConfigResult { view: View; @@ -18,6 +21,8 @@ interface UseOlMapConfigResult { export const useOlMapConfig = (): UseOlMapConfigResult => { const mapPosition = useSelector(mapDataPositionSelector); const mapSize = useSelector(mapDataSizeSelector); + const currentBackgroundImagePath = useSelector(currentBackgroundImagePathSelector); + const project = useSelector(projectDataSelector); const pointToProjection = usePointToProjection(); const center = useMemo(() => { @@ -40,19 +45,18 @@ export const useOlMapConfig = (): UseOlMapConfigResult => { ); const tileLayer = useMemo( - () => + (): TileLayer<XYZ> => new TileLayer({ visible: true, source: new XYZ({ - url: 'https://pdmap.uni.lu/map_images/9d4911bdeeea752f076e57a91d9b1f45/_nested0/{z}/{x}/{y}.PNG', - // TODO: build url from data in redux + url: getMapTileUrl({ projectDirectory: project?.directory, currentBackgroundImagePath }), maxZoom: mapSize.maxZoom, minZoom: mapSize.minZoom, tileSize: mapSize.tileSize, wrapX: OPTIONS.wrapXInTileLayer, }), }), - [mapSize], + [mapSize, currentBackgroundImagePath, project?.directory], ); return { diff --git a/src/components/Map/MapViewer/utils/useOlMap.ts b/src/components/Map/MapViewer/utils/useOlMap.ts index b7525d19859c5db6fefef9f9a8899b134bb91c7e..ca82591af5077a935f1a5833451885696e70a81c 100644 --- a/src/components/Map/MapViewer/utils/useOlMap.ts +++ b/src/components/Map/MapViewer/utils/useOlMap.ts @@ -1,8 +1,7 @@ import Map from 'ol/Map'; import React, { MutableRefObject, useEffect, useState } from 'react'; import { MapInstance } from '../MapViewer.types'; -import { useOlMapConfig } from './useOlMapConfig'; -import { useOlMapInit } from './useOlMapInit'; +import { useOlMapConfig } from './config/useOlMapConfig'; interface UseOlMapInput { target?: HTMLElement; @@ -18,7 +17,6 @@ export const useOlMap: UseOlMap = ({ target } = {}) => { const mapRef = React.useRef<null | HTMLDivElement>(null); const [mapInstance, setMapInstance] = useState<MapInstance>(undefined); const mapConfig = useOlMapConfig(); - useOlMapInit(); useEffect(() => { // checking if innerHTML is empty due to possibility of target element cloning by openlayers map instance diff --git a/src/components/Map/MapViewer/utils/useOlMapInit.test.ts b/src/components/Map/MapViewer/utils/useOlMapInit.test.ts deleted file mode 100644 index 100eaaee71d7afc9ab80ffdd5f711ac10d306e38..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/useOlMapInit.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -describe.skip('useOlMapConfig - util', () => { - // TODO: tests - // everything is mocked in the file, so we need to firstly wait for module API connection - - it('noop', () => { - // eslint-disable-next-line no-magic-numbers - expect(1).toEqual(1); - }); -}); diff --git a/src/components/Map/MapViewer/utils/useOlMapInit.ts b/src/components/Map/MapViewer/utils/useOlMapInit.ts deleted file mode 100644 index 335758e7f5ac6d9f7c7fed938b7389de3b41605b..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/useOlMapInit.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* eslint-disable no-magic-numbers */ -// TODO: Remove mocks and implement communication with API - -import { DEFAULT_ZOOM } from '@/constants/map'; -import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; -import { setMapData } from '@/redux/map/map.slice'; -import { useCallback, useEffect } from 'react'; - -const MOCK_PROJECT = { - version: '', - disease: { - link: 'http://id.nlm.nih.gov/mesh/D010300', - type: 'MESH_2012', - resource: 'D010300', - id: 3211856, - annotatorClassName: '', - }, - idObject: 6065, - status: 'Ok', - directory: '9d4911bdeeea752f076e57a91d9b1f45', - progress: 100, - notifyEmail: 'ewa.smula@uni.lu', - logEntries: true, - name: "Parkinson's disease map", - sharedInMinervaNet: true, - owner: 'ewa.smula', - projectId: 'pd_map_winter_23', - creationDate: '2023-02-15 16:35:11', - mapCanvasType: 'OPEN_LAYERS', -}; - -const MOCK_MODEL = { - idObject: 5053, - width: 26779.25, - height: 13503, - defaultCenterX: null, - defaultCenterY: null, - description: '', - name: 'Core PD map', - defaultZoomLevel: null, - tileSize: 256, - references: [], - authors: [], - creationDate: null, - modificationDates: [], - minZoom: 2, - maxZoom: 9, -}; - -export const useOlMapInit = (): void => { - const dispatch = useAppDispatch(); - - const mapInit = useCallback(() => { - dispatch( - setMapData({ - meshId: MOCK_PROJECT.disease.resource, - modelId: MOCK_MODEL.idObject, - size: { - width: MOCK_MODEL.width, - height: MOCK_MODEL.height, - tileSize: MOCK_MODEL.tileSize, - minZoom: MOCK_MODEL.minZoom, - maxZoom: MOCK_MODEL.maxZoom, - }, - position: { - x: MOCK_MODEL.defaultCenterX || MOCK_MODEL.width / 2, - y: MOCK_MODEL.defaultCenterY || MOCK_MODEL.height / 2, - z: MOCK_MODEL.defaultZoomLevel || DEFAULT_ZOOM, - }, - }), - ); - }, [dispatch]); - - useEffect(() => mapInit(), [mapInit]); -}; diff --git a/src/components/SPA/MinervaSPA.component.tsx b/src/components/SPA/MinervaSPA.component.tsx index c9b5381584b0a04df6a647779e15e82e4bff4962..856e3487b746b504123995ebdf46329736f81787 100644 --- a/src/components/SPA/MinervaSPA.component.tsx +++ b/src/components/SPA/MinervaSPA.component.tsx @@ -1,10 +1,8 @@ +import { FunctionalArea } from '@/components/FunctionalArea'; +import { Map } from '@/components/Map'; import { Manrope } from '@next/font/google'; import { twMerge } from 'tailwind-merge'; -import { Map } from '@/components/Map'; -import { FunctionalArea } from '@/components/FunctionalArea'; -import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; -import { useEffect } from 'react'; -import { getModels } from '@/redux/models/models.thunks'; +import { useInitializeStore } from './utils/useInitializeStore'; const manrope = Manrope({ variable: '--font-manrope', @@ -14,11 +12,7 @@ const manrope = Manrope({ }); export const MinervaSPA = (): JSX.Element => { - const dispatch = useAppDispatch(); - - useEffect(() => { - dispatch(getModels()); - }, [dispatch]); + useInitializeStore(); return ( <div className={twMerge('relative', manrope.variable)}> diff --git a/src/components/SPA/utils/useInitializeStore.test.ts b/src/components/SPA/utils/useInitializeStore.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..d1b7da08d0e98f26cc0fc4403efc1c333dfa877b --- /dev/null +++ b/src/components/SPA/utils/useInitializeStore.test.ts @@ -0,0 +1,85 @@ +import { PROJECT_ID } from '@/constants'; +import { backgroundsFixture } from '@/models/fixtures/backgroundsFixture'; +import { modelsFixture } from '@/models/fixtures/modelsFixture'; +import { overlaysFixture } from '@/models/fixtures/overlaysFixture'; +import { projectFixture } from '@/models/fixtures/projectFixture'; +import { apiPath } from '@/redux/apiPath'; +import { backgroundsDataSelector } from '@/redux/backgrounds/background.selectors'; +import { modelsDataSelector } from '@/redux/models/models.selectors'; +import { overlaysDataSelector } from '@/redux/overlays/overlays.selectors'; +import { projectDataSelector } from '@/redux/project/project.selectors'; +import { initDataLoadingInitialized } from '@/redux/root/init.selectors'; +import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; +import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; +import { renderHook, waitFor } from '@testing-library/react'; +import { HttpStatusCode } from 'axios'; +import * as hook from './useInitializeStore'; + +const mockedAxiosClient = mockNetworkResponse(); + +describe('useInitializeStore - hook', () => { + describe('when fired', () => { + beforeAll(() => { + mockedAxiosClient.onGet(apiPath.getModelsString()).reply(HttpStatusCode.Ok, modelsFixture); + mockedAxiosClient + .onGet(apiPath.getAllOverlaysByProjectIdQuery(PROJECT_ID, { publicOverlay: true })) + .reply(HttpStatusCode.Ok, overlaysFixture); + mockedAxiosClient + .onGet(apiPath.getProjectById(PROJECT_ID)) + .reply(HttpStatusCode.Ok, projectFixture); + mockedAxiosClient + .onGet(apiPath.getAllBackgroundsByProjectIdQuery(PROJECT_ID)) + .reply(HttpStatusCode.Ok, backgroundsFixture); + }); + + it('should fetch project data in store', async () => { + const { Wrapper, store } = getReduxWrapperWithStore(); + renderHook(() => hook.useInitializeStore(), { wrapper: Wrapper }); + + await waitFor(() => { + const data = projectDataSelector(store.getState()); + expect(data).toEqual(projectFixture); + }); + }); + + it('should fetch backgrounds data in store', async () => { + const { Wrapper, store } = getReduxWrapperWithStore(); + renderHook(() => hook.useInitializeStore(), { wrapper: Wrapper }); + + await waitFor(() => { + const data = backgroundsDataSelector(store.getState()); + expect(data).toEqual(backgroundsFixture); + }); + }); + + it('should fetch overlays data in store', async () => { + const { Wrapper, store } = getReduxWrapperWithStore(); + renderHook(() => hook.useInitializeStore(), { wrapper: Wrapper }); + + await waitFor(() => { + const data = overlaysDataSelector(store.getState()); + expect(data).toEqual(overlaysFixture); + }); + }); + + it('should fetch models data in store', async () => { + const { Wrapper, store } = getReduxWrapperWithStore(); + renderHook(() => hook.useInitializeStore(), { wrapper: Wrapper }); + + await waitFor(() => { + const data = modelsDataSelector(store.getState()); + expect(data).toEqual(modelsFixture); + }); + }); + + it('should use valid initialize value', () => { + const { Wrapper, store } = getReduxWrapperWithStore(); + const initializedeBefore = initDataLoadingInitialized(store.getState()); + renderHook(() => hook.useInitializeStore(), { wrapper: Wrapper }); + const initializedAfter = initDataLoadingInitialized(store.getState()); + + expect(initializedeBefore).toBe(false); + expect(initializedAfter).toBe(true); + }); + }); +}); diff --git a/src/components/SPA/utils/useInitializeStore.ts b/src/components/SPA/utils/useInitializeStore.ts new file mode 100644 index 0000000000000000000000000000000000000000..7e2fb0af61681ddf2f73cefa1571580fd8b36022 --- /dev/null +++ b/src/components/SPA/utils/useInitializeStore.ts @@ -0,0 +1,29 @@ +import { PROJECT_ID } from '@/constants'; +import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; +import { initMapData } from '@/redux/map/map.thunks'; +import { getProjectById } from '@/redux/project/project.thunks'; +import { initDataLoadingInitialized } from '@/redux/root/init.selectors'; +import { AppDispatch } from '@/redux/store'; +import { useEffect } from 'react'; +import { useSelector } from 'react-redux'; + +/* prettier-ignore */ +export const getInitStoreData = + () => + (dispatch: AppDispatch): void => { + dispatch(getProjectById(PROJECT_ID)); + dispatch(initMapData()); + }; + +export const useInitializeStore = (): void => { + const dispatch = useAppDispatch(); + const isInitialized = useSelector(initDataLoadingInitialized); + + useEffect(() => { + if (isInitialized) { + return; + } + + dispatch(getInitStoreData()); + }, [dispatch, isInitialized]); +}; diff --git a/src/models/fixtures/modelsFixture.ts b/src/models/fixtures/modelsFixture.ts index 2eb9109dbac56042aafff5dd0a25a49f61be2c64..91bc15d71cd0b9d631fe2ceb68aa3f09ee9e557d 100644 --- a/src/models/fixtures/modelsFixture.ts +++ b/src/models/fixtures/modelsFixture.ts @@ -8,3 +8,8 @@ export const modelsFixture = createFixture(z.array(mapModelSchema), { seed: ZOD_SEED, array: { min: 3, max: 3 }, }); + +export const singleModelFixture = createFixture(mapModelSchema, { + seed: ZOD_SEED, + array: { min: 3, max: 3 }, +}); diff --git a/src/redux/backgrounds/background.selectors.ts b/src/redux/backgrounds/background.selectors.ts index 01b6ed81bf434fb40ee56ec1784e3365ea8c2666..16b233972a19c5eb7edc916f74c716c41550e48b 100644 --- a/src/redux/backgrounds/background.selectors.ts +++ b/src/redux/backgrounds/background.selectors.ts @@ -6,7 +6,7 @@ export const backgroundsSelector = createSelector(rootSelector, state => state.b export const backgroundsDataSelector = createSelector( backgroundsSelector, - backgrounds => backgrounds.data || [], + backgrounds => backgrounds?.data || [], ); export const currentBackgroundSelector = createSelector( diff --git a/src/redux/map/map.constants.ts b/src/redux/map/map.constants.ts index a6307772f6c81239b02d58d839c0e050112fa761..ba88a03811be7e0e0d08fe58bb13ceaff6f51d65 100644 --- a/src/redux/map/map.constants.ts +++ b/src/redux/map/map.constants.ts @@ -26,3 +26,5 @@ export const MAP_DATA_INITIAL_STATE: MapData = { maxZoom: DEFAULT_MAX_ZOOM, }, }; + +export const MIDDLEWARE_ALLOWED_ACTIONS: string[] = ['map/setMapData', 'map/initMapData']; diff --git a/src/redux/map/map.reducers.ts b/src/redux/map/map.reducers.ts index 95d763254f76bcea970ec8f428e2082e80fba5d2..962704b36070701d2e763147d57f2f99c8751ae8 100644 --- a/src/redux/map/map.reducers.ts +++ b/src/redux/map/map.reducers.ts @@ -1,9 +1,22 @@ -import { PayloadAction } from '@reduxjs/toolkit'; -import { MapData, MapState } from './map.types'; +import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; +import { initMapData } from './map.thunks'; +import { MapState, SetMapDataAction } from './map.types'; -export const setMapDataReducer = ( - state: MapState, - action: PayloadAction<Partial<MapData> | undefined>, -): void => { +export const setMapDataReducer = (state: MapState, action: SetMapDataAction): void => { state.data = { ...state.data, ...action.payload }; }; + +export const getMapReducers = (builder: ActionReducerMapBuilder<MapState>): void => { + builder.addCase(initMapData.pending, state => { + state.loading = 'pending'; + }); + builder.addCase(initMapData.fulfilled, (state, action) => { + const payload = action.payload || {}; + state.data = { ...state.data, ...payload }; + state.loading = 'succeeded'; + }); + builder.addCase(initMapData.rejected, state => { + state.loading = 'failed'; + // TODO to discuss manage state of failure + }); +}; diff --git a/src/redux/map/map.slice.ts b/src/redux/map/map.slice.ts index 62b95df2be671ec12d4dec2341978c586ef235c2..3565dc2500f9c45182b563f636f16fdf5b47f4a9 100644 --- a/src/redux/map/map.slice.ts +++ b/src/redux/map/map.slice.ts @@ -1,6 +1,6 @@ import { createSlice } from '@reduxjs/toolkit'; import { MAP_DATA_INITIAL_STATE } from './map.constants'; -import { setMapDataReducer } from './map.reducers'; +import { getMapReducers, setMapDataReducer } from './map.reducers'; import { MapState } from './map.types'; const initialState: MapState = { @@ -15,6 +15,9 @@ const mapSlice = createSlice({ reducers: { setMapData: setMapDataReducer, }, + extraReducers: builder => { + getMapReducers(builder); + }, }); export const { setMapData } = mapSlice.actions; diff --git a/src/redux/map/map.thunks.test.ts b/src/redux/map/map.thunks.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..b14e744dab577afaa1b8f25b56d76f2162053908 --- /dev/null +++ b/src/redux/map/map.thunks.test.ts @@ -0,0 +1,87 @@ +import { PROJECT_ID } from '@/constants'; +import { backgroundsFixture } from '@/models/fixtures/backgroundsFixture'; +import { modelsFixture } from '@/models/fixtures/modelsFixture'; +import { overlaysFixture } from '@/models/fixtures/overlaysFixture'; +import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; +import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; +import { HttpStatusCode } from 'axios'; +import { apiPath } from '../apiPath'; +import { backgroundsDataSelector } from '../backgrounds/background.selectors'; +import { modelsDataSelector } from '../models/models.selectors'; +import { overlaysDataSelector } from '../overlays/overlays.selectors'; +import { AppDispatch, StoreType } from '../store'; +import { initMapData } from './map.thunks'; +import { InitMapDataActionPayload } from './map.types'; + +const mockedAxiosClient = mockNetworkResponse(); + +describe('map thunks', () => { + describe('initMapData - thunk', () => { + describe('when API is returning valid data', () => { + let store = {} as StoreType; + let payload = {} as InitMapDataActionPayload; + + beforeAll(async () => { + mockedAxiosClient.resetHandlers(); + mockedAxiosClient.onGet(apiPath.getModelsString()).reply(HttpStatusCode.Ok, modelsFixture); + mockedAxiosClient + .onGet(apiPath.getAllOverlaysByProjectIdQuery(PROJECT_ID, { publicOverlay: true })) + .reply(HttpStatusCode.Ok, overlaysFixture); + mockedAxiosClient + .onGet(apiPath.getAllBackgroundsByProjectIdQuery(PROJECT_ID)) + .reply(HttpStatusCode.Ok, backgroundsFixture); + + store = getReduxWrapperWithStore().store; + const dispatch = store.dispatch as AppDispatch; + payload = (await dispatch(initMapData())).payload as InitMapDataActionPayload; + }); + + it('should fetch backgrounds data in store', async () => { + const data = backgroundsDataSelector(store.getState()); + expect(data).toEqual(backgroundsFixture); + }); + + it('should fetch overlays data in store', async () => { + const data = overlaysDataSelector(store.getState()); + expect(data).toEqual(overlaysFixture); + }); + + it('should fetch models data in store', async () => { + const data = modelsDataSelector(store.getState()); + expect(data).toEqual(modelsFixture); + }); + + it('should return valid payload', () => { + const FIRST = 0; + + expect(payload).toMatchObject({ + modelId: modelsFixture[FIRST].idObject, + backgroundId: backgroundsFixture[FIRST].id, + }); + }); + }); + + describe('when API is returning empty array', () => { + let store = {} as StoreType; + let payload = {} as InitMapDataActionPayload; + + beforeEach(async () => { + mockedAxiosClient.onGet(apiPath.getModelsString()).reply(HttpStatusCode.Ok, []); + mockedAxiosClient + .onGet(apiPath.getAllOverlaysByProjectIdQuery(PROJECT_ID, { publicOverlay: true })) + .reply(HttpStatusCode.Ok, []); + mockedAxiosClient + .onGet(apiPath.getAllBackgroundsByProjectIdQuery(PROJECT_ID)) + .reply(HttpStatusCode.Ok, []); + + store = getReduxWrapperWithStore().store; + const dispatch = store.dispatch as AppDispatch; + payload = (await dispatch(initMapData())).payload as InitMapDataActionPayload; + }); + + it('should return empty payload', () => { + expect(payload).toStrictEqual({}); + }); + }); + }); +}); diff --git a/src/redux/map/map.thunks.ts b/src/redux/map/map.thunks.ts new file mode 100644 index 0000000000000000000000000000000000000000..e6d8fef456ef2f0bda91c46d99cd7c45347c192e --- /dev/null +++ b/src/redux/map/map.thunks.ts @@ -0,0 +1,41 @@ +import { PROJECT_ID } from '@/constants'; +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { backgroundsDataSelector } from '../backgrounds/background.selectors'; +import { getAllBackgroundsByProjectId } from '../backgrounds/backgrounds.thunks'; +import { modelsDataSelector } from '../models/models.selectors'; +import { getModels } from '../models/models.thunks'; +import { getAllPublicOverlaysByProjectId } from '../overlays/overlays.thunks'; +import type { AppDispatch, RootState } from '../store'; +import { InitMapDataActionPayload } from './map.types'; + +const getPayloadFromState = (state: RootState): InitMapDataActionPayload => { + const FIRST = 0; + const models = modelsDataSelector(state); + const backgrounds = backgroundsDataSelector(state); + const modelId = models?.[FIRST]?.idObject; + const backgroundId = backgrounds?.[FIRST]?.id; + + if (!modelId || !backgroundId) { + return {}; + } + + return { + modelId, + backgroundId, + }; +}; + +export const initMapData = createAsyncThunk< + InitMapDataActionPayload, + void, + { dispatch: AppDispatch; state: RootState } +>('map/initMapData', async (_, { dispatch, getState }): Promise<InitMapDataActionPayload> => { + await Promise.all([ + dispatch(getAllBackgroundsByProjectId(PROJECT_ID)), + dispatch(getAllPublicOverlaysByProjectId(PROJECT_ID)), + dispatch(getModels()), + ]); + + const state = getState(); + return getPayloadFromState(state); +}); diff --git a/src/redux/map/map.types.ts b/src/redux/map/map.types.ts index ac46d776e601f8ae23c18c2e0dd58de9edb8fe75..7051691ed57d56fe19e934c18e8fa020a9f9246e 100644 --- a/src/redux/map/map.types.ts +++ b/src/redux/map/map.types.ts @@ -1,5 +1,6 @@ import { FetchDataState } from '@/types/fetchDataState'; import { Point } from '@/types/map'; +import { PayloadAction } from '@reduxjs/toolkit'; export interface MapSize { width: number; @@ -24,3 +25,15 @@ export type MapData = { }; export type MapState = FetchDataState<MapData, MapData>; + +export type SetMapDataActionPayload = Partial<MapData> | undefined; + +export type SetMapDataAction = PayloadAction<SetMapDataActionPayload>; + +export type InitMapDataActionPayload = { modelId: number; backgroundId: number } | object; + +export type InitMapDataAction = PayloadAction<SetMapDataAction>; + +export type MiddlewareAllowedAction = PayloadAction< + SetMapDataActionPayload | InitMapDataActionPayload +>; diff --git a/src/redux/map/middleware/checkIfIsMapUpdateActionValid.test.ts b/src/redux/map/middleware/checkIfIsMapUpdateActionValid.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..aa889249020931981df2631afd7550950ced7dbf --- /dev/null +++ b/src/redux/map/middleware/checkIfIsMapUpdateActionValid.test.ts @@ -0,0 +1,68 @@ +import { RootState } from '@/redux/store'; +import { Loading } from '@/types/loadingState'; +import { MAP_DATA_INITIAL_STATE, MIDDLEWARE_ALLOWED_ACTIONS } from '../map.constants'; +import { SetMapDataAction } from '../map.types'; +import { checkIfIsMapUpdateActionValid } from './checkIfIsMapUpdateActionValid'; + +const state: Pick<RootState, 'map'> = { + map: { + data: { + ...MAP_DATA_INITIAL_STATE, + modelId: 2137, + }, + loading: 'idle' as Loading, + error: { name: '', message: '' }, + }, +}; + +describe('checkIfIsActionValid - util', () => { + describe('when action payload has model id equal to current', () => { + const modelId = 2137; + + it.each(MIDDLEWARE_ALLOWED_ACTIONS)('should return false', actionType => { + expect( + checkIfIsMapUpdateActionValid( + { + type: actionType, + payload: { + modelId, + }, + } as SetMapDataAction, + state as RootState, + ), + ).toBe(false); + }); + }); + + describe('when action payload has model id different than current', () => { + const modelId = 997; + + it.each(MIDDLEWARE_ALLOWED_ACTIONS)('should return true', actionType => { + expect( + checkIfIsMapUpdateActionValid( + { + type: actionType, + payload: { + modelId, + }, + } as SetMapDataAction, + state as RootState, + ), + ).toBe(true); + }); + }); + + describe('when action payload has NOT model id', () => { + it.each(MIDDLEWARE_ALLOWED_ACTIONS)('should return true', actionType => { + expect( + checkIfIsMapUpdateActionValid( + { + type: actionType, + payload: {}, + } as SetMapDataAction, + state as RootState, + ), + ).toBe(true); + }); + }); +}); diff --git a/src/redux/map/middleware/checkIfIsMapUpdateActionValid.ts b/src/redux/map/middleware/checkIfIsMapUpdateActionValid.ts new file mode 100644 index 0000000000000000000000000000000000000000..19379e967a2afcb41d9d21f646b210d2361519ff --- /dev/null +++ b/src/redux/map/middleware/checkIfIsMapUpdateActionValid.ts @@ -0,0 +1,11 @@ +import type { RootState } from '@/redux/store'; +import { Action } from '@reduxjs/toolkit'; +import { getModelIdFromAction } from './getModelIdFromAction'; + +export const checkIfIsMapUpdateActionValid = (action: Action, state: RootState): boolean => { + const { modelId: currentModelId } = state.map.data; + const payloadModelId = getModelIdFromAction(action); + const isModelIdTheSame = currentModelId === payloadModelId; + + return !isModelIdTheSame; +}; diff --git a/src/redux/map/middleware/getModelIdFromAction.ts b/src/redux/map/middleware/getModelIdFromAction.ts new file mode 100644 index 0000000000000000000000000000000000000000..c7e786ae947b5faa7328e540b3c5f003cb30441b --- /dev/null +++ b/src/redux/map/middleware/getModelIdFromAction.ts @@ -0,0 +1,10 @@ +import { Action } from '@reduxjs/toolkit'; + +export const getModelIdFromAction = (action: Action): number | null => { + try { + const payload = 'payload' in action ? (action.payload as object) : {}; + return 'modelId' in payload ? (payload.modelId as number) : null; + } catch { + return null; + } +}; diff --git a/src/redux/map/middleware/getUpdatedModel.test.ts b/src/redux/map/middleware/getUpdatedModel.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..65cd7f25616a14c8722c967f7bf850fd70174629 --- /dev/null +++ b/src/redux/map/middleware/getUpdatedModel.test.ts @@ -0,0 +1,53 @@ +/* eslint-disable no-magic-numbers */ +import { modelsFixture } from '@/models/fixtures/modelsFixture'; +import { RootState } from '@/redux/store'; +import { MIDDLEWARE_ALLOWED_ACTIONS } from '../map.constants'; +import { getUpdatedModel } from './getUpdatedModel'; + +const state: Pick<RootState, 'models'> = { + models: { + data: modelsFixture, + loading: 'idle', + error: { name: '', message: '' }, + }, +}; + +describe('getUpdatedModel - util', () => { + describe('when payload has valid modelId', () => { + const model = modelsFixture[0]; + const action = { + type: MIDDLEWARE_ALLOWED_ACTIONS[0], + payload: { + modelId: model.idObject, + }, + }; + + it('returns model object', () => { + expect(getUpdatedModel(action, state as RootState)).toStrictEqual(model); + }); + }); + + describe('when payload has invalid modelId', () => { + const action = { + type: MIDDLEWARE_ALLOWED_ACTIONS[0], + payload: { + modelId: null, + }, + }; + + it('returns undefined', () => { + expect(getUpdatedModel(action, state as RootState)).toStrictEqual(undefined); + }); + }); + + describe('when payload does not have modelId', () => { + const action = { + type: MIDDLEWARE_ALLOWED_ACTIONS[0], + payload: {}, + }; + + it('returns undefined', () => { + expect(getUpdatedModel(action, state as RootState)).toStrictEqual(undefined); + }); + }); +}); diff --git a/src/redux/map/middleware/getUpdatedModel.ts b/src/redux/map/middleware/getUpdatedModel.ts new file mode 100644 index 0000000000000000000000000000000000000000..4302c5525250f2d1fe9f980a5359d897ab3f3ef6 --- /dev/null +++ b/src/redux/map/middleware/getUpdatedModel.ts @@ -0,0 +1,12 @@ +import { modelsDataSelector } from '@/redux/models/models.selectors'; +import type { RootState } from '@/redux/store'; +import { MapModel } from '@/types/models'; +import { Action } from '@reduxjs/toolkit'; +import { getModelIdFromAction } from './getModelIdFromAction'; + +export const getUpdatedModel = (action: Action, state: RootState): MapModel | undefined => { + const models = modelsDataSelector(state); + const payloadModelId = getModelIdFromAction(action); + + return models.find(model => model.idObject === payloadModelId); +}; diff --git a/src/redux/map/middleware/map.middleware.test.ts b/src/redux/map/middleware/map.middleware.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..c821eeda0b149f11cfaab37b30feb5aa3e5061ce --- /dev/null +++ b/src/redux/map/middleware/map.middleware.test.ts @@ -0,0 +1,134 @@ +/* eslint-disable no-magic-numbers */ +import { modelsFixture } from '@/models/fixtures/modelsFixture'; +import { Loading } from '@/types/loadingState'; +import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; +import { Action } from '@reduxjs/toolkit'; +import { MAP_DATA_INITIAL_STATE, MIDDLEWARE_ALLOWED_ACTIONS } from '../map.constants'; +import * as setMapData from '../map.slice'; +import * as checkIfIsMapUpdateActionValid from './checkIfIsMapUpdateActionValid'; +import * as getUpdatedModel from './getUpdatedModel'; +import { mapDataMiddlewareListener } from './map.middleware'; + +jest.mock('./checkIfIsMapUpdateActionValid', () => { + return { + __esModule: true, + ...jest.requireActual('./checkIfIsMapUpdateActionValid'), + }; +}); + +jest.mock('./getUpdatedModel', () => { + return { + __esModule: true, + ...jest.requireActual('./getUpdatedModel'), + }; +}); + +jest.mock('./getUpdatedModel', () => { + return { + __esModule: true, + ...jest.requireActual('./getUpdatedModel'), + }; +}); + +jest.mock('../map.slice', () => { + return { + __esModule: true, + ...jest.requireActual('../map.slice'), + }; +}); + +const defaultSliceState = { + data: [], + loading: 'idle' as Loading, + error: { name: '', message: '' }, +}; + +const checkIfIsMapUpdateActionValidSpy = jest.spyOn( + checkIfIsMapUpdateActionValid, + 'checkIfIsMapUpdateActionValid', +); +const getUpdatedModelSpy = jest.spyOn(getUpdatedModel, 'getUpdatedModel'); +const setMapDataSpy = jest.spyOn(setMapData, 'setMapData'); + +const { store } = getReduxWrapperWithStore({ + map: { + ...defaultSliceState, + data: { + ...MAP_DATA_INITIAL_STATE, + modelId: modelsFixture[0].idObject, + }, + }, + models: { + ...defaultSliceState, + data: modelsFixture, + }, +}); + +const doDispatch = jest.fn(); +const doGetState = store.getState; + +const handler = async (action: Action): Promise<void> => + mapDataMiddlewareListener(action, { + dispatch: doDispatch, + getOriginalState: doGetState, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any); + +describe('map middleware', () => { + describe('on listen', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('when model is valid and different than current', () => { + it.each(MIDDLEWARE_ALLOWED_ACTIONS)('should dispatch setMapData', async actionType => { + const model = modelsFixture[1]; + + const action = { + payload: { + modelId: model.idObject, + }, + type: actionType, + }; + + await handler(action); + expect(checkIfIsMapUpdateActionValidSpy).toHaveLastReturnedWith(true); + expect(getUpdatedModelSpy).toHaveLastReturnedWith(model); + expect(setMapDataSpy).toBeCalled(); + }); + }); + + describe('when model is valid and identical with current', () => { + it.each(MIDDLEWARE_ALLOWED_ACTIONS)('should NOT dispatch setMapData', async actionType => { + const model = modelsFixture[0]; + const action = { + payload: { + modelId: model.idObject, + }, + type: actionType, + }; + + await handler(action); + expect(checkIfIsMapUpdateActionValidSpy).toHaveLastReturnedWith(false); + expect(getUpdatedModelSpy).toHaveLastReturnedWith(model); + expect(setMapDataSpy).not.toBeCalled(); + }); + }); + + describe('when model is NOT valid', () => { + it.each(MIDDLEWARE_ALLOWED_ACTIONS)('should NOT dispatch setMapData', async actionType => { + const action = { + payload: { + modelId: null, + }, + type: actionType, + }; + + await handler(action); + expect(checkIfIsMapUpdateActionValidSpy).toHaveLastReturnedWith(true); + expect(getUpdatedModelSpy).toHaveLastReturnedWith(undefined); + expect(setMapDataSpy).not.toBeCalled(); + }); + }); + }); +}); diff --git a/src/redux/map/middleware/map.middleware.ts b/src/redux/map/middleware/map.middleware.ts new file mode 100644 index 0000000000000000000000000000000000000000..0221d9f6a2705f68cad2a51b2c1c24c7b2cb7565 --- /dev/null +++ b/src/redux/map/middleware/map.middleware.ts @@ -0,0 +1,37 @@ +import type { AppListenerEffectAPI, AppStartListening } from '@/redux/store'; +import { getUpdatedMapData } from '@/utils/map/getUpdatedMapData'; +import { Action, createListenerMiddleware } from '@reduxjs/toolkit'; +import { setMapData } from '../map.slice'; +import { initMapData } from '../map.thunks'; +import { checkIfIsMapUpdateActionValid } from './checkIfIsMapUpdateActionValid'; +import { getUpdatedModel } from './getUpdatedModel'; + +export const mapListenerMiddleware = createListenerMiddleware(); + +const startListening = mapListenerMiddleware.startListening as AppStartListening; + +export const mapDataMiddlewareListener = async ( + action: Action, + { getOriginalState, dispatch }: AppListenerEffectAPI, +): Promise<void> => { + const state = getOriginalState(); + const updatedModel = getUpdatedModel(action, state); + const isActionValid = checkIfIsMapUpdateActionValid(action, state); + + if (!updatedModel || !isActionValid) { + return; + } + + const updatedMapData = getUpdatedMapData({ model: updatedModel }); + dispatch(setMapData(updatedMapData)); +}; + +startListening({ + actionCreator: initMapData.fulfilled, + effect: mapDataMiddlewareListener, +}); + +startListening({ + type: 'map/setMapData', + effect: mapDataMiddlewareListener, +}); diff --git a/src/redux/models/models.selectors.ts b/src/redux/models/models.selectors.ts index 1ae57c7e2e962dc33485300be74d02a61e888d91..8a9478b171d44dfc00afb4a23f34517b14ab6d33 100644 --- a/src/redux/models/models.selectors.ts +++ b/src/redux/models/models.selectors.ts @@ -4,7 +4,7 @@ import { mapDataSelector } from '../map/map.selectors'; export const modelsSelector = createSelector(rootSelector, state => state.models); -export const modelsDataSelector = createSelector(modelsSelector, models => models.data || []); +export const modelsDataSelector = createSelector(modelsSelector, models => models?.data || []); export const currentModelSelector = createSelector( modelsDataSelector, diff --git a/src/redux/models/models.thunks.test.ts b/src/redux/models/models.thunks.test.ts index 3d85a15e274e0d0ee6352353d25e504de8097259..a8daf71d82f9a7ef7bd229df4afe8947e9c6c38a 100644 --- a/src/redux/models/models.thunks.test.ts +++ b/src/redux/models/models.thunks.test.ts @@ -1,12 +1,12 @@ -import { HttpStatusCode } from 'axios'; -import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; +import { modelsFixture } from '@/models/fixtures/modelsFixture'; +import { apiPath } from '@/redux/apiPath'; +import { ModelsState } from '@/redux/models/models.types'; import { ToolkitStoreWithSingleSlice, createStoreInstanceUsingSliceReducer, } from '@/utils/createStoreInstanceUsingSliceReducer'; -import { ModelsState } from '@/redux/models/models.types'; -import { apiPath } from '@/redux/apiPath'; -import { modelsFixture } from '@/models/fixtures/modelsFixture'; +import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; +import { HttpStatusCode } from 'axios'; import modelsReducer from './models.slice'; import { getModels } from './models.thunks'; diff --git a/src/redux/overlays/overlays.selectors.ts b/src/redux/overlays/overlays.selectors.ts index 80b0439bc90d4f04f09ac02c9b5a64803dff2fd4..f9d36cad0a63c93ae55565e795b897c6bbd4db3e 100644 --- a/src/redux/overlays/overlays.selectors.ts +++ b/src/redux/overlays/overlays.selectors.ts @@ -5,5 +5,5 @@ export const overlaysSelector = createSelector(rootSelector, state => state.over export const overlaysDataSelector = createSelector( overlaysSelector, - overlays => overlays.data || [], + overlays => overlays?.data || [], ); diff --git a/src/redux/project/project.selectors.ts b/src/redux/project/project.selectors.ts index 9ba0ec033ee3257bc54f091e2194b7af739d4b60..2725956ae48088e4c4c8de5054b5425c3a73981d 100644 --- a/src/redux/project/project.selectors.ts +++ b/src/redux/project/project.selectors.ts @@ -3,4 +3,4 @@ import { rootSelector } from '../root/root.selectors'; export const projectSelector = createSelector(rootSelector, state => state.project); -export const projectDataSelector = createSelector(projectSelector, project => project.data); +export const projectDataSelector = createSelector(projectSelector, project => project?.data); diff --git a/src/redux/root/init.selectors.ts b/src/redux/root/init.selectors.ts new file mode 100644 index 0000000000000000000000000000000000000000..82176735f129c72fbb1161a73b6ee241f3e6909c --- /dev/null +++ b/src/redux/root/init.selectors.ts @@ -0,0 +1,13 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { backgroundsSelector } from '../backgrounds/background.selectors'; +import { modelsSelector } from '../models/models.selectors'; +import { overlaysSelector } from '../overlays/overlays.selectors'; +import { projectSelector } from '../project/project.selectors'; + +export const initDataLoadingInitialized = createSelector( + projectSelector, + backgroundsSelector, + modelsSelector, + overlaysSelector, + (...selectors) => selectors.every(selector => selector.loading !== 'idle'), +); diff --git a/src/redux/root/mapStages.selectors.ts b/src/redux/root/mapStages.selectors.ts deleted file mode 100644 index cda0f3b219e9b7f7fe6e7d9ebd7099b9f0530271..0000000000000000000000000000000000000000 --- a/src/redux/root/mapStages.selectors.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; -import { backgroundsSelector } from '../backgrounds/background.selectors'; -import { modelsSelector } from '../models/models.selectors'; -import { overlaysSelector } from '../overlays/overlays.selectors'; -import { projectSelector } from '../project/project.selectors'; - -export const mapLoadingFirstStageInitializedSelector = createSelector( - projectSelector, - project => project.loading !== 'idle', -); - -export const mapLoadingFirstStageCompletedSelector = createSelector( - projectSelector, - project => project.loading === 'succeeded', -); - -export const mapLoadingSecondStageInitializedSelector = createSelector( - backgroundsSelector, - modelsSelector, - overlaysSelector, - (backgrounds, models, overlays) => - [backgrounds.loading, models.loading, overlays.loading].every(loading => loading !== 'idle'), -); - -export const mapLoadingSecondStageCompletedSelector = createSelector( - backgroundsSelector, - modelsSelector, - overlaysSelector, - (backgrounds, models, overlays) => - [backgrounds.loading, models.loading, overlays.loading].every( - loading => loading === 'succeeded', - ), -); - -export const mapLoadingAllStagesCompletedSelector = createSelector( - mapLoadingFirstStageCompletedSelector, - mapLoadingSecondStageCompletedSelector, - (firstStageCompleted, secondStageCompleted) => - [firstStageCompleted, secondStageCompleted].every(completed => completed === true), -); diff --git a/src/redux/store.ts b/src/redux/store.ts index 0c59b4e1559b94a670f200f78ea4bee197498aa8..d0d3e67326e6a7fa49a0f5764368ea525da40eb6 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -9,7 +9,14 @@ import modelsReducer from '@/redux/models/models.slice'; import overlaysReducer from '@/redux/overlays/overlays.slice'; import projectReducer from '@/redux/project/project.slice'; import searchReducer from '@/redux/search/search.slice'; -import { configureStore } from '@reduxjs/toolkit'; +import { + AnyAction, + ListenerEffectAPI, + ThunkDispatch, + TypedStartListening, + configureStore, +} from '@reduxjs/toolkit'; +import { mapListenerMiddleware } from './map/middleware/map.middleware'; export const reducers = { search: searchReducer, @@ -25,13 +32,19 @@ export const reducers = { models: modelsReducer, }; +export const middlewares = [mapListenerMiddleware.middleware]; + export const store = configureStore({ reducer: reducers, devTools: true, + middleware: getDefaultMiddleware => getDefaultMiddleware().prepend(...middlewares), }); export type StoreType = typeof store; // Infer the `RootState` and `AppDispatch` types from the store itself export type RootState = ReturnType<typeof store.getState>; // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} -export type AppDispatch = typeof store.dispatch; +export type TypedDispatch<T> = ThunkDispatch<T, unknown, AnyAction>; +export type AppDispatch = TypedDispatch<RootState>; +export type AppStartListening = TypedStartListening<RootState, AppDispatch>; +export type AppListenerEffectAPI = ListenerEffectAPI<RootState, AppDispatch>; diff --git a/src/utils/map/getUpdatedMapData.test.ts b/src/utils/map/getUpdatedMapData.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..5afdbc3c00a37776b425577324036ae739553b07 --- /dev/null +++ b/src/utils/map/getUpdatedMapData.test.ts @@ -0,0 +1,94 @@ +import { DEFAULT_ZOOM } from '@/constants/map'; +import { singleModelFixture } from '@/models/fixtures/modelsFixture'; +import { getUpdatedMapData } from './getUpdatedMapData'; + +const HALF = 2; + +describe('getUpdatedMapData - util', () => { + describe('when model does not have default values', () => { + const model = { + ...singleModelFixture, + defaultCenterX: null, + defaultCenterY: null, + defaultZoomLevel: null, + }; + + it('should return correct value', () => { + const result = { + modelId: model.idObject, + size: { + width: model.width, + height: model.height, + tileSize: model.tileSize, + minZoom: model.minZoom, + maxZoom: model.maxZoom, + }, + position: { + x: model.width / HALF, + y: model.height / HALF, + z: DEFAULT_ZOOM, + }, + }; + + expect(getUpdatedMapData({ model })).toMatchObject(result); + }); + }); + + describe('when model has default falsy values', () => { + const model = { + ...singleModelFixture, + defaultCenterX: 0, + defaultCenterY: 0, + defaultZoomLevel: null, + }; + + it('should return correct value', () => { + const result = { + modelId: model.idObject, + size: { + width: model.width, + height: model.height, + tileSize: model.tileSize, + minZoom: model.minZoom, + maxZoom: model.maxZoom, + }, + position: { + x: 0, + y: 0, + z: DEFAULT_ZOOM, + }, + }; + + expect(getUpdatedMapData({ model })).toMatchObject(result); + }); + }); + + describe('when model has default truthy values', () => { + const model = { + ...singleModelFixture, + defaultCenterX: 10, + defaultCenterY: 10, + defaultZoomLevel: 1, + }; + + it('should return correct value', () => { + const result = { + modelId: model.idObject, + size: { + width: model.width, + height: model.height, + tileSize: model.tileSize, + minZoom: model.minZoom, + maxZoom: model.maxZoom, + }, + position: { + x: 10, + y: 10, + z: 1, + }, + }; + + expect(getUpdatedMapData({ model })).toMatchObject(result); + }); + }); +}); diff --git a/src/utils/map/getUpdatedMapData.ts b/src/utils/map/getUpdatedMapData.ts new file mode 100644 index 0000000000000000000000000000000000000000..552e29a64b20d955929f5e99a2233c915aebe9f0 --- /dev/null +++ b/src/utils/map/getUpdatedMapData.ts @@ -0,0 +1,27 @@ +import { DEFAULT_ZOOM } from '@/constants/map'; +import { MapData } from '@/redux/map/map.types'; +import { MapModel } from '@/types/models'; + +interface GetUpdatedMapDataArgs { + model: MapModel; +} + +type GetUpdatedMapDataResult = Pick<MapData, 'modelId' | 'size' | 'position'>; + +const HALF = 2; + +export const getUpdatedMapData = ({ model }: GetUpdatedMapDataArgs): GetUpdatedMapDataResult => ({ + modelId: model.idObject, + size: { + width: model.width, + height: model.height, + tileSize: model.tileSize, + minZoom: model.minZoom, + maxZoom: model.maxZoom, + }, + position: { + x: model.defaultCenterX ?? model.width / HALF, + y: model.defaultCenterY ?? model.height / HALF, + z: model.defaultZoomLevel ?? DEFAULT_ZOOM, + }, +}); diff --git a/src/utils/testing/getReduxWrapperWithStore.tsx b/src/utils/testing/getReduxWrapperWithStore.tsx index 604f81c4c2ddec336dc0040bd1b57fa08e7f8f48..5c799a25d2413cc0d6fffdd57fc40537bd5c29bd 100644 --- a/src/utils/testing/getReduxWrapperWithStore.tsx +++ b/src/utils/testing/getReduxWrapperWithStore.tsx @@ -1,4 +1,4 @@ -import { RootState, StoreType, reducers } from '@/redux/store'; +import { RootState, StoreType, middlewares, reducers } from '@/redux/store'; import { configureStore } from '@reduxjs/toolkit'; import { Provider } from 'react-redux'; @@ -19,6 +19,7 @@ export const getReduxWrapperWithStore: GetReduxWrapperUsingSliceReducer = ( const testStore = configureStore({ reducer: reducers, preloadedState, + middleware: getDefaultMiddleware => getDefaultMiddleware().prepend(...middlewares), }); const Wrapper = ({ children }: WrapperProps): JSX.Element => ( diff --git a/yarn.lock b/yarn.lock index 4a3561b2d8ee49f9d76bbca06a2c8c3b7a945c24..c865fa7436c7be819c80651b30e867e2502d5574 100644 --- a/yarn.lock +++ b/yarn.lock @@ -878,6 +878,18 @@ "resolved" "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.8.4.tgz" "version" "3.8.4" +"@pkgr/utils@^2.3.1": + "integrity" "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==" + "resolved" "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz" + "version" "2.4.2" + dependencies: + "cross-spawn" "^7.0.3" + "fast-glob" "^3.3.0" + "is-glob" "^4.0.3" + "open" "^9.1.0" + "picocolors" "^1.0.0" + "tslib" "^2.6.0" + "@reduxjs/toolkit@^1.9.6": "integrity" "sha512-Gc4ikl90ORF4viIdAkY06JNUnODjKfGxZRwATM30EdHq8hLSVoSrwXne5dd739yenP5bJxAX7tLuOWK5RPGtrw==" "resolved" "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.6.tgz" @@ -1780,6 +1792,11 @@ dependencies: "tweetnacl" "^0.14.3" +"big-integer@^1.6.44": + "integrity" "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==" + "resolved" "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" + "version" "1.6.51" + "binary-extensions@^2.0.0": "integrity" "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" "resolved" "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" @@ -1804,6 +1821,13 @@ "resolved" "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz" "version" "3.7.2" +"bplist-parser@^0.2.0": + "integrity" "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==" + "resolved" "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz" + "version" "0.2.0" + dependencies: + "big-integer" "^1.6.44" + "brace-expansion@^1.1.7": "integrity" "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==" "resolved" "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" @@ -1861,6 +1885,13 @@ dependencies: "semver" "^7.0.0" +"bundle-name@^3.0.0": + "integrity" "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==" + "resolved" "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "run-applescript" "^5.0.0" + "busboy@1.6.0": "integrity" "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==" "resolved" "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz" @@ -2437,6 +2468,11 @@ "resolved" "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz" "version" "10.4.3" +"decode-uri-component@^0.2.2": + "integrity" "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==" + "resolved" "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz" + "version" "0.2.2" + "dedent@^1.0.0": "integrity" "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==" "resolved" "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz" @@ -2481,6 +2517,24 @@ "resolved" "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz" "version" "4.3.1" +"default-browser-id@^3.0.0": + "integrity" "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==" + "resolved" "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "bplist-parser" "^0.2.0" + "untildify" "^4.0.0" + +"default-browser@^4.0.0": + "integrity" "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==" + "resolved" "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz" + "version" "4.0.0" + dependencies: + "bundle-name" "^3.0.0" + "default-browser-id" "^3.0.0" + "execa" "^7.1.1" + "titleize" "^3.0.0" + "defaults@^1.0.3": "integrity" "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==" "resolved" "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz" @@ -2497,6 +2551,11 @@ "gopd" "^1.0.1" "has-property-descriptors" "^1.0.0" +"define-lazy-prop@^3.0.0": + "integrity" "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==" + "resolved" "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz" + "version" "3.0.0" + "define-properties@^1.1.3", "define-properties@^1.1.4", "define-properties@^1.2.0", "define-properties@^1.2.1": "integrity" "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==" "resolved" "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz" @@ -2954,6 +3013,14 @@ "resolve" "^1.22.2" "semver" "^7.5.3" +"eslint-plugin-prettier@^5.0.1": + "integrity" "sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==" + "resolved" "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz" + "version" "5.0.1" + dependencies: + "prettier-linter-helpers" "^1.0.0" + "synckit" "^0.8.5" + "eslint-plugin-promise@^6.0.0", "eslint-plugin-promise@^6.1.1": "integrity" "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==" "resolved" "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz" @@ -3022,7 +3089,7 @@ "resolved" "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" "version" "3.4.3" -"eslint@*", "eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8", "eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8", "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "eslint@^6.0.0 || ^7.0.0 || ^8.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.0.0 || ^8.0.0", "eslint@^7.23.0 || ^8.0.0", "eslint@^7.32.0 || ^8.2.0", "eslint@^7.5.0 || ^8.0.0", "eslint@^8.0.1", "eslint@^8.49.0", "eslint@>=7.0.0", "eslint@>=8": +"eslint@*", "eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8", "eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8", "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "eslint@^6.0.0 || ^7.0.0 || ^8.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.0.0 || ^8.0.0", "eslint@^7.23.0 || ^8.0.0", "eslint@^7.32.0 || ^8.2.0", "eslint@^7.5.0 || ^8.0.0", "eslint@^8.0.1", "eslint@^8.49.0", "eslint@>=7.0.0", "eslint@>=8", "eslint@>=8.0.0": "integrity" "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==" "resolved" "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz" "version" "8.50.0" @@ -3133,6 +3200,21 @@ "signal-exit" "^3.0.3" "strip-final-newline" "^2.0.0" +"execa@^7.1.1": + "integrity" "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==" + "resolved" "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz" + "version" "7.2.0" + dependencies: + "cross-spawn" "^7.0.3" + "get-stream" "^6.0.1" + "human-signals" "^4.3.0" + "is-stream" "^3.0.0" + "merge-stream" "^2.0.0" + "npm-run-path" "^5.1.0" + "onetime" "^6.0.0" + "signal-exit" "^3.0.7" + "strip-final-newline" "^3.0.0" + "execa@4.1.0": "integrity" "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==" "resolved" "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz" @@ -3228,7 +3310,12 @@ "resolved" "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" "version" "3.1.3" -"fast-glob@^3.2.12", "fast-glob@^3.2.5", "fast-glob@^3.2.9", "fast-glob@^3.3.1": +"fast-diff@^1.1.2": + "integrity" "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==" + "resolved" "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz" + "version" "1.3.0" + +"fast-glob@^3.2.12", "fast-glob@^3.2.5", "fast-glob@^3.2.9", "fast-glob@^3.3.0", "fast-glob@^3.3.1": "integrity" "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==" "resolved" "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz" "version" "3.3.1" @@ -3291,6 +3378,11 @@ dependencies: "to-regex-range" "^5.0.1" +"filter-obj@^1.1.0": + "integrity" "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==" + "resolved" "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz" + "version" "1.1.0" + "find-node-modules@^2.1.2": "integrity" "sha512-UC2I2+nx1ZuOBclWVNdcnbDR5dlrOdVb7xNjmT/lHE+LsgztWks3dG7boJ37yTS/venXw84B/mAW9uHVoC5QRg==" "resolved" "https://registry.npmjs.org/find-node-modules/-/find-node-modules-2.1.3.tgz" @@ -4015,6 +4107,16 @@ dependencies: "has-tostringtag" "^1.0.0" +"is-docker@^2.0.0": + "integrity" "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" + "resolved" "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz" + "version" "2.2.1" + +"is-docker@^3.0.0": + "integrity" "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==" + "resolved" "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz" + "version" "3.0.0" + "is-extglob@^2.1.1": "integrity" "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" "resolved" "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" @@ -4056,6 +4158,13 @@ dependencies: "is-extglob" "^2.1.1" +"is-inside-container@^1.0.0": + "integrity" "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==" + "resolved" "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "is-docker" "^3.0.0" + "is-installed-globally@~0.4.0": "integrity" "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==" "resolved" "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz" @@ -4209,6 +4318,13 @@ "resolved" "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz" "version" "1.0.2" +"is-wsl@^2.2.0": + "integrity" "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==" + "resolved" "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz" + "version" "2.2.0" + dependencies: + "is-docker" "^2.0.0" + "isarray@^2.0.5": "integrity" "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" "resolved" "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz" @@ -5243,7 +5359,12 @@ "resolved" "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" "version" "1.4.0" -"next@13.4.19": +"next-router-mock@^0.9.10": + "integrity" "sha512-bK6sRb/xGNFgHVUZuvuApn6KJBAKTPiP36A7a9mO77U4xQO5ukJx9WHlU67Tv8AuySd09pk0+Hu8qMVIAmLO6A==" + "resolved" "https://registry.npmjs.org/next-router-mock/-/next-router-mock-0.9.10.tgz" + "version" "0.9.10" + +"next@>=10.0.0", "next@13.4.19": "integrity" "sha512-HuPSzzAbJ1T4BD8e0bs6B9C1kWQ6gv8ykZoRWs5AQoiIuqbGHHdQO7Ljuvg05Q0Z24E2ABozHe6FxDvI6HfyAw==" "resolved" "https://registry.npmjs.org/next/-/next-13.4.19.tgz" "version" "13.4.19" @@ -5440,6 +5561,16 @@ dependencies: "mimic-fn" "^4.0.0" +"open@^9.1.0": + "integrity" "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==" + "resolved" "https://registry.npmjs.org/open/-/open-9.1.0.tgz" + "version" "9.1.0" + dependencies: + "default-browser" "^4.0.0" + "define-lazy-prop" "^3.0.0" + "is-inside-container" "^1.0.0" + "is-wsl" "^2.2.0" + "optionator@^0.9.3": "integrity" "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==" "resolved" "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz" @@ -5708,12 +5839,19 @@ "resolved" "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz" "version" "2.8.8" +"prettier-linter-helpers@^1.0.0": + "integrity" "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==" + "resolved" "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "fast-diff" "^1.1.2" + "prettier-plugin-tailwindcss@^0.5.6": "integrity" "sha512-2Xgb+GQlkPAUCFi3sV+NOYcSI5XgduvDBL2Zt/hwJudeKXkyvRS65c38SB0yb9UB40+1rL83I6m0RtlOQ8eHdg==" "resolved" "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.6.tgz" "version" "0.5.6" -"prettier@^3.0", "prettier@^3.0.3": +"prettier@^3.0", "prettier@^3.0.3", "prettier@>=3.0.0": "integrity" "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==" "resolved" "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz" "version" "3.0.3" @@ -5817,6 +5955,16 @@ dependencies: "side-channel" "^1.0.4" +"query-string@7.1.3": + "integrity" "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==" + "resolved" "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz" + "version" "7.1.3" + dependencies: + "decode-uri-component" "^0.2.2" + "filter-obj" "^1.1.0" + "split-on-first" "^1.0.0" + "strict-uri-encode" "^2.0.0" + "querystringify@^2.1.1": "integrity" "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" "resolved" "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz" @@ -5897,7 +6045,7 @@ "react-is" "^18.0.0" "use-sync-external-store" "^1.0.0" -"react@^16.3.2 || ^17.0.0 || ^18.0.0", "react@^16.8 || ^17.0 || ^18.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0-0 || ^17.0.0 || ^18.0.0", "react@^16.9.0 || ^17.0.0 || ^18", "react@^18.0.0", "react@^18.2.0", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", "react@18.2.0": +"react@^16.3.2 || ^17.0.0 || ^18.0.0", "react@^16.8 || ^17.0 || ^18.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0-0 || ^17.0.0 || ^18.0.0", "react@^16.9.0 || ^17.0.0 || ^18", "react@^18.0.0", "react@^18.2.0", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", "react@>=17.0.0", "react@18.2.0": "integrity" "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==" "resolved" "https://registry.npmjs.org/react/-/react-18.2.0.tgz" "version" "18.2.0" @@ -6124,6 +6272,13 @@ dependencies: "glob" "^7.1.3" +"run-applescript@^5.0.0": + "integrity" "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==" + "resolved" "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz" + "version" "5.0.0" + dependencies: + "execa" "^5.0.0" + "run-async@^2.4.0": "integrity" "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" "resolved" "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz" @@ -6328,6 +6483,11 @@ "resolved" "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.15.tgz" "version" "3.0.15" +"split-on-first@^1.0.0": + "integrity" "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" + "resolved" "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz" + "version" "1.1.0" + "split2@^3.0.0", "split2@^3.2.2": "integrity" "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==" "resolved" "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz" @@ -6374,6 +6534,11 @@ "resolved" "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" "version" "1.1.0" +"strict-uri-encode@^2.0.0": + "integrity" "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==" + "resolved" "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz" + "version" "2.0.0" + "string_decoder@^1.1.1": "integrity" "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==" "resolved" "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" @@ -6566,6 +6731,14 @@ "resolved" "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz" "version" "3.2.4" +"synckit@^0.8.5": + "integrity" "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==" + "resolved" "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz" + "version" "0.8.5" + dependencies: + "@pkgr/utils" "^2.3.1" + "tslib" "^2.5.0" + "tailwind-merge@^1.14.0": "integrity" "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==" "resolved" "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz" @@ -6654,6 +6827,11 @@ dependencies: "readable-stream" "3" +"titleize@^3.0.0": + "integrity" "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==" + "resolved" "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz" + "version" "3.0.0" + "tmp@^0.0.33": "integrity" "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==" "resolved" "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz" @@ -6751,7 +6929,7 @@ "resolved" "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" "version" "1.14.1" -"tslib@^2.1.0", "tslib@^2.4.0": +"tslib@^2.1.0", "tslib@^2.4.0", "tslib@^2.5.0", "tslib@^2.6.0": "integrity" "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" "resolved" "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" "version" "2.6.2"