diff --git a/Gemfile.lock b/Gemfile.lock index f567c01167..9df3375765 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -87,7 +87,7 @@ GEM base64 (0.3.0) bcrypt (3.1.22) benchmark (0.5.0) - bigdecimal (4.1.1) + bigdecimal (4.1.2) brakeman (8.0.4) racc builder (3.3.0) @@ -133,14 +133,14 @@ GEM docile (1.4.1) drb (2.2.3) e2mmap (0.1.0) - erb (6.0.2) + erb (6.0.4) erubi (1.13.1) factory_bot (6.5.6) activesupport (>= 6.1.0) factory_bot_rails (6.5.1) factory_bot (~> 6.5) railties (>= 6.1.0) - faker (3.6.1) + faker (3.8.0) i18n (>= 1.8.11, < 2) faraday (2.14.1) faraday-net_http (>= 2.0, < 3.5) @@ -200,7 +200,7 @@ GEM prism (>= 1.3.0) rdoc (>= 4.0.0) reline (>= 0.4.2) - json (2.19.3) + json (2.19.4) jsonapi-renderer (0.2.2) jwt (3.1.2) base64 @@ -236,10 +236,10 @@ GEM marcel (1.1.0) method_source (1.1.0) mini_mime (1.1.5) - minitest (6.0.3) + minitest (6.0.5) drb (~> 2.0) prism (~> 1.5) - multi_json (1.19.1) + multi_json (1.20.1) mutations (0.9.2) activesupport mutex_m (0.3.0) @@ -262,7 +262,7 @@ GEM orm_adapter (0.5.0) os (1.1.4) ostruct (0.6.3) - parallel (1.28.0) + parallel (2.0.1) parser (3.3.11.1) ast (~> 2.4.1) racc @@ -300,7 +300,7 @@ GEM rack-cors (3.0.0) logger rack (>= 3.0.14) - rack-session (2.1.1) + rack-session (2.1.2) base64 (>= 0.1.0) rack (>= 3.0.0) rack-test (2.2.0) @@ -338,7 +338,7 @@ GEM tsort (>= 0.2) zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.3.1) + rake (13.4.2) rbtree (0.4.6) rdoc (7.2.0) erb @@ -383,11 +383,11 @@ GEM rspec-support (3.13.7) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.86.0) + rubocop (1.86.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) - parallel (~> 1.10) + parallel (>= 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) diff --git a/app/views/layouts/dashboard.html.erb b/app/views/layouts/dashboard.html.erb index 03eb8934d4..05fe4d426c 100644 --- a/app/views/layouts/dashboard.html.erb +++ b/app/views/layouts/dashboard.html.erb @@ -17,7 +17,7 @@ .initial-loading-text { position: absolute; top: 385px; text-align: center; width: 100%; padding-top: 10%; color: #434343; } - <%= stylesheet_link_tag *@css_assets %> + <%= stylesheet_link_tag(*@css_assets, preload_links_header: false) %> <% manifest_file = diff --git a/bun.lock b/bun.lock index ee15fb8f44..edd6ff89b2 100644 --- a/bun.lock +++ b/bun.lock @@ -5,27 +5,27 @@ "": { "name": "farmbot-web-frontend", "dependencies": { - "@blueprintjs/core": "6.11.3", - "@blueprintjs/select": "6.1.8", + "@blueprintjs/core": "6.12.0", + "@blueprintjs/select": "6.1.9", "@monaco-editor/react": "4.7.0", "@react-spring/three": "10.0.3", "@react-three/drei": "10.7.7", - "@react-three/fiber": "9.5.0", + "@react-three/fiber": "9.6.0", "@rollbar/react": "1.0.0", - "@types/bun": "^1.3.11", + "@types/bun": "1.3.13", "@types/lodash": "4.17.24", "@types/markdown-it": "14.1.2", - "@types/markdown-it-emoji": "^3.0.1", + "@types/markdown-it-emoji": "3.0.1", "@types/promise-timeout": "1.3.3", "@types/react": "19.2.14", "@types/react-color": "3.0.13", "@types/react-dom": "19.2.3", - "@types/react-test-renderer": "^19.1.0", - "@types/redux-immutable-state-invariant": "^2.1.4", - "@types/three": "0.183.1", + "@types/react-test-renderer": "19.1.0", + "@types/redux-immutable-state-invariant": "2.1.4", + "@types/three": "0.184.0", "@types/ws": "8.18.1", "@xterm/xterm": "6.0.0", - "axios": "1.14.0", + "axios": "1.15.2", "bowser": "2.14.1", "browser-speech": "1.1.1", "delaunator": "5.1.0", @@ -33,7 +33,7 @@ "farmbot": "15.9.3", "fengari": "0.1.5", "fengari-web": "0.1.4", - "i18next": "26.0.3", + "i18next": "26.0.6", "lodash": "4.18.1", "markdown-it": "14.1.1", "markdown-it-emoji": "3.0.0", @@ -44,24 +44,24 @@ "promise-timeout": "1.3.0", "punycode": "2.3.1", "querystring-es3": "0.2.1", - "react": "19.2.4", + "react": "19.2.5", "react-color": "2.19.3", - "react-dom": "19.2.4", + "react-dom": "19.2.5", "react-redux": "9.2.0", - "react-router": "7.14.0", + "react-router": "7.14.2", "redux": "5.0.1", "redux-immutable-state-invariant": "2.1.0", "redux-thunk": "3.1.0", "rollbar": "3.1.0", "suncalc": "1.9.0", "takeme": "0.12.0", - "three": "0.183.2", - "typescript": "6.0.2", + "three": "0.184.0", + "typescript": "6.0.3", "url": "0.11.4", }, "devDependencies": { "@eslint/js": "10.0.1", - "@happy-dom/global-registrator": "20.8.9", + "@happy-dom/global-registrator": "20.9.0", "@react-three/eslint-plugin": "0.1.2", "@testing-library/dom": "10.4.1", "@testing-library/jest-dom": "6.9.1", @@ -71,17 +71,17 @@ "@types/jest": "30.0.0", "@types/readable-stream": "4.0.23", "@types/suncalc": "1.9.2", - "@typescript-eslint/eslint-plugin": "8.58.0", - "@typescript-eslint/parser": "8.58.0", - "eslint": "10.2.0", + "@typescript-eslint/eslint-plugin": "8.59.0", + "@typescript-eslint/parser": "8.59.0", + "eslint": "10.2.1", "eslint-plugin-eslint-comments": "3.2.0", "eslint-plugin-import": "2.32.0", - "eslint-plugin-jest": "29.15.1", + "eslint-plugin-jest": "29.15.2", "eslint-plugin-no-null": "1.0.2", "eslint-plugin-promise": "7.2.1", "eslint-plugin-react": "7.37.5", - "eslint-plugin-react-hooks": "7.0.1", - "happy-dom": "20.8.9", + "eslint-plugin-react-hooks": "7.1.1", + "happy-dom": "20.9.0", "jest": "30.3.0", "jest-canvas-mock": "2.5.2", "jest-cli": "30.3.0", @@ -92,14 +92,14 @@ "madge": "8.0.0", "path-browserify": "1.0.1", "playwright": "1.59.1", - "postcss": "^8.5.9", - "postcss-scss": "^4.0.9", + "postcss": "8.5.10", + "postcss-scss": "4.0.9", "raf": "3.4.1", - "react-test-renderer": "19.2.4", + "react-test-renderer": "19.2.5", "sass": "1.99.0", "sass-lint": "1.13.1", - "stylelint": "^17.6.0", - "stylelint-config-standard-scss": "^17.0.0", + "stylelint": "17.8.0", + "stylelint-config-standard-scss": "17.0.0", "ts-jest": "29.4.9", "tslint": "6.1.3", }, @@ -186,11 +186,11 @@ "@blueprintjs/colors": ["@blueprintjs/colors@5.1.16", "", { "dependencies": { "tslib": "~2.6.2" } }, "sha512-P9uX0Aj2TP9+6aUcori1iPl4snxM/Vgq0LZbhUl1l5bHTgNxxwm/0+IoS/SlQg93HBRl8KTAM1evEqtPbwV10A=="], - "@blueprintjs/core": ["@blueprintjs/core@6.11.3", "", { "dependencies": { "@blueprintjs/colors": "^5.1.16", "@blueprintjs/icons": "^6.8.0", "@floating-ui/react": "^0.27.13", "@popperjs/core": "^2.11.8", "classnames": "^2.3.1", "normalize.css": "^8.0.1", "react-popper": "^2.3.0", "react-transition-group": "^4.4.5", "tslib": "~2.6.2", "use-sync-external-store": "^1.2.0" }, "peerDependencies": { "@types/react": "18", "react": "18", "react-dom": "18" }, "optionalPeers": ["@types/react"], "bin": { "upgrade-blueprint-2.0.0-rename": "scripts/upgrade-blueprint-2.0.0-rename.sh", "upgrade-blueprint-3.0.0-rename": "scripts/upgrade-blueprint-3.0.0-rename.sh" } }, "sha512-pHApk7prF8aQeTUjcuVhoT7gA89BbDO2Qwn/XdD1/ewFawjK53YbYGuerLv2vQ6BmIMeFf2Cs6cM34XCtT0LWw=="], + "@blueprintjs/core": ["@blueprintjs/core@6.12.0", "", { "dependencies": { "@blueprintjs/colors": "^5.1.16", "@blueprintjs/icons": "^6.9.0", "@floating-ui/react": "^0.27.13", "@popperjs/core": "^2.11.8", "classnames": "^2.3.1", "normalize.css": "^8.0.1", "react-popper": "^2.3.0", "react-transition-group": "^4.4.5", "tslib": "~2.6.2", "use-sync-external-store": "^1.2.0" }, "peerDependencies": { "@types/react": "18", "react": "18", "react-dom": "18" }, "optionalPeers": ["@types/react"], "bin": { "upgrade-blueprint-2.0.0-rename": "scripts/upgrade-blueprint-2.0.0-rename.sh", "upgrade-blueprint-3.0.0-rename": "scripts/upgrade-blueprint-3.0.0-rename.sh" } }, "sha512-huBwfAU0/n4XG33C1xl4cWd/f+POtU11AL1wjTG/zWqyV4HRd57TE92z+SdioBbvCm6fmwEjNfjKAu1P1ojWxw=="], - "@blueprintjs/icons": ["@blueprintjs/icons@6.8.0", "", { "dependencies": { "change-case": "^4.1.2", "classnames": "^2.3.1", "tslib": "~2.6.2" }, "peerDependencies": { "@types/react": "18", "react": "18", "react-dom": "18" }, "optionalPeers": ["@types/react"] }, "sha512-a281z8VTNesqVunBO2OigrPFxMRWHpYc84fN+xWbdN5hCiqQNv8ha6sqxeXHUsaTdnemiTH07thiZ8u8Uhfj4A=="], + "@blueprintjs/icons": ["@blueprintjs/icons@6.9.0", "", { "dependencies": { "change-case": "^4.1.2", "classnames": "^2.3.1", "tslib": "~2.6.2" }, "peerDependencies": { "@types/react": "18", "react": "18", "react-dom": "18" }, "optionalPeers": ["@types/react"] }, "sha512-pnwnw6dPARk7Q4CZ9ZKeVe9C8PO1OE9cv9DO4mPgNqJBIOrI9aVAWbHsfjqpbOPXx82/O65kNOQweP8foAzPRA=="], - "@blueprintjs/select": ["@blueprintjs/select@6.1.8", "", { "dependencies": { "@blueprintjs/colors": "^5.1.16", "@blueprintjs/core": "^6.11.3", "@blueprintjs/icons": "^6.8.0", "classnames": "^2.3.1", "tslib": "~2.6.2" }, "peerDependencies": { "@types/react": "18", "react": "18", "react-dom": "18" }, "optionalPeers": ["@types/react"] }, "sha512-6cSoUDN5bYery4TBVEsHFeoZcQGUB26c8x+HJ+zrrjL1yyE1iK+0VTcW+kB0PW4J/fFN0vhVkveLKlrJQIcJcA=="], + "@blueprintjs/select": ["@blueprintjs/select@6.1.9", "", { "dependencies": { "@blueprintjs/colors": "^5.1.16", "@blueprintjs/core": "^6.12.0", "@blueprintjs/icons": "^6.9.0", "classnames": "^2.3.1", "tslib": "~2.6.2" }, "peerDependencies": { "@types/react": "18", "react": "18", "react-dom": "18" }, "optionalPeers": ["@types/react"] }, "sha512-PtHQl0MWr606/PGxvN3xjYYvMc+gLyeHy/FdzK0/SXK4s9dBCBXGbdJEo8yVtDo71jyrJXZfjUkJlf+KpqRibQ=="], "@cacheable/memory": ["@cacheable/memory@2.0.8", "", { "dependencies": { "@cacheable/utils": "^2.4.0", "@keyv/bigmap": "^1.3.1", "hookified": "^1.15.1", "keyv": "^5.6.0" } }, "sha512-FvEb29x5wVwu/Kf93IWwsOOEuhHh6dYCJF3vcKLzXc0KXIW181AOzv6ceT4ZpBHDvAfG60eqb+ekmrnLHIy+jw=="], @@ -228,19 +228,19 @@ "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - "@eslint/config-array": ["@eslint/config-array@0.23.4", "", { "dependencies": { "@eslint/object-schema": "^3.0.4", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-lf19F24LSMfF8weXvW5QEtnLqW70u7kgit5e9PSx0MsHAFclGd1T9ynvWEMDT1w5J4Qt54tomGeAhdoAku1Xow=="], + "@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="], - "@eslint/config-helpers": ["@eslint/config-helpers@0.5.4", "", { "dependencies": { "@eslint/core": "^1.2.0" } }, "sha512-jJhqiY3wPMlWWO3370M86CPJ7pt8GmEwSLglMfQhjXal07RCvhmU0as4IuUEW5SJeunfItiEetHmSxCCe9lDBg=="], + "@eslint/config-helpers": ["@eslint/config-helpers@0.5.5", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w=="], - "@eslint/core": ["@eslint/core@1.2.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A=="], + "@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="], "@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="], "@eslint/js": ["@eslint/js@10.0.1", "", { "peerDependencies": { "eslint": "^10.0.0" }, "optionalPeers": ["eslint"] }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="], - "@eslint/object-schema": ["@eslint/object-schema@3.0.4", "", {}, "sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw=="], + "@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="], - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.0", "", { "dependencies": { "@eslint/core": "^1.2.0", "levn": "^0.4.1" } }, "sha512-ejvBr8MQCbVsWNZnCwDXjUKq40MDmHalq7cJ6e9s/qzTUFIIo/afzt1Vui9T97FM/V/pN4YsFVoed5NIa96RDg=="], + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.1", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ=="], "@floating-ui/core": ["@floating-ui/core@1.7.4", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg=="], @@ -252,7 +252,7 @@ "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="], - "@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.8.9", "", { "dependencies": { "@types/node": ">=20.0.0", "happy-dom": "^20.8.9" } }, "sha512-DtZeRRHY9A/bisTJziUBBPrdnPui7+R185G/hzi6/Boymhqh7/wi53AY+IvQHS1+7OPaqfO/1XNpngNwthLz+A=="], + "@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.9.0", "", { "dependencies": { "@types/node": ">=20.0.0", "happy-dom": "^20.9.0" } }, "sha512-lBW6/m5BIFl3pMuWPNN0lIOYw9LMCmPfix53ExS3FBi4E+NELEljQ3xH6aAV9IYiQRfn9YIIgzzMrD0vIcD7tw=="], "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], @@ -394,7 +394,7 @@ "@react-three/eslint-plugin": ["@react-three/eslint-plugin@0.1.2", "", { "dependencies": { "@babel/runtime": "^7.17.8", "eslint": "^8.12.0" } }, "sha512-jenNIhvt+/1fb3NDr3M5vwF06U9euX6kI2SuAFltVKdQP2nzUPY+zdati2Rd67ewAKn0jfljQWT7DWIe6siChg=="], - "@react-three/fiber": ["@react-three/fiber@9.5.0", "", { "dependencies": { "@babel/runtime": "^7.17.8", "@types/webxr": "*", "base64-js": "^1.5.1", "buffer": "^6.0.3", "its-fine": "^2.0.0", "react-use-measure": "^2.1.7", "scheduler": "^0.27.0", "suspend-react": "^0.1.3", "use-sync-external-store": "^1.4.0", "zustand": "^5.0.3" }, "peerDependencies": { "expo": ">=43.0", "expo-asset": ">=8.4", "expo-file-system": ">=11.0", "expo-gl": ">=11.0", "react": ">=19 <19.3", "react-dom": ">=19 <19.3", "react-native": ">=0.78", "three": ">=0.156" }, "optionalPeers": ["expo", "expo-asset", "expo-file-system", "expo-gl", "react-dom", "react-native"] }, "sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA=="], + "@react-three/fiber": ["@react-three/fiber@9.6.0", "", { "dependencies": { "@babel/runtime": "^7.17.8", "@types/webxr": "*", "base64-js": "^1.5.1", "buffer": "^6.0.3", "its-fine": "^2.0.0", "react-use-measure": "^2.1.7", "scheduler": "^0.27.0", "suspend-react": "^0.1.3", "use-sync-external-store": "^1.4.0", "zustand": "^5.0.3" }, "peerDependencies": { "expo": ">=43.0", "expo-asset": ">=8.4", "expo-file-system": ">=11.0", "expo-gl": ">=11.0", "react": ">=19 <19.3", "react-dom": ">=19 <19.3", "react-native": ">=0.78", "three": ">=0.156" }, "optionalPeers": ["expo", "expo-asset", "expo-file-system", "expo-gl", "react-dom", "react-native"] }, "sha512-90abYK2q5/qDM+GACs9zRvc5KhEEpEWqWlHSd64zTPNxg+9wCJvTfyD9x2so7hlQhjRYO1Fa6flR3BC/kpTFkA=="], "@rollbar/react": ["@rollbar/react@1.0.0", "", { "dependencies": { "tiny-invariant": "^1.1.0" }, "peerDependencies": { "prop-types": "^15.7.2", "react": "16.x || 17.x || 18.x || 19.x", "rollbar": "^2.26.4 || ^3.0.0-alpha.3" } }, "sha512-e3S9K9k1BLNuqAFA/AD8XH4kcypClYWoMBuW5LO7fnu5Jy1/qiRFIgS5cFZpAFWQqJaSPQAqrLP6n41sH08X9A=="], @@ -444,7 +444,7 @@ "@types/babel__traverse": ["@types/babel__traverse@7.20.6", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg=="], - "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], + "@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], "@types/css-font-loading-module": ["@types/css-font-loading-module@0.0.7", "", {}, "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q=="], @@ -508,7 +508,7 @@ "@types/suncalc": ["@types/suncalc@1.9.2", "", {}, "sha512-ATAGBHHfA1TlE2tjfidLyTcysjoT2JHHEAmWRULh73SU9UTn++j5fqHEW16X6Y/2Li87jEQXzgu4R/OOdlDqzw=="], - "@types/three": ["@types/three@0.183.1", "", { "dependencies": { "@dimforge/rapier3d-compat": "~0.12.0", "@tweenjs/tween.js": "~23.1.3", "@types/stats.js": "*", "@types/webxr": ">=0.5.17", "@webgpu/types": "*", "fflate": "~0.8.2", "meshoptimizer": "~1.0.1" } }, "sha512-f2Pu5Hrepfgavttdye3PsH5RWyY/AvdZQwIVhrc4uNtvF7nOWJacQKcoVJn0S4f0yYbmAE6AR+ve7xDcuYtMGw=="], + "@types/three": ["@types/three@0.184.0", "", { "dependencies": { "@dimforge/rapier3d-compat": "~0.12.0", "@tweenjs/tween.js": "~23.1.3", "@types/stats.js": "*", "@types/webxr": ">=0.5.17", "fflate": "~0.8.2", "meshoptimizer": "~1.1.1" } }, "sha512-4mY2tZAu0y0B0567w7013BBXSpsP0+Z48NJvmNo4Y/Pf76yCyz6Jw4P3tUVs10WuYNXXZ+wmHyGWpCek3amJxA=="], "@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="], @@ -526,25 +526,25 @@ "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.58.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/type-utils": "8.58.0", "@typescript-eslint/utils": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.58.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.59.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/type-utils": "8.59.0", "@typescript-eslint/utils": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.59.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw=="], - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA=="], + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.59.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/types": "8.59.0", "@typescript-eslint/typescript-estree": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.58.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.58.0", "@typescript-eslint/types": "^8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.59.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.59.0", "@typescript-eslint/types": "^8.59.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw=="], - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0" } }, "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ=="], + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.0", "", { "dependencies": { "@typescript-eslint/types": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0" } }, "sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.58.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.59.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg=="], - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg=="], + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.59.0", "", { "dependencies": { "@typescript-eslint/types": "8.59.0", "@typescript-eslint/typescript-estree": "8.59.0", "@typescript-eslint/utils": "8.59.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg=="], - "@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], + "@typescript-eslint/types": ["@typescript-eslint/types@8.59.0", "", {}, "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.58.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.58.0", "@typescript-eslint/tsconfig-utils": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.59.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.59.0", "@typescript-eslint/tsconfig-utils": "8.59.0", "@typescript-eslint/types": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw=="], - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.58.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA=="], + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.59.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/types": "8.59.0", "@typescript-eslint/typescript-estree": "8.59.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.0", "", { "dependencies": { "@typescript-eslint/types": "8.59.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], @@ -676,7 +676,7 @@ "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], - "axios": ["axios@1.14.0", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } }, "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ=="], + "axios": ["axios@1.15.2", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } }, "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A=="], "babel-jest": ["babel-jest@30.3.0", "", { "dependencies": { "@jest/transform": "30.3.0", "@types/babel__core": "^7.20.5", "babel-plugin-istanbul": "^7.0.1", "babel-preset-jest": "30.3.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.11.0 || ^8.0.0-0" } }, "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ=="], @@ -722,7 +722,7 @@ "builtin-modules": ["builtin-modules@1.1.1", "", {}, "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ=="], - "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + "bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], "cache-base": ["cache-base@1.0.1", "", { "dependencies": { "collection-visit": "^1.0.0", "component-emitter": "^1.2.1", "get-value": "^2.0.6", "has-value": "^1.0.0", "isobject": "^3.0.1", "set-value": "^2.0.0", "to-object-path": "^0.3.0", "union-value": "^1.0.0", "unset-value": "^1.0.0" } }, "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ=="], @@ -984,7 +984,7 @@ "escope": ["escope@3.6.0", "", { "dependencies": { "es6-map": "^0.1.3", "es6-weak-map": "^2.0.1", "esrecurse": "^4.1.0", "estraverse": "^4.1.1" } }, "sha512-75IUQsusDdalQEW/G/2esa87J7raqdJF+Ca0/Xm5C3Q58Nr4yVYjZGp/P1+2xiEVgXRrA39dpRb8LcshajbqDQ=="], - "eslint": ["eslint@10.2.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.4", "@eslint/config-helpers": "^0.5.4", "@eslint/core": "^1.2.0", "@eslint/plugin-kit": "^0.7.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA=="], + "eslint": ["eslint@10.2.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.5.5", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q=="], "eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.9", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g=="], @@ -994,7 +994,7 @@ "eslint-plugin-import": ["eslint-plugin-import@2.32.0", "", { "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", "array.prototype.findlastindex": "^1.2.6", "array.prototype.flat": "^1.3.3", "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", "object.values": "^1.2.1", "semver": "^6.3.1", "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA=="], - "eslint-plugin-jest": ["eslint-plugin-jest@29.15.1", "", { "dependencies": { "@typescript-eslint/utils": "^8.0.0" }, "peerDependencies": { "@typescript-eslint/eslint-plugin": "^8.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "jest": "*", "typescript": ">=4.8.4 <7.0.0" }, "optionalPeers": ["@typescript-eslint/eslint-plugin", "jest", "typescript"] }, "sha512-6BjyErCQauz3zfJvzLw/kAez2lf4LEpbHLvWBfEcG4EI0ZiRSwjoH2uZulMouU8kRkBH+S0rhqn11IhTvxKgKw=="], + "eslint-plugin-jest": ["eslint-plugin-jest@29.15.2", "", { "dependencies": { "@typescript-eslint/utils": "^8.0.0" }, "peerDependencies": { "@typescript-eslint/eslint-plugin": "^8.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "jest": "*", "typescript": ">=4.8.4 <7.0.0" }, "optionalPeers": ["@typescript-eslint/eslint-plugin", "jest", "typescript"] }, "sha512-kEN4r9RZl1xcsb4arGq89LrcVdOUFII/JSCwtTPJyv16mDwmPrcuEQwpxqZHeINvcsd7oK5O/rhdGlxFRaZwvQ=="], "eslint-plugin-no-null": ["eslint-plugin-no-null@1.0.2", "", { "peerDependencies": { "eslint": ">=3.0.0" } }, "sha512-uRDiz88zCO/2rzGfgG15DBjNsgwWtWiSo4Ezy7zzajUgpnFIqd1TjepKeRmJZHEfBGu58o2a8S0D7vglvvhkVA=="], @@ -1002,7 +1002,7 @@ "eslint-plugin-react": ["eslint-plugin-react@7.37.5", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA=="], - "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="], + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.1.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g=="], "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], @@ -1176,7 +1176,7 @@ "handlebars": ["handlebars@4.7.9", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ=="], - "happy-dom": ["happy-dom@20.8.9", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-Tz23LR9T9jOGVZm2x1EPdXqwA37G/owYMxRwU0E4miurAtFsPMQ1d2Jc2okUaSjZqAFz2oEn3FLXC5a0a+siyA=="], + "happy-dom": ["happy-dom@20.9.0", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ=="], "has-ansi": ["has-ansi@2.0.0", "", { "dependencies": { "ansi-regex": "^2.0.0" } }, "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg=="], @@ -1226,7 +1226,7 @@ "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], - "i18next": ["i18next@26.0.3", "", { "dependencies": { "@babel/runtime": "^7.29.2" }, "peerDependencies": { "typescript": "^5 || ^6" }, "optionalPeers": ["typescript"] }, "sha512-1571kXINxHKY7LksWp8wP+zP0YqHSSpl/OW0Y0owFEf2H3s8gCAffWaZivcz14rMkOvn3R/psiQxVsR9t2Nafg=="], + "i18next": ["i18next@26.0.6", "", { "dependencies": { "@babel/runtime": "^7.29.2" }, "peerDependencies": { "typescript": "^5 || ^6" }, "optionalPeers": ["typescript"] }, "sha512-A4U6eCXodIbrhf8EarRurB9/4ebyaurH4+fu4gig9bqxmpSt+fCAFm/GpRQDcN1Xzu/LdFCx4nYHsnM1edIIbg=="], "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], @@ -1560,7 +1560,7 @@ "meshline": ["meshline@3.3.1", "", { "peerDependencies": { "three": ">=0.137" } }, "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ=="], - "meshoptimizer": ["meshoptimizer@1.0.1", "", {}, "sha512-Vix+QlA1YYT3FwmBBZ+49cE5y/b+pRrcXKqGpS5ouh33d3lSp2PoTpCw19E0cKDFWalembrHnIaZetf27a+W2g=="], + "meshoptimizer": ["meshoptimizer@1.1.1", "", {}, "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g=="], "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], @@ -1730,7 +1730,7 @@ "possible-typed-array-names": ["possible-typed-array-names@1.0.0", "", {}, "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q=="], - "postcss": ["postcss@8.5.9", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw=="], + "postcss": ["postcss@8.5.10", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ=="], "postcss-media-query-parser": ["postcss-media-query-parser@0.2.3", "", {}, "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig=="], @@ -1790,23 +1790,23 @@ "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": "cli.js" }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], - "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], + "react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="], "react-color": ["react-color@2.19.3", "", { "dependencies": { "@icons/material": "^0.2.4", "lodash": "^4.17.15", "lodash-es": "^4.17.15", "material-colors": "^1.2.1", "prop-types": "^15.5.10", "reactcss": "^1.2.0", "tinycolor2": "^1.4.1" }, "peerDependencies": { "react": "*" } }, "sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA=="], - "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], + "react-dom": ["react-dom@19.2.5", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.5" } }, "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag=="], "react-fast-compare": ["react-fast-compare@3.2.2", "", {}, "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="], - "react-is": ["react-is@19.2.4", "", {}, "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA=="], + "react-is": ["react-is@19.2.5", "", {}, "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ=="], "react-popper": ["react-popper@2.3.0", "", { "dependencies": { "react-fast-compare": "^3.0.1", "warning": "^4.0.2" }, "peerDependencies": { "@popperjs/core": "^2.0.0", "react": "^16.8.0 || ^17 || ^18", "react-dom": "^16.8.0 || ^17 || ^18" } }, "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q=="], "react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" } }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="], - "react-router": ["react-router@7.14.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-m/xR9N4LQLmAS0ZhkY2nkPA1N7gQ5TUVa5n8TgANuDTARbn1gt+zLPXEm7W0XDTbrQ2AJSJKhoa6yx1D8BcpxQ=="], + "react-router": ["react-router@7.14.2", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-yCqNne6I8IB6rVCH7XUvlBK7/QKyqypBFGv+8dj4QBFJiiRX+FG7/nkdAvGElyvVZ/HQP5N19wzteuTARXi5Gw=="], - "react-test-renderer": ["react-test-renderer@19.2.4", "", { "dependencies": { "react-is": "^19.2.4", "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-Ttl5D7Rnmi6JGMUpri4UjB4BAN0FPs4yRDnu2XSsigCWOLm11o8GwRlVsh27ER+4WFqsGtrBuuv5zumUaRCmKw=="], + "react-test-renderer": ["react-test-renderer@19.2.5", "", { "dependencies": { "react-is": "^19.2.5", "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.5" } }, "sha512-kwViRpdISMTpcpy5B6TSewfJzRjnajihRaj57ZmOWKD+SPN6k9LUM13O0pfOuW8ir6B6OOiAXwCRqOoVxRNykA=="], "react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="], @@ -2020,7 +2020,7 @@ "strip-json-comments": ["strip-json-comments@1.0.4", "", { "bin": "cli.js" }, "sha512-AOPG8EBc5wAikaG1/7uFCNFJwnKOuQwFTpYBdTW6OvWHeZBQBrAA/amefHGrEiOnCPcLFZK6FUPtWVKpQVIRgg=="], - "stylelint": ["stylelint@17.6.0", "", { "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-syntax-patches-for-csstree": "^1.1.1", "@csstools/css-tokenizer": "^4.0.0", "@csstools/media-query-list-parser": "^5.0.0", "@csstools/selector-resolve-nested": "^4.0.0", "@csstools/selector-specificity": "^6.0.0", "colord": "^2.9.3", "cosmiconfig": "^9.0.1", "css-functions-list": "^3.3.3", "css-tree": "^3.2.1", "debug": "^4.4.3", "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^11.1.2", "global-modules": "^2.0.0", "globby": "^16.1.1", "globjoin": "^0.1.4", "html-tags": "^5.1.0", "ignore": "^7.0.5", "import-meta-resolve": "^4.2.0", "is-plain-object": "^5.0.0", "mathml-tag-names": "^4.0.0", "meow": "^14.1.0", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.5.8", "postcss-safe-parser": "^7.0.1", "postcss-selector-parser": "^7.1.1", "postcss-value-parser": "^4.2.0", "string-width": "^8.2.0", "supports-hyperlinks": "^4.4.0", "svg-tags": "^1.0.0", "table": "^6.9.0", "write-file-atomic": "^7.0.1" }, "bin": { "stylelint": "bin/stylelint.mjs" } }, "sha512-tokrsMIVAR9vAQ/q3UVEr7S0dGXCi7zkCezPRnS2kqPUulvUh5Vgfwngrk4EoAoW7wnrThqTdnTFN5Ra7CaxIg=="], + "stylelint": ["stylelint@17.8.0", "", { "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-syntax-patches-for-csstree": "^1.1.2", "@csstools/css-tokenizer": "^4.0.0", "@csstools/media-query-list-parser": "^5.0.0", "@csstools/selector-resolve-nested": "^4.0.0", "@csstools/selector-specificity": "^6.0.0", "colord": "^2.9.3", "cosmiconfig": "^9.0.1", "css-functions-list": "^3.3.3", "css-tree": "^3.2.1", "debug": "^4.4.3", "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^11.1.2", "global-modules": "^2.0.0", "globby": "^16.2.0", "globjoin": "^0.1.4", "html-tags": "^5.1.0", "ignore": "^7.0.5", "import-meta-resolve": "^4.2.0", "is-plain-object": "^5.0.0", "mathml-tag-names": "^4.0.0", "meow": "^14.1.0", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.5.9", "postcss-safe-parser": "^7.0.1", "postcss-selector-parser": "^7.1.1", "postcss-value-parser": "^4.2.0", "string-width": "^8.2.0", "supports-hyperlinks": "^4.4.0", "svg-tags": "^1.0.0", "table": "^6.9.0", "write-file-atomic": "^7.0.1" }, "bin": { "stylelint": "bin/stylelint.mjs" } }, "sha512-oHkld9T60LDSaUQ4CSVc+tlt9eUoDlxhaGWShsUCKyIL14boZfmK5bSphZqx64aiC5tCqX+BsQMTMoSz8D1zIg=="], "stylelint-config-recommended": ["stylelint-config-recommended@18.0.0", "", { "peerDependencies": { "stylelint": "^17.0.0" } }, "sha512-mxgT2XY6YZ3HWWe3Di8umG6aBmWmHTblTgu/f10rqFXnyWxjKWwNdjSWkgkwCtxIKnqjSJzvFmPT5yabVIRxZg=="], @@ -2062,7 +2062,7 @@ "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], - "three": ["three@0.183.2", "", {}, "sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ=="], + "three": ["three@0.184.0", "", {}, "sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg=="], "three-mesh-bvh": ["three-mesh-bvh@0.8.3", "", { "peerDependencies": { "three": ">= 0.159.0" } }, "sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg=="], @@ -2134,7 +2134,7 @@ "typedarray": ["typedarray@0.0.6", "", {}, "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="], - "typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="], + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], @@ -2380,6 +2380,8 @@ "@react-three/eslint-plugin/eslint": ["eslint@8.57.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.0", "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": "bin/eslint.js" }, "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ=="], + "@react-three/fiber/@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], + "@testing-library/jest-dom/dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], "@tybys/wasm-util/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -2478,7 +2480,7 @@ "eslint-plugin-import/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "eslint-plugin-jest/@typescript-eslint/utils": ["@typescript-eslint/utils@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.57.1", "@typescript-eslint/types": "8.57.1", "@typescript-eslint/typescript-estree": "8.57.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ=="], + "eslint-plugin-jest/@typescript-eslint/utils": ["@typescript-eslint/utils@8.58.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA=="], "eslint-plugin-promise/@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="], @@ -2954,11 +2956,11 @@ "eslint-plugin-import/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], - "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.57.1", "", { "dependencies": { "@typescript-eslint/types": "8.57.1", "@typescript-eslint/visitor-keys": "8.57.1" } }, "sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg=="], + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0" } }, "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ=="], - "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.57.1", "", {}, "sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ=="], + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], - "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.57.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.57.1", "@typescript-eslint/tsconfig-utils": "8.57.1", "@typescript-eslint/types": "8.57.1", "@typescript-eslint/visitor-keys": "8.57.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g=="], + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.58.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.58.0", "@typescript-eslint/tsconfig-utils": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA=="], "eslint-plugin-promise/@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], @@ -3202,17 +3204,15 @@ "eslint-plugin-import/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.1", "", { "dependencies": { "@typescript-eslint/types": "8.57.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A=="], - - "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.57.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.57.1", "@typescript-eslint/types": "^8.57.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg=="], + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="], - "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.57.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg=="], + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.58.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.58.0", "@typescript-eslint/types": "^8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg=="], - "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.1", "", { "dependencies": { "@typescript-eslint/types": "8.57.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A=="], + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.58.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A=="], - "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": "bin/semver.js" }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="], - "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="], + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], "eslint-plugin-react/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], diff --git a/db/migrate/20260417190743_add_new_3d_view_options_to_web_app_config.rb b/db/migrate/20260417190743_add_new_3d_view_options_to_web_app_config.rb new file mode 100644 index 0000000000..4ccfc37cd1 --- /dev/null +++ b/db/migrate/20260417190743_add_new_3d_view_options_to_web_app_config.rb @@ -0,0 +1,11 @@ +class AddNew3dViewOptionsToWebAppConfig < ActiveRecord::Migration[8.1] + def up + add_column :web_app_configs, :top_down_view, :boolean, default: false + add_column :web_app_configs, :viewpoint_heading, :integer, default: 0 + end + + def down + remove_column :web_app_configs, :top_down_view + remove_column :web_app_configs, :viewpoint_heading + end +end diff --git a/db/migrate/20260422013033_change_viewpoint_heading_default.rb b/db/migrate/20260422013033_change_viewpoint_heading_default.rb new file mode 100644 index 0000000000..de72f4efc1 --- /dev/null +++ b/db/migrate/20260422013033_change_viewpoint_heading_default.rb @@ -0,0 +1,5 @@ +class ChangeViewpointHeadingDefault < ActiveRecord::Migration[8.1] + def change + change_column_default(:web_app_configs, :viewpoint_heading, from: 0, to: 30) + end +end diff --git a/db/structure.sql b/db/structure.sql index 7e7751df36..d9cef71e9b 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -2037,7 +2037,9 @@ CREATE TABLE public.web_app_configs ( show_missed_step_plot boolean DEFAULT false, enable_3d_electronics_box_top boolean DEFAULT true, three_d_garden boolean DEFAULT false, - dark_mode boolean DEFAULT true + dark_mode boolean DEFAULT true, + top_down_view boolean DEFAULT false, + viewpoint_heading integer DEFAULT 30 ); @@ -3763,6 +3765,8 @@ ALTER TABLE ONLY public.users SET search_path TO "$user", public; INSERT INTO "schema_migrations" (version) VALUES +('20260422013033'), +('20260417190743'), ('20260305192457'), ('20250930204600'), ('20250925195004'), diff --git a/frontend/__test_support__/bun_test_setup.ts b/frontend/__test_support__/bun_test_setup.ts index 7bbc62a2e2..b8d34187ea 100644 --- a/frontend/__test_support__/bun_test_setup.ts +++ b/frontend/__test_support__/bun_test_setup.ts @@ -28,7 +28,7 @@ const ensureSyntaxError = () => { writable: true, }); }; - assign(globalThis as unknown as Record); + assign(globalThis); assign(globalAny.window as unknown as Record | undefined); const windowCtor = globalAny.window?.constructor as | { prototype?: Record } @@ -74,13 +74,11 @@ const withAxiosDefaultExport = (factory: () => unknown) => () => { if (globalAny.jest?.mock) { const originalMock = globalAny.jest.mock.bind(globalAny.jest); - globalAny.jest.mock = ((specifier: string, factory?: unknown) => { - const moduleFactory = - typeof factory === "function" ? factory as () => unknown : undefined; + globalAny.jest.mock = ((specifier: string, factory?: () => unknown) => { return specifier === "axios" && typeof factory === "function" ? originalMock(specifier, - withAxiosDefaultExport(moduleFactory as () => unknown)) - : originalMock(specifier, factory as never); + withAxiosDefaultExport(factory)) + : originalMock(specifier, factory); }) as typeof globalAny.jest.mock; } @@ -229,6 +227,10 @@ const defaultThreeFiberState = () => ({ scene: { traverse: jest.fn() }, size: { width: 800, height: 600 }, pointer: { x: 0, y: 0 }, + raycaster: { + setFromCamera: jest.fn(), + intersectObjects: jest.fn(() => []), + }, }); type MockLike = { @@ -274,7 +276,7 @@ beforeEach(() => { resetMutableFixture(globalAny.globalConfig, globalConfigBaseline); } else { globalAny.globalConfig = - cloneForReset(globalConfigBaseline) as Record; + cloneForReset(globalConfigBaseline); } globalThis.localStorage?.clear(); globalThis.sessionStorage?.clear(); diff --git a/frontend/__test_support__/fake_designer_state.ts b/frontend/__test_support__/fake_designer_state.ts index 3edbefd273..53f5c3f3ed 100644 --- a/frontend/__test_support__/fake_designer_state.ts +++ b/frontend/__test_support__/fake_designer_state.ts @@ -51,7 +51,8 @@ export const fakeDesignerState = (): DesignerState => ({ cropRadius: undefined, distanceIndicator: "", panelOpen: true, - threeDTopDownView: false, + threeDTopDownView: undefined, + threeDCameraSelection: false, threeDExaggeratedZ: false, threeDTime: undefined, }); diff --git a/frontend/__test_support__/setup_tests.ts b/frontend/__test_support__/setup_tests.ts index 15dd9b291b..43f87b4568 100644 --- a/frontend/__test_support__/setup_tests.ts +++ b/frontend/__test_support__/setup_tests.ts @@ -1,2 +1,17 @@ import "@testing-library/jest-dom"; import "./customMatchers"; + +expect.extend({ + toContainHTML(received: Element | { innerHTML?: string }, expected: string) { + const actual = received?.innerHTML ?? ""; + const pass = actual.includes(expected); + + return { + pass, + message: () => + `expected html to${pass ? " not" : ""} contain ` + + `${this.utils.printExpected(expected)}\n` + + `received: ${this.utils.printReceived(actual)}`, + }; + }, +}); diff --git a/frontend/__test_support__/three_d_mocks.tsx b/frontend/__test_support__/three_d_mocks.tsx index 38079a3e97..6ec691e255 100644 --- a/frontend/__test_support__/three_d_mocks.tsx +++ b/frontend/__test_support__/three_d_mocks.tsx @@ -119,7 +119,7 @@ jest.mock("../three_d_garden/components", () => ({ instanceColor: { needsUpdate: false }, }) as unknown as THREE.InstancedMesh); return } + ref={ref} {...rest} />; }, ), @@ -203,6 +203,10 @@ jest.mock("@react-three/fiber", () => ({ scene: { traverse: jest.fn() }, size: { width: 800, height: 600 }, pointer: { x: 0, y: 0 }, + raycaster: { + setFromCamera: jest.fn(), + intersectObjects: jest.fn(() => []), + }, })), useThree: jest.fn(() => ({ gl: { @@ -214,6 +218,10 @@ jest.mock("@react-three/fiber", () => ({ scene: { traverse: jest.fn() }, pointer: { x: 0, y: 0 }, camera: new THREE.PerspectiveCamera(), + raycaster: { + setFromCamera: jest.fn(), + intersectObjects: jest.fn(() => []), + }, size: { width: 800, height: 600 }, })), extend: jest.fn(), diff --git a/frontend/__tests__/loading_plant_test.tsx b/frontend/__tests__/loading_plant_test.tsx index 612d277a89..39b1effce2 100644 --- a/frontend/__tests__/loading_plant_test.tsx +++ b/frontend/__tests__/loading_plant_test.tsx @@ -26,7 +26,7 @@ describe("", () => { }); it("clears initial loading text", () => { - const el = { outerHTML: "hidden" } as Pick; + const el = { outerHTML: "hidden" }; const collection = [el as unknown as Element] as unknown as HTMLCollectionOf; jest.spyOn(document, "getElementsByClassName") diff --git a/frontend/__tests__/revert_to_english_test.ts b/frontend/__tests__/revert_to_english_test.ts index 00d67b40fe..1099e08e32 100644 --- a/frontend/__tests__/revert_to_english_test.ts +++ b/frontend/__tests__/revert_to_english_test.ts @@ -4,7 +4,7 @@ import { revertToEnglish } from "../revert_to_english"; describe("revertToEnglish", () => { it("runs without throwing", async () => { jest.spyOn(I18n, "detectLanguage") - .mockResolvedValue({ lng: "en" } as never); + .mockResolvedValue({ lng: "en" }); await expect(Promise.resolve(revertToEnglish() as unknown)) .resolves.toBeUndefined(); diff --git a/frontend/api/__tests__/crud_data_tracking_test.ts b/frontend/api/__tests__/crud_data_tracking_test.ts index 821d9adbcf..eca611c877 100644 --- a/frontend/api/__tests__/crud_data_tracking_test.ts +++ b/frontend/api/__tests__/crud_data_tracking_test.ts @@ -56,7 +56,7 @@ describe("AJAX data tracking", () => { if (!destroy) { return; } const thunk = destroy(uuid); if (typeof thunk !== "function") { return; } - await thunk(dispatch as unknown as Function, () => + await thunk(dispatch, () => ({ resources: { index: resourceIndex() } })); expect(maybeStartTrackingModule.maybeStartTracking).toHaveBeenCalled(); }); @@ -69,7 +69,7 @@ describe("AJAX data tracking", () => { }); const saveAllAction = loadCrud().saveAll?.(r); if (typeof saveAllAction !== "function") { return; } - await saveAllAction(dispatch as unknown as Function); + await saveAllAction(dispatch); expect(maybeStartTrackingModule.maybeStartTracking).toHaveBeenCalled(); }); @@ -94,7 +94,7 @@ describe("AJAX data tracking", () => { email: "test@test.com" }); if (typeof initSaveGetIdAction !== "function") { return; } - const result = initSaveGetIdAction(statefulDispatch as unknown as Function); + const result = initSaveGetIdAction(statefulDispatch); if (result && typeof result === "object" && result && "catch" in result) { await (result as Promise).catch(() => { }); } @@ -107,7 +107,7 @@ describe("AJAX data tracking", () => { email: "test@test.com" }); if (typeof action !== "function") { return; } - await action(dispatch as unknown as Function); + await action(dispatch); expect(maybeStartTrackingModule.maybeStartTracking).toHaveBeenCalled(); }); }); diff --git a/frontend/api/__tests__/crud_destroy_test.ts b/frontend/api/__tests__/crud_destroy_test.ts index 0bc91f20c6..6245f3a0e8 100644 --- a/frontend/api/__tests__/crud_destroy_test.ts +++ b/frontend/api/__tests__/crud_destroy_test.ts @@ -69,7 +69,7 @@ describe("destroy", () => { .mockImplementation(() => mockReadonlyState); mockDelete = Promise.resolve({}); deleteSpy = jest.spyOn(axios, "delete") - .mockImplementation(() => mockDelete as never); + .mockImplementation(() => mockDelete); }); API.setBaseUrl("http://localhost:3000"); @@ -183,11 +183,11 @@ describe("destroyAll", () => { .mockImplementation(() => mockReadonlyState); mockDelete = Promise.resolve({}); deleteSpy = jest.spyOn(axios, "delete") - .mockImplementation(() => mockDelete as never); + .mockImplementation(() => mockDelete); }); it("confirmed", async () => { - deleteSpy.mockResolvedValueOnce(undefined as never); + deleteSpy.mockResolvedValueOnce(undefined); const result = fakeDestroyAll("FarmwareEnv", true); if (!result) { return; } await expect(result).resolves.toEqual(undefined); @@ -230,7 +230,7 @@ describe("destroyAll", () => { }); it("rejected", async () => { - deleteSpy.mockRejectedValueOnce("error" as never); + deleteSpy.mockRejectedValueOnce("error"); const result = fakeDestroyAll("FarmwareEnv", true); if (!result) { return; } await expect(result).rejects.toEqual("error"); diff --git a/frontend/config/__tests__/actions_test.ts b/frontend/config/__tests__/actions_test.ts index 80f8dd6cc5..59a051cb17 100644 --- a/frontend/config/__tests__/actions_test.ts +++ b/frontend/config/__tests__/actions_test.ts @@ -29,9 +29,9 @@ describe("ready()", () => { didLoginSpy = jest.spyOn(authActions, "didLogin") .mockImplementation(() => { }); maybeRefreshTokenSpy = jest.spyOn(refreshToken, "maybeRefreshToken") - .mockImplementation(() => Promise.resolve(undefined) as never); + .mockImplementation(() => Promise.resolve(undefined)); timeoutSpy = jest.spyOn(promiseTimeoutModule, "timeout") - .mockImplementation(() => mockTimeout as never); + .mockImplementation(() => mockTimeout); fetchStoredTokenSpy = jest.spyOn(Session, "fetchStoredToken") .mockReturnValue(undefined); clearSpy = jest.spyOn(Session, "clear") diff --git a/frontend/connectivity/__tests__/connect_device/index_test.ts b/frontend/connectivity/__tests__/connect_device/index_test.ts index a5ea40cf76..74a50301cc 100644 --- a/frontend/connectivity/__tests__/connect_device/index_test.ts +++ b/frontend/connectivity/__tests__/connect_device/index_test.ts @@ -339,7 +339,7 @@ describe("onPublicBroadcast", () => { log.message = "bot xyz is offline"; const taggedLog = fn(log); const getStateSpy = - jest.spyOn(store, "getState").mockReturnValue(fakeState() as never); + jest.spyOn(store, "getState").mockReturnValue(fakeState()); globalQueue.maybeWork(); getStateSpy.mockRestore(); expect(taggedLog?.kind).toEqual("Log"); diff --git a/frontend/constants.ts b/frontend/constants.ts index 3796bee82b..c4b9f8c3e8 100644 --- a/frontend/constants.ts +++ b/frontend/constants.ts @@ -1015,6 +1015,12 @@ export namespace Content { trim(`Select a map origin by clicking on one of the four quadrants to adjust the garden map to your viewing angle.`); + export const TOP_DOWN_VIEW = + trim(`Upon open, display the 3D garden map from a top-down perspective.`); + + export const CAMERA_STARTING_LOCATION = + trim(`Location of the camera when the 3D garden map is opened.`); + export const CROP_MAP_IMAGES = trim(`Crop images displayed in the garden map to remove black borders from image rotation. Crop amount determined by CAMERA ROTATION value.`); @@ -1773,6 +1779,10 @@ export namespace SetupWizardContent { map to your real life FarmBot. The relevant controls are available below the video for your convenience.`); + export const SET_CAMERA_LOCATION = + trim(`Press the "SET" button to show camera location options. + Select the correct camera angle by clicking on the camera location.`); + export const PRESS_RIGHT_JOG_BUTTON = trim(`Standing from where you will normally view the FarmBot, **press the right arrow button**.`); @@ -2234,6 +2244,8 @@ export enum DeviceSetting { mapSize = `Map size`, rotateMap = `Rotate map`, mapOrigin = `Map origin`, + topDownView = `Top down view`, + setCameraStartingLocation = `Set camera starting location`, cropMapImages = `Crop map images`, clipPhotosOutOfBounds = `Clip photos out of bounds`, cameraView = `Camera view`, @@ -2562,6 +2574,7 @@ export enum Actions { // 3D SET_DISTANCE_INDICATOR = "SET_DISTANCE_INDICATOR", TOGGLE_3D_TOP_DOWN_VIEW = "TOGGLE_3D_TOP_DOWN_VIEW", + TOGGLE_3D_CAMERA_SELECTION = "TOGGLE_3D_CAMERA_SELECTION", TOGGLE_3D_EXAGGERATED_Z = "TOGGLE_3D_EXAGGERATED_Z", SET_3D_TIME = "RESET_3D_TIME", diff --git a/frontend/controls/__tests__/pin_form_fields_test.tsx b/frontend/controls/__tests__/pin_form_fields_test.tsx index 63496c1433..5cb1193cd6 100644 --- a/frontend/controls/__tests__/pin_form_fields_test.tsx +++ b/frontend/controls/__tests__/pin_form_fields_test.tsx @@ -1,4 +1,3 @@ -import React from "react"; import { fakeSensor } from "../../__test_support__/fake_state/resources"; import { Actions } from "../../constants"; import * as crud from "../../api/crud"; @@ -34,7 +33,7 @@ describe("", () => { const input = NameInputBox(p); input.props.onChange({ currentTarget: { value: "GPIO 3" }, - } as React.ChangeEvent); + }); expect(p.dispatch).toHaveBeenCalledWith( expectedPayload({ label: "GPIO 3" })); }); diff --git a/frontend/controls/move/__tests__/take_photo_button_test.tsx b/frontend/controls/move/__tests__/take_photo_button_test.tsx index 6bf10e3fb5..4309c58807 100644 --- a/frontend/controls/move/__tests__/take_photo_button_test.tsx +++ b/frontend/controls/move/__tests__/take_photo_button_test.tsx @@ -18,7 +18,7 @@ describe("", () => { mockPhotoOutcome = Promise.resolve(); takePhotoSpy = jest.spyOn(deviceActions, "takePhoto") - .mockImplementation(() => mockPhotoOutcome as never); + .mockImplementation(() => mockPhotoOutcome); }); afterEach(() => { diff --git a/frontend/demo/lua_runner/index.ts b/frontend/demo/lua_runner/index.ts index 394742ed0d..a8a51036ae 100644 --- a/frontend/demo/lua_runner/index.ts +++ b/frontend/demo/lua_runner/index.ts @@ -26,14 +26,14 @@ export const collectDemoSequenceActions = ( } const sequence = findSequenceById(resources, sequenceId); const varData = resources.sequenceMetas[sequence.uuid]; - const sequenceVariables = Object.values(varData || {}) + const sequenceVariables: ParameterApplication[] = Object.values(varData || {}) .map(v => v?.celeryNode) .filter(v => v?.kind == "variable_declaration") .filter(v => !bodyVariables?.map(v => v.args.label).includes(v.args.label)) .map(v => ({ kind: "parameter_application", args: v.args, - } as ParameterApplication)); + })); const variables = [...sequenceVariables, ...(bodyVariables || [])]; const actions: Action[] = []; const firstVarArgs = variables[0]?.args; diff --git a/frontend/demo/lua_runner/run.ts b/frontend/demo/lua_runner/run.ts index de4687799d..d2d43eba1e 100644 --- a/frontend/demo/lua_runner/run.ts +++ b/frontend/demo/lua_runner/run.ts @@ -318,7 +318,7 @@ export const runLua = lua.lua_pushjsfunction(L, () => { const n = lua.lua_gettop(L); - const args = []; + const args: string[] = []; for (let i = 1; i <= n; i++) { args.push(luaToJs(L, i) as string); } @@ -377,7 +377,7 @@ export const runLua = lua.lua_setfield(L, envIndex, to_luastring("set_job")); lua.lua_pushjsfunction(L, () => { - const args = []; + const args: number[] = []; const n = lua.lua_gettop(L); if (n == 1) { const params = luaToJs(L, 1) as XyzNumber; @@ -394,7 +394,7 @@ export const runLua = lua.lua_pushjsfunction(L, () => { const n = lua.lua_gettop(L); - const args = []; + const args: number[] = []; for (let i = 1; i <= n; i++) { args.push(luaToJs(L, i) as number); } diff --git a/frontend/devices/__tests__/actions_test.ts b/frontend/devices/__tests__/actions_test.ts index 2c1586ff62..b5822d578c 100644 --- a/frontend/devices/__tests__/actions_test.ts +++ b/frontend/devices/__tests__/actions_test.ts @@ -75,7 +75,7 @@ beforeEach(() => { getDeviceSpy = jest.spyOn(deviceModule, "getDevice") .mockImplementation(() => mockDevice.current as Farmbot); axiosGetSpy = jest.spyOn(axios, "get") - .mockImplementation(() => mockGet as never); + .mockImplementation(() => mockGet); editSpy = jest.spyOn(crud, "edit").mockImplementation(jest.fn()); saveSpy = jest.spyOn(crud, "save").mockImplementation(jest.fn()); jest.spyOn(demoLuaRunner, "runDemoSequence").mockImplementation(jest.fn()); @@ -351,7 +351,7 @@ describe("takePhoto()", () => { getDeviceSpy.mockImplementation(() => ({ ...mockDeviceDefault, takePhoto, - }) as unknown as Farmbot); + })); await deviceActions().takePhoto(); expect(takePhoto).toHaveBeenCalled(); expect(success).toHaveBeenCalledWith(Content.PROCESSING_PHOTO, @@ -364,7 +364,7 @@ describe("takePhoto()", () => { getDeviceSpy.mockImplementation(() => ({ ...mockDeviceDefault, takePhoto, - }) as unknown as Farmbot); + })); localStorage.setItem("myBotIs", "online"); await deviceActions().takePhoto(); expect(takePhoto).not.toHaveBeenCalled(); @@ -377,7 +377,7 @@ describe("takePhoto()", () => { getDeviceSpy.mockImplementation(() => ({ ...mockDeviceDefault, takePhoto, - }) as unknown as Farmbot); + })); await deviceActions().takePhoto(); await expect(takePhoto).toHaveBeenCalled(); expect(success).not.toHaveBeenCalled(); diff --git a/frontend/devices/actions.ts b/frontend/devices/actions.ts index 85c1cb1f47..19e3398fce 100644 --- a/frontend/devices/actions.ts +++ b/frontend/devices/actions.ts @@ -27,7 +27,6 @@ import * as crud from "../api/crud"; import { CONFIG_DEFAULTS } from "farmbot/dist/config"; import { Log } from "farmbot/dist/resources/api_resources"; import { FbosConfig } from "farmbot/dist/resources/configs/fbos"; -import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware"; import { getFirmwareConfig, getFbosConfig } from "../resources/getters"; import { isObject, isString, get, noop } from "lodash"; import { t } from "../i18next_wrapper"; @@ -524,7 +523,7 @@ export function updateMCU(key: ConfigKey, val: string) { function proceed() { if (firmwareConfig) { - dispatch(crud.edit(firmwareConfig, { [key]: val } as Partial)); + dispatch(crud.edit(firmwareConfig, { [key]: val })); dispatch(crud.save(firmwareConfig.uuid)); } } diff --git a/frontend/farm_designer/__tests__/index_test.tsx b/frontend/farm_designer/__tests__/index_test.tsx index bcc2a3463d..bc15cfb2f7 100644 --- a/frontend/farm_designer/__tests__/index_test.tsx +++ b/frontend/farm_designer/__tests__/index_test.tsx @@ -51,7 +51,7 @@ describe("", () => { .mockImplementation(((props: GardenMapLegendProps) => { lastLegendProps = props; return
; - }) as never); + })); gardenMapSpy = jest.spyOn(gardenMap, "GardenMap") .mockImplementation(((props: GardenMapProps) => { lastGardenMapProps = props; diff --git a/frontend/farm_designer/__tests__/location_info_test.tsx b/frontend/farm_designer/__tests__/location_info_test.tsx index cf476fa1ca..fcff27e34a 100644 --- a/frontend/farm_designer/__tests__/location_info_test.tsx +++ b/frontend/farm_designer/__tests__/location_info_test.tsx @@ -232,7 +232,7 @@ describe("", () => { if (buttons.length === 0) { return; } - fireEvent.click(buttons[0] as HTMLButtonElement); + fireEvent.click(buttons[0]); expect(container.querySelectorAll( "button.image-flipper-left, button.image-flipper-right").length) .toBe(1); diff --git a/frontend/farm_designer/__tests__/reducer_test.ts b/frontend/farm_designer/__tests__/reducer_test.ts index 9d449acf06..43dbcc1579 100644 --- a/frontend/farm_designer/__tests__/reducer_test.ts +++ b/frontend/farm_designer/__tests__/reducer_test.ts @@ -135,6 +135,15 @@ describe("designer reducer", () => { expect(newState.threeDTopDownView).toEqual(true); }); + it("sets camera", () => { + const action: ReduxAction = { + type: Actions.TOGGLE_3D_CAMERA_SELECTION, + payload: true, + }; + const newState = designer(oldState(), action); + expect(newState.threeDCameraSelection).toEqual(true); + }); + it("sets exaggerated z", () => { const action: ReduxAction = { type: Actions.TOGGLE_3D_EXAGGERATED_Z, diff --git a/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx b/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx index d8d0d0fe51..f4c7b39510 100644 --- a/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx +++ b/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx @@ -25,7 +25,7 @@ beforeEach(() => { getPositionSpy = jest.spyOn(suncalc, "getPosition").mockReturnValue({ altitude: 0.5, azimuth: 1.0, - } as never); + }); }); afterEach(() => { @@ -59,7 +59,7 @@ describe("", () => { designer: fakeDesignerState(), plants: [fakePlant()], dispatch: jest.fn(), - getWebAppConfigValue: jest.fn(), + getWebAppConfigValue: () => 0, curves: [], mapPoints: [], weeds: [], @@ -133,11 +133,14 @@ describe("", () => { expectedConfig.imgScale = 0.6; expectedConfig.imgCenterX = 0; expectedConfig.imgCenterY = 0; + expectedConfig.mirrorX = true; + expectedConfig.mirrorY = true; + expectedConfig.viewpointHeading = 0; const call = lastThreeDGardenProps(); expect(call).toEqual(expect.objectContaining({ config: expectedConfig, - configPosition: { x: 1, y: 2, z: 3 }, + configPosition: { x: 2999, y: 1498, z: 3 }, threeDPlants: [{ id: expect.any(Number), icon: expect.any(String), @@ -146,8 +149,8 @@ describe("", () => { seed: 0, size: 50, spread: 30, - x: 101, - y: 201, + x: 100, + y: 200, }], addPlantProps: expect.any(Object), ...EMPTY_PROPS, @@ -161,7 +164,7 @@ describe("", () => { render(); const call = lastThreeDGardenProps(); expect(call).toEqual(expect.objectContaining({ - configPosition: { x: 0, y: 0, z: 0 }, + configPosition: { x: 3000, y: 1500, z: 0 }, threeDPlants: [], addPlantProps: expect.any(Object), ...EMPTY_PROPS, @@ -177,7 +180,7 @@ describe("", () => { const call = lastThreeDGardenProps(); expect(call).toEqual(expect.objectContaining({ config: expect.objectContaining({ negativeZ: true }), - configPosition: { x: 0, y: 0, z: -100 }, + configPosition: { x: 3000, y: 1500, z: -100 }, threeDPlants: [], addPlantProps: expect.any(Object), ...EMPTY_PROPS, @@ -336,8 +339,8 @@ describe("convertPlants()", () => { seed: 0, size: 50, spread: 20, - x: 110, - y: 201, + x: 100, + y: 200, }, { icon: CROPS["generic-plant"].icon, @@ -347,8 +350,8 @@ describe("convertPlants()", () => { seed: 0, size: 50, spread: 0, - x: 1010, - y: 2001, + x: 1000, + y: 2000, }, ]); }); diff --git a/frontend/farm_designer/index.tsx b/frontend/farm_designer/index.tsx index ea8cad8f6a..cea3dcfaf0 100755 --- a/frontend/farm_designer/index.tsx +++ b/frontend/farm_designer/index.tsx @@ -188,6 +188,7 @@ export class RawFarmDesigner showZones={show_zones} showSensorReadings={show_sensor_readings} showMoistureInterpolationMap={show_moisture_interpolation_map} + designer={this.props.designer} dispatch={this.props.dispatch} timeSettings={this.props.timeSettings} getConfigValue={this.props.getConfigValue} @@ -316,6 +317,7 @@ export class RawFarmDesigner dispatch={this.props.dispatch} device={this.props.device} designer={this.props.designer} + getConfigValue={this.props.getConfigValue} threeDGarden={threeDGarden} />
; } diff --git a/frontend/farm_designer/interfaces.ts b/frontend/farm_designer/interfaces.ts index 54ff2e0c34..e5b0a9dba4 100644 --- a/frontend/farm_designer/interfaces.ts +++ b/frontend/farm_designer/interfaces.ts @@ -179,7 +179,8 @@ export interface DesignerState { cropRadius: number | undefined; distanceIndicator: string; panelOpen: boolean; - threeDTopDownView: boolean; + threeDTopDownView: boolean | undefined; + threeDCameraSelection: boolean; threeDExaggeratedZ: boolean; threeDTime: string | undefined; } diff --git a/frontend/farm_designer/map/__tests__/garden_map_test.tsx b/frontend/farm_designer/map/__tests__/garden_map_test.tsx index a9a4cab8ca..f7fb13efe7 100644 --- a/frontend/farm_designer/map/__tests__/garden_map_test.tsx +++ b/frontend/farm_designer/map/__tests__/garden_map_test.tsx @@ -68,9 +68,9 @@ interface RenderedGardenMap { // eslint-disable-next-line complexity const fire = (target: Element, event: EventName, payload?: unknown) => { - const eventPayload = { + const eventPayload: Record = { ...((typeof payload === "object" && payload) ? payload : {}), - } as Record; + }; if (!("clientX" in eventPayload) && "pageX" in eventPayload) { eventPayload.clientX = eventPayload.pageX; } @@ -171,16 +171,16 @@ const fireWrapperEvent = ( dragStart?: (event: unknown) => void; }; if ((selector == ".drop-area-svg" || selector == "svg") && event == "click") { - instance.click?.(payload as never); + instance.click?.(payload); return; } if (selector == ".drop-area-svg" || selector == "svg") { if (event == "mouseDown") { - instance.startDrag?.(payload as never); + instance.startDrag?.(payload); return; } if (event == "mouseMove") { - instance.drag?.(payload as never); + instance.drag?.(payload); return; } if (event == "mouseUp") { @@ -189,20 +189,20 @@ const fireWrapperEvent = ( } } if (selector == ".drop-area-background" && event == "mouseDown") { - instance.startDragOnBackground?.(payload as never); + instance.startDragOnBackground?.(payload); return; } if (selector == ".drop-area") { if (event == "dragOver") { - instance.handleDragOver?.(payload as never); + instance.handleDragOver?.(payload); return; } if (event == "dragEnter") { - instance.handleDragEnter?.(payload as never); + instance.handleDragEnter?.(payload); return; } if (event == "dragStart") { - instance.dragStart?.(payload as never); + instance.dragStart?.(payload); return; } } @@ -244,8 +244,10 @@ const makeWrapper = ( }; }; -const renderMap = (element: React.ReactElement) => makeWrapper(element); -const renderMapWithContext = (element: React.ReactElement) => +// eslint-disable-next-line comma-spacing +const renderMap = (element: React.ReactElement) => makeWrapper(element); +// eslint-disable-next-line comma-spacing +const renderMapWithContext = (element: React.ReactElement) => makeWrapper(element, true); const DEFAULT_EVENT = { preventDefault: jest.fn(), pageX: NaN, pageY: NaN }; diff --git a/frontend/farm_designer/map/__tests__/util_test.ts b/frontend/farm_designer/map/__tests__/util_test.ts index a3b2f47ba9..010c7e7fcd 100644 --- a/frontend/farm_designer/map/__tests__/util_test.ts +++ b/frontend/farm_designer/map/__tests__/util_test.ts @@ -448,6 +448,18 @@ describe("getMode()", () => { location.pathname = Path.mock(Path.app()); expect(getMode()).toEqual(Mode.none); }); + + it("returns camera selection mode", () => { + const state = fakeState(); + state.resources.consumers.farm_designer = fakeDesignerState(); + state.resources.consumers.farm_designer.threeDCameraSelection = true; + const originalGetState = store.getState; + (store as unknown as { getState: () => typeof state }).getState = () => state; + location.pathname = Path.mock(Path.app()); + expect(getMode()).toEqual(Mode.cameraSelection); + (store as unknown as { getState: typeof originalGetState }).getState = + originalGetState; + }); }); describe("savedGardenOpen", () => { diff --git a/frontend/farm_designer/map/background/__tests__/selection_box_actions_test.ts b/frontend/farm_designer/map/background/__tests__/selection_box_actions_test.ts index cbab8623e6..31725adf63 100644 --- a/frontend/farm_designer/map/background/__tests__/selection_box_actions_test.ts +++ b/frontend/farm_designer/map/background/__tests__/selection_box_actions_test.ts @@ -89,8 +89,7 @@ describe("resizeBox", () => { it("doesn't resize box: no location", () => { const p = fakeProps(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - p.gardenCoords = undefined as any; + p.gardenCoords = undefined; resizeBox(p); expect(p.setMapState).not.toHaveBeenCalled(); expect(p.dispatch).not.toHaveBeenCalled(); @@ -98,8 +97,7 @@ describe("resizeBox", () => { it("doesn't resize box: no box", () => { const p = fakeProps(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - p.selectionBox = undefined as any; + p.selectionBox = undefined; resizeBox(p); expect(p.setMapState).not.toHaveBeenCalled(); expect(p.dispatch).not.toHaveBeenCalled(); @@ -156,8 +154,7 @@ describe("startNewSelectionBox", () => { it("doesn't start box", () => { const p = fakeProps(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - p.gardenCoords = undefined as any; + p.gardenCoords = undefined; startNewSelectionBox(p); expect(p.setMapState).not.toHaveBeenCalled(); expect(p.dispatch).toHaveBeenCalledWith({ diff --git a/frontend/farm_designer/map/interfaces.ts b/frontend/farm_designer/map/interfaces.ts index b606eded99..64ff0e8b57 100644 --- a/frontend/farm_designer/map/interfaces.ts +++ b/frontend/farm_designer/map/interfaces.ts @@ -9,6 +9,7 @@ import type { } from "farmbot"; import type { State, BotOriginQuadrant, MountedToolInfo, CameraCalibrationData, + DesignerState, } from "../interfaces"; import type { BotPosition, BotLocationData, SourceFbosConfig, @@ -64,6 +65,7 @@ export interface GardenMapLegendProps { firmwareConfig: McuParams; botLocationData: BotLocationData; botSize: BotSize; + designer: DesignerState; } export type MapTransformProps = { @@ -200,4 +202,5 @@ export enum Mode { templateView = "templateView", editGroup = "editGroup", profile = "profile", + cameraSelection = "cameraSelection", } diff --git a/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.ts b/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.ts index a33eb138d4..08a199a8b7 100644 --- a/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.ts +++ b/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.ts @@ -209,8 +209,7 @@ describe("plantActions().dropPlant()", () => { it("throws error", () => { const p = fakeProps(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - p.gardenCoords = undefined as any; + p.gardenCoords = undefined; expect(() => plantActions().dropPlant(p)) .toThrow(/while trying to add a plant/); }); @@ -349,8 +348,7 @@ describe("plantActions().setActiveSpread()", () => { it("sets crop spread value", async () => { const p = fakeProps(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - p.selectedPlant = undefined as any; + p.selectedPlant = undefined; await plantActions().setActiveSpread(p); expect(p.setMapState).toHaveBeenCalledWith({ activeDragSpread: 75 }); }); @@ -369,8 +367,7 @@ describe("plantActions().beginPlantDrag()", () => { it("starts drag: not plant", () => { const p = fakeProps(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - p.plant = undefined as any; + p.plant = undefined; plantActions().beginPlantDrag(p); }); }); diff --git a/frontend/farm_designer/map/legend/__tests__/garden_map_legend_test.tsx b/frontend/farm_designer/map/legend/__tests__/garden_map_legend_test.tsx index c229ecd091..97944428e8 100644 --- a/frontend/farm_designer/map/legend/__tests__/garden_map_legend_test.tsx +++ b/frontend/farm_designer/map/legend/__tests__/garden_map_legend_test.tsx @@ -21,6 +21,8 @@ import { import { fakeFirmwareConfig, } from "../../../../__test_support__/fake_state/resources"; +import { fakeDesignerState } from "../../../../__test_support__/fake_designer_state"; +import { Actions } from "../../../../constants"; let atMaxZoomSpy: jest.SpyInstance; let atMinZoomSpy: jest.SpyInstance; @@ -67,6 +69,7 @@ describe("", () => { firmwareConfig: fakeFirmwareConfig().body, botLocationData: fakeBotLocationData(), botSize: fakeBotSize(), + designer: fakeDesignerState(), }); it("renders", () => { @@ -137,6 +140,7 @@ const fakeProps = (): SettingsSubMenuProps => ({ dispatch: jest.fn(), getConfigValue: () => true, firmwareConfig: fakeFirmwareConfig().body, + designer: fakeDesignerState(), }); describe("", () => { @@ -185,4 +189,35 @@ describe("", () => { expect(setWebAppConfigValueSpy).toHaveBeenCalledWith( BooleanSetting.dynamic_map, false); }); + + it("shows 2D-only controls", () => { + const p = fakeProps(); + p.getConfigValue = key => key != BooleanSetting.three_d_garden; + const { container } = render(); + expect(container.textContent).toContain("Rotate map"); + expect(container.textContent).toContain("Map origin"); + expect(container.textContent).not.toContain("Set camera starting location"); + }); + + it("shows 3D-only controls", () => { + const p = fakeProps(); + p.getConfigValue = key => key == BooleanSetting.three_d_garden; + const { container } = render(); + expect(container.textContent).toContain("Set camera starting location"); + expect(container.textContent).not.toContain("Rotate map"); + }); + + it("toggles camera selection view", () => { + const p = fakeProps(); + p.getConfigValue = key => key == BooleanSetting.three_d_garden; + const { container } = render(); + const toggleBtn = + container.querySelector("button[title='Set camera starting location']"); + if (!toggleBtn) { throw new Error("Missing camera selection toggle"); } + fireEvent.click(toggleBtn); + expect(p.dispatch).toHaveBeenCalledWith({ + type: Actions.TOGGLE_3D_CAMERA_SELECTION, + payload: undefined, + }); + }); }); diff --git a/frontend/farm_designer/map/legend/garden_map_legend.tsx b/frontend/farm_designer/map/legend/garden_map_legend.tsx index bc3b6f013d..ba1caab37a 100644 --- a/frontend/farm_designer/map/legend/garden_map_legend.tsx +++ b/frontend/farm_designer/map/legend/garden_map_legend.tsx @@ -22,8 +22,11 @@ import { ZDisplay, ZDisplayToggle } from "./z_display"; import { getModifiedClassName } from "../../../settings/default_values"; import { Position } from "@blueprintjs/core"; import { MapSizeInputs } from "../../map_size_setting"; -import { OriginSelector } from "../../../settings/farm_designer_settings"; +import { + CameraStartingLocationButton, OriginSelector, +} from "../../../settings/farm_designer_settings"; import { McuParams } from "farmbot"; +import { DesignerState } from "../../interfaces"; export interface ZoomControlsProps { zoom(value: number): () => void; @@ -89,6 +92,7 @@ export interface SettingsSubMenuProps { dispatch: Function; getConfigValue: GetWebAppConfigValue; firmwareConfig: McuParams; + designer: DesignerState; } export const PointsSubMenu = (props: SettingsSubMenuProps) => @@ -127,8 +131,8 @@ export const FarmbotSubMenu = (props: SettingsSubMenuProps) => interface LayerTogglesProps extends GardenMapLegendProps { } const LayerToggles = (props: LayerTogglesProps) => { - const { toggle, getConfigValue, dispatch, firmwareConfig } = props; - const subMenuProps = { dispatch, getConfigValue, firmwareConfig }; + const { toggle, getConfigValue, dispatch, firmwareConfig, designer } = props; + const subMenuProps = { dispatch, getConfigValue, firmwareConfig, designer }; const is3D = getConfigValue(BooleanSetting.three_d_garden); const only2DClass = is3D ? "disabled" : ""; return
@@ -215,8 +219,9 @@ const LayerToggles = (props: LayerTogglesProps) => {
; }; -export const MapSettingsContent = (props: SettingsSubMenuProps) => -
+export const MapSettingsContent = (props: SettingsSubMenuProps) => { + const is3D = props.getConfigValue(BooleanSetting.three_d_garden); + return
helpText={Content.MAP_SIZE}> - - } + {!is3D && - + } + {is3D &&
+ + +
}
; +}; const MapSettings = (props: SettingsSubMenuProps) =>
@@ -270,6 +281,7 @@ export function GardenMapLegend(props: GardenMapLegendProps) { diff --git a/frontend/farm_designer/map/profile/__tests__/options_test.tsx b/frontend/farm_designer/map/profile/__tests__/options_test.tsx index 10bc3f056e..7cf5810fa4 100644 --- a/frontend/farm_designer/map/profile/__tests__/options_test.tsx +++ b/frontend/farm_designer/map/profile/__tests__/options_test.tsx @@ -61,7 +61,7 @@ describe("", () => { const p = fakeProps(); const { container } = render(); const buttons = container.querySelectorAll("button"); - fireEvent.click(buttons[buttons.length - 1] as Element); + fireEvent.click(buttons[buttons.length - 1]); expect(p.dispatch).toHaveBeenCalledWith({ type: Actions.SET_PROFILE_FOLLOW_BOT, payload: true, diff --git a/frontend/farm_designer/map/profile/__tests__/viewer_test.tsx b/frontend/farm_designer/map/profile/__tests__/viewer_test.tsx index 2b66a53a29..00fbdee92b 100644 --- a/frontend/farm_designer/map/profile/__tests__/viewer_test.tsx +++ b/frontend/farm_designer/map/profile/__tests__/viewer_test.tsx @@ -131,7 +131,7 @@ describe("", () => { p.designer.profilePosition = { x: 1, y: 2 }; const { container } = render(); const icons = container.querySelectorAll("i"); - fireEvent.click(icons[icons.length - 1] as Element); + fireEvent.click(icons[icons.length - 1]); expect(container.querySelector("svg")?.classList.contains("expand")) .toBeFalsy(); fireEvent.click(container.querySelector(".profile-button") as Element); diff --git a/frontend/farm_designer/map/util.ts b/frontend/farm_designer/map/util.ts index a8f5d827bf..95030f196c 100644 --- a/frontend/farm_designer/map/util.ts +++ b/frontend/farm_designer/map/util.ts @@ -305,6 +305,9 @@ export const getMode = (): Mode => { if (store.getState().resources.consumers.farm_designer.profileOpen) { return Mode.profile; } + if (store.getState().resources.consumers.farm_designer.threeDCameraSelection) { + return Mode.cameraSelection; + } const panelSlug = Path.getSlug(Path.designer()); if ((panelSlug === "groups" || panelSlug === "zones") && Path.getSlug(Path.groups())) { return Mode.editGroup; } diff --git a/frontend/farm_designer/reducer.ts b/frontend/farm_designer/reducer.ts index ffb0d1c413..b3e1ceb29c 100644 --- a/frontend/farm_designer/reducer.ts +++ b/frontend/farm_designer/reducer.ts @@ -59,7 +59,8 @@ export const initialState: DesignerState = { cropRadius: undefined, distanceIndicator: "", panelOpen: true, - threeDTopDownView: false, + threeDTopDownView: undefined, + threeDCameraSelection: false, threeDExaggeratedZ: false, threeDTime: undefined, }; @@ -246,6 +247,10 @@ export const designer = generateReducer(initialState) s.threeDTopDownView = payload; return s; }) + .add(Actions.TOGGLE_3D_CAMERA_SELECTION, (s) => { + s.threeDCameraSelection = !s.threeDCameraSelection; + return s; + }) .add(Actions.TOGGLE_3D_EXAGGERATED_Z, (s, { payload }) => { s.threeDExaggeratedZ = payload; return s; diff --git a/frontend/farm_designer/three_d_garden_map.tsx b/frontend/farm_designer/three_d_garden_map.tsx index 60950c908a..43fee1869d 100644 --- a/frontend/farm_designer/three_d_garden_map.tsx +++ b/frontend/farm_designer/three_d_garden_map.tsx @@ -13,7 +13,7 @@ import { } from "farmbot"; import { CameraCalibrationData, DesignerState } from "./interfaces"; import { GetWebAppConfigValue } from "../config_storage/actions"; -import { BooleanSetting } from "../session_keys"; +import { BooleanSetting, NumericSetting } from "../session_keys"; import { SlotWithTool } from "../resources/interfaces"; import { calcSunCoordinate, ThreeDGardenPlant } from "../three_d_garden/garden"; import { findCrop, findIcon } from "../crops/find"; @@ -25,6 +25,7 @@ import { get3DTime, latLng } from "../three_d_garden/time_travel"; import { parseCalibrationData } from "./map/layers/images/map_image"; import { fetchInterpolationOptions } from "./map/layers/points/interpolation_map"; import { unpackUUID } from "../util"; +import { isTopDown } from "../three_d_garden/helpers"; export interface ThreeDGardenMapProps { botSize: BotSize; @@ -76,10 +77,19 @@ export const ThreeDGardenMap = (props: ThreeDGardenMapProps) => { config.negativeZ = props.negativeZ; config.exaggeratedZ = props.designer.threeDExaggeratedZ; + const getValue = props.get3DConfigValue; + config.mirrorX = !!getValue("mirrorX"); + config.mirrorY = !!getValue("mirrorY"); + config.bedXOffset = getValue("bedXOffset"); + config.bedYOffset = getValue("bedYOffset"); + config.bedZOffset = getValue("bedZOffset"); + const position = clone(INITIAL_POSITION); position.x = props.botPosition.x || 0; position.y = props.botPosition.y || 0; position.z = props.botPosition.z || 0; + if (config.mirrorY) { position.y = gridSize.y - position.y; } + if (config.mirrorX) { position.x = gridSize.x - position.x; } const { designer } = props; config.distanceIndicator = designer.distanceIndicator; @@ -89,16 +99,12 @@ export const ThreeDGardenMap = (props: ThreeDGardenMapProps) => { config.zGantryOffset = fbosConfig("gantry_height"); config.soilHeight = Math.abs(fbosConfig("soil_height")); - const getValue = props.get3DConfigValue; config.bedWallThickness = getValue("bedWallThickness"); config.bedHeight = getValue("bedHeight"); config.ccSupportSize = getValue("ccSupportSize"); config.beamLength = getValue("beamLength"); config.columnLength = getValue("columnLength"); config.zAxisLength = getValue("zAxisLength"); - config.bedXOffset = getValue("bedXOffset"); - config.bedYOffset = getValue("bedYOffset"); - config.bedZOffset = getValue("bedZOffset"); config.legSize = getValue("legSize"); config.legsFlush = !!getValue("legsFlush"); config.extraLegsX = getValue("extraLegsX"); @@ -175,10 +181,14 @@ export const ThreeDGardenMap = (props: ThreeDGardenMapProps) => { config.interpolationUseNearest = options.useNearest; config.interpolationPower = options.power; + config.topDown = isTopDown(props.designer, props.getWebAppConfigValue); config.zoom = true; config.pan = true; - config.rotate = !props.designer.threeDTopDownView; - config.perspective = !props.designer.threeDTopDownView; + config.rotate = !config.topDown; + config.perspective = !config.topDown; + config.viewpointHeading = + parseInt("" + props.getWebAppConfigValue(NumericSetting.viewpoint_heading)); + config.cameraSelectionView = props.designer.threeDCameraSelection; const lastCaptureTime = React.useMemo(() => { const localIds = props.logs @@ -214,15 +224,15 @@ export const ThreeDGardenMap = (props: ThreeDGardenMapProps) => { }; export const convertPlants = - (config: Config, plants: TaggedPlant[]): ThreeDGardenPlant[] => + (_config: Config, plants: TaggedPlant[]): ThreeDGardenPlant[] => plants.map(plant => ({ id: plant.body.id, label: plant.body.name, icon: findIcon(plant.body.openfarm_slug), size: plant.body.radius * 2, spread: findCrop(plant.body.openfarm_slug).spread, - x: plant.body.x + config.bedXOffset, - y: plant.body.y + config.bedYOffset, + x: plant.body.x, + y: plant.body.y, key: "", seed: 0, })); diff --git a/frontend/farm_events/__tests__/add_farm_event_test.tsx b/frontend/farm_events/__tests__/add_farm_event_test.tsx index 7535703f4c..56f78e4b13 100644 --- a/frontend/farm_events/__tests__/add_farm_event_test.tsx +++ b/frontend/farm_events/__tests__/add_farm_event_test.tsx @@ -198,10 +198,9 @@ describe("", () => { p.findFarmEventByUuid = () => farmEvent; const formRef = { current: undefined as unknown as EditFEForm }; const createRefSpy = jest.spyOn(React, "createRef") - .mockReturnValue(formRef as React.RefObject); + .mockReturnValue(formRef); const { container } = render(); - formRef.current.commitViewModel = - mockSave as unknown as EditFEForm["commitViewModel"]; + formRef.current.commitViewModel = mockSave; fireEvent.click(container.querySelector(".save-btn") as Element); expect(mockSave).toHaveBeenCalled(); createRefSpy.mockRestore(); diff --git a/frontend/farm_events/__tests__/edit_farm_event_test.tsx b/frontend/farm_events/__tests__/edit_farm_event_test.tsx index b249cfafbd..d181371f3c 100644 --- a/frontend/farm_events/__tests__/edit_farm_event_test.tsx +++ b/frontend/farm_events/__tests__/edit_farm_event_test.tsx @@ -74,10 +74,9 @@ describe("", () => { it("calls farm event save", () => { const formRef = { current: undefined as unknown as EditFEForm }; const createRefSpy = jest.spyOn(React, "createRef") - .mockReturnValue(formRef as React.RefObject); + .mockReturnValue(formRef); const { container } = render(); - formRef.current.commitViewModel = - mockSave as unknown as EditFEForm["commitViewModel"]; + formRef.current.commitViewModel = mockSave; fireEvent.click(container.querySelector(".save-btn") as Element); expect(mockSave).toHaveBeenCalled(); createRefSpy.mockRestore(); @@ -85,7 +84,7 @@ describe("", () => { it("doesn't call farm event save if event is missing", () => { const p = fakeProps(); - p.getFarmEvent = () => undefined as never; + p.getFarmEvent = () => undefined; location.pathname = Path.mock(Path.farmEvents("nope")); const { container } = render(); fireEvent.click(container.querySelector(".save-btn") as Element); diff --git a/frontend/farm_events/__tests__/edit_fe_form_test.tsx b/frontend/farm_events/__tests__/edit_fe_form_test.tsx index 9a819d329e..8448af3a1c 100644 --- a/frontend/farm_events/__tests__/edit_fe_form_test.tsx +++ b/frontend/farm_events/__tests__/edit_fe_form_test.tsx @@ -63,7 +63,7 @@ describe("", () => { const update = isFunction(state) ? state(i.state, i.props) : state; i.state = { ...i.state, ...update }; callback?.(); - }) as EditFEForm["setState"]; + }); i.forceUpdate = jest.fn(); return i; } diff --git a/frontend/farm_events/__tests__/farm_event_repeat_form_test.tsx b/frontend/farm_events/__tests__/farm_event_repeat_form_test.tsx index 9f8de7f9df..eb158599a1 100644 --- a/frontend/farm_events/__tests__/farm_event_repeat_form_test.tsx +++ b/frontend/farm_events/__tests__/farm_event_repeat_form_test.tsx @@ -72,7 +72,7 @@ describe("", () => { defaultValue={props.value} disabled={props.disabled} onChange={() => { }} - onBlur={e => props.onCommit(e)} />) as never); + onBlur={e => props.onCommit(e)} />)); }); afterEach(() => { @@ -100,7 +100,7 @@ describe("", () => { it("defaults to `daily` when a bad input it passed", () => { const p = fakeProps(); - p.timeUnit = "never" as "daily"; + p.timeUnit = "never"; const { container } = render(); expect((container.querySelector( `[data-testid="${Selectors.REPEAT}"]`) as HTMLInputElement).value) diff --git a/frontend/farm_events/__tests__/farm_events_test.tsx b/frontend/farm_events/__tests__/farm_events_test.tsx index 309848c7b5..1510681c14 100644 --- a/frontend/farm_events/__tests__/farm_events_test.tsx +++ b/frontend/farm_events/__tests__/farm_events_test.tsx @@ -124,7 +124,7 @@ describe("", () => { : state; instance.state = { ...instance.state, ...update }; callback?.(); - }) as FarmEvents["setState"]; + }); instance.setState({ searchTerm: "farm events" }); instance.resetCalendar(); expect(mockScrollTo).toHaveBeenCalledWith(0, 0); @@ -142,7 +142,7 @@ describe("", () => { : state; instance.state = { ...instance.state, ...update }; callback?.(); - }) as FarmEvents["setState"]; + }); instance.setState({ searchTerm: "farm events" }); instance.resetCalendar(); expect(instance.state.searchTerm).toEqual(""); diff --git a/frontend/farmware/__tests__/actions_test.ts b/frontend/farmware/__tests__/actions_test.ts index e909c8a86b..b6ea2a315e 100644 --- a/frontend/farmware/__tests__/actions_test.ts +++ b/frontend/farmware/__tests__/actions_test.ts @@ -14,9 +14,9 @@ beforeEach(() => { { package: "farmware0" }, { package: "farmware1" }, ] - }) as never); + })); axiosPostSpy = jest.spyOn(axios, "post") - .mockImplementation(() => Promise.resolve({}) as never); + .mockImplementation(() => Promise.resolve({})); }); afterEach(() => { diff --git a/frontend/farmware/panel/__tests__/info_test.tsx b/frontend/farmware/panel/__tests__/info_test.tsx index a95a6e77b0..adaad97c1c 100644 --- a/frontend/farmware/panel/__tests__/info_test.tsx +++ b/frontend/farmware/panel/__tests__/info_test.tsx @@ -26,13 +26,13 @@ let activeFarmwareSpy: jest.SpyInstance; beforeEach(() => { designerPanelSpy = jest.spyOn(designerPanel, "DesignerPanel") .mockImplementation(((props: React.PropsWithChildren) => -
{props.children}
) as never); +
{props.children}
)); designerPanelTopSpy = jest.spyOn(designerPanel, "DesignerPanelTop") .mockImplementation(((props: React.PropsWithChildren) => -
{props.children}
) as never); +
{props.children}
)); designerPanelContentSpy = jest.spyOn(designerPanel, "DesignerPanelContent") .mockImplementation(((props: React.PropsWithChildren) => -
{props.children}
) as never); +
{props.children}
)); activeFarmwareSpy = jest.spyOn(activeFarmware, "setActiveFarmwareByName") .mockImplementation(jest.fn()); }); diff --git a/frontend/folders/__tests__/actions_test.ts b/frontend/folders/__tests__/actions_test.ts index 0100643238..9db641704f 100644 --- a/frontend/folders/__tests__/actions_test.ts +++ b/frontend/folders/__tests__/actions_test.ts @@ -60,7 +60,7 @@ beforeEach(() => { stepGetSpy = jest.spyOn(draggableActions, "stepGet") .mockImplementation(() => (() => mockStepGetResult) as unknown as - ReturnType); + ReturnType); destroySpy = jest.spyOn(crudModule, "destroy").mockImplementation(jest.fn()); editSpy = jest.spyOn(crudModule, "edit").mockImplementation(jest.fn()); initSpy = jest.spyOn(crudModule, "init").mockImplementation(jest.fn()); @@ -340,7 +340,7 @@ describe("moveSequence", () => { index: { references: { [sequence.uuid]: sequence }, } - } as Everything["resources"], + }, }; (store as unknown as { getState: () => DeepPartial }).getState = () => localState; diff --git a/frontend/folders/__tests__/component_test.tsx b/frontend/folders/__tests__/component_test.tsx index 1d71a40cdc..558ba73c65 100644 --- a/frontend/folders/__tests__/component_test.tsx +++ b/frontend/folders/__tests__/component_test.tsx @@ -157,7 +157,7 @@ const setStateSync = ), }; callback?.(); - }) as T["setState"]; + }); return instance; }; @@ -836,14 +836,14 @@ describe("", () => { it("creates new folder", () => { const p = fakeProps(); const { container } = render(); - fireEvent.click(container.querySelectorAll("button")[1] as Element); + fireEvent.click(container.querySelectorAll("button")[1]); expect(createFolder).toHaveBeenCalled(); }); it("creates new sequence", () => { const p = fakeProps(); const { container } = render(); - fireEvent.click(container.querySelectorAll("button")[2] as Element); + fireEvent.click(container.querySelectorAll("button")[2]); expect(addNewSequenceToFolder).toHaveBeenCalled(); }); }); diff --git a/frontend/front_page/__tests__/demo_login_option_test.tsx b/frontend/front_page/__tests__/demo_login_option_test.tsx index 256fb9eff6..05ce89c64e 100644 --- a/frontend/front_page/__tests__/demo_login_option_test.tsx +++ b/frontend/front_page/__tests__/demo_login_option_test.tsx @@ -24,7 +24,7 @@ describe("", () => { .mockImplementation(() => typeof mockResponse === "string" ? Promise.resolve(mockResponse) - : Promise.reject(mockResponse) as never); + : Promise.reject(mockResponse)); }); afterEach(() => { @@ -65,7 +65,7 @@ describe("", () => { : state; instance.state = { ...instance.state, ...update }; callback?.(); - }) as DemoLoginOption["setState"]; + }); expect(instance.state.productLine).toEqual("genesis_1.8"); const select = instance["seedDataSelect"]() as React.ReactElement<{ onChange: (ddi: { value: string }) => void; diff --git a/frontend/front_page/__tests__/front_page_test.tsx b/frontend/front_page/__tests__/front_page_test.tsx index 83a13ba7a6..9db581ad22 100644 --- a/frontend/front_page/__tests__/front_page_test.tsx +++ b/frontend/front_page/__tests__/front_page_test.tsx @@ -28,7 +28,7 @@ let originalPrivUrl: string; const setStateSync = (instance: FrontPage) => { instance.setState = ((state: Partial) => { instance.state = { ...instance.state, ...state }; - }) as FrontPage["setState"]; + }); return instance; }; @@ -65,9 +65,9 @@ describe("", () => { originalPrivUrl = globalConfig.PRIV_URL; const mockState = fakeState(); getStateSpy = jest.spyOn(store, "getState") - .mockReturnValue(mockState as never); + .mockReturnValue(mockState); postSpy = jest.spyOn(axios, "post") - .mockImplementation(() => mockAxiosResponse as never); + .mockImplementation(() => mockAxiosResponse); fetchStoredTokenSpy = jest.spyOn(Session, "fetchStoredToken") .mockImplementation(() => mockAuth); replaceTokenSpy = jest.spyOn(Session, "replaceToken") @@ -140,8 +140,8 @@ describe("", () => { const content = instance.defaultContent(); const createAccount = findElement(content, element => element.type === CreateAccount) as React.ReactElement<{ - set: (key: keyof FrontPage["state"], val: string | boolean) => void; - }>; + set: (key: keyof FrontPage["state"], val: string | boolean) => void; + }>; if (!createAccount) { throw new Error("Expected create account panel"); } @@ -241,7 +241,8 @@ describe("", () => { instance.submitRegistration(fakeFormEvent); await flushPromises(); expect(axios.post).toHaveBeenCalledWith( - "http://localhost:3000/api/users/", { + "http://localhost:3000/api/users/", + { user: { agree_to_terms: true, email: "foo@bar.io", name: "Foo Bar", password: "password", password_confirmation: "password" @@ -265,7 +266,8 @@ describe("", () => { instance.submitRegistration(fakeFormEvent); await flushPromises(); expect(axios.post).toHaveBeenCalledWith( - "http://localhost:3000/api/users/", { + "http://localhost:3000/api/users/", + { user: { agree_to_terms: true, email: "foo@bar.io", name: "Foo Bar", password: "password", password_confirmation: "password" diff --git a/frontend/front_page/__tests__/login_test.tsx b/frontend/front_page/__tests__/login_test.tsx index 184fe76529..28fe20d170 100644 --- a/frontend/front_page/__tests__/login_test.tsx +++ b/frontend/front_page/__tests__/login_test.tsx @@ -21,12 +21,12 @@ describe("", () => { it("interacts with login options", () => { const p = fakeProps(); const { container } = render(); - fireEvent.change(container.querySelectorAll("input")[0] as Element, { + fireEvent.change(container.querySelectorAll("input")[0], { target: { value: "email" } }); expect(p.onEmailChange).toHaveBeenCalled(); expect((p.onEmailChange as jest.Mock).mock.calls.length).toEqual(1); - fireEvent.change(container.querySelectorAll("input")[1] as Element, { + fireEvent.change(container.querySelectorAll("input")[1], { target: { value: "password" } }); expect(p.onLoginPasswordChange).toHaveBeenCalled(); diff --git a/frontend/front_page/__tests__/resend_verification_test.tsx b/frontend/front_page/__tests__/resend_verification_test.tsx index c252030616..71b8e83567 100644 --- a/frontend/front_page/__tests__/resend_verification_test.tsx +++ b/frontend/front_page/__tests__/resend_verification_test.tsx @@ -17,7 +17,7 @@ describe("", () => { beforeEach(() => { mockPost = Promise.resolve({ data: "whatever" }); axiosPostSpy = jest.spyOn(axios, "post") - .mockImplementation(() => mockPost as never); + .mockImplementation(() => mockPost); }); afterEach(() => { diff --git a/frontend/help/documentation.tsx b/frontend/help/documentation.tsx index e66753c1cd..0bb86af867 100644 --- a/frontend/help/documentation.tsx +++ b/frontend/help/documentation.tsx @@ -12,12 +12,8 @@ export interface DocumentationPanelProps { export const DocumentationPanel = (props: DocumentationPanelProps) => { const location = useLocation(); - const [src, setSrc] = React.useState(""); - - React.useEffect(() => { - const page = new URLSearchParams(location.search).get("page"); - setSrc(page ? `${props.url}/${page}` : props.url); - }, [props, location]); + const page = new URLSearchParams(location.search).get("page"); + const src = page ? `${props.url}/${page}` : props.url; return diff --git a/frontend/logs/__tests__/index_test.tsx b/frontend/logs/__tests__/index_test.tsx index 96168dc4aa..0f073fb0a5 100644 --- a/frontend/logs/__tests__/index_test.tsx +++ b/frontend/logs/__tests__/index_test.tsx @@ -50,7 +50,7 @@ describe("", () => { const setStateSync = (instance: Logs) => { instance.setState = ((state: Partial) => { instance.state = { ...instance.state, ...state }; - }) as Logs["setState"]; + }); return instance; }; diff --git a/frontend/logs/components/__tests__/settings_menu_test.tsx b/frontend/logs/components/__tests__/settings_menu_test.tsx index d7b9424f8d..7f7fd52bac 100644 --- a/frontend/logs/components/__tests__/settings_menu_test.tsx +++ b/frontend/logs/components/__tests__/settings_menu_test.tsx @@ -95,7 +95,7 @@ describe("", () => { p.dispatch = jest.fn(); const { container } = render(); const buttons = container.querySelectorAll("button"); - fireEvent.click(buttons[buttons.length - 1] as Element); + fireEvent.click(buttons[buttons.length - 1]); await Promise.resolve(); await Promise.resolve(); expect(crud.destroyAll).toHaveBeenCalledWith( diff --git a/frontend/messages/__tests__/actions_test.ts b/frontend/messages/__tests__/actions_test.ts index cb5a64dea6..1613825f31 100644 --- a/frontend/messages/__tests__/actions_test.ts +++ b/frontend/messages/__tests__/actions_test.ts @@ -18,9 +18,9 @@ beforeEach(() => { toastErrorsSpy = jest.spyOn(toastErrorsModule, "toastErrors") .mockImplementation(jest.fn()); axiosGetSpy = jest.spyOn(axios, "get") - .mockImplementation(() => Promise.resolve({ data: { foo: "bar" } }) as never); + .mockImplementation(() => Promise.resolve({ data: { foo: "bar" } })); axiosPostSpy = jest.spyOn(axios, "post") - .mockImplementation(() => mockPostResponse as never); + .mockImplementation(() => mockPostResponse); }); afterEach(() => { @@ -55,7 +55,7 @@ describe("seedAccount()", () => { }); it("returns error while trying to seed account", async () => { - axiosPostSpy.mockRejectedValueOnce({ response: { data: ["error"] } } as never); + axiosPostSpy.mockRejectedValueOnce({ response: { data: ["error"] } }); const dismiss = jest.fn(); await seedAccount(dismiss)({ label: "Genesis v1.2", value: "genesis_1.2" }); expect(axios.post).toHaveBeenCalledWith(API.current.accountSeedPath, { diff --git a/frontend/messages/__tests__/cards_test.tsx b/frontend/messages/__tests__/cards_test.tsx index 2bd0ade4b3..2dd1f2f65f 100644 --- a/frontend/messages/__tests__/cards_test.tsx +++ b/frontend/messages/__tests__/cards_test.tsx @@ -65,10 +65,10 @@ beforeEach(() => { shouldDisplayFeatureSpy = jest.spyOn(shouldDisplay, "shouldDisplayFeature") .mockImplementation(() => mockFeatureBoolean); fetchBulletinContentSpy = jest.spyOn(messageActions, "fetchBulletinContent") - .mockImplementation(() => Promise.resolve(mockData) as never); + .mockImplementation(() => Promise.resolve(mockData)); seedAccountSpy = jest.spyOn(messageActions, "seedAccount") .mockImplementation(() => mockSeedAccount as never); - axiosDeleteSpy = jest.spyOn(axios, "delete").mockResolvedValue({} as never); + axiosDeleteSpy = jest.spyOn(axios, "delete").mockResolvedValue({}); sessionClearSpy = jest.spyOn(Session, "clear") .mockImplementation(() => undefined as never); fbSelectSpy = jest.spyOn(ui, "FBSelect") @@ -233,8 +233,8 @@ describe("", () => { const { container } = render(); expect(container.textContent).toContain("currently using"); const links = container.querySelectorAll("a"); - fireEvent.click(links[0] as Element); - fireEvent.click(links[links.length - 1] as Element); + fireEvent.click(links[0]); + fireEvent.click(links[links.length - 1]); expect(Session.clear).toHaveBeenCalledTimes(2); }); @@ -341,7 +341,7 @@ describe("", () => { const { container } = render(); fireEvent.click(container.querySelector(".fb-select-mock") as Element); const buttons = container.querySelectorAll("button"); - fireEvent.click(buttons[buttons.length - 1] as Element); + fireEvent.click(buttons[buttons.length - 1]); expect(mockSeedAccount).toHaveBeenCalledWith({ label: "", value: "selection", diff --git a/frontend/messages/__tests__/reducer_test.ts b/frontend/messages/__tests__/reducer_test.ts index cb65d79dc2..c57681df76 100644 --- a/frontend/messages/__tests__/reducer_test.ts +++ b/frontend/messages/__tests__/reducer_test.ts @@ -12,8 +12,7 @@ beforeEach(() => { describe("Contextual `Alert` creation", () => { it("toggles on", () => { const c = fakeFbosConfig(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - c.body.firmware_hardware = undefined as any; + c.body.firmware_hardware = undefined; const s: AlertReducerState = { alerts: {} }; diff --git a/frontend/password_reset/__tests__/password_reset_test.tsx b/frontend/password_reset/__tests__/password_reset_test.tsx index ac98f989af..9469795af7 100644 --- a/frontend/password_reset/__tests__/password_reset_test.tsx +++ b/frontend/password_reset/__tests__/password_reset_test.tsx @@ -15,7 +15,7 @@ describe("", () => { originalPathname = location.pathname; location.pathname = "/password_resets/"; axiosPutSpy = jest.spyOn(axios, "put") - .mockImplementation(() => mockPut as never); + .mockImplementation(() => mockPut); }); afterEach(() => { diff --git a/frontend/photos/images/__tests__/photos_test.tsx b/frontend/photos/images/__tests__/photos_test.tsx index af3966b0e4..6dd14dcf48 100644 --- a/frontend/photos/images/__tests__/photos_test.tsx +++ b/frontend/photos/images/__tests__/photos_test.tsx @@ -96,7 +96,7 @@ describe("", () => { ...(update as Partial), }; callback?.(); - }) as Photos["setState"]; + }); return instance; }; diff --git a/frontend/photos/photo_filter_settings/__tests__/image_filter_menu_test.tsx b/frontend/photos/photo_filter_settings/__tests__/image_filter_menu_test.tsx index 63d6261c46..19e435b9c9 100644 --- a/frontend/photos/photo_filter_settings/__tests__/image_filter_menu_test.tsx +++ b/frontend/photos/photo_filter_settings/__tests__/image_filter_menu_test.tsx @@ -44,7 +44,7 @@ beforeEach(() => { { length: props.max - props.min + 1 }, (_, index) => props.min + index, ).map(day => {props.labelRenderer(day)})} -
) as never); +
)); }); afterEach(() => { @@ -64,7 +64,7 @@ describe("", () => { const setStateSync = (instance: ImageFilterMenu) => { instance.setState = (((update: Partial) => { instance.state = { ...instance.state, ...update }; - }) as unknown) as typeof instance.setState; + })); }; const setConfigDispatch = ( diff --git a/frontend/plants/__tests__/crop_info_test.tsx b/frontend/plants/__tests__/crop_info_test.tsx index 70256cf0b5..c680fddc57 100644 --- a/frontend/plants/__tests__/crop_info_test.tsx +++ b/frontend/plants/__tests__/crop_info_test.tsx @@ -56,7 +56,7 @@ beforeEach(() => { .mockImplementation(((props: BIProps) => - props.onCommit(e as React.SyntheticEvent)} />) as never); + props.onCommit(e)} />) as never); }); afterEach(() => { @@ -246,6 +246,7 @@ describe("", () => { const { container } = render(); expect(normalizedText(container)).toContain("sowingnotavailable"); expect(normalizedText(container)).toContain("commonnamesnotavailable"); + expect(container.querySelector("img[src='']")).toBeNull(); }); it("handles string of names", () => { diff --git a/frontend/plants/crop_info.tsx b/frontend/plants/crop_info.tsx index 51086226fe..c37840f4f7 100644 --- a/frontend/plants/crop_info.tsx +++ b/frontend/plants/crop_info.tsx @@ -273,10 +273,6 @@ export function mapStateToProps(props: Everything): CropInfoProps { export const RawCropInfo = (props: CropInfoProps) => { const [gridOpen, setGridOpen] = React.useState(false); const toggleOpen = () => setGridOpen(!gridOpen); - React.useEffect(() => { - selectMostUsedCurves(Path.getCropSlug()); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); const selectMostUsedCurves = (slug: string) => { const findCurve = findMostUsedCurveForCrop({ @@ -289,6 +285,10 @@ export const RawCropInfo = (props: CropInfoProps) => { changeCurve(props.dispatch)(id, curveType); }); }; + React.useEffect(() => { + selectMostUsedCurves(Path.getCropSlug()); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const { dispatch, designer } = props; const slug = Path.getCropSlug(); @@ -387,7 +387,7 @@ export const RawCropInfo = (props: CropInfoProps) => { findCurve={findCurve(props.curves, designer)} plants={props.plants} onChange={changeCurve(dispatch)} /> - + {image && } ; }; diff --git a/frontend/point_groups/__tests__/actions_test.ts b/frontend/point_groups/__tests__/actions_test.ts index 9a014bd582..2b318658e9 100644 --- a/frontend/point_groups/__tests__/actions_test.ts +++ b/frontend/point_groups/__tests__/actions_test.ts @@ -38,7 +38,7 @@ beforeEach(() => { selectAllPlantPointersSpy = jest.spyOn(selectors, "selectAllPlantPointers") .mockImplementation(jest.fn(() => [])); findUuidSpy = jest.spyOn(selectors, "findUuid") - .mockImplementation((() => "PointGroup.0.0") as typeof selectors.findUuid); + .mockImplementation(() => "PointGroup.0.0"); }); afterEach(() => { diff --git a/frontend/point_groups/criteria/__tests__/component_test.tsx b/frontend/point_groups/criteria/__tests__/component_test.tsx index 3c80e4cf0b..d9962beec7 100644 --- a/frontend/point_groups/criteria/__tests__/component_test.tsx +++ b/frontend/point_groups/criteria/__tests__/component_test.tsx @@ -170,7 +170,7 @@ describe("", () => { p.group.body.point_ids = [1, 2]; const { container } = render(); window.confirm = () => true; - fireEvent.click(container.querySelectorAll("button")[0] as Element); + fireEvent.click(container.querySelectorAll("button")[0]); const expectedBody = cloneDeep(p.group.body); expectedBody.point_ids = []; expect(overwriteGroupSpy).toHaveBeenCalledWith(p.group, expectedBody); @@ -181,7 +181,7 @@ describe("", () => { p.group.body.point_ids = [1, 2]; const { container } = render(); window.confirm = () => false; - fireEvent.click(container.querySelectorAll("button")[0] as Element); + fireEvent.click(container.querySelectorAll("button")[0]); expect(overwriteGroupSpy).not.toHaveBeenCalled(); }); @@ -189,7 +189,7 @@ describe("", () => { const p = fakeProps(); const { container } = render(); window.confirm = () => true; - fireEvent.click(container.querySelectorAll("button")[1] as Element); + fireEvent.click(container.querySelectorAll("button")[1]); const expectedBody = cloneDeep(p.group.body); expectedBody.criteria = DEFAULT_CRITERIA; expect(overwriteGroupSpy).toHaveBeenCalledWith(p.group, expectedBody); @@ -199,7 +199,7 @@ describe("", () => { const p = fakeProps(); const { container } = render(); window.confirm = () => false; - fireEvent.click(container.querySelectorAll("button")[1] as Element); + fireEvent.click(container.querySelectorAll("button")[1]); expect(overwriteGroupSpy).not.toHaveBeenCalled(); }); @@ -305,7 +305,7 @@ describe("", () => { const p = fakeProps(); p.pointTypes = ["Plant", "Weed"]; const { container } = render(); - fireEvent.click(container.querySelectorAll("input")[0] as Element); + fireEvent.click(container.querySelectorAll("input")[0]); expect(togglePointTypeCriteriaSpy).toHaveBeenCalledWith(p.group, "Plant"); }); }); diff --git a/frontend/point_groups/criteria/__tests__/show_test.tsx b/frontend/point_groups/criteria/__tests__/show_test.tsx index 91c4df7936..62523db63f 100644 --- a/frontend/point_groups/criteria/__tests__/show_test.tsx +++ b/frontend/point_groups/criteria/__tests__/show_test.tsx @@ -89,7 +89,7 @@ describe(" />", () => { const p = fakeProps(); p.eqCriteria = { openfarm_slug: ["slug"] }; const { container } = render( {...p} />); - fireEvent.click(container.querySelectorAll("button")[1] as Element); + fireEvent.click(container.querySelectorAll("button")[1]); expect(removeEqCriteriaValueSpy).toHaveBeenCalledWith( p.group, { openfarm_slug: ["slug"] }, @@ -121,7 +121,7 @@ describe("", () => { p.criteria.number_gt = { x: 1 }; const { container } = render(); expect(container.textContent).toContain(">"); - fireEvent.click(container.querySelectorAll("button")[1] as Element); + fireEvent.click(container.querySelectorAll("button")[1]); expect(clearCriteriaFieldSpy).toHaveBeenCalledWith( p.group, ["number_gt"], @@ -192,7 +192,7 @@ describe("", () => { it("changes number_gt", () => { const p = fakeProps(); const { container } = render(); - fireEvent.blur(container.querySelectorAll("input")[0] as Element, { + fireEvent.blur(container.querySelectorAll("input")[0], { target: { value: "1" } }); expect(editGtLtCriteriaFieldSpy).toHaveBeenCalledWith( @@ -205,7 +205,7 @@ describe("", () => { it("changes number_lt", () => { const p = fakeProps(); const { container } = render(); - fireEvent.blur(container.querySelectorAll("input")[1] as Element, { + fireEvent.blur(container.querySelectorAll("input")[1], { target: { value: "1" } }); expect(editGtLtCriteriaFieldSpy).toHaveBeenCalledWith( diff --git a/frontend/points/__tests__/point_info_test.tsx b/frontend/points/__tests__/point_info_test.tsx index bd3f550d5a..d4339d3e56 100644 --- a/frontend/points/__tests__/point_info_test.tsx +++ b/frontend/points/__tests__/point_info_test.tsx @@ -34,7 +34,7 @@ beforeEach(() => { saveSpy = jest.spyOn(crud, "save").mockImplementation(jest.fn()); popoverSpy = jest.spyOn(popover, "Popover").mockImplementation((( { target, content }: PopoverProps, - ) =>
{target}{content}
) as never); + ) =>
{target}{content}
)); }); afterEach(() => { diff --git a/frontend/promo/__tests__/plants_test.ts b/frontend/promo/__tests__/plants_test.ts index 035576743e..d3033d9c60 100644 --- a/frontend/promo/__tests__/plants_test.ts +++ b/frontend/promo/__tests__/plants_test.ts @@ -22,7 +22,7 @@ describe("calculatePlantPositions()", () => { seed: expect.any(Number), size: 150, spread: 17.5, - x: 350, + x: 200, y: 680, }); expect(positions.length).toEqual(65); diff --git a/frontend/promo/plants.ts b/frontend/promo/plants.ts index 21ef640187..a94fb9d187 100644 --- a/frontend/promo/plants.ts +++ b/frontend/promo/plants.ts @@ -7,11 +7,11 @@ import { Season, SEASON_DURATIONS } from "./constants"; export const calculatePlantPositions = (config: Config): ThreeDGardenPlant[] => { const gardenPlants = GARDENS[config.plants] || []; const positions: ThreeDGardenPlant[] = []; - const startX = 350; + const startX = 200; let nextX = startX; let index = 0; let nextId = 1; - while (nextX <= config.bedLengthOuter - 100) { + while (nextX <= config.bedLengthOuter - 250) { const plantKey = gardenPlants[index]; const plant = PLANTS[plantKey]; if (!plant) { return []; } diff --git a/frontend/promo/promo.tsx b/frontend/promo/promo.tsx index 3f204fbbf3..487a256432 100644 --- a/frontend/promo/promo.tsx +++ b/frontend/promo/promo.tsx @@ -16,6 +16,7 @@ import { ThreeDGardenPlant } from "../three_d_garden/garden"; import { TaggedGenericPointer } from "farmbot"; import { calculatePointPositions } from "./points"; import { SEASON_TIMINGS, SEASONS } from "./constants"; +import { isMobile } from "../screen_size"; const PROMO_BED_SIZES = [ { @@ -29,6 +30,7 @@ const PROMO_BED_SIZES = [ ]; type ThreeDPlantsCache = Record; +const PLANTS_CACHE: ThreeDPlantsCache = {}; const calcCacheKey = (config: Config): string => `${config.bedLengthOuter}x${config.bedWidthOuter}: ${config.plants}`; @@ -41,11 +43,38 @@ const calcPlantsCache = ( if (cache[cacheKey]) { return cache; } - const positions = calculatePlantPositions(config); - cache[cacheKey] = positions; - return cache; + return { + ...cache, + [cacheKey]: calculatePlantPositions(config), + }; +}; + +const prewarmPlantsCache = () => { + let next = PLANTS_CACHE; + PROMO_BED_SIZES.map(({ length, width }) => { + SEASONS.map(season => { + next = calcPlantsCache(next, { + ...INITIAL, + bedLengthOuter: length, + bedWidthOuter: width, + plants: season, + }); + }); + }); + Object.assign(PLANTS_CACHE, next); +}; + +const getCachedPlants = (config: Config) => { + const cacheKey = calcCacheKey(config); + const cachedPlants = PLANTS_CACHE[cacheKey]; + if (cachedPlants) { return cachedPlants; } + + Object.assign(PLANTS_CACHE, calcPlantsCache(PLANTS_CACHE, config)); + return PLANTS_CACHE[cacheKey] || []; }; +prewarmPlantsCache(); + export const getSeasonTimings = (currentSeason: string, step = 0) => { const seasons = SEASON_TIMINGS.map(s => s.season); const seasonIndex = seasons.indexOf(currentSeason); @@ -56,50 +85,32 @@ export const getSeasonTimings = (currentSeason: string, step = 0) => { }; export const Promo = () => { - const [config, setConfig] = React.useState(INITIAL); + const [config, setConfig] = React.useState(() => { + let next = INITIAL; + if (isMobile()) { + next = { ...next, viewpointHeading: 80 }; + } + next = modifyConfigsFromUrlParams(next); + return next; + }); const [toolTip, setToolTip] = React.useState({ timeoutId: 0, text: "" }); - const [activeFocus, setActiveFocus] = React.useState(""); + const [activeFocus, setActiveFocus] = React.useState(() => + getFocusFromUrlParams()); const common = { config, setConfig, toolTip, setToolTip, activeFocus, setActiveFocus, }; - React.useEffect(() => { - setConfig(modifyConfigsFromUrlParams(config)); - setActiveFocus(getFocusFromUrlParams()); - PROMO_BED_SIZES.map(({ length, width }) => { - SEASONS.map(season => { - const tmpConfig = { - ...INITIAL, - bedLengthOuter: length, - bedWidthOuter: width, - plants: season, - }; - setPlantsCache(calcPlantsCache(plantsCache, tmpConfig)); - }); - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); // intentionally empty dependency array - - const [plantsCache, setPlantsCache] = React.useState({}); - const [mapPoints, setMapPoints] = React.useState([]); - - React.useEffect(() => { - setPlantsCache(calcPlantsCache(plantsCache, config)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [config.plants, config.bedLengthOuter, config.bedWidthOuter]); - - React.useEffect(() => { - setMapPoints(calculatePointPositions(config)); + const mapPoints = React.useMemo(() => // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ + calculatePointPositions(config), [ config.soilSurface, config.soilHeight, config.soilSurfacePointCount, config.soilSurfaceVariance, config.bedXOffset, config.bedYOffset, config.bedWallThickness, config.bedLengthOuter, config.bedWidthOuter, ]); - const startTimeRef = React.useRef(performance.now() / 1000); + const startTimeRef = React.useRef(0); React.useEffect(() => { if (!config.animateSeasons) { return; } @@ -121,13 +132,16 @@ export const Promo = () => { startTimeRef.current = performance.now() / 1000; }, []); - const getPlants = () => { - const plants = plantsCache[calcCacheKey(config)] || []; - if (config.promoSpread) { - return plants.map(plant => ({ ...plant, id: 0 })); - } - return plants; - }; + const plants = React.useMemo(() => { + return getCachedPlants(config); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [config.plants, config.bedLengthOuter, config.bedWidthOuter]); + + const threeDPlants = React.useMemo(() => { + return config.promoSpread + ? plants.map(plant => ({ ...plant, id: 0 })) + : plants; + }, [plants, config.promoSpread]); return
@@ -140,7 +154,7 @@ export const Promo = () => { diff --git a/frontend/redux/__tests__/create_refresh_trigger_test.ts b/frontend/redux/__tests__/create_refresh_trigger_test.ts index 10b4ce1384..5df00c7912 100644 --- a/frontend/redux/__tests__/create_refresh_trigger_test.ts +++ b/frontend/redux/__tests__/create_refresh_trigger_test.ts @@ -11,7 +11,7 @@ describe("createRefreshTrigger", () => { .mockImplementation(jest.fn(() => jest.fn())); maybeGetDeviceSpy = jest.spyOn(deviceModule, "maybeGetDevice") .mockImplementation((() => - ({} as import("farmbot").Farmbot)) as typeof deviceModule.maybeGetDevice); + ({} as import("farmbot").Farmbot))); }); afterEach(() => { diff --git a/frontend/redux/__tests__/root_reducer_test.ts b/frontend/redux/__tests__/root_reducer_test.ts index 5f8734a56e..23c0ff90fb 100644 --- a/frontend/redux/__tests__/root_reducer_test.ts +++ b/frontend/redux/__tests__/root_reducer_test.ts @@ -9,7 +9,7 @@ describe("rootReducer()", () => { beforeEach(() => { clearSpy = jest.spyOn(Session, "clear") - .mockImplementation((() => undefined as never) as typeof Session.clear); + .mockImplementation((() => undefined) as typeof Session.clear); }); afterEach(() => { diff --git a/frontend/regimens/editor/__tests__/state_to_props_test.ts b/frontend/regimens/editor/__tests__/state_to_props_test.ts index e7c977605e..4f8dccda79 100644 --- a/frontend/regimens/editor/__tests__/state_to_props_test.ts +++ b/frontend/regimens/editor/__tests__/state_to_props_test.ts @@ -63,7 +63,7 @@ describe("mapStateToProps()", () => { const varData = fakeVariableNameSet(); state.resources.index.sequenceMetas[seq.uuid] = varData; const findSequenceByIdSpy = jest.spyOn(selectors, "findSequenceById") - .mockReturnValue(seq as never); + .mockReturnValue(seq); try { const props = mapStateToProps(state); expect(props.variableData).toEqual(expect.objectContaining(varData)); diff --git a/frontend/regimens/list/__tests__/list_test.tsx b/frontend/regimens/list/__tests__/list_test.tsx index 93ef214043..aff02c6639 100644 --- a/frontend/regimens/list/__tests__/list_test.tsx +++ b/frontend/regimens/list/__tests__/list_test.tsx @@ -28,15 +28,15 @@ describe("", () => { designerPanelSpy = jest.spyOn(designerPanel, "DesignerPanel") .mockImplementation((( { children }: React.ComponentProps) => -
{children}
) as never); +
{children}
)); designerPanelContentSpy = jest.spyOn(designerPanel, "DesignerPanelContent") .mockImplementation((( { children }: React.ComponentProps) => -
{children}
) as never); +
{children}
)); designerPanelTopSpy = jest.spyOn(designerPanel, "DesignerPanelTop") .mockImplementation((( props: React.ComponentProps) => - mockDesignerPanelTop(props)) as never); + mockDesignerPanelTop(props))); mockDesignerPanelTop.mockClear(); }); diff --git a/frontend/resources/reducer_support.ts b/frontend/resources/reducer_support.ts index ed40a37878..9b5196e744 100644 --- a/frontend/resources/reducer_support.ts +++ b/frontend/resources/reducer_support.ts @@ -423,7 +423,7 @@ export function beforeEach(state: RestResources, const { warning } = require("../toast/toast"); warning(`(${place}) Can't modify account data when in read-only mode.`); }; - const { kind } = unpackUUID(get(action, "payload.uuid", "x.y.z") as string); + const { kind } = unpackUUID(get(action, "payload.uuid", "x.y.z")); switch (action.type) { case Actions.EDIT_RESOURCE: diff --git a/frontend/resources/selectors_by_id.ts b/frontend/resources/selectors_by_id.ts index 15e1f93f0d..843220e957 100644 --- a/frontend/resources/selectors_by_id.ts +++ b/frontend/resources/selectors_by_id.ts @@ -30,7 +30,7 @@ const byId = const resources = findAll(index, kind); const f = (x: TaggedResource) => (x.kind === kind) && (x.body.id === id); // Maybe we should add a throw here? - return resources.filter(f)[0] as T | undefined; + return resources.filter(f)[0]; }; export const findFarmEventById = (ri: ResourceIndex, fe_id: number) => { diff --git a/frontend/resources/sequence_meta.ts b/frontend/resources/sequence_meta.ts index e0e054beaa..9500d6bf0e 100644 --- a/frontend/resources/sequence_meta.ts +++ b/frontend/resources/sequence_meta.ts @@ -170,7 +170,7 @@ export const determineDropdown = label: get({ "SavedGarden": t("Garden"), "PointGroup": t("Group"), - }, resourceType, resourceType) as string, + }, resourceType, resourceType), value: data_value.args.resource_type, headingId: "Resource", }; diff --git a/frontend/saved_gardens/__tests__/garden_snapshot_test.tsx b/frontend/saved_gardens/__tests__/garden_snapshot_test.tsx index 3bab78fe87..2f7a0509ae 100644 --- a/frontend/saved_gardens/__tests__/garden_snapshot_test.tsx +++ b/frontend/saved_gardens/__tests__/garden_snapshot_test.tsx @@ -14,7 +14,7 @@ let axiosPostSpy: jest.SpyInstance; beforeEach(() => { axiosPostSpy = jest.spyOn(axios, "post") - .mockImplementation(() => Promise.resolve({}) as never); + .mockImplementation(() => Promise.resolve({})); snapshotGardenSpy = jest.spyOn(savedGardenActions, "snapshotGarden") .mockImplementation(jest.fn()); newSavedGardenSpy = jest.spyOn(savedGardenActions, "newSavedGarden") @@ -82,7 +82,7 @@ describe("", () => { currentTarget: { value: "new saved garden" }, target: { value: "new saved garden" }, }); - fireEvent.click(container.querySelectorAll("button")[1] as Element); + fireEvent.click(container.querySelectorAll("button")[1]); expect(newSavedGarden).toHaveBeenCalledWith(expect.any(Function), "new saved garden", ""); }); diff --git a/frontend/sensors/__tests__/sensor_list_test.tsx b/frontend/sensors/__tests__/sensor_list_test.tsx index 32b528c583..fcccfd34ff 100644 --- a/frontend/sensors/__tests__/sensor_list_test.tsx +++ b/frontend/sensors/__tests__/sensor_list_test.tsx @@ -89,9 +89,9 @@ describe("", function () { it("reads sensors", () => { const { container } = render(); const readSensorBtn = container.querySelectorAll("button"); - fireEvent.click(readSensorBtn[0] as Element); + fireEvent.click(readSensorBtn[0]); expect(mockDevice.readPin).toHaveBeenCalledWith(expectedPayload(51, 0)); - fireEvent.click(readSensorBtn[1] as Element); + fireEvent.click(readSensorBtn[1]); expect(mockDevice.readPin).toHaveBeenLastCalledWith(expectedPayload(50, 1)); expect(mockDevice.readPin).toHaveBeenCalledTimes(2); }); @@ -101,8 +101,8 @@ describe("", function () { p.disabled = true; const { container } = render(); const readSensorBtn = container.querySelectorAll("button"); - fireEvent.click(readSensorBtn[0] as Element); - fireEvent.click(readSensorBtn[readSensorBtn.length - 1] as Element); + fireEvent.click(readSensorBtn[0]); + fireEvent.click(readSensorBtn[readSensorBtn.length - 1]); expect(mockDevice.readPin).not.toHaveBeenCalled(); }); diff --git a/frontend/sequences/__tests__/actions_test.ts b/frontend/sequences/__tests__/actions_test.ts index 4e9b1cc3aa..eeff629d14 100644 --- a/frontend/sequences/__tests__/actions_test.ts +++ b/frontend/sequences/__tests__/actions_test.ts @@ -46,7 +46,7 @@ beforeEach(() => { overwriteSpy = jest.spyOn(crud, "overwrite") .mockImplementation(jest.fn()); axiosPostSpy = jest.spyOn(axios, "post") - .mockImplementation(() => mockPost as never); + .mockImplementation(() => mockPost); setActiveSequenceByNameSpy = jest.spyOn( activeSequenceByName, "setActiveSequenceByName") .mockImplementation(jest.fn()); diff --git a/frontend/sequences/__tests__/request_auto_generation_test.ts b/frontend/sequences/__tests__/request_auto_generation_test.ts index d5d6961cd8..56de16188e 100644 --- a/frontend/sequences/__tests__/request_auto_generation_test.ts +++ b/frontend/sequences/__tests__/request_auto_generation_test.ts @@ -62,7 +62,7 @@ describe("requestAutoGeneration()", () => { .mockResolvedValueOnce({ done: false, value: new Uint8Array([101]) }) .mockResolvedValueOnce({ done: false, value: new Uint8Array([100]) }), ))); - global.fetch = fetchMock as unknown as typeof fetch; + global.fetch = fetchMock as never; const p = fakeProps(); p.contextKey = "color"; actualRequestAutoGeneration(p); @@ -90,7 +90,7 @@ describe("requestAutoGeneration()", () => { jest.fn().mockResolvedValue({ done: true, value: "" }), { ok: false, body: undefined }, ))); - global.fetch = fetchMock as unknown as typeof fetch; + global.fetch = fetchMock as never; const p = fakeProps(); p.contextKey = "lua"; actualRequestAutoGeneration(p); diff --git a/frontend/sequences/__tests__/sequences_test.tsx b/frontend/sequences/__tests__/sequences_test.tsx index 8df7b085a2..e3190c3e8a 100644 --- a/frontend/sequences/__tests__/sequences_test.tsx +++ b/frontend/sequences/__tests__/sequences_test.tsx @@ -33,7 +33,7 @@ beforeEach(() => { data: [ { id: 1, name: "name", description: "", path: "", color: "gray" }, ] - }) as never); + })); }); afterEach(() => { diff --git a/frontend/sequences/locals_list/__tests__/default_value_form_test.tsx b/frontend/sequences/locals_list/__tests__/default_value_form_test.tsx index 0dac0d71ab..0252f786c6 100644 --- a/frontend/sequences/locals_list/__tests__/default_value_form_test.tsx +++ b/frontend/sequences/locals_list/__tests__/default_value_form_test.tsx @@ -31,7 +31,7 @@ describe("", () => { mockVariableFormOnChangeArg.args.label, )} />
; - }) as never); + })); }); afterEach(() => { diff --git a/frontend/sequences/locals_list/__tests__/variable_form_test.tsx b/frontend/sequences/locals_list/__tests__/variable_form_test.tsx index e3f588e5fa..f72023d545 100644 --- a/frontend/sequences/locals_list/__tests__/variable_form_test.tsx +++ b/frontend/sequences/locals_list/__tests__/variable_form_test.tsx @@ -54,7 +54,7 @@ beforeEach(() => { ...e, currentTarget: { ...e.currentTarget, value } - } as React.SyntheticEvent)} /> + })} />
; }; + +export interface CameraStartingLocationButtonProps { + dispatch: Function; +} + +export const CameraStartingLocationButton = + (props: CameraStartingLocationButtonProps) => ; diff --git a/frontend/settings/fbos_settings/farmbot_os_row.tsx b/frontend/settings/fbos_settings/farmbot_os_row.tsx index 9cc20374dd..187baabd9e 100644 --- a/frontend/settings/fbos_settings/farmbot_os_row.tsx +++ b/frontend/settings/fbos_settings/farmbot_os_row.tsx @@ -36,9 +36,9 @@ export const getOsReleaseNotesForVersion = ( }; export const FarmbotOsRow = (props: FarmbotOsRowProps) => { - const [version, setVersion] = React.useState( + const versionRef = React.useRef( props.bot.hardware.informational_settings.controller_version); - const [channel, setChannel] = React.useState( + const channelRef = React.useRef( "" + props.sourceFbosConfig("update_channel").value); const { dispatch, bot, sourceFbosConfig } = props; @@ -53,21 +53,22 @@ export const FarmbotOsRow = (props: FarmbotOsRowProps) => { }, []); React.useEffect(() => { - const versionChange = controller_version && version != controller_version; - const channelChange = configChannel && channel != configChannel; + const versionChange = + controller_version && versionRef.current != controller_version; + const channelChange = configChannel && channelRef.current != configChannel; if (versionChange || channelChange) { - setVersion(controller_version); - setChannel(configChannel); + versionRef.current = controller_version; + channelRef.current = configChannel; dispatch(fetchOsUpdateVersion(target)); } if (versionChange) { removeToast("EOL"); } - }, [dispatch, controller_version, target, configChannel, version, channel]); + }, [dispatch, controller_version, target, configChannel]); const releaseNotes = getOsReleaseNotesForVersion( props.bot.osReleaseNotes, - version || props.device.body.fbos_version); + controller_version || props.device.body.fbos_version); return