diff --git a/package-lock.json b/package-lock.json index 3610bee..2c4d240 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#master", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#bin_player_inventory_changes", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", @@ -146,9 +146,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", "engines": { @@ -156,21 +156,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -233,14 +233,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -482,9 +482,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -507,14 +507,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2481,9 +2481,9 @@ } }, "node_modules/@mediapipe/tasks-vision": { - "version": "0.10.17", - "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz", - "integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==", + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.8.tgz", + "integrity": "sha512-Rp7ll8BHrKB3wXaRFKhrltwZl1CiXGdibPxuWXvqGnKTnv8fqa/nvftYNuSbf+pbJWKYCXdBtYTITdAUTGGh0Q==", "license": "Apache-2.0" }, "node_modules/@mui/core-downloads-tracker": { @@ -3239,27 +3239,28 @@ } }, "node_modules/@react-spring/animated": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.5.tgz", - "integrity": "sha512-Tqrwz7pIlsSDITzxoLS3n/v/YCUHQdOIKtOJf4yL6kYVSDTSmVK1LI1Q3M/uu2Sx4X3pIWF3xLUhlsA6SPNTNg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.6.1.tgz", + "integrity": "sha512-ls/rJBrAqiAYozjLo5EPPLLOb1LM0lNVQcXODTC1SMtS6DbuBCPaKco5svFUQFMP2dso3O+qcC4k9FsKc0KxMQ==", "license": "MIT", "dependencies": { - "@react-spring/shared": "~9.7.5", - "@react-spring/types": "~9.7.5" + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "node_modules/@react-spring/core": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.5.tgz", - "integrity": "sha512-rmEqcxRcu7dWh7MnCcMXLvrf6/SDlSokLaLTxiPlAYi11nN3B5oiCUAblO72o+9z/87j2uzxa2Inm8UbLjXA+w==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.6.1.tgz", + "integrity": "sha512-3HAAinAyCPessyQNNXe5W0OHzRfa8Yo5P748paPcmMowZ/4sMfaZ2ZB6e5x5khQI8NusOHj8nquoutd6FRY5WQ==", "license": "MIT", "dependencies": { - "@react-spring/animated": "~9.7.5", - "@react-spring/shared": "~9.7.5", - "@react-spring/types": "~9.7.5" + "@react-spring/animated": "~9.6.1", + "@react-spring/rafz": "~9.6.1", + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" }, "funding": { "type": "opencollective", @@ -3270,34 +3271,34 @@ } }, "node_modules/@react-spring/rafz": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.5.tgz", - "integrity": "sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.6.1.tgz", + "integrity": "sha512-v6qbgNRpztJFFfSE3e2W1Uz+g8KnIBs6SmzCzcVVF61GdGfGOuBrbjIcp+nUz301awVmREKi4eMQb2Ab2gGgyQ==", "license": "MIT" }, "node_modules/@react-spring/shared": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.5.tgz", - "integrity": "sha512-wdtoJrhUeeyD/PP/zo+np2s1Z820Ohr/BbuVYv+3dVLW7WctoiN7std8rISoYoHpUXtbkpesSKuPIw/6U1w1Pw==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.6.1.tgz", + "integrity": "sha512-PBFBXabxFEuF8enNLkVqMC9h5uLRBo6GQhRMQT/nRTnemVENimgRd+0ZT4yFnAQ0AxWNiJfX3qux+bW2LbG6Bw==", "license": "MIT", "dependencies": { - "@react-spring/rafz": "~9.7.5", - "@react-spring/types": "~9.7.5" + "@react-spring/rafz": "~9.6.1", + "@react-spring/types": "~9.6.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "node_modules/@react-spring/three": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/three/-/three-9.7.5.tgz", - "integrity": "sha512-RxIsCoQfUqOS3POmhVHa1wdWS0wyHAUway73uRLp3GAL5U2iYVNdnzQsep6M2NZ994BlW8TcKuMtQHUqOsy6WA==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/three/-/three-9.6.1.tgz", + "integrity": "sha512-Tyw2YhZPKJAX3t2FcqvpLRb71CyTe1GvT3V+i+xJzfALgpk10uPGdGaQQ5Xrzmok1340DAeg2pR/MCfaW7b8AA==", "license": "MIT", "dependencies": { - "@react-spring/animated": "~9.7.5", - "@react-spring/core": "~9.7.5", - "@react-spring/shared": "~9.7.5", - "@react-spring/types": "~9.7.5" + "@react-spring/animated": "~9.6.1", + "@react-spring/core": "~9.6.1", + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" }, "peerDependencies": { "@react-three/fiber": ">=6.0", @@ -3306,44 +3307,45 @@ } }, "node_modules/@react-spring/types": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.5.tgz", - "integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.6.1.tgz", + "integrity": "sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q==", "license": "MIT" }, "node_modules/@react-three/drei": { - "version": "9.122.0", - "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-9.122.0.tgz", - "integrity": "sha512-SEO/F/rBCTjlLez7WAlpys+iGe9hty4rNgjZvgkQeXFSiwqD4Hbk/wNHMAbdd8vprO2Aj81mihv4dF5bC7D0CA==", + "version": "9.105.6", + "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-9.105.6.tgz", + "integrity": "sha512-JBgYeV36N9N9f1c3o1ZfLYW4rXZA7UQTq32Y8s3DEF6lwj1/y+RP/yq2VG5I8OzUPl7gsmWdy8fpWZgrlAqUpQ==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.26.0", - "@mediapipe/tasks-vision": "0.10.17", - "@monogrid/gainmap-js": "^3.0.6", - "@react-spring/three": "~9.7.5", - "@use-gesture/react": "^10.3.1", - "camera-controls": "^2.9.0", + "@babel/runtime": "^7.11.2", + "@mediapipe/tasks-vision": "0.10.8", + "@monogrid/gainmap-js": "^3.0.5", + "@react-spring/three": "~9.6.1", + "@use-gesture/react": "^10.2.24", + "camera-controls": "^2.4.2", "cross-env": "^7.0.3", - "detect-gpu": "^5.0.56", + "detect-gpu": "^5.0.28", "glsl-noise": "^0.0.0", - "hls.js": "^1.5.17", - "maath": "^0.10.8", - "meshline": "^3.3.1", + "hls.js": "1.3.5", + "maath": "^0.10.7", + "meshline": "^3.1.6", "react-composer": "^5.0.3", - "stats-gl": "^2.2.8", + "stats-gl": "^2.0.0", "stats.js": "^0.17.0", "suspend-react": "^0.1.3", - "three-mesh-bvh": "^0.7.8", - "three-stdlib": "^2.35.6", - "troika-three-text": "^0.52.0", + "three-mesh-bvh": "^0.7.0", + "three-stdlib": "^2.29.9", + "troika-three-text": "^0.49.0", "tunnel-rat": "^0.1.2", - "utility-types": "^3.11.0", - "zustand": "^5.0.1" + "utility-types": "^3.10.0", + "uuid": "^9.0.1", + "zustand": "^3.7.1" }, "peerDependencies": { - "@react-three/fiber": "^8", - "react": "^18", - "react-dom": "^18", + "@react-three/fiber": ">=8.0", + "react": ">=18.0", + "react-dom": ">=18.0", "three": ">=0.137" }, "peerDependenciesMeta": { @@ -3364,35 +3366,6 @@ "three": ">= 0.159.0" } }, - "node_modules/@react-three/drei/node_modules/zustand": { - "version": "5.0.14", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", - "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", - "license": "MIT", - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=18.0.0", - "immer": ">=9.0.6", - "react": ">=18.0.0", - "use-sync-external-store": ">=1.2.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - }, - "use-sync-external-store": { - "optional": true - } - } - }, "node_modules/@react-three/fiber": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.18.0.tgz", @@ -7013,9 +6986,9 @@ } }, "node_modules/axios": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", - "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.17.0.tgz", + "integrity": "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.16.0", @@ -7025,16 +6998,16 @@ } }, "node_modules/axios/node_modules/form-data": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", - "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.4", - "mime-types": "^2.1.35" + "hasown": "^2.0.2", + "mime-types": "^2.1.12" }, "engines": { "node": ">= 6" @@ -8077,9 +8050,9 @@ } }, "node_modules/deeks": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/deeks/-/deeks-3.2.1.tgz", - "integrity": "sha512-D/o0k3pCG1aI1cxb/dDiWmtMc4Rh7ZQBybXpfMsw9Rbtqwg8kUA9SpYkWcw0pAUjZSnPm8MluctiS0o68r69jQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/deeks/-/deeks-3.1.0.tgz", + "integrity": "sha512-e7oWH1LzIdv/prMQ7pmlDlaVoL64glqzvNgkgQNgyec9ORPHrT2jaOqMtRyqJuwWjtfb6v+2rk9pmaHj+F137A==", "license": "MIT", "engines": { "node": ">= 16" @@ -8209,9 +8182,9 @@ "license": "MIT" }, "node_modules/doc-path": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/doc-path/-/doc-path-4.1.4.tgz", - "integrity": "sha512-yw5D++UCIB6a033PvQaUvSpW2QuKW0+DOId763n0Q4z3brxS7G8oQr8yBQ1nQFkognKrAVrV6I55TLeU9cfXTg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/doc-path/-/doc-path-4.1.1.tgz", + "integrity": "sha512-h1ErTglQAVv2gCnOpD3sFS6uolDbOKHDU1BZq+Kl3npPqroU3dYL42lUgMfd5UimlwtRgp7C9dLGwqQ5D2HYgQ==", "license": "MIT", "engines": { "node": ">=16" @@ -8384,25 +8357,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-abstract-get": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", - "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.2", - "is-callable": "^1.2.7", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-cookie": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.3.2.tgz", @@ -8462,18 +8416,15 @@ } }, "node_modules/es-to-primitive": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", - "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, "license": "MIT", "dependencies": { - "es-abstract-get": "^1.0.0", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", "is-callable": "^1.2.7", - "is-date-object": "^1.1.0", - "is-symbol": "^1.1.1" + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { "node": ">= 0.4" @@ -8813,9 +8764,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", - "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "dev": true, "funding": [ { @@ -9026,15 +8977,15 @@ } }, "node_modules/form-data": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.5.tgz", - "integrity": "sha512-j23EibVLnp4zNXGW7LjryXYa2X6U/M96yoOX+ybZxwkYajdxRNEqYY3zhh7y0i6kfISKS2jr+EJq1YTUDEv5+w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", + "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.4", + "hasown": "^2.0.2", "mime-types": "^2.1.35" }, "engines": { @@ -9109,21 +9060,18 @@ } }, "node_modules/function.prototype.name": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", - "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2", - "hasown": "^2.0.4", - "is-callable": "^1.2.7", - "is-document.all": "^1.0.0" + "hasown": "^2.0.2", + "is-callable": "^1.2.7" }, "engines": { "node": ">= 0.4" @@ -9368,9 +9316,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -9587,9 +9535,9 @@ } }, "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -9599,9 +9547,9 @@ } }, "node_modules/hls.js": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.16.tgz", - "integrity": "sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.3.5.tgz", + "integrity": "sha512-uybAvKS6uDe0MnWNEPnO0krWVr+8m2R0hJ/viql8H3MVK+itq8gGQuIYoFHL3rECkIpNH98Lw8YuuWMKZxp3Ew==", "license": "Apache-2.0" }, "node_modules/hoist-non-react-statics": { @@ -9965,22 +9913,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-document.all": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", - "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -10424,20 +10356,10 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", - "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -10459,13 +10381,13 @@ } }, "node_modules/json-2-csv": { - "version": "5.5.11", - "resolved": "https://registry.npmjs.org/json-2-csv/-/json-2-csv-5.5.11.tgz", - "integrity": "sha512-kVuwgVL7rfad9ETf02ZZxJPuMR5ZSUn139+T34BfmVxYhb/IsAIm/LzEeQ8YLJmXfuQ5z7LUAFrgPZZM6VLJPw==", + "version": "5.5.10", + "resolved": "https://registry.npmjs.org/json-2-csv/-/json-2-csv-5.5.10.tgz", + "integrity": "sha512-Dep8wO3Fr5wNjQevO2Z8Y7yeee/nYSGRsi7q6zJDKEVHxXkXT+v21vxHmDX923UzmCXXkSo62HaTz6eTWzFLaw==", "license": "MIT", "dependencies": { - "deeks": "3.2.1", - "doc-path": "4.1.4" + "deeks": "3.1.0", + "doc-path": "4.1.1" }, "engines": { "node": ">= 16" @@ -11709,9 +11631,9 @@ } }, "node_modules/postcss": { - "version": "8.5.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", - "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -11744,9 +11666,9 @@ "license": "MIT" }, "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.15", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", - "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -11830,7 +11752,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#d6a992e46192a8ab8195235357318dc297738064", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#4bd44bd7e19e7e58344c224b3413a81ece379fed", "dependencies": { "protobufjs": "^6.8.8" } @@ -12981,9 +12903,9 @@ } }, "node_modules/serialize-javascript": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.7.tgz", - "integrity": "sha512-YAy8Od6KV+uuwUuU50np8fGB/Aues6Y0nAhA9y/hId74PlKUcme4pXcBD46NWKr1Q4osN/iseZ17YqO1XfmI8g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -13759,10 +13681,9 @@ "license": "MIT" }, "node_modules/three-mesh-bvh": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.7.8.tgz", - "integrity": "sha512-BGEZTOIC14U0XIRw3tO4jY7IjP7n7v24nv9JXS1CyeVRWOCkcOMhRnmENUjuV39gktAw4Ofhr0OvIAiTspQrrw==", - "deprecated": "Deprecated due to three.js version incompatibility. Please use v0.8.0, instead.", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.7.6.tgz", + "integrity": "sha512-rCjsnxEqR9r1/C/lCqzGLS67NDty/S/eT6rAJfDvsanrIctTWdNoR4ZOGWewCB13h1QkVo2BpmC0wakj1+0m8A==", "license": "MIT", "peerDependencies": { "three": ">= 0.151.0" @@ -13940,14 +13861,14 @@ } }, "node_modules/troika-three-text": { - "version": "0.52.4", - "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.4.tgz", - "integrity": "sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==", + "version": "0.49.1", + "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.49.1.tgz", + "integrity": "sha512-lXGWxgjJP9kw4i4Wh+0k0Q/7cRfS6iOME4knKht/KozPu9GcFA9NnNpRvehIhrUawq9B0ZRw+0oiFHgRO+4Wig==", "license": "MIT", "dependencies": { "bidi-js": "^1.0.2", - "troika-three-utils": "^0.52.4", - "troika-worker-utils": "^0.52.0", + "troika-three-utils": "^0.49.0", + "troika-worker-utils": "^0.49.0", "webgl-sdf-generator": "1.1.1" }, "peerDependencies": { @@ -13955,18 +13876,18 @@ } }, "node_modules/troika-three-utils": { - "version": "0.52.4", - "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.52.4.tgz", - "integrity": "sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==", + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.49.0.tgz", + "integrity": "sha512-umitFL4cT+Fm/uONmaQEq4oZlyRHWwVClaS6ZrdcueRvwc2w+cpNQ47LlJKJswpqtMFWbEhOLy0TekmcPZOdYA==", "license": "MIT", "peerDependencies": { "three": ">=0.125.0" } }, "node_modules/troika-worker-utils": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.52.0.tgz", - "integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==", + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.49.0.tgz", + "integrity": "sha512-1xZHoJrG0HFfCvT/iyN41DvI/nRykiBtHqFkGaGgJwq5iXfIZFBiPPEHFpPpgyKM3Oo5ITHXP5wM2TNQszYdVg==", "license": "MIT" }, "node_modules/ts-api-utils": { @@ -14420,6 +14341,19 @@ "node": ">= 4" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", diff --git a/package.json b/package.json index 76ac48b..e86d5f5 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#master", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#bin_player_inventory_changes", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", diff --git a/src/app/Header.tsx b/src/app/Header.tsx index 5ff6184..6e4dbaf 100644 --- a/src/app/Header.tsx +++ b/src/app/Header.tsx @@ -16,7 +16,7 @@ const useStyles = makeStyles((theme: Theme) => { appBar: { zIndex: theme.zIndex.drawer + 1, backgroundImage: "none", // This prevents de-saturation of header in dark mode - backgroundColor: theme.palette.header.main + backgroundColor: theme.palette.mode === "light" ? "#3b3b3b" : "#272727" }, toolbar: { marginLeft: theme.spacing(0.5), @@ -128,4 +128,4 @@ export default function Header() { {isMobile && {setNavOpen(isOpen)}} sideIsOpen={navOpen} />} ) -} +} \ No newline at end of file diff --git a/src/chat/ChatOutput.tsx b/src/chat/ChatOutput.tsx index 765809e..198690b 100644 --- a/src/chat/ChatOutput.tsx +++ b/src/chat/ChatOutput.tsx @@ -24,11 +24,11 @@ export default function ChatOutput(props: Props) { const ScrollToBottom = () => { const elementRef = useRef(null); - useEffect(() => { + useEffect(() => elementRef?.current?.scrollIntoView({ block: "end" - }); - }); + }) + ); let elem =
; return elem; }; diff --git a/src/common/PendingChangesChip.tsx b/src/common/PendingChangesChip.tsx deleted file mode 100644 index f2b393a..0000000 --- a/src/common/PendingChangesChip.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { Chip, CircularProgress, Tooltip } from "@mui/material"; -import { CheckCircleOutline } from "@mui/icons-material"; - -interface Props { - pending: boolean; - accepted: boolean; - size?: "small" | "medium"; -} - -/** - * Capsule tag shown alongside the other status chips while changes are - * waiting to be synced to a device or component. Shows a small loading - * circle while pending and a checkmark once the change is accepted. - * Derive the flags with usePendingChanges. - */ -export default function PendingChangesChip(props: Props) { - const { pending, accepted, size } = props; - - if (pending) { - return ( - - } - size={size} - /> - - ); - } - - if (accepted) { - return ( - - } - size={size} - /> - - ); - } - - return null; -} diff --git a/src/common/PendingChangesIndicator.tsx b/src/common/PendingChangesIndicator.tsx deleted file mode 100644 index 2d3ff59..0000000 --- a/src/common/PendingChangesIndicator.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { CircularProgress, Grid2 as Grid } from "@mui/material"; -import { CheckCircleOutline } from "@mui/icons-material"; - -interface Props { - pending: boolean; - accepted: boolean; - text: string; -} - -/** - * Small inline status line used inside cards: a loading circle beside the - * caption while changes are pending, and a checkmark once they are accepted. - * Derive the flags with usePendingChanges (or per-item tracking like - * InteractionsOverview does). - */ -export default function PendingChangesIndicator(props: Props) { - const { pending, accepted, text } = props; - if (!pending && !accepted) return null; - - return ( - - {pending && } - {accepted && } - {text} - - ); -} diff --git a/src/common/StatusChip.tsx b/src/common/StatusChip.tsx new file mode 100644 index 0000000..c1b16b3 --- /dev/null +++ b/src/common/StatusChip.tsx @@ -0,0 +1,20 @@ +import { Chip, Tooltip } from "@mui/material"; +import { getStatusHelper } from "pbHelpers/Status"; + +interface Props { + status: string; + size?: "small" | "medium"; +} + +export default function StatusChip(props: Props) { + const { status, size } = props; + const statusHelper = getStatusHelper(status); + const icon = statusHelper.icon; + const description = statusHelper.description; + + return icon !== null ? ( + + + + ) : null; +} diff --git a/src/component/ComponentCard.tsx b/src/component/ComponentCard.tsx index 8b9b767..8355abe 100644 --- a/src/component/ComponentCard.tsx +++ b/src/component/ComponentCard.tsx @@ -13,13 +13,11 @@ import { } from "@mui/material"; import Grid from '@mui/material/Grid2'; import EventBlocker from "common/EventBlocker"; -import PendingChangesIndicator from "common/PendingChangesIndicator"; import MeasurementSummary from "component/MeasurementSummary"; -import { useComponentAPI, usePendingChanges, useSnackbar, useThemeType } from "hooks"; +import { useComponentAPI, useSnackbar, useThemeType } from "hooks"; import InteractionsOverview from "interactions/InteractionsOverview"; import { cloneDeep } from "lodash"; import { Component, Device, Interaction } from "models"; -import moment from "moment"; import { getFriendlyAddressTypeName, getHumanReadableAddress } from "pbHelpers/AddressType"; import { controllerModeLabel } from "pbHelpers/Component"; import { @@ -123,9 +121,6 @@ export default function ComponentCard(props: Props) { const location = useLocation() const [sensors, setSensors] = useState([]); const [{ user, showErrors, as }] = useGlobalState(); - const { pending: pendingChanges, accepted: changesAccepted } = usePendingChanges( - component.status.synced - ); const updateControllerState = (checked: boolean) => { let updatedComponent = cloneDeep(component); @@ -138,8 +133,6 @@ export default function ComponentCard(props: Props) { .update(device.id(), updatedComponent.settings, undefined, undefined, as) .then(() => { success(component.name() + "'s mode was set to " + describe); - updatedComponent.status.synced = false; - updatedComponent.status.lastUpdate = moment().toISOString(); refreshCallback(updatedComponent); }) .catch(() => { @@ -294,34 +287,19 @@ export default function ComponentCard(props: Props) { className={classes.cardHeader} titleTypographyProps={{ variant: "subtitle1" }} subheader={ - - {(pendingChanges || changesAccepted) && ( - - - - )} - {/* !newStructure ? ( - - ) : ( */} + // !newStructure ? ( + // + // ) : ( - {/* ) */} - + // ) } action={ diff --git a/src/component/ComponentForm.tsx b/src/component/ComponentForm.tsx index 362b0ae..17c1bf7 100644 --- a/src/component/ComponentForm.tsx +++ b/src/component/ComponentForm.tsx @@ -19,10 +19,9 @@ import { Tooltip, Typography } from "@mui/material"; -import { +import { ExpandMore, - Close as CloseIcon, - Remove as RemoveIcon + Close as CloseIcon } from "@mui/icons-material"; import PeriodSelect from "common/time/PeriodSelect"; import SearchSelect, { Option } from "common/SearchSelect"; @@ -98,7 +97,6 @@ interface Props { component: Component; componentChanged: (component: Component, formValid: boolean) => void; canEdit: boolean; - hideControllerFields?: boolean; } interface ComponentData { @@ -121,7 +119,7 @@ export default function ComponentForm(props: Props) { const classes = useStyles(); const grainOptions = GrainOptions(); const [{ user }] = useGlobalState(); - const { device, component, componentChanged, canEdit, hideControllerFields } = props; + const { device, component, componentChanged, canEdit } = props; const [reportExpanded, setReportExpanded] = useState(false); const [dataUsageWarningDismissed, setDataUsageWarningDismissed] = useState(true); const [form, setForm] = useState({ @@ -139,8 +137,7 @@ export default function ComponentForm(props: Props) { sensorDistance: "0", }); const [compMode, setCompMode] = useState(); - const [useCustomGrain, setUseCustomGrain] = useState(false); - const [controlEmail, setControlEmail] = useState(""); + const [useCustomGrain, setUseCustomGrain] = useState(false) //const [numCalibrations, setNumCalibrations] = useState(0) useEffect(() => { @@ -359,21 +356,6 @@ export default function ComponentForm(props: Props) { setForm(f); }; - const addControlEmail = () => { - const trimmed = controlEmail.trim().toLowerCase(); - if (trimmed === "" || form.component.settings.controlEmails.includes(trimmed)) return; - let f = cloneDeep(form); - f.component.settings.controlEmails.push(trimmed); - setForm(f); - setControlEmail(""); - }; - - const removeControlEmail = (index: number) => { - let f = cloneDeep(form); - f.component.settings.controlEmails.splice(index, 1); - setForm(f); - }; - const toggleMeasure = (event: any) => { let f = cloneDeep(form); f.measure = event.target.checked; @@ -457,29 +439,11 @@ export default function ComponentForm(props: Props) { setForm(f); }; - const modeDictionaryValue = (dictionary: any, key: string): number | undefined => { - if (!Array.isArray(dictionary)) { - return undefined; - } - let entry = dictionary.find((elem: any) => elem.key === key); - return typeof entry?.value === "number" ? entry.value : undefined; - }; - const isCoefficientValid = () => { let calibrate = form.component.settings.calibrate; let coefficient = Number(form.coefficient); - if (isNaN(coefficient)) { - return false; - } - if (!calibrate) { - return true; - } - if (compMode && compMode.mode.entryA === "number") { - let minimum = modeDictionaryValue(compMode.mode.dictionaryA, "min") ?? 0; - let maximum = modeDictionaryValue(compMode.mode.dictionaryA, "max") ?? 100; - return coefficient >= minimum && coefficient <= maximum; - } - return coefficient > 0; + let valid = !isNaN(coefficient) && (!calibrate || (calibrate && coefficient > 0)); + return valid; }; const changeCoefficient = (event: any) => { @@ -503,14 +467,10 @@ export default function ComponentForm(props: Props) { }; const isOffsetValid = (min?: number, max?: number) => { - const minimum = min ?? modeDictionaryValue(compMode?.mode.dictionaryB, "min") ?? 0; - const maximum = max ?? modeDictionaryValue(compMode?.mode.dictionaryB, "max") ?? 100; + let minimum = min ?? 0; + let maximum = max ?? 100; if (compMode && form.component.settings.calibrate) { - const offset = Number(form.offset); - const meetsMinimum = compMode.mode.exclusiveMinimumB - ? offset > minimum - : offset >= minimum; - return !isNaN(offset) && offset <= maximum && meetsMinimum; + return Number(form.offset) <= maximum && Number(form.offset) >= minimum; } return !isNaN(Number(form.offset)); }; @@ -854,11 +814,6 @@ export default function ComponentForm(props: Props) { onFocus={selectText} margin="normal" fullWidth - inputProps={ - compMode.mode.stepB !== undefined - ? { step: compMode.mode.stepB } - : undefined - } type={compMode.mode.entryB} variant="outlined"> {compMode.mode.entryB === "select" && @@ -1386,7 +1341,7 @@ export default function ComponentForm(props: Props) { }} /> )} - {isController(component.settings.type) && !hideControllerFields && ( + {isController(component.settings.type) && ( - - - Authorized Control Emails - - - - setControlEmail(e.target.value)} - onKeyDown={e => { - if (e.key === "Enter") { - e.preventDefault(); - addControlEmail(); - } - }} - disabled={!canEdit} - /> - - - - - - {component.settings.controlEmails.map((email, i) => ( - - - {email} - - - removeControlEmail(i)} - disabled={!canEdit} - className={classes.redButton}> - - - - - ))} - )} {showDataUsageWarning && ( diff --git a/src/component/ComponentMode.json b/src/component/ComponentMode.json index 4443d1f..2c66a6e 100644 --- a/src/component/ComponentMode.json +++ b/src/component/ComponentMode.json @@ -110,7 +110,7 @@ "dictionaryA": [ { "key": "min", - "value": 0, + "value": 1, "restricted": false }, { @@ -121,12 +121,10 @@ ], "labelB": "To Node Id", "entryB": "number", - "exclusiveMinimumB": true, - "stepB": "any", "dictionaryB": [ { "key": "min", - "value": 0, + "value": 1, "restricted": false }, { diff --git a/src/component/ComponentSettings.tsx b/src/component/ComponentSettings.tsx index ce56e56..db0e5ad 100644 --- a/src/component/ComponentSettings.tsx +++ b/src/component/ComponentSettings.tsx @@ -8,7 +8,6 @@ import { DialogContentText, DialogTitle, Divider, - FormControl, FormControlLabel, Grid2 as Grid, IconButton, @@ -42,10 +41,8 @@ import { GetComponentTypeOptions, getFriendlyName, getMeasurements, - isController, + isController } from "pbHelpers/ComponentType"; -import PeriodSelect from "common/time/PeriodSelect"; -import { bestUnit, milliToX } from "common/time/duration"; import { ComponentAvailabilityMap, DeviceAvailabilityMap, @@ -180,7 +177,6 @@ export default function ComponentSettings(props: Props) { const [formValid, setFormValid] = useState(false); const [excludedNodes, setExcludedNodes] = useState([]); const [nodeToExclude, setNodeToExclude] = useState(0); - const [controlEmail, setControlEmail] = useState(""); const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => { setTabVal(newValue); @@ -213,13 +209,14 @@ export default function ComponentSettings(props: Props) { ) ); setFormComponent(initComponent); - setExcludedNodes(cloneDeep(initComponent.settings.excludedNodes)); + setExcludedNodes(initComponent.settings.excludedNodes) }, [props.component, componentTypeOptions]); useEffect(() => { - if (!isDialogOpen || prevIsDialogOpen) return; - init(); - }, [init, isDialogOpen, prevIsDialogOpen]); + if (props.component !== prevComponent || (!isDialogOpen && prevIsDialogOpen)) { + init(); + } + }, [props.component, prevComponent, init, isDialogOpen, prevIsDialogOpen]); const close = () => { closeDialogCallback(); @@ -501,7 +498,6 @@ export default function ComponentSettings(props: Props) { component={formComponent} device={device} canEdit={canEdit} - hideControllerFields={isController(formComponent.settings.type)} componentChanged={(formComp, isValid) => { formComponent.settings = formComp.settings; setFormValid(isValid); @@ -511,7 +507,7 @@ export default function ComponentSettings(props: Props) { ); }; - const hasCableID = () => { + const isCableComponent = () => { return (formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE || formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE || @@ -523,9 +519,7 @@ export default function ComponentSettings(props: Props) { const supportsExpansion = () => { return ((formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE || - formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE || - formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE - ) && + formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE) && formComponent.settings.addressType === quack.AddressType.ADDRESS_TYPE_I2C) } @@ -583,8 +577,7 @@ export default function ComponentSettings(props: Props) { )} {supportsExpansion() && setExpLine()} - - {(hasCableID() && !supportsExpansion())&& setComponentAddrType()} + {(isCableComponent() && !supportsExpansion())&& setComponentAddrType()} ) : activeStep === 1 ? ( { - const trimmed = controlEmail.trim().toLowerCase(); - if (trimmed === "" || formComponent.settings.controlEmails.includes(trimmed)) return; - let f = cloneDeep(formComponent); - f.settings.controlEmails.push(trimmed); - setFormComponent(f); - setControlEmail(""); - }; - - const removeControlEmail = (index: number) => { - let f = cloneDeep(formComponent); - f.settings.controlEmails.splice(index, 1); - setFormComponent(f); - }; - - const handleOutputModeChanged = (event: any) => { - let f = cloneDeep(formComponent); - f.settings.defaultOutputState = event.target.value; - setFormComponent(f); - }; - - const handleMinCycleTimeChanged = (ms: number) => { - let f = cloneDeep(formComponent); - f.settings.minCycleTimeMs = Number(ms); - setFormComponent(f); - }; - - const isMinCycleTimeValid = () => { - const ext = extension(formComponent.settings.type, formComponent.settings.subtype); - if (!ext.isController) return true; - let min = Math.max(0, ext.minCycleTimeMs ? ext.minCycleTimeMs : 0); - return formComponent.settings.minCycleTimeMs >= min; - }; - - const minCycleTimeDescription = () => { - const ext = extension(formComponent.settings.type, formComponent.settings.subtype); - if (!ext.isController) return ""; - const min = Math.max(0, ext.minCycleTimeMs ? ext.minCycleTimeMs : 0); - const unit = bestUnit(min); - const value = milliToX(min, unit).toString(); - return "Must be at least " + value + " " + unit; - }; - - const controlContent = () => { - return ( - - - Auto - On - Off - - - - - - Authorized Control Emails - - - - setControlEmail(e.target.value)} - onKeyDown={e => { - if (e.key === "Enter") { - e.preventDefault(); - addControlEmail(); - } - }} - disabled={!canEdit} - /> - - - - - - {formComponent.settings.controlEmails.map((email, i) => ( - - - {email} - - - removeControlEmail(i)} - disabled={!canEdit} - className={classNames(classes.redButton)}> - - - - - ))} - - - Email Control - - - Authorized emails can control this component by sending an email to the - control address. Contact your administrator for the control email - address. Use one of the following as the email subject: - - -
{device.id()}:{formComponent.settings.type}-{formComponent.settings.addressType}-{formComponent.settings.address} - ON
-
{device.id()}:{formComponent.settings.type}-{formComponent.settings.addressType}-{formComponent.settings.address} - OFF
-
{device.id()}:{formComponent.settings.type}-{formComponent.settings.addressType}-{formComponent.settings.address} - AUTO
-
-
- ); - }; - const actions = () => { return ( @@ -1210,55 +1042,36 @@ export default function ComponentSettings(props: Props) { scroll="paper"> {title()} - {isController(formComponent.settings.type) ? ( - - - - - - - {content()} - - - {controlContent()} - - - ) : ( - - - - - - - - {content()} - - - - - {overlayGroup()} - - - - - {componentPrefs()} - - + {!isController(formComponent.settings.type) && ( + + + + + )} + + {content()} + + + + + {overlayGroup()} + + + + + {componentPrefs()} + {actions()} )} diff --git a/src/device/DeviceOverview.tsx b/src/device/DeviceOverview.tsx index ac20e89..0115568 100644 --- a/src/device/DeviceOverview.tsx +++ b/src/device/DeviceOverview.tsx @@ -1,9 +1,9 @@ import { Chip, Grid2 as Grid, Theme, Tooltip, Skeleton } from "@mui/material"; import { DataUsage, SimCard, Launch, DeviceHub } from "@mui/icons-material"; -import PendingChangesChip from "common/PendingChangesChip"; +import StatusChip from "common/StatusChip"; import DeviceTags from "device/DeviceTags"; import VersionChip from "device/VersionChip"; -import { useSnackbar, useWidth, usePendingChanges, usePrevious } from "hooks"; +import { useSnackbar, useWidth, usePrevious } from "hooks"; import { Device, latestFirmwareVersion, Component } from "models"; import moment from "moment"; import { describeConnectivity } from "pbHelpers/Connectivity"; @@ -54,10 +54,6 @@ export default function DeviceOverview(props: Props) { // const match = useRouteMatch(); const [powerComponent, setPowerComponent] = useState(); const [modemComponent, setModemComponent] = useState(); - const { pending: pendingChanges, accepted: changesAccepted } = usePendingChanges( - device.status.synced, - !loading && device.id() > 0 - ); // const [hologramDialog, setHologramDialog] = useState(false); useEffect(() => { @@ -232,9 +228,9 @@ export default function DeviceOverview(props: Props) { )} - {(pendingChanges || changesAccepted) && ( + {!device.status.synced && ( - + )} {device.settings.needsBusControl && ( diff --git a/src/device/DeviceSettings.tsx b/src/device/DeviceSettings.tsx index 32831f5..a9277f5 100644 --- a/src/device/DeviceSettings.tsx +++ b/src/device/DeviceSettings.tsx @@ -31,7 +31,6 @@ import LinearMutationBuilder from "common/LinearMutationBuilder"; import { makeStyles } from "@mui/styles"; import { useNavigate } from "react-router-dom"; import CancelSubmit from "common/CancelSubmit"; -import { cloneDeep } from "lodash"; interface TabPanelProps { children?: React.ReactNode; @@ -95,7 +94,7 @@ export default function DeviceSettings(props: Props) { const { success, error } = useSnackbar(); const deviceAPI = useDeviceAPI(); const { device, isDialogOpen, closeDialogCallback, canEdit, refreshCallback, components } = props; - const prevIsDialogOpen = usePrevious(isDialogOpen); + const prevDevice = usePrevious(device); const [deviceForm, setDeviceForm] = useState(deviceFromForm(Device.clone(device))); const [isRemoveDeviceDialogOpen, setIsRemoveDeviceDialogOpen] = useState(false); const [sleeps, setSleeps] = useState(device.settings.sleepDurationS > 0); @@ -103,9 +102,7 @@ export default function DeviceSettings(props: Props) { const [compExtTwo, setCompExtTwo] = useState(""); const [compExtThree, setCompExtThree] = useState(""); const [currentTab, setCurrentTab] = useState(0); - const [linearMutations, setLinearMutations] = useState( - cloneDeep(device.settings.mutations) - ); + const [linearMutations, setLinearMutations] = useState([]); const [componentsByDevice, setComponentsByDevice] = useState>( new Map() ); @@ -113,22 +110,27 @@ export default function DeviceSettings(props: Props) { const [existingMutation, setExistingMutation] = useState(); useEffect(() => { - if (!isDialogOpen || prevIsDialogOpen) return; - - const settingsForm = deviceFromForm(Device.clone(device)); - setDeviceForm(settingsForm); - setSleeps(settingsForm.settings.sleepDurationS > 0); - setCompExtOne(settingsForm.settings.extensionComponents[0] ?? ""); - setCompExtTwo(settingsForm.settings.extensionComponents[1] ?? ""); - setCompExtThree(settingsForm.settings.extensionComponents[2] ?? ""); - setLinearMutations(cloneDeep(settingsForm.settings.mutations)); - - if (components) { - let cbd = new Map(); - cbd.set(settingsForm.id().toString(), components); - setComponentsByDevice(cbd); + if (prevDevice !== device) { + setDeviceForm(deviceFromForm(Device.clone(props.device))); + if (device.settings.extensionComponents[0]) { + setCompExtOne(device.settings.extensionComponents[0]); + } + if (device.settings.extensionComponents[1]) { + setCompExtTwo(device.settings.extensionComponents[1]); + } + if (device.settings.extensionComponents[2]) { + setCompExtThree(device.settings.extensionComponents[2]); + } + if (device.settings.mutations) { + setLinearMutations(device.settings.mutations); + } + if (components) { + let cbd = new Map(); + cbd.set(device.id().toString(), components); + setComponentsByDevice(cbd); + } } - }, [device, components, isDialogOpen, prevIsDialogOpen]); + }, [device, prevDevice, props.device, components]); const close = () => { closeDialogCallback(); @@ -143,7 +145,7 @@ export default function DeviceSettings(props: Props) { const minCheckPeriodS = (): number => { let defaultPeriod = 60; - switch (deviceForm.settings.platform) { + switch (device.settings.platform) { case pond.DevicePlatform.DEVICE_PLATFORM_ELECTRON: return user.hasFeature("admin") ? defaultPeriod : 300; default: @@ -315,7 +317,7 @@ export default function DeviceSettings(props: Props) { id="name" name="name" label="Name" - value={deviceForm.settings.name} + defaultValue={deviceForm.settings.name} onChange={changeName} margin="normal" variant="outlined" @@ -328,7 +330,7 @@ export default function DeviceSettings(props: Props) { id="description" name="description" label="Description" - value={deviceForm.settings.description} + defaultValue={deviceForm.settings.description} onChange={changeDescription} multiline rows={2} @@ -546,8 +548,8 @@ export default function DeviceSettings(props: Props) { const mutationsContent = () => { let mutations: JSX.Element[] = []; - if (linearMutations) { - linearMutations.forEach((mut, i) => { + if (device.settings.mutations) { + device.settings.mutations.forEach((mut, i) => { mutations.push( { - setLinearMutations(linearMutations.filter((_, index) => index !== i)); + let lm = linearMutations; + lm.splice(i, 1); + setLinearMutations([...lm]); }}> Remove @@ -690,7 +694,9 @@ export default function DeviceSettings(props: Props) { setNewMutationDialog(false); }} onSubmit={newMutation => { - setLinearMutations([...linearMutations, newMutation]); + let lm = linearMutations; + lm.push(newMutation); + setLinearMutations(lm); }} /> diff --git a/src/device/VersionChip.tsx b/src/device/VersionChip.tsx index 8826239..09dac0f 100644 --- a/src/device/VersionChip.tsx +++ b/src/device/VersionChip.tsx @@ -22,14 +22,6 @@ class VersionChip extends React.Component { variant="outlined" label={firmwareVersionHelper.description} icon={firmwareVersionHelper.icon} - sx={ - firmwareVersionHelper.colour - ? { - borderColor: firmwareVersionHelper.colour, - color: firmwareVersionHelper.colour - } - : undefined - } /> ); diff --git a/src/device/autoDetect/DeviceScannedComponents.tsx b/src/device/autoDetect/DeviceScannedComponents.tsx index 4cb94e1..95c3759 100644 --- a/src/device/autoDetect/DeviceScannedComponents.tsx +++ b/src/device/autoDetect/DeviceScannedComponents.tsx @@ -75,6 +75,7 @@ export default function DeviceScannedComponents(props: Props){ let expanders: quack.AddressData[] = [] //filter the address data let addr = scannedI2C?.settings?.foundAddresses + console.log(addr) if(addr){ addr.forEach(addrData => { if(!i2cBlacklistAddresses.includes(addrData.address)){ @@ -86,6 +87,7 @@ export default function DeviceScannedComponents(props: Props){ } }) } + console.log(valid) setExpanderAddresses(expanders) setValidI2CComponentAddresses(valid) },[scannedI2C]) @@ -211,13 +213,14 @@ export default function DeviceScannedComponents(props: Props){ }) }) } + console.log(cloneList) setComponents(cloneList) } const removeScan = (key: string, type: pond.ObjectType) => { deviceAPI.removeFoundComponents(device.settings.deviceId, key, type) .then(resp => { - // console.log("Cleared this scan") + console.log("Cleared this scan") }).catch(err => { console.log("There was a problem clearing this scan") }) @@ -226,7 +229,7 @@ export default function DeviceScannedComponents(props: Props){ const removeAllScans = () => { deviceAPI.removeAllFoundComponents(device.settings.deviceId) .then(resp => { - // console.log("Cleared all scans") + console.log("Cleared all scans") refreshCallback() }).catch(err => { console.log("There was a problem clearing scans") diff --git a/src/hooks/index.ts b/src/hooks/index.ts index e93b9d5..0bd5baf 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -26,7 +26,6 @@ export { export * from "./useDebounce"; // export * from "./useForceUpdate"; // export * from "./useInterval"; -export * from "./usePendingChanges"; export * from "./usePrevious"; export * from "./useThemeType"; export * from "./useWidth"; diff --git a/src/hooks/usePendingChanges.ts b/src/hooks/usePendingChanges.ts deleted file mode 100644 index 5852adb..0000000 --- a/src/hooks/usePendingChanges.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useEffect, useRef, useState } from "react"; - -/** - * Tracks a synced flag and reports whether changes are pending or were - * accepted while mounted (a false -> true transition, e.g. from a websocket - * status update). Pass enabled=false until real data has loaded so the - * initial placeholder -> loaded flip is not mistaken for an accepted change. - */ -export function usePendingChanges(synced: boolean, enabled: boolean = true) { - const prevSynced = useRef(undefined); - const [accepted, setAccepted] = useState(false); - - useEffect(() => { - if (!enabled) { - prevSynced.current = undefined; - setAccepted(false); - return; - } - if (prevSynced.current === false && synced) { - setAccepted(true); - } else if (!synced) { - setAccepted(false); - } - prevSynced.current = synced; - }, [synced, enabled]); - - return { - pending: enabled && !synced, - accepted: enabled && synced && accepted - }; -} diff --git a/src/interactions/InteractionSettings.tsx b/src/interactions/InteractionSettings.tsx index f1f3d8c..4f2a2d3 100644 --- a/src/interactions/InteractionSettings.tsx +++ b/src/interactions/InteractionSettings.tsx @@ -32,6 +32,7 @@ import moment from "moment-timezone"; import { componentIDToString, emptyComponentId, + getComponentIDString, sameComponentID, stringToComponentId } from "pbHelpers/Component"; @@ -129,7 +130,9 @@ export default function InteractionSettings(props: Props) { } = props; const theme = useTheme(); const { success, error } = useSnackbar(); - const prevIsDialogOpen = usePrevious(isDialogOpen); + const prevInitialInteraction = usePrevious(initialInteraction); + const prevComponents = usePrevious(components); + const prevInitialComponent = usePrevious(initialComponent); const classes = useStyles(); const [{ user, as }] = useGlobalState(); const interactionsAPI = useInteractionsAPI(); @@ -163,7 +166,7 @@ export default function InteractionSettings(props: Props) { const setDefaultState = useCallback(() => { let interaction = getDefaultInteraction(); if (initialInteraction && mode === "update") { - interaction = Interaction.clone(initialInteraction); + interaction = initialInteraction; if (interaction.settings.subtype === 0 || interaction.settings.subtype === 1) { setSubtypeDropdown(interaction.settings.subtype); } else { @@ -219,7 +222,7 @@ export default function InteractionSettings(props: Props) { setDutyCycle(interactionResult.dutyCycle.toString()); setMappedComponents(mappedComponents); setInteraction(interaction); - }, [components, initialComponent, initialConditions, initialInteraction, mode, sensor, user]); + }, [components, initialComponent, initialConditions, initialInteraction, mode, /*sensor*/]); const availableSources = () => { return components.filter(component => isSource(component.settings.type)); @@ -229,12 +232,23 @@ export default function InteractionSettings(props: Props) { if (user && user.settings.timezone) { setTimezone(user.settings.timezone); } - }, [user]); - - useEffect(() => { - if (!isDialogOpen || prevIsDialogOpen) return; - setDefaultState(); - }, [isDialogOpen, prevIsDialogOpen, setDefaultState]); + if ( + prevInitialInteraction !== initialInteraction || + (prevComponents && prevComponents.length !== components.length) || + getComponentIDString(prevInitialComponent) !== getComponentIDString(initialComponent) + ) { + setDefaultState(); + } + }, [ + components.length, + initialComponent, + initialInteraction, + prevComponents, + prevInitialComponent, + prevInitialInteraction, + setDefaultState, + user + ]); const getAvailableSinks = () => { let type = or(interaction.settings.result, pond.InteractionResult.create()).type; diff --git a/src/interactions/InteractionsOverview.tsx b/src/interactions/InteractionsOverview.tsx index 929a58d..600c88a 100644 --- a/src/interactions/InteractionsOverview.tsx +++ b/src/interactions/InteractionsOverview.tsx @@ -15,7 +15,6 @@ import { Typography } from "@mui/material"; import { Settings } from "@mui/icons-material"; -import PendingChangesIndicator from "common/PendingChangesIndicator"; import { useInteractionsAPI, usePrevious, useSnackbar } from "hooks"; import { cloneDeep } from "lodash"; import { Component, Device, Interaction } from "models"; @@ -81,7 +80,6 @@ export default function InteractionsOverview(props: Props) { const [interactions, setInteractions] = useState(props.interactions); const [dirtyInteractions, setDirtyInteractions] = useState>(new Map()); const [mappedComponents, setMappedComponents] = useState>(new Map()); - const [acceptedInteractions, setAcceptedInteractions] = useState>(new Set()); const [renderToggle, setRenderToggle] = useState(false); const [selectedInteraction, setSelectedInteraction] = useState( undefined @@ -97,19 +95,6 @@ export default function InteractionsOverview(props: Props) { } if (props.interactions !== prevInteractions) { - setAcceptedInteractions(prev => { - const updated = new Set(prev); - props.interactions.forEach((interaction: Interaction) => { - const key = interaction.key(); - const prevInteraction = prevInteractions?.find(p => p.key() === key); - if (!interaction.status.synced) { - updated.delete(key); - } else if (prevInteraction && !prevInteraction.status.synced) { - updated.add(key); - } - }); - return updated; - }); setInteractions(cloneDeep(props.interactions)); } }, [components, prevComponents, props.interactions, prevInteractions]); @@ -222,23 +207,8 @@ export default function InteractionsOverview(props: Props) { let updatedDirtyInteractions = cloneDeep(dirtyInteractions); updatedDirtyInteractions.set(index, false); setDirtyInteractions(updatedDirtyInteractions); - setInteractions(prev => { - const updated = cloneDeep(prev); - if (updated[index]) { - updated[index].status.synced = false; - updated[index].status.lastUpdate = moment().toISOString(); - } - return updated; - }); - const interactionKey = settings.key ?? ""; - if (interactionKey) { - setAcceptedInteractions(prev => { - const updated = new Set(prev); - updated.delete(interactionKey); - return updated; - }); - } success("Successfully updated the interaction for " + component.name()); + refreshCallback(); }) .catch((_err: any) => { error("Error occurred while updating the interaction for " + component.name()); @@ -256,12 +226,10 @@ export default function InteractionsOverview(props: Props) { let key = interaction.key(); let source = mappedComponents.get(componentIDToString(interaction.settings.source)); let sink = mappedComponents.get(componentIDToString(interaction.settings.sink)); - const isAccepted = acceptedInteractions.has(interaction.key()); - const isPending = !interaction.status.synced; - let statusText = isAccepted - ? "Pending change accepted" - : isPending - ? "Pending " + moment(interaction.status.lastUpdate).fromNow() + let statusText = + !interaction.status.synced && interaction.status.lastUpdate + ? "Pending " + + moment(interaction.status.lastUpdate && interaction.status.lastUpdate).fromNow() : ""; let schedule = pond.InteractionSchedule.create( interaction.settings.schedule !== null ? interaction.settings.schedule : undefined @@ -304,14 +272,7 @@ export default function InteractionsOverview(props: Props) { className={classes.header} titleTypographyProps={{ variant: "subtitle2" }} title={interactionResultText(interaction, sink)} - subheader={ - statusText ? ( - - ) : ""} + subheader={statusText} subheaderTypographyProps={{ variant: "caption" }} action={action} /> diff --git a/src/pages/Device.tsx b/src/pages/Device.tsx index 801c1f1..6c9535d 100644 --- a/src/pages/Device.tsx +++ b/src/pages/Device.tsx @@ -19,7 +19,7 @@ import { DeviceAvailabilityMap, FindAvailablePositions, OffsetAvailabilityMap } import ComponentCard from "component/ComponentCard"; import { sameComponentID, sortComponents } from "pbHelpers/Component"; import { isController } from "pbHelpers/ComponentType"; -import { getTimestampMillis, isStaleStatusUpdate, or } from "utils"; +import { or } from "utils"; import ComponentDiagnostics from "component/ComponentDiagnostics"; import DeviceWizard from "device/DeviceWizard"; import DeviceScannedComponents from "device/autoDetect/DeviceScannedComponents"; @@ -27,6 +27,12 @@ import { useWebSocket } from "hooks/useWebSocket"; type TimestampedReading = { timestamp?: string }; +function getTimestampMillis(timestamp?: string): number | undefined { + if (!timestamp) return undefined; + const parsed = Date.parse(timestamp); + return Number.isNaN(parsed) ? undefined : parsed; +} + function pickNewestReading( current: T | null | undefined, incoming: T | null | undefined @@ -264,10 +270,6 @@ export default function DevicePage() { if (!data) return; const { key, component } = data; setComponents((prev) => { - const existing = prev.get(key); - if (existing && isStaleStatusUpdate(existing.status, component.status)) { - return prev; - } const updated = new Map(prev); updated.set(key, component); return updated; @@ -280,54 +282,6 @@ export default function DevicePage() { enabled: !!deviceID, }); - // --- Real-time interaction updates --- - // Streams interaction changes for this device, including status changes - // when the device accepts pending interaction updates. - useWebSocket<{ key: string; interaction: Interaction } | null>({ - path: `/v1/live/devices/${deviceID}/interactions`, - parse: (e) => { - try { - const raw = JSON.parse(e.data); - const interaction = Interaction.any(raw); - if (!interaction?.settings) { - console.warn("[ws] received interaction without settings:", raw); - return null; - } - return { key: interaction.key(), interaction }; - } catch (err) { - console.warn("[ws] failed to parse interaction:", err); - return null; - } - }, - onMessage: (data) => { - if (!data) return; - const { key, interaction } = data; - console.log("[ws:interaction] received", key, { - synced: interaction.status.synced, - lastUpdate: interaction.status.lastUpdate, - lastSynced: interaction.status.lastSynced, - }); - setInteractions((prev) => { - const index = prev.findIndex((existing) => existing.key() === key); - if (index < 0) { - return [...prev, interaction]; - } - const existing = prev[index]; - if (isStaleStatusUpdate(existing.status, interaction.status)) { - return prev; - } - const updated = [...prev]; - updated[index] = interaction; - return updated; - }); - }, - keys: liveContextKeys, - types: liveContextTypes, - as: liveAs, - token, - enabled: !!deviceID, - }); - const loadPortScan = () => { deviceAPI.listFoundComponents(parseInt(deviceID)) .then(resp => { @@ -441,7 +395,7 @@ export default function DevicePage() { interactions={filteredInteractions} permissions={permissions} deviceComponentPreferences={prefsMap.get(c.key())} - key={c.key()} + key={i} refreshCallback={(updatedComponent?: Component) => updatedComponent ? handleComponentChanged(updatedComponent) : loadDevice() } @@ -458,7 +412,7 @@ export default function DevicePage() { interactions={filteredInteractions} permissions={permissions} deviceComponentPreferences={prefsMap.get(c.key())} - key={c.key()} + key={i} refreshCallback={(updatedComponent?: Component) => updatedComponent ? handleComponentChanged(updatedComponent) : loadDevice() } diff --git a/src/pages/DeviceComponent.tsx b/src/pages/DeviceComponent.tsx index e6ab9fa..a245e51 100644 --- a/src/pages/DeviceComponent.tsx +++ b/src/pages/DeviceComponent.tsx @@ -4,7 +4,7 @@ import { NextMeasurementChip } from "common/NextMeasurementChip"; import ResponsiveTable, { Column } from "common/ResponsiveTable"; // import { getTableIcons } from "common/ResponsiveTable"; import SmartBreadcrumb from "common/SmartBreadcrumb"; -import PendingChangesChip from "common/PendingChangesChip"; +import StatusChip from "common/StatusChip"; import { GetDefaultDateRange } from "common/time/DateRange"; import ComponentActions from "component/ComponentActions"; import ComponentChart from "component/ComponentChart"; @@ -16,14 +16,11 @@ import { useComponentAPI, useDeviceAPI, useGroupAPI, - useHTTP, useInteractionsAPI, - usePendingChanges, usePrevious, useSnackbar, useUserAPI } from "hooks"; -import { useWebSocket } from "hooks/useWebSocket"; import InteractionChip from "interactions/InteractionChip"; import InteractionSettings from "interactions/InteractionSettings"; import { cloneDeep } from "lodash"; @@ -43,10 +40,9 @@ import { getDefaultInteraction } from "pbHelpers/Interaction"; import { pond } from "protobuf-ts/pond"; import { useGlobalState } from "providers"; import { useTeamAPI } from "providers/pond/teamAPI"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useParams } from "react-router-dom"; // import { useRouteMatch } from "react-router"; -import { isStaleStatusUpdate } from "utils/syncStatus"; import { or } from "utils/types"; const useStyles = makeStyles((theme: Theme) => { @@ -146,55 +142,6 @@ export default function DeviceComponent() { const [deviceComponentPrefs, setDeviceComponentPrefs] = useState< pond.DeviceComponentPreferences >(); - const { token } = useHTTP(); - const componentPending = usePendingChanges( - component.status.synced, - !loadingInitial && component.key() !== "" - ); - - const liveContextKeys = useMemo(() => getContextKeys(), []); - const liveContextTypes = useMemo(() => getContextTypes(), []); - /** Matches the REST calls: omit ?as= when first context type is "team". */ - const liveAs = - as && !(liveContextTypes.length > 0 && liveContextTypes[0] === "team") - ? as - : undefined; - - // --- Real-time component updates --- - // Streams component changes for this device, including status changes - // when the device accepts pending component updates. - useWebSocket<{ key: string; component: Component } | null>({ - path: `/v1/live/devices/${deviceID}/components`, - parse: (e) => { - try { - const raw = JSON.parse(e.data); - const comp = Component.any(raw); - if (!comp?.settings) { - console.warn("[ws] received component without settings:", raw); - return null; - } - return { key: comp.key(), component: comp }; - } catch (err) { - console.warn("[ws] failed to parse component:", err); - return null; - } - }, - onMessage: (data) => { - if (!data || data.key !== componentID) return; - setComponent((prev) => { - if (prev.key() !== data.key) return prev; - if (isStaleStatusUpdate(prev.status, data.component.status)) { - return prev; - } - return data.component; - }); - }, - keys: liveContextKeys, - types: liveContextTypes, - as: liveAs, - token, - enabled: !!deviceID && componentID !== "", - }); const setDefaultState = () => { setDevice(Device.create()); @@ -442,12 +389,9 @@ export default function DeviceComponent() { alignItems="center" wrap="nowrap" className={classes.overviewContainer}> - {(componentPending.pending || componentPending.accepted) && ( + {!component.status.synced && ( - + )} diff --git a/src/pbHelpers/ComponentType.tsx b/src/pbHelpers/ComponentType.tsx index 860e2aa..047c852 100644 --- a/src/pbHelpers/ComponentType.tsx +++ b/src/pbHelpers/ComponentType.tsx @@ -39,8 +39,7 @@ import { Voltage, VPD, Weight, - CapacitorCable, - AnalogPressure + CapacitorCable } from "pbHelpers/ComponentTypes"; //import { multilineGrainCableData } from "pbHelpers/ComponentTypes/GrainCable"; //import { multilineCapCableData } from "./ComponentTypes/CapacitorCable"; @@ -97,8 +96,7 @@ const COMPONENT_TYPE_MAP = new Map([ [quack.ComponentType.COMPONENT_TYPE_SEN5X, Sen5x], [quack.ComponentType.COMPONENT_TYPE_VIBRATION_CHAIN, VibrationCable], [quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, dragerGasDongle], - [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, Airflow], - [quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE, AnalogPressure] + [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, Airflow] ]); export interface Subtype { @@ -534,7 +532,7 @@ export function simpleLineChartData( } }); } - // console.log(lineData) + console.log(lineData) return lineData; } diff --git a/src/pbHelpers/ComponentTypes/AnalogPressure.ts b/src/pbHelpers/ComponentTypes/AnalogPressure.ts deleted file mode 100644 index 7921b38..0000000 --- a/src/pbHelpers/ComponentTypes/AnalogPressure.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { - ComponentTypeExtension, - Summary, - Subtype, - simpleMeasurements, - simpleSummaries, - unitMeasurementSummaries, - AreaChartData, - GraphFilters, - simpleAreaChartData, - LineChartData, - simpleLineChartData - } from "pbHelpers/ComponentType"; - import PressureDarkIcon from "assets/components/pressureDark.png"; - import PressureLightIcon from "assets/components/pressureLight.png"; - import { quack } from "protobuf-ts/quack"; - import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; - import { convertedUnitMeasurement } from "models/UnitMeasurement"; - import { pond } from "protobuf-ts/pond"; - - export function AnalogPressure(subtype: number = 0): ComponentTypeExtension { - let pressure = describeMeasurement( - quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE, - quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE, - subtype - ); - let voltage = describeMeasurement( - quack.MeasurementType.MEASUREMENT_TYPE_VOLTAGE, - quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE, - subtype - ) - let addressTypes = [quack.AddressType.ADDRESS_TYPE_I2C]; - return { - type: quack.ComponentType.COMPONENT_TYPE_PRESSURE, - subtypes: [ - { - key: quack.AnalogPressureSubtype.ANALOG_PRESSURE_SUBTYPE_PM1618, - value: "ANALOG_PRESSURE_SUBTYPE_PM1618", - friendlyName: "PM 1618" - } as Subtype - ], - friendlyName: "Analog Pressure", - description: "Measures the atmospheric pressure or absolute pressure", - isController: false, - isSource: true, - isCalibratable: true, - hasFan: true, - addressTypes: addressTypes, - interactionResultTypes: [], - states: [], - measurements: simpleMeasurements(pressure, voltage), - measurementSummary: async function(measurement: quack.Measurement): Promise> { - return simpleSummaries(measurement, pressure); - }, - unitMeasurementSummary: ( - measurements: convertedUnitMeasurement, - ): Summary[] => { - return unitMeasurementSummaries( - measurements, - quack.ComponentType.COMPONENT_TYPE_PRESSURE, - subtype, - ); - }, - areaChartData: ( - measurement: pond.UnitMeasurementsForComponent, - smoothingAverages?: number, - filters?: GraphFilters - ): AreaChartData => { - return simpleAreaChartData(measurement, smoothingAverages, filters); - }, - lineChartData: ( - measurement: pond.UnitMeasurementsForComponent, - smoothingAverages?: number, - filters?: GraphFilters - ): LineChartData => { - return simpleLineChartData( - quack.ComponentType.COMPONENT_TYPE_PRESSURE, - measurement, - smoothingAverages, - filters - ); - }, - minMeasurementPeriodMs: 1000, - icon: (theme?: "light" | "dark"): string | undefined => { - return theme === "light" ? PressureDarkIcon : PressureLightIcon; - } - }; - } \ No newline at end of file diff --git a/src/pbHelpers/ComponentTypes/index.ts b/src/pbHelpers/ComponentTypes/index.ts index 39b1560..349bbbe 100644 --- a/src/pbHelpers/ComponentTypes/index.ts +++ b/src/pbHelpers/ComponentTypes/index.ts @@ -29,4 +29,3 @@ export * from "./Voltage"; export * from "./VPD"; export * from "./Weight"; export * from "./CapacitorCable"; -export * from "./AnalogPressure"; diff --git a/src/pbHelpers/DeviceAvailability.ts b/src/pbHelpers/DeviceAvailability.ts index da39022..972c2e2 100644 --- a/src/pbHelpers/DeviceAvailability.ts +++ b/src/pbHelpers/DeviceAvailability.ts @@ -64,8 +64,7 @@ const DefaultAvailability: DeviceAvailabilityMap = new Map; const oldFirmwareIcon = ; const currentFirmwareIcon = ; - const unknownFirmwareIcon = ; let helper = {} as FirmwareVersionHelper; if (!version || version === "") { helper.description = "Unknown version"; helper.icon = oldFirmwareIcon; helper.tooltip = "We don't know what version your device is, it may behave unexpectedly"; - helper.colour = oldFirmwareColour; - } else if (version.startsWith("!")) { - const cleanVersion = version.substring(1); - helper.description = cleanVersion; - helper.icon = unknownFirmwareIcon; - helper.tooltip = "Firmware " + cleanVersion + " was reported by the device but is missing from the firmware list"; - helper.colour = unknownFirmwareColour; } else if (available !== "" && version !== available) { helper.description = version; helper.icon = oldFirmwareIcon; helper.tooltip = "A firmware upgrade is available, some features may be disabled for out-of-date devices"; - helper.colour = oldFirmwareColour; } else if (available === "") { helper.description = version; helper.icon = firmwareIcon; helper.tooltip = "Running version " + version; - helper.colour = ""; } else { helper.description = version; helper.icon = currentFirmwareIcon; helper.tooltip = "Your device is running the latest firmware"; - helper.colour = currentFirmwareColour; } return helper; } diff --git a/src/pbHelpers/MeasurementDescriber.ts b/src/pbHelpers/MeasurementDescriber.ts index ccc50a7..947c545 100644 --- a/src/pbHelpers/MeasurementDescriber.ts +++ b/src/pbHelpers/MeasurementDescriber.ts @@ -174,9 +174,10 @@ export class MeasurementDescriber { this.details.path = "power.inputVoltageTimes10"; this.details.decimals = 1; } - if (componentType === quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE || componentType === quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE) { + if (componentType === quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE) { this.details.unit = "mV"; this.details.graph = GraphType.MULTILINE; + this.details.unit = "mV"; this.details.decimals = 0 // this.details.nodeDetails = { // colours: ["white", "black", "red", "blue"], diff --git a/src/pbHelpers/Status.tsx b/src/pbHelpers/Status.tsx new file mode 100644 index 0000000..54d6f70 --- /dev/null +++ b/src/pbHelpers/Status.tsx @@ -0,0 +1,67 @@ +import { cyan } from "@mui/material/colors"; +import AcceptedIcon from "@mui/icons-material/CloudDone"; +import PendingIcon from "@mui/icons-material/CloudQueueTwoTone"; +import RejectedIcon from "@mui/icons-material/SmsFailed"; + +export interface StatusHelper { + description: string; + icon: any; +} + +const Unknown: StatusHelper = { + description: "Status unknown", + icon: null +}; + +const Pending: StatusHelper = { + description: "Pending changes", + icon: +}; + +const Stale: StatusHelper = { + description: "Stale update", + icon: null +}; + +const Accepted: StatusHelper = { + description: "Settings synced", + icon: +}; + +const Rejected: StatusHelper = { + description: "Update failed", + icon: +}; + +const Received: StatusHelper = { + description: "Settings synced", + icon: +}; + +const STATUS_MAP = new Map([ + ["pending", Pending], + ["stale", Stale], + ["accepted", Accepted], + ["rejected", Rejected], + ["received", Received] +]); + +export function getStatusHelper(status: string): StatusHelper { + const statuses = Array.from(STATUS_MAP.keys()); + for (var i = 0; i < statuses.length; i++) { + let key = statuses[i]; + if (status === key) { + return STATUS_MAP.get(key) as StatusHelper; + } + } + + return Unknown; +} + +export function getStatusDescription(status: string): string { + return getStatusHelper(status).description; +} + +export function getStatusIcon(status: string): any { + return getStatusHelper(status).icon; +} diff --git a/src/products/MiVent/MiVentAvailability.ts b/src/products/MiVent/MiVentAvailability.ts index 87d0db2..0108806 100644 --- a/src/products/MiVent/MiVentAvailability.ts +++ b/src/products/MiVent/MiVentAvailability.ts @@ -10,7 +10,6 @@ const MiVentV1Pins: ConfigurablePin[] = [ { address: 1024, label: "C2" } ]; -//no longer in development so if you are adding new component types DO NOT add the I2C address here because the firmware of the device will not support it export const MiVentV1Availability: DeviceAvailabilityMap = new Map< quack.AddressType, DevicePositions @@ -54,8 +53,7 @@ export const MiVentV2Availability: DeviceAvailabilityMap = new Map< [quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]], [quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]], [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x6c]], - [quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, [0x6d]], - [quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE, [0x6e]] + [quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, [0x6d]] ]) ], diff --git a/src/providers/pond/componentAPI.tsx b/src/providers/pond/componentAPI.tsx index e78b75a..a224952 100644 --- a/src/providers/pond/componentAPI.tsx +++ b/src/providers/pond/componentAPI.tsx @@ -417,8 +417,7 @@ export default function ComponentProvider(props: PropsWithChildren) { "&end=" + end + (keys ? "&keys=" + keys : "&keys=" + [deviceId.toString()]) + - (types ? "&types=" + types : "&types=" + ["device"]) + - (as ? "&as=" + as : "") + (types ? "&types=" + types : "&types=" + ["device"]) ) return new Promise>((resolve,reject) => { get(url).then(resp => { diff --git a/src/services/whiteLabel.ts b/src/services/whiteLabel.ts index 90cf4ed..35a9c8c 100644 --- a/src/services/whiteLabel.ts +++ b/src/services/whiteLabel.ts @@ -28,8 +28,6 @@ const protips: string[] = [ "Want to receive text messages about your devices? Update your phone number and enable SMS notifications!" ]; -const DEFAULT_HEADER_COLOUR = "#1f2a35"; - export interface WhiteLabelFeatures { bins: boolean; visualFarm: boolean; @@ -57,7 +55,6 @@ interface WhiteLabel { secondaryColour: any; signatureColour: any; signatureAccentColour: any; - headerColour?: any; auth0ClientId: string; redirectOnLogout: boolean; logoutRedirectTarget: string; @@ -447,14 +444,6 @@ export function getSignatureColour(): any { return getWhitelabel().signatureColour; } -export function getHeaderColour(): any { - return getWhitelabel().headerColour ?? DEFAULT_HEADER_COLOUR; -} - -export function getHeaderColor(): any { - return getHeaderColour(); -} - export function getSignatureAccentColour(): any { return getWhitelabel().signatureAccentColour; } diff --git a/src/theme/theme.ts b/src/theme/theme.ts index ce44c5d..739a54f 100644 --- a/src/theme/theme.ts +++ b/src/theme/theme.ts @@ -2,21 +2,10 @@ import * as Colours from "@mui/material/colors" import { createTheme, ThemeOptions } from '@mui/material/styles'; import { - getHeaderColor, getPrimaryColour, getSecondaryColour, } from "../services/whiteLabel"; -declare module "@mui/material/styles" { - interface Palette { - header: Palette["primary"]; - } - - interface PaletteOptions { - header?: PaletteOptions["primary"]; - } -} - const baseTheme: ThemeOptions = { palette: { mode: 'light', // default mode; will be overridden by system if needed @@ -73,9 +62,6 @@ export const getTheme = (mode: 'light' | 'dark') => ...baseTheme.palette, primary: makePaletteColor(getPrimaryColour()), secondary: makePaletteColor(getSecondaryColour()), - header: { - main: getHeaderColor(), - }, mode, ...(mode === 'dark' ? { diff --git a/src/utils/index.ts b/src/utils/index.ts index 29ff37c..5a5ef3f 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -2,6 +2,5 @@ export * from "./download"; export * from "./environment"; export * from "./numbers"; export * from "./strings"; -export * from "./syncStatus"; export * from "./types"; export * from "./units"; diff --git a/src/utils/syncStatus.ts b/src/utils/syncStatus.ts deleted file mode 100644 index 107c2a8..0000000 --- a/src/utils/syncStatus.ts +++ /dev/null @@ -1,41 +0,0 @@ -export function getTimestampMillis(timestamp?: string): number | undefined { - if (!timestamp) return undefined; - const parsed = Date.parse(timestamp); - return Number.isNaN(parsed) ? undefined : parsed; -} - -/** The sync-tracking fields shared by ComponentStatus and InteractionStatus. */ -export interface SyncStatusLike { - synced: boolean; - lastUpdate?: string; - lastSynced?: string; -} - -/** - * Decides whether an incoming websocket status message is stale and should - * not replace the status we already hold. A message is stale when it was - * generated before our latest local update, including a synced echo that - * predates a pending change we just submitted. - */ -export function isStaleStatusUpdate(existing: SyncStatusLike, incoming: SyncStatusLike): boolean { - const existingLastUpdate = getTimestampMillis(existing.lastUpdate); - const incomingLastUpdate = getTimestampMillis(incoming.lastUpdate); - const incomingLastSynced = getTimestampMillis(incoming.lastSynced); - if ( - existingLastUpdate !== undefined && - incomingLastUpdate !== undefined && - existingLastUpdate > incomingLastUpdate - ) { - return true; - } - if ( - !existing.synced && - incoming.synced && - incomingLastUpdate !== undefined && - existingLastUpdate === incomingLastUpdate && - (incomingLastSynced === undefined || incomingLastSynced < incomingLastUpdate) - ) { - return true; - } - return false; -}