diff --git a/package-lock.json b/package-lock.json index 3610bee..b39e4d5 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#plenum_ambient_component", "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#1a3b63d5a2f4c9165642d994c842abecc3f3bffc", "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..a1891d0 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#plenum_ambient_component", "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/bin/BinPlayer.tsx b/src/bin/BinPlayer.tsx index 364d5af..88bc45c 100644 --- a/src/bin/BinPlayer.tsx +++ b/src/bin/BinPlayer.tsx @@ -8,7 +8,7 @@ import { Bin } from "models"; import moment, { Moment } from "moment"; import { pond } from "protobuf-ts/pond"; import { quack } from "protobuf-ts/quack"; -import { useBinAPI, useComponentAPI } from "providers"; +import { useComponentAPI } from "providers"; import React, { useEffect, useRef, useState } from "react"; const CHECKPOINT_COUNT = 10; @@ -16,7 +16,6 @@ const CHECKPOINT_COUNT = 10; interface StatusCheckpoint { time: Moment; timeString: string; - statusBushels: number; cables: pond.GrainCable[]; fans: pond.BinFan[]; heaters: pond.BinHeater[]; @@ -91,103 +90,6 @@ function upsertReading( } } -/** - * A trimmed record of a single component-settings history entry: just the timestamp and the - * top-node value (`grain_filled_to`) that was in effect from that point forward. - */ -type TopNodeHistoryEntry = { - timestamp: Moment; - topNode: number; -}; - -function buildTopNodeHistory( - history: pond.ComponentHistory[] -): TopNodeHistoryEntry[] { - return history - .filter(h => h.component && h.component.grainFilledTo > 0 && h.timestamp) - .map(h => ({ - timestamp: moment(h.timestamp), - topNode: h.component ? h.component.grainFilledTo : 0, - })) - .sort((a, b) => a.timestamp.valueOf() - b.timestamp.valueOf()); -} - -function topNodeAtTime( - history: TopNodeHistoryEntry[], - checkpointTime: Moment -): number | undefined { - if (history.length === 0) return undefined; - const t = checkpointTime.valueOf(); - let lo = 0; - let hi = history.length - 1; - let result: number | undefined = undefined; - while (lo <= hi) { - const mid = (lo + hi) >>> 1; - if (history[mid].timestamp.valueOf() <= t) { - result = history[mid].topNode; - lo = mid + 1; - } else { - hi = mid - 1; - } - } - return result; -} - -/** - * A single recorded bushel estimate with its timestamp, sourced from bin object measurements. - */ -type BushelEntry = { - timestamp: Moment; - bushels: number; -}; - -function buildBushelHistory( - response: pond.ListObjectMeasurementsResponse -): BushelEntry[] { - const entries: BushelEntry[] = []; - - response.measurements.forEach(um => { - const isBushelType = - um.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_BUSHEL_LIDAR || - um.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_BUSHEL_CABLE || - um.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_BUSHEL_LIBRACART; - - if (!isBushelType) return; - - um.timestamps.forEach((ts, i) => { - const valueArray = um.values[i]; - if (!ts || !valueArray || valueArray.error || valueArray.values.length === 0) return; - entries.push({ - timestamp: moment(ts), - bushels: valueArray.values[0], - }); - }); - }); - - return entries.sort((a, b) => a.timestamp.valueOf() - b.timestamp.valueOf()); -} - -function bushelsAtTime( - history: BushelEntry[], - checkpointTime: Moment -): number | undefined { - if (history.length === 0) return undefined; - const t = checkpointTime.valueOf(); - let lo = 0; - let hi = history.length - 1; - let result: number | undefined = undefined; - while (lo <= hi) { - const mid = (lo + hi) >>> 1; - if (history[mid].timestamp.valueOf() <= t) { - result = history[mid].bushels; - lo = mid + 1; - } else { - hi = mid - 1; - } - } - return result; -} - /** * Produces `count` wall-clock times evenly spaced from `start` through `end` (inclusive). * These are the playback frames — not derived from measurement data. @@ -458,12 +360,6 @@ function buildStatusCheckpoints( cableMeasurementResponses: pond.SampleUnitMeasurementsResponse[], fanMeasurementResponses: pond.SampleUnitMeasurementsResponse[], heaterMeasurementResponses: pond.SampleUnitMeasurementsResponse[], - /** Sorted top-node history per cable key. Pass an empty Map when not needed. */ - topNodeHistories: Map, - /** Sorted bushel history derived from bin object measurements. */ - bushelHistory: BushelEntry[], - /** Fallback bushel count used when no measurement precedes a checkpoint. */ - fallbackBushels: number, start: Moment, end: Moment, count: number = CHECKPOINT_COUNT @@ -520,19 +416,7 @@ function buildStatusCheckpoints( const bucketReadings = cableBucket.get(key) ?? {}; const merged = mergeCarriedReadings(carriedByCable.get(key)!, bucketReadings); carriedByCable.set(key, merged); - const cable = buildGrainCableFromReadings(template, merged, time); - - // Apply the most recent top-node setting that was in effect at this checkpoint time. - // Falls back to the template's current top_node when no history entry precedes this time. - const history = topNodeHistories.get(key); - if (history) { - const tn = topNodeAtTime(history, time); - if (tn !== undefined) { - cable.topNode = tn; - } - } - - return cable; + return buildGrainCableFromReadings(template, merged, time); }); // Build fans @@ -553,12 +437,7 @@ function buildStatusCheckpoints( return buildBinHeaterFromState(template, merged); }); - // Resolve bushels: binary search gives the most recent recorded value at or before this - // checkpoint (carry-forward is implicit in the search). Falls back to the current live - // status bushels when the playback range predates all recorded measurements. - const statusBushels = bushelsAtTime(bushelHistory, time) ?? fallbackBushels; - - return { time, cables, fans, heaters, timeString: time.toISOString(), statusBushels }; + return { time, cables, fans, heaters, timeString: time.toISOString() }; }).filter(checkpoint => // Keep checkpoints that have at least some cable data checkpoint.cables.some(cable => @@ -592,7 +471,6 @@ export default function BinPlayer(props: Props) { const { bin, componentDevices, setBin } = props; const isMobile = useMobile(); const componentAPI = useComponentAPI(); - const binAPI = useBinAPI(); const [dateSelect, setDateSelect] = useState(0); const [startDate, setStartDate] = useState(moment().subtract(1, 'week')); const [endDate, setEndDate] = useState(moment); @@ -651,17 +529,6 @@ export default function BinPlayer(props: Props) { return componentAPI.sampleUnitMeasurements(deviceID, cable.key, startDate, endDate, 100, undefined, undefined, undefined, undefined, true); }); - // Cable top-node history requests — one per cable, covering the full playback range. - const cableHistoryRequests = bin.status.grainCables.map(cable => { - const deviceID = componentDevices.get(cable.key) ?? 0; - return componentAPI.listHistoryBetween( - deviceID, - cable.key, - startDate.toISOString(), - endDate.toISOString() - ); - }); - // Fan requests const fanSampleRequests = bin.status.fans.map(fan => { const deviceID = componentDevices.get(fan.key) ?? 0; @@ -674,27 +541,12 @@ export default function BinPlayer(props: Props) { return componentAPI.sampleUnitMeasurements(deviceID, heater.key, startDate, endDate, 100, undefined, undefined, undefined, undefined, true); }); - const binMeasurementsRequest = binAPI.listBinMeasurements(bin.key(), startDate, endDate, 0, 0, 'asc'); - - const [cableResponses, fanResponses, heaterResponses, cableHistoryResponses, binMeasurementsResponse] = await Promise.all([ + const [cableResponses, fanResponses, heaterResponses] = await Promise.all([ Promise.all(cableSampleRequests), Promise.all(fanSampleRequests), Promise.all(heaterSampleRequests), - Promise.all(cableHistoryRequests), - binMeasurementsRequest, ]); - // Build a per-cable sorted top-node history map for use during checkpoint construction. - const topNodeHistories = new Map(); - bin.status.grainCables.forEach((cable, i) => { - const historyResp = cableHistoryResponses[i]; - const entries = buildTopNodeHistory(historyResp?.data?.history ?? []); - topNodeHistories.set(cable.key, entries); - }); - - // Build sorted bushel history from bin object measurements. - const bushelHistory = buildBushelHistory(binMeasurementsResponse.data); - const checkpoints = buildStatusCheckpoints( bin.status.grainCables, bin.status.fans, @@ -702,9 +554,6 @@ export default function BinPlayer(props: Props) { cableResponses.map(r => r.data), fanResponses.map(r => r.data), heaterResponses.map(r => r.data), - topNodeHistories, - bushelHistory, - bin.status.grainBushels, startDate, endDate, checkpointCountFromRange(startDate, endDate) @@ -725,7 +574,6 @@ export default function BinPlayer(props: Props) { newBin.status.grainCables = checkpoints[i].cables; newBin.status.fans = checkpoints[i].fans; newBin.status.heaters = checkpoints[i].heaters; - newBin.status.grainBushels = checkpoints[i].statusBushels; setBin(newBin); setCurrentCheckpointTime(checkpoints[i].time); @@ -867,4 +715,4 @@ export default function BinPlayer(props: Props) { ); -} \ No newline at end of file +} diff --git a/src/bin/binTableView.tsx b/src/bin/binTableView.tsx index e11132e..0659335 100644 --- a/src/bin/binTableView.tsx +++ b/src/bin/binTableView.tsx @@ -312,7 +312,7 @@ export default function BinTableView(props: Props) { - - - {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..e526858 100644 --- a/src/pbHelpers/ComponentType.tsx +++ b/src/pbHelpers/ComponentType.tsx @@ -40,7 +40,9 @@ import { VPD, Weight, CapacitorCable, - AnalogPressure + Plenum, + AmbientOnewire, + AmbientI2C } from "pbHelpers/ComponentTypes"; //import { multilineGrainCableData } from "pbHelpers/ComponentTypes/GrainCable"; //import { multilineCapCableData } from "./ComponentTypes/CapacitorCable"; @@ -98,7 +100,10 @@ const COMPONENT_TYPE_MAP = new Map([ [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_PLENUM, Plenum], + [quack.ComponentType.COMPONENT_TYPE_AMBIENT_ONEWIRE, AmbientOnewire], + [quack.ComponentType.COMPONENT_TYPE_AMBIENT_I2C, AmbientI2C], + ]); export interface Subtype { @@ -534,7 +539,7 @@ export function simpleLineChartData( } }); } - // console.log(lineData) + console.log(lineData) return lineData; } diff --git a/src/pbHelpers/ComponentTypes/AmbientI2C.ts b/src/pbHelpers/ComponentTypes/AmbientI2C.ts new file mode 100644 index 0000000..7a2cbf9 --- /dev/null +++ b/src/pbHelpers/ComponentTypes/AmbientI2C.ts @@ -0,0 +1,79 @@ +import TemperatureHumidityDarkIcon from "assets/components/temperatureHumidityDark.png"; +import TemperatureHumidityLightIcon from "assets/components/temperatureHumidityLight.png"; +import { convertedUnitMeasurement } from "models/UnitMeasurement"; +import { + AreaChartData, + ComponentTypeExtension, + GraphFilters, + LineChartData, + simpleAreaChartData, + simpleLineChartData, + simpleMeasurements, + simpleSummaries, + Summary, + unitMeasurementSummaries +} from "pbHelpers/ComponentType"; +import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; +import { pond } from "protobuf-ts/pond"; +import { quack } from "protobuf-ts/quack"; + +export function AmbientI2C(subtype: number = 0): ComponentTypeExtension { + let temperature = describeMeasurement( + quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, + quack.ComponentType.COMPONENT_TYPE_AMBIENT_I2C, + subtype + ); + let humidity = describeMeasurement( + quack.MeasurementType.MEASUREMENT_TYPE_PERCENT, + quack.ComponentType.COMPONENT_TYPE_AMBIENT_I2C, + subtype + ); + return { + type: quack.ComponentType.COMPONENT_TYPE_AMBIENT_I2C, + subtypes: [], + friendlyName: "Ambient I2C", + description: "Measures temperature and humidity", + isController: false, + isSource: true, + isCalibratable: true, + addressTypes: [quack.AddressType.ADDRESS_TYPE_I2C], + interactionResultTypes: [], + states: [], + measurements: simpleMeasurements(temperature, humidity), + measurementSummary: async function(measurement: quack.Measurement): Promise> { + return simpleSummaries(measurement, temperature, humidity); + }, + unitMeasurementSummary: ( + measurements: convertedUnitMeasurement + ): Summary[] => { + return unitMeasurementSummaries( + measurements, + quack.ComponentType.COMPONENT_TYPE_AMBIENT_I2C, + 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_AMBIENT_I2C, + measurement, + smoothingAverages, + filters + ); + }, + minMeasurementPeriodMs: 1000, + icon: (theme?: "light" | "dark"): string | undefined => { + return theme === "light" ? TemperatureHumidityDarkIcon : TemperatureHumidityLightIcon; + } + }; +} diff --git a/src/pbHelpers/ComponentTypes/AmbientOnewire.ts b/src/pbHelpers/ComponentTypes/AmbientOnewire.ts new file mode 100644 index 0000000..4fd394a --- /dev/null +++ b/src/pbHelpers/ComponentTypes/AmbientOnewire.ts @@ -0,0 +1,79 @@ +import TemperatureHumidityDarkIcon from "assets/components/temperatureHumidityDark.png"; +import TemperatureHumidityLightIcon from "assets/components/temperatureHumidityLight.png"; +import { convertedUnitMeasurement } from "models/UnitMeasurement"; +import { + AreaChartData, + ComponentTypeExtension, + GraphFilters, + LineChartData, + simpleAreaChartData, + simpleLineChartData, + simpleMeasurements, + simpleSummaries, + Summary, + unitMeasurementSummaries +} from "pbHelpers/ComponentType"; +import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; +import { pond } from "protobuf-ts/pond"; +import { quack } from "protobuf-ts/quack"; + +export function AmbientOnewire(subtype: number = 0): ComponentTypeExtension { + let temperature = describeMeasurement( + quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, + quack.ComponentType.COMPONENT_TYPE_AMBIENT_ONEWIRE, + subtype + ); + let humidity = describeMeasurement( + quack.MeasurementType.MEASUREMENT_TYPE_PERCENT, + quack.ComponentType.COMPONENT_TYPE_AMBIENT_ONEWIRE, + subtype + ); + return { + type: quack.ComponentType.COMPONENT_TYPE_AMBIENT_ONEWIRE, + subtypes: [], + friendlyName: "Onewire Ambient", + description: "Measures temperature and humidity", + isController: false, + isSource: true, + isCalibratable: true, + addressTypes: [quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY], + interactionResultTypes: [], + states: [], + measurements: simpleMeasurements(temperature, humidity), + measurementSummary: async function(measurement: quack.Measurement): Promise> { + return simpleSummaries(measurement, temperature, humidity); + }, + unitMeasurementSummary: ( + measurements: convertedUnitMeasurement + ): Summary[] => { + return unitMeasurementSummaries( + measurements, + quack.ComponentType.COMPONENT_TYPE_AMBIENT_ONEWIRE, + 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_AMBIENT_ONEWIRE, + measurement, + smoothingAverages, + filters + ); + }, + minMeasurementPeriodMs: 1000, + icon: (theme?: "light" | "dark"): string | undefined => { + return theme === "light" ? TemperatureHumidityDarkIcon : TemperatureHumidityLightIcon; + } + }; +} 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/Plenum.ts b/src/pbHelpers/ComponentTypes/Plenum.ts new file mode 100644 index 0000000..687d6fd --- /dev/null +++ b/src/pbHelpers/ComponentTypes/Plenum.ts @@ -0,0 +1,84 @@ +import TemperatureHumidityDarkIcon from "assets/components/temperatureHumidityDark.png"; +import TemperatureHumidityLightIcon from "assets/components/temperatureHumidityLight.png"; +import { convertedUnitMeasurement } from "models/UnitMeasurement"; +import { + AreaChartData, + ComponentTypeExtension, + GraphFilters, + LineChartData, + simpleAreaChartData, + simpleLineChartData, + simpleMeasurements, + simpleSummaries, + Summary, + unitMeasurementSummaries +} from "pbHelpers/ComponentType"; +import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; +import { pond } from "protobuf-ts/pond"; +import { quack } from "protobuf-ts/quack"; + +export function Plenum(subtype: number = 0): ComponentTypeExtension { + let temperature = describeMeasurement( + quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, + quack.ComponentType.COMPONENT_TYPE_PLENUM, + subtype + ); + let humidity = describeMeasurement( + quack.MeasurementType.MEASUREMENT_TYPE_PERCENT, + quack.ComponentType.COMPONENT_TYPE_PLENUM, + subtype + ); + let pressure = describeMeasurement( + quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE, + quack.ComponentType.COMPONENT_TYPE_PLENUM, + subtype + ); + return { + type: quack.ComponentType.COMPONENT_TYPE_PLENUM, + subtypes: [], + friendlyName: "Plenum", + description: "Measures temperature, humidity and pressure", + isController: false, + isSource: true, + isCalibratable: true, + addressTypes: [quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY], + interactionResultTypes: [], + states: [], + measurements: simpleMeasurements(temperature, humidity, pressure), + measurementSummary: async function(measurement: quack.Measurement): Promise> { + return simpleSummaries(measurement, temperature, humidity, pressure); + }, + unitMeasurementSummary: ( + measurements: convertedUnitMeasurement + ): Summary[] => { + return unitMeasurementSummaries( + measurements, + quack.ComponentType.COMPONENT_TYPE_PLENUM, + 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_PLENUM, + measurement, + smoothingAverages, + filters + ); + }, + minMeasurementPeriodMs: 1000, + icon: (theme?: "light" | "dark"): string | undefined => { + return theme === "light" ? TemperatureHumidityDarkIcon : TemperatureHumidityLightIcon; + } + }; +} diff --git a/src/pbHelpers/ComponentTypes/index.ts b/src/pbHelpers/ComponentTypes/index.ts index 39b1560..ba5912d 100644 --- a/src/pbHelpers/ComponentTypes/index.ts +++ b/src/pbHelpers/ComponentTypes/index.ts @@ -29,4 +29,6 @@ export * from "./Voltage"; export * from "./VPD"; export * from "./Weight"; export * from "./CapacitorCable"; -export * from "./AnalogPressure"; +export * from "./Plenum"; +export * from "./AmbientOnewire"; +export * from "./AmbientI2C"; diff --git a/src/pbHelpers/DeviceAvailability.ts b/src/pbHelpers/DeviceAvailability.ts index da39022..a7d747e 100644 --- a/src/pbHelpers/DeviceAvailability.ts +++ b/src/pbHelpers/DeviceAvailability.ts @@ -65,7 +65,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/Bindapt/BindaptAvailability.ts b/src/products/Bindapt/BindaptAvailability.ts index 89b3a47..4925878 100644 --- a/src/products/Bindapt/BindaptAvailability.ts +++ b/src/products/Bindapt/BindaptAvailability.ts @@ -252,7 +252,8 @@ export const BindaptV2MonitorAvailability: DeviceAvailabilityMap = new Map< [quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62, 0x66, 0x10]], [quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]], [quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]], - [quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]] + [quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]], + [quack.ComponentType.COMPONENT_TYPE_AMBIENT_I2C, [0x44]] ]) ], [quack.AddressType.ADDRESS_TYPE_POWER, [0]], @@ -285,7 +286,8 @@ export const BindaptV2AutomateAvailability: DeviceAvailabilityMap = new Map< [quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62, 0x66, 0x10]], [quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]], [quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]], - [quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]] + [quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]], + [quack.ComponentType.COMPONENT_TYPE_AMBIENT_I2C, [0x44]] ]) ], [quack.AddressType.ADDRESS_TYPE_POWER, [0]], 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..3e2207e 100644 --- a/src/providers/pond/componentAPI.tsx +++ b/src/providers/pond/componentAPI.tsx @@ -62,14 +62,6 @@ export interface IComponentAPIContext { keys?: string[], types?: string[] ) => Promise>; - listHistoryBetween: ( - deviceId: string | number, - componentKey: string, - start: string, - end: string, - keys?: string[], - types?: string[] - ) => Promise>; // Old measuremnt structure functions, no longer used in codebase // listMeasurements: ( // device: number, @@ -399,37 +391,6 @@ export default function ComponentProvider(props: PropsWithChildren) { }) }; - const listHistoryBetween = ( - deviceId: string | number, - componentKey: string, - start: string, - end: string, - keys?: string[], - types?: string[] - ) => { - let url = pondURL( - "/devices/" + - deviceId + - "/components/" + - componentKey + - "/historyBetween?start=" + - start + - "&end=" + - end + - (keys ? "&keys=" + keys : "&keys=" + [deviceId.toString()]) + - (types ? "&types=" + types : "&types=" + ["device"]) + - (as ? "&as=" + as : "") - ) - return new Promise>((resolve,reject) => { - get(url).then(resp => { - resp.data = pond.ListComponentHistoryBetweenResponse.fromObject(resp.data) - return resolve(resp) - }).catch(err => { - return reject(err) - }) - }) - }; - // const listMeasurements = ( // device: number, // component: string, @@ -635,7 +596,6 @@ export default function ComponentProvider(props: PropsWithChildren) { listForObject: listComponentsForObject, listComponentCardData: listComponentCardData, listHistory, - listHistoryBetween, //listMeasurements: listMeasurements, //sampleMeasurements: sampleMeasurements, sampleUnitMeasurements, 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/user/ObjectUsers.tsx b/src/user/ObjectUsers.tsx index 4facea6..ce28936 100644 --- a/src/user/ObjectUsers.tsx +++ b/src/user/ObjectUsers.tsx @@ -169,6 +169,7 @@ export default function ObjectUsers(props: Props) { userAPI .listObjectUsers(scope) .then((response: any) => { + console.log(response) let rUsers: User[] = []; or(response.data, { users: [] }).users.forEach((user: any) => { rUsers.push(User.any(user)); 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; -}