diff --git a/.env b/.env
index b1dbccb..6e8f870 100644
--- a/.env
+++ b/.env
@@ -7,7 +7,6 @@ VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com
VITE_AUTH0_AUDIENCE=api.brandxtech.ca
VITE_AUTH0_DEV_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1
VITE_AUTH0_STAGING_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP
-VITE_AUTH0_STREAMLINE_CLIENT_ID=HwUV0hHNdVvU96zuMBTAU8i7nFdwwgIX
VITE_AUTH0_BXT_CLIENT_ID=sLnqOu40uWfQT1lYSDYj2wYmlLEHRB74
VITE_AUTH0_ADAPTIVE_AGRICULTURE_CLIENT_ID=5pUCkl2SfogkWmM244UDcLEUOp8EFdHd
@@ -15,19 +14,9 @@ VITE_AUTH0_ADAPTIVE_CONSTRUCTION_CLIENT_ID=32rABabJzXRvJiWivTmeKFgwFiqh4ok7
VITE_AUTH0_AEROGROW_CLIENT_ID=KHl9ooUt1nia1RYw5n224dyggCXdbsSd
VITE_AUTH0_MIVENT_CLIENT_ID=VNALE7RW6l3dY5uYcxgwElZV0lcT25Fg
VITE_AUTH0_OMNIAIR_CLIENT_ID=IblmarD8wFafiD6doxTmOHQ6Bx3L9wWl
-VITE_AUTH0_INTELLIFARMS_CLIENT_ID=RYtuAyOcB4DSaaqJMLDMf3pV8SFY9PdY
-
-#Crisp
-VITE_CRISP_WEBSITE_ID=80170383-b426-43c0-8f66-8e20a05bcdce
#Branding (Default theme)
VITE_APP_WEBSITE_TITLE="Adaptive Dashboard"
VITE_APP_PRIMARY_COLOUR=blue
VITE_APP_SECONDARY_COLOUR=blueGrey
VITE_APP_SIGNATURE_COLOUR="#323232"
-
-# Live device/component WebSocket streams (must match string "true" in code)
-VITE_ENABLE_WEBSOCKETS=true
-
-# Default whitelabel; overwrite in .env.local
-#VITE_WHITELABEL=adaptive-ag
diff --git a/README.md b/README.md
index 4a47964..b7a7555 100644
--- a/README.md
+++ b/README.md
@@ -18,36 +18,31 @@ Steps to add a new white label
- add a `CNAME` record pointing their custom subdomain (ex: dashboard.example.com) to brandxtech.ca
- add a login button on their website point to `/login` of their custom subdomain (ex: dashboard.example.com/login)
-It is recommended to start with the Auth0 stage once everything from the client has been recieved as things like the client ID will be needed in other stages
-
-Frontend Steps
-1. Create a PWA folder in `public` that is named after the client and generate PWA assets using [this](https://realfavicongenerator.net/) website (use the company logo but you might need to use a custom favicon depending on the result)
+2. Create a PWA folder in `public` that is named after the client and generate PWA assets using [this](https://realfavicongenerator.net/) website (use the company logo but you might need to use a custom favicon depending on the result)
- https://maskable.app/ also create a maskable icon for PWA
- create a 512x512 png as well for PWA
-2. Create a white-label branding folder in `src/assets/whitelabels` and create a `darkLogo.png` and `lightLogo.png` from the client's logo
-3. In `src/services/whiteLabel.ts`, add a new `Whitelabel` instance with the required fields and add its hostname mapping to the `whitelabels` map
-4. If there are any pages specific to the whitelabel be sure to update the side and bottom navigator
+3. Create a white-label branding folder in `src/assets/whitelabels` and create a `darkLogo.png` and `lightLogo.png` from the client's logo
+4. In `src/services/whiteLabel.ts`, add a new `Whitelabel` instance with the required fields and add its hostname mapping to the `whitelabels` map (it is very similar to step one but had some constraints when dealing with the `public` folder)
+5. If there are any pages specific to the whitelabel be sure to update the side and bottom navigator
-Backend Steps
-1. In `dynamic-config.prod.yml`, add a router entry for the new whitelabel
-2. In `pond/http/server.go`, add the sub-domain to the `allowedOrigins`
-3. In `pond/whitelabel.go`, add an entry for the new white label
+6. In `dynamic-config.prod.yml`, add a router entry for the new whitelabel
+7. In `pond/http/server.go`, add the sub-domain to the `allowedOrigins`
+8. In `pond/whitelabel.go`, add an entry for the new white label
-
-Auth0 Steps
-1. Create a new Single Page App on Auth0 using React as the tech
-2. Once you have the client ID add it to your .env file in the frontend
-3. Adjust settings accordingly in the new application
+7. Create a new `Auth0` application for the client
- use a image bucket like [postimg](https://postimages.org/) to store their company logo and favicon
- add a whitelabel logo
- - allow necessary `origins`, `callback URLs`, and `CORS domains` to the application settings (see other applications/whitelabels as a guideline)
+ - allow necessary `origins`, `callback URLs`, and `CORS domains` to the application settings (see the default application as a guideline)
- customize the Universal Login page to handle the new whitelabel
- check if the client ID matches this client's Auth0 application
- change the Universal Login theme using white-label colours and images stored in a bucket (ex: [postimg](https://postimages.org/))
+8. Enable the white-label sub-domain for CORS in the backend
+
+- in [http.go](https://gitlab.com/brandx/backend/blob/master/pond/http.go), add the white-label sub-domain to the `AllowedOrigins` list
## Expanding the ESLint configuration
diff --git a/deploy.sh b/deploy.sh
deleted file mode 100755
index 0a84fdb..0000000
--- a/deploy.sh
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-# Load .env.local if it exists
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-ENV_FILE="$SCRIPT_DIR/.env.local"
-
-if [[ -f "$ENV_FILE" ]]; then
- # Export only the vars we care about, ignoring comments and blank lines
- set -o allexport
- # shellcheck source=/dev/null
- source <(grep -E '^(DEPLOY_USER|DEPLOY_HOST|DEPLOY_SSH_KEY)=' "$ENV_FILE")
- set +o allexport
-fi
-
-# Prompt for any missing values
-if [[ -z "${DEPLOY_USER:-}" ]]; then
- read -rp "SSH username: " DEPLOY_USER
-fi
-
-if [[ -z "${DEPLOY_HOST:-}" ]]; then
- read -rp "Server IP/hostname: " DEPLOY_HOST
-fi
-
-if [[ -z "${DEPLOY_SSH_KEY:-}" ]]; then
- read -rp "SSH key path [~/.ssh/id_ed25519]: " DEPLOY_SSH_KEY
- DEPLOY_SSH_KEY="${DEPLOY_SSH_KEY:-~/.ssh/id_ed25519}"
-fi
-
-SSH_OPTS="-i $DEPLOY_SSH_KEY"
-
-echo "→ Deploying to $DEPLOY_USER@$DEPLOY_HOST using key $DEPLOY_SSH_KEY"
-
-# 1. Build frontend for LAN / self-contained hosts (indexLocal.html via vite --mode localnet)
-# Pass VITE_WHITELABEL if provided as an argument; otherwise Vite reads it from .env
-(
- cd "$SCRIPT_DIR"
- if [[ -n "${1:-}" ]]; then
- echo "→ Whitelabel override: $1"
- VITE_WHITELABEL="$1" npm run build:local
- else
- npm run build:local
- fi
-)
-
-# Fail if the bundle still embeds Crisp (wrong vite mode or stale build/)
-INDEX_HTML="$SCRIPT_DIR/build/index.html"
-if [[ ! -f "$INDEX_HTML" ]]; then
- echo "error: missing $INDEX_HTML after build:local" >&2
- exit 1
-fi
-
-# 2. Build container image
-docker build -t webui:local -f Dockerfile .
-
-# 3. Transfer image
-docker save webui:local | ssh $SSH_OPTS "$DEPLOY_USER@$DEPLOY_HOST" docker load
-
-# 4. SCP compose file
-scp $SSH_OPTS docker-compose.local.yml "$DEPLOY_USER@$DEPLOY_HOST:~/"
-
-# 5. Deploy — then force service recreate. Swarm often keeps the old task when the tag stays
-# webui:local after docker load, so without --force you still see the previous HTML/JS.
-STACK_NAME="${LOCAL_DOCKER_STACK_NAME:-webui}"
-SERVICE_NAME="${STACK_NAME}_webui"
-ssh $SSH_OPTS "$DEPLOY_USER@$DEPLOY_HOST" \
- "docker stack deploy -c docker-compose.local.yml $STACK_NAME && docker service update --force $SERVICE_NAME"
\ No newline at end of file
diff --git a/docker-compose.local.yml b/docker-compose.local.yml
deleted file mode 100644
index b8722cb..0000000
--- a/docker-compose.local.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-version: "3.6"
-
-services:
- webui:
- image: webui:local
- ports:
- - "8080:80"
- stop_signal: SIGQUIT
- deploy:
- replicas: 1
- update_config:
- order: start-first
- healthcheck:
- test: ["CMD", "curl", "-f", "localhost:80/health"]
- interval: 1m
- timeout: 10s
- retries: 3
- start_period: 30s
\ No newline at end of file
diff --git a/indexLocal.html b/indexLocal.html
deleted file mode 100644
index af3867c..0000000
--- a/indexLocal.html
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
- Adaptive Dashboard
-
-
-
-
-
-
-
diff --git a/package-lock.json b/package-lock.json
index 3610bee..dfbde6f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,13 +25,10 @@
"@mui/styles": "^6.1.6",
"@mui/x-date-pickers": "^7.26.0",
"@react-pdf/renderer": "^4.3.0",
- "@react-three/drei": "^9.105.6",
- "@react-three/fiber": "^8.18.0",
"@sentry/react": "^8.38.0",
"@turf/area": "^7.2.0",
"@turf/turf": "^7.2.0",
"@types/classnames": "^2.3.0",
- "@types/three": "^0.183.1",
"axios": "^1.7.7",
"crisp-sdk-web": "^1.0.26",
"dayjs": "^1.11.13",
@@ -55,6 +52,7 @@
"react-dom": "^18.3.1",
"react-emoji-render": "^2.0.1",
"react-error-boundary": "^5.0.0",
+ "react-full-screen": "^1.1.1",
"react-horizontal-scrolling-menu": "^7.1.1",
"react-infinite-scroller": "^1.2.6",
"react-joyride": "^2.9.3",
@@ -64,7 +62,6 @@
"react-virtualized-auto-sizer": "^1.0.25",
"recharts": "^2.15.1",
"semver": "^7.7.1",
- "three": "^0.152.2",
"victory": "^37.3.6",
"weather-icons-react": "^1.2.0"
},
@@ -132,12 +129,12 @@
}
},
"node_modules/@babel/code-frame": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
- "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
"license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.28.5",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
@@ -146,9 +143,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 +153,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",
@@ -204,13 +201,13 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
- "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.29.7",
- "@babel/types": "^7.29.7",
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
@@ -233,14 +230,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"
@@ -337,9 +334,9 @@
}
},
"node_modules/@babel/helper-globals": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
- "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -360,28 +357,28 @@
}
},
"node_modules/@babel/helper-module-imports": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
- "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
"license": "MIT",
"dependencies": {
- "@babel/traverse": "^7.29.7",
- "@babel/types": "^7.29.7"
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
- "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-module-imports": "^7.29.7",
- "@babel/helper-validator-identifier": "^7.29.7",
- "@babel/traverse": "^7.29.7"
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -404,9 +401,9 @@
}
},
"node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
"dev": true,
"license": "MIT",
"engines": {
@@ -464,27 +461,27 @@
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
- "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
- "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"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,26 +504,26 @@
}
},
"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"
}
},
"node_modules/@babel/parser": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
- "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.29.7"
+ "@babel/types": "^7.29.0"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -1103,16 +1100,16 @@
}
},
"node_modules/@babel/plugin-transform-modules-systemjs": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.7.tgz",
- "integrity": "sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz",
+ "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-module-transforms": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/helper-validator-identifier": "^7.29.7",
- "@babel/traverse": "^7.29.7"
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.29.0"
},
"engines": {
"node": ">=6.9.0"
@@ -1689,31 +1686,31 @@
}
},
"node_modules/@babel/template": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
- "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.29.7",
- "@babel/parser": "^7.29.7",
- "@babel/types": "^7.29.7"
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
- "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.29.7",
- "@babel/generator": "^7.29.7",
- "@babel/helper-globals": "^7.29.7",
- "@babel/parser": "^7.29.7",
- "@babel/template": "^7.29.7",
- "@babel/types": "^7.29.7",
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
"debug": "^4.3.1"
},
"engines": {
@@ -1721,13 +1718,13 @@
}
},
"node_modules/@babel/types": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
- "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.29.7",
- "@babel/helper-validator-identifier": "^7.29.7"
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1779,12 +1776,6 @@
}
}
},
- "node_modules/@dimforge/rapier3d-compat": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
- "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==",
- "license": "Apache-2.0"
- },
"node_modules/@emotion/babel-plugin": {
"version": "11.13.5",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
@@ -2480,12 +2471,6 @@
"gl-style-validate": "dist/gl-style-validate.mjs"
}
},
- "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==",
- "license": "Apache-2.0"
- },
"node_modules/@mui/core-downloads-tracker": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.5.0.tgz",
@@ -3065,9 +3050,9 @@
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/utf8": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz",
- "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
"license": "BSD-3-Clause"
},
"node_modules/@react-pdf/fns": {
@@ -3238,223 +3223,10 @@
"@react-pdf/stylesheet": "^6.1.2"
}
},
- "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==",
- "license": "MIT",
- "dependencies": {
- "@react-spring/shared": "~9.7.5",
- "@react-spring/types": "~9.7.5"
- },
- "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==",
- "license": "MIT",
- "dependencies": {
- "@react-spring/animated": "~9.7.5",
- "@react-spring/shared": "~9.7.5",
- "@react-spring/types": "~9.7.5"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/react-spring/donate"
- },
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
- }
- },
- "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==",
- "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==",
- "license": "MIT",
- "dependencies": {
- "@react-spring/rafz": "~9.7.5",
- "@react-spring/types": "~9.7.5"
- },
- "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==",
- "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"
- },
- "peerDependencies": {
- "@react-three/fiber": ">=6.0",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
- "three": ">=0.126"
- }
- },
- "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==",
- "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==",
- "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",
- "cross-env": "^7.0.3",
- "detect-gpu": "^5.0.56",
- "glsl-noise": "^0.0.0",
- "hls.js": "^1.5.17",
- "maath": "^0.10.8",
- "meshline": "^3.3.1",
- "react-composer": "^5.0.3",
- "stats-gl": "^2.2.8",
- "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",
- "tunnel-rat": "^0.1.2",
- "utility-types": "^3.11.0",
- "zustand": "^5.0.1"
- },
- "peerDependencies": {
- "@react-three/fiber": "^8",
- "react": "^18",
- "react-dom": "^18",
- "three": ">=0.137"
- },
- "peerDependenciesMeta": {
- "react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/@react-three/drei/node_modules/@monogrid/gainmap-js": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.4.0.tgz",
- "integrity": "sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==",
- "license": "MIT",
- "dependencies": {
- "promise-worker-transferable": "^1.0.4"
- },
- "peerDependencies": {
- "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",
- "integrity": "sha512-FYZZqD0UUHUswKz3LQl2Z7H24AhD14XGTsIRw3SJaXUxyfVMi+1yiZGmqTcPt/CkPpdU7rrxqcyQ1zJE5DjvIQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.17.8",
- "@types/react-reconciler": "^0.26.7",
- "@types/webxr": "*",
- "base64-js": "^1.5.1",
- "buffer": "^6.0.3",
- "its-fine": "^1.0.6",
- "react-reconciler": "^0.27.0",
- "react-use-measure": "^2.1.7",
- "scheduler": "^0.21.0",
- "suspend-react": "^0.1.3",
- "zustand": "^3.7.1"
- },
- "peerDependencies": {
- "expo": ">=43.0",
- "expo-asset": ">=8.4",
- "expo-file-system": ">=11.0",
- "expo-gl": ">=11.0",
- "react": ">=18 <19",
- "react-dom": ">=18 <19",
- "react-native": ">=0.64",
- "three": ">=0.133"
- },
- "peerDependenciesMeta": {
- "expo": {
- "optional": true
- },
- "expo-asset": {
- "optional": true
- },
- "expo-file-system": {
- "optional": true
- },
- "expo-gl": {
- "optional": true
- },
- "react-dom": {
- "optional": true
- },
- "react-native": {
- "optional": true
- }
- }
- },
- "node_modules/@react-three/fiber/node_modules/scheduler": {
- "version": "0.21.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz",
- "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0"
- }
- },
"node_modules/@remix-run/router": {
- "version": "1.23.3",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz",
- "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==",
+ "version": "1.23.2",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
+ "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
@@ -3467,37 +3239,10 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@rollup/plugin-babel": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.1.0.tgz",
- "integrity": "sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^7.18.6",
- "@rollup/pluginutils": "^5.0.1"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0",
- "@types/babel__core": "^7.1.9",
- "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
- },
- "peerDependenciesMeta": {
- "@types/babel__core": {
- "optional": true
- },
- "rollup": {
- "optional": true
- }
- }
- },
"node_modules/@rollup/plugin-node-resolve": {
- "version": "16.0.3",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz",
- "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==",
+ "version": "15.3.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
+ "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3519,41 +3264,19 @@
}
}
},
- "node_modules/@rollup/plugin-replace": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.3.tgz",
- "integrity": "sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@rollup/pluginutils": "^5.0.1",
- "magic-string": "^0.30.3"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
- },
- "peerDependenciesMeta": {
- "rollup": {
- "optional": true
- }
- }
- },
"node_modules/@rollup/plugin-terser": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-1.0.0.tgz",
- "integrity": "sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==",
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
+ "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "serialize-javascript": "^7.0.3",
+ "serialize-javascript": "^6.0.1",
"smob": "^1.0.0",
"terser": "^5.17.4"
},
"engines": {
- "node": ">=20.0.0"
+ "node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^2.0.0||^3.0.0||^4.0.0"
@@ -3565,9 +3288,9 @@
}
},
"node_modules/@rollup/pluginutils": {
- "version": "5.4.0",
- "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz",
- "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
+ "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3726,6 +3449,29 @@
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
+ "node_modules/@surma/rollup-plugin-off-main-thread": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
+ "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "ejs": "^3.1.6",
+ "json5": "^2.2.0",
+ "magic-string": "^0.25.0",
+ "string.prototype.matchall": "^4.0.6"
+ }
+ },
+ "node_modules/@surma/rollup-plugin-off-main-thread/node_modules/magic-string": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
+ "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sourcemap-codec": "^1.4.8"
+ }
+ },
"node_modules/@swc/helpers": {
"version": "0.5.18",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz",
@@ -3747,22 +3493,6 @@
"node": ">=10"
}
},
- "node_modules/@trickfilm400/rollup-plugin-off-main-thread": {
- "version": "3.0.0-pre1",
- "resolved": "https://registry.npmjs.org/@trickfilm400/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-3.0.0-pre1.tgz",
- "integrity": "sha512-/67zpWDBLV+oYAEL682s1ktXL0HgqX76f6gaVGkGnVZlBbm1zd0v4Bz8MFF2GGhoX9rvfq3KSQHubFHwa6w6/Q==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "ejs": "^3.1.10",
- "json5": "^2.2.3",
- "magic-string": "^0.30.21",
- "string.prototype.matchall": "^4.0.12"
- },
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/@turf/along": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/@turf/along/-/along-7.3.4.tgz",
@@ -5804,12 +5534,6 @@
"url": "https://opencollective.com/turf"
}
},
- "node_modules/@tweenjs/tween.js": {
- "version": "23.1.3",
- "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
- "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
- "license": "MIT"
- },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -5963,12 +5687,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@types/draco3d": {
- "version": "1.4.10",
- "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz",
- "integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==",
- "license": "MIT"
- },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -6128,12 +5846,6 @@
"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
"license": "MIT"
},
- "node_modules/@types/offscreencanvas": {
- "version": "2019.7.3",
- "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
- "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==",
- "license": "MIT"
- },
"node_modules/@types/parse-json": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
@@ -6218,15 +5930,6 @@
"@types/viewport-mercator-project": "*"
}
},
- "node_modules/@types/react-reconciler": {
- "version": "0.26.7",
- "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz",
- "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==",
- "license": "MIT",
- "dependencies": {
- "@types/react": "*"
- }
- },
"node_modules/@types/react-redux": {
"version": "7.1.34",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.34.tgz",
@@ -6281,12 +5984,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@types/stats.js": {
- "version": "0.17.4",
- "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz",
- "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==",
- "license": "MIT"
- },
"node_modules/@types/supercluster": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz",
@@ -6296,21 +5993,6 @@
"@types/geojson": "*"
}
},
- "node_modules/@types/three": {
- "version": "0.183.1",
- "resolved": "https://registry.npmjs.org/@types/three/-/three-0.183.1.tgz",
- "integrity": "sha512-f2Pu5Hrepfgavttdye3PsH5RWyY/AvdZQwIVhrc4uNtvF7nOWJacQKcoVJn0S4f0yYbmAE6AR+ve7xDcuYtMGw==",
- "license": "MIT",
- "dependencies": {
- "@dimforge/rapier3d-compat": "~0.12.0",
- "@tweenjs/tween.js": "~23.1.3",
- "@types/stats.js": "*",
- "@types/webxr": ">=0.5.17",
- "@webgpu/types": "*",
- "fflate": "~0.8.2",
- "meshoptimizer": "~1.0.1"
- }
- },
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
@@ -6328,12 +6010,6 @@
"gl-matrix": "^3.2.0"
}
},
- "node_modules/@types/webxr": {
- "version": "0.5.24",
- "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
- "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
- "license": "MIT"
- },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.56.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.0.tgz",
@@ -6523,9 +6199,9 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
- "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6603,24 +6279,6 @@
"url": "https://opencollective.com/eslint"
}
},
- "node_modules/@use-gesture/core": {
- "version": "10.3.1",
- "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz",
- "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==",
- "license": "MIT"
- },
- "node_modules/@use-gesture/react": {
- "version": "10.3.1",
- "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz",
- "integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==",
- "license": "MIT",
- "dependencies": {
- "@use-gesture/core": "10.3.1"
- },
- "peerDependencies": {
- "react": ">= 16.8.0"
- }
- },
"node_modules/@vis.gl/react-mapbox": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@vis.gl/react-mapbox/-/react-mapbox-8.1.0.tgz",
@@ -6678,15 +6336,15 @@
}
},
"node_modules/@vitest/expect": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.6.tgz",
- "integrity": "sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
+ "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/chai": "^5.2.2",
- "@vitest/spy": "3.2.6",
- "@vitest/utils": "3.2.6",
+ "@vitest/spy": "3.2.4",
+ "@vitest/utils": "3.2.4",
"chai": "^5.2.0",
"tinyrainbow": "^2.0.0"
},
@@ -6695,13 +6353,13 @@
}
},
"node_modules/@vitest/mocker": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.6.tgz",
- "integrity": "sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz",
+ "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "3.2.6",
+ "@vitest/spy": "3.2.4",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.17"
},
@@ -6722,9 +6380,9 @@
}
},
"node_modules/@vitest/pretty-format": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.6.tgz",
- "integrity": "sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz",
+ "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6735,13 +6393,13 @@
}
},
"node_modules/@vitest/runner": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.6.tgz",
- "integrity": "sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz",
+ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/utils": "3.2.6",
+ "@vitest/utils": "3.2.4",
"pathe": "^2.0.3",
"strip-literal": "^3.0.0"
},
@@ -6750,13 +6408,13 @@
}
},
"node_modules/@vitest/snapshot": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.6.tgz",
- "integrity": "sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz",
+ "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "3.2.6",
+ "@vitest/pretty-format": "3.2.4",
"magic-string": "^0.30.17",
"pathe": "^2.0.3"
},
@@ -6765,9 +6423,9 @@
}
},
"node_modules/@vitest/spy": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.6.tgz",
- "integrity": "sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz",
+ "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6778,13 +6436,13 @@
}
},
"node_modules/@vitest/utils": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.6.tgz",
- "integrity": "sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz",
+ "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "3.2.6",
+ "@vitest/pretty-format": "3.2.4",
"loupe": "^3.1.4",
"tinyrainbow": "^2.0.0"
},
@@ -6792,12 +6450,6 @@
"url": "https://opencollective.com/vitest"
}
},
- "node_modules/@webgpu/types": {
- "version": "0.1.69",
- "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz",
- "integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==",
- "license": "BSD-3-Clause"
- },
"node_modules/abs-svg-path": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz",
@@ -6827,18 +6479,6 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
- "node_modules/agent-base": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
- "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
- "license": "MIT",
- "dependencies": {
- "debug": "4"
- },
- "engines": {
- "node": ">= 6.0.0"
- }
- },
"node_modules/ajv": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
@@ -7013,28 +6653,27 @@
}
},
"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.13.5",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
+ "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
"license": "MIT",
"dependencies": {
- "follow-redirects": "^1.16.0",
+ "follow-redirects": "^1.15.11",
"form-data": "^4.0.5",
- "https-proxy-agent": "^5.0.1",
- "proxy-from-env": "^2.1.0"
+ "proxy-from-env": "^1.1.0"
}
},
"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"
@@ -7171,9 +6810,9 @@
}
},
"node_modules/brace-expansion": {
- "version": "1.1.15",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
- "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7243,30 +6882,6 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "node_modules/buffer": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.2.1"
- }
- },
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -7331,15 +6946,15 @@
}
},
"node_modules/call-bind": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
- "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==",
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
+ "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
"dev": true,
"license": "MIT",
"dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "get-intrinsic": "^1.3.0",
+ "call-bind-apply-helpers": "^1.0.0",
+ "es-define-property": "^1.0.0",
+ "get-intrinsic": "^1.2.4",
"set-function-length": "^1.2.2"
},
"engines": {
@@ -7423,15 +7038,6 @@
"node": ">=8"
}
},
- "node_modules/camera-controls": {
- "version": "2.10.1",
- "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-2.10.1.tgz",
- "integrity": "sha512-KnaKdcvkBJ1Irbrzl8XD6WtZltkRjp869Jx8c0ujs9K+9WD+1D7ryBsCiVqJYUqt6i/HR5FxT7RLASieUD+Q5w==",
- "license": "MIT",
- "peerDependencies": {
- "three": ">=0.126.1"
- }
- },
"node_modules/caniuse-lite": {
"version": "1.0.30001770",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz",
@@ -7658,9 +7264,9 @@
}
},
"node_modules/cosmiconfig/node_modules/yaml": {
- "version": "1.10.3",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
- "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"license": "ISC",
"engines": {
"node": ">= 6"
@@ -7672,28 +7278,11 @@
"integrity": "sha512-aNWR3te65YiaVFu/iwdqOo3cyUBZHUheE4d6EtgQu/T18jh/9SpoYXjXF/OzUD3Cqy0pGryoqtuy5gxD8tqX9Q==",
"license": "MIT"
},
- "node_modules/cross-env": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
- "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
- "license": "MIT",
- "dependencies": {
- "cross-spawn": "^7.0.1"
- },
- "bin": {
- "cross-env": "src/bin/cross-env.js",
- "cross-env-shell": "src/bin/cross-env-shell.js"
- },
- "engines": {
- "node": ">=10.14",
- "npm": ">=6",
- "yarn": ">=1"
- }
- },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@@ -8077,9 +7666,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"
@@ -8193,15 +7782,6 @@
"node": ">=0.4.0"
}
},
- "node_modules/detect-gpu": {
- "version": "5.0.70",
- "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz",
- "integrity": "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==",
- "license": "MIT",
- "dependencies": {
- "webgl-constants": "^1.1.1"
- }
- },
"node_modules/dfa": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz",
@@ -8209,9 +7789,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"
@@ -8236,12 +7816,6 @@
"url": "https://github.com/sponsors/panva"
}
},
- "node_modules/draco3d": {
- "version": "1.5.7",
- "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz",
- "integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==",
- "license": "Apache-2.0"
- },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -8316,9 +7890,9 @@
}
},
"node_modules/es-abstract": {
- "version": "1.24.2",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz",
- "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==",
+ "version": "1.24.1",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz",
+ "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8384,25 +7958,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",
@@ -8435,9 +7990,9 @@
"license": "MIT"
},
"node_modules/es-object-atoms": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
- "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -8462,18 +8017,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"
@@ -8733,19 +8285,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/eta": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/eta/-/eta-4.6.0.tgz",
- "integrity": "sha512-lW6is4T1NFOYnmqGZIfvixqj7A7sSvScF+DN8EK6K58xI5MZ5UvYe0GjopxOXQtZvUn4eDdVuZ8XSoYWTMEKwA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/bgub/eta?sponsor=1"
- }
- },
"node_modules/eventemitter3": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
@@ -8813,9 +8352,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.0",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
+ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
"dev": true,
"funding": [
{
@@ -8847,12 +8386,6 @@
}
}
},
- "node_modules/fflate": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
- "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
- "license": "MIT"
- },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -8867,9 +8400,9 @@
}
},
"node_modules/filelist": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
- "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
+ "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -8877,9 +8410,9 @@
}
},
"node_modules/filelist/node_modules/brace-expansion": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
- "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8949,16 +8482,16 @@
}
},
"node_modules/flatted": {
- "version": "3.4.2",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
- "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
"dev": true,
"license": "ISC"
},
"node_modules/follow-redirects": {
- "version": "1.16.0",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
- "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
@@ -9026,15 +8559,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": {
@@ -9084,20 +8617,11 @@
"node": ">=10"
}
},
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
+ "node_modules/fscreen": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/fscreen/-/fscreen-1.2.0.tgz",
+ "integrity": "sha512-hlq4+BU0hlPmwsFjwGGzZ+OZ9N/wq9Ljg/sq3pX+2CD7hrJsX9tJgWWK/wiNTFM212CLHWhicOoqwXyZGGetJg==",
+ "license": "MIT"
},
"node_modules/function-bind": {
"version": "1.1.2",
@@ -9109,21 +8633,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,16 +8889,16 @@
}
},
"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.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz",
+ "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
- "node": "18 || 20 || >=22"
+ "node": "20 || >=22"
}
},
"node_modules/glob/node_modules/minimatch": {
@@ -9433,12 +8954,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/glsl-noise": {
- "version": "0.0.0",
- "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz",
- "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==",
- "license": "MIT"
- },
"node_modules/goober": {
"version": "2.1.18",
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz",
@@ -9587,9 +9102,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"
@@ -9598,12 +9113,6 @@
"node": ">= 0.4"
}
},
- "node_modules/hls.js": {
- "version": "1.6.16",
- "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.16.tgz",
- "integrity": "sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==",
- "license": "Apache-2.0"
- },
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@@ -9683,19 +9192,6 @@
"node": ">=10.19.0"
}
},
- "node_modules/https-proxy-agent": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
- "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
- "license": "MIT",
- "dependencies": {
- "agent-base": "6",
- "debug": "4"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/hyphen": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/hyphen/-/hyphen-1.14.1.tgz",
@@ -9715,26 +9211,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/ieee754": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
- "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "BSD-3-Clause"
- },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -9745,12 +9221,6 @@
"node": ">= 4"
}
},
- "node_modules/immediate": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
- "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
- "license": "MIT"
- },
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -9965,22 +9435,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",
@@ -10142,12 +9596,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/is-promise": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
- "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
- "license": "MIT"
- },
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -10333,6 +9781,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
"license": "ISC"
},
"node_modules/isobject": {
@@ -10344,27 +9793,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/its-fine": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.2.5.tgz",
- "integrity": "sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==",
- "license": "MIT",
- "dependencies": {
- "@types/react-reconciler": "^0.28.0"
- },
- "peerDependencies": {
- "react": ">=18.0"
- }
- },
- "node_modules/its-fine/node_modules/@types/react-reconciler": {
- "version": "0.28.9",
- "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz",
- "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==",
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "*"
- }
- },
"node_modules/jackspeak": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz",
@@ -10424,20 +9852,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 +9877,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"
@@ -10705,15 +10123,6 @@
"integrity": "sha512-rDU6bkpuMs8YRt/UpkuYEAsYSoNuDEbrE41I3KNvmXREGH6DGBJ8Wbak4by29wNOQ27zk4g4HL82zf0OGhwRuw==",
"license": "MIT"
},
- "node_modules/lie": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
- "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
- "license": "MIT",
- "dependencies": {
- "immediate": "~3.0.5"
- }
- },
"node_modules/linebreak": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz",
@@ -10756,15 +10165,15 @@
}
},
"node_modules/lodash": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
- "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
+ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT"
},
"node_modules/lodash-es": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
- "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
+ "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
"license": "MIT"
},
"node_modules/lodash.debounce": {
@@ -10861,16 +10270,6 @@
"yallist": "^3.0.2"
}
},
- "node_modules/maath": {
- "version": "0.10.8",
- "resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz",
- "integrity": "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==",
- "license": "MIT",
- "peerDependencies": {
- "@types/three": ">=0.134.0",
- "three": ">=0.134.0"
- }
- },
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -11015,21 +10414,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/meshline": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz",
- "integrity": "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==",
- "license": "MIT",
- "peerDependencies": {
- "three": ">=0.137"
- }
- },
- "node_modules/meshoptimizer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.0.1.tgz",
- "integrity": "sha512-Vix+QlA1YYT3FwmBBZ+49cE5y/b+pRrcXKqGpS5ouh33d3lSp2PoTpCw19E0cKDFWalembrHnIaZetf27a+W2g==",
- "license": "MIT"
- },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -11561,6 +10945,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -11644,9 +11029,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -11709,9 +11094,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.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"dev": true,
"funding": [
{
@@ -11729,7 +11114,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.12",
+ "nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -11744,9 +11129,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.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"funding": [
{
@@ -11801,16 +11186,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/promise-worker-transferable": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz",
- "integrity": "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==",
- "license": "Apache-2.0",
- "dependencies": {
- "is-promise": "^2.1.0",
- "lie": "^3.0.2"
- }
- },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -11830,15 +11205,15 @@
},
"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#40e15b5ed18b10c97125260a2d53a81d1eec398e",
"dependencies": {
"protobufjs": "^6.8.8"
}
},
"node_modules/protobufjs": {
- "version": "6.11.6",
- "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.6.tgz",
- "integrity": "sha512-k8BHqgPBOtrlougZZqF2uUk5Z7bN8f0wj+3e8M3hvtSv0NBAz4VBy5f6R5Nxq/l+i7mRFTgNZb2trxqTpHNY/A==",
+ "version": "6.11.4",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz",
+ "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -11862,19 +11237,16 @@
}
},
"node_modules/protocol-buffers-schema": {
- "version": "3.6.1",
- "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz",
- "integrity": "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==",
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
+ "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==",
"license": "MIT"
},
"node_modules/proxy-from-env": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
- "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- }
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
},
"node_modules/pump": {
"version": "3.0.3",
@@ -11946,6 +11318,16 @@
"integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==",
"license": "MIT"
},
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
"node_modules/rbush": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz",
@@ -12011,18 +11393,6 @@
"react": "*"
}
},
- "node_modules/react-composer": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/react-composer/-/react-composer-5.0.3.tgz",
- "integrity": "sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==",
- "license": "MIT",
- "dependencies": {
- "prop-types": "^15.6.0"
- },
- "peerDependencies": {
- "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
- }
- },
"node_modules/react-day-picker": {
"version": "9.13.2",
"resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.13.2.tgz",
@@ -12146,6 +11516,21 @@
"is-lite": "^0.8.2"
}
},
+ "node_modules/react-full-screen": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/react-full-screen/-/react-full-screen-1.1.1.tgz",
+ "integrity": "sha512-xoEgkoTiN0dw9cjYYGViiMCBYbkS97BYb4bHPhQVWXj1UnOs8PZ1rPzpX+2HMhuvQV1jA5AF9GaRbO3fA5aZtg==",
+ "license": "MIT",
+ "dependencies": {
+ "fscreen": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0"
+ }
+ },
"node_modules/react-horizontal-scrolling-menu": {
"version": "7.1.8",
"resolved": "https://registry.npmjs.org/react-horizontal-scrolling-menu/-/react-horizontal-scrolling-menu-7.1.8.tgz",
@@ -12278,31 +11663,6 @@
"react-dom": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0"
}
},
- "node_modules/react-reconciler": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.27.0.tgz",
- "integrity": "sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0",
- "scheduler": "^0.21.0"
- },
- "engines": {
- "node": ">=0.10.0"
- },
- "peerDependencies": {
- "react": "^18.0.0"
- }
- },
- "node_modules/react-reconciler/node_modules/scheduler": {
- "version": "0.21.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz",
- "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0"
- }
- },
"node_modules/react-redux": {
"version": "7.2.9",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
@@ -12345,12 +11705,12 @@
}
},
"node_modules/react-router": {
- "version": "6.30.4",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz",
- "integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==",
+ "version": "6.30.3",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
+ "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
"license": "MIT",
"dependencies": {
- "@remix-run/router": "1.23.3"
+ "@remix-run/router": "1.23.2"
},
"engines": {
"node": ">=14.0.0"
@@ -12360,13 +11720,13 @@
}
},
"node_modules/react-router-dom": {
- "version": "6.30.4",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.4.tgz",
- "integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==",
+ "version": "6.30.3",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
+ "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
"license": "MIT",
"dependencies": {
- "@remix-run/router": "1.23.3",
- "react-router": "6.30.4"
+ "@remix-run/router": "1.23.2",
+ "react-router": "6.30.3"
},
"engines": {
"node": ">=14.0.0"
@@ -12407,21 +11767,6 @@
"react-dom": ">=16.6.0"
}
},
- "node_modules/react-use-measure": {
- "version": "2.1.7",
- "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz",
- "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==",
- "license": "MIT",
- "peerDependencies": {
- "react": ">=16.13",
- "react-dom": ">=16.13"
- },
- "peerDependenciesMeta": {
- "react-dom": {
- "optional": true
- }
- }
- },
"node_modules/react-virtualized-auto-sizer": {
"version": "1.0.26",
"resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.26.tgz",
@@ -12867,15 +12212,15 @@
"license": "BSD-3-Clause"
},
"node_modules/safe-array-concat": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz",
- "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
+ "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "call-bind": "^1.0.9",
- "call-bound": "^1.0.4",
- "get-intrinsic": "^1.3.0",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
"has-symbols": "^1.1.0",
"isarray": "^2.0.5"
},
@@ -12981,13 +12326,13 @@
}
},
"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": "6.0.2",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
+ "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
"dev": true,
"license": "BSD-3-Clause",
- "engines": {
- "node": ">=20.0.0"
+ "dependencies": {
+ "randombytes": "^2.1.0"
}
},
"node_modules/set-function-length": {
@@ -13058,6 +12403,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
@@ -13070,21 +12416,22 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/side-channel": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
- "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
- "object-inspect": "^1.13.4",
- "side-channel-list": "^1.0.1",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
@@ -13096,14 +12443,14 @@
}
},
"node_modules/side-channel-list": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
- "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
- "object-inspect": "^1.13.4"
+ "object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
@@ -13193,9 +12540,9 @@
"license": "MIT"
},
"node_modules/smob": {
- "version": "1.6.2",
- "resolved": "https://registry.npmjs.org/smob/-/smob-1.6.2.tgz",
- "integrity": "sha512-RQsvleCbF8cVHEv+xuDGaA4pOizFqJ0GgjtMSRo6oP8pnN7WsigHgVGey6aILRBKv4W2YOMHLqbKdnB6hpB9fw==",
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/smob/-/smob-1.6.1.tgz",
+ "integrity": "sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==",
"dev": true,
"license": "MIT",
"engines": {
@@ -13286,6 +12633,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/sourcemap-codec": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "deprecated": "Please use @jridgewell/sourcemap-codec instead",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/spdx-correct": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
@@ -13386,32 +12741,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/stats-gl": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-2.4.2.tgz",
- "integrity": "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==",
- "license": "MIT",
- "dependencies": {
- "@types/three": "*",
- "three": "^0.170.0"
- },
- "peerDependencies": {
- "@types/three": "*",
- "three": "*"
- }
- },
- "node_modules/stats-gl/node_modules/three": {
- "version": "0.170.0",
- "resolved": "https://registry.npmjs.org/three/-/three-0.170.0.tgz",
- "integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==",
- "license": "MIT"
- },
- "node_modules/stats.js": {
- "version": "0.17.0",
- "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz",
- "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==",
- "license": "MIT"
- },
"node_modules/std-env": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
@@ -13482,20 +12811,19 @@
}
},
"node_modules/string.prototype.trim": {
- "version": "1.2.11",
- "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz",
- "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==",
+ "version": "1.2.10",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
+ "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "call-bind": "^1.0.9",
- "call-bound": "^1.0.4",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
"define-data-property": "^1.1.4",
"define-properties": "^1.2.1",
- "es-abstract": "^1.24.2",
- "es-object-atoms": "^1.1.2",
- "has-property-descriptors": "^1.0.2",
- "safe-regex-test": "^1.1.0"
+ "es-abstract": "^1.23.5",
+ "es-object-atoms": "^1.0.0",
+ "has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
@@ -13505,16 +12833,16 @@
}
},
"node_modules/string.prototype.trimend": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz",
- "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==",
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
+ "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "call-bind": "^1.0.9",
- "call-bound": "^1.0.4",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
"define-properties": "^1.2.1",
- "es-object-atoms": "^1.1.2"
+ "es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
@@ -13667,15 +12995,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/suspend-react": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz",
- "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==",
- "license": "MIT",
- "peerDependencies": {
- "react": ">=17.0"
- }
- },
"node_modules/svg-arc-to-cubic-bezier": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz",
@@ -13752,51 +13071,6 @@
"node": ">=10"
}
},
- "node_modules/three": {
- "version": "0.152.2",
- "resolved": "https://registry.npmjs.org/three/-/three-0.152.2.tgz",
- "integrity": "sha512-Ff9zIpSfkkqcBcpdiFo2f35vA9ZucO+N8TNacJOqaEE6DrB0eufItVMib8bK8Pcju/ZNT6a7blE1GhTpkdsILw==",
- "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.",
- "license": "MIT",
- "peerDependencies": {
- "three": ">= 0.151.0"
- }
- },
- "node_modules/three-stdlib": {
- "version": "2.36.1",
- "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.36.1.tgz",
- "integrity": "sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==",
- "license": "MIT",
- "dependencies": {
- "@types/draco3d": "^1.4.0",
- "@types/offscreencanvas": "^2019.6.4",
- "@types/webxr": "^0.5.2",
- "draco3d": "^1.4.1",
- "fflate": "^0.6.9",
- "potpack": "^1.0.1"
- },
- "peerDependencies": {
- "three": ">=0.128.0"
- }
- },
- "node_modules/three-stdlib/node_modules/fflate": {
- "version": "0.6.10",
- "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz",
- "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==",
- "license": "MIT"
- },
- "node_modules/three-stdlib/node_modules/potpack": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
- "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==",
- "license": "ISC"
- },
"node_modules/tiny-inflate": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
@@ -13939,36 +13213,6 @@
"node": ">=8"
}
},
- "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==",
- "license": "MIT",
- "dependencies": {
- "bidi-js": "^1.0.2",
- "troika-three-utils": "^0.52.4",
- "troika-worker-utils": "^0.52.0",
- "webgl-sdf-generator": "1.1.1"
- },
- "peerDependencies": {
- "three": ">=0.125.0"
- }
- },
- "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==",
- "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==",
- "license": "MIT"
- },
"node_modules/ts-api-utils": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
@@ -14009,43 +13253,6 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
- "node_modules/tunnel-rat": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/tunnel-rat/-/tunnel-rat-0.1.2.tgz",
- "integrity": "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==",
- "license": "MIT",
- "dependencies": {
- "zustand": "^4.3.2"
- }
- },
- "node_modules/tunnel-rat/node_modules/zustand": {
- "version": "4.5.7",
- "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
- "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
- "license": "MIT",
- "dependencies": {
- "use-sync-external-store": "^1.2.2"
- },
- "engines": {
- "node": ">=12.7.0"
- },
- "peerDependencies": {
- "@types/react": ">=16.8",
- "immer": ">=9.0.6",
- "react": ">=16.8"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "immer": {
- "optional": true
- },
- "react": {
- "optional": true
- }
- }
- },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -14129,18 +13336,18 @@
}
},
"node_modules/typed-array-length": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz",
- "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz",
+ "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "call-bind": "^1.0.9",
- "for-each": "^0.3.5",
- "gopd": "^1.2.0",
- "is-typed-array": "^1.1.15",
- "possible-typed-array-names": "^1.1.0",
- "reflect.getprototypeof": "^1.0.10"
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "is-typed-array": "^1.1.13",
+ "possible-typed-array-names": "^1.0.0",
+ "reflect.getprototypeof": "^1.0.6"
},
"engines": {
"node": ">= 0.4"
@@ -14396,30 +13603,12 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
- "node_modules/use-sync-external-store": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
- "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
- "license": "MIT",
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- }
- },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
- "node_modules/utility-types": {
- "version": "3.11.0",
- "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz",
- "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==",
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
"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",
@@ -15213,9 +14402,9 @@
}
},
"node_modules/vite": {
- "version": "6.4.3",
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
- "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
+ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -15376,20 +14565,20 @@
}
},
"node_modules/vitest": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.6.tgz",
- "integrity": "sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
+ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/chai": "^5.2.2",
- "@vitest/expect": "3.2.6",
- "@vitest/mocker": "3.2.6",
- "@vitest/pretty-format": "^3.2.6",
- "@vitest/runner": "3.2.6",
- "@vitest/snapshot": "3.2.6",
- "@vitest/spy": "3.2.6",
- "@vitest/utils": "3.2.6",
+ "@vitest/expect": "3.2.4",
+ "@vitest/mocker": "3.2.4",
+ "@vitest/pretty-format": "^3.2.4",
+ "@vitest/runner": "3.2.4",
+ "@vitest/snapshot": "3.2.4",
+ "@vitest/spy": "3.2.4",
+ "@vitest/utils": "3.2.4",
"chai": "^5.2.0",
"debug": "^4.4.1",
"expect-type": "^1.2.1",
@@ -15419,8 +14608,8 @@
"@edge-runtime/vm": "*",
"@types/debug": "^4.1.12",
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
- "@vitest/browser": "3.2.6",
- "@vitest/ui": "3.2.6",
+ "@vitest/browser": "3.2.4",
+ "@vitest/ui": "3.2.4",
"happy-dom": "*",
"jsdom": "*"
},
@@ -15454,17 +14643,6 @@
"integrity": "sha512-VlLyZb9rb0Ir/NvC6T3YOcnftzUMg8hDYMb9xf1pzzM5z5wKogL6q1wZeqE5e+oHSw0CVcTFtYCbSa1ZEwnDZQ==",
"license": "MIT"
},
- "node_modules/webgl-constants": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz",
- "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg=="
- },
- "node_modules/webgl-sdf-generator": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz",
- "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==",
- "license": "MIT"
- },
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@@ -15491,6 +14669,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
@@ -15570,14 +14749,14 @@
}
},
"node_modules/which-typed-array": {
- "version": "1.1.22",
- "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz",
- "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==",
+ "version": "1.1.20",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz",
+ "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==",
"dev": true,
"license": "MIT",
"dependencies": {
"available-typed-arrays": "^1.0.7",
- "call-bind": "^1.0.9",
+ "call-bind": "^1.0.8",
"call-bound": "^1.0.4",
"for-each": "^0.3.5",
"get-proto": "^1.0.1",
@@ -15619,30 +14798,30 @@
}
},
"node_modules/workbox-background-sync": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.4.1.tgz",
- "integrity": "sha512-HhT7KE8tOWDm02wRNshXUnUPofMlhenF2DBdUnDPOubhizzPeItkYTmAB6td1Z2cjYPa98vzEiPLEuzn5hN66g==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.4.0.tgz",
+ "integrity": "sha512-8CB9OxKAgKZKyNMwfGZ1XESx89GryWTfI+V5yEj8sHjFH8MFelUwYXEyldEK6M6oKMmn807GoJFUEA1sC4XS9w==",
"dev": true,
"license": "MIT",
"dependencies": {
"idb": "^7.0.1",
- "workbox-core": "7.4.1"
+ "workbox-core": "7.4.0"
}
},
"node_modules/workbox-broadcast-update": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.4.1.tgz",
- "integrity": "sha512-uAlgslKLvbQY+suirIdnBCSYrcgBhjp81Nj4l1lj/Jmj0MJO2CJERnCJjT0GFVwmReV0N+zs78K6gqd5gr9/+A==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.4.0.tgz",
+ "integrity": "sha512-+eZQwoktlvo62cI0b+QBr40v5XjighxPq3Fzo9AWMiAosmpG5gxRHgTbGGhaJv/q/MFVxwFNGh/UwHZ/8K88lA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "workbox-core": "7.4.1"
+ "workbox-core": "7.4.0"
}
},
"node_modules/workbox-build": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.4.1.tgz",
- "integrity": "sha512-SDhxIvEAde9Gy/5w4Yo1Jh/M49Z0qE3q0oteyE8zGq0DScxFqVBcCtIXFuLtmtxRQZCMbf0prco4VyEu3KBQuw==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.4.0.tgz",
+ "integrity": "sha512-Ntk1pWb0caOFIvwz/hfgrov/OJ45wPEhI5PbTywQcYjyZiVhT3UrwwUPl6TRYbTm4moaFYithYnl1lvZ8UjxcA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -15650,39 +14829,39 @@
"@babel/core": "^7.24.4",
"@babel/preset-env": "^7.11.0",
"@babel/runtime": "^7.11.2",
- "@rollup/plugin-babel": "^6.1.0",
- "@rollup/plugin-node-resolve": "^16.0.3",
- "@rollup/plugin-replace": "^6.0.3",
- "@rollup/plugin-terser": "^1.0.0",
- "@trickfilm400/rollup-plugin-off-main-thread": "^3.0.0-pre1",
+ "@rollup/plugin-babel": "^5.2.0",
+ "@rollup/plugin-node-resolve": "^15.2.3",
+ "@rollup/plugin-replace": "^2.4.1",
+ "@rollup/plugin-terser": "^0.4.3",
+ "@surma/rollup-plugin-off-main-thread": "^2.2.3",
"ajv": "^8.6.0",
"common-tags": "^1.8.0",
- "eta": "^4.5.1",
"fast-json-stable-stringify": "^2.1.0",
"fs-extra": "^9.0.1",
"glob": "^11.0.1",
+ "lodash": "^4.17.20",
"pretty-bytes": "^5.3.0",
- "rollup": "^4.53.3",
+ "rollup": "^2.79.2",
"source-map": "^0.8.0-beta.0",
"stringify-object": "^3.3.0",
"strip-comments": "^2.0.1",
"tempy": "^0.6.0",
"upath": "^1.2.0",
- "workbox-background-sync": "7.4.1",
- "workbox-broadcast-update": "7.4.1",
- "workbox-cacheable-response": "7.4.1",
- "workbox-core": "7.4.1",
- "workbox-expiration": "7.4.1",
- "workbox-google-analytics": "7.4.1",
- "workbox-navigation-preload": "7.4.1",
- "workbox-precaching": "7.4.1",
- "workbox-range-requests": "7.4.1",
- "workbox-recipes": "7.4.1",
- "workbox-routing": "7.4.1",
- "workbox-strategies": "7.4.1",
- "workbox-streams": "7.4.1",
- "workbox-sw": "7.4.1",
- "workbox-window": "7.4.1"
+ "workbox-background-sync": "7.4.0",
+ "workbox-broadcast-update": "7.4.0",
+ "workbox-cacheable-response": "7.4.0",
+ "workbox-core": "7.4.0",
+ "workbox-expiration": "7.4.0",
+ "workbox-google-analytics": "7.4.0",
+ "workbox-navigation-preload": "7.4.0",
+ "workbox-precaching": "7.4.0",
+ "workbox-range-requests": "7.4.0",
+ "workbox-recipes": "7.4.0",
+ "workbox-routing": "7.4.0",
+ "workbox-strategies": "7.4.0",
+ "workbox-streams": "7.4.0",
+ "workbox-sw": "7.4.0",
+ "workbox-window": "7.4.0"
},
"engines": {
"node": ">=20.0.0"
@@ -15706,6 +14885,69 @@
"ajv": ">=8"
}
},
+ "node_modules/workbox-build/node_modules/@rollup/plugin-babel": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
+ "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.10.4",
+ "@rollup/pluginutils": "^3.1.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0",
+ "@types/babel__core": "^7.1.9",
+ "rollup": "^1.20.0||^2.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/babel__core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/workbox-build/node_modules/@rollup/plugin-replace": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz",
+ "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^3.1.0",
+ "magic-string": "^0.25.7"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0 || ^2.0.0"
+ }
+ },
+ "node_modules/workbox-build/node_modules/@rollup/pluginutils": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
+ "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "0.0.39",
+ "estree-walker": "^1.0.1",
+ "picomatch": "^2.2.2"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0"
+ }
+ },
+ "node_modules/workbox-build/node_modules/@types/estree": {
+ "version": "0.0.39",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
+ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/workbox-build/node_modules/ajv": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
@@ -15723,6 +14965,13 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/workbox-build/node_modules/estree-walker": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
+ "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/workbox-build/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
@@ -15730,6 +14979,29 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/workbox-build/node_modules/magic-string": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
+ "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sourcemap-codec": "^1.4.8"
+ }
+ },
+ "node_modules/workbox-build/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/workbox-build/node_modules/pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
@@ -15743,6 +15015,22 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/workbox-build/node_modules/rollup": {
+ "version": "2.80.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz",
+ "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
"node_modules/workbox-build/node_modules/source-map": {
"version": "0.8.0-beta.0",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz",
@@ -15787,140 +15075,140 @@
}
},
"node_modules/workbox-cacheable-response": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.4.1.tgz",
- "integrity": "sha512-8xaFoJdDc2OjrlbbL3gEeBO1WKcMwRqwLRupgqahYXu75yXajPLuwrbXMrIGZuWYXrQwk0xDjOxZ/ujCy/oJYw==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.4.0.tgz",
+ "integrity": "sha512-0Fb8795zg/x23ISFkAc7lbWes6vbw34DGFIMw31cwuHPgDEC/5EYm6m/ZkylLX0EnEbbOyOCLjKgFS/Z5g0HeQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "workbox-core": "7.4.1"
+ "workbox-core": "7.4.0"
}
},
"node_modules/workbox-core": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.4.1.tgz",
- "integrity": "sha512-DT+vu46eh/2vRsSHTY4Xmc32Z1rr9PRlQUXr1Dx30ZuXRWwOsvZgGgcwxcasubQLQmbTNYZjv44LkBAQ4tT5tQ==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.4.0.tgz",
+ "integrity": "sha512-6BMfd8tYEnN4baG4emG9U0hdXM4gGuDU3ectXuVHnj71vwxTFI7WOpQJC4siTOlVtGqCUtj0ZQNsrvi6kZZTAQ==",
"dev": true,
"license": "MIT"
},
"node_modules/workbox-expiration": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.4.1.tgz",
- "integrity": "sha512-lRKUF7b+OGbeXkQk1s6MHXOa3d7Xxf7Of31W6c6hCfipfIyrtdWZ89stq21AHZMaoG7VNFoHply4Ox+rU31TWg==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.4.0.tgz",
+ "integrity": "sha512-V50p4BxYhtA80eOvulu8xVfPBgZbkxJ1Jr8UUn0rvqjGhLDqKNtfrDfjJKnLz2U8fO2xGQJTx/SKXNTzHOjnHw==",
"dev": true,
"license": "MIT",
"dependencies": {
"idb": "^7.0.1",
- "workbox-core": "7.4.1"
+ "workbox-core": "7.4.0"
}
},
"node_modules/workbox-google-analytics": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.4.1.tgz",
- "integrity": "sha512-Mks1JwLEt++ZAkF6sS1OpSh9RtAMIsiDgRpK+codiHGIPXeaUOgi4cPc3GFadUl8V5QPeypEk8Oxgl3HlwVzHw==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.4.0.tgz",
+ "integrity": "sha512-MVPXQslRF6YHkzGoFw1A4GIB8GrKym/A5+jYDUSL+AeJw4ytQGrozYdiZqUW1TPQHW8isBCBtyFJergUXyNoWQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "workbox-background-sync": "7.4.1",
- "workbox-core": "7.4.1",
- "workbox-routing": "7.4.1",
- "workbox-strategies": "7.4.1"
+ "workbox-background-sync": "7.4.0",
+ "workbox-core": "7.4.0",
+ "workbox-routing": "7.4.0",
+ "workbox-strategies": "7.4.0"
}
},
"node_modules/workbox-navigation-preload": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.4.1.tgz",
- "integrity": "sha512-C4KVsjPcYKJOhr631AxR9XoG2rLF3QiTk5aMv36MXOjtWvm8axwNFAtKUPGsWUwLXXAMgYM1En7fsvndaXeXRQ==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.4.0.tgz",
+ "integrity": "sha512-etzftSgdQfjMcfPgbfaZCfM2QuR1P+4o8uCA2s4rf3chtKTq/Om7g/qvEOcZkG6v7JZOSOxVYQiOu6PbAZgU6w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "workbox-core": "7.4.1"
+ "workbox-core": "7.4.0"
}
},
"node_modules/workbox-precaching": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.4.1.tgz",
- "integrity": "sha512-cdr/9qByww7yzEp7zg/qI4ukUrrNjQLgN+ONQRpjy/VqGQXwkgHwr00KksGJK8v0VifwDXBb8a4cWNZH71jn3Q==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.4.0.tgz",
+ "integrity": "sha512-VQs37T6jDqf1rTxUJZXRl3yjZMf5JX/vDPhmx2CPgDDKXATzEoqyRqhYnRoxl6Kr0rqaQlp32i9rtG5zTzIlNg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "workbox-core": "7.4.1",
- "workbox-routing": "7.4.1",
- "workbox-strategies": "7.4.1"
+ "workbox-core": "7.4.0",
+ "workbox-routing": "7.4.0",
+ "workbox-strategies": "7.4.0"
}
},
"node_modules/workbox-range-requests": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.4.1.tgz",
- "integrity": "sha512-7i2oxAUE82gHdAJBCAQ04JzNOdRPqzuOzGfoUyJpFSmeqBNYGPrAH8GPoPjUQTfp+NycwrD2H68VtuF8qxv0vQ==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.4.0.tgz",
+ "integrity": "sha512-3Vq854ZNuP6Y0KZOQWLaLC9FfM7ZaE+iuQl4VhADXybwzr4z/sMmnLgTeUZLq5PaDlcJBxYXQ3U91V7dwAIfvw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "workbox-core": "7.4.1"
+ "workbox-core": "7.4.0"
}
},
"node_modules/workbox-recipes": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.4.1.tgz",
- "integrity": "sha512-gnbVfmV4/TtmQaM4x9AtuXhcdstJsep3XMVeztOrQVPT+R6+6DeBjGTCQ7fFCXm+4GEHUA5VEBTyi5+4gWGeog==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.4.0.tgz",
+ "integrity": "sha512-kOkWvsAn4H8GvAkwfJTbwINdv4voFoiE9hbezgB1sb/0NLyTG4rE7l6LvS8lLk5QIRIto+DjXLuAuG3Vmt3cxQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "workbox-cacheable-response": "7.4.1",
- "workbox-core": "7.4.1",
- "workbox-expiration": "7.4.1",
- "workbox-precaching": "7.4.1",
- "workbox-routing": "7.4.1",
- "workbox-strategies": "7.4.1"
+ "workbox-cacheable-response": "7.4.0",
+ "workbox-core": "7.4.0",
+ "workbox-expiration": "7.4.0",
+ "workbox-precaching": "7.4.0",
+ "workbox-routing": "7.4.0",
+ "workbox-strategies": "7.4.0"
}
},
"node_modules/workbox-routing": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.4.1.tgz",
- "integrity": "sha512-yubJGErZOusuidAenaL5ypfhQOa7urxP/f8E0ws7FPb4039RiWXUWBAyUkmUoOL/BcQGen3h0J8872d51IYxtA==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.4.0.tgz",
+ "integrity": "sha512-C/ooj5uBWYAhAqwmU8HYQJdOjjDKBp9MzTQ+otpMmd+q0eF59K+NuXUek34wbL0RFrIXe/KKT+tUWcZcBqxbHQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "workbox-core": "7.4.1"
+ "workbox-core": "7.4.0"
}
},
"node_modules/workbox-strategies": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.4.1.tgz",
- "integrity": "sha512-GZxpaw9NbmOelj7667uZ2kpk5BFpOGbO4X0qjwh5ls8XQ8C+Lha5LQchTiUzsTFSS+NlUpftYAyOVXvQUrcqOQ==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.4.0.tgz",
+ "integrity": "sha512-T4hVqIi5A4mHi92+5EppMX3cLaVywDp8nsyUgJhOZxcfSV/eQofcOA6/EMo5rnTNmNTpw0rUgjAI6LaVullPpg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "workbox-core": "7.4.1"
+ "workbox-core": "7.4.0"
}
},
"node_modules/workbox-streams": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.4.1.tgz",
- "integrity": "sha512-HWWtraKUbJknd9kgqGcpQ3G114HOPYvqs8HaJMDs2ebLNAimDkVDaWfAXE6Ybl+m8U6KsCE6pWyLYuigWmnAXw==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.4.0.tgz",
+ "integrity": "sha512-QHPBQrey7hQbnTs5GrEVoWz7RhHJXnPT+12qqWM378orDMo5VMJLCkCM1cnCk+8Eq92lccx/VgRZ7WAzZWbSLg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "workbox-core": "7.4.1",
- "workbox-routing": "7.4.1"
+ "workbox-core": "7.4.0",
+ "workbox-routing": "7.4.0"
}
},
"node_modules/workbox-sw": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.4.1.tgz",
- "integrity": "sha512-fez5f2DUlDJWTFYkCWQpY10N8gtztd849NswCbVFk0QlcSM4HT5A8x4g4ii650yem4I8tHY0R7JZahwp3ltIPw==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.4.0.tgz",
+ "integrity": "sha512-ltU+Kr3qWR6BtbdlMnCjobZKzeV1hN+S6UvDywBrwM19TTyqA03X66dzw1tEIdJvQ4lYKkBFox6IAEhoSEZ8Xw==",
"dev": true,
"license": "MIT"
},
"node_modules/workbox-window": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.4.1.tgz",
- "integrity": "sha512-notZDH2u8VXaqyuD7xaqIfEFi6SRM4SUSd7ewe9PDsVqADuepxX2ZMY3uvuZGxzY5ZOsGC/vD3A/3smFtJt4/A==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.4.0.tgz",
+ "integrity": "sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/trusted-types": "^2.0.2",
- "workbox-core": "7.4.1"
+ "workbox-core": "7.4.0"
}
},
"node_modules/wrappy": {
@@ -15946,9 +15234,9 @@
"license": "ISC"
},
"node_modules/yaml": {
- "version": "2.9.0",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
- "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
+ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
"dev": true,
"license": "ISC",
"optional": true,
@@ -15990,23 +15278,6 @@
"resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz",
"integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==",
"license": "MIT"
- },
- "node_modules/zustand": {
- "version": "3.7.2",
- "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz",
- "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==",
- "license": "MIT",
- "engines": {
- "node": ">=12.7.0"
- },
- "peerDependencies": {
- "react": ">=16.8"
- },
- "peerDependenciesMeta": {
- "react": {
- "optional": true
- }
- }
}
}
}
diff --git a/package.json b/package.json
index 76ac48b..7f5044c 100644
--- a/package.json
+++ b/package.json
@@ -5,10 +5,10 @@
"type": "module",
"scripts": {
"start": "VITE_AUTH0_CLIENT_ID=5pUCkl2SfogkWmM244UDcLEUOp8EFdHd VITE_AUTH0_AUDIENCE=api.brandxtech.ca VITE_APP_API_URL=https://api.brandxtech.ca/v1 VITE_APP_WS_URL=ws://api.brandxtech.ca/v1/live vite",
- "start-local": "VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_APP_API_URL=http://localhost:50052/v1 VITE_APP_WS_URL=ws://localhost:50052/v1/live VITE_APP_BILLING_URL=http://localhost:50053/v1 VITE_APP_RECLUSE_URL=http://localhost:50054/v1 VITE_APP_GITLAB_URL=http://localhost:50055/v1 vite --mode localnet",
+ "start-local": "VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_APP_API_URL=http://localhost:50052/v1 VITE_APP_WS_URL=ws://localhost:50052/v1/live VITE_APP_BILLING_URL=http://localhost:50053/v1 VITE_APP_RECLUSE_URL=http://localhost:50054/v1 VITE_APP_GITLAB_URL=http://localhost:50055/v1 vite",
"start-dev": "VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_APP_API_URL=https://bxt-dev.api.adaptiveagriculture.ca/v1 VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_AUTH0_DEV_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 vite",
- "start-streamline": "VITE_WHITELABEL=streamline VITE_AUTH0_CLIENT_ID=HwUV0hHNdVvU96zuMBTAU8i7nFdwwgIX VITE_APP_API_URL=https://streamline.api.adaptiveagriculture.ca/v1 VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=streamline.api.adaptiveagriculture.ca vite",
- "start-staging": "VITE_WHITELABEL=staging VITE_AUTH0_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP VITE_AUTH0_AUDIENCE=stagingapi.brandxtech.ca VITE_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_APP_API_URL=https://stagingapi.brandxtech.ca/v1 VITE_APP_WS_URL=ws://stagingapi.brandxtech.ca/v1/live vite",
+ "start-streamline": "VITE_AUTH0_CLIENT_ID=HwUV0hHNdVvU96zuMBTAU8i7nFdwwgIX VITE_APP_API_URL=https://streamline.api.adaptiveagriculture.ca/v1 VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=streamline.api.adaptiveagriculture.ca vite",
+ "start-staging": "VITE_LOCAL_STAGING=true VITE_AUTH0_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP VITE_AUTH0_AUDIENCE=stagingapi.brandxtech.ca VITE_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_APP_API_URL=https://stagingapi.brandxtech.ca/v1 VITE_APP_WS_URL=ws://stagingapi.brandxtech.ca/v1/live VITE_APP_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_APP_AUTH0_AUDIENCE=stagingapi.brandxtech.ca vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
@@ -16,7 +16,6 @@
"build:development": "VITE_CRISP_WEBSITE_ID={CRISP_WEBSITE_ID} VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_APP_API_URL=https://bxt-dev.api.adaptiveagriculture.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_DEVELOPMENT} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP=${OPEN_WEATHERMAP} vite build",
"build:production": "VITE_CRISP_WEBSITE_ID={CRISP_WEBSITE_ID} VITE_AUTH0_CLIENT_ID=5pUCkl2SfogkWmM244UDcLEUOp8EFdHd VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=api.brandxtech.ca VITE_APP_API_URL=https://api.brandxtech.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_PRODUCTION} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP=${OPEN_WEATHERMAP} vite build",
"build:streamline": "VITE_CRISP_WEBSITE_ID={CRISP_WEBSITE_ID} VITE_AUTH0_CLIENT_ID=HwUV0hHNdVvU96zuMBTAU8i7nFdwwgIX VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=streamline.api.adaptiveagriculture.ca VITE_APP_API_URL=https://streamline.api.adaptiveagriculture.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_PRODUCTION} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP=${OPEN_WEATHERMAP} vite build",
- "build:local": "VITE_CRISP_WEBSITE_ID= VITE_AUTH0_CLIENT_ID=local VITE_AUTH0_AUDIENCE=local VITE_AUTH0_CLIENT_DOMAIN=local VITE_APP_API_URL=http://172.16.1.20:50052/v1 VITE_APP_WS_URL=ws://172.16.1.20:50052/v1/live NODE_OPTIONS=--max_old_space_size=4096 vite build --mode localnet",
"build:offline": "npx env-cmd offline,whitelabel npm run build",
"test": "vitest"
},
@@ -38,13 +37,10 @@
"@mui/styles": "^6.1.6",
"@mui/x-date-pickers": "^7.26.0",
"@react-pdf/renderer": "^4.3.0",
- "@react-three/drei": "^9.105.6",
- "@react-three/fiber": "^8.18.0",
"@sentry/react": "^8.38.0",
"@turf/area": "^7.2.0",
"@turf/turf": "^7.2.0",
"@types/classnames": "^2.3.0",
- "@types/three": "^0.183.1",
"axios": "^1.7.7",
"crisp-sdk-web": "^1.0.26",
"dayjs": "^1.11.13",
@@ -68,6 +64,7 @@
"react-dom": "^18.3.1",
"react-emoji-render": "^2.0.1",
"react-error-boundary": "^5.0.0",
+ "react-full-screen": "^1.1.1",
"react-horizontal-scrolling-menu": "^7.1.1",
"react-infinite-scroller": "^1.2.6",
"react-joyride": "^2.9.3",
@@ -77,7 +74,6 @@
"react-virtualized-auto-sizer": "^1.0.25",
"recharts": "^2.15.1",
"semver": "^7.7.1",
- "three": "^0.152.2",
"victory": "^37.3.6",
"weather-icons-react": "^1.2.0"
},
diff --git a/public/Intellifarms/IFND-2023-Logo-White.png b/public/Intellifarms/IFND-2023-Logo-White.png
deleted file mode 100644
index 0afb3bf..0000000
Binary files a/public/Intellifarms/IFND-2023-Logo-White.png and /dev/null differ
diff --git a/public/Intellifarms/IFND-2023-Logo.png b/public/Intellifarms/IFND-2023-Logo.png
deleted file mode 100644
index 11ff1c8..0000000
Binary files a/public/Intellifarms/IFND-2023-Logo.png and /dev/null differ
diff --git a/public/Intellifarms/android-chrome-192x192.png b/public/Intellifarms/android-chrome-192x192.png
deleted file mode 100644
index 11f1839..0000000
Binary files a/public/Intellifarms/android-chrome-192x192.png and /dev/null differ
diff --git a/public/Intellifarms/android-chrome-512x512.png b/public/Intellifarms/android-chrome-512x512.png
deleted file mode 100644
index e4f7470..0000000
Binary files a/public/Intellifarms/android-chrome-512x512.png and /dev/null differ
diff --git a/public/Intellifarms/apple-touch-icon.png b/public/Intellifarms/apple-touch-icon.png
deleted file mode 100644
index 408dde9..0000000
Binary files a/public/Intellifarms/apple-touch-icon.png and /dev/null differ
diff --git a/public/Intellifarms/browserconfig.xml b/public/Intellifarms/browserconfig.xml
deleted file mode 100644
index f62a123..0000000
--- a/public/Intellifarms/browserconfig.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
- #ffffff
-
-
-
diff --git a/public/Intellifarms/favicon-16x16.png b/public/Intellifarms/favicon-16x16.png
deleted file mode 100644
index f052e75..0000000
Binary files a/public/Intellifarms/favicon-16x16.png and /dev/null differ
diff --git a/public/Intellifarms/favicon-32x32.png b/public/Intellifarms/favicon-32x32.png
deleted file mode 100644
index 460f109..0000000
Binary files a/public/Intellifarms/favicon-32x32.png and /dev/null differ
diff --git a/public/Intellifarms/favicon-48x48.png b/public/Intellifarms/favicon-48x48.png
deleted file mode 100644
index 948fec7..0000000
Binary files a/public/Intellifarms/favicon-48x48.png and /dev/null differ
diff --git a/public/Intellifarms/favicon.ico b/public/Intellifarms/favicon.ico
deleted file mode 100644
index f3b5e0c..0000000
Binary files a/public/Intellifarms/favicon.ico and /dev/null differ
diff --git a/public/Intellifarms/manifest.json b/public/Intellifarms/manifest.json
deleted file mode 100644
index 5288b8b..0000000
--- a/public/Intellifarms/manifest.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "name": "Intellifarms",
- "short_name": "Intellifarms",
- "icons": [
- {
- "src": "/android-chrome-192x192.png",
- "sizes": "192x192",
- "type": "image/png"
- },
- {
- "src": "/android-chrome-512x512.png",
- "sizes": "512x512",
- "type": "image/png"
- }
- ],
- "theme_color": "#ffffff",
- "background_color": "#ffffff",
- "display": "standalone"
-}
\ No newline at end of file
diff --git a/public/Intellifarms/mstile-150x150.png b/public/Intellifarms/mstile-150x150.png
deleted file mode 100644
index 03b9ab0..0000000
Binary files a/public/Intellifarms/mstile-150x150.png and /dev/null differ
diff --git a/public/Intellifarms/safari-pinned-tab.svg b/public/Intellifarms/safari-pinned-tab.svg
deleted file mode 100644
index 6ea0a3f..0000000
--- a/public/Intellifarms/safari-pinned-tab.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/src/3dModels/CameraControls/CameraOverlay.tsx b/src/3dModels/CameraControls/CameraOverlay.tsx
deleted file mode 100644
index 080aab9..0000000
--- a/src/3dModels/CameraControls/CameraOverlay.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { useThree } from "@react-three/fiber";
-import { useEffect, useRef, useState } from "react";
-import ReactDOM from "react-dom";
-
-interface Props {
- showResetButton?: boolean;
- onReset?: () => void;
-}
-
-/**
- * Renders camera control UI buttons as a DOM overlay on top of the R3F canvas.
- * Must be placed inside a so it has access to useThree().
- *
- * Uses a portal into a div that is absolutely positioned over the canvas element,
- * so the buttons sit in 2D screen space rather than 3D world space.
- */
-export default function CameraOverlay({ showResetButton, onReset }: Props) {
- const { gl } = useThree();
- const [container, setContainer] = useState(null);
-
- useEffect(() => {
- const canvas = gl.domElement;
- const parent = canvas.parentElement;
- if (!parent) return;
-
- // Ensure the parent is positioned so absolute children are relative to it
- const parentPosition = getComputedStyle(parent).position;
- if (parentPosition === "static") {
- parent.style.position = "relative";
- }
-
- const div = document.createElement("div");
- div.style.cssText = `
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- pointer-events: none;
- z-index: 10;
- `;
- parent.appendChild(div);
- setContainer(div);
-
- return () => {
- parent.removeChild(div);
- };
- }, [gl]);
-
- if (!container) return null;
-
- return ReactDOM.createPortal(
-
- {showResetButton && (
- (e.currentTarget.style.background = "rgba(0,0,0,0.7)")}
- onMouseLeave={e => (e.currentTarget.style.background = "rgba(0,0,0,0.45)")}
- >
- ⟳
-
- )}
-
,
- container
- );
-}
\ No newline at end of file
diff --git a/src/3dModels/CameraControls/OrbitCameraControls.tsx b/src/3dModels/CameraControls/OrbitCameraControls.tsx
deleted file mode 100644
index 7e098d6..0000000
--- a/src/3dModels/CameraControls/OrbitCameraControls.tsx
+++ /dev/null
@@ -1,329 +0,0 @@
-import React from "react";
-import { useThree } from "@react-three/fiber";
-import { useEffect, useRef } from "react";
-import { Vector2, Vector3 } from "three";
-
-interface CameraTarget {
- x: number;
- y: number;
- z: number;
-}
-
-interface Props {
- target?: CameraTarget;
- clampVerticalRotation?: boolean;
- /**
- * The minimum vertical rotation in radians
- * @default 0.01
- */
- minPhi?: number;
- /**
- * The maximum vertical rotation in radians
- * @default Math.PI - 0.01
- */
- maxPhi?: number;
- /**
- * Starting camera distance from the target.
- * Pass a value computed from the bin dimensions so the whole bin
- * fits in frame on first render. If omitted defaults to 10.
- */
- initialRadius?: number;
- /**
- * Maximum zoom-out distance. Defaults to 30 but should be set
- * larger when initialRadius is large.
- */
- maxRadius?: number;
- /**
- * used to control the offset of the target from the center that the camera rotates around
- */
- offset?: { x: number; y: number; z: number };
- viewOffset?: number;
- /**
- * Called when the camera is reset to its initial position.
- * OrbitCameraControls manages the reset internally — use this to
- * trigger it from outside (e.g. from CameraOverlay's reset button).
- * Wire it by passing a ref setter: onReset={fn => resetFn.current = fn}
- */
- onReset?: (resetFn: () => void) => void;
-}
-
-export default function OrbitCameraControls(props: Props) {
- const {
- target,
- clampVerticalRotation,
- minPhi = 0.01,
- maxPhi = Math.PI - 0.01,
- offset,
- viewOffset,
- initialRadius,
- maxRadius,
- onReset,
- } = props;
-
- const { camera, gl } = useThree();
-
- const mouse = useRef(new Vector2());
- const targetRef = useRef({
- x: (target?.x ?? 0) + (offset?.x ?? 0),
- y: (target?.y ?? 0) + (offset?.y ?? 0),
- z: (target?.z ?? 0) + (offset?.z ?? 0),
- });
-
- const isDragging = useRef(false);
- const isPanning = useRef(false);
- const lastPos = useRef({ x: 0, y: 0 });
- const panCameraPosition = useRef(new Vector3());
- const lastPinchDist = useRef(0);
-
- // Spherical coords
- const spherical = useRef({
- radius: initialRadius ?? 10,
- theta: 0, // horizontal angle
- phi: Math.PI / 2, // vertical angle
- });
-
- useEffect(() => {
- // Sync radius whenever initialRadius prop changes (e.g. after bin data loads)
- spherical.current.radius = initialRadius ?? 10;
-
- const canvas = gl.domElement;
- canvas.addEventListener("contextmenu", e => e.preventDefault());
-
- const updateCamera = () => {
- const { radius, theta, phi } = spherical.current;
-
- const x = radius * Math.sin(phi) * Math.sin(theta);
- const y = radius * Math.cos(phi);
- const z = radius * Math.sin(phi) * Math.cos(theta);
-
- camera.position.set(
- targetRef.current.x + x,
- targetRef.current.y + y,
- targetRef.current.z + z
- );
- camera.lookAt(
- targetRef.current.x,
- targetRef.current.y,
- targetRef.current.z
- );
-
- // Shift the projected image without affecting orbit
- const { width, height } = gl.domElement.getBoundingClientRect();
- (camera as any).setViewOffset(width, height, viewOffset ?? 0, 0, width, height);
- };
-
- // Hand the reset function to the caller so they can trigger it
- // (e.g. from a button in CameraOverlay) without needing an external ref.
- if (onReset) {
- onReset(() => {
- spherical.current.radius = initialRadius ?? 10;
- spherical.current.theta = 0;
- spherical.current.phi = Math.PI / 2;
- targetRef.current = {
- x: (target?.x ?? 0) + (offset?.x ?? 0),
- y: (target?.y ?? 0) + (offset?.y ?? 0),
- z: (target?.z ?? 0) + (offset?.z ?? 0),
- };
- updateCamera();
- });
- }
-
- updateCamera();
-
- const handleMouseDown = (e: MouseEvent) => {
- if (e.target !== canvas) return;
-
- lastPos.current = { x: e.clientX, y: e.clientY };
- panCameraPosition.current.copy(camera.position);
-
- if (e.button === 0) {
- isDragging.current = true;
- isPanning.current = false;
- }
-
- if (e.button === 2) {
- isPanning.current = true;
- isDragging.current = false;
- }
- };
-
- const handleMouseMove = (e: MouseEvent) => {
- const deltaX = e.clientX - lastPos.current.x;
- const deltaY = e.clientY - lastPos.current.y;
-
- // ROTATE — left drag
- if (isDragging.current) {
- spherical.current.theta -= deltaX * 0.005;
- spherical.current.phi -= deltaY * 0.005;
-
- if (clampVerticalRotation) {
- spherical.current.phi = Math.max(
- minPhi,
- Math.min(maxPhi, spherical.current.phi)
- );
- }
- }
-
- // PAN — right drag
- // Uses screen-space axes derived from the camera so panning is
- // correct at any angle including top-down.
- if (isPanning.current) {
- const distance = spherical.current.radius;
- const panSpeed = 0.002 * distance;
-
- const forward = new Vector3();
- camera.getWorldDirection(forward);
-
- // Right vector — perpendicular to forward in the horizontal plane
- const right = new Vector3();
- right.crossVectors(forward, new Vector3(0, 1, 0)).normalize();
-
- // Degenerate guard: when camera is nearly straight up or down,
- // fall back to camera.up
- if (right.lengthSq() < 0.001) {
- right.crossVectors(forward, camera.up).normalize();
- }
-
- // screenUp — perpendicular to both forward and right.
- // This is the actual "up" direction on screen regardless of
- // camera tilt, avoiding the zoom-while-panning bug.
- const screenUp = new Vector3();
- screenUp.crossVectors(right, forward).normalize();
-
- right.multiplyScalar(-deltaX * panSpeed);
- screenUp.multiplyScalar(deltaY * panSpeed);
-
- const pan = new Vector3().addVectors(right, screenUp);
-
- targetRef.current.x += pan.x;
- targetRef.current.y += pan.y;
- targetRef.current.z += pan.z;
- }
-
- lastPos.current = { x: e.clientX, y: e.clientY };
- updateCamera();
- };
-
- const handleMouseUp = () => {
- isDragging.current = false;
- isPanning.current = false;
- };
-
- const handleWheel = (e: WheelEvent) => {
- if (e.target !== canvas) return;
-
- e.preventDefault();
-
- spherical.current.radius += e.deltaY * 0.01;
- spherical.current.radius = Math.max(
- 3,
- Math.min(maxRadius ?? 30, spherical.current.radius)
- );
-
- updateCamera();
- };
-
- const handleTouchStart = (e: TouchEvent) => {
- if (e.target !== canvas) return;
- e.preventDefault();
-
- if (e.touches.length === 1) {
- isDragging.current = true;
- isPanning.current = false;
- lastPos.current = { x: e.touches[0].clientX, y: e.touches[0].clientY };
- } else if (e.touches.length === 2) {
- isDragging.current = false;
- isPanning.current = true;
- lastPos.current = {
- x: (e.touches[0].clientX + e.touches[1].clientX) / 2,
- y: (e.touches[0].clientY + e.touches[1].clientY) / 2,
- };
- // Store initial pinch distance for zoom
- const dx = e.touches[0].clientX - e.touches[1].clientX;
- const dy = e.touches[0].clientY - e.touches[1].clientY;
- lastPinchDist.current = Math.sqrt(dx * dx + dy * dy);
- }
- };
-
- const handleTouchMove = (e: TouchEvent) => {
- e.preventDefault();
-
- if (e.touches.length === 1 && isDragging.current) {
- const deltaX = e.touches[0].clientX - lastPos.current.x;
- const deltaY = e.touches[0].clientY - lastPos.current.y;
- spherical.current.theta -= deltaX * 0.005;
- spherical.current.phi -= deltaY * 0.005;
- if (clampVerticalRotation) {
- spherical.current.phi = Math.max(minPhi, Math.min(maxPhi, spherical.current.phi));
- }
- lastPos.current = { x: e.touches[0].clientX, y: e.touches[0].clientY };
- updateCamera();
-
- } else if (e.touches.length === 2) {
- // Pinch zoom
- const dx = e.touches[0].clientX - e.touches[1].clientX;
- const dy = e.touches[0].clientY - e.touches[1].clientY;
- const dist = Math.sqrt(dx * dx + dy * dy);
- const pinchDelta = lastPinchDist.current - dist;
- spherical.current.radius += pinchDelta * 0.05;
- spherical.current.radius = Math.max(3, Math.min(maxRadius ?? 30, spherical.current.radius));
- lastPinchDist.current = dist;
-
- // Two-finger pan
- const midX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
- const midY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
- const panDeltaX = midX - lastPos.current.x;
- const panDeltaY = midY - lastPos.current.y;
-
- const distance = spherical.current.radius;
- const panSpeed = 0.002 * distance;
- const forward = new Vector3();
- camera.getWorldDirection(forward);
- const right = new Vector3().crossVectors(forward, new Vector3(0, 1, 0)).normalize();
- if (right.lengthSq() < 0.001) right.crossVectors(forward, camera.up).normalize();
- const screenUp = new Vector3().crossVectors(right, forward).normalize();
- right.multiplyScalar(-panDeltaX * panSpeed);
- screenUp.multiplyScalar(panDeltaY * panSpeed);
- const pan = new Vector3().addVectors(right, screenUp);
- targetRef.current.x += pan.x;
- targetRef.current.y += pan.y;
- targetRef.current.z += pan.z;
-
- lastPos.current = { x: midX, y: midY };
- updateCamera();
- }
- };
-
- const handleTouchEnd = (e: TouchEvent) => {
- if (e.touches.length === 0) {
- isDragging.current = false;
- isPanning.current = false;
- } else if (e.touches.length === 1) {
- // Transitioned from pinch back to single finger
- isDragging.current = true;
- isPanning.current = false;
- lastPos.current = { x: e.touches[0].clientX, y: e.touches[0].clientY };
- }
- };
-
- window.addEventListener("mousedown", handleMouseDown);
- window.addEventListener("mousemove", handleMouseMove);
- window.addEventListener("mouseup", handleMouseUp);
- canvas.addEventListener("wheel", handleWheel, { passive: false });
- canvas.addEventListener("touchstart", handleTouchStart, { passive: false });
- canvas.addEventListener("touchmove", handleTouchMove, { passive: false });
- canvas.addEventListener("touchend", handleTouchEnd);
-
- return () => {
- window.removeEventListener("mousedown", handleMouseDown);
- window.removeEventListener("mousemove", handleMouseMove);
- window.removeEventListener("mouseup", handleMouseUp);
- canvas.removeEventListener("wheel", handleWheel);
- canvas.removeEventListener("touchstart", handleTouchStart);
- canvas.removeEventListener("touchmove", handleTouchMove);
- canvas.removeEventListener("touchend", handleTouchEnd);
- };
- }, [camera, gl, target, clampVerticalRotation, minPhi, maxPhi, initialRadius, maxRadius]);
-
- return null;
-}
\ No newline at end of file
diff --git a/src/3dModels/Shapes/3D/Circle.tsx b/src/3dModels/Shapes/3D/Circle.tsx
deleted file mode 100644
index 8204a29..0000000
--- a/src/3dModels/Shapes/3D/Circle.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { useMemo } from "react";
-import * as THREE from "three";
-import BaseMesh, { BaseMeshProps } from "../BaseMesh";
-
-export interface CircleGeometry{
- radius: number
- radialSegments?: number
- thetaStart?: number
- thetaLength?: number
-}
-
-interface Props extends Omit {
- geometry: CircleGeometry;
-}
-
-export default function Circle(props: Props) {
- const { geometry } = props;
-
- const geo = useMemo(() => {
- return new THREE.CircleGeometry(
- geometry.radius,
- geometry.radialSegments ?? 24,
- geometry.thetaStart ?? 0,
- geometry.thetaLength ?? Math.PI * 2
- );
- }, [
- geometry.radius,
- geometry.radialSegments,
- geometry.thetaStart,
- geometry.thetaLength
- ]);
-
- return ;
-}
\ No newline at end of file
diff --git a/src/3dModels/Shapes/3D/Cone.tsx b/src/3dModels/Shapes/3D/Cone.tsx
deleted file mode 100644
index 90d3e81..0000000
--- a/src/3dModels/Shapes/3D/Cone.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { useMemo } from "react";
-import * as THREE from "three";
-import BaseMesh, { BaseMeshProps } from "../BaseMesh";
-
-export interface ConeGeometry {
- radius: number;
- height: number;
- radialSegments?: number;
- heightSegments?: number;
- openEnded?: boolean;
- thetaStart?: number;
- thetaLength?: number;
-}
-
-interface Props extends Omit {
- geometry: ConeGeometry;
-}
-
-export default function Cone(props: Props) {
- const { geometry } = props;
-
- const geo = useMemo(() => {
- return new THREE.ConeGeometry(
- geometry.radius,
- geometry.height,
- geometry.radialSegments ?? 24,
- geometry.heightSegments ?? 1,
- geometry.openEnded ?? false,
- geometry.thetaStart ?? 0,
- geometry.thetaLength ?? Math.PI * 2
- );
- }, [
- geometry.radius,
- geometry.height,
- geometry.radialSegments,
- geometry.heightSegments,
- geometry.openEnded,
- geometry.thetaStart,
- geometry.thetaLength
- ]);
-
- return ;
-}
\ No newline at end of file
diff --git a/src/3dModels/Shapes/3D/Cube.tsx b/src/3dModels/Shapes/3D/Cube.tsx
deleted file mode 100644
index 94ae03b..0000000
--- a/src/3dModels/Shapes/3D/Cube.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { useMemo } from "react";
-import * as THREE from "three";
-import BaseMesh, { BaseMeshProps } from "../BaseMesh";
-
-export interface CubeGeometry {
- height: number;
- width: number;
- depth: number;
- widthSegments?: number;
- heightSegments?: number;
- depthSegments?: number;
-}
-
-interface Props extends Omit {
- geometry: CubeGeometry;
-}
-
-export default function Cube(props: Props) {
- const { geometry } = props;
-
- const geo = useMemo(() => {
- return new THREE.BoxGeometry(
- geometry.height,
- geometry.width,
- geometry.depth,
- geometry.widthSegments ?? 2,
- geometry.heightSegments ?? 2,
- geometry.depthSegments ?? 2
- );
- }, [
- geometry.height,
- geometry.width,
- geometry.depth,
- geometry.widthSegments,
- geometry.heightSegments,
- geometry.depthSegments
- ]);
-
- return ;
-}
\ No newline at end of file
diff --git a/src/3dModels/Shapes/3D/Cylinder.tsx b/src/3dModels/Shapes/3D/Cylinder.tsx
deleted file mode 100644
index ee074df..0000000
--- a/src/3dModels/Shapes/3D/Cylinder.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { useMemo } from "react";
-import * as THREE from "three";
-import BaseMesh, { BaseMeshProps } from "../BaseMesh";
-
-export interface CylinderGeometry {
- radiusTop: number;
- radiusBottom: number;
- height: number;
- radialSegments?: number;
- heightSegments?: number;
- openEnded?: boolean;
- thetaStart?: number;
- thetaLength?: number;
-}
-
-interface Props extends Omit {
- geometry: CylinderGeometry;
-}
-
-export default function Cylinder(props: Props) {
- const { geometry } = props;
-
- const geo = useMemo(() => {
- return new THREE.CylinderGeometry(
- geometry.radiusTop,
- geometry.radiusBottom,
- geometry.height,
- geometry.radialSegments ?? 24,
- geometry.heightSegments ?? 1,
- geometry.openEnded ?? false,
- geometry.thetaStart ?? 0,
- geometry.thetaLength ?? Math.PI * 2
- );
- }, [
- geometry.radiusTop,
- geometry.radiusBottom,
- geometry.height,
- geometry.radialSegments,
- geometry.heightSegments,
- geometry.openEnded,
- geometry.thetaStart,
- geometry.thetaLength
- ]);
-
- return ;
-}
\ No newline at end of file
diff --git a/src/3dModels/Shapes/3D/Ring.tsx b/src/3dModels/Shapes/3D/Ring.tsx
deleted file mode 100644
index 1a79d40..0000000
--- a/src/3dModels/Shapes/3D/Ring.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { useMemo } from "react";
-import * as THREE from "three";
-import BaseMesh, { BaseMeshProps } from "../BaseMesh";
-
-export interface RingGeometry{
- // the radius of the ring from the center to the ouside edge, must be larger than thickness
- radius: number
- // the thickness of the tube, must be smaller than radius
- thickness: number
- radialSegments?: number
- tubularSegments?: number
- // central angle in radians
- arc?: number
-}
-
-interface Props extends Omit {
- geometry: RingGeometry;
-}
-
-export default function Ring(props: Props) {
- const { geometry } = props;
-
- const geo = useMemo(() => {
- return new THREE.TorusGeometry(
- geometry.radius,
- geometry.thickness,
- geometry.radialSegments ?? 12,
- geometry.tubularSegments ?? 48,
- geometry.arc ?? Math.PI * 2
- );
- }, [
- geometry.radius,
- geometry.thickness,
- geometry.radialSegments,
- geometry.tubularSegments,
- geometry.arc
- ]);
-
- return ;
-}
\ No newline at end of file
diff --git a/src/3dModels/Shapes/3D/Sphere.tsx b/src/3dModels/Shapes/3D/Sphere.tsx
deleted file mode 100644
index 6104369..0000000
--- a/src/3dModels/Shapes/3D/Sphere.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { useMemo } from "react";
-import * as THREE from "three";
-import BaseMesh, { BaseMeshProps } from "../BaseMesh";
-
-export interface SphereGeometry {
- radius: number
- widthSegments?: number
- heightSegments?: number
- phiStart?: number
- phiLength?: number
- thetaStart?: number
- thetaLength?: number
-}
-
-interface Props extends Omit {
- geometry: SphereGeometry;
-}
-
-export default function Sphere(props: Props){
- const { geometry } = props;
-
- const geo = useMemo(() => {
- return new THREE.SphereGeometry(
- geometry.radius,
- geometry.widthSegments,
- geometry.heightSegments,
- geometry.phiStart,
- geometry.phiLength,
- geometry.thetaStart,
- geometry.thetaLength
- );
- }, [
- geometry.radius,
- geometry.widthSegments,
- geometry.heightSegments,
- geometry.phiStart,
- geometry.phiLength,
- geometry.thetaStart,
- geometry.thetaLength
- ]);
-
- return ;
-}
\ No newline at end of file
diff --git a/src/3dModels/Shapes/BaseMesh.tsx b/src/3dModels/Shapes/BaseMesh.tsx
deleted file mode 100644
index e468c0c..0000000
--- a/src/3dModels/Shapes/BaseMesh.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import { Euler, Vector3, BufferGeometry, MaterialParameters, Side } from "three";
-import { ThreeEvent } from "@react-three/fiber";
-
-export interface BaseMeshProps {
- geometry: BufferGeometry;
- position?: Vector3;
- rotation?: Euler;
- colour?: string;
- opacity?: number;
- metalness?: number;
- roughness?: number;
- wireframe?: boolean;
- frameColour?: string;
- materialType?: "standard" | "basic";
- materialProps?: Partial;
- materialOverride?: React.ReactNode;
- side?: Side;
- depthWrite?: boolean;
- depthTest?: boolean;
- renderOrder?: number;
- onClick?: (event: ThreeEvent) => void;
-}
-
-export default function BaseMesh(props: BaseMeshProps) {
- const {
- geometry,
- position,
- rotation,
- colour = "#fff",
- opacity = 1,
- metalness = 0,
- roughness = 1,
- wireframe = false,
- frameColour = "black",
- materialType,
- materialProps,
- materialOverride,
- side,
- depthWrite = true,
- depthTest = true,
- renderOrder = 0,
- onClick,
- } = props;
-
- const isTransparent = opacity < 1;
-
- const buildMaterial = () => {
- if (materialOverride) return materialOverride;
- switch (materialType) {
- case "basic":
- return (
-
- );
-
- case "standard":
- default:
- return (
-
- );
- }
- };
-
- return (
-
- {/* Main surface */}
-
- {buildMaterial()}
-
-
- {/* Optional wireframe overlay */}
- {wireframe && (
-
-
-
- )}
-
- );
-}
\ No newline at end of file
diff --git a/src/app/App.tsx b/src/app/App.tsx
index 3616784..2c6b94b 100644
--- a/src/app/App.tsx
+++ b/src/app/App.tsx
@@ -1,20 +1,15 @@
import { Auth0Provider } from '@auth0/auth0-react'
import { or } from '../utils/types'
-import { isAuth0Configured, isAuth0SpaOriginAllowed, shouldMountAuth0Provider } from '../utils/auth0Config'
import AuthWrapper from '../providers/auth'
import HTTPProvider from 'providers/http'
import { useState } from 'react'
import LoadingScreen from './LoadingScreen'
-import LocalAuthPlaceholder from './LocalAuthPlaceholder'
import UserWrapper from './UserWrapper'
import { getWhitelabel } from 'services/whiteLabel'
import { AppThemeProvider } from 'theme/AppThemeProvider'
-import { LocalAuthProvider, Auth0AuthBridge } from '../providers/authContext'
function App() {
- const [token, setToken] = useState(() => {
- return localStorage.getItem('local_auth_token') || undefined
- })
+ const [token, setToken] = useState(undefined)
const whiteLabel = getWhitelabel()
const manifestPath = "/" + whiteLabel.name.replace(/\s/g, "") + "/manifest.json"
@@ -59,29 +54,6 @@ function App() {
"/libracart"
]
- if (!shouldMountAuth0Provider()) {
- const placeholderReason =
- isAuth0Configured() && !isAuth0SpaOriginAllowed() ? 'insecure_origin' : 'unconfigured'
-
- if (!token) {
- return (
-
-
-
- )
- }
-
- return (
-
-
-
-
-
-
-
- )
- }
-
return (
{/* */}
- { token ?
-
-
-
-
-
+ { token ?
+
+
+
:
{
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/app/LocalAuthPlaceholder.tsx b/src/app/LocalAuthPlaceholder.tsx
deleted file mode 100644
index 91f11b3..0000000
--- a/src/app/LocalAuthPlaceholder.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import { Box, Button, CssBaseline, Paper, Stack, TextField, Typography } from '@mui/material'
-import { getName } from 'services/whiteLabel'
-import { useState } from 'react'
-import axios from 'axios'
-
-export type LocalAuthPlaceholderReason = 'unconfigured' | 'insecure_origin'
-
-interface Props {
- reason?: LocalAuthPlaceholderReason
- setToken?: (token: string) => void
-}
-
-export default function LocalAuthPlaceholder(props: Props) {
- const { reason = 'unconfigured', setToken } = props
- const productName = getName()
-
- const [mode, setMode] = useState<'login' | 'signup'>('login')
- const [email, setEmail] = useState('')
- const [password, setPassword] = useState('')
- const [name, setName] = useState('')
- const [error, setError] = useState('')
- const [loading, setLoading] = useState(false)
-
- const apiUrl = import.meta.env.VITE_APP_API_URL
-
- const handleSignup = async () => {
- setError('')
- setLoading(true)
- try {
- const resp = await axios.post(`${apiUrl}/local-auth/signup`, { email, password, name })
- const token = resp.data.token
- if (token && setToken) {
- localStorage.setItem('local_auth_token', token)
- setToken(token)
- }
- } catch (err: any) {
- setError(err.response?.data?.error || 'Signup failed')
- } finally {
- setLoading(false)
- }
- }
-
- const handleLogin = async () => {
- setError('')
- setLoading(true)
- try {
- const resp = await axios.post(`${apiUrl}/local-auth/login`, { email, password })
- const token = resp.data.token
- if (token && setToken) {
- localStorage.setItem('local_auth_token', token)
- setToken(token)
- }
- } catch (err: any) {
- setError(err.response?.data?.error || 'Login failed')
- } finally {
- setLoading(false)
- }
- }
-
- const handleSubmit = (e: React.FormEvent) => {
- e.preventDefault()
- if (mode === 'signup') {
- handleSignup()
- } else {
- handleLogin()
- }
- }
-
- const explanation =
- reason === 'insecure_origin'
- ? 'This URL is not a secure context for cloud sign-in. Use local account sign-in instead.'
- : 'Local account sign-in for this deployment.'
-
- return (
- <>
-
-
-
-
-
-
- >
- )
-}
diff --git a/src/app/UserWrapper.tsx b/src/app/UserWrapper.tsx
index a625411..fac8487 100644
--- a/src/app/UserWrapper.tsx
+++ b/src/app/UserWrapper.tsx
@@ -1,20 +1,20 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import './App.css'
import { useUserAPI } from '../providers/pond/userAPI'
+import { useAuth0 } from '@auth0/auth0-react'
import { or } from '../utils/types'
import LoadingScreen from './LoadingScreen'
import NavigationContainer from '../navigation/NavigationContainer'
import { GlobalState, GlobalStateAction, StateProvider } from '../providers/StateContainer'
import { User } from '../models/user'
import { Team } from '../models/team'
-import { getWhitelabel } from '../services/whiteLabel'
import { makeStyles } from '@mui/styles'
import { CssBaseline, Theme } from '@mui/material'
import { AppThemeProvider } from 'theme/AppThemeProvider'
import HTTPProvider from 'providers/http'
+import { Crisp } from "crisp-sdk-web";
import { useMobile, useSnackbar } from 'hooks'
-import { initCrisp, isCrispEnabled } from '../chat/CrispChat'
-import { useTeamAPI } from '../providers/pond/teamAPI'
+import { initCrisp } from '../chat/CrispChat'
// import FirmwareLoader from './FirmwareLoader'
const reducer = (state: GlobalState, action: GlobalStateAction): GlobalState => {
@@ -63,43 +63,26 @@ export default function UserWrapper(props: Props) {
const { token } = props;
const [loading, setLoading] = useState(false)
const userAPI = useUserAPI();
- const teamAPI = useTeamAPI();
const classes = useStyles();
+ const useAuth = useAuth0();
const hasFetched = useRef(false);
const [global, setGlobal] = useState(undefined)
const snackbar = useSnackbar()
const isMobile = useMobile()
- const user_id = (() => {
- try {
- const payload = JSON.parse(atob(token.split('.')[1]))
- return or(payload.sub, '')
- } catch {
- return ''
- }
- })()
+ const user_id = or(useAuth.user?.sub, "")
+
+ const crispInitialized = useRef(false);
+ Crisp.configure(import.meta.env.VITE_CRISP_WEBSITE_ID);
const loadUser = useCallback(() => {
if (!userAPI.getUserWithTeam) return;
if (hasFetched.current) return;
setLoading(true)
userAPI.getUserWithTeam(user_id).then(resp => {
- const loadedUser = resp.data.user ? User.create(resp.data.user) : User.create();
- const loadedTeam = resp.data.team ? Team.create(resp.data.team) : Team.create();
-
- const currentSlug = getWhitelabel().slug;
- if (!loadedUser.empty() && loadedUser.settings.whitelabel !== currentSlug) {
- loadedUser.settings.whitelabel = currentSlug;
- userAPI.updateUser(loadedUser.id(), loadedUser.protobuf()).catch(() => {});
- }
- if (!loadedTeam.empty() && loadedTeam.settings.whitelabel === "") {
- loadedTeam.settings.whitelabel = currentSlug;
- teamAPI.updateTeam(loadedTeam.key(), loadedTeam.settings).catch(() => {});
- }
-
setGlobal({
- user: loadedUser,
- team: loadedTeam,
+ user: resp.data.user ? User.create(resp.data.user) : User.create(),
+ team: resp.data.team ? Team.create(resp.data.team) : Team.create(),
as: resp.data.user?.settings?.useTeam === true ? resp.data.user.settings.defaultTeam : "",
showErrors: false,
userTeamPermissions: [],
@@ -131,14 +114,15 @@ export default function UserWrapper(props: Props) {
}, [setGlobal])
useEffect(() => {
- if (!global?.user || !isCrispEnabled()) return;
- initCrisp({
- websiteId: import.meta.env.VITE_CRISP_WEBSITE_ID,
- email: global.user.settings.email,
- nickname: global.user.settings.name || global.user.settings.email,
- phone: global.user.settings.phoneNumber,
- tokenId: global.user.id(),
- });
+ if (global?.user) {
+ initCrisp({
+ websiteId: import.meta.env.VITE_CRISP_WEBSITE_ID,
+ email: global.user.settings.email,
+ nickname: global.user.settings.name || global.user.settings.email,
+ phone: global.user.settings.phoneNumber,
+ tokenId: global.user.id(),
+ });
+ }
}, [global]);
// useEffect(() => {
@@ -166,7 +150,6 @@ export default function UserWrapper(props: Props) {
// }, [isMobile]);
useEffect(() => {
- if (!isCrispEnabled()) return;
let style = document.getElementById("crisp-offset-override");
if (!style) {
style = document.createElement("style");
@@ -211,4 +194,4 @@ export default function UserWrapper(props: Props) {
)
-}
+}
\ No newline at end of file
diff --git a/src/app/main.tsx b/src/app/main.tsx
index feae822..2d8615c 100644
--- a/src/app/main.tsx
+++ b/src/app/main.tsx
@@ -1,6 +1,5 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
-import { StyledEngineProvider } from '@mui/material/styles'
import App from './App'
// I don't consider providing a disabled button to a Tooltip an error.
@@ -21,8 +20,6 @@ if ('serviceWorker' in navigator) {
createRoot(document.getElementById('root')!).render(
//
-
-
-
+
// ,
)
diff --git a/src/assets/whitelabels/Intellifarms/IFND-2023-Logo-White.png b/src/assets/whitelabels/Intellifarms/IFND-2023-Logo-White.png
deleted file mode 100644
index 0afb3bf..0000000
Binary files a/src/assets/whitelabels/Intellifarms/IFND-2023-Logo-White.png and /dev/null differ
diff --git a/src/assets/whitelabels/Intellifarms/IFND-2023-Logo.png b/src/assets/whitelabels/Intellifarms/IFND-2023-Logo.png
deleted file mode 100644
index 11ff1c8..0000000
Binary files a/src/assets/whitelabels/Intellifarms/IFND-2023-Logo.png and /dev/null differ
diff --git a/src/bin/3dView/BinParts/BinShell.tsx b/src/bin/3dView/BinParts/BinShell.tsx
deleted file mode 100644
index f4c0c4e..0000000
--- a/src/bin/3dView/BinParts/BinShell.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-import Circle, { CircleGeometry } from "3dModels/Shapes/3D/Circle"
-import Cone, { ConeGeometry } from "3dModels/Shapes/3D/Cone"
-import Cylinder, { CylinderGeometry } from "3dModels/Shapes/3D/Cylinder"
-import React from "react"
-import { Euler, Vector3 } from "three"
-
-interface Props {
- radialSegments: number
- binBodyColour: string
- binMetalness: number
- binRoughness: number
- binOpacity: number
- diameter: number
- sidewallHeight: number
- roofHeight: number
- hopperHeight?: number
- wireframe?: boolean
- renderOrder?: number
-}
-
-export default function BinShell(props: Props) {
- const { radialSegments, binBodyColour, binMetalness, binRoughness, binOpacity, diameter, sidewallHeight, roofHeight, hopperHeight, wireframe, renderOrder } = props
- const cylinderGeometry = React.useMemo(() => ({
- radiusTop: diameter / 2,
- radiusBottom: diameter / 2,
- height: sidewallHeight,
- radialSegments,
- heightSegments: 2,
- openEnded: true,
- } as CylinderGeometry), [diameter, sidewallHeight, radialSegments]);
-
- const roofGeometry = React.useMemo(() => ({
- height: roofHeight,
- radius: diameter /2,
- radialSegments: radialSegments,
- openEnded: true
- } as ConeGeometry), [diameter, roofHeight, radialSegments])
-
- const hopperGeometry = React.useMemo(() => ({
- height: hopperHeight,
- radius: diameter /2,
- radialSegments: radialSegments,
- openEnded: true
- } as ConeGeometry),[hopperHeight, diameter, radialSegments])
-
- const flatBottomGeometry = React.useMemo(() => ({
- radius: diameter/2,
- radialSegments: radialSegments
- } as CircleGeometry),[diameter, radialSegments])
-
- const roofPosition = React.useMemo(
- () => new Vector3(0, sidewallHeight/2 + roofHeight/2, 0),
- [sidewallHeight, roofHeight]
- );
-
- const bottomPosition = React.useMemo(
- () => {
- if(hopperHeight !== undefined && hopperHeight > 0){
- //this returns the position for the center of the cone for the hopper
- return new Vector3(0, (sidewallHeight/2 + hopperHeight/2)*-1, 0)
- } else {
- //this returns the position for the circle for the flat bottom
- return new Vector3(0, -sidewallHeight/2, 0)
- }
- },
- [sidewallHeight, hopperHeight]
- )
-
- const hopperRotation = React.useMemo(
- () => new Euler(Math.PI, 0, 0),
- []
- );
-
- const flatBottomRotation = React.useMemo(
- () => new Euler(-Math.PI / 2, 0, 0),
- []
- );
-
- return (
-
- {/* bin roof - cone */}
-
- {/* bin sidewall - cylinder (open ended for the roof and bottom)*/}
-
- {/* bin bottom - cone -OR- circle depending on the bins shape*/}
- {hopperHeight !== undefined && hopperHeight > 0 ?
-
- :
-
- }
-
- )
-
-}
\ No newline at end of file
diff --git a/src/bin/3dView/Data/BuildCableData.ts b/src/bin/3dView/Data/BuildCableData.ts
deleted file mode 100644
index 4b34fb9..0000000
--- a/src/bin/3dView/Data/BuildCableData.ts
+++ /dev/null
@@ -1,212 +0,0 @@
-import { RadiansToDegrees } from "common/TrigFunctions";
-import { Bin } from "models";
-import { GrainCable } from "models/GrainCable";
-import { pond } from "protobuf-ts/pond";
-import { Vector3 } from "three";
-import { avg } from "utils";
-
-export interface CableData {
- cableIndex: number
- radius: number
- topPosition: Vector3
- bottomPosition: Vector3
- grainCable: pond.GrainCable
- radialSegments?: number
- heightSegments?: number
-}
-
-export interface CableOrbit {
- orbit: number;
- radius: number; // in cm
- expectedCount: number;
- assignedCount?: number;
-}
-
-/**
- * Effective bin dimensions used for cable placement.
- * Passed in from Bin3dView so that default fallback values are applied
- * consistently — cables will always match the rendered shell even when
- * the bin has no advanced dimensions configured.
- */
-export interface BinDims {
- diameter: number
- sidewallHeight: number
- roofHeight: number
- hopperHeight: number
-}
-
-
-function getLayoutFromDiameter(diameterCm: number): CableOrbit[] {
- const diameterFt = diameterCm / 30.48;
-
- let summaries: { Orbit: number; DistanceFromCenter: number; NumberOfCables: number }[] = [];
-
- if (diameterFt < 24) {
- summaries.push({ Orbit: 0, DistanceFromCenter: 2, NumberOfCables: 1 });
- } else if (diameterFt < 36) {
- summaries.push({ Orbit: 1, DistanceFromCenter: 7, NumberOfCables: 3 });
- } else if (diameterFt < 42) {
- summaries.push({ Orbit: 0, DistanceFromCenter: 2, NumberOfCables: 1 });
- summaries.push({ Orbit: 1, DistanceFromCenter: 9, NumberOfCables: 3 });
- } else if (diameterFt < 48) {
- summaries.push({ Orbit: 0, DistanceFromCenter: 2, NumberOfCables: 1 });
- summaries.push({ Orbit: 1, DistanceFromCenter: 14, NumberOfCables: 4 });
- } else if (diameterFt < 54) {
- summaries.push({ Orbit: 0, DistanceFromCenter: 2, NumberOfCables: 1 });
- summaries.push({ Orbit: 1, DistanceFromCenter: 16, NumberOfCables: 5 });
- } else if (diameterFt < 60) {
- summaries.push({ Orbit: 0, DistanceFromCenter: 2, NumberOfCables: 1 });
- summaries.push({ Orbit: 1, DistanceFromCenter: 18, NumberOfCables: 6 });
- } else if (diameterFt < 72) {
- summaries.push({ Orbit: 0, DistanceFromCenter: 2, NumberOfCables: 1 });
- summaries.push({ Orbit: 1, DistanceFromCenter: 20, NumberOfCables: 7 });
- } else if (diameterFt < 78) {
- summaries.push({ Orbit: 1, DistanceFromCenter: 10, NumberOfCables: 4 });
- summaries.push({ Orbit: 2, DistanceFromCenter: 27, NumberOfCables: 9 });
- } else if (diameterFt < 90) {
- summaries.push({ Orbit: 1, DistanceFromCenter: 10, NumberOfCables: 4 });
- summaries.push({ Orbit: 2, DistanceFromCenter: 29, NumberOfCables: 10 });
- } else if (diameterFt < 105) {
- summaries.push({ Orbit: 0, DistanceFromCenter: 2, NumberOfCables: 1 });
- summaries.push({ Orbit: 1, DistanceFromCenter: 18, NumberOfCables: 6 });
- summaries.push({ Orbit: 2, DistanceFromCenter: 36, NumberOfCables: 12 });
- } else {
- summaries.push({ Orbit: 1, DistanceFromCenter: 9, NumberOfCables: 3 });
- summaries.push({ Orbit: 2, DistanceFromCenter: 26.5, NumberOfCables: 9 });
- summaries.push({ Orbit: 3, DistanceFromCenter: 44, NumberOfCables: 14 });
- }
-
- return summaries.map(s => ({
- orbit: s.Orbit,
- radius: s.DistanceFromCenter * 30.48, // ft → cm
- expectedCount: s.NumberOfCables
- }));
-};
-
-function distributeCables(cableCount: number, layout: CableOrbit[]) {
- const totalExpected = layout.reduce((sum, o) => sum + o.expectedCount, 0);
-
- let assigned = layout.map(o => ({
- ...o,
- assignedCount: Math.round((o.expectedCount / totalExpected) * cableCount)
- }));
-
- // normalize rounding
- let currentTotal = assigned.reduce((sum, o) => sum + (o.assignedCount ?? 0), 0);
-
- while (currentTotal < cableCount) {
- assigned[assigned.length - 1].assignedCount!++;
- currentTotal++;
- }
-
- while (currentTotal > cableCount) {
- assigned[assigned.length - 1].assignedCount!--;
- currentTotal--;
- }
-
- return assigned;
-};
-
-function getTopY(radiusFromCenter: number, dims: BinDims) {
- const binRadius = dims.diameter / 2;
-
- const distanceFromEdge = binRadius - radiusFromCenter;
-
- // Use the real roof angle when available; when dimensions aren't configured
- // the angle will be 0 which gives a flat cable top — correct for a default shape.
- const angleRad = Math.atan((dims.roofHeight) / (binRadius));
-
- const roofRise = Math.tan(angleRad) * distanceFromEdge;
-
- return dims.sidewallHeight / 2 + roofRise;
-};
-
-function getBottomY(radiusFromCenter: number, dims: BinDims) {
- const binRadius = dims.diameter / 2;
-
- const distanceFromCenter = binRadius - radiusFromCenter;
-
- const angleRad = Math.atan((dims.hopperHeight) / (binRadius))
-
- const hopperDrop = Math.tan(angleRad) * distanceFromCenter;
-
- const clearance = 60; // cm
-
- return -dims.sidewallHeight / 2 - hopperDrop + clearance;
-};
-
-
-export function BuildCableData(bin: Bin, dims: BinDims): CableData[] {
- const cables = [...(bin.status.grainCables ?? [])];
-
- if (cables.length === 0) return [];
-
- // optional: sort cables (better visual consistency later)
- //cables.sort((a, b) => b.celcius.length - a.celcius.length);
- cables.sort((a, b) => {
- //if the cables have the same number of nodes
- if (b.celcius.length === a.celcius.length) {
- //sort by temp only last and any type that has humidity first
- if ((avg(a.relativeHumidity) === 0) && (avg(b.relativeHumidity) !== 0)) {
- return 1;
- } else if ((avg(b.relativeHumidity) === 0) && (avg(a.relativeHumidity) !== 0)) {
- return -1;
- }
- else {
- return a.key > b.key ? 1 : -1;
- }
- }
- //otherwise sort by the longest cable first
- return b.celcius.length - a.celcius.length;
- });
-
- const layout = getLayoutFromDiameter(dims.diameter);
- const distributed = distributeCables(cables.length, layout);
-
- const cableRadius = 3;
-
- const result: CableData[] = [];
-
- let cableIndex = 0;
-
- distributed.forEach(orbit => {
- const count = orbit.assignedCount ?? 0;
-
- const topY = getTopY(orbit.radius, dims); //this is if we want the cables to start at the roof
- //const topY = dims.sidewallHeight/2 //this will start the cables at the top of the sidewall
-
- const bottomY = getBottomY(orbit.radius, dims);
-
- if (orbit.orbit === 0 && count > 0) {
- const cable = cables[cableIndex];
- if (!cable) return;
- result.push({
- cableIndex: cableIndex,
- radius: cableRadius,
- topPosition: new Vector3(0, topY, 0),
- bottomPosition: new Vector3(0, bottomY, 0),
- grainCable: cable
- });
- cableIndex++;
- return;
- }
-
- for (let i = 0; i < count; i++) {
- const angle = (i / count) * Math.PI * 2;
- const x = Math.cos(angle) * orbit.radius;
- const z = Math.sin(angle) * orbit.radius;
- const cable = cables[cableIndex];
-
- result.push({
- cableIndex: cableIndex,
- radius: cableRadius,
- topPosition: new Vector3(x, topY, z),
- bottomPosition: new Vector3(x, bottomY, z),
- grainCable: cable
- });
- cableIndex++;
- }
- });
-
- return result;
-};
\ No newline at end of file
diff --git a/src/bin/3dView/Data/BuildNodeData.ts b/src/bin/3dView/Data/BuildNodeData.ts
deleted file mode 100644
index c1d88a8..0000000
--- a/src/bin/3dView/Data/BuildNodeData.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import { Vector3 } from "three"
-import { CableData } from "./BuildCableData"
-
-export interface NodeData {
- cableIndex: number
- nodeIndex: number
- radius: number
- position: Vector3
- celcius: number
- humidity?: number
- moisture?: number
- topNode?: boolean
- excluded?: boolean
- inGrain?: boolean
- /**
- * The vertical spacing between nodes on this cable (cm).
- * Used by GrainCableFill to offset the grain surface half a spacing above the top node,
- * matching the 2D bin view behaviour.
- */
- nodeSpacing: number
-}
-
-export function BuildNodeData(cables: CableData[]): NodeData[] {
- const nodeRadius = 10
- let nodeData: NodeData[] = []
-
- cables.forEach((cable, cableIndex) => {
- const grainCable = cable.grainCable
-
- const nodeCount = grainCable.celcius.length;
-
- if (nodeCount === 0) return;
-
- const bottomY = cable.bottomPosition.y;
- const topY = cable.topPosition.y;
- const height = topY - bottomY;
-
- const spacing = height / nodeCount;
-
- grainCable.celcius.forEach((celcius, i) => {
-
- const nodeY = bottomY + (i * spacing);
-
- const humidity = grainCable.relativeHumidity[i] || undefined;
- const moisture = grainCable.moisture[i] || undefined;
- let t = celcius
- // for testing
- // if(i===0){
- // t = 40
- // }
- // if (i===5){
- // t = 0
- // }
-
- nodeData.push({
- cableIndex: cableIndex,
- nodeIndex: i,
- radius: nodeRadius,
- position: new Vector3(cable.bottomPosition.x, nodeY, cable.bottomPosition.z),
- celcius: t,
- humidity: humidity,
- moisture: moisture,
- topNode: grainCable.topNode === i + 1,
- excluded: grainCable.excludedNodes?.includes(i) ?? false,
- inGrain: i + 1 <= grainCable.topNode || !grainCable.topNode,
- nodeSpacing: spacing,
- })
- })
- })
-
- return nodeData
-}
\ No newline at end of file
diff --git a/src/bin/3dView/Scene/Bin3dView.tsx b/src/bin/3dView/Scene/Bin3dView.tsx
deleted file mode 100644
index 0d15558..0000000
--- a/src/bin/3dView/Scene/Bin3dView.tsx
+++ /dev/null
@@ -1,216 +0,0 @@
-import React from "react";
-import OrbitCameraControls from "3dModels/CameraControls/OrbitCameraControls";
-import CameraOverlay from "3dModels/CameraControls/CameraOverlay";
-import { Canvas } from "@react-three/fiber";
-import { Bin } from "models";
-import BinShell from "../BinParts/BinShell";
-import GrainFillFlat from "../Systems/Inventory/GrainFillFlat";
-import BinCables from "../Systems/Cables/BinCables";
-// import { Vector3 } from "three";
-import { useMemo } from "react";
-import { BuildCableData, CableData } from "../Data/BuildCableData";
-import { BuildNodeData, NodeData } from "../Data/BuildNodeData";
-import { pond } from "protobuf-ts/pond";
-import GrainCableFill from "../Systems/Inventory/GrainCableFill";
-import TempHeatMap from "../Systems/Heatmap/TempHeatMap";
-import NodePointCloud from "../Systems/Heatmap/NodePointCloud";
-// import { Box } from "@mui/material";
-import MoistureHeatMap from "../Systems/Heatmap/MoistureHeatmap";
-import { grainYBoundsFromFillPercent } from "../utils/grainFillBounds";
-// import { RadiansToDegrees } from "common/TrigFunctions";
-// import { GrainCable } from "models/GrainCable"
-
-// Fallback dimensions used when a bin has no advanced dimensions configured.
-// Chosen to resemble a typical mid-size grain bin.
-const DEFAULT_ROOF_HEIGHT_CM = 200; // 2 m
-const DEFAULT_FLAT_HEIGHT_CM = 0; // flat bottom
-const DEFAULT_HOPPER_HEIGHT_CM = 280; // hopper bottom
-
-interface Props {
- bin: Bin
- scale: number
- /**
- * cables can come from the bins status when not passed in, however data may be out of synce from when the cables read to when the bins status updates
- * they may need to be passed in but for the moment we wont worry about it due to the last active on the bin page can be used to explain being out of sync
- * if it becomes an issue I can make the changes necessary to pass the cable components in
- */
- // cables?: GrainCable[]
- /**
- * the percentage of the bin to be filled, expects a number between 0 and 1
- */
- fillPercent?: number
- nodeClick?: (node: NodeData, cable: CableData) => void
- /**
- * When true, renders the basic fill and suppresses the heatmaps.
- * Heatmaps already simulate the grain level so showing both is redundant.
- */
- showGrain?: boolean
- showTempHeatmap?: boolean
- showMoistureHeatmap?: boolean
- showHotspots?: boolean
- /**
- * When true renders a reset camera button overlaid on the 3D view.
- */
- showResetButton?: boolean
- showTemp?: boolean
- showMoisture?: boolean
- xOffset?: number
- width?: number
- height?: number
-}
-
-export default function Bin3dView(props: Props) {
- const { bin, scale = 100, fillPercent, nodeClick, showTempHeatmap, showGrain, showHotspots, showResetButton, showMoistureHeatmap, showTemp, showMoisture, xOffset, width = 1, height = 1 } = props
-
- // Resolve effective dimensions, falling back to defaults when advanced
- // dimensions have not been configured (bin methods return 0 in that case).
- const dims = useMemo(() => ({
- diameter: bin.diameter(),
- sidewallHeight: bin.sidewallHeight() || bin.diameter() + 50, //calculated using the diameter so 'default' bins always have the same shape
- roofHeight: bin.roofHeight() || DEFAULT_ROOF_HEIGHT_CM,
- hopperHeight: bin.hopperHeight() || (bin.binShape() === pond.BinShape.BIN_SHAPE_HOPPER_BOTTOM ? DEFAULT_HOPPER_HEIGHT_CM : DEFAULT_FLAT_HEIGHT_CM),
- }), [bin]);
-
- // Compute the initial camera radius so the full bin fits in frame.
- // Uses the perspective camera FOV (default 75°) with a padding factor.
- // Both the diameter and total height are considered so neither is clipped.
- const initialCameraRadius = useMemo(() => {
- const totalHeight = (dims.sidewallHeight + dims.roofHeight + dims.hopperHeight) / scale;
- const diameter = dims.diameter / scale;
- const largestDim = Math.max(totalHeight, diameter);
- const fovRad = (75 * Math.PI) / 180;
- const aspect = width/height
- const aspectPadding = aspect < 1 ? 1.4 : 1.0 // extra room on mobile
- // const padding = 1.3; // 30% breathing room
- const padding = (1.1 + (largestDim/100) * 4.5) * aspectPadding
- return (largestDim / (2 * Math.tan(fovRad / 2))) * padding;
- }, [dims, scale]);
-
- const cableData = useMemo(() => BuildCableData(bin, dims), [bin, dims]);
- const nodeData = useMemo(() => BuildNodeData(cableData), [cableData]);
-
- const isCableInventory =
- bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC ||
- bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_CABLE;
-
- // For cable inventory, TempHeatMap derives the wavy surface directly from
- // the top nodes in nodeData — no scalar needed.
- //
- // Flat fill: Y bounds from the same volume math as GrainFillFlat.
- const flatGrainBounds = useMemo(() => {
- if (isCableInventory || fillPercent === undefined) return undefined;
- return grainYBoundsFromFillPercent(
- dims.diameter,
- dims.sidewallHeight,
- dims.hopperHeight,
- dims.roofHeight,
- fillPercent
- );
- }, [isCableInventory, fillPercent, dims]);
-
- // Internal ref — wired to OrbitCameraControls and called by CameraOverlay
- const resetCameraFn = React.useRef<(() => void) | null>(null);
-
- const grainInventory = () => {
- const grainColour = "#fff300";
- //note that adjusting the opacity is how to adjust the brightness, less makes it dimmer
- if (isCableInventory) {
- return (
-
- )
- } else if (fillPercent) {
- return (
-
- )
- }
- }
-
- return (
-
- { resetCameraFn.current = fn; }}
- viewOffset={xOffset}
- />
-
-
-
- {
- if (nodeClick) nodeClick(node, cable)
- }}
- renderOrder={5}
- />
-
- {showHotspots && }
-
- {/* note that the heatmaps internally use render order 1,2, and 3 for the three separate coloured meshes */}
- {showGrain ? grainInventory() :
- showTempHeatmap && nodeData.length > 0
- ?
- : showMoistureHeatmap && nodeData.length > 0
- ?
- : grainInventory()
- }
-
-
-
-
-
-
-
-
-
- {/* Overlay — must be inside so it shares the same stacking context */}
- {showResetButton && (
- resetCameraFn.current?.()} />
- )}
-
- )
-}
diff --git a/src/bin/3dView/Systems/Cables/BinCables.tsx b/src/bin/3dView/Systems/Cables/BinCables.tsx
deleted file mode 100644
index 1901178..0000000
--- a/src/bin/3dView/Systems/Cables/BinCables.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import GrainCable from "./GrainCable";
-import { Bin } from "models";
-import React from "react";
-import { CableData } from "../../Data/BuildCableData";
-import { NodeData } from "../../Data/BuildNodeData";
-
-interface Props {
- cableData: CableData[],
- nodeData: NodeData[],
- bin: Bin,
- renderOrder?: number,
- displayTemp?: boolean,
- displayMoisture?: boolean,
- onNodeClick?: (node: NodeData, cable: CableData) => void
-}
-
-export default function BinCables(props: Props){
- const {bin, cableData, nodeData, onNodeClick, renderOrder, displayTemp, displayMoisture} = props
- return (
-
- {cableData.map((cable, i) => {
- const cableNodes = nodeData.filter(n => n.cableIndex === cable.cableIndex);
-
- return (
- {
- if(onNodeClick){
- onNodeClick(node, cable)
- }
- }}
- renderOrder={renderOrder}
- />
- )}
- )}
-
-
- )
-}
\ No newline at end of file
diff --git a/src/bin/3dView/Systems/Cables/CableNode.tsx b/src/bin/3dView/Systems/Cables/CableNode.tsx
deleted file mode 100644
index faca893..0000000
--- a/src/bin/3dView/Systems/Cables/CableNode.tsx
+++ /dev/null
@@ -1,187 +0,0 @@
-import Sphere, { SphereGeometry } from "3dModels/Shapes/3D/Sphere"
-import { useFrame, useThree } from "@react-three/fiber"
-import { Text } from "@react-three/drei"
-import React, { useMemo } from "react"
-import { Euler, Group, Vector3 } from "three"
-import { green, grey, orange, red } from "@mui/material/colors";
-import { useGlobalState } from "providers"
-import { pond } from "protobuf-ts/pond"
-import Ring, { RingGeometry } from "3dModels/Shapes/3D/Ring"
-import { NodeData } from "../../Data/BuildNodeData"
-
-interface Props {
- node: NodeData
- upperThreshold: number,
- targetMoisture: number,
- renderOrder?: number,
- displayTemp?: boolean,
- displayMoisture?: boolean,
- onNodeClick?: (node: NodeData) => void
-}
-
-export default function CableNode(props: Props) {
- const { node, upperThreshold, targetMoisture, onNodeClick, renderOrder, displayTemp, displayMoisture } = props
- const { camera } = useThree();
- const [{ user }] = useGlobalState();
- // const [showLabel, setShowLabel] = useState(false);
- const ROW_HEIGHT = 35;
- const PADDING = 5;
- const LABEL_WIDTH = 200;
- const FONT_SIZE = 25;
- const flagRef = React.useRef(null);
-
- useFrame(() => {
- if (!flagRef.current || !showLabel) return;
-
- // Get camera's right direction in world space
- const cameraRight = new Vector3();
- camera.getWorldDirection(cameraRight); // forward
- const up = new Vector3(0, 1, 0);
- cameraRight.crossVectors(cameraRight, up).normalize(); // right = forward × up
-
- const pos = node.position.clone();
- pos.addScaledVector(cameraRight, LABEL_WIDTH / 2); // offset right in screen space
- pos.addScaledVector(camera.position.clone().sub(node.position).normalize(), 1); // nudge toward camera
-
- flagRef.current.position.copy(pos);
- flagRef.current.quaternion.copy(camera.quaternion);
- });
-
- const tempDisplay = useMemo(() => {
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
- return ((node.celcius * 9 / 5) + 32).toFixed(2) + "°F"
- }
- return node.celcius.toFixed(2) + "°C"
- }, [node.celcius, user]);
-
- const rows = useMemo(() => {
- const anyToggleOn = displayTemp || displayMoisture;
-
- if (node.excluded) {
- if (!anyToggleOn) return [];
- return [{ text: "Excluded", color: grey[700] }];
- }
-
- const r: { text: string; color: string }[] = [];
-
- // TEMP
- if (displayTemp && node.celcius !== 0) {
- r.push({
- text: tempDisplay,
- // color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE).colour()
- //will be green when below threshold, orange when it goeas above but by less than 5 degrees, and red if more than 5 over
- color: node.celcius > upperThreshold ? node.celcius > upperThreshold + 5 ? red[700] : orange[800]: green[800]
- });
- }
-
- // MOISTURE
- if (displayMoisture) {
- if (node.moisture && node.moisture > 0) {
- r.push({
- text: `${node.moisture.toFixed(2)}% EMC`,
- // color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC).colour()
- color: node.moisture > targetMoisture ? node.moisture > targetMoisture + 0.5 ? red[700] : orange[800]: green[800]
- });
- }
- //apparently we dont want to display humidity at all
- // else if (node.humidity && node.humidity > 0) {
- // r.push({
- // text: `${node.humidity.toFixed(2)}%`,
- // // color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_PERCENT).colour()
- // color: "black"
- // });
- // }
- }
-
- const hasData = r.length > 0;
-
- // Only show top node if there's actual data
- if (node.topNode && hasData) {
- r.unshift({ text: "Top Node", color: grey[700] });
- }
-
- return r;
- }, [node, tempDisplay, displayTemp, displayMoisture]);
-
- const geometry = React.useMemo(() => ({
- radius: node.radius,
- } as SphereGeometry), [node]);
-
- const topNodeGeo = React.useMemo(() => ({
- radius: node.radius,
- thickness: 5,
- } as RingGeometry), [node]);
-
- const topNodePosition = React.useMemo(
- () => new Vector3(node.position.x, node.position.y + 25, node.position.z),
- [node]
- );
-
- const topNodeRotation = React.useMemo(() => new Euler(-Math.PI / 2, 0, 0), []);
-
- const labelHeight = rows.length * ROW_HEIGHT + PADDING;
-
- const getRowY = (index: number) => {
- const totalHeight = rows.length * ROW_HEIGHT;
- return totalHeight / 2 - (index * ROW_HEIGHT) - ROW_HEIGHT / 2;
- };
-
- const nodeColour = () => {
- if (node.excluded) return "grey";
- return "white";
- };
-
- const handleClick = (e: any) => {
- e.stopPropagation();
- onNodeClick?.(node);
- };
-
- const showLabel = rows.length > 0;
-
- return (
-
-
-
- {node.topNode && (
-
- )}
-
- {node.inGrain && showLabel &&
-
-
-
-
-
- {rows.map((row, i) => (
-
- {row.text}
-
- ))}
-
-}
-
- )
-}
\ No newline at end of file
diff --git a/src/bin/3dView/Systems/Cables/GrainCable.tsx b/src/bin/3dView/Systems/Cables/GrainCable.tsx
deleted file mode 100644
index 71ba6bd..0000000
--- a/src/bin/3dView/Systems/Cables/GrainCable.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import Cylinder, { CylinderGeometry } from "3dModels/Shapes/3D/Cylinder"
-import React from "react"
-import { Vector3 } from "three"
-import CableNode from "./CableNode"
-import { Bin } from "models"
-import { CableData } from "../../Data/BuildCableData"
-import { NodeData } from "../../Data/BuildNodeData"
-
-interface Props {
- cable: CableData,
- nodes: NodeData[],
- bin: Bin,
- renderOrder?: number,
- displayTemp?: boolean,
- displayMoisture?: boolean,
- onNodeClick?: (node: NodeData) => void
-}
-export default function GrainCable(props: Props) {
- const {cable, nodes, bin, onNodeClick, renderOrder, displayTemp, displayMoisture} = props
-
- const calculateHeight = () => {
- return cable.topPosition.distanceTo(cable.bottomPosition)
- }
-
- const calculateCenter = () => {
- return cable.bottomPosition.clone()
- .add(
- cable.topPosition.clone()
- .sub(cable.bottomPosition)
- .multiplyScalar(0.5)
- );
- };
-
- const geometry = React.useMemo(() => ({
- radiusTop: cable.radius,
- radiusBottom: cable.radius,
- height: calculateHeight(),
- radialSegments: cable.radialSegments ?? 10,
- heightSegments: cable.heightSegments ?? 2
- } as CylinderGeometry), [cable]);
-
- return (
-
-
- {nodes.map((node, i) => (
-
- ))}
-
- )
-}
\ No newline at end of file
diff --git a/src/bin/3dView/Systems/Heatmap/MoistureHeatmap.tsx b/src/bin/3dView/Systems/Heatmap/MoistureHeatmap.tsx
deleted file mode 100644
index 4756c4a..0000000
--- a/src/bin/3dView/Systems/Heatmap/MoistureHeatmap.tsx
+++ /dev/null
@@ -1,466 +0,0 @@
-import { useMemo } from "react";
-import * as THREE from "three";
-import { Bin } from "models";
-import { NodeData } from "../../Data/BuildNodeData";
-import React from "react";
-import { BinDims } from "bin/3dView/Data/BuildCableData";
-
-interface Props {
- binDimensions: BinDims
- targetMoisture: number
- nodes: NodeData[];
- /**
- * For flat fill percent inventory — a scalar Y ceiling for the heatmap top.
- * Used when there are no cable top nodes to drive a surface.
- */
- flatMaxY?: number;
- /** For flat fill percent — Y floor of filled grain (hopper tip or cylinder base). */
- flatMinY?: number;
-}
-
-const RADIAL_RINGS = 20;
-const THETA_SEGMENTS = 40;
-const HEIGHT_STEPS = 28;
-
-const YELLOW_DELTA = 2;
-const RED_DELTA = 4;
-
-const IDW_POWER = 4;
-const RED_OPACITY = 0.3;
-const GREEN_OPACITY = 0.3;
-const YELLOW_OPACITY = 0.8;
-
-const ANGLE_JITTER = 0.2;
-const RADIAL_JITTER = 0.05;
-const LAYER_TWIST = 0.22;
-
-// IDW power for the horizontal surface interpolation —
-// matches GrainCableFill's default of 2
-const SURFACE_IDW_POWER = 2;
-
-function moistureToHeat(moisture: number, target: number): number {
- if (moisture <= target) return 0;
- const delta = moisture - target;
- if (delta >= RED_DELTA) return 2;
- if (delta >= YELLOW_DELTA) return 1 + (delta - YELLOW_DELTA) / (RED_DELTA - YELLOW_DELTA);
- return delta / YELLOW_DELTA;
-}
-
-function heatToRGB(heat: number): number[] {
- const GREEN = [0, 0.5, 0.02];
- const YELLOW = [0.7, 0.86, 0.0];
- const RED = [0.8, 0.0, 0.01];
-
- if (heat <= 0) return GREEN;
-
- if (heat <= 1) {
- const t = Math.pow(heat, 0.75);
- return [
- GREEN[0] + (YELLOW[0] - GREEN[0]) * t,
- GREEN[1] + (YELLOW[1] - GREEN[1]) * t,
- GREEN[2] + (YELLOW[2] - GREEN[2]) * t,
- ];
- }
-
- const t = Math.pow(heat - 1, 0.75);
- return [
- YELLOW[0] + (RED[0] - YELLOW[0]) * t,
- YELLOW[1] + (RED[1] - YELLOW[1]) * t,
- YELLOW[2] + (RED[2] - YELLOW[2]) * t,
- ];
-}
-
-interface MoistureAnchor {
- x: number; y: number; z: number;
- moisture: number;
-}
-
-interface SurfaceAnchor {
- x: number; z: number; y: number;
-}
-
-// 3D IDW for moisture interpolation
-function idwMoisture(
- px: number, py: number, pz: number,
- anchors: MoistureAnchor[],
- power: number
-): number {
- let totalWeight = 0;
- let weightedSum = 0;
-
- for (const a of anchors) {
- const dx = px - a.x;
- const dy = py - a.y;
- const dz = pz - a.z;
- const distSq = dx * dx + dy * dy + dz * dz;
- if (distSq < 0.001) return a.moisture;
- const weight = 1 / Math.pow(distSq, power / 2);
- totalWeight += weight;
- weightedSum += a.moisture * weight;
- }
-
- return totalWeight === 0 ? 0 : weightedSum / totalWeight;
-}
-
-// 2D IDW for surface height interpolation — matches GrainCableFill exactly
-function idwSurfaceY(
- x: number, z: number,
- anchors: SurfaceAnchor[],
- power: number
-): number {
- let totalWeight = 0;
- let weightedY = 0;
-
- for (const a of anchors) {
- const dx = x - a.x;
- const dz = z - a.z;
- const distSq = dx * dx + dz * dz;
- if (distSq < 0.001) return a.y;
- const weight = 1 / Math.pow(distSq, power / 2);
- totalWeight += weight;
- weightedY += a.y * weight;
- }
-
- return totalWeight === 0 ? 0 : weightedY / totalWeight;
-}
-
-function hashNoise(a: number, b: number, c: number) {
- const x = Math.sin(a * 127.1 + b * 311.7 + c * 74.7) * 43758.5453;
- return (x - Math.floor(x)) * 2 - 1;
-}
-
-export default function MoistureHeatMap({ binDimensions, targetMoisture, nodes, flatMaxY, flatMinY }: Props) {
- const binRadius = binDimensions.diameter / 2;
- const sidewallHeight = binDimensions.sidewallHeight;
- const hopperHeight = binDimensions.hopperHeight ?? 0;
- const roofHeight = binDimensions.roofHeight ?? 0;
-
- const sidewallBaseY = -sidewallHeight / 2;
- const sidewallTopY = sidewallHeight / 2;
- const hopperTipY = sidewallBaseY - hopperHeight;
- const roofTipY = sidewallTopY + roofHeight;
-
- const maxRadiusAtY = (y: number) => {
- if (y > sidewallTopY) {
- if (roofHeight <= 0 || y >= roofTipY) return 0;
- return binRadius * (1 - (y - sidewallTopY) / roofHeight);
- }
- if (y >= sidewallBaseY) return binRadius;
- if (hopperHeight <= 0 || y <= hopperTipY) return 0;
- return binRadius * ((y - hopperTipY) / hopperHeight);
- };
-
- // Moisture anchors — all in-grain nodes that have a moisture reading
- const moistureAnchors = useMemo(
- () =>
- nodes
- .filter(n => n.inGrain && !n.excluded && n.moisture !== undefined)
- .map(n => ({
- x: n.position.x,
- y: n.position.y,
- z: n.position.z,
- moisture: n.moisture as number,
- })),
- [nodes]
- );
-
- // Surface anchors — top nodes only, Y offset by half spacing.
- // Only used when flatMaxY is NOT provided (i.e. Automatic / Hybrid_Cable
- // inventory control). For any other control type the caller passes flatMaxY
- // and we must use a flat surface instead of the wavy cable-driven one.
- const surfaceAnchors = useMemo(() => {
- if (flatMaxY !== undefined) {
- // Non-cable inventory: force flat surface — ignore cable top nodes
- return [];
- }
- return nodes
- .filter(n => n.topNode && n.inGrain && !n.excluded)
- .map(n => ({
- x: n.position.x,
- z: n.position.z,
- y: n.position.y + n.nodeSpacing * 0.5,
- }));
- }, [nodes, flatMaxY]);
-
- // Wall clamp Y — average of surface anchor heights.
- // Prevents the surface from piling up at the bin wall where there are no cables.
- // Matches GrainCableFill's wallY computation.
- const wallY = useMemo(() => {
- if (surfaceAnchors.length === 0) return flatMaxY ?? sidewallBaseY;
- return surfaceAnchors.reduce((sum, a) => sum + a.y, 0) / surfaceAnchors.length;
- }, [surfaceAnchors, flatMaxY, sidewallBaseY]);
-
- // Lowest Y the grain surface or heatmap may occupy (hopper tip or cylinder floor).
- const grainFloorY = hopperHeight > 0 ? hopperTipY : sidewallBaseY;
-
- // For each (x, z) position, returns the Y ceiling of the grain surface.
- // Uses cable surface IDW when top nodes exist, falls back to flatMaxY.
- const getSurfaceY = (x: number, z: number): number => {
- if (surfaceAnchors.length > 0) {
- const raw = idwSurfaceY(x, z, surfaceAnchors, SURFACE_IDW_POWER);
- return Math.max(grainFloorY, Math.min(roofTipY, raw));
- }
- return flatMaxY ?? sidewallBaseY;
- };
-
- // Global max Y — highest point of the surface (used for grid bottom calc)
- const maxGrainY = useMemo(() => {
- if (surfaceAnchors.length > 0) {
- return Math.max(...surfaceAnchors.map(a => a.y), wallY);
- }
- return flatMaxY ?? sidewallBaseY;
- }, [surfaceAnchors, wallY, flatMaxY, sidewallBaseY]);
-
- // Bottom of filled grain. Flat fill uses volume bounds; cable fill rests on
- // the hopper tip (matches GrainCableFill) rather than the lowest cable node.
- const minGrainY = useMemo(() => {
- if (flatMinY !== undefined) return flatMinY;
- return grainFloorY;
- }, [flatMinY, grainFloorY]);
-
- const geometry = useMemo(() => {
- if (!moistureAnchors.length) return null;
-
- const pointsPerLayer = 1 + RADIAL_RINGS * THETA_SEGMENTS;
- const totalVerts = HEIGHT_STEPS * pointsPerLayer + 1;
-
- const positions = new Float32Array(totalVerts * 3);
- const colors = new Float32Array(totalVerts * 3);
- const heats = new Float32Array(totalVerts);
-
- const grainBottomY = minGrainY;
- const grainHeight = maxGrainY - grainBottomY;
-
- if (grainHeight <= 0) return null;
-
- // Pre-compute the (x, z) position for each (ring, seg) slot so we
- // can look up the surface Y ceiling per column
- const colX: number[] = new Array(RADIAL_RINGS * THETA_SEGMENTS);
- const colZ: number[] = new Array(RADIAL_RINGS * THETA_SEGMENTS);
- const colSurfaceY: number[] = new Array(RADIAL_RINGS * THETA_SEGMENTS);
-
- // Use the outermost layer's radius for surface Y lookup (no jitter/twist)
- // so the surface shape matches GrainCableFill cleanly
- const topLayerAllowedRadius = maxRadiusAtY(maxGrainY) * 0.97;
-
- for (let ring = 0; ring < RADIAL_RINGS; ring++) {
- const u = (ring + 1) / RADIAL_RINGS;
- const rFrac = 1 - Math.pow(1 - u, 2.2);
- const r = rFrac * topLayerAllowedRadius;
-
- for (let seg = 0; seg < THETA_SEGMENTS; seg++) {
- const angle = (seg / THETA_SEGMENTS) * Math.PI * 2;
- const x = Math.cos(angle) * r;
- const z = Math.sin(angle) * r;
- const ci = ring * THETA_SEGMENTS + seg;
- colX[ci] = x;
- colZ[ci] = z;
- // Outermost ring uses wallY, inner rings use full IDW surface
- colSurfaceY[ci] = ring === RADIAL_RINGS - 1
- ? wallY
- : getSurfaceY(x, z);
- }
- }
- // Center column surface Y
- const centerSurfaceY = getSurfaceY(0, 0);
-
- for (let hStep = 0; hStep < HEIGHT_STEPS; hStep++) {
- const t = hStep / (HEIGHT_STEPS - 1);
-
- const layerBase = hStep * pointsPerLayer;
- const allowedRadius = maxRadiusAtY(grainBottomY + t * grainHeight) * 0.97;
-
- // Center vertex — Y is lerped from bottom to its column surface ceiling
- const centerY = grainBottomY + t * (centerSurfaceY - grainBottomY);
-
- const centerMoisture = idwMoisture(0, centerY, 0, moistureAnchors, IDW_POWER);
- const centerHeat = moistureToHeat(centerMoisture, targetMoisture);
- const [cr, cg, cb] = heatToRGB(centerHeat);
-
- positions[layerBase * 3] = 0;
- positions[layerBase * 3 + 1] = centerY;
- positions[layerBase * 3 + 2] = 0;
- colors[layerBase * 3] = cr;
- colors[layerBase * 3 + 1] = cg;
- colors[layerBase * 3 + 2] = cb;
- heats[layerBase] = centerHeat;
-
- for (let ring = 0; ring < RADIAL_RINGS; ring++) {
- const u = (ring + 1) / RADIAL_RINGS;
- const rFrac = 1 - Math.pow(1 - u, 2.2);
-
- for (let seg = 0; seg < THETA_SEGMENTS; seg++) {
- const noiseA = hashNoise(hStep, ring, seg);
- const noiseR = hashNoise(seg, hStep, ring + 11);
-
- const baseRadius = rFrac * allowedRadius;
- const jitterRadius = baseRadius + noiseR * RADIAL_JITTER * allowedRadius;
- const r = Math.max(0, Math.min(jitterRadius, allowedRadius));
-
- const layerPhase = t * LAYER_TWIST;
- const angle =
- (seg / THETA_SEGMENTS) * Math.PI * 2 +
- layerPhase +
- noiseA * ANGLE_JITTER;
-
- const x = Math.cos(angle) * r;
- const z = Math.sin(angle) * r;
-
- // Per-column surface Y ceiling — this is what gives the wavy top
- const ci = ring * THETA_SEGMENTS + seg;
- const surfaceY = colSurfaceY[ci];
-
- // Lerp this vertex Y from grainBottomY up to its column surface ceiling
- const y = grainBottomY + t * (surfaceY - grainBottomY);
-
- const moisture = idwMoisture(x, y, z, moistureAnchors, IDW_POWER);
- const heat = moistureToHeat(moisture, targetMoisture);
- const [vr, vg, vb] = heatToRGB(heat);
-
- const vi = layerBase + 1 + ring * THETA_SEGMENTS + seg;
-
- heats[vi] = heat;
- positions[vi * 3] = x;
- positions[vi * 3 + 1] = y;
- positions[vi * 3 + 2] = z;
- colors[vi * 3] = vr;
- colors[vi * 3 + 1] = vg;
- colors[vi * 3 + 2] = vb;
- }
- }
- }
-
- const idx = (hStep: number, ring: number, seg: number) => {
- if (ring < 0) return hStep * pointsPerLayer;
- const s = ((seg % THETA_SEGMENTS) + THETA_SEGMENTS) % THETA_SEGMENTS;
- return hStep * pointsPerLayer + 1 + ring * THETA_SEGMENTS + s;
- };
-
- const green: number[] = [];
- const yellow: number[] = [];
- const red: number[] = [];
-
- function pushTri(a: number, b: number, c: number) {
- const avg = (heats[a] + heats[b] + heats[c]) / 3;
- if (avg < 1) green.push(a, b, c);
- else if (avg < 2) yellow.push(a, b, c);
- else red.push(a, b, c);
- }
-
- for (let h = 0; h < HEIGHT_STEPS - 1; h++) {
- for (let ring = 0; ring < RADIAL_RINGS - 1; ring++) {
- for (let seg = 0; seg < THETA_SEGMENTS; seg++) {
- const next = (seg + 1) % THETA_SEGMENTS;
- const a = idx(h, ring, seg);
- const b = idx(h, ring, next);
- const e = idx(h + 1, ring, seg);
- const f = idx(h + 1, ring, next);
- pushTri(a, e, b);
- pushTri(e, f, b);
- }
- }
-
- // center fill
- for (let seg = 0; seg < THETA_SEGMENTS; seg++) {
- const next = (seg + 1) % THETA_SEGMENTS;
- const center0 = idx(h, -1, 0);
- const center1 = idx(h + 1, -1, 0);
- const a = idx(h, 0, seg);
- const b = idx(h, 0, next);
- const c = idx(h + 1, 0, seg);
- const d = idx(h + 1, 0, next);
- pushTri(center0, c, a);
- pushTri(center0, center1, c);
- pushTri(a, c, b);
- pushTri(b, c, d);
- }
- }
-
- // Close the bottom at the hopper tip only when grain reaches the tip.
- const tipVertexIndex = HEIGHT_STEPS * pointsPerLayer;
- if (hopperHeight > 0 && grainBottomY <= hopperTipY + 0.01) {
- const tipMoisture = idwMoisture(0, hopperTipY, 0, moistureAnchors, IDW_POWER);
- const tipHeat = moistureToHeat(tipMoisture, targetMoisture);
- const [tr, tg, tb] = heatToRGB(tipHeat);
-
- positions[tipVertexIndex * 3] = 0;
- positions[tipVertexIndex * 3 + 1] = hopperTipY;
- positions[tipVertexIndex * 3 + 2] = 0;
- colors[tipVertexIndex * 3] = tr;
- colors[tipVertexIndex * 3 + 1] = tg;
- colors[tipVertexIndex * 3 + 2] = tb;
- heats[tipVertexIndex] = tipHeat;
-
- const outerRing = RADIAL_RINGS - 1;
- for (let seg = 0; seg < THETA_SEGMENTS; seg++) {
- const next = (seg + 1) % THETA_SEGMENTS;
- pushTri(tipVertexIndex, idx(0, outerRing, next), idx(0, outerRing, seg));
- }
- }
-
- function makeGeo(indices: number[]) {
- const g = new THREE.BufferGeometry();
- g.setAttribute("position", new THREE.BufferAttribute(positions, 3));
- g.setAttribute("color", new THREE.BufferAttribute(colors, 3));
- g.setIndex(indices);
- return g;
- }
-
- return {
- green: makeGeo(green),
- yellow: makeGeo(yellow),
- red: makeGeo(red),
- };
- }, [
- moistureAnchors,
- surfaceAnchors,
- wallY,
- maxGrainY,
- minGrainY,
- hopperTipY,
- sidewallBaseY,
- binRadius,
- targetMoisture,
- hopperHeight,
- roofHeight,
- roofTipY,
- flatMaxY,
- flatMinY,
- ]);
-
- if (!geometry) return null;
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/bin/3dView/Systems/Heatmap/NodePointCloud.tsx b/src/bin/3dView/Systems/Heatmap/NodePointCloud.tsx
deleted file mode 100644
index 3d096b4..0000000
--- a/src/bin/3dView/Systems/Heatmap/NodePointCloud.tsx
+++ /dev/null
@@ -1,254 +0,0 @@
-import { NodeData } from "bin/3dView/Data/BuildNodeData";
-import { colourFade } from "bin/3dView/utils/tempToColour";
-import { Bin } from "models";
-import { useMemo } from "react";
-import { CanvasTexture, NormalBlending } from "three";
-
-interface Props {
- bin: Bin;
- nodes: NodeData[];
-}
-
-export default function NodePointCloud(props: Props) {
- const { bin, nodes } = props;
- // 🎛️ TUNING KNOBS
- const BASE_RADIUS_FACTOR = 0.15;
- const INTENSITY_DENSITY_MULT = 1.25;
- const INTENSITY_DENSITY_BASE = 0.3;
-
- const RADIAL_BASE = 6;
- const THETA_BASE = 10;
- const PHI_BASE = 10;
-
- const MIN_EDGE_INTENSITY = 0.2;
-
- const BASE_BRIGHTNESS = 0.7;
- const INTENSITY_BRIGHTNESS_MULT = 0.7;
-
- const OPACITY = 0.3;
- const POINT_SIZE = 1;
-
- // IDW power — how sharply nearer nodes dominate the field
- const IDW_POWER = 2;
-
- // Minimum net signed magnitude to emit a point.
- // Keeps near-zero cancellation zones empty rather than noisy.
- const EMIT_THRESHOLD = 0.05;
-
- // -----------------------------
- // BIN GEOMETRY
- // -----------------------------
- const binRadius = bin.diameter() / 2;
- const sidewallHeight = bin.sidewallHeight();
- const hopperHeight = bin.hopperHeight() ?? 0;
- const sidewallBaseY = -sidewallHeight / 2;
- const hopperTipY = sidewallBaseY - hopperHeight;
-
- const maxRadiusAtY = (y: number): number => {
- if (y >= sidewallBaseY) return binRadius;
- if (hopperHeight <= 0 || y <= hopperTipY) return 0;
- const t = (y - hopperTipY) / hopperHeight;
- return binRadius * t;
- };
-
- // -----------------------------
- // GRAIN LIMIT
- // -----------------------------
- const maxGrainY = useMemo(() => {
- let maxY = -Infinity;
- nodes.forEach((node) => {
- if (!node.inGrain || node.excluded) return;
- if (node.position.y > maxY) maxY = node.position.y;
- });
- return maxY;
- }, [nodes]);
-
- // -----------------------------
- // PRE-COMPUTE SIGNED NODE CONTRIBUTIONS
- // Only nodes outside threshold contribute to the field.
- // Hot nodes carry a positive signed intensity, cold nodes negative.
- // -----------------------------
- const signedNodes = useMemo(() => {
- return nodes
- .filter(n => n.inGrain && !n.excluded)
- .map(n => {
- const lower = bin.lowerTempThreshold();
- const upper = bin.targetTemp();
-
- if (n.celcius >= lower && n.celcius <= upper) return null;
-
- const distance = n.celcius < lower
- ? lower - n.celcius
- : n.celcius - upper;
-
- const intensity = Math.min(1, distance / colourFade);
- // positive = hot, negative = cold
- const signedIntensity = n.celcius > upper ? intensity : -intensity;
-
- return {
- x: n.position.x,
- y: n.position.y,
- z: n.position.z,
- signedIntensity,
- cloudRadius: bin.diameter() * BASE_RADIUS_FACTOR * (0.5 + intensity),
- densityMultiplier: INTENSITY_DENSITY_BASE + intensity * INTENSITY_DENSITY_MULT,
- };
- })
- .filter(Boolean) as {
- x: number; y: number; z: number;
- signedIntensity: number;
- cloudRadius: number;
- densityMultiplier: number;
- }[];
- }, [nodes, bin]);
-
- /**
- * Evaluates the net signed field at (px, py, pz) by summing
- * IDW-weighted signed intensities from all contributing nodes.
- *
- * Returns a value in [-1, 1]:
- * > 0 → net hot
- * < 0 → net cold
- * ~0 → cancelled out — point will not be emitted
- */
- const evaluateField = (px: number, py: number, pz: number): number => {
- let totalWeight = 0;
- let weightedSum = 0;
-
- for (const node of signedNodes) {
- const dx = px - node.x;
- const dy = py - node.y;
- const dz = pz - node.z;
- const distSq = dx * dx + dy * dy + dz * dz;
-
- const weight = distSq < 0.001
- ? 1e6
- : 1 / Math.pow(distSq, IDW_POWER / 2);
-
- totalWeight += weight;
- weightedSum += node.signedIntensity * weight;
- }
-
- if (totalWeight === 0) return 0;
- return weightedSum / totalWeight;
- };
-
- // -----------------------------
- // BUILD POINT CLOUD
- // -----------------------------
- const { positions, colors } = useMemo(() => {
- const positions: number[] = [];
- const colors: number[] = [];
-
- signedNodes.forEach((node) => {
- const radialSteps = Math.floor(RADIAL_BASE * node.densityMultiplier);
- const thetaSteps = Math.floor(THETA_BASE * node.densityMultiplier);
- const phiSteps = Math.floor(PHI_BASE * node.densityMultiplier);
-
- for (let rStep = 0; rStep < radialSteps; rStep++) {
- const r = Math.sqrt(rStep / radialSteps) * node.cloudRadius;
-
- for (let pStep = 0; pStep < phiSteps; pStep++) {
- const phi = ((pStep + 0.5) / phiSteps) * Math.PI;
-
- for (let tStep = 0; tStep < thetaSteps; tStep++) {
- const theta = (tStep / thetaSteps) * Math.PI * 2;
-
- const x = node.x + r * Math.sin(phi) * Math.cos(theta);
- const y = node.y + r * Math.cos(phi);
- const z = node.z + r * Math.sin(phi) * Math.sin(theta);
-
- // Bin boundary checks
- if (y < hopperTipY || y > maxGrainY) continue;
- const distXZ = Math.sqrt(x * x + z * z);
- if (distXZ > maxRadiusAtY(y)) continue;
-
- // Evaluate the full signed field at this point.
- // A point in the overlap of a hot and cold cloud will have
- // a net value near zero and will be skipped rather than
- // rendered pink.
- const netField = evaluateField(x, y, z);
-
- if (Math.abs(netField) < EMIT_THRESHOLD) continue;
-
- positions.push(x, y, z);
-
- // Brightness driven by net magnitude, not per-node intensity
- const netMagnitude = Math.abs(netField);
- const falloff = 1 - r / node.cloudRadius;
- const spatialFalloff = Math.pow(Math.max(0, falloff), 2);
- const adjustedFalloff = MIN_EDGE_INTENSITY + (1 - MIN_EDGE_INTENSITY) * spatialFalloff;
- const intensityCurve = netMagnitude * netMagnitude;
- const brightness = adjustedFalloff * (BASE_BRIGHTNESS + INTENSITY_BRIGHTNESS_MULT * intensityCurve);
-
- if (netField > 0) {
- colors.push(brightness, 0, 0); // hot → red
- } else {
- colors.push(0, 0, brightness); // cold → blue
- }
- }
- }
- }
- });
-
- return {
- positions: new Float32Array(positions),
- colors: new Float32Array(colors),
- };
- }, [signedNodes, maxGrainY, hopperTipY]);
-
- const circleTexture = useMemo(() => {
- const size = 64;
- const canvas = document.createElement("canvas");
- canvas.width = size;
- canvas.height = size;
-
- const ctx = canvas.getContext("2d")!;
- const gradient = ctx.createRadialGradient(
- size / 2, size / 2, 0,
- size / 2, size / 2, size / 2
- );
-
- gradient.addColorStop(0, "rgba(255,255,255,1)");
- gradient.addColorStop(1, "rgba(255,255,255,0)");
-
- ctx.fillStyle = gradient;
- ctx.fillRect(0, 0, size, size);
-
- const texture = new CanvasTexture(canvas);
- texture.needsUpdate = true;
- return texture;
- }, []);
-
- // -----------------------------
- // RENDER
- // -----------------------------
- return (
-
-
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/bin/3dView/Systems/Heatmap/TempHeatMap.tsx b/src/bin/3dView/Systems/Heatmap/TempHeatMap.tsx
deleted file mode 100644
index 1b25f0a..0000000
--- a/src/bin/3dView/Systems/Heatmap/TempHeatMap.tsx
+++ /dev/null
@@ -1,470 +0,0 @@
-import { useMemo } from "react";
-import * as THREE from "three";
-import { Bin } from "models";
-import { NodeData } from "../../Data/BuildNodeData";
-import React from "react";
-import { BinDims } from "bin/3dView/Data/BuildCableData";
-
-interface Props {
- binDimensions: BinDims
- targetTemp: number
- nodes: NodeData[];
- /**
- * For flat fill percent inventory — a scalar Y ceiling for the heatmap top.
- * Used when there are no cable top nodes to drive a surface.
- */
- flatMaxY?: number;
- /** For flat fill percent — Y floor of filled grain (hopper tip or cylinder base). */
- flatMinY?: number;
-}
-
-const RADIAL_RINGS = 20;
-const THETA_SEGMENTS = 40;
-const HEIGHT_STEPS = 28;
-
-const YELLOW_DELTA = 5;
-const RED_DELTA = 10;
-
-const IDW_POWER = 4;
-const RED_OPACITY = 0.3;
-const GREEN_OPACITY = 0.3;
-const YELLOW_OPACITY = 0.8;
-
-const ANGLE_JITTER = 0.2;
-const RADIAL_JITTER = 0.05;
-const LAYER_TWIST = 0.22;
-
-// IDW power for the horizontal surface interpolation —
-// matches GrainCableFill's default of 2
-const SURFACE_IDW_POWER = 2;
-
-function tempToHeat(temp: number, upper: number): number {
- if (temp <= upper) return 0;
- const delta = temp - upper;
- if (delta >= RED_DELTA) return 2;
- if (delta >= YELLOW_DELTA) return 1 + (delta - YELLOW_DELTA) / (RED_DELTA - YELLOW_DELTA);
- return delta / YELLOW_DELTA;
-}
-
-function heatToRGB(heat: number): number[] {
- const GREEN = [0, 0.5, 0.02];
- const YELLOW = [0.7, 0.86, 0.0];
- const RED = [0.8, 0.0, 0.01];
-
- if (heat <= 0) return GREEN;
-
- if (heat <= 1) {
- const t = Math.pow(heat, 0.75);
- return [
- GREEN[0] + (YELLOW[0] - GREEN[0]) * t,
- GREEN[1] + (YELLOW[1] - GREEN[1]) * t,
- GREEN[2] + (YELLOW[2] - GREEN[2]) * t,
- ];
- }
-
- const t = Math.pow(heat - 1, 0.75);
- return [
- YELLOW[0] + (RED[0] - YELLOW[0]) * t,
- YELLOW[1] + (RED[1] - YELLOW[1]) * t,
- YELLOW[2] + (RED[2] - YELLOW[2]) * t,
- ];
-}
-
-interface TempAnchor {
- x: number; y: number; z: number;
- celcius: number;
-}
-
-interface SurfaceAnchor {
- x: number; z: number; y: number;
-}
-
-// 3D IDW for temperature interpolation
-function idwTemp(
- px: number, py: number, pz: number,
- anchors: TempAnchor[],
- power: number
-): number {
- let totalWeight = 0;
- let weightedSum = 0;
-
- for (const a of anchors) {
- const dx = px - a.x;
- const dy = py - a.y;
- const dz = pz - a.z;
- const distSq = dx * dx + dy * dy + dz * dz;
- if (distSq < 0.001) return a.celcius;
- const weight = 1 / Math.pow(distSq, power / 2);
- totalWeight += weight;
- weightedSum += a.celcius * weight;
- }
-
- return totalWeight === 0 ? 0 : weightedSum / totalWeight;
-}
-
-// 2D IDW for surface height interpolation — matches GrainCableFill exactly
-function idwSurfaceY(
- x: number, z: number,
- anchors: SurfaceAnchor[],
- power: number
-): number {
- let totalWeight = 0;
- let weightedY = 0;
-
- for (const a of anchors) {
- const dx = x - a.x;
- const dz = z - a.z;
- const distSq = dx * dx + dz * dz;
- if (distSq < 0.001) return a.y;
- const weight = 1 / Math.pow(distSq, power / 2);
- totalWeight += weight;
- weightedY += a.y * weight;
- }
-
- return totalWeight === 0 ? 0 : weightedY / totalWeight;
-}
-
-function hashNoise(a: number, b: number, c: number) {
- const x = Math.sin(a * 127.1 + b * 311.7 + c * 74.7) * 43758.5453;
- return (x - Math.floor(x)) * 2 - 1;
-}
-
-export default function TempHeatMap({ binDimensions, targetTemp, nodes, flatMaxY, flatMinY }: Props) {
- const binRadius = binDimensions.diameter / 2;
- const sidewallHeight = binDimensions.sidewallHeight;
- const hopperHeight = binDimensions.hopperHeight ?? 0;
- const roofHeight = binDimensions.roofHeight ?? 0;
- const upperThreshold = targetTemp;
-
- const sidewallBaseY = -sidewallHeight / 2;
- const sidewallTopY = sidewallHeight / 2;
- const hopperTipY = sidewallBaseY - hopperHeight;
- const roofTipY = sidewallTopY + roofHeight;
-
- const maxRadiusAtY = (y: number) => {
- // Inside the roof cone — tapers from binRadius at sidewallTopY to 0 at roofTipY
- if (y > sidewallTopY) {
- if (roofHeight <= 0 || y >= roofTipY) return 0;
- return binRadius * (1 - (y - sidewallTopY) / roofHeight);
- }
- // Straight sidewall
- if (y >= sidewallBaseY) return binRadius;
- // Inside the hopper cone — tapers from binRadius at sidewallBaseY to 0 at hopperTipY
- if (hopperHeight <= 0 || y <= hopperTipY) return 0;
- return binRadius * ((y - hopperTipY) / hopperHeight);
- };
-
- // Temperature anchors — all in-grain nodes
- const tempAnchors = useMemo(
- () =>
- nodes
- .filter(n => n.inGrain && !n.excluded)
- .map(n => ({
- x: n.position.x,
- y: n.position.y,
- z: n.position.z,
- celcius: n.celcius,
- })),
- [nodes]
- );
-
- // Surface anchors — top nodes only, Y offset by half spacing.
- // Only used when flatMaxY is NOT provided (i.e. Automatic / Hybrid_Cable
- // inventory control). For any other control type the caller passes flatMaxY
- // and we must use a flat surface instead of the wavy cable-driven one.
- const surfaceAnchors = useMemo(() => {
- if (flatMaxY !== undefined) {
- // Non-cable inventory: force flat surface — ignore cable top nodes
- return [];
- }
- return nodes
- .filter(n => n.topNode && n.inGrain && !n.excluded)
- .map(n => ({
- x: n.position.x,
- z: n.position.z,
- y: n.position.y + n.nodeSpacing * 0.5,
- }));
- }, [nodes, flatMaxY]);
-
- // Wall clamp Y — average of surface anchor heights.
- // Prevents the surface from piling up at the bin wall where there are no cables.
- // Matches GrainCableFill's wallY computation.
- const wallY = useMemo(() => {
- if (surfaceAnchors.length === 0) return flatMaxY ?? sidewallBaseY;
- return surfaceAnchors.reduce((sum, a) => sum + a.y, 0) / surfaceAnchors.length;
- }, [surfaceAnchors, flatMaxY, sidewallBaseY]);
-
- // Lowest Y the grain surface or heatmap may occupy (hopper tip or cylinder floor).
- const grainFloorY = hopperHeight > 0 ? hopperTipY : sidewallBaseY;
-
- // For each (x, z) position, returns the Y ceiling of the grain surface.
- // Uses cable surface IDW when top nodes exist, falls back to flatMaxY.
- const getSurfaceY = (x: number, z: number): number => {
- if (surfaceAnchors.length > 0) {
- const raw = idwSurfaceY(x, z, surfaceAnchors, SURFACE_IDW_POWER);
- return Math.max(grainFloorY, Math.min(roofTipY, raw));
- }
- return flatMaxY ?? sidewallBaseY;
- };
-
- // Global max Y — highest point of the surface (used for grid bottom calc)
- const maxGrainY = useMemo(() => {
- if (surfaceAnchors.length > 0) {
- return Math.max(...surfaceAnchors.map(a => a.y), wallY);
- }
- return flatMaxY ?? sidewallBaseY;
- }, [surfaceAnchors, wallY, flatMaxY, sidewallBaseY]);
-
- // Bottom of filled grain. Flat fill uses volume bounds; cable fill rests on
- // the hopper tip (matches GrainCableFill) rather than the lowest cable node.
- const minGrainY = useMemo(() => {
- if (flatMinY !== undefined) return flatMinY;
- return grainFloorY;
- }, [flatMinY, grainFloorY]);
-
- const geometry = useMemo(() => {
- if (!tempAnchors.length) return null;
-
- const pointsPerLayer = 1 + RADIAL_RINGS * THETA_SEGMENTS;
- const totalVerts = HEIGHT_STEPS * pointsPerLayer + 1;
-
- const positions = new Float32Array(totalVerts * 3);
- const colors = new Float32Array(totalVerts * 3);
- const heats = new Float32Array(totalVerts);
-
- const grainBottomY = minGrainY;
- const grainHeight = maxGrainY - grainBottomY;
-
- if (grainHeight <= 0) return null;
-
- // Pre-compute the (x, z) position for each (ring, seg) slot so we
- // can look up the surface Y ceiling per column
- const colX: number[] = new Array(RADIAL_RINGS * THETA_SEGMENTS);
- const colZ: number[] = new Array(RADIAL_RINGS * THETA_SEGMENTS);
- const colSurfaceY: number[] = new Array(RADIAL_RINGS * THETA_SEGMENTS);
-
- // Use the outermost layer's radius for surface Y lookup (no jitter/twist)
- // so the surface shape matches GrainCableFill cleanly
- const topLayerAllowedRadius = maxRadiusAtY(maxGrainY) * 0.97;
-
- for (let ring = 0; ring < RADIAL_RINGS; ring++) {
- const u = (ring + 1) / RADIAL_RINGS;
- const rFrac = 1 - Math.pow(1 - u, 2.2);
- const r = rFrac * topLayerAllowedRadius;
-
- for (let seg = 0; seg < THETA_SEGMENTS; seg++) {
- const angle = (seg / THETA_SEGMENTS) * Math.PI * 2;
- const x = Math.cos(angle) * r;
- const z = Math.sin(angle) * r;
- const ci = ring * THETA_SEGMENTS + seg;
- colX[ci] = x;
- colZ[ci] = z;
- // Outermost ring uses wallY, inner rings use full IDW surface
- colSurfaceY[ci] = ring === RADIAL_RINGS - 1
- ? wallY
- : getSurfaceY(x, z);
- }
- }
- // Center column surface Y
- const centerSurfaceY = getSurfaceY(0, 0);
-
- for (let hStep = 0; hStep < HEIGHT_STEPS; hStep++) {
- const t = hStep / (HEIGHT_STEPS - 1);
-
- const layerBase = hStep * pointsPerLayer;
- const allowedRadius = maxRadiusAtY(grainBottomY + t * grainHeight) * 0.97;
-
- // Center vertex — Y is lerped from bottom to its column surface ceiling
- const centerY = grainBottomY + t * (centerSurfaceY - grainBottomY);
-
- const centerTemp = idwTemp(0, centerY, 0, tempAnchors, IDW_POWER);
- const centerHeat = tempToHeat(centerTemp, upperThreshold);
- const [cr, cg, cb] = heatToRGB(centerHeat);
-
- positions[layerBase * 3] = 0;
- positions[layerBase * 3 + 1] = centerY;
- positions[layerBase * 3 + 2] = 0;
- colors[layerBase * 3] = cr;
- colors[layerBase * 3 + 1] = cg;
- colors[layerBase * 3 + 2] = cb;
- heats[layerBase] = centerHeat;
-
- for (let ring = 0; ring < RADIAL_RINGS; ring++) {
- const u = (ring + 1) / RADIAL_RINGS;
- const rFrac = 1 - Math.pow(1 - u, 2.2);
-
- for (let seg = 0; seg < THETA_SEGMENTS; seg++) {
- const noiseA = hashNoise(hStep, ring, seg);
- const noiseR = hashNoise(seg, hStep, ring + 11);
-
- const baseRadius = rFrac * allowedRadius;
- const jitterRadius = baseRadius + noiseR * RADIAL_JITTER * allowedRadius;
- const r = Math.max(0, Math.min(jitterRadius, allowedRadius));
-
- const layerPhase = t * LAYER_TWIST;
- const angle =
- (seg / THETA_SEGMENTS) * Math.PI * 2 +
- layerPhase +
- noiseA * ANGLE_JITTER;
-
- const x = Math.cos(angle) * r;
- const z = Math.sin(angle) * r;
-
- // Per-column surface Y ceiling — this is what gives the wavy top
- const ci = ring * THETA_SEGMENTS + seg;
- const surfaceY = colSurfaceY[ci];
-
- // Lerp this vertex Y from grainBottomY up to its column surface ceiling
- const y = grainBottomY + t * (surfaceY - grainBottomY);
-
- const temp = idwTemp(x, y, z, tempAnchors, IDW_POWER);
- const heat = tempToHeat(temp, upperThreshold);
- const [vr, vg, vb] = heatToRGB(heat);
-
- const vi = layerBase + 1 + ring * THETA_SEGMENTS + seg;
-
- heats[vi] = heat;
- positions[vi * 3] = x;
- positions[vi * 3 + 1] = y;
- positions[vi * 3 + 2] = z;
- colors[vi * 3] = vr;
- colors[vi * 3 + 1] = vg;
- colors[vi * 3 + 2] = vb;
- }
- }
- }
-
- const idx = (hStep: number, ring: number, seg: number) => {
- if (ring < 0) return hStep * pointsPerLayer;
- const s = ((seg % THETA_SEGMENTS) + THETA_SEGMENTS) % THETA_SEGMENTS;
- return hStep * pointsPerLayer + 1 + ring * THETA_SEGMENTS + s;
- };
-
- const green: number[] = [];
- const yellow: number[] = [];
- const red: number[] = [];
-
- function pushTri(a: number, b: number, c: number) {
- const avg = (heats[a] + heats[b] + heats[c]) / 3;
- if (avg < 1) green.push(a, b, c);
- else if (avg < 2) yellow.push(a, b, c);
- else red.push(a, b, c);
- }
-
- for (let h = 0; h < HEIGHT_STEPS - 1; h++) {
- for (let ring = 0; ring < RADIAL_RINGS - 1; ring++) {
- for (let seg = 0; seg < THETA_SEGMENTS; seg++) {
- const next = (seg + 1) % THETA_SEGMENTS;
- const a = idx(h, ring, seg);
- const b = idx(h, ring, next);
- const e = idx(h + 1, ring, seg);
- const f = idx(h + 1, ring, next);
- pushTri(a, e, b);
- pushTri(e, f, b);
- }
- }
-
- // center fill
- for (let seg = 0; seg < THETA_SEGMENTS; seg++) {
- const next = (seg + 1) % THETA_SEGMENTS;
- const center0 = idx(h, -1, 0);
- const center1 = idx(h + 1, -1, 0);
- const a = idx(h, 0, seg);
- const b = idx(h, 0, next);
- const c = idx(h + 1, 0, seg);
- const d = idx(h + 1, 0, next);
- pushTri(center0, c, a);
- pushTri(center0, center1, c);
- pushTri(a, c, b);
- pushTri(b, c, d);
- }
- }
-
- // Close the bottom at the hopper tip only when grain reaches the tip.
- const tipVertexIndex = HEIGHT_STEPS * pointsPerLayer;
- if (hopperHeight > 0 && grainBottomY <= hopperTipY + 0.01) {
- const tipTemp = idwTemp(0, hopperTipY, 0, tempAnchors, IDW_POWER);
- const tipHeat = tempToHeat(tipTemp, upperThreshold);
- const [tr, tg, tb] = heatToRGB(tipHeat);
-
- positions[tipVertexIndex * 3] = 0;
- positions[tipVertexIndex * 3 + 1] = hopperTipY;
- positions[tipVertexIndex * 3 + 2] = 0;
- colors[tipVertexIndex * 3] = tr;
- colors[tipVertexIndex * 3 + 1] = tg;
- colors[tipVertexIndex * 3 + 2] = tb;
- heats[tipVertexIndex] = tipHeat;
-
- const outerRing = RADIAL_RINGS - 1;
- for (let seg = 0; seg < THETA_SEGMENTS; seg++) {
- const next = (seg + 1) % THETA_SEGMENTS;
- pushTri(tipVertexIndex, idx(0, outerRing, next), idx(0, outerRing, seg));
- }
- }
-
- function makeGeo(indices: number[]) {
- const g = new THREE.BufferGeometry();
- g.setAttribute("position", new THREE.BufferAttribute(positions, 3));
- g.setAttribute("color", new THREE.BufferAttribute(colors, 3));
- g.setIndex(indices);
- return g;
- }
-
- return {
- green: makeGeo(green),
- yellow: makeGeo(yellow),
- red: makeGeo(red),
- };
- }, [
- tempAnchors,
- surfaceAnchors,
- wallY,
- maxGrainY,
- minGrainY,
- hopperTipY,
- sidewallBaseY,
- binRadius,
- upperThreshold,
- hopperHeight,
- roofHeight,
- roofTipY,
- flatMaxY,
- flatMinY,
- ]);
-
- if (!geometry) return null;
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/bin/3dView/Systems/Inventory/GrainCableFill.tsx b/src/bin/3dView/Systems/Inventory/GrainCableFill.tsx
deleted file mode 100644
index f7b769b..0000000
--- a/src/bin/3dView/Systems/Inventory/GrainCableFill.tsx
+++ /dev/null
@@ -1,353 +0,0 @@
-import { useMemo } from "react";
-import * as THREE from "three";
-import { NodeData } from "../../Data/BuildNodeData";
-import Cone from "3dModels/Shapes/3D/Cone";
-import { Vector3, Euler } from "three";
-import Cylinder from "3dModels/Shapes/3D/Cylinder";
-
-interface Props {
- diameter: number;
- sidewallHeight: number;
- hopperHeight?: number;
- nodes: NodeData[];
- grainOpacity?: number
- /**
- * Fallback flat fill percent (0–1) used when no top nodes are available.
- * If undefined and no top nodes exist, nothing is rendered.
- */
- fallbackFillPercent?: number;
- colour?: string
-}
-
-// Tuning knobs
-const RADIAL_RINGS = 24; // vertex rings radiating outward from center
-const THETA_SEGMENTS = 36; // vertices around each ring
-
-/**
- * Inverse-distance weighted interpolation of Y height at a given (x, z) point.
- * Each top node contributes a weighted Y based on its horizontal distance from the point.
- * The power parameter controls how sharply nearer nodes dominate (2 = standard IDW).
- */
-function idwHeight(
- x: number,
- z: number,
- anchors: { x: number; z: number; y: number }[],
- power = 2
-): number {
- let totalWeight = 0;
- let weightedY = 0;
-
- for (const anchor of anchors) {
- const dx = x - anchor.x;
- const dz = z - anchor.z;
- const distSq = dx * dx + dz * dz;
-
- // If we're sitting exactly on an anchor, return its Y immediately
- if (distSq < 0.001) return anchor.y;
-
- const weight = 1 / Math.pow(distSq, power / 2);
- totalWeight += weight;
- weightedY += anchor.y * weight;
- }
-
- return weightedY / totalWeight;
-}
-
-export default function GrainCableFill(props: Props) {
- const {
- diameter,
- sidewallHeight,
- hopperHeight = 0,
- nodes,
- fallbackFillPercent,
- grainOpacity,
- colour
- } = props;
-
- const binRadius = diameter / 2;
- // Slightly inset to avoid z-fighting with the shell
- const grainRadius = binRadius * 0.98;
-
- // --- Collect top nodes (non-excluded, inGrain) ---
- const topNodes = useMemo(() =>
- nodes.filter(n => n.topNode && n.inGrain && !n.excluded),
- [nodes]
- );
-
- // --- Build surface anchors: top node Y + half spacing offset ---
- // This places the grain line halfway between the top node and the node above it,
- // matching the 2D bin view convention.
- const anchors = useMemo(() =>
- topNodes.map(n => ({
- x: n.position.x,
- z: n.position.z,
- y: n.position.y + n.nodeSpacing * 0.5,
- })),
- [topNodes]
- );
-
- // --- Wall clamp: average of all anchor Y values ---
- // Prevents grain from piling up at the wall where there are no cables.
- const wallY = useMemo(() => {
- if (anchors.length === 0) return -sidewallHeight / 2;
- return anchors.reduce((sum, a) => sum + a.y, 0) / anchors.length;
- }, [anchors, sidewallHeight]);
-
- // --- Build the polar surface mesh ---
- const surfaceGeometry = useMemo(() => {
- if (anchors.length === 0) return null;
-
- // Total vertices: center point + (RADIAL_RINGS * THETA_SEGMENTS) ring vertices
- const ringCount = RADIAL_RINGS;
- const segCount = THETA_SEGMENTS;
- const vertexCount = 1 + ringCount * segCount;
-
- const positions = new Float32Array(vertexCount * 3);
- const normals = new Float32Array(vertexCount * 3);
- const uvs = new Float32Array(vertexCount * 2);
-
- // Center vertex
- const centerY = idwHeight(0, 0, anchors);
- positions[0] = 0;
- positions[1] = centerY;
- positions[2] = 0;
- normals[0] = 0; normals[1] = 1; normals[2] = 0;
- uvs[0] = 0.5; uvs[1] = 0.5;
-
- // Ring vertices
- for (let ring = 0; ring < ringCount; ring++) {
- // sqrt distribution for even area density across rings
- const t = Math.sqrt((ring + 1) / ringCount);
- const r = t * grainRadius;
-
- for (let seg = 0; seg < segCount; seg++) {
- const angle = (seg / segCount) * Math.PI * 2;
- const x = Math.cos(angle) * r;
- const z = Math.sin(angle) * r;
-
- // Outermost ring clamps to wall average; inner rings interpolate
- //const isOuterRing = ring === ringCount - 1;
- // const y = idwHeight(x, z, anchors);
- const rawY = idwHeight(x, z, anchors);
-
- // Blend outer 20% of radius toward wallY
- const edgeStart = 0.8; // start taper at 80% radius
- const blendT = Math.max(0, (t - edgeStart) / (1 - edgeStart));
-
- // smoothstep
- const s = blendT * blendT * (3 - 2 * blendT);
-
- const y = rawY * (1 - s) + wallY * s;
-
- // Clamp Y to valid range: no higher than roof base, no lower than bin floor
- const clampedY = Math.max(
- -sidewallHeight / 2,
- Math.min(sidewallHeight / 2, y)
- );
-
- const vi = (1 + ring * segCount + seg) * 3;
- positions[vi] = x;
- positions[vi + 1] = clampedY;
- positions[vi + 2] = z;
-
- // Approximate normals — pointing up (will look fine for grain)
- normals[vi] = 0; normals[vi + 1] = 1; normals[vi + 2] = 0;
-
- const ui = (1 + ring * segCount + seg) * 2;
- uvs[ui] = (x / grainRadius) * 0.5 + 0.5;
- uvs[ui + 1] = (z / grainRadius) * 0.5 + 0.5;
- }
- }
-
- // --- Build triangle indices ---
- // Center fan: triangles from center point to first ring
- const indexList: number[] = [];
-
- for (let seg = 0; seg < segCount; seg++) {
- const next = (seg + 1) % segCount;
- indexList.push(0, 1 + seg, 1 + next);
- }
-
- // Ring quads: two triangles per quad between adjacent rings
- for (let ring = 0; ring < ringCount - 1; ring++) {
- for (let seg = 0; seg < segCount; seg++) {
- const next = (seg + 1) % segCount;
-
- const a = 1 + ring * segCount + seg;
- const b = 1 + ring * segCount + next;
- const c = 1 + (ring + 1) * segCount + seg;
- const d = 1 + (ring + 1) * segCount + next;
-
- indexList.push(a, c, b);
- indexList.push(b, c, d);
- }
- }
-
- const geo = new THREE.BufferGeometry();
- geo.setAttribute("position", new THREE.BufferAttribute(positions, 3));
- geo.setAttribute("normal", new THREE.BufferAttribute(normals, 3));
- geo.setAttribute("uv", new THREE.BufferAttribute(uvs, 2));
- geo.setIndex(indexList);
- geo.computeVertexNormals(); // smooth out the approximated normals
-
- return geo;
- }, [anchors, wallY, grainRadius, sidewallHeight]);
-
- // --- Hopper fill (reuse existing cone approach) ---
- // The surface mesh handles the cylindrical portion.
- // The hopper below is always fully filled if the surface is above the bin floor.
- const lowestSurfaceY = useMemo(() => {
- if (anchors.length === 0) return -sidewallHeight / 2;
- return Math.min(...anchors.map(a => a.y), wallY);
- }, [anchors, wallY, sidewallHeight]);
-
- const hopperIsActive = hopperHeight > 0 && lowestSurfaceY > -sidewallHeight / 2;
-
- const hopperPosition = useMemo(
- () => new Vector3(0, -(sidewallHeight / 2 + hopperHeight / 2), 0),
- [sidewallHeight, hopperHeight]
- );
-
- const hopperRotation = useMemo(() => new Euler(Math.PI, 0, 0), []);
-
- // --- Fallback: flat fill when no top nodes ---
- const fallbackGeometry = useMemo(() => {
- if (anchors.length > 0 || fallbackFillPercent === undefined) return null;
-
- const radius = diameter / 2;
- const fbRadius = radius * 0.98;
- const cylinderVolume = Math.PI * radius * radius * sidewallHeight;
- const hopperVolume = hopperHeight > 0
- ? (1 / 3) * Math.PI * radius * radius * hopperHeight
- : 0;
- const totalVolume = cylinderVolume + hopperVolume;
- const filledVolume = totalVolume * fallbackFillPercent;
-
- let hopperFillHeight = 0;
- let cylinderFillHeight = 0;
-
- if (hopperHeight > 0 && filledVolume <= hopperVolume) {
- const ratio = filledVolume / hopperVolume;
- hopperFillHeight = hopperHeight * Math.pow(ratio, 1 / 3);
- cylinderFillHeight = 0;
- } else {
- hopperFillHeight = hopperHeight;
- const remaining = filledVolume - hopperVolume;
- cylinderFillHeight = Math.max(0, remaining / (Math.PI * radius * radius));
- }
-
- return { fbRadius, hopperFillHeight, cylinderFillHeight };
- }, [anchors.length, fallbackFillPercent, diameter, sidewallHeight, hopperHeight]);
-
- // --- Render fallback ---
- if (anchors.length === 0) {
- if (!fallbackGeometry) return null;
-
- const { fbRadius, hopperFillHeight, cylinderFillHeight } = fallbackGeometry;
-
- return (
- <>
- {hopperHeight > 0 && hopperFillHeight > 0 && (
-
- )}
- {cylinderFillHeight > 0 && (
-
- )}
- >
- );
- }
-
- // --- Render cable-driven surface ---
- return (
- <>
- {/* Interpolated grain surface - i have not made a react component for this shape, not exactly a basic shape, so am just using mesh as is */}
- {surfaceGeometry && (
-
-
-
- )}
-
- {/* Cylindrical body of grain below the surface down to the bin floor / hopper top */}
-
-
- {/* Hopper fill — always full when grain surface exists above the floor */}
- {hopperIsActive && (
-
- )}
- >
- );
-}
\ No newline at end of file
diff --git a/src/bin/3dView/Systems/Inventory/GrainFillFlat.tsx b/src/bin/3dView/Systems/Inventory/GrainFillFlat.tsx
deleted file mode 100644
index d0cfcf5..0000000
--- a/src/bin/3dView/Systems/Inventory/GrainFillFlat.tsx
+++ /dev/null
@@ -1,132 +0,0 @@
-import React, { useMemo } from "react";
-import Cylinder from "3dModels/Shapes/3D/Cylinder";
-import Cone from "3dModels/Shapes/3D/Cone";
-import { Vector3, Euler } from "three";
-
-interface Props {
- diameter: number;
- sidewallHeight: number;
- hopperHeight?: number;
- fillPercent: number;
- grainOpacity?: number;
- colour?: string
-}
-
-export default function GrainFillFlat(props: Props) {
- const {
- diameter,
- sidewallHeight,
- hopperHeight = 0,
- fillPercent,
- grainOpacity,
- colour
- } = props;
-
- const radius = diameter / 2;
-
- // Slightly smaller to avoid z-fighting with shell
- const grainRadius = radius * 0.98;
-
- // --- volumes ---
- const cylinderVolume = Math.PI * radius * radius * sidewallHeight;
- const hopperVolume = hopperHeight > 0
- ? (1 / 3) * Math.PI * radius * radius * hopperHeight
- : 0;
-
- const totalVolume = cylinderVolume + hopperVolume;
- const filledVolume = totalVolume * fillPercent;
-
- // --- result values ---
- let hopperFillHeight = 0;
- let cylinderFillHeight = 0;
-
- if (hopperHeight > 0 && filledVolume <= hopperVolume) {
- // ONLY hopper filling
- const ratio = filledVolume / hopperVolume;
-
- hopperFillHeight = hopperHeight * Math.pow(ratio, 1 / 3);
- cylinderFillHeight = 0;
- } else {
- // hopper full (or no hopper)
- hopperFillHeight = hopperHeight;
-
- const remainingVolume = filledVolume - hopperVolume;
-
- cylinderFillHeight = Math.max(
- 0,
- remainingVolume / (Math.PI * radius * radius)
- );
- }
-
- // --- positions ---
-
- const hopperPosition = useMemo(
- () => new Vector3(
- 0,
- -(sidewallHeight / 2 + hopperHeight) + hopperFillHeight / 2, //adding the hopper height so it starts at the cone and not the flat side
- 0
- ),
- [sidewallHeight, hopperFillHeight]
- );
-
- //flip the cone so it matches the hoppers rotation with the cone pointed down
- const hopperRotation = useMemo(
- () => new Euler(Math.PI, 0, 0),
- []
- );
-
- const cylinderPosition = useMemo(
- () => new Vector3(
- 0,
- -sidewallHeight / 2 + cylinderFillHeight / 2,
- 0
- ),
- [sidewallHeight, cylinderFillHeight]
- );
-
- return (
- <>
- {/* Hopper fill */}
- {hopperHeight > 0 && hopperFillHeight > 0 && (
-
- )}
-
- {/* Cylinder fill */}
- {cylinderFillHeight > 0 && (
-
- )}
- >
- );
-}
\ No newline at end of file
diff --git a/src/bin/3dView/utils/grainFillBounds.ts b/src/bin/3dView/utils/grainFillBounds.ts
deleted file mode 100644
index d1b6fef..0000000
--- a/src/bin/3dView/utils/grainFillBounds.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { ConeVolume, CylinderVolume } from "common/TrigFunctions";
-
-/**
- * Y bounds of filled grain volume — matches GrainFillFlat / GrainCableFill fallback volume math.
- */
-export interface GrainYBounds {
- minY: number;
- maxY: number;
-}
-
-// Grain never physically fills all the way to the roof tip — cap roof fill
-// at this fraction of roofHeight so there is always visible breathing room.
-const MAX_ROOF_FILL_FRACTION = 0.75;
-
-export function grainYBoundsFromFillPercent(
- diameter: number,
- sidewallHeight: number,
- hopperHeight: number,
- roofHeight: number,
- fillPercent: number
-): GrainYBounds {
- const radius = diameter / 2;
- const sidewallBaseY = -sidewallHeight / 2;
- const hopperTipY = sidewallBaseY - hopperHeight;
- const roofBaseY = sidewallHeight / 2;
-
- if (fillPercent <= 0) {
- return { minY: sidewallBaseY, maxY: sidewallBaseY };
- }
-
- const cylinderVolume = CylinderVolume(radius, sidewallHeight);
- const hopperVolume = hopperHeight > 0 ? ConeVolume(radius, hopperHeight) : 0;
- const roofVolume = roofHeight > 0 ? ConeVolume(radius, roofHeight) : 0;
-
- // Volume of the usable roof portion (up to MAX_ROOF_FILL_FRACTION of height).
- // A cone fills as h³ so the capped volume fraction = MAX_ROOF_FILL_FRACTION³.
- const roofVolumeCapped = roofVolume * Math.pow(MAX_ROOF_FILL_FRACTION, 3);
-
- const totalVolume = cylinderVolume + hopperVolume + roofVolumeCapped;
- const filledVolume = totalVolume * fillPercent;
-
- // Fill the hopper first
- if (hopperHeight > 0 && filledVolume <= hopperVolume) {
- const ratio = filledVolume / hopperVolume;
- const hopperFillHeight = hopperHeight * Math.pow(ratio, 1 / 3);
- return {
- minY: hopperTipY,
- maxY: hopperTipY + hopperFillHeight,
- };
- }
-
- // Then fill the cylinder
- const volumeAfterHopper = filledVolume - hopperVolume;
- if (volumeAfterHopper <= cylinderVolume) {
- const cylinderFillHeight = volumeAfterHopper / (Math.PI * radius * radius);
- return {
- minY: hopperHeight > 0 ? hopperTipY : sidewallBaseY,
- maxY: sidewallBaseY + cylinderFillHeight,
- };
- }
-
- // Finally fill into the roof cone, capped at MAX_ROOF_FILL_FRACTION
- const roofFilledVolume = volumeAfterHopper - cylinderVolume;
- const roofRatio = Math.min(1, roofFilledVolume / roofVolumeCapped);
- const roofFillHeight = roofHeight * MAX_ROOF_FILL_FRACTION * Math.pow(roofRatio, 1 / 3);
-
- return {
- minY: hopperHeight > 0 ? hopperTipY : sidewallBaseY,
- maxY: roofBaseY + roofFillHeight,
- };
-}
\ No newline at end of file
diff --git a/src/bin/3dView/utils/tempToColour.ts b/src/bin/3dView/utils/tempToColour.ts
deleted file mode 100644
index b8317df..0000000
--- a/src/bin/3dView/utils/tempToColour.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { Color } from "three";
-import { NodeData } from "../Data/BuildNodeData";
-
-type ColourMode = "node" | "heatmap";
-
-const GREEN = new Color("#52c41a");
-const BLUE = new Color("#3399ff");
-const RED = new Color("#ff4d4f");
-
-export const colourFade = 20;
-const minimumLightness = 0.3;
-const lightnessRange = 0.2;
-const minimumSaturation = 0.7;
-const saturationRange = 0.8;
-
-const getTemperatureIntensity = (temp: number, lower: number, upper: number) => {
- if (temp >= lower && temp <= upper) return 0;
- if (temp < lower) return lower - temp;
- return temp - upper;
-}
-
-const getVisualIntensity = (distance: number, mode: ColourMode) => {
- // 1 = near threshold, 0 = far
- const intensity = Math.min(1, distance / colourFade);
- switch(mode){
- case "node":
- return 1 - intensity
- case "heatmap":
- return intensity
- }
-};
-
-export function TempToColour(
- node: NodeData,
- lower: number,
- upper: number,
- mode: ColourMode
-): Color | null {
-
- if (!node.inGrain || node.excluded) return null;
-
- const temp = node.celcius;
-
- // in-threshold behavior
- if (temp >= lower && temp <= upper) {
- return mode === "heatmap" ? null : GREEN;
- }
-
- const distance = getTemperatureIntensity(temp, lower, upper);
- const intensity = getVisualIntensity(distance, mode);
-
- const color = new Color();
- const hsl = { h: 0, s: 1, l: 1 };
-
- if (temp < lower) {
- BLUE.getHSL(hsl);
- } else {
- RED.getHSL(hsl);
- }
-
- color.setHSL(
- hsl.h,
- (saturationRange * intensity) + minimumSaturation,
- (lightnessRange * intensity) + minimumLightness
- );
-
- return color;
-}
\ No newline at end of file
diff --git a/src/bin/AddBinFab.tsx b/src/bin/AddBinFab.tsx
index 1eea8e3..774785e 100644
--- a/src/bin/AddBinFab.tsx
+++ b/src/bin/AddBinFab.tsx
@@ -28,7 +28,6 @@ const useStyles = makeStyles((theme: Theme) => {
position: "fixed",
bottom: theme.spacing(8), //for mobile navigator
right: theme.spacing(2),
- zIndex: theme.zIndex.speedDial,
[theme.breakpoints.up("sm")]: {
bottom: theme.spacing(1.75)
}
diff --git a/src/bin/BinActions.tsx b/src/bin/BinActions.tsx
index c9f7c8f..a425ea6 100644
--- a/src/bin/BinActions.tsx
+++ b/src/bin/BinActions.tsx
@@ -57,8 +57,6 @@ interface Props {
components?: Map;
setComponents?: React.Dispatch>>;
updateBinStatus?: (componentKeys: string[], removed?: boolean) => void;
- componentDevices?: Map
- setComponentDevices?: React.Dispatch>>;
}
interface OpenState {
@@ -79,9 +77,7 @@ export default function BinActions(props: Props) {
refreshCallback,
userID,
components,
- componentDevices,
setComponents,
- setComponentDevices,
updateBinStatus
} = props;
const [anchorEl, setAnchorEl] = React.useState(null);
@@ -249,9 +245,7 @@ export default function BinActions(props: Props) {
open={openState.sensors}
userID={userID}
components={components}
- componentDevices={componentDevices}
setComponents={setComponents}
- setComponentDevices={setComponentDevices}
updateBinStatus={updateBinStatus}
onClose={refresh => {
if (refresh === true) {
diff --git a/src/bin/BinCardV2.tsx b/src/bin/BinCardV2.tsx
index ac2b300..5244db9 100644
--- a/src/bin/BinCardV2.tsx
+++ b/src/bin/BinCardV2.tsx
@@ -17,7 +17,7 @@ import moment from "moment";
import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers";
import React, { useEffect, useState } from "react";
-import { celsiusToFahrenheit, or } from "utils";
+import { celsiusToFahrenheit, getGrainUnit, or } from "utils";
//import { useHistory } from "react-router";
//import BinModeDot from "./BinModeDot";
import BinSVGV2 from "./BinSVGV2";
@@ -444,16 +444,16 @@ export default function BinCard(props: Props) {
);
}
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) {
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) {
return (
- bin.grainInventory(user).toLocaleString() +
+ bin.grainInventory().toLocaleString() +
" mT " +
bin.fillPercent() +
"%"
);
- } else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) {
+ } else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) {
return (
- bin.grainInventory(user).toLocaleString() +
+ bin.grainInventory().toLocaleString() +
" t " +
bin.fillPercent() +
"%"
diff --git a/src/bin/BinComponents.tsx b/src/bin/BinComponents.tsx
index 0b81cba..7864c46 100644
--- a/src/bin/BinComponents.tsx
+++ b/src/bin/BinComponents.tsx
@@ -71,9 +71,7 @@ const useStyles = makeStyles((theme: Theme) => {
interface Props {
components?: Map;
- componentDevices?: Map;
setComponents?: React.Dispatch>>;
- setComponentDevices?: React.Dispatch>>;
bin: string;
binGrain: pond.Grain;
updateBinStatus?: (componentKeys: string[], removed?: boolean) => void;
@@ -86,7 +84,7 @@ interface Option {
}
export default function BinComponents(props: Props) {
- const { components, componentDevices, bin, setComponents, setComponentDevices, updateBinStatus, binGrain } = props;
+ const { components, bin, setComponents, updateBinStatus, binGrain } = props;
const [{as}] = useGlobalState();
const classes = useStyles();
const binAPI = useBinAPI();
@@ -210,54 +208,20 @@ export default function BinComponents(props: Props) {
// }
// }, [selectedDevice, componentAPI, deviceComponents, snackbar]);
- const removeComponent = (component: string, device?: number) => {
- console.log(device)
- binAPI.removeComponent(bin, component, device, as).then(() => {
-
+ const removeComponent = (component: string) => {
+ binAPI.removeComponent(bin, component, as).then(() => {
if (components && setComponents) {
if (components.delete(component)) {
let newComponents = new Map(components);
setComponents(newComponents);
}
}
- if(componentDevices && setComponentDevices) {
- if (componentDevices.delete(component)) {
- let newComponentDevices = new Map(componentDevices);
- setComponentDevices(newComponentDevices);
- }
- }
snackbar.info("Component removed from bin");
});
setComponentToRemove(undefined);
};
- const removeComponentConfirmation = (last?: boolean) => {
- let lastComponent = false;
- let devId: number | undefined;
- if(last){
- lastComponent = last
- }else{
- if (componentDevices && componentToRemove) {
- const id = componentDevices.get(componentToRemove.key());
-
- if (id !== undefined) {
- devId = id
- let count = 0;
-
- for (const val of componentDevices.values()) {
- if (val === devId) {
- count++;
-
- if (count > 1) {
- break; // stop early once we know it's not the last
- }
- }
- }
-
- lastComponent = count === 1;
- }
- }
- }
+ const removeComponentConfirmation = () => {
return (
setComponentToRemove(undefined)}>
Remove {componentToRemove?.name()}?
- {lastComponent ?
-
- This component {componentToRemove?.name()} is the last component from its device. You can choose to remove only the component itself or the device along with it.
- Leaving the device may have unexpected results if the device will continue to be used and it is recommended to remove it if the device is being moved to a new bin.
-
- :
-
- This will remove {componentToRemove?.name()} from this bin. If you don't have direct
- access to this component or the device it is attached to, you will not be able to add it
- back.
-
- }
+
+ This will remove {componentToRemove?.name()} from this bin. If you don't have direct
+ access to this component or the device it is attached to, you will not be able to add it
+ back.
+
setComponentToRemove(undefined)} color="primary">
Cancel
- {lastComponent
- ?
-
- removeComponent(componentToRemove!.key())} color="primary">
- Remove Component
-
- removeComponent(componentToRemove!.key(), devId)} color="primary">
- Remove Component And Device
-
- :
removeComponent(componentToRemove!.key())} color="primary">
Remove
- }
);
@@ -310,7 +256,7 @@ export default function BinComponents(props: Props) {
Remove All Components?
- This will remove All attached components and devices from this bin. If you don't have direct access
+ This will remove All attached components from this bin. If you don't have direct access
to these components or the devices they are attached to, you will not be able to add
them back.
@@ -374,12 +320,6 @@ export default function BinComponents(props: Props) {
setComponents(newComponents);
}
}
- if(componentDevices && setComponentDevices) {
- if (componentDevices.set(component.key(), device)) {
- let newComponentDevices = new Map(componentDevices);
- setComponentDevices(newComponentDevices);
- }
- }
//if a grain cable was added to the bin update the components grain type to match the bin
if (preferences.type === pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE) {
let settings = component.settings;
@@ -398,40 +338,15 @@ export default function BinComponents(props: Props) {
}
});
} else {
- let lastComponent = false;
- if (componentDevices) {
- console.log(componentDevices)
- let count = 0;
- for (const val of componentDevices.values()) {
- if (val === device) {
- count++;
-
- if (count > 1) {
- break; // stop early once we know it's not the last
- }
- }
- }
- lastComponent = count === 1;
- }
- if(lastComponent){
- setComponentToRemove(component)
- }else{
- binAPI.removeComponent(bin, component.key(), undefined, as).then(resp => {
- if (components && setComponents) {
+ binAPI.removeComponent(bin, component.key(), as).then(resp => {
+ if (components && setComponents) {
if (components.delete(component.key())) {
let newComponents = new Map(components);
setComponents(newComponents);
}
}
- if(componentDevices && setComponentDevices) {
- if (componentDevices.delete(component.key())) {
- let newComponentDevices = new Map(componentDevices);
- setComponentDevices(newComponentDevices);
- }
- }
snackbar.info("Component removed from bin");
- });
- }
+ });
}
};
@@ -652,10 +567,7 @@ export default function BinComponents(props: Props) {
{component.name()}
- {
- setComponentToRemove(component)
-
- }}>
+ setComponentToRemove(component)}>
diff --git a/src/bin/BinConditioningInteraction.tsx b/src/bin/BinConditioningInteraction.tsx
index d729462..bd6c3c7 100644
--- a/src/bin/BinConditioningInteraction.tsx
+++ b/src/bin/BinConditioningInteraction.tsx
@@ -21,7 +21,7 @@ import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
import { pond, quack } from "protobuf-ts/pond";
import { useGlobalState, useInteractionsAPI, useSnackbar } from "providers";
import React, { useEffect, useState } from "react";
-import { avg, fahrenheitToCelsius } from "utils";
+import { avg, fahrenheitToCelsius, getTemperatureUnit } from "utils";
import { makeStyles } from "@mui/styles";
import { Mark } from "@mui/material/Slider/useSlider.types";
@@ -103,7 +103,7 @@ export default function BinConditioningInteraction(props: Props) {
let sliderVals: Map = new Map();
let sliderMarks: Map = new Map();
passedInteraction.conditions().forEach(condition => {
- let describer = describeMeasurement(condition.measurementType, source.type(), undefined, undefined, user);
+ let describer = describeMeasurement(condition.measurementType, source.type());
//NOTE: toDisplay will convert the temp value to fahrenheit
sliderVals.set(condition.measurementType, describer.toDisplay(condition.value));
});
@@ -131,7 +131,7 @@ export default function BinConditioningInteraction(props: Props) {
temp &&
hum
) {
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
//the emc calc needs the temp to be in celsius
temp = fahrenheitToCelsius(temp);
}
@@ -139,7 +139,7 @@ export default function BinConditioningInteraction(props: Props) {
setBaseEMC(emc === hum ? undefined : emc);
}
- }, [sliderVals, grain, user]);
+ }, [sliderVals, grain]);
const updateInteraction = () => {
interactionAPI
@@ -173,17 +173,17 @@ export default function BinConditioningInteraction(props: Props) {
return (
{interaction.conditions().map((condition, i) => {
- let describer = describeMeasurement(condition.measurementType, source?.type(), source.subType(), undefined, user);
- let labelTail = describer.unit();
+ let describer = describeMeasurement(condition.measurementType, source?.type());
+ let labelTail = "";
let marks: Mark[] = [];
- // if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
- // if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
- // labelTail = "°F";
- // } else {
- // labelTail = "°C";
- // }
- // }
+ if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ labelTail = "°F";
+ } else {
+ labelTail = "°C";
+ }
+ }
if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT) {
labelTail = "%";
}
@@ -198,7 +198,7 @@ export default function BinConditioningInteraction(props: Props) {
return (
- {interactionConditionText(source, condition, false, user)}
+ {interactionConditionText(source, condition, false)}
>;
- componentDevices: Map;
-}
-
-type ReadingSlot = {
- timestamp: string;
- values: number[];
-};
-
-type CableReadingsAtCheckpoint = {
- temperature?: ReadingSlot;
- humidity?: ReadingSlot;
- moisture?: ReadingSlot;
-};
-
-/**
- * Per-checkpoint bucket for boolean output components (fans, heaters).
- * Only the most recent boolean reading in the time slice is kept.
- */
-type BooleanStateAtCheckpoint = {
- state?: ReadingSlot; // values[0] is the boolean (1 = on, 0 = off)
-};
-
-/** Per-checkpoint bucket: cable key → latest temp/humidity/moisture readings in that time slice. */
-type CheckpointReadings = Map;
-
-/** Per-checkpoint bucket: component key → latest boolean state in that time slice. */
-type CheckpointBooleanStates = Map;
-
-/**
- * Returns true when `candidate` should replace `existing` in a bucket.
- * Used so multiple samples in the same checkpoint keep only the newest reading per type.
- */
-function readingIsNewer(candidate: ReadingSlot, existing?: ReadingSlot): boolean {
- if (!existing) {
- return true;
- }
- return moment(candidate.timestamp).valueOf() > moment(existing.timestamp).valueOf();
-}
-
-/**
- * Stores one sampled reading on a cable's in-progress bucket state.
- * Routes by measurement type: temperature → celcius nodes, percent RH → humidity nodes,
- * grain EMC → moisture nodes. Ignores the update if an equal-or-newer reading is already stored.
- */
-function upsertReading(
- readings: CableReadingsAtCheckpoint,
- type: quack.MeasurementType,
- timestamp: string,
- values: number[]
-): void {
- const slot: ReadingSlot = { timestamp, values };
- if (type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
- if (readingIsNewer(slot, readings.temperature)) {
- readings.temperature = slot;
- }
- } else if (type === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT) {
- if (readingIsNewer(slot, readings.humidity)) {
- readings.humidity = slot;
- }
- } else if (type === quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC) {
- if (readingIsNewer(slot, readings.moisture)) {
- readings.moisture = slot;
- }
- }
-}
-
-/**
- * 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.
- */
-function buildCheckpointTimes(start: Moment, end: Moment, count: number): Moment[] {
- const startMs = start.valueOf();
- const endMs = end.valueOf();
- if (count <= 1 || endMs <= startMs) {
- return [moment(startMs)];
- }
- const span = endMs - startMs;
- return Array.from({ length: count }, (_, i) =>
- moment(startMs + (span * i) / (count - 1))
- );
-}
-
-/**
- * Maps a measurement timestamp to a checkpoint bucket index in [0, count - 1].
- * The [start, end] range is split into `count` equal-width segments; a reading lands in
- * the segment it falls into. Times before start → 0, after end → last bucket.
- */
-function checkpointIndexForTimestamp(
- timestamp: Moment,
- start: Moment,
- end: Moment,
- count: number
-): number {
- const startMs = start.valueOf();
- const endMs = end.valueOf();
- if (count <= 1 || endMs <= startMs) {
- return 0;
- }
- const t = timestamp.valueOf();
- if (t <= startMs) {
- return 0;
- }
- if (t >= endMs) {
- return count - 1;
- }
- const fraction = (t - startMs) / (endMs - startMs);
- const idx = Math.floor(fraction * count);
- return Math.min(idx, count - 1);
-}
-
-/**
- * Creates `count` empty buckets, each pre-populated with an empty reading map per cable key.
- * Output length matches checkpoint count; index aligns with `buildCheckpointTimes`.
- */
-function emptyCheckpointReadings(cableKeys: string[], count: number): CheckpointReadings[] {
- return Array.from({ length: count }, () => {
- const map: CheckpointReadings = new Map();
- cableKeys.forEach(key => map.set(key, {}));
- return map;
- });
-}
-
-/**
- * Creates `count` empty boolean state buckets, each pre-populated per component key.
- */
-function emptyCheckpointBooleanStates(
- componentKeys: string[],
- count: number
-): CheckpointBooleanStates[] {
- return Array.from({ length: count }, () => {
- const map: CheckpointBooleanStates = new Map();
- componentKeys.forEach(key => map.set(key, {}));
- return map;
- });
-}
-
-/**
- * Walks every sampled measurement from each cable response and places it into a time bucket.
- * `measurementResponses[i]` corresponds to `cableKeys[i]` (same order as the sample requests).
- * Within a bucket, only the newest reading per measurement type is kept per cable.
- */
-function assignMeasurementsToCheckpoints(
- cableKeys: string[],
- measurementResponses: pond.SampleUnitMeasurementsResponse[],
- start: Moment,
- end: Moment,
- count: number
-): CheckpointReadings[] {
- const buckets = emptyCheckpointReadings(cableKeys, count);
-
- measurementResponses.forEach((response, cableIndex) => {
- const cableKey = cableKeys[cableIndex];
- if (!cableKey) {
- return;
- }
-
- response.measurements.forEach(um => {
- um.timestamps.forEach((ts, i) => {
- const valueArray = um.values[i];
- if (!ts || !valueArray || valueArray.error) {
- return;
- }
- const idx = checkpointIndexForTimestamp(moment(ts), start, end, count);
- const readings = buckets[idx].get(cableKey)!;
- upsertReading(readings, um.type, ts, valueArray.values);
- });
- });
- });
-
- return buckets;
-}
-
-/**
- * Walks every sampled measurement from each boolean output component (fan/heater) response
- * and places the boolean state into a time bucket. Only the newest MEASUREMENT_TYPE_BOOLEAN
- * reading per component per bucket is kept.
- */
-function assignBooleanStatesToCheckpoints(
- componentKeys: string[],
- measurementResponses: pond.SampleUnitMeasurementsResponse[],
- start: Moment,
- end: Moment,
- count: number
-): CheckpointBooleanStates[] {
- const buckets = emptyCheckpointBooleanStates(componentKeys, count);
-
- measurementResponses.forEach((response, componentIndex) => {
- const componentKey = componentKeys[componentIndex];
- if (!componentKey) {
- return;
- }
-
- response.measurements.forEach(um => {
- if (um.type !== quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN) {
- return;
- }
- um.timestamps.forEach((ts, i) => {
- const valueArray = um.values[i];
- if (!ts || !valueArray || valueArray.error || valueArray.values.length === 0) {
- return;
- }
- const slot: ReadingSlot = { timestamp: ts, values: valueArray.values };
- const idx = checkpointIndexForTimestamp(moment(ts), start, end, count);
- const entry = buckets[idx].get(componentKey)!;
- if (readingIsNewer(slot, entry.state)) {
- entry.state = slot;
- }
- });
- });
- });
-
- return buckets;
-}
-
-/**
- * Picks the newest timestamp across temp, humidity, and moisture slots for a cable.
- * Used as `lastRead` on the reconstructed `pond.GrainCable`.
- */
-function latestTimestamp(readings: CableReadingsAtCheckpoint): string | undefined {
- const slots = [readings.temperature, readings.humidity, readings.moisture].filter(
- (s): s is ReadingSlot => s !== undefined
- );
- if (slots.length === 0) {
- return undefined;
- }
- return slots.reduce(
- (latest, slot) =>
- moment(slot.timestamp).valueOf() > moment(latest).valueOf() ? slot.timestamp : latest,
- slots[0].timestamp
- );
-}
-
-/**
- * Combines readings carried forward from earlier checkpoints with readings in the current bucket.
- * Any type missing in the current bucket keeps the previous checkpoint's value so playback
- * does not drop cables back to template defaults between sparse samples.
- */
-function mergeCarriedReadings(
- carried: CableReadingsAtCheckpoint,
- current: CableReadingsAtCheckpoint
-): CableReadingsAtCheckpoint {
- return {
- temperature: current.temperature ?? carried.temperature,
- humidity: current.humidity ?? carried.humidity,
- moisture: current.moisture ?? carried.moisture
- };
-}
-
-/**
- * Combines boolean states carried forward from earlier checkpoints with the current bucket.
- * If no reading exists in the current bucket the last known state is preserved, so fans/heaters
- * don't flicker back to their template default between sparse samples.
- */
-function mergeCarriedBooleanState(
- carried: BooleanStateAtCheckpoint,
- current: BooleanStateAtCheckpoint
-): BooleanStateAtCheckpoint {
- return {
- state: current.state ?? carried.state
- };
-}
-
-/**
- * Clones a live `pond.GrainCable` template (name, key, device, top node, etc.) and overwrites
- * node arrays from reconciled readings. `lastRead` is the newest measurement time, or the
- * checkpoint time when no readings exist yet for that cable.
- */
-function buildGrainCableFromReadings(
- template: pond.GrainCable,
- readings: CableReadingsAtCheckpoint,
- checkpointTime: Moment
-): pond.GrainCable {
- const cable = pond.GrainCable.fromObject(cloneDeep(template));
-
- // Clear template values so empty checkpoints don't inherit live data
- cable.celcius = [];
- cable.relativeHumidity = [];
- cable.moisture = [];
-
- if (readings.temperature) {
- cable.celcius = [...readings.temperature.values];
- }
- if (readings.humidity) {
- cable.relativeHumidity = [...readings.humidity.values];
- }
- if (readings.moisture) {
- cable.moisture = [...readings.moisture.values];
- }
-
- cable.lastRead = latestTimestamp(readings) ?? checkpointTime.toISOString();
- return cable;
-}
-
-/**
- * Clones a `pond.BinFan` template and overwrites its `state` from the reconciled boolean
- * reading. If no reading has been seen yet the template state is left as-is (carry-forward
- * will eventually set it once the first sample arrives).
- */
-function buildBinFanFromState(
- template: pond.BinFan,
- booleanState: BooleanStateAtCheckpoint
-): pond.BinFan {
- const fan = pond.BinFan.fromObject(cloneDeep(template));
- if (booleanState.state !== undefined) {
- fan.state = booleanState.state.values[0] !== 0;
- }
- return fan;
-}
-
-/**
- * Clones a `pond.BinHeater` template and overwrites its `state` from the reconciled boolean
- * reading. Same carry-forward semantics as `buildBinFanFromState`.
- */
-function buildBinHeaterFromState(
- template: pond.BinHeater,
- booleanState: BooleanStateAtCheckpoint
-): pond.BinHeater {
- const heater = pond.BinHeater.fromObject(cloneDeep(template));
- if (booleanState.state !== undefined) {
- heater.state = booleanState.state.values[0] !== 0;
- }
- return heater;
-}
-
-/**
- * End-to-end pipeline: evenly spaced times → bucket measurements → carry-forward merge → cables + fans + heaters.
- * Returns one `StatusCheckpoint` per frame with `time` (playback moment), `cables`
- * (full `grainCables` array), `fans`, and `heaters` as they would have appeared at that moment.
- */
-function buildStatusCheckpoints(
- cableTemplates: pond.GrainCable[],
- fanTemplates: pond.BinFan[],
- heaterTemplates: pond.BinHeater[],
- 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
-): StatusCheckpoint[] {
- const times = buildCheckpointTimes(start, end, count);
- console.log(times);
-
- // --- cables ---
- const cableKeys = cableTemplates.map(c => c.key);
- const cableBuckets = assignMeasurementsToCheckpoints(
- cableKeys,
- cableMeasurementResponses,
- start,
- end,
- count
- );
-
- // --- fans ---
- const fanKeys = fanTemplates.map(f => f.key);
- const fanBuckets = assignBooleanStatesToCheckpoints(
- fanKeys,
- fanMeasurementResponses,
- start,
- end,
- count
- );
-
- // --- heaters ---
- const heaterKeys = heaterTemplates.map(h => h.key);
- const heaterBuckets = assignBooleanStatesToCheckpoints(
- heaterKeys,
- heaterMeasurementResponses,
- start,
- end,
- count
- );
-
- // Carry-forward state per component
- const carriedByCable = new Map();
- cableKeys.forEach(key => carriedByCable.set(key, {}));
-
- const carriedByFan = new Map();
- fanKeys.forEach(key => carriedByFan.set(key, {}));
-
- const carriedByHeater = new Map();
- heaterKeys.forEach(key => carriedByHeater.set(key, {}));
-
- return cableBuckets.map((cableBucket, i) => {
- const time = times[i];
-
- // Build cables
- const cables = cableTemplates.map(template => {
- const key = template.key;
- 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;
- });
-
- // Build fans
- const fans = fanTemplates.map(template => {
- const key = template.key;
- const bucketState = fanBuckets[i].get(key) ?? {};
- const merged = mergeCarriedBooleanState(carriedByFan.get(key)!, bucketState);
- carriedByFan.set(key, merged);
- return buildBinFanFromState(template, merged);
- });
-
- // Build heaters
- const heaters = heaterTemplates.map(template => {
- const key = template.key;
- const bucketState = heaterBuckets[i].get(key) ?? {};
- const merged = mergeCarriedBooleanState(carriedByHeater.get(key)!, bucketState);
- carriedByHeater.set(key, merged);
- 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 };
- }).filter(checkpoint =>
- // Keep checkpoints that have at least some cable data
- checkpoint.cables.some(cable =>
- cable.celcius.length > 0 ||
- cable.relativeHumidity.length > 0 ||
- cable.moisture.length > 0
- )
- );
-}
-
-/**
- * Calculates the number of checkpoints to create based on the start and end dates.
- * Is hard coded to space the checkpoints 6 hours apart.
- * @param start - The start date
- * @param end - The end date
- * @returns The number of checkpoints to create
- */
-function checkpointCountFromRange(start: Moment, end: Moment): number {
- const hours = end.diff(start, 'hours');
- return Math.max(1, Math.floor(hours / 6) + 1);
-}
-
-/**
- * This component is used to reconcile measurements with an array of bin objects in order to see how the bin has progressed over a set period.
- * It gets the measurements for connected components, and uses those measurements to create a bin and simulate what its status would have been
- * at that time, creates an array of bins doing this and then uses a timer in a loop to change the bin in the status.
- * Fans and heaters are included so playback also reflects which equipment was running at each checkpoint.
- * @returns a JSX Element
- */
-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);
- const [dateRangeDialog, setDateRangeDialog] = useState(false);
- const [tempStartDate, setTempStartDate] = useState(moment().subtract(1, 'week'));
- const [tempEndDate, setTempEndDate] = useState(moment());
- const [currentCheckpointTime, setCurrentCheckpointTime] = useState(undefined);
- const [displayProgress, setDisplayProgress] = useState(0);
- const totalCheckpoints = useRef(0);
- const checkpointStartTime = useRef(0);
- const currentCheckpointIndex = useRef(0);
- const isPlaying = useRef(false);
- const PLAYBACK_INTERVAL = 1000;
- const [isPlayingState, setIsPlayingState] = useState(false);
- const stopPlayer = () => { isPlaying.current = false; };
- const originalBin = useRef(null);
-
- useEffect(() => {
- if (!isPlayingState) return;
-
- const fps = 30;
- const interval = 1000 / fps;
- const stepDuration = PLAYBACK_INTERVAL;
-
- const timer = setInterval(() => {
- if (!isPlayingState) {
- clearInterval(timer);
- setDisplayProgress(0);
- return;
- }
- const total = totalCheckpoints.current;
- if (total === 0) return;
-
- const idx = currentCheckpointIndex.current;
- const elapsed = Date.now() - checkpointStartTime.current;
- const stepProgress = Math.min(elapsed / stepDuration, 1);
- const raw = total <= 1 ? 100 : ((idx + stepProgress) / (total - 1)) * 100;
- setDisplayProgress(Math.min(raw, 100));
- }, interval);
-
- return () => clearInterval(timer);
- }, [isPlayingState]);
-
- /**
- * Fetches sampled unit measurements for every grain cable, fan, and heater in the current
- * bin status, then builds `statusCheckpoints` for playback over [startDate, endDate].
- */
- const startPlayer = async () => {
- originalBin.current = cloneDeep(bin);
- isPlaying.current = true;
- setIsPlayingState(true);
-
- // Cable requests
- const cableSampleRequests = bin.status.grainCables.map(cable => {
- const deviceID = componentDevices.get(cable.key) ?? 0;
- 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;
- return componentAPI.sampleUnitMeasurements(deviceID, fan.key, startDate, endDate, 100, undefined, undefined, undefined, undefined, true);
- });
-
- // Heater requests
- const heaterSampleRequests = bin.status.heaters.map(heater => {
- const deviceID = componentDevices.get(heater.key) ?? 0;
- 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([
- 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,
- bin.status.heaters,
- 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)
- );
-
- for (let i = 0; i < checkpoints.length; i++) {
- if (!isPlaying.current) {
- setBin(originalBin.current!);
- setCurrentCheckpointTime(undefined);
- setIsPlayingState(false);
- return;
- }
- currentCheckpointIndex.current = i;
- totalCheckpoints.current = checkpoints.length;
- checkpointStartTime.current = Date.now();
-
- const newBin = cloneDeep(originalBin.current!);
- 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);
- await new Promise(res => setTimeout(res, PLAYBACK_INTERVAL));
- }
-
- isPlaying.current = false;
- setIsPlayingState(false);
- setBin(originalBin.current!);
- setCurrentCheckpointTime(undefined);
- };
-
- const datePickerDialog = () => {
- return (
- setDateRangeDialog(false)}
- aria-labelledby="date-range-dialog">
-
- {
- setTempStartDate(moment(e.target.value));
- }}
- />
- {
- setTempEndDate(moment(e.target.value));
- }}
- />
-
-
- setDateRangeDialog(false)} color="primary">
- Close
-
- {
- const now = moment();
- setStartDate(tempStartDate.clone().set({
- hour: now.hour(),
- minute: now.minute(),
- second: now.second()
- }));
- setEndDate(tempEndDate.clone().set({
- hour: now.hour(),
- minute: now.minute(),
- second: now.second()
- }));
- setDateRangeDialog(false);
- }} color="primary">
- Submit
-
-
-
- );
- };
-
- const dateSelector = () => {
- return (
-
-
- {
- setDateSelect(event.target.value as number);
- let currentDate = moment();
- setEndDate(currentDate);
- switch (event.target.value) {
- case 0:
- setStartDate(currentDate.clone().subtract(1, 'week'));
- break;
- case 1:
- setStartDate(currentDate.clone().subtract(2, 'weeks'));
- break;
- case 2:
- setStartDate(currentDate.clone().subtract(4, 'weeks'));
- break;
- }
- }}
- >
- 1 Week
- 2 Weeks
- 4 Weeks
- Custom
-
-
- {dateSelect === 3 && (
- setDateRangeDialog(true)} color="primary">
- )}
-
- );
- };
-
- return (
-
- {datePickerDialog()}
-
- {/* start/stop button */}
-
- {isPlayingState ? (
-
- ) : (
-
- )}
-
- {/* progress bar and date display */}
-
- {currentCheckpointTime ? currentCheckpointTime.format("MMM D, YYYY h:mm A") : moment().format("MMM D, YYYY h:mm A")}
-
-
- {startDate.format("MMM D, YYYY")}
- {endDate.format("MMM D, YYYY")}
-
-
- {/* date range selector */}
-
- {dateSelector()}
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/bin/BinSVGV2.tsx b/src/bin/BinSVGV2.tsx
index 0a24d5c..ecbeb53 100644
--- a/src/bin/BinSVGV2.tsx
+++ b/src/bin/BinSVGV2.tsx
@@ -615,9 +615,7 @@ export default function BinSVGV2(props: Props) {
//determine how high to draw the node on the cable
const nodeY = nodeSpacingY * (index + 1) + minNodeY;
- //if we are using the auto top nodes only show the heatmap for nodes at or below the top node and it is not an excluded node
- //NOTE it is nodeNumber - 1 because the excluded node array uses a 0 based count for the node number but the nodeNumber variable uses a 1 based count
- //ie. the bottom node of the cable would be nodeNumber of 1 but in the array it would be 0
+ //if we are using the auto top nodes only show the heatmap for nodes at or below the top node and it is not an ecluded node
if (!cable.excludedNodes.includes(nodeNumber - 1)){
if (cable.topNode > 0) {
if (nodeNumber <= cable.topNode) {
diff --git a/src/bin/BinSelector.tsx b/src/bin/BinSelector.tsx
index 04387a0..3f330f7 100644
--- a/src/bin/BinSelector.tsx
+++ b/src/bin/BinSelector.tsx
@@ -4,7 +4,7 @@ import SearchSelect, { Option } from "common/SearchSelect";
import { useMobile } from "hooks";
import { pond } from "protobuf-ts/pond";
import React, { useEffect, useState } from "react";
-import { useGlobalState } from "providers";
+import { getDistanceUnit } from "utils";
interface Props {
optionsChanged: (binOptions: jsonBin[]) => void;
@@ -13,7 +13,6 @@ interface Props {
export default function BinSelector(props: Props) {
const { optionsChanged, vertical } = props;
- const [{user}] = useGlobalState();
const isMobile = useMobile();
const [manufacturerOptions, SetManufacturerOptions] = useState([]);
const [manufacturer, SetManufacturer] = useState (null);
@@ -127,7 +126,7 @@ export default function BinSelector(props: Props) {
}}
label={
"Minimum Diameter " +
- (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)")
+ (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)")
}
/>
@@ -144,7 +143,7 @@ export default function BinSelector(props: Props) {
}}
label={
"Maximum Diameter " +
- (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)")
+ (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)")
}
/>
diff --git a/src/bin/BinSensorCard.tsx b/src/bin/BinSensorCard.tsx
deleted file mode 100644
index f14d4a9..0000000
--- a/src/bin/BinSensorCard.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import { AccessTime, MoreVert } from "@mui/icons-material";
-import { Avatar, Box, Card, IconButton, Table, TableBody, TableCell, TableHead, TableRow, Typography, useTheme } from "@mui/material";
-import moment from "moment";
-import React from "react";
-
-interface SensorRow {
- label: string;
- data: string;
-}
-
-interface CableRow {
- label: string;
- min: string;
- avg: string;
- max: string;
-}
-
-interface Props {
- name: string;
- icon?: string;
- tag: string;
- lastReading: string;
- onMenuClick: (event: React.MouseEvent) => void;
- rows?: SensorRow[];
- cableRows?: CableRow[];
- /**
- * the time in hours that a reading is considered 'good'
- * defaults to 6
- */
- staleLimit?: number;
-}
-
-
-export default function BinSensorCard(props: Props) {
- const { name, icon, tag, lastReading, onMenuClick, rows, cableRows, staleLimit = 6 } = props
- const theme = useTheme()
- const isStale = moment().diff(moment(lastReading), "hours") > staleLimit
-
- return (
-
-
-
-
- {icon &&
-
- }
- {name}
- - {tag}
-
-
-
-
-
-
-
- {cableRows ? (
- <>
-
-
-
- Min
- Avg
- Max
-
-
-
- {cableRows.map((row) => (
-
- {row.label}
- {row.min}
- {row.avg}
- {row.max}
-
- ))}
-
- >
- ) : (
-
- {rows?.map((row) => (
-
- {row.label}
- {row.data}
-
- ))}
-
- )}
-
-
-
-
-
- {moment(lastReading).fromNow()}
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/src/bin/BinSensors.tsx b/src/bin/BinSensors.tsx
index 492091e..f1c81ac 100644
--- a/src/bin/BinSensors.tsx
+++ b/src/bin/BinSensors.tsx
@@ -30,9 +30,7 @@ interface Props {
coords?: { longitude: number; latitude: number };
binYards?: pond.BinYardSettings[];
components?: Map;
- componentDevices?: Map
setComponents?: React.Dispatch>>;
- setComponentDevices?: React.Dispatch>>;
updateBinStatus?: (componentKeys: string[], removed?: boolean) => void;
}
@@ -45,9 +43,7 @@ export default function BinSensors(props: Props) {
mode,
openedBinYard,
components,
- componentDevices,
setComponents,
- setComponentDevices,
updateBinStatus
} = props;
const [initialized, setInitialized] = useState(false);
@@ -97,9 +93,7 @@ export default function BinSensors(props: Props) {
{
if (mode === "remove") {
@@ -475,7 +475,7 @@ export default function BinSettings(props: Props) {
if (form.inventory) {
form.inventory.inventoryControl = inventoryControl
form.inventory.autoThreshold = autoFillThreshold
- form.inventory.lidarDropCm = user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? lidarDropDistance * 30.48 : lidarDropDistance
+ form.inventory.lidarDropCm = getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? lidarDropDistance * 30.48 : lidarDropDistance
if(form.inventory.grainType !== pond.Grain.GRAIN_CUSTOM){
form.inventory.customGrain = undefined
}else{
@@ -529,7 +529,7 @@ export default function BinSettings(props: Props) {
form.inventory.inventoryControl = inventoryControl
form.inventory.autoThreshold = autoFillThreshold
//if the users preferences are in feet convert the distance to cm otherwise it was entered as cm so use that
- form.inventory.lidarDropCm = user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? lidarDropDistance * 30.48 : lidarDropDistance
+ form.inventory.lidarDropCm = getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? lidarDropDistance * 30.48 : lidarDropDistance
if(form.inventory.grainType !== pond.Grain.GRAIN_CUSTOM){
form.inventory.customGrain = undefined
}else{
@@ -804,7 +804,7 @@ export default function BinSettings(props: Props) {
const binQuantity = formExtension.grainBushels;
//as long as the storage type is not fertilizer it is some sort of grain, whether it is supported or custom is not important in this instance so we do not need to check for unknown
- if(storageType !== pond.BinStorage.BIN_STORAGE_FERTILIZER && ((user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) && bushelConversion !== 0 && grainWeight !== "")){
+ if(storageType !== pond.BinStorage.BIN_STORAGE_FERTILIZER && ((getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) && bushelConversion !== 0 && grainWeight !== "")){
return (
{user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE ? "mT" : "t"}
+ endAdornment: {getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE ? "mT" : "t"}
}}
className={classes.bottomSpacing}
/>
@@ -1099,7 +1099,7 @@ export default function BinSettings(props: Props) {
InputProps={{
endAdornment: (
- {user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "ft" : "cm"}
+ {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "ft" : "cm"}
)
}}
@@ -1179,9 +1179,9 @@ export default function BinSettings(props: Props) {
setCustomGrain(newGrainSettings)
let conversion = 0
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
conversion = newGrainSettings.bushelsPerTonne
- }else if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
+ }else if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
conversion = newGrainSettings.bushelsPerTonne * 0.907
}
updateForm(
@@ -1218,9 +1218,9 @@ export default function BinSettings(props: Props) {
})
);
let conversion = 0
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
conversion = GrainDescriber(newGrainType).bushelsPerTonne
- }else if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
+ }else if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
conversion = GrainDescriber(newGrainType).bushelsPerTon
}
setBushelConversion(conversion)
@@ -1435,7 +1435,7 @@ export default function BinSettings(props: Props) {
let sH = sidewallHeight;
let hH = hopperHeight ?? 0;
let d = diameter;
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
tcH = tcH * 3.281;
sH = sH * 3.281;
hH = hH * 3.281;
@@ -1463,7 +1463,7 @@ export default function BinSettings(props: Props) {
let hopperHeight = formExtension.hopperHeight;
let diameter = formExtension.diameter;
- // if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
+ // if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
// diameterM = (Number(diameterM) * 3.28084).toFixed(2);
// heightM = (Number(heightM) * 3.28084).toFixed(2);
// }
@@ -1552,7 +1552,7 @@ export default function BinSettings(props: Props) {
let d = diameter;
let valueM = value;
let dM = diameter;
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
valueM = (Number(value) * 0.3048).toFixed(2);
dM = (Number(d) * 0.3048).toFixed(2);
}
@@ -1577,7 +1577,7 @@ export default function BinSettings(props: Props) {
InputProps={{
endAdornment: (
- {user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET ? "m" : "ft"}
+ {getDistanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET ? "m" : "ft"}
)
}}
@@ -1637,7 +1637,7 @@ export default function BinSettings(props: Props) {
let value = event?.target.value;
let valueM = value;
setModelID(0);
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
valueM = (Number(value) * 0.3048).toFixed(2);
}
let coneCM =
@@ -1674,7 +1674,7 @@ export default function BinSettings(props: Props) {
//need to get the total height in cm
let totalHeightCM = s + t + h; //the total height in the users units
- if(user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){
+ if(getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){
totalHeightCM = totalHeightCM * 30.48 //convert from feet to cm
}else{
totalHeightCM = totalHeightCM * 100 //convert from m to cm
@@ -1698,7 +1698,7 @@ export default function BinSettings(props: Props) {
InputProps={{
endAdornment: (
- {user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET
+ {getDistanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET
? "m"
: "ft"}
@@ -1725,7 +1725,7 @@ export default function BinSettings(props: Props) {
//calculate the new cone height for the roof angle
let coneCM = TriangleOppositeLength(d / 2, angle ?? 0);
let newConeHeight =
- user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET
+ getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET
? (coneCM / 30.48).toFixed(2)
: (coneCM / 100).toFixed(2);
@@ -1749,7 +1749,7 @@ export default function BinSettings(props: Props) {
//need to get the total height in cm
let totalHeightCM = s + t + h; //the total height in the users units
- if(user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){
+ if(getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){
totalHeightCM = totalHeightCM * 30.48 //convert from feet to cm
}else{
totalHeightCM = totalHeightCM * 100 //convert from m to cm
@@ -1786,7 +1786,7 @@ export default function BinSettings(props: Props) {
onChange={event => {
let value = event?.target.value;
let valueM = value;
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
valueM = (Number(value) * 0.3048).toFixed(2);
}
let ext = cloneDeep(formExtension);
@@ -1811,7 +1811,7 @@ export default function BinSettings(props: Props) {
//need to get the total height in cm
let totalHeightCM = s + t + h; //the total height in the users units
- if(user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){
+ if(getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){
totalHeightCM = totalHeightCM * 30.48 //convert from feet to cm
}else{
totalHeightCM = totalHeightCM * 100 //convert from m to cm
@@ -1838,7 +1838,7 @@ export default function BinSettings(props: Props) {
InputProps={{
endAdornment: (
- {user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET
+ {getDistanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET
? "m"
: "ft"}
@@ -1868,7 +1868,7 @@ export default function BinSettings(props: Props) {
let coneCM = TriangleOppositeLength(d / 2, angle ?? 0);
let newHopperHeight =
- user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET
+ getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET
? (coneCM / 30.48).toFixed(2)
: (coneCM / 100).toFixed(2);
@@ -1926,7 +1926,7 @@ export default function BinSettings(props: Props) {
onChange={event => {
let value = event?.target.value;
let valueM = value;
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
valueM = (Number(value) * 0.3048).toFixed(2);
}
@@ -1982,7 +1982,7 @@ export default function BinSettings(props: Props) {
InputProps={{
endAdornment: (
- {user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET
+ {getDistanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET
? "m"
: "ft"}
@@ -2009,7 +2009,7 @@ export default function BinSettings(props: Props) {
let h = height;
let valueM = value;
let hM = height;
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
valueM = (Number(value) * 0.3048).toFixed(2);
hM = (Number(h) * 0.3048).toFixed(2);
}
@@ -2063,7 +2063,7 @@ export default function BinSettings(props: Props) {
InputProps={{
endAdornment: (
- {user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET ? "m" : "ft"}
+ {getDistanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET ? "m" : "ft"}
)
}}
@@ -2253,7 +2253,7 @@ export default function BinSettings(props: Props) {
let userHopperHeight = hopperHeight;
let userDiameter = jsonBin.Diameter;
//since the jsone data is in feet just check if we need to make it in meters
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
userHeight = userHeight / 3.281;
userDiameter = userDiameter / 3.281;
userTopCone = userTopCone / 3.281;
@@ -2308,7 +2308,7 @@ export default function BinSettings(props: Props) {
onChange={event => {
let value = event?.target.value;
let valueC = value;
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
valueC = ((Number(value) - 32) * (5 / 9)).toFixed(2);
}
@@ -2320,7 +2320,7 @@ export default function BinSettings(props: Props) {
InputProps={{
endAdornment: (
- {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
+ {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "F"
: "C"}
@@ -2348,7 +2348,7 @@ export default function BinSettings(props: Props) {
onChange={event => {
let value = event?.target.value;
let valueC = value;
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
valueC = ((Number(value) - 32) * (5 / 9)).toFixed(2);
}
setLowTempC(+valueC);
@@ -2357,7 +2357,7 @@ export default function BinSettings(props: Props) {
InputProps={{
endAdornment: (
- {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
+ {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "F"
: "C"}
@@ -2383,7 +2383,7 @@ export default function BinSettings(props: Props) {
onChange={event => {
let value = event?.target.value;
let valueC = value;
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
valueC = ((Number(value) - 32) * (5 / 9)).toFixed(2);
}
setHighTempC(+valueC);
@@ -2392,7 +2392,7 @@ export default function BinSettings(props: Props) {
InputProps={{
endAdornment: (
- {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
+ {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "F"
: "C"}
diff --git a/src/bin/BinStorageConditions.tsx b/src/bin/BinStorageConditions.tsx
index b0e7e8a..fa0e957 100644
--- a/src/bin/BinStorageConditions.tsx
+++ b/src/bin/BinStorageConditions.tsx
@@ -24,7 +24,7 @@ import Co2Icon from "products/CommonIcons/co2Icon";
import { pond, quack } from "protobuf-ts/pond";
import { useBinAPI, useGlobalState, useSnackbar } from "providers";
import React, { useEffect, useState } from "react";
-import { avg } from "utils";
+import { avg, getTemperatureUnit } from "utils";
import { makeStyles } from "@mui/styles";
import { Mark } from "@mui/material/Slider/useSlider.types";
import { CO2 } from "models/CO2";
@@ -134,7 +134,7 @@ export default function BinStorageConditions(props: Props) {
const binAPI = useBinAPI();
const { openSnack } = useSnackbar();
const { bin, headspaceCO2, cables } = props;
- const [{as, user}] = useGlobalState()
+ const [{as}] = useGlobalState()
const [sliderTemps, setSliderTemps] = useState([-40, 40]);
//boolean that if a cable is missing its filled to display an icon or something to let the user know not all of the cables have their fill set
const [missingTopNodeWarning, setMissingTopNodeWarning] = useState(false);
@@ -233,7 +233,7 @@ export default function BinStorageConditions(props: Props) {
}
setTempTargets(tempCounts);
let tempVal = bin.settings.inventory?.targetTemperature ?? 0;
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
tempVal = tempVal * 1.8 + 32;
}
setTargetTemp(tempVal.toFixed(1));
@@ -244,7 +244,7 @@ export default function BinStorageConditions(props: Props) {
setEmcTargets(emcCounts);
setTargetEMC(bin.settings.inventory?.targetMoisture.toFixed(1) ?? "");
setEmcDeviation(bin.settings.inventory?.moistureTargetDeviation.toFixed(1) ?? "");
- }, [bin, cables, user]);
+ }, [bin, cables]);
//useEffect that watches for changes in the target emc and deviation to update the targets
useEffect(() => {
@@ -301,7 +301,7 @@ export default function BinStorageConditions(props: Props) {
settings.highTemp = sliderTemps[1];
if (settings.inventory) {
let tempVal = parseFloat(targetTemp);
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
tempVal = Math.fround(((tempVal - 32) * 5) / 9);
}
settings.inventory.targetTemperature = tempVal;
@@ -376,14 +376,14 @@ export default function BinStorageConditions(props: Props) {
let mark: Mark[] = [];
if (averageTemp) {
let temp = averageTemp;
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
temp = temp * 1.8 + 32;
}
mark = [
{
label: customMark(
temp.toFixed(1) +
- (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
+ (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F"
: "°C"),
"AVG"
@@ -422,7 +422,7 @@ export default function BinStorageConditions(props: Props) {
InputProps={{
endAdornment: (
- {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
+ {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F"
: "°C"}
@@ -451,7 +451,7 @@ export default function BinStorageConditions(props: Props) {
max={sliderEdge}
valueLabelDisplay="on"
valueLabelFormat={value => {
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
return ((value * 9) / 5 + 32).toFixed(1) + "°F";
}
return value.toFixed(1) + "°C";
diff --git a/src/bin/BinTableExpanded.tsx b/src/bin/BinTableExpanded.tsx
deleted file mode 100644
index afc16a8..0000000
--- a/src/bin/BinTableExpanded.tsx
+++ /dev/null
@@ -1,194 +0,0 @@
-import { Box, Table, TableBody, TableCell, TableHead, TableRow, Theme, Typography, useTheme } from "@mui/material";
-import { pond } from "protobuf-ts/pond";
-import { makeStyles } from "@mui/styles";
-import { useEffect, useMemo, useState } from "react";
-import { avg, celsiusToFahrenheit } from "utils";
-import { useGlobalState } from "providers";
-import { darken } from "@mui/material";
-import React from "react";
-
-const useStyles = makeStyles((theme: Theme) => ({
- tableStyle: {
- borderCollapse: "separate",
- borderSpacing: 0,
- },
- headerCellStyle: {
- fontWeight: 650,
- backgroundColor: darken(theme.palette.background.paper, 0.3),
- fontSize: 15,
- textAlign: "center",
- borderBottom: "none",
- padding: 10,
- },
- defaultCellStyle: {
- padding: 2,
- fontWeight: 650,
- fontSize: 17,
- textAlign: "center",
- backgroundColor: darken(theme.palette.background.paper, 0.1),
- borderTop: "1px solid " + darken(theme.palette.background.paper, 0.4),
- borderBottom: "none",
- },
- colouredCellStyle: {
- padding: 2,
- fontWeight: 650,
- fontSize: 17,
- textAlign: "center",
- borderTop: "1px solid " + darken(theme.palette.background.paper, 0.4),
- borderBottom: "none",
- },
-}));
-
-// interface Column {
-// label: string // e.g. "Cable 1 Temp", "Cable 2 Moisture"
-// cableIndex: number
-// type: "temp" | "moisture"
-// }
-
-interface Props {
- cables: pond.GrainCable[]
- targetTemp: number // in Celsius, for colouring
- targetMoisture: number // for colouring
- inBounds?: string
- caution?: string
- warning?: string
-
-}
-
-export default function BinTableExpanded(props: Props) {
- const { cables, targetTemp, targetMoisture, inBounds = "#43a047", caution = "#f9a825", warning = "#c62828" } = props
- const classes = useStyles()
- const [{ user }] = useGlobalState()
- const theme = useTheme()
-
- const useFahrenheit = user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
-
- // Row count driven by the longest cable
- const rowCount = useMemo(
- () => Math.max(0, ...cables.map(c => c.celcius.length)),
- [cables]
- )
-
- // Render a single cell value + background colour
- const renderCell = (cable: pond.GrainCable, nodeIndex: number, type: "temp" | "moisture") => {
- if (type === "temp") {
- const raw = cable.celcius[nodeIndex]
- if (raw === undefined) return { display: "--", color: "" }
-
- const isExcluded = cable.excludedNodes.includes(nodeIndex)
- if (isExcluded) return { display: "--", color: "" }
-
- let display: string
- let color = ""
- const inGrain = nodeIndex < cable.topNode
-
- if (inGrain) {
- if (raw > targetTemp + 10) color = warning
- else if (raw > targetTemp + 5) color = caution
- else color = inBounds
- }
-
- const converted = useFahrenheit ? celsiusToFahrenheit(raw) : raw
- display = converted.toFixed(1) + (useFahrenheit ? " °F" : " °C")
- return { display, color }
- }
-
- // moisture
- const raw = cable.moisture[nodeIndex]
- if (raw === undefined || raw === 0) return { display: "--", color: "" }
-
- const isExcluded = cable.excludedNodes.includes(nodeIndex)
- if (isExcluded) return { display: "--", color: "" }
-
- let color = ""
- const inGrain = nodeIndex < cable.topNode
- if (inGrain) {
- if (raw > targetMoisture + 1.5) color = warning
- else if (raw > targetMoisture + 0.5) color = caution
- else color = inBounds
- }
- return { display: raw.toFixed(2) + "%", color }
- }
-
- const cableTable = (cable: pond.GrainCable) => {
- return (
-
-
-
-
- Level
-
-
- Temperature
-
-
- Moisture
-
-
-
-
- {Array.from({ length: rowCount }, (_, i) => rowCount - 1 - i).map((nodeIndex, rowI) => {
- const isLast = rowI === rowCount - 1
- const tempData = renderCell(cable, nodeIndex, "temp")
- const moistureData = renderCell(cable, nodeIndex, "moisture")
- return (
-
-
- {nodeIndex + 1}
-
-
- {tempData.display}
-
-
- {moistureData.display}
-
-
- )
- })}
-
-
- )
- }
-
- return (
-
- {/* Table Legend */}
-
-
-
- On target
-
-
-
- {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "~10 °F" : "+5 °C"} above
-
-
-
- {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "~20 °F" : "+10 °C"} above
-
-
-
- {cables.map((cable, i) => (
-
-
- {cable.name}
-
- {cableTable(cable)}
-
- ))}
-
-
- )
-}
\ No newline at end of file
diff --git a/src/bin/BinTransactions.tsx b/src/bin/BinTransactions.tsx
index 925201c..4c9d598 100644
--- a/src/bin/BinTransactions.tsx
+++ b/src/bin/BinTransactions.tsx
@@ -10,10 +10,11 @@ import { Transaction } from "models/Transaction";
import moment from "moment";
import ObjectDescriber from "objects/ObjectDescriber";
import { pond } from "protobuf-ts/pond";
-import { useBinAPI, useContractAPI, useFieldAPI, useGlobalState, useGrainBagAPI, useSnackbar, useTransactionAPI } from "providers";
+import { useBinAPI, useContractAPI, useFieldAPI, useGrainBagAPI, useSnackbar, useTransactionAPI } from "providers";
import React from "react";
import { useCallback, useEffect, useState } from "react";
import TransactionDataDisplay from "transactions/transactionDataDisplay";
+import { getGrainUnit } from "utils";
interface Props {
bin: Bin
@@ -25,7 +26,6 @@ export default function BinTransactions(props: Props){
const { bin, permissions, refresh } = props
const [state, setState] = useState(pond.TransactionState.TRANSACTION_STATE_APPROVAL_REQUIRED)
const transactionAPI = useTransactionAPI()
- const [{user}] = useGlobalState()
const binAPI = useBinAPI()
const grainBagAPI = useGrainBagAPI()
const fieldAPI = useFieldAPI()
@@ -103,7 +103,7 @@ export default function BinTransactions(props: Props){
const loadContracts = useCallback(()=>{
contractAPI.listContracts(0,0, "asc", "name").then(resp => {
setContractOptions(resp.data.contracts.map(c => {
- let contract = Contract.create(c, user)
+ let contract = Contract.create(c)
return {label: contract.name(), value: contract.key(), group: contract.grainName()}
}))
})
@@ -198,11 +198,11 @@ export default function BinTransactions(props: Props){
const grainQuantityDisplay = (bushels: number) => {
const grainTransaction = selectedTransaction?.transaction.transaction?.grainTransaction ?? pond.GrainTransaction.create()
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainTransaction.bushelsPerTonne > 1){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainTransaction.bushelsPerTonne > 1){
let tonneWeight = bushels / grainTransaction.bushelsPerTonne
return tonneWeight + " mT"
}
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainTransaction.bushelsPerTonne > 1){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainTransaction.bushelsPerTonne > 1){
let tonneWeight = bushels / (grainTransaction.bushelsPerTonne * 0.907)
return tonneWeight + " t"
}
diff --git a/src/bin/BinVisualizerV2.tsx b/src/bin/BinVisualizerV2.tsx
index 8eee39e..2da6f78 100644
--- a/src/bin/BinVisualizerV2.tsx
+++ b/src/bin/BinVisualizerV2.tsx
@@ -36,10 +36,11 @@ import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import { useGlobalState, useSnackbar } from "providers";
import React, { useCallback, useEffect, useState } from "react";
+import { FullScreen, useFullScreenHandle } from "react-full-screen";
import moment, { Moment } from "moment";
import ResponsiveDialog from "common/ResponsiveDialog";
import { getThemeType } from "theme";
-import { or } from "utils";
+import { getGrainUnit, getTemperatureUnit, or } from "utils";
import { useBinAPI } from "providers/pond/binAPI";
import BindaptIcon from "products/Bindapt/BindaptIcon";
import {
@@ -71,7 +72,6 @@ import ButtonGroup from "common/ButtonGroup";
import ModeChangeDialog from "./conditioning/modeChangeDialog";
import CustomGrainSelector from "grain/CustomGrainSelector";
import BinControllerDisplay from "./BinControllerDisplay";
-import { useFullScreen } from "hooks/FullScreenHandle";
const useStyles = makeStyles((theme: Theme) => {
return ({
@@ -217,7 +217,7 @@ export default function BinVisualizer(props: Props) {
const isMobile = useMobile();
const classes = useStyles();
const theme = useTheme();
- const {fullScreenHandler, FullScreenWrapper} = useFullScreen()
+ const fullScreenHandler = useFullScreenHandle();
const viewport = useViewport();
const { openSnack } = useSnackbar();
const [fillPercentage, setFillPercentage] = useState(0);
@@ -498,9 +498,9 @@ export default function BinVisualizer(props: Props) {
if (bin.storage() === pond.BinStorage.BIN_STORAGE_FERTILIZER) {
return Math.round(val * 35.239 * 100) / 100;
} else {
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) {
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) {
return Math.round((val / bin.bushelsPerTonne()) * 100) / 100;
- } else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) {
+ } else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) {
return Math.round((val / (bin.bushelsPerTonne()*0.907)) * 100) / 100;
}
}
@@ -516,9 +516,9 @@ export default function BinVisualizer(props: Props) {
if (bin.storage() === pond.BinStorage.BIN_STORAGE_FERTILIZER) {
return " / " + capacity.toLocaleString() + " L";
}
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) {
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) {
return "mT (" + bin.fillPercent() + "%)";
- } else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) {
+ } else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) {
return "t (" + bin.fillPercent() + "%)";
} else {
return " / " + capacity.toLocaleString() + " bu";
@@ -605,7 +605,7 @@ export default function BinVisualizer(props: Props) {
break;
}
if (valC) {
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
temp = CtoF(valC).toFixed(1) + "°F";
} else {
temp = valC.toFixed(1) + "°C";
@@ -705,7 +705,7 @@ export default function BinVisualizer(props: Props) {
break;
}
if (valC) {
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
temp = Math.abs(valC * 1.8).toFixed(1) + "°F"; //since this is a measurement of change a change in 1 degrre celsius is equivalent to 1.8 fahrenheit
} else {
temp = Math.abs(valC).toFixed(1) + "°C";
@@ -1159,7 +1159,7 @@ export default function BinVisualizer(props: Props) {
- No Plenums found
+ View Smart Bin Devices
)}
@@ -1328,14 +1328,14 @@ export default function BinVisualizer(props: Props) {
}
}
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) {
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) {
diffDisplay = diffDisplay / bin.bushelsPerTonne();
if (pendingDisplay) {
pendingDisplay = pendingDisplay / bin.bushelsPerTonne();
}
}
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) {
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) {
diffDisplay = diffDisplay / (bin.bushelsPerTonne()*0.907);
if (pendingDisplay) {
pendingDisplay = pendingDisplay / (bin.bushelsPerTonne()*0.907);
@@ -1344,7 +1344,7 @@ export default function BinVisualizer(props: Props) {
return (
-
+
-
+
);
};
@@ -1539,7 +1539,7 @@ export default function BinVisualizer(props: Props) {
) : (
- fullScreenHandler.enter()}>
+
)}
@@ -1676,7 +1676,7 @@ export default function BinVisualizer(props: Props) {
fontWeight: 650,
color: tempColour
}}>
- {ambient?.getTempString(user.tempUnit())}
+ {ambient?.getTempString(getTemperatureUnit())}
diff --git a/src/bin/BinyardDisplay.tsx b/src/bin/BinyardDisplay.tsx
index 905ab8c..b639759 100644
--- a/src/bin/BinyardDisplay.tsx
+++ b/src/bin/BinyardDisplay.tsx
@@ -35,7 +35,7 @@ import { Bin } from "models";
import { pond } from "protobuf-ts/pond";
import { useBinAPI, useGlobalState } from "providers";
import React, { useEffect, useState, useCallback, SetStateAction } from "react";
-import { stringToMaterialColour } from "utils";
+import { getGrainUnit, stringToMaterialColour } from "utils";
//import BinsFansStatusTable from "./BinFansStatusTable";
import BinsList from "./BinsList";
@@ -95,7 +95,7 @@ export default function BinyardDisplay(props: Props) {
const isMobile = useMobile();
const [carouselIndex, setCarouselIndex] = useState(0);
const [binMenuAnchorEl, setBinMenuAnchorEl] = useState(null);
- const [{ as, user }] = useGlobalState();
+ const [{ as }] = useGlobalState();
const maxBins = 40;
const [binsLoading, setBinsLoading] = useState(false);
const [expandTotal, setExpandTotal] = useState(false);
@@ -658,7 +658,7 @@ export default function BinyardDisplay(props: Props) {
return " bu";
}
- switch (user.grainUnit()) {
+ switch (getGrainUnit()) {
case pond.GrainUnit.GRAIN_UNIT_TONNE:
return " mT";
case pond.GrainUnit.GRAIN_UNIT_TON:
@@ -671,9 +671,9 @@ export default function BinyardDisplay(props: Props) {
const customQuantityDisplay = (customInventory: pond.CustomInventory, bushels: number) => {
let amount = bushels
if(customInventory.bushelsPerTonne > 1){
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
amount = bushels/customInventory.bushelsPerTonne
- }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
+ }else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
amount = bushels/(customInventory.bushelsPerTonne*0.907)
}
}
@@ -683,9 +683,9 @@ export default function BinyardDisplay(props: Props) {
const supportedGrainDisplay = (grain: pond.Grain, bushels: number) => {
const describer = GrainDescriber(grain)
let amount = bushels
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
amount = bushels/describer.bushelsPerTonne
- }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
+ }else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
amount = bushels/describer.bushelsPerTon
}
return Math.round(amount*100)/100
diff --git a/src/bin/GrainNodeInteractions.tsx b/src/bin/GrainNodeInteractions.tsx
index cc341dd..e34cfd1 100644
--- a/src/bin/GrainNodeInteractions.tsx
+++ b/src/bin/GrainNodeInteractions.tsx
@@ -87,24 +87,9 @@ export default function GrainNodeInteractions(props: Props) {
const [nodeHum, setNodeHum] = useState();
const [nodeMoist, setNodeMoist] = useState();
const [{ user, as }] = useGlobalState();
- const tempDescriber = describeMeasurement(
- quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
- quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE,
- undefined,
- undefined,
- user);
- const humDescriber = describeMeasurement(
- quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
- quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE,
- undefined,
- undefined,
- user);
- const moistureDescriber = describeMeasurement(
- quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC,
- quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE,
- undefined,
- undefined,
- user);
+ const tempDescriber = describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE);
+ const humDescriber = describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_PERCENT);
+ const moistureDescriber = describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC);
const [topNode, setTopNode] = useState(false);
const [excluded, setExcluded] = useState(false);
const componentAPI = useComponentAPI();
diff --git a/src/bin/bin3dVisualizer.tsx b/src/bin/bin3dVisualizer.tsx
deleted file mode 100644
index 9bdfd0c..0000000
--- a/src/bin/bin3dVisualizer.tsx
+++ /dev/null
@@ -1,423 +0,0 @@
-import { Box, Checkbox, FormControl, MenuItem, List, ListItem, InputLabel, Select, Typography, Stack, FormControlLabel, useTheme } from "@mui/material";
-import Bin3dView from "bin/3dView/Scene/Bin3dView";
-import { Bin, Component, Device } from "models";
-import { Controller } from "models/Controller";
-import { pond, quack } from "protobuf-ts/pond";
-import React, { useEffect } from "react";
-import { useState } from "react";
-import NodeControls from "./binSummary/components/nodeControls";
-import { NodeData } from "bin/3dView/Data/BuildNodeData";
-import { CableData } from "bin/3dView/Data/BuildCableData";
-import ModeChangeDialog from "bin/conditioning/modeChangeDialog";
-import { cloneDeep } from "lodash";
-import { useMobile } from "hooks";
-
-interface Props {
- bin: Bin
- // cables?: GrainCable[]
- devices: Device[]
- // fans?: Controller[]
- // heaters?: Controller[]
- permissions: pond.Permission[]
- componentDevices: Map
- componentMap: Map
- binPrefs?: Map
- updateBinCallback?: (bin: Bin) => void
-}
-export default function bin3dVisualizer(props: Props){
- const {bin, permissions, devices, componentDevices, componentMap, binPrefs, updateBinCallback} = props
- const theme = useTheme()
- const [showHeatmap, setShowHeatmap] = useState(true)
- // const [showHotspots, setShowHotspots] = useState(false)
- // const [showFill, setShowFill] = useState(false)
- const [showTemp, setShowTemp] = useState(true)
- const [showMoisture, setShowMoisture] = useState(false)
- const [showMoistureHeatmap, setShowMoistureHeatmap] = useState(false)
- const [binDisplay, setBinDisplay] = useState("temp")
- const [binMode, setBinMode] = useState(pond.BinMode.BIN_MODE_NONE)
- const [openModeChange, setOpenModeChange] = useState(false)
- const [selectedCable, setSelectedCable] = useState(undefined)
- const [selectedNode, setSelectedNode] = useState(undefined)
- const [selectedCableDevice, setSelectedCableDevice] = useState(Device.create()) //this is the device that the cable that has the node that was clicked on belongs to
- const [filteredComponents, setFilteredComponents] = useState([]) //components that are filtered to only include ones on the same device
- const [openNodeControls, setOpenNodeControls] = useState(false)
- const [devMap, setDevMap] = useState>(new Map())
- const [modeChangeInProgress, setModeChangeInProgress] = useState(false)
- const [showLabels, setShowLabels] = useState(true)
- const isMobile = useMobile()
-
- useEffect(()=>{
- let newMap: Map = new Map()
- devices.forEach(d => {
- newMap.set(d.id(), d)
- })
- setDevMap(newMap)
- },[devices])
-
- useEffect(()=>{
- setBinMode(bin.settings.mode)
- },[bin])
-
- const updateBin = () => {
- if(updateBinCallback){
- let clone = cloneDeep(bin)
- clone.settings.mode = binMode
- updateBinCallback(clone)
- }
- };
-
- const binModeControl = () => {
- return (
-
- {bin.name()}
-
- {
- setBinMode(event.target.value as pond.BinMode)
- setOpenModeChange(true)
- }}
- >
- Select Mode..
- Storage
- Drying
- Hydrating
- Cooldown
-
-
-
- )
- }
-
- const heatmapDisplay = () => {
- return (
-
-
- Heatmap
-
-
- {
- let selection = event.target.value
- setBinDisplay(selection)
- if (selection === "temp") {
- setShowHeatmap(true)
- setShowTemp(true)
- setShowMoisture(false)
- setShowMoistureHeatmap(false)
- }
- if (selection === "moisture") {
- setShowHeatmap(false)
- setShowTemp(false)
- setShowMoisture(true)
- setShowMoistureHeatmap(true)
- }
- }}
- >
- Temperature
- Moisture
-
-
- {/* Checkbox moved below dropdown — no longer affects top alignment */}
- setShowLabels(checked)}
- />
- }
- label={Show Values }
- sx={{ mt: 0.5, ml: '-9px' }}
- />
-
- )
- }
-
- // const controllerState = (controller: Controller) => {
- // let state = false
- // controller.status.lastGoodMeasurement.forEach(um => {
- // if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN){
- // if(um.values.length > 0){
- // let readings = um.values
- // if(readings[readings.length-1].values.length > 0){
- // let nodes = readings[readings.length-1].values
- // if (nodes[nodes.length -1] === 1){
- // state = true
- // }
- // }
- // }
- // }
- // })
- // return state
- // }
-
- const controllerDisplay = () => {
- if ((bin.status.fans && bin.status.fans.length > 0) || (bin.status.heaters && bin.status.heaters.length > 0)){
- return (
-
-
- Controllers
-
- {bin.status.fans && bin.status.fans.map(fan => {
- // let isOn = controllerState(fan)
-
- return (
-
- {fan.name}
-
-
- {fan.state ? "ON" : "OFF"}
-
-
-
-
- )})}
- {bin.status.heaters && bin.status.heaters.map(heater => {
- // let isOn = controllerState(heater)
- return (
-
- {heater.name}
-
-
- {heater.state ? "ON" : "OFF"}
-
-
-
-
- )})}
-
- // loop through controllers displaying each one
-
- )
- }else{
- return
- }
- }
-
- const desktopHUD = () => {
- return (
-
-
- {binModeControl()}
- {heatmapDisplay()}
- {controllerDisplay()}
-
-
- )
- }
-
- const mobileHUD = () => {
- return (
-
- {/* Top-left: Bin Name / Mode */}
-
-
- {binModeControl()}
-
-
-
- {/* Top-right: Heatmap Display */}
-
-
- {heatmapDisplay()}
-
-
-
- {/* Bottom-left: Controller Status */}
-
-
- {controllerDisplay()}
-
-
-
- )
- }
-
- return (
-
- {(selectedCable && selectedNode) &&
- {
- setOpenNodeControls(false)
- }}
- />
- }
- {
- setOpenModeChange(false)
- if(refresh) {
- updateBin()
- }else{
- setBinMode(bin.settings.mode)
- };
- }}
- defaultTargetMoisture={bin.settings.inventory?.initialMoisture}
- // defaultOutdoorTemp={outdoorTemp}
- // defaultOutdoorHumidity={outdoorHumidity}
- // changeTargetMoisture={newMoisture => {
- // setTargetMoisture(newMoisture)
- // }}
- // changeOutdoorTemp={newTemp => {
- // setOutdoorTemp(newTemp)
- // }}
- // changeOutdoorHumidity={newHumidity => {
- // setOutdoorHumidity(newHumidity)
- // }}
- startChange={() => {
- setModeChangeInProgress(true)
- }}
- changeComplete={() => {
- setModeChangeInProgress(false)
- }}
-
- />
-
- {
- //this will be how to control the dialog to update the top nodes and node exclusion etc (make new component called NodeControls for this)
- //use the cable data to get the device it belongs to
- let d = devMap.get(cable.grainCable.device)
- if(d !== undefined){
- setSelectedNode(node)
- setSelectedCable(cable)
- setSelectedCableDevice(d)
- //filter the controls so that only components on THIS device are options for new interactions, a side note of this using the components that are in the bins status
- //is that it will indirectly also filter by bin as well so only components that are both on the bin and the same device as the cable will appear
- let filtered: Component[] = []
- if(bin.status.fans){
- bin.status.fans.forEach((f) => {
- if(componentDevices.get(f.key) === d.id()){
- let comp = componentMap.get(f.key)
- if(comp){
- filtered.push(comp)
- }
- }
- })
- }
- if(bin.status.heaters){
- bin.status.heaters.forEach((h) => {
- if(componentDevices.get(h.key) === d.id()){
- let comp = componentMap.get(h.key)
- if(comp){
- filtered.push(comp)
- }
- }
- })
- }
- //also make sure to push the cable into the list of filtered components for the source
- let c = componentMap.get(cable.grainCable.key)
- if(c !== undefined){
- filtered.push(c)
- }
- setFilteredComponents(filtered)
- setOpenNodeControls(true)
- }
- }}
- scale={100}
- showTempHeatmap={showHeatmap}
- showMoistureHeatmap={showMoistureHeatmap}
- showTemp={showTemp && showLabels}
- showMoisture={showMoisture && showLabels}
- xOffset={isMobile ? undefined : -120}
- />
- {isMobile ? mobileHUD() : desktopHUD()}
-
-
- )
-}
\ No newline at end of file
diff --git a/src/bin/binSensorsDisplay.tsx b/src/bin/binSensorsDisplay.tsx
deleted file mode 100644
index 9785b5e..0000000
--- a/src/bin/binSensorsDisplay.tsx
+++ /dev/null
@@ -1,1032 +0,0 @@
-import { AccessTime, MoreVert } from "@mui/icons-material";
-import { Avatar, Box, Button, Card, Checkbox, FormControlLabel, Grid2, IconButton, Menu, MenuItem, Typography, useTheme } from "@mui/material";
-import BinSensorCard from "bin/BinSensorCard";
-import { GetDefaultDateRange } from "common/time/DateRange";
-import { ExtractMoisture } from "grain";
-import { useMobile, useThemeType } from "hooks";
-import { Bin, Component } from "models";
-import { Ambient } from "models/Ambient";
-import { CO2 } from "models/CO2";
-import { Controller } from "models/Controller";
-import { GrainCable } from "models/GrainCable";
-import { Headspace } from "models/Headspace";
-import { Plenum } from "models/Plenum";
-import { Pressure } from "models/Pressure";
-import moment, { Moment } from "moment";
-import { GetComponentIcon } from "pbHelpers/ComponentType";
-import { pond } from "protobuf-ts/pond";
-import { quack } from "protobuf-ts/quack";
-import { useBinAPI, useComponentAPI, useGlobalState, useSnackbar } from "providers";
-import React, { useEffect, useState } from "react";
-import { avg } from "utils";
-import BinSensorGraph from "./graphs/BinSensorGraph";
-import TimeBar from "common/time/TimeBar";
-
-interface Props {
- bin: Bin
- components: Map;
- componentDevices: Map;
- preferences?: Map;
- setPreferences: React.Dispatch<
- React.SetStateAction | undefined>
- >;
-
-}
-
-export default function BinSensorsDisplay(props: Props){
- const {bin, components, componentDevices, preferences, setPreferences} = props
- const themeType = useThemeType()
- const [{user, as}] = useGlobalState()
- const binAPI = useBinAPI()
- const componentAPI = useComponentAPI()
- const theme = useTheme()
- const snackbar = useSnackbar()
- const [cables, setCables] = useState([])
- const [plenums, setPlenums] = useState([])
- const [ambient, setAmbient] = useState([])
- const [pressures, setPressures] = useState([])
- const [fans, setFans] = useState([])
- const [heaters, setHeaters] = useState([])
- //the above component preferences all have only components that are one type of sensor, however headspace has components of three different sensors T/H, CO2, and Lidar
- const [headspaceTH, setHeadspaceTH] = useState([]) //the components in the headspace that measure temp/humidity
- const [headspaceCO2, setHeadspaceCO2] = useState([]) //the CO2 components in the headspace
- const [headspaceLidar, setHeadspaceLidar] = useState([]) //the lidar components in the headspace
-
- const [unassignedComponents, setUnassignedComponents] = useState([])
-
- //using counts so i dont have to have to check multiple arrays for sensors and controllers
- const [sensorCount, setSensorCount] = useState(0)
- const [controllerCount, setControllerCount] = useState(0)
-
- const [anchorEl, setAnchorEl] = React.useState(null);
- //only used to determine what preferences are options for the selected component ie. DHT can be a plenum or a headspace
- const [selectedComponentType, setSelectedComponentType] = useState<
- quack.ComponentType | undefined
- >(undefined);
- const [componentUnassigned, setComponentUnnassigned] = useState(false);
- const [selectedComponentKey, setSelectedComponentKey] = useState("");
- const [selectedComponentSubtype, setSelectedComponentSubtype] = useState(0);
- const [selectedComponentTopNode, setSelectedComponentTopNode] = useState(0);
- const [showGraphs, setShowGraphs] = useState(false);
- const defaultDateRange = GetDefaultDateRange();
- const [startDate, setStartDate] = useState(defaultDateRange.start);
- const [endDate, setEndDate] = useState(defaultDateRange.end);
- const [filterNodes, setFilterNodes] = useState(true)
- const isMobile = useMobile()
-
- //organize the components into the correct 'positions' using the bins preferences
- useEffect(()=>{
- let unassigned: Component[] = []
- let cables: GrainCable[] = []
- let plenums: Plenum[] = []
- let ambients: Ambient[] = []
- let pressures: Pressure[] = []
- let fans: Controller[] = []
- let heaters: Controller[] = []
- let headspaceTH: Headspace[] = []
- let headspaceCO2: CO2[] = []
- let headspaceLidar: Component[] = []
- let sensorCount = 0
- let controllerCount = 0
-
- components.forEach(comp => {
- if(preferences){
- let pref = preferences.get(comp.key())
- switch(pref?.type){
- case pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE:
- cables.push(GrainCable.create(comp))
- sensorCount++
- break;
- case pond.BinComponent.BIN_COMPONENT_PLENUM:
- plenums.push(Plenum.create(comp))
- sensorCount++
- break;
- case pond.BinComponent.BIN_COMPONENT_AMBIENT:
- ambients.push(Ambient.create(comp))
- sensorCount++
- break;
- case pond.BinComponent.BIN_COMPONENT_PRESSURE:
- pressures.push(Pressure.create(comp))
- sensorCount++
- break;
- case pond.BinComponent.BIN_COMPONENT_FAN:
- fans.push(Controller.create(comp))
- controllerCount++
- break;
- case pond.BinComponent.BIN_COMPONENT_HEATER:
- heaters.push(Controller.create(comp))
- controllerCount++
- break;
- case pond.BinComponent.BIN_COMPONENT_HEADSPACE:
- if (comp.type() === quack.ComponentType.COMPONENT_TYPE_DHT) {
- headspaceTH.push(Headspace.create(comp));
- } else if (comp.type() === quack.ComponentType.COMPONENT_TYPE_LIDAR) {
- headspaceLidar.push(comp);
- } else if (comp.type() === quack.ComponentType.COMPONENT_TYPE_CO2) {
- let coComp = CO2.create(comp)
- headspaceCO2.push(coComp);
- }
- sensorCount++
- break;
- default:
- unassigned.push(comp)
- }
- }else{
- unassigned.push(comp)
- }
- })
- // console.log(cables)
- // console.log(unassigned)
- setSensorCount(sensorCount)
- setControllerCount(controllerCount)
- setCables(cables)
- setPlenums(plenums)
- setPressures(pressures)
- setAmbient(ambients)
- setFans(fans)
- setHeaters(heaters)
- setHeadspaceTH(headspaceTH)
- setHeadspaceCO2(headspaceCO2)
- setHeadspaceLidar(headspaceLidar)
- setUnassignedComponents(unassigned)
- },[components, preferences])
-
- const selectType = (component: string, type: pond.BinComponent, topNode?: number) => {
- setAnchorEl(null);
- if (!preferences) return
- let p = preferences.get(component);
- if (p) {
- p.type = type;
- p.node = topNode ?? 0;
- binAPI
- .updateComponentPreferences(bin.key(), component, p, as)
- .then(() => {
- snackbar.success("Component preferences updated");
- preferences.set(component, p!);
- setPreferences(new Map(preferences));
- })
- .catch(() => {
- snackbar.error("Component preference update failed");
- });
- }
- };
-
- const getOptions = () => {
- let options: JSX.Element[] = [];
-
- if (!componentUnassigned) {
- options.push(
- {
- selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_UNKNOWN);
- }}>
- Remove
-
- );
- }
- if (
- selectedComponentType === quack.ComponentType.COMPONENT_TYPE_DHT ||
- selectedComponentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE
- ) {
- options.push(
- {
- selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_AMBIENT);
- }}>
- Ambient
-
- );
- options.push(
- {
- selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_PLENUM);
- }}>
- Plenum
-
- );
- }
- if (
- selectedComponentType === quack.ComponentType.COMPONENT_TYPE_DHT ||
- selectedComponentType === quack.ComponentType.COMPONENT_TYPE_LIDAR ||
- selectedComponentType === quack.ComponentType.COMPONENT_TYPE_CO2
- ) {
- options.push(
- {
- selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_HEADSPACE);
- }}>
- Headspace
-
- );
- }
- if (
- selectedComponentType === quack.ComponentType.COMPONENT_TYPE_PRESSURE ||
- selectedComponentType === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE
- ) {
- options.push(
- {
- selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_PRESSURE);
- }}>
- Plenum Pressure
-
- );
- }
- if (selectedComponentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) {
- options.push(
- {
- selectType(
- selectedComponentKey,
- pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE,
- selectedComponentTopNode
- );
- }}>
- Grain Cable
-
- );
- }
- if (selectedComponentType === quack.ComponentType.COMPONENT_TYPE_BOOLEAN_OUTPUT) {
- options.push(
- {
- if (
- selectedComponentSubtype === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER
- ) {
- selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_HEATER);
- } else if (
- selectedComponentSubtype ===
- quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN
- ) {
- selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_FAN);
- }
- }}>
- Heaters & Fans
-
- );
- }
-
- if (options.length === 0) {
- return No Available Options ;
- } else {
- return options;
- }
- };
-
- const assignmentMenu = () => {
- return (
-
- );
- };
-
- const cableCard = (cable: GrainCable) => {
- const component = cable.asComponent()
- let icon = GetComponentIcon(component.type(), component.subType(), themeType)
- let unit = user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C"
-
- const hasHumidity = cable.aveHumidity() !== 0
- const hasMoisture = cable.grainMoistures.length > 0
-
- const rows: { label: string; min: string; avg: string; max: string }[] = [
- {
- label: `Temp ${unit}`,
- min: cable.minTemp(user.tempUnit(), filterNodes).toFixed(2),
- avg: cable.aveTemp(user.tempUnit(), filterNodes).toFixed(2),
- max: cable.maxTemp(user.tempUnit(), filterNodes).toFixed(2),
- },
- ...(hasHumidity ? [{
- label: "RH %",
- min: cable.minHumidity(filterNodes).toFixed(2),
- avg: cable.aveHumidity(filterNodes).toFixed(2),
- max: cable.maxHumidity(filterNodes).toFixed(2),
- }] : []),
- ...(hasMoisture ? [{
- label: "EMC %",
- min: cable.minMoisture(filterNodes).toFixed(2),
- avg: cable.aveMoisture(filterNodes).toFixed(2),
- max: cable.maxMoisture(filterNodes).toFixed(2),
- }] : []),
- ]
-
- return (
- {
- setComponentUnnassigned(false);
- setSelectedComponentType(cable.type());
- setSelectedComponentKey(cable.key());
- setSelectedComponentTopNode(cable.settings.grainFilledTo);
- setAnchorEl(event.currentTarget);
- }}/>
- )
- }
-
- const tempHumidCard = (sensor: Plenum | Ambient | Headspace, showEMC?: boolean) => {
- let icon = GetComponentIcon(sensor.settings.type, sensor.settings.subtype, themeType)
- let prefDisplay = ""
- switch (preferences?.get(sensor.key())?.type){
- case pond.BinComponent.BIN_COMPONENT_PLENUM:
- prefDisplay = "Plenum"
- break;
- case pond.BinComponent.BIN_COMPONENT_AMBIENT:
- prefDisplay = "Ambient"
- break;
- case pond.BinComponent.BIN_COMPONENT_HEADSPACE:
- prefDisplay = "Headspace"
- }
-
- let rows: {label: string, data: string}[] = [
- // build temperature row data
- {
- label: "Temp " + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C"),
- data: (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? sensor.temperature * (9 / 5) + 32 : sensor.temperature).toFixed(2)
- },
- // build humidity row
- {
- label: "RH %",
- data: sensor.humidity.toFixed(2)
- }
- ]
- // build moisture row assuming the bin has a grain type to use to calculatee the moisture
- if(showEMC && bin.grain() !== pond.Grain.GRAIN_INVALID && bin.grain() !== pond.Grain.GRAIN_NONE){
- let emc = ExtractMoisture(
- bin.grain(),
- sensor.temperature,
- sensor.humidity,
- bin.customGrain()
- )
- rows.push(
- {
- label: "EMC %",
- data: emc.toFixed(2)
- }
- )
- }
-
- return (
- {
- setComponentUnnassigned(false);
- setSelectedComponentType(sensor.type());
- setSelectedComponentKey(sensor.key());
- setAnchorEl(event.currentTarget);
- }}
- />
- )
- }
-
- const pressureCard = (pressure: Pressure) => {
- let icon = GetComponentIcon(pressure.settings.type, pressure.settings.subtype, themeType)
- let rows: {label: string, data: string}[] = [
- {
- label: "Pressure " + (user.pressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? "iwg" : "kPa"),
- data: (user.pressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? pressure.pascals/249 : pressure.pascals/1000).toFixed(2)
- }
- ]
-
- if(pressure.airflow){
- rows.push({
- label: "Airflow CFM",
- data: pressure.airflow.toFixed(2)
- })
- }
-
- return (
- {
- setComponentUnnassigned(false);
- setSelectedComponentType(pressure.type());
- setSelectedComponentKey(pressure.key());
- setAnchorEl(event.currentTarget);
- }}
- />
- )
- }
- const co2Card = (sensor: CO2) => {
- let icon = GetComponentIcon(sensor.settings.type, sensor.settings.subtype, themeType)
- let rows: {label: string, data: string}[] = [
- {
- label: "CO2 ppm",
- data: sensor.ppm.toFixed(2)
- }
- ]
-
- return (
- {
- setComponentUnnassigned(false);
- setSelectedComponentType(sensor.type());
- setSelectedComponentKey(sensor.key());
- setAnchorEl(event.currentTarget);
- }}
- />
- )
- }
-
- const lidarCard = (sensor: Component) => {
- let icon = GetComponentIcon(sensor.settings.type, sensor.settings.subtype, themeType)
- let rows: {label: string, data: string}[] = []
- let lastReading = moment.now().toString()
- sensor.lastMeasurement.forEach(unitMeasurement => {
- let label = ""
- let value = 0
- if(sensor.type() === quack.ComponentType.COMPONENT_TYPE_LIDAR){
- if(unitMeasurement.type === quack.MeasurementType.MEASUREMENT_TYPE_DISTANCE_CM){
- label = "Lidar " + (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? " ft" : " M")
- if(unitMeasurement.timestamps.length > 0){
- lastReading = unitMeasurement.timestamps[0]
- }
- if (unitMeasurement.values.length > 0){
- //distance is stored in cm so needs to be converted
- value = avg(unitMeasurement.values[0].values)
- value = user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? value/30.48 : value/100
- }
-
- }
- }
- rows.push({label: label, data: value.toFixed(2)})
- })
-
- return (
- {
- setComponentUnnassigned(false);
- setSelectedComponentType(sensor.type());
- setSelectedComponentKey(sensor.key());
- setAnchorEl(event.currentTarget);
- }}
- />
-
- )
- }
-
- const controllerCard = (controller: Controller) => {
- const component = controller.asComponent()
- let icon = GetComponentIcon(component.type(), component.subType(), themeType)
- const isStale = moment().diff(moment(controller.lastReading), "hours") > 6
- const isAuto = controller.mode === quack.OutputMode.OUTPUT_MODE_AUTO
- const isOn = controller.mode === quack.OutputMode.OUTPUT_MODE_ON
- const isOff = controller.mode === quack.OutputMode.OUTPUT_MODE_OFF
-
- const prefType = preferences?.get(controller.key())?.type
- const tag = prefType === pond.BinComponent.BIN_COMPONENT_FAN ? "Fan" : "Heater"
-
- const segStyle = (active: boolean, activeColor?: string): React.CSSProperties => ({
- flex: 1,
- padding: "5px 0",
- fontSize: 11,
- fontWeight: 500,
- textAlign: "center",
- cursor: "pointer",
- borderRadius: 0,
- backgroundColor: active ? (activeColor ?? theme.palette.success.main) : "transparent",
- color: active ? "#fff" : theme.palette.text.secondary,
- border: "none",
- transition: "background-color 0.15s",
- })
-
- return (
-
-
-
-
-
-
- {controller.name()}
- - {tag}
-
- ) => {
- setComponentUnnassigned(false)
- setSelectedComponentType(controller.type())
- setSelectedComponentKey(controller.key())
- setAnchorEl(event.currentTarget)
- }}
- >
-
-
-
-
-
- Current state
-
-
-
- {controller.on ? "Running" : isAuto ? "Standby" : "Off"}
-
-
-
-
-
- {[
- { label: "Auto", mode: quack.OutputMode.OUTPUT_MODE_AUTO, active: isAuto },
- { label: "On", mode: quack.OutputMode.OUTPUT_MODE_ON, active: isOn },
- { label: "Off", mode: quack.OutputMode.OUTPUT_MODE_OFF, active: isOff },
- ].map(({ label, mode, active }, i) => (
- {
- //this will update the components settings here to change the mode
- console.log("changing mode: " + mode)
- controller.settings.defaultOutputState = mode
- let device = componentDevices.get(controller.key())
- if(device){
- componentAPI.update(device, controller.settings).then(resp => {
- controller.mode = mode // ← update the field the UI reads
-
- if (prefType === pond.BinComponent.BIN_COMPONENT_FAN) {
- setFans(prev => prev.map(f => f.key() === controller.key() ? controller : f))
- } else {
- setHeaters(prev => prev.map(h => h.key() === controller.key() ? controller : h))
- }
- }).catch(err => {
- console.log("there was a problem updating the controller")
- })
- }
-
- }}
- sx={{
- ...segStyle(active, label === "Off" ? theme.palette.action.selected : undefined),
- borderLeft: i > 0 ? `0.5px solid ${theme.palette.divider}` : "none",
- }}
- >
- {label}
-
- ))}
-
-
-
-
-
- {moment(controller.lastReading).fromNow()}
-
-
-
-
-
- )
- }
-
- const unassignedComponentCard = (component: Component) => {
- let icon = GetComponentIcon(component.type(), component.subType(), themeType)
- const lastReading = component.lastMeasurement.length > 0
- ? component.lastMeasurement[0].timestamps[0] ?? ""
- : ""
- const isStale = moment().diff(moment(lastReading), "hours") > 6
-
- return (
-
-
-
-
-
- {component.name()}
-
- ) => {
- setComponentUnnassigned(true)
- setSelectedComponentType(component.type())
- setSelectedComponentKey(component.key())
- setSelectedComponentSubtype(component.subType())
- setSelectedComponentTopNode(0)
- setAnchorEl(event.currentTarget)
- }}
- >
-
-
-
-
-
-
-
- {lastReading ? moment(lastReading).fromNow() : "No readings yet"}
-
-
-
-
-
- )
- }
- const updateDateRange = (newStartDate: any, newEndDate: any) => {
- let range = GetDefaultDateRange();
- range.start = newStartDate;
- range.end = newEndDate;
- setStartDate(newStartDate);
- setEndDate(newEndDate);
- };
-
- const graphControls = () => {
- return (
-
- {
- setShowGraphs(!showGraphs)
- }}>
- {showGraphs ? "Show Cards" : "Show Graphs"}
-
- {showGraphs && !isMobile &&
-
- }
- {cables.length > 0 &&
- {
- setFilterNodes(!filterNodes)
- }}
- />
- }
- label={Show All Nodes
- }
- />
- }
-
- )
- }
-
- return (
-
- {assignmentMenu()}
- {sensorCount > 0 &&
-
-
- Sensors
- {graphControls()}
-
- {showGraphs && isMobile &&
-
- }
-
- {plenums.map(plenum => {
- let device = componentDevices.get(plenum.key())
- if(showGraphs && device){
- let icon = GetComponentIcon(plenum.type(), plenum.subType(), themeType)
- return(
-
- {
- setComponentUnnassigned(false);
- setSelectedComponentType(plenum.type());
- setSelectedComponentKey(plenum.key());
- setAnchorEl(event.currentTarget);
- }}
- />
-
- )
- }
- return(
-
- {tempHumidCard(plenum)}
-
- )
- }
- )}
- {pressures.map(pressure => {
- let device = componentDevices.get(pressure.key())
- if(showGraphs && device){
- let icon = GetComponentIcon(pressure.type(), pressure.subType(), themeType)
- return(
-
- {
- setComponentUnnassigned(false);
- setSelectedComponentType(pressure.type());
- setSelectedComponentKey(pressure.key());
- setAnchorEl(event.currentTarget);
- }}
- />
-
- )
- }
- return(
-
- {pressureCard(pressure)}
-
- )
- })}
- {ambient.map(ambient => {
- let device = componentDevices.get(ambient.key())
- if(showGraphs && device){
- let icon = GetComponentIcon(ambient.type(), ambient.subType(), themeType)
- return(
-
- {
- setComponentUnnassigned(false);
- setSelectedComponentType(ambient.type());
- setSelectedComponentKey(ambient.key());
- setAnchorEl(event.currentTarget);
- }}
- />
-
- )
- }
- return(
-
- {tempHumidCard(ambient)}
-
- )
- }
- )}
- {headspaceTH.map(h => {
- let device = componentDevices.get(h.key())
- if(showGraphs && device){
- let icon = GetComponentIcon(h.type(), h.subType(), themeType)
- return(
-
- {
- setComponentUnnassigned(false);
- setSelectedComponentType(h.type());
- setSelectedComponentKey(h.key());
- setAnchorEl(event.currentTarget);
- }}
- />
-
- )
- }
- return(
-
- {tempHumidCard(h)}
-
- )
- })}
-
- {headspaceCO2.map(co2 => {
- let device = componentDevices.get(co2.key())
- if(showGraphs && device){
- let icon = GetComponentIcon(co2.type(), co2.subType(), themeType)
- return(
-
- {
- setComponentUnnassigned(false);
- setSelectedComponentType(co2.type());
- setSelectedComponentKey(co2.key());
- setAnchorEl(event.currentTarget);
- }}
- />
-
- )
- }
- return(
-
- {co2Card(co2)}
-
- )
- })}
- {headspaceLidar.map(lidar => {
- let device = componentDevices.get(lidar.key())
- if(showGraphs && device){
- let icon = GetComponentIcon(lidar.type(), lidar.subType(), themeType)
- return(
-
- {
- setComponentUnnassigned(false);
- setSelectedComponentType(lidar.type());
- setSelectedComponentKey(lidar.key());
- setAnchorEl(event.currentTarget);
- }}
- />
-
- )
- }
- return (
-
- {lidarCard(lidar)}
-
- )
- })}
- {cables.map(cable => {
- let device = componentDevices.get(cable.key())
- if(showGraphs && device){
- let icon = GetComponentIcon(cable.type(), cable.subType(), themeType)
- return(
-
- {
- setComponentUnnassigned(false);
- setSelectedComponentType(cable.type());
- setSelectedComponentKey(cable.key());
- setSelectedComponentTopNode(cable.settings.grainFilledTo);
- setAnchorEl(event.currentTarget);
- }}
- />
-
- )
- }
- return(
-
- {cableCard(cable)}
-
- )
- })}
-
-
- }
-
- {controllerCount > 0 &&
-
- Controllers
-
- {heaters.map(heater => {
- let device = componentDevices.get(heater.key())
- if(showGraphs && device){
- let icon = GetComponentIcon(heater.type(), heater.subType(), themeType)
- return(
-
- {
- setComponentUnnassigned(false);
- setSelectedComponentType(heater.type());
- setSelectedComponentKey(heater.key());
- setAnchorEl(event.currentTarget);
- }}
- />
-
- )
- }
- return (
-
- {controllerCard(heater)}
-
- )
- })}
- {fans.map(fan => {
- let device = componentDevices.get(fan.key())
- if(showGraphs && device){
- let icon = GetComponentIcon(fan.type(), fan.subType(), themeType)
- return(
-
- {
- setComponentUnnassigned(false);
- setSelectedComponentType(fan.type());
- setSelectedComponentKey(fan.key());
- setAnchorEl(event.currentTarget);
- }}
- />
-
- )
- }
- return (
-
- {controllerCard(fan)}
-
- )
- })}
-
-
- }
- {unassignedComponents.length > 0 &&
-
- Unassigned Components
-
- {unassignedComponents.map(comp => (
-
- {unassignedComponentCard(comp)}
-
- ))}
-
-
- }
-
- )
-}
\ No newline at end of file
diff --git a/src/bin/binSummary/BinSummary.tsx b/src/bin/binSummary/BinSummary.tsx
deleted file mode 100644
index dcab089..0000000
--- a/src/bin/binSummary/BinSummary.tsx
+++ /dev/null
@@ -1,415 +0,0 @@
-import { Box, Card, DialogContent, DialogTitle, Grid2, IconButton, LinearProgress, Skeleton, Slider, Switch, Tooltip, Typography } from "@mui/material";
-import { useMobile } from "hooks";
-import { Bin, Component, Device } from "models";
-import Bin3dVisualizer from "../bin3dVisualizer";
-// import GrassIcon from "@mui/icons-material/Grass"
-import { grey, orange, red } from "@mui/material/colors";
-import { AccessTime, Spa } from "@mui/icons-material";
-import moment from "moment";
-import { Controller } from "models/Controller";
-import { GrainCable } from "models/GrainCable";
-import { pond } from "protobuf-ts/pond";
-import BinDetails from "./components/binDetails";
-import { Plenum } from "models/Plenum";
-import BinSensorsDisplay from "../binSensorsDisplay";
-import ResponsiveDialog from "common/ResponsiveDialog";
-import { useEffect, useRef, useState } from "react";
-import SearchSelect from "common/SearchSelect";
-import ChangeGrainDialog from "grain/ChangeGrainDialog";
-import { useGlobalState } from "providers";
-import GrainTransaction from "grain/GrainTransaction";
-import { cloneDeep } from "lodash";
-import BinPlayer from "bin/BinPlayer";
-
-interface Props {
- bin: Bin
- loading?: boolean,
- devices: Device[]
- cables?: GrainCable[]
- plenums?: Plenum[]
- fans?: Controller[]
- heaters?: Controller[]
- permissions: pond.Permission[]
- componentDevices: Map
- componentMap: Map
- binPrefs?: Map
- setPreferences: React.Dispatch<
- React.SetStateAction | undefined>
- >;
- updateBinCallback?: (bin: Bin) => void
- setBin?: React.Dispatch>;
-
-}
-export default function BinSummary(props: Props){
- const {bin, loading, devices, fans, heaters, permissions, componentDevices, componentMap, binPrefs, updateBinCallback, cables = [], plenums = [], setPreferences, setBin} = props
- //const [currentDevice, setCurrentDevice] = useState(undefined)
- const isMobile = useMobile()
- const [openGrainDialog, setOpenGrainDialog] = useState(false)
- const [manualFillPercent, setManualFillPercent] = useState(0)
- const [manualBushels, setManualBushels] = useState(0)
- const [{user}] = useGlobalState()
- const fillCapRef = useRef(null);
- const [fillCapWidth, setFillCapWidth] = useState(undefined);
- const [openNewTransaction, setOpenNewTransaction] = useState(false)
-
- useEffect(() => {
- if (fillCapRef.current) {
- // measure the max possible string width once
- fillCapRef.current.textContent = `${bin.grainCapacity(user)}/${bin.grainCapacity(user)}`;
- setFillCapWidth(fillCapRef.current.offsetWidth);
- }
- }, [bin, user]);
-
- useEffect(()=>{
- setManualFillPercent(bin.fillPercent())
- setManualBushels(bin.bushels())
- },[bin])
-
- // useEffect(() => {
- // if(devices.length > 0){
- // setCurrentDevice(devices[0])
- // }
- // },[devices])
-
- const inventoryControl = () => {
- switch (bin.inventoryControl()) {
- case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_MANUAL:
- return "Manual"
- case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC:
- return "Auto Cable"
- case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR:
- return "Auto Lidar"
- case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_CABLE:
- return "Hybrid Cable"
- case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR:
- return "Hybrid Lidar"
- case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART:
- return "Libra Cart"
- default:
- return "Not Set"
- }
- }
-
- const activity = (reading: string) => {
- //if the device hasnt checked in in 6 hours = missing 12 hours = inactive within 6 hours = live
- let status = "Live"
- let colour = "#4caf50"
- let diff = moment().diff(moment(reading), "hour")
- if(diff > 12){
- status = "Inactive"
- colour = red[700]
- }else if (diff > 6){
- status = "Missing"
- colour = orange[300]
- }
-
- return (
-
-
-
- {status}
-
-
- )
- }
-
- const currentFill = () => {
- if(bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_MANUAL){
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
- return Math.round((manualBushels / bin.bushelsPerTonne())*10)/10
- }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){
- return Math.round((manualBushels / (bin.bushelsPerTonne() * 0.907))*10)/10
- }
- return Math.round(manualBushels)
- }
- return bin.grainInventory(user)
- }
-
- const inventorySummaryMobile = () => (
-
- {/* Header row */}
-
-
- setOpenGrainDialog(true)}>
-
-
-
- Grain type
- {bin.grainName()}
-
-
-
-
-
- Last updated
- {moment(bin.status.timestamp).fromNow()}
-
-
-
-
- {/* Fill row */}
-
-
- Bin fill ({inventoryControl()})
-
-
- {bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_MANUAL
- ? manualFillPercent : bin.fillPercent()}%
-
-
- {currentFill().toLocaleString()} / {bin.grainCapacity(user).toLocaleString()}
-
-
-
- {bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_MANUAL ? (
-
- setOpenNewTransaction(true)}
- onChange={(_, val) => { setManualFillPercent(val as number); setManualBushels((val as number / 100) * bin.bushelCapacity()); }}
- min={0} max={100} sx={{
- color: "#4caf50",
- py: 0,
- mt: 0,
- mb: 0,
- height: 4,
- "& .MuiSlider-root": { py: 0 },
- "& .MuiSlider-thumb": { width: 16, height: 16 },
- "& .MuiSlider-rail": { backgroundColor: "rgba(255,255,255,0.1)" },
- }} />
-
- ) : (
-
- )}
-
-
- );
-
- const inventorySummaryDesktop = () => {
- return (
-
-
-
- {/* Grain Type */}
-
-
- Grain Type
-
- setOpenGrainDialog(true)}
- sx={{
- cursor: "pointer",
- mt: 0.5,
- px: 1,
- py: 0.5,
- borderRadius: 1,
- border: "1px solid",
- borderColor: grey[700],
- "&:hover": {
- borderColor: grey[500],
- backgroundColor: "rgba(255,255,255,0.05)",
- },
- }}
- >
-
- {bin.grainName()}
-
-
-
- {/* Bin Fill */}
-
-
- Bin Fill ({inventoryControl()})
-
-
- {/* hidden measuring span, renders off-screen */}
-
-
- {currentFill()}/{bin.grainCapacity(user)}
-
- {bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_MANUAL ?
- {
- setOpenNewTransaction(true)
- }}
- onChange={(_, val) => {
- let fillPercent = val as number
- setManualFillPercent(fillPercent)
- let newBushels = (fillPercent/100) * bin.bushelCapacity()
- setManualBushels(newBushels)
- //this does casue the 3d bin to fill as you drag it but it throws off the transaction and the reset if you cancel
- // let clone = cloneDeep(bin)
- // let inventory = clone.settings.inventory ?? pond.BinInventory.create()
- // inventory.grainBushels = newBushels
- // if(setBin){
- // setBin(clone)
- // }
- }}
- min={0}
- max={100}
- sx={{
- flexGrow: 1,
- color: "#4caf50",
- "& .MuiSlider-thumb": {
- width: 16,
- height: 16,
- },
- "& .MuiSlider-rail": {
- backgroundColor: "rgba(255,255,255,0.1)",
- },
- }}
- />
- :
-
- }
-
- {bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_MANUAL ? manualFillPercent : bin.fillPercent()}%
-
-
-
-
- {/* Last Updated */}
-
-
- Last Updated
-
-
-
-
-
-
- {moment(bin.status.timestamp).fromNow()}
-
-
- {activity(bin.status.timestamp)}
-
-
-
-
-
- );
- };
-
- return (
-
- {
- //this resets all of the state variables used
- setOpenNewTransaction(false);
- if(!confirmed){
- setManualBushels(bin.bushels())
- setManualFillPercent(bin.fillPercent())
- }else{
- let clone = cloneDeep(bin)
- let inventory = clone.settings.inventory ?? pond.BinInventory.create()
- inventory.grainBushels = manualBushels
- if(setBin){
- setBin(clone)
- }
- }
-
- }}
- />
- setOpenGrainDialog(false)} permissions={permissions} updateBin={updateBinCallback}/>
- {loading ? : isMobile ? inventorySummaryMobile() : inventorySummaryDesktop()}
-
- {!isMobile &&
-
- {loading ?
-
- :
-
-
- {setBin &&
-
-
-
- }
-
- }
-
- }
-
- {loading ?
-
- :
-
-
-
- }
-
-
- {loading ?
-
- :
-
-
-
- }
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/src/bin/binSummary/components/binAlerts.tsx b/src/bin/binSummary/components/binAlerts.tsx
deleted file mode 100644
index f8c1bac..0000000
--- a/src/bin/binSummary/components/binAlerts.tsx
+++ /dev/null
@@ -1,137 +0,0 @@
-import { LinearProgress } from "@mui/material";
-import { Bin, Component, Device, Interaction } from "models";
-import Alerts, { Alert } from "objects/objectInteractions/Alerts";
-import NewObjectInteraction from "objects/objectInteractions/NewObjectInteraction";
-import { extension } from "pbHelpers/ComponentType";
-import { pond } from "protobuf-ts/pond";
-import { useGlobalState, useInteractionsAPI } from "providers";
-import React, { useCallback, useEffect, useState } from "react";
-
-interface Props {
- linkedComponents: Map; //the component key to the component object
- componentDevices: Map;
- permissions: pond.Permission[]
-}
-
-export default function BinAlerts(props: Props){
- const {linkedComponents, componentDevices, permissions} = props
- const [{as}] = useGlobalState();
- const interactionsAPI = useInteractionsAPI();
-
- //list of alerts, each alert contains a list of interactions with matching conditions
- const [alerts, setAlerts] = useState([]);
- const [loading, setLoading] = useState(false)
- const [newAlert, setNewAlert] = useState(false)
-
- const matchConditions = (interaction: Interaction, set: Alert) => {
- //if the number of conditions are not the same they are different
- if (interaction.settings.conditions.length !== set.conditions.length) {
- return false;
- }
- //continue with the comparison
- let matching = true;
- interaction.settings.conditions.forEach((condition, i) => {
- if (
- condition.comparison !== set.conditions[i].comparison ||
- condition.measurementType !== set.conditions[i].measurementType ||
- condition.value !== set.conditions[i].value
- ) {
- matching = false;
- }
- });
- return matching;
- };
-
- const load = useCallback(()=>{
- if(!loading){
- //load the interactions for the linked components
- setLoading(true)
- const newInteractions: Interaction[] = [];
- const interactionSourceMap = new Map();
- const sinkOptions: Component[] = [];
- const run = async () => {
- // Run all component API fetches in parallel
- await Promise.all(
- [...linkedComponents].map(async ([key, comp]) => {
- const device = componentDevices.get(key);
-
- if (device) {
- const resp = await interactionsAPI.listInteractionsByComponent(
- device,
- comp.location(),
- undefined,
- as
- );
-
- resp.forEach(interaction => interactionSourceMap.set(interaction.key(), key));
- newInteractions.push(...resp);
- }
-
- // Collect controller components
- if (extension(comp.type()).isController) {
- sinkOptions.push(comp);
- }
- })
- );
- };
-
- run().then(() => {
- newInteractions.forEach(interaction => {
- //alert and control are not mutally exclusive, it is possible to be both if the control is sending notifications
- if (interaction.isAlert()) {
- // Find matching alert
- let similarAlertIndex = alerts.findIndex(alert =>
- matchConditions(interaction, alert)
- );
-
- const compKey = interactionSourceMap.get(interaction.key());
- const component = compKey ? linkedComponents.get(compKey) : undefined;
-
- if (component) {
- if (similarAlertIndex === -1) {
- alerts.push({
- conditions: interaction.settings.conditions,
- sourceComponents: [component],
- });
- } else {
- alerts[similarAlertIndex].sourceComponents.push(component);
- }
- }
-
- }
- });
- setAlerts(alerts);
- }).catch(err => {
- console.error("Interaction fetch error:", err)
- }).finally(() => {
- setLoading(false)
- })
- }
- },[linkedComponents, interactionsAPI, componentDevices, as])
-
- useEffect(()=>{
- load()
- },[load])
-
-
-
- return (
-
- {
- setNewAlert(false)
- if(refresh){
- load()
- }
- }}
- linkedComponents={linkedComponents}
- componentDevices={componentDevices}/>
- {loading ?
-
- :
- {setNewAlert(true)}}/>
- }
-
- )
-}
\ No newline at end of file
diff --git a/src/bin/binSummary/components/binAnalysisGraphs.tsx b/src/bin/binSummary/components/binAnalysisGraphs.tsx
deleted file mode 100644
index cfb4a12..0000000
--- a/src/bin/binSummary/components/binAnalysisGraphs.tsx
+++ /dev/null
@@ -1,410 +0,0 @@
-import { Avatar, Box, Button, CardHeader, Checkbox, FormControlLabel, Grid2, Theme, Tooltip, Typography } from "@mui/material";
-import { useMobile, useThemeType } from "hooks";
-import VPDDark from "assets/products/Ag/dryingDark.png";
-import VPDLight from "assets/products/Ag/dryingLight.png";
-import TrendLight from "assets/products/Ag/trendingLight.png";
-import TrendDark from "assets/products/Ag/trendingDark.png";
-import { useEffect, useState } from "react";
-import { makeStyles } from "@mui/styles";
-import moment, { Moment } from "moment";
-import { blue, orange, teal } from "@mui/material/colors";
-import { GrainDryingPoint } from "charts/GrainDryingChart";
-import { GrainCable } from "models/GrainCable";
-import { Plenum } from "models/Plenum";
-import BinGraphsVPD from "bin/graphs/BinGraphsVPD";
-import { UnitMeasurement } from "models/UnitMeasurement";
-import { Bin } from "models";
-import { useBinAPI, useGlobalState } from "providers";
-import TimeBar from "common/time/TimeBar";
-import { GetDefaultDateRange } from "common/time/DateRange";
-import { ZoomOut } from "@mui/icons-material";
-import BinGraphsTrending from "bin/graphs/BinGraphsTrending";
-import { DataPoint, TrendPoint } from "charts/TrendingChart";
-import WaterLight from "assets/products/Ag/waterContentLight.png";
-import WaterDark from "assets/products/Ag/waterContentDark.png";
-import { Pressure } from "models/Pressure";
-import { DataPoint as WaterPoint } from "charts/WaterLevelChart";
-import BinWaterLevel from "bin/graphs/BinWaterLevel";
-import { Controller } from "models/Controller";
-import { pond } from "protobuf-ts/pond";
-
-interface Props {
- cables: GrainCable[]
- plenums: Plenum[]
- componentDevices: Map
- bin: Bin
-}
-
-const useStyles = makeStyles((theme: Theme) =>{
- return ({
- root: {
- display: "flex",
- flexDirection: "column",
- height: "100%",
- overflow: "hidden",
- },
- toolbar: {
- flexShrink: 0,
- },
- scrollContent: {
- flex: 1,
- minHeight: 0,
- overflowY: "auto",
- },
- card: {
- position: "relative",
- display: "flex",
- height: "100%",
- flexDirection: "column",
- overflow: "visible"
- },
- cardHeader: {
- padding: theme.spacing(1),
- paddingLeft: theme.spacing(2),
- marginRight: 10
- },
- avatarIcon: {
- width: 33,
- height: 33
- }
- })
- });
-
-export default function BinAnalysisGraphs(props: Props){
- const {cables, plenums, componentDevices, bin} = props
- const themeType = useThemeType()
- const classes = useStyles()
- const [{user, as, showErrors}, dispatch] = useGlobalState()
- const isMobile = useMobile()
- const [xDomain, setXDomain] = useState(["dataMin", "dataMax"]);
- const [zoomed, setZoomed] = useState(false);
- const [recentVPD, setRecentVPD] = useState();
- const [loading, setLoading] = useState(false)
- // map using the component key and the unitmeasurements for that component
- const [compMeasurements, setCompMeasurements] = useState>(
- new Map()
- );
- const binAPI = useBinAPI()
- const defaultDateRange = GetDefaultDateRange()
- const [startDate, setStartDate] = useState(defaultDateRange.start);
- const [endDate, setEndDate] = useState(defaultDateRange.end);
- const [recentPlenumTrend, setRecentPlenumTrend] = useState();
- const [recentCableTrend, setRecentCableTrend] = useState();
- const [recentWaterContent, setRecentWaterContent] = useState();
-
- useEffect(() => {
- let measurementMap: Map = new Map();
- if (!loading) {
- setLoading(true);
- //check if the bin has a fan to use the cfm in the drying graph
- // if (bin.settings.fan?.type !== pond.FanType.FAN_TYPE_UNKNOWN) {
- // setIncludeCFM(true);
- // }
- binAPI
- .listBinComponentsMeasurements(
- bin.key(),
- startDate.toISOString(),
- endDate.toISOString(),
- showErrors,
- showErrors,
- as
- )
- .then(resp => {
- resp.data.measurements.forEach((um: any) => {
- let unitMeasurement = UnitMeasurement.any(um, user);
- let entry = measurementMap.get(unitMeasurement.componentId);
- if (entry) {
- entry.push(unitMeasurement);
- } else {
- measurementMap.set(unitMeasurement.componentId, [unitMeasurement]);
- }
- });
- setCompMeasurements(measurementMap);
- setLoading(false);
- });
- }
- }, [bin, binAPI, startDate, endDate, user, as]); // eslint-disable-line react-hooks/exhaustive-deps
-
- const zoomOut = () => {
- setXDomain(["dataMin", "dataMax"]);
- setZoomed(false);
- };
-
- const zoomIn = (domain: string[] | number[]) => {
- if (!isMobile) {
- setXDomain(domain);
- setZoomed(true);
- }
- };
-
- const updateDateRange = (newStartDate: any, newEndDate: any) => {
- let range = GetDefaultDateRange();
- range.start = newStartDate;
- range.end = newEndDate;
- setStartDate(newStartDate);
- setEndDate(newEndDate);
- };
-
- //this uses the components found in the bins status
- const waterGraph = () => {
- let moistureCables: pond.GrainCable[] = [];
- bin.status.grainCables.forEach(cable => {
- if (cable.relativeHumidity.length > 0) {
- moistureCables.push(cable);
- }
- });
- if (bin.status.plenums[0] && moistureCables[0] && bin.status.pressures[0] && bin.supportedGrain()) {
- return (
-
- 0
- ? componentDevices.get(bin.status.fans[0].key) + ":" + bin.status.fans[0].key
- : undefined
- }
- newXDomain={xDomain}
- multiGraphZoom={zoomIn}
- multiGraphZoomOut
- returnLast={lastWater => {
- setRecentWaterContent(lastWater);
- }}
- />
-
- );
- } else {
- return (
-
-
- Plenum with pressure and moisture cable as well as an initial moisture and supported
- grain type set in the bin settings required to calculate Water Content
-
-
- );
- }
- };
-
- return (
-
-
-
-
-
-
- {zoomed && (
-
-
-
-
-
- )}
- {
- //setShowErrors(!showErrors);
- dispatch({ key: "showErrors", value: !showErrors });
- }}
- />
- }
- />
-
-
-
-
- }
- title={
-
-
-
- Drying vs. Hydrating
-
-
-
- }
- subheader={
- recentVPD ? (
-
-
-
- 0 ? orange[500] : blue[500] }}>
- {recentVPD.dryScore > 0 ? "drying" : "hydrating"}
-
- {" : "}
- 0 ? orange[500] : blue[500],
- fontWeight: 500
- }}>
- {recentVPD.dryScore.toFixed(2)}
-
-
-
-
-
-
- {moment(recentVPD.timestamp).fromNow()}
-
-
-
- ) : (
-
- No Data
-
- )
- }
- />
-
- {cables.length > 0 && plenums.length > 0 ? (
- {
- setRecentVPD(lastVPD);
- }}
- />
- ) : (
-
-
- Plenum and moisture cable required to calculate VPD
-
-
- )}
-
-
- }
- title={
- Moisture Trending
- }
- subheader={
- recentPlenumTrend && recentCableTrend ? (
-
-
-
- {"Plenum Moisture: "}
-
- {recentPlenumTrend.trend.toFixed(2)}
-
-
-
-
- {"Grain Moisture: "}
-
- {recentCableTrend.moisture.toString()}
-
-
-
-
-
-
- {recentPlenumTrend.timestamp > recentCableTrend.timestamp
- ? moment(recentPlenumTrend.timestamp).fromNow()
- : moment(recentCableTrend.timestamp).fromNow()}
-
-
-
- ) : (
-
- No Data
-
- )
- }
- />
-
- {cables.length > 0 && plenums.length > 0 ? (
- {
- setRecentCableTrend(lastData);
- setRecentPlenumTrend(lastTrend);
- }}
- />
- ) : (
-
-
- Plenum and moisture cable required to calculate trending data
-
-
- )}
-
-
- }
- title={
- Grain Water Content
- }
- subheader={
- recentWaterContent &&
- bin.settings.inventory &&
- bin.settings.inventory.initialMoisture > 0 ? (
-
-
-
- {"Water Content: "}
-
- {recentWaterContent.liters && !isNaN(recentWaterContent.liters)
- ? recentWaterContent.liters.toFixed(2)
- : ""}
-
-
-
-
-
-
- {moment(recentWaterContent.timestamp).fromNow()}
-
-
-
- ) : (
-
- No Data
-
- )
- }
- />
-
- {waterGraph()}
-
-
-
- )
-}
\ No newline at end of file
diff --git a/src/bin/binSummary/components/binControls.tsx b/src/bin/binSummary/components/binControls.tsx
deleted file mode 100644
index a1a4cde..0000000
--- a/src/bin/binSummary/components/binControls.tsx
+++ /dev/null
@@ -1,188 +0,0 @@
-import { LinearProgress } from "@mui/material";
-import { Bin, Component, Device, Interaction } from "models";
-import Controls, { Control } from "objects/objectInteractions/Controls";
-import NewObjectInteraction from "objects/objectInteractions/NewObjectInteraction";
-import { sameComponentID } from "pbHelpers/Component";
-import { extension } from "pbHelpers/ComponentType";
-import { pond } from "protobuf-ts/pond";
-import { quack } from "protobuf-ts/quack";
-import { useGlobalState } from "providers";
-import interactionsAPI, { useInteractionsAPI } from "providers/pond/interactionsAPI";
-import React, { useCallback, useEffect, useState } from "react";
-
-interface Props {
- linkedComponents: Map; //the component key to the component object
- componentDevices: Map;
- devices: Device[];
- permissions: pond.Permission[]
-}
-
-export default function BinControls(props: Props) {
- const {linkedComponents, componentDevices, devices, permissions} = props
- const [{as}] = useGlobalState()
- const interactionsAPI = useInteractionsAPI()
- const [controls, setControls] = useState([]);
- const [selectedDevice, setSelectedDevice] = useState(undefined)
- const [openNewInteraction, setOpenNewInteraction] = useState(false)
- const [loading, setLoading] = useState(false)
-
-
- const findDevice = (deviceID: number) => {
- for (let device of devices) {
- if (device.id() === deviceID) return device
- }
- }
- const findSinkComponent = (sinks: Component[], interactionSink: quack.ComponentID, sourceDevice: number) => {
- for (let component of sinks) {
- if (sameComponentID(component.location(), interactionSink) && sourceDevice === componentDevices.get(component.key())){
- return component
- }
- }
- }
-
- const matchConditions = (interaction: Interaction, set: Control) => {
- //if the number of conditions are not the same they are different
- if (interaction.settings.conditions.length !== set.conditions.length) {
- return false;
- }
- //continue with the comparison
- let matching = true;
- interaction.settings.conditions.forEach((condition, i) => {
- if (
- condition.comparison !== set.conditions[i].comparison ||
- condition.measurementType !== set.conditions[i].measurementType ||
- condition.value !== set.conditions[i].value
- ) {
- matching = false;
- }
- });
- return matching;
- };
-
- const matchResult = (interaction: Interaction, set: Control) => {
- let interactionResult = interaction.settings.result
- let setResult = set.result
- let matching = true
- // if anything in the result for the interaction is different from the set make matching false
- if(interactionResult?.type !== setResult?.type ||
- interactionResult?.mode !== setResult?.mode ||
- interactionResult?.value !== setResult?.value ||
- interactionResult?.dutyCycle !== setResult?.dutyCycle
- ){
- matching = false
- }
- return matching
- }
- const load = useCallback(()=>{
- if(!loading){
- setLoading(true)
- const newInteractions: Interaction[] = [];
- const interactionSourceMap = new Map();
- const sinkOptions: Component[] = [];
- const controls: Control[] = [];
-
- const run = async () => {
- // Run all component API fetches in parallel
- await Promise.all(
- [...linkedComponents].map(async ([key, comp]) => {
- const device = componentDevices.get(key);
-
- if (device) {
- const resp = await interactionsAPI.listInteractionsByComponent(
- device,
- comp.location(),
- undefined,
- as
- );
-
- resp.forEach(interaction => interactionSourceMap.set(interaction.key(), key));
- newInteractions.push(...resp);
- }
-
- // Collect controller components
- if (extension(comp.type()).isController) {
- sinkOptions.push(comp);
- }
- })
- );
- };
-
- run().then(() => {
- newInteractions.forEach(interaction => {
- if (interaction.isControl()) {
- if (!interaction.settings.sink) return //if there is no sink it cant be a control so move on to the next
- const compKey = interactionSourceMap.get(interaction.key());
- if (!compKey) return //we have to have a valid component key
- const component = linkedComponents.get(compKey);
- const deviceID = componentDevices.get(compKey);
- if (!deviceID) return //we have to know which device it is for
- const controlDevice = findDevice(deviceID)
- if (!controlDevice) return //failed to get the device from the array
- const sinkComponent = findSinkComponent(sinkOptions, interaction.settings.sink, deviceID)
- if (!sinkComponent) return //failed to get the correct sink component
-
- let similarControlIndex = controls.findIndex(control =>
- matchConditions(interaction, control) &&
- matchResult(interaction, control) &&
- sameComponentID(interaction.settings.sink, control.sinkComponent.location()) && //if the sink for the interaction is the same address as the existing control
- deviceID === control.device.id() //if the device id for source component is the same as the device id in the control
- );
-
- if (component && interaction.settings.result) {
- if (similarControlIndex === -1) {
- controls.push({
- conditions: interaction.settings.conditions,
- result: interaction.settings.result,
- sourceComponents: [component],
- sinkComponent: sinkComponent,
- device: controlDevice
- });
- } else {
- controls[similarControlIndex].sourceComponents.push(component);
- }
- }
- }
- });
- setControls(controls);
- }).catch(err => {
- console.error("Interaction fetch error:", err)
- }).finally(() => {
- setLoading(false)
- })
- }
- },[linkedComponents, interactionsAPI, componentDevices, as])
-
- useEffect(() => {
- load()
- }, [load]);
-
- return (
-
- {
- setOpenNewInteraction(false)
- if(refresh){
- load()
- }
- }}
- linkedComponents={linkedComponents}
- componentDevices={componentDevices}
- device={selectedDevice}/>
- {loading ?
-
- :
- {
- console.log("new control")
- setSelectedDevice(device)
- setOpenNewInteraction(true)
- }}
- />
- }
-
- )
-}
\ No newline at end of file
diff --git a/src/bin/binSummary/components/binDetails.tsx b/src/bin/binSummary/components/binDetails.tsx
deleted file mode 100644
index c4acfca..0000000
--- a/src/bin/binSummary/components/binDetails.tsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import { Box, Tab, Tabs } from "@mui/material";
-import React from "react";
-import { useState } from "react";
-import BinTableView from "../../binTableView";
-import { Bin, Component, Device } from "models";
-import Alerts from "objects/objectInteractions/Alerts";
-import BinAlerts from "./binAlerts";
-import { pond } from "protobuf-ts/pond";
-import BinControls from "./binControls";
-import BinAnalysisGraphs from "./binAnalysisGraphs";
-import { GrainCable } from "models/GrainCable";
-import { Plenum } from "models/Plenum";
-import { Controller } from "models/Controller";
-import { useMobile } from "hooks";
-import Bin3dVisualizer from "bin/bin3dVisualizer";
-import BinPlayer from "bin/BinPlayer";
-
-interface Props {
- bin: Bin
- updateBinCallback?: (bin: Bin) => void
- linkedComponents: Map; //the component key to the component object
- componentDevices: Map;
- devices: Device[];
- permissions: pond.Permission[]
- cables: GrainCable[]
- plenums: Plenum[]
- showBinTab?: boolean
- fans?: Controller[]
- heaters?: Controller[]
- componentMap: Map
- binPrefs?: Map
- setBin?: React.Dispatch>;
-}
-
-interface TabPanelProps {
- children?: React.ReactNode;
- index: any;
- value: any;
-}
-
-function TabPanelMine(props: TabPanelProps) {
- const { children, value, index, ...other } = props;
-
- return (
-
- {value === index && children}
-
- );
- }
-
-export default function BinDetails (props: Props) {
- const {bin, updateBinCallback, linkedComponents, componentDevices, devices, permissions, cables, plenums, showBinTab, fans, heaters, componentMap, binPrefs, setBin} = props
- const isMobile = useMobile()
- const [currentTab, setCurrentTab] = useState((isMobile || showBinTab) ? "bin" : "table")
-
- //this is where we will have the tabs and tab panels for the table view, alerts, controls, and analysis
-
- return (
-
- setCurrentTab(value)}
- indicatorColor="primary"
- textColor="primary"
- variant={isMobile ? "scrollable" : "fullWidth"}
- scrollButtons="auto"
- allowScrollButtonsMobile
- sx={{
- flexShrink: 0,
- '& .MuiTabs-scroller': {
- overflowX: 'auto !important',
- }
- }}>
- {isMobile && }
-
-
-
-
-
-
- {(isMobile || showBinTab) &&
-
-
- {setBin &&
-
-
-
- }
-
- }
-
-
- {setBin && isMobile &&
-
-
-
- }
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/src/bin/binSummary/components/nodeControls.tsx b/src/bin/binSummary/components/nodeControls.tsx
deleted file mode 100644
index 2a8966a..0000000
--- a/src/bin/binSummary/components/nodeControls.tsx
+++ /dev/null
@@ -1,406 +0,0 @@
-import { AppBar, Box, Button, Card, CardActionArea, Checkbox, DialogActions, DialogContent, DialogTitle, FormControlLabel, Grid2, IconButton, Typography } from "@mui/material";
-import { green } from "@mui/material/colors";
-import { makeStyles } from "@mui/styles";
-import { CableData } from "bin/3dView/Data/BuildCableData";
-import { NodeData } from "bin/3dView/Data/BuildNodeData";
-import ResponsiveDialog from "common/ResponsiveDialog";
-import HumidityIcon from "component/HumidityIcon";
-import TemperatureIcon from "component/TemperatureIcon";
-import { useComponentAPI, useMobile, useSnackbar } from "hooks";
-import InteractionsOverview from "interactions/InteractionsOverview";
-import { Bin, Component, Device, Interaction } from "models";
-import moment from "moment";
-import AddIcon from "@mui/icons-material/AddCircle";
-import GraphIcon from "products/CommonIcons/graphIcon";
-import { pond } from "protobuf-ts/pond";
-import { useBinAPI, useGlobalState, useInteractionsAPI } from "providers";
-import React from "react";
-import { useEffect, useState } from "react";
-import { useNavigate } from "react-router-dom";
-import { canWrite } from "pbHelpers/Permission";
-import InteractionSettings from "interactions/InteractionSettings";
-
-const useStyles = makeStyles(() => {
- return ({
- dialog: {
- maxWidth: 350
- },
- appBar: {
- position: "static",
- borderRadius: 30
- },
- readingCard: {
- padding: 10
- },
- greenButton: {
- color: green["600"],
- padding: 0
- }
- });
- });
-
-interface Props {
- open: boolean
- onClose: () => void
- node: NodeData
- cable: CableData
- device: Device
- bin: Bin
- /**
- * the possible options for controllers when creating new interactions, these controllers must be on the same device as the cable for interactions to work
- */
- filteredComponents?: Component[]
- componentMap: Map
- permissions: pond.Permission[]
- /**
- * callback function for updating the cable with a new top node or excluded node
- * @returns void
- */
- updateComponentCallback?: () => void
-}
-
-export default function NodeControls(props: Props){
- const {open, onClose, node, cable, device, bin, filteredComponents, permissions, updateComponentCallback, componentMap} = props
- const classes = useStyles()
- const [isTopNode, setIsTopNode] = useState(false)
- const [isExcluded, setIsExcluded] = useState(false)
- const [cableComponent, setCableComponent] = useState(Component.create())
- const [cableInteractions, setCableInteractions] = useState([])
- const isMobile = useMobile()
- const navigate = useNavigate()
- const [{as, user}] = useGlobalState()
- const interactionAPI = useInteractionsAPI()
- const binAPI = useBinAPI()
- const componentAPI = useComponentAPI()
- const {openSnack} = useSnackbar()
- const [newInteraction, setNewInteraction] = useState(false);
-
- //will need to list the interactions for the cable
- useEffect(() => {
- let component = componentMap.get(cable.grainCable.key) ?? Component.create()
- setCableComponent(component)
- interactionAPI.listInteractionsByComponent(device.id(), cableComponent.location(), undefined, as).then(resp => {
- setCableInteractions(resp);
- });
- },[cable, device, interactionAPI])
-
- //determine if the node is excluded/the top node
- useEffect(()=>{
- if(node.topNode !== undefined){
- setIsTopNode(node.topNode)
- }
- if(cable.grainCable.excludedNodes.includes(node.nodeIndex)){
- setIsExcluded(true)
- }else(
- setIsExcluded(false)
- )
- },[node, cable])
-
- //navigation function to go to the component page
- const goToComponent = () => {
- navigate("/devices/" + device.id() + "/components/" + cable.grainCable.key);
- }
-
- const close = ()=>{
- onClose()
- }
-
- const setEMC = () => {
- let settings = cableComponent.settings;
- //make sure the mutation is not there first
- if(!settings.defaultMutations.includes(pond.Mutator.MUTATOR_EMC)){
- settings.defaultMutations.push(pond.Mutator.MUTATOR_EMC)
- }
- settings.grainType = bin.grain();
- settings.customGrain = bin.customGrain();
- componentAPI
- .update(device.id(), settings, [bin.key()], ["bin"], as)
- .then(resp => {
- openSnack("EMC set on cable " + cableComponent.name());
- })
- .catch(err => {});
- }
-
- const submit = () => {
- //should update the component and then update the bin prefs if successful
- componentAPI.update(device.id(), cableComponent.settings).then(resp => {
- let pref = pond.BinComponentPreferences.create({
- type: pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE,
- node: cableComponent.settings.grainFilledTo
- });
- //update the bins preferences to have the new top node for the cable
- binAPI
- .updateComponentPreferences(bin.key(), cableComponent.key(), pref, as)
- .then(resp => {
- openSnack("Changes will be reflected once the bins status updates")
- if(updateComponentCallback){
- updateComponentCallback()
- }
- })
- .catch(err => {});
- }).catch(err => {
-
- })
- close()
- }
-
- const updateTopNode = (checked: boolean) => {
- if (isTopNode && !checked) {
- cableComponent.settings.grainFilledTo = 0;
- cableInteractions.forEach(interaction => {
- interaction.settings.subtype = 0;
- interaction.settings.nodeOne = 0;
- interaction.settings.nodeTwo = 0;
- });
- } else if (checked) {
- cableComponent.settings.grainFilledTo = node.nodeIndex+1;
- cableInteractions.forEach(interaction => {
- interaction.settings.subtype = Interaction.upToSubtype(node.nodeIndex+1);
- interaction.settings.nodeOne = node.nodeIndex+1;
- });
- }
- setIsTopNode(checked);
- }
-
- const updateNodeExclusion = (checked: boolean) => {
- if (checked) {
- cableComponent.settings.excludedNodes.push(node.nodeIndex)
- } else {
- cableComponent.settings.excludedNodes.splice(cableComponent.settings.excludedNodes.indexOf(node.nodeIndex), 1)
- }
- setIsExcluded(checked)
- }
-
- const displayTemp = () => {
- let isF = user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
- let tempFinal = node.celcius ?? 0;
- if (isF) {
- tempFinal = (tempFinal * 1.8 + 32);
- }
- return tempFinal.toFixed(2) + (isF ? " F" : "C");
- };
-
- const latestReading = () => {
- return (
-
- Latest Reading
- {moment(cable.grainCable.lastRead).fromNow()}
-
-
-
-
-
-
- {displayTemp()}
-
-
-
- Temperature
-
-
-
-
-
-
-
-
- {node.humidity} %
-
-
-
- Humidity
-
-
-
-
- {/* only show the emc if the bin grain type and the cable grain type match */}
- {node.moisture ? (
-
-
-
-
- {node.moisture} %
-
-
-
- Grain EMC
-
-
- ) : (
-
- {
- setEMC();
- }}>
-
-
-
-
- Set EMC
-
-
-
- )}
-
-
-
- goToComponent()}>
-
-
-
-
- See Graphs
-
-
-
-
-
-
- );
- };
-
- const nodeControl = () => {
- return (
-
- Node Control
- {
- updateNodeExclusion(e.target.checked);
- }}
- name="excludedNode"
- />
- }
- label={
-
-
- Exclude Node
-
-
- If checked this will set the node to be disabled and will not be used for any calculations done for the bin
-
-
- }
- />
- {
- updateTopNode(e.target.checked);
- }}
- name="topNode"
- />
- }
- label={
-
-
- Set as top node in grain
-
-
- If checked this will set interactions to ignore all nodes above it. Interactions can
- still be adjusted manually to use all nodes
-
-
- }
- />
-
- );
- };
-
- const interactionDisplay = () => {
- return (
-
- Interactions
-
- {
- interactionAPI
- .listInteractionsByComponent(device.id(), cableComponent.location(), undefined, as)
- .then(resp => {
- setCableInteractions(resp);
- });
- }}
- />
-
-
- {
- setNewInteraction(true);
- }}
- className={classes.greenButton}>
-
-
-
-
- );
- };
-
- return (
-
-
-
-
- {cable.grainCable.name}
-
-
-
-
- Node {node.nodeIndex+1}
-
-
-
-
-
- {latestReading()}
- {nodeControl()}
- {interactionDisplay()}
- {/*
- */}
-
-
-
- Cancel
-
-
- Submit
-
-
-
- {
- setNewInteraction(false);
- }}
- refreshCallback={() => {
- interactionAPI.listInteractionsByComponent(device.id(), cableComponent.location(), undefined, as).then(resp => {
- setCableInteractions(resp);
- });
- }}
- canEdit={canWrite(permissions)}
- />
-
- )
-}
\ No newline at end of file
diff --git a/src/bin/binTableView.tsx b/src/bin/binTableView.tsx
deleted file mode 100644
index e11132e..0000000
--- a/src/bin/binTableView.tsx
+++ /dev/null
@@ -1,466 +0,0 @@
-import { Edit, GppGood, Info, Save } from "@mui/icons-material";
-import { Box, Button, darken, DialogActions, IconButton, Table, TableBody, TableCell, TableHead, TableRow, TextField, Theme, Tooltip, Typography, useTheme } from "@mui/material";
-import { green, grey, orange, red, yellow } from "@mui/material/colors";
-import { makeStyles } from "@mui/styles";
-import ResponsiveDialog from "common/ResponsiveDialog";
-import HumidityIcon from "component/HumidityIcon";
-import TemperatureIcon from "component/TemperatureIcon";
-import { cloneDeep } from "lodash";
-import { Bin } from "models";
-import { pond } from "protobuf-ts/pond";
-import { useGlobalState } from "providers";
-import React, { useEffect, useRef, useState } from "react";
-import { useMemo } from "react";
-import { avg, celsiusToFahrenheit } from "utils";
-import BinTableExpanded from "./BinTableExpanded";
-import { useMobile } from "hooks";
-
-const useStyles = makeStyles((theme: Theme) => ({
- tableStyle: {
- borderCollapse: "separate",
- borderSpacing: 0, // removes the default gap that "separate" introduces
- },
- headerCellStyle: {
- fontWeight: 650,
- backgroundColor: darken(theme.palette.background.paper, 0.3),
- textAlign: "center",
- padding: 10
- },
- mobileHeaderCellStyle: {
- fontWeight: 650,
- backgroundColor: darken(theme.palette.background.paper, 0.3),
- textAlign: "center",
- padding: 2
- },
- targetCellStyle: {
- textAlign: "center",
- backgroundColor: theme.palette.background.paper
- },
- mobileTargetCellStyle: {
- textAlign: "center",
- backgroundColor: theme.palette.background.paper,
- padding: 2
- },
- defaultCellStyle: {
- padding: 2,
- fontSize: 15,
- textAlign: "center",
- backgroundColor: darken(theme.palette.background.paper, 0.1),
- borderTop: "1px solid " + darken(theme.palette.background.paper, 0.4),
- borderBottom: "none"
- },
- colouredCellStyle: {
- padding: 2,
- borderTop: "1px solid " + darken(theme.palette.background.paper, 0.4),
- // fontWeight: 650,
- fontSize: 15,
- textAlign: "center",
- borderBottom: "none"
- }
- })
-);
-
-interface Props {
- bin: Bin
- updateBinCallback?: (bin: Bin) => void
-}
-
-interface BinLevel {
- grainTemps: number[]
- airTemps: number[]
- grainMoistures: number[]
- airMoistures: number[]
-}
-
-
-export default function BinTableView(props: Props) {
- const classes = useStyles()
- const {bin, updateBinCallback} = props
- const [{user}] = useGlobalState()
- const theme = useTheme()
- const [enterTemp, setEnterTemp] = useState(false)
- const [enterMoisture, setEnterMoisture] = useState(false)
- const [tempTarget, setTempTarget] = useState(0)
- const [tempEntry, setTempEntry] = useState("")
- const [moistureEntry, setMoistureEntry] = useState("")
- const [moistureTarget, setMoistureTarget] = useState(0)
- const [openExpandedCables, setOpenExpandedCables] = useState(false)
- const isMobile = useMobile()
- const inBounds = green[600]
- const caution = yellow[800]
- const warning = red[700]
- const headerRow1Ref = useRef(null)
- const [firstRowHeight, setFirstRowHeight] = useState(0)
-
- useEffect(() => {
- if (headerRow1Ref.current) {
- setFirstRowHeight(headerRow1Ref.current.getBoundingClientRect().height)
- }
- }, [enterTemp, enterMoisture]) // re-measure when the row height might change due to TextField appearing
-
- useEffect(()=>{
- let val = bin.targetTemp()
- if(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT){
- val = bin.targetTemp() * (9/5) + 32
- }
- setTempTarget(val)
- setTempEntry(val.toFixed(2))
- setMoistureTarget(bin.targetMoisture())
- setMoistureEntry(bin.targetMoisture().toFixed(2))
- },[bin])
-
- const binLevels = useMemo(() => {
- const cables = bin.status.grainCables
- let levels: BinLevel[] = []
- //note for future self, the first item in the measurement arrays is the lowest node on the cable
- //the zones we are creating here will match with the node number because most cables will either have the same number of nodes or only have a difference of 1 or 2
- //example: cable 1 has 5 nodes cable 2 has 7 nodes, the first five nodes of the cables will match up and average together for each zone
- //and then zones 6 and 7 will be made using only cable 2 because cable 1 doesn't have them
- cables.forEach(cable => {
- cable.celcius.forEach((c, index) => {
- //remember index will be 0 based but the top node starts at 1 for the lowest cable
- let inGrain = index < cable.topNode
- if (levels[index]){
- if(cable.excludedNodes.includes(index+1)) return
- if(inGrain){
- levels[index].grainTemps.push(c)
- //need to check that the cable not only has the moisture mutation on it, but also that it reads humidity
- //cables that dont read humidity return an array of 0 values so if the average of all of the values is 0 then it is a temp only cable
- if(cable.moisture.length > 0 && avg(cable.moisture) > 0){
- //based on all the other checks and the fact that the indexes between the array should match this should never be 0 but put a guard here just in case
- //also note that we are only doing moisture and not humidity, it seems like users dont care about humidity
- levels[index].grainMoistures.push(cable.moisture[index] ?? 0)
- }
- }else{
- levels[index].airTemps.push(c)
- //need to check that the cable not only has the moisture mutation on it, but also that it reads humidity
- //cables that dont read humidity return an array of 0 values so if the average of all of the values is 0 then it is a temp only cable
- if(cable.moisture.length > 0 && avg(cable.moisture) > 0){
- //based on all the other checks and the fact that the indexes between the array should match this should never be 0 but put a guard here just in case
- levels[index].airMoistures.push(cable.moisture[index] ?? 0)
- }
- }
- }else{
- let newlevel: BinLevel = {
- grainTemps: [],
- grainMoistures: [],
- airTemps: [],
- airMoistures: []
- }
- if(!cable.excludedNodes.includes(index)){
- if(inGrain){
- newlevel.grainTemps.push(c)
- if(cable.moisture.length > 0 && avg(cable.moisture) > 0){
- newlevel.grainMoistures.push(cable.moisture[index] ?? 0)
- }
- }else{
- newlevel.airTemps.push(c)
- if(cable.moisture.length > 0 && avg(cable.moisture) > 0){
- newlevel.airMoistures.push(cable.moisture[index] ?? 0)
- }
- }
- }
- levels.push(newlevel)
- }
-
- })
- })
- //flip the levels so that the highest nodes are on the top
- levels.reverse()
- return levels
- },[bin])
-
- const levelDisplay = (level: BinLevel, lastRow?: boolean) => {
- let tempDisplay = "--"
- let moistureDisplay = "--"
- let lvlTemp
- let lvlMoisture
- let tempColour: string = darken(theme.palette.background.paper, 0.1)
- let moistureColour: string = darken(theme.palette.background.paper, 0.1)
-
- //determine the temp
- if(level.grainTemps.length > 0){
- lvlTemp = avg(level.grainTemps)
- //if the average temp is 5 degrees above the bins threshold
- if(lvlTemp > (bin.targetTemp() + 10)){
- tempColour = warning
- }else if(lvlTemp > bin.targetTemp() + 5){
- //if the average temp is greater than 5 degrees over
- tempColour = caution
- }else {
- tempColour = inBounds
- }
- }else if (level.airTemps.length > 0){
- lvlTemp = avg(level.airTemps)
- }
- if (lvlTemp){
- if(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT){
- lvlTemp = celsiusToFahrenheit(lvlTemp)
- }
- tempDisplay = lvlTemp.toFixed(2)
- }
-
- //determine the moisture to show
- if(level.grainMoistures.length > 0){
- lvlMoisture = avg(level.grainMoistures)
- if(lvlMoisture > (bin.targetMoisture() + 1.5)){
- moistureColour = warning
- }else if(lvlMoisture > bin.targetMoisture() + 0.5){
- moistureColour = caution
- }else {
- moistureColour = inBounds
- }
- }else if (level.airMoistures.length > 0){
- lvlMoisture = avg(level.airMoistures)
- }
- if(lvlMoisture){
- moistureDisplay = lvlMoisture.toFixed(2)
- }
- return (
-
- {tempDisplay}
- {moistureDisplay}
-
- )
- }
-
- const update = () => {
- let temp = tempTarget
- if(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT){
- //convert to c
- temp = (temp - 32) * (5 / 9);
-
- }
- let clone = cloneDeep(bin)
- if(clone.settings.inventory && updateBinCallback){
- clone.settings.inventory.targetTemperature = temp
- clone.settings.inventory.targetMoisture = moistureTarget
- updateBinCallback(clone)
- }
- }
-
- const tempThreshDisplay = (hideUnit?: boolean) => {
- if(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT){
- return tempTarget.toFixed(2) + (hideUnit ? "" : " °F")
- }
- return tempTarget.toFixed(2) + (hideUnit ? "" : " °C")
- }
-
- const LegendSwatch = ({ color }: { color: string }) => (
-
- )
-
-
- const updateTempEntry = () => {
- return (
- {
- let val = parseFloat(e.target.value)
- if(!isNaN(val)){
- setTempTarget(val)
- }
- setTempEntry(e.target.value)
- }}
- />
- )
- }
-
- const updateMoistureEntry = () => {
- return (
- {
- let val = parseFloat(e.target.value)
- if(!isNaN(val)){
- setMoistureTarget(val)
- }
- setMoistureEntry(e.target.value)
- }}
- />
- )
- }
-
- const expandedTableDialog = () => {
- return (
- {setOpenExpandedCables(false)}}>
-
-
- {setOpenExpandedCables(false)}}>
- Close
-
-
-
- )
- }
-
-
-
- return (
-
- {expandedTableDialog()}
- {/* Level Table */}
-
- Grain Table
-
- {
- setOpenExpandedCables(true)
- }}>
- Show All Cables
-
-
-
-
-
-
-
- {/* this box is to make the whole table scrollable */}
-
-
-
-
- {/* "Targets" cell — hide on mobile when either field is being edited */}
- {!(isMobile && (enterTemp || enterMoisture)) && (
-
-
-
- {!isMobile &&
-
- Targets
-
- }
-
-
- )}
-
- {/* Temperature cell */}
- {!(isMobile && enterMoisture) && (
-
-
-
- {enterTemp ? updateTempEntry() : (
-
- {tempThreshDisplay(isMobile)}
-
- )}
- {enterTemp ? (
- { setEnterTemp(false); update(); }}>
-
-
- ) : (
- setEnterTemp(true)}>
-
-
- )}
-
-
- )}
- {/* Moisture cell — hide on mobile when temperature is being edited */}
- {!(isMobile && enterTemp) && (
-
-
-
- {enterMoisture ? updateMoistureEntry() : (
-
- {bin.targetMoisture()}
-
- )}
- {enterMoisture ? (
- { setEnterMoisture(false); update(); }}>
-
-
- ) : (
- setEnterMoisture(true)}>
-
-
- )}
-
-
- )}
-
-
-
- Level
-
-
- {isMobile ? "T" : "Temperature"}{(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " (°F)" : " (°C)")}
-
-
- EMC (%)
-
-
-
-
-
- {binLevels.map((level, i) => (
-
- {binLevels.length - i}
- {levelDisplay(level, i === binLevels.length-1)}
-
- ))}
-
-
-
- {/* Table Legend */}
-
-
-
- On target
-
-
-
- {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "~10 °F" : "+5 °C"} (0.5%) above
-
-
-
- {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "~20 °F" : "+10 °C"} (1.5%) above
-
-
-
- )
-}
\ No newline at end of file
diff --git a/src/bin/conditioning/modeChangeDialog.tsx b/src/bin/conditioning/modeChangeDialog.tsx
index ef2dfbd..b31c8f8 100644
--- a/src/bin/conditioning/modeChangeDialog.tsx
+++ b/src/bin/conditioning/modeChangeDialog.tsx
@@ -19,6 +19,7 @@ import { sameComponentID } from "pbHelpers/Component";
import moment from "moment";
import { lowerCase } from "lodash";
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
+import { fahrenheitToCelsius, getTemperatureUnit } from "utils";
import { GetGrainExtensionMap } from "grain";
import { PromiseProgress, Stage, Step as PromiseStep } from "common/PromiseProgress";
@@ -75,7 +76,7 @@ export default function ModeChangeDialog(props: Props){
changeOutdoorHumidity,
presets
} = props
- const [{as, user}] = useGlobalState()
+ const [{as}] = useGlobalState()
const [componentSets, setComponentSets] = useState([])
const interactionAPI = useInteractionsAPI()
const componentAPI = useComponentAPI();
@@ -288,24 +289,20 @@ if (!selectedDevice) return;
describeMeasurement(
quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
sensor.settings.type,
- sensor.settings.subtype,
- undefined,
- user
+ sensor.settings.subtype
).toStored(hum)
)
});
conditions.push(humidityCondition);
//since the measurement describers function to stored does a conversion into celsius if the users pref is fahrenheit for temperature we need to convert the preset value into fahrenheit
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
temp = Math.round((temp * (9 / 5) + 32) * 100) / 100;
}
let tempVal = describeMeasurement(
quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
sensor.settings.type,
- sensor.settings.subtype,
- undefined,
- user
+ sensor.settings.subtype
).toStored(temp);
let tempCondition = pond.InteractionCondition.create({
@@ -389,23 +386,19 @@ if (!selectedDevice) return;
value: describeMeasurement(
quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
sensor.settings.type,
- sensor.settings.subtype,
- undefined,
- user
+ sensor.settings.subtype
).toStored(humidityPreset)
});
conditions.push(fanConditionOne);
//since the measurement describers function toStored does a conversion into celsius for temperature if the users pref is fahrenheit we need to convert the preset value into fahrenheit
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
tempPreset = Math.round((tempPreset * (9 / 5) + 32) * 100) / 100;
}
let tempVal = describeMeasurement(
quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
sensor.settings.type,
- sensor.settings.subtype,
- undefined,
- user
+ sensor.settings.subtype
).toStored(tempPreset);
let fanConditionTwo = pond.InteractionCondition.create({
@@ -638,7 +631,7 @@ if (!selectedDevice) return;
InputProps={{
endAdornment: (
- {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
+ {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F"
: "℃"}
diff --git a/src/bin/graphs/BinComponentGraph.tsx b/src/bin/graphs/BinComponentGraph.tsx
index 07a9325..3c5a625 100644
--- a/src/bin/graphs/BinComponentGraph.tsx
+++ b/src/bin/graphs/BinComponentGraph.tsx
@@ -346,14 +346,14 @@ export default function BinComponentGraph(props: Props) {
? measurements?.map(um =>
areaGraph(
um,
- describeMeasurement(um.type, component.type(), component.subType(), undefined, user),
+ describeMeasurement(um.type, component.type(), component.subType()),
component.settings.smoothingAverages
)
)
: measurements?.map(um =>
lineGraph(
um,
- describeMeasurement(um.type, component.type(), component.subType(), undefined, user),
+ describeMeasurement(um.type, component.type(), component.subType()),
component.settings.smoothingAverages
)
)}
diff --git a/src/bin/graphs/BinGraphsVPD.tsx b/src/bin/graphs/BinGraphsVPD.tsx
index f4fe68c..037bf2d 100644
--- a/src/bin/graphs/BinGraphsVPD.tsx
+++ b/src/bin/graphs/BinGraphsVPD.tsx
@@ -4,7 +4,6 @@ import { GrainCable } from "models/GrainCable";
import { Plenum } from "models/Plenum";
import { UnitMeasurement } from "models/UnitMeasurement";
import moment from "moment";
-import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import React, { useEffect, useState } from "react";
import { avg } from "utils";
diff --git a/src/bin/graphs/BinLevelOverTime.tsx b/src/bin/graphs/BinLevelOverTime.tsx
index ed4de8d..5fe36bc 100644
--- a/src/bin/graphs/BinLevelOverTime.tsx
+++ b/src/bin/graphs/BinLevelOverTime.tsx
@@ -11,6 +11,7 @@ import { pond } from "protobuf-ts/pond";
import { useBinAPI, useGlobalState } from "providers";
import { useCallback, useEffect, useState } from "react";
import { Legend } from "recharts";
+import { getGrainUnit } from "utils";
import BinLevelAreaGraph from "./BinLevelAreaGraph";
interface Props {
@@ -31,7 +32,7 @@ interface InventoryAt {
export default function BinLevelOverTime(props: Props) {
const { bin, fertilizerBin, colour, startDate, endDate, customHeight } = props;
const binAPI = useBinAPI();
- const [{as, user}] = useGlobalState();
+ const [{as}] = useGlobalState();
const [inventoryData, setInventoryData] = useState([]);
const [dataLoading, setDataLoading] = useState(false);
const [capacity, setCapacity] = useState();
@@ -68,10 +69,10 @@ export default function BinLevelOverTime(props: Props) {
if (fertilizerBin && cap) {
cap = cap * 35.239;
}
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && cap) {
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && cap) {
cap = cap / bin.bushelsPerTonne();
}
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && cap) {
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && cap) {
cap = cap / (bin.bushelsPerTonne()*0.907);
}
setCapacity(cap);
@@ -88,10 +89,10 @@ export default function BinLevelOverTime(props: Props) {
let bushels = hist.settings.inventory.grainBushels ?? 0;
if (bushels !== lastBushels) {
let grainDisplay = bushels
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
grainDisplay = Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100;
}
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){
grainDisplay = Math.round((bushels / (bin.bushelsPerTonne()*0.907)) * 100) / 100;
}
let newData: InventoryAt = {
@@ -120,10 +121,10 @@ export default function BinLevelOverTime(props: Props) {
if (data.length === 0) {
let bushels = bin.bushels();
let grainDisplay = bushels
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
grainDisplay = Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100;
}
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){
grainDisplay = Math.round((bushels / (bin.bushelsPerTonne()*0.907)) * 100) / 100;
}
data.push({
@@ -155,10 +156,10 @@ export default function BinLevelOverTime(props: Props) {
if (fertilizerBin && cap) {
cap = cap * 35.239;
}
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && cap) {
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && cap) {
cap = cap / bin.bushelsPerTonne();
}
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && cap) {
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && cap) {
cap = cap / (bin.bushelsPerTonne()*0.907);
}
setCapacity(cap);
@@ -179,10 +180,10 @@ export default function BinLevelOverTime(props: Props) {
if (val.values[0] && m.timestamps[i]) {
if (lastBushels !== val.values[0]) {
let grainDisplay = val.values[0]
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
grainDisplay = Math.round((grainDisplay / bin.bushelsPerTonne()) * 100) / 100;
}
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){
grainDisplay = Math.round((grainDisplay / (bin.bushelsPerTonne()*0.907)) * 100) / 100;
}
autoBarData.push({
@@ -198,10 +199,10 @@ export default function BinLevelOverTime(props: Props) {
let bushels = bin.bushels();
let currentTime = moment().valueOf();
let grainDisplay = bushels
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
grainDisplay = Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100;
}
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){
grainDisplay = Math.round((bushels / (bin.bushelsPerTonne()*0.907)) * 100) / 100;
}
autoBarData.push({
diff --git a/src/bin/graphs/BinSensorGraph.tsx b/src/bin/graphs/BinSensorGraph.tsx
deleted file mode 100644
index 443e49e..0000000
--- a/src/bin/graphs/BinSensorGraph.tsx
+++ /dev/null
@@ -1,292 +0,0 @@
-import { Avatar, Box, Card, CircularProgress, IconButton, Paper, Typography, useTheme } from "@mui/material";
-import moment, { Moment } from "moment";
-import { Component } from "models";
-import { useComponentAPI, useGlobalState } from "providers";
-import { useEffect, useMemo, useState } from "react";
-import { UnitMeasurement } from "models/UnitMeasurement";
-import { quack } from "protobuf-ts/quack";
-import {
- Legend,
- Line,
- LineChart,
- ResponsiveContainer,
- Tooltip,
- TooltipProps,
- XAxis,
- YAxis
-} from "recharts";
-import { avg, roundTo } from "utils";
-import { MoreVert } from "@mui/icons-material";
-
-interface Props {
- device: number;
- icon?: string;
- title?: string;
- tag?: string;
- onMenuClick?: (event: React.MouseEvent) => void;
- component: Component;
- startDate: Moment;
- endDate: Moment;
- customHeight?: string | number;
- /**
- * this will cause all of the readings for a cable to be used in the average,
- * by default it will filter any excluded nodes and only use nodes below the top node
- */
- allNodes?: boolean
-}
-
-/** Grain-cable nodes to include when averaging (matches GrainCable.filter indexing). */
-function valuesForAveraging(
- nodeValues: number[],
- component: Component,
- allNodes?: boolean
-): number[] {
- if (allNodes) {
- return nodeValues.filter(v => !isNaN(v));
- }
-
- const grainFilledTo = component.settings.grainFilledTo ?? 0;
- const excludedNodes = component.settings.excludedNodes ?? [];
- const included: number[] = [];
-
- nodeValues.forEach((v, index) => {
- if (isNaN(v)) {
- return;
- }
- if (excludedNodes.includes(index)) {
- return;
- }
- if (grainFilledTo > 0 && index >= grainFilledTo) {
- return;
- }
- included.push(v);
- });
-
- return included;
-}
-
-interface MeasurementMeta {
- value: number;
- unit: string;
- label: string;
-}
-
-interface ChartPoint {
- timestamp: number;
- _meta: Record;
- [seriesKey: string]: number | Record | undefined;
-}
-
-interface SeriesInfo {
- key: string;
- colour: string;
- unit: string;
- label: string;
-}
-
-function normalizeValue(value: number, min: number, max: number): number {
- if (max === min) {
- return 50;
- }
- return ((value - min) / (max - min)) * 100;
-}
-
-function buildChartData(
- measurements: UnitMeasurement[],
- component: Component,
- allNodes?: boolean
-): {
- data: ChartPoint[];
- series: SeriesInfo[];
-} {
- const rawSeries = measurements
- .filter(um => um.timestamps.length > 0 && um.values.length > 0)
- .map(um => {
- const points: { timestamp: number; value: number }[] = [];
- um.values.forEach((valArray, i) => {
- if (valArray.error || !um.timestamps[i]) {
- return;
- }
- const nodeValues = valuesForAveraging(valArray.values, component, allNodes);
- if (nodeValues.length === 0) {
- return;
- }
- points.push({
- timestamp: moment(um.timestamps[i]).valueOf(),
- value: avg(nodeValues)
- });
- });
- const actualValues = points.map(p => p.value);
- const min = actualValues.length > 0 ? Math.min(...actualValues) : 0;
- const max = actualValues.length > 0 ? Math.max(...actualValues) : 100;
- return { um, points, min, max, key: um.label };
- })
- .filter(s => s.points.length > 0);
-
- const timestampMap = new Map();
- rawSeries.forEach(({ um, points, min, max, key }) => {
- points.forEach(({ timestamp, value }) => {
- let point = timestampMap.get(timestamp);
- if (!point) {
- point = { timestamp, _meta: {} };
- timestampMap.set(timestamp, point);
- }
- point[key] = normalizeValue(value, min, max);
- point._meta[key] = { value, unit: um.unit, label: um.label };
- });
- });
-
- const data = Array.from(timestampMap.values()).sort((a, b) => a.timestamp - b.timestamp);
- const series = rawSeries.map(({ um, key }) => ({
- key,
- colour: um.colour,
- unit: um.unit,
- label: um.label
- }));
-
- return { data, series };
-}
-
-function SensorGraphTooltip(props: TooltipProps) {
- const { active, label, payload } = props;
- if (!active || !payload?.length) {
- return null;
- }
-
- return (
-
-
- {payload.map(p => {
- const dataKey = String(p.dataKey);
- const meta = (p.payload as ChartPoint)._meta?.[dataKey];
- return (
-
- {meta?.label ?? dataKey}
- {": "}
-
- {meta ? `${roundTo(meta.value, 2)} ${meta.unit}` : p.value}
-
-
- );
- })}
-
- {moment(label).format("lll")}
-
-
-
- );
-}
-
-export default function BinSensorGraph(props: Props) {
- const { device, icon, title, tag, component, startDate, endDate, customHeight = 350, onMenuClick, allNodes } = props;
- const [measurements, setMeasurements] = useState([]);
- const [loading, setLoading] = useState(false);
- const componentAPI = useComponentAPI();
- const [{ user }] = useGlobalState();
- const theme = useTheme();
- const now = moment();
-
- useEffect(() => {
- setLoading(true);
- componentAPI
- .sampleUnitMeasurements(device, component.key(), startDate, endDate, 1000, undefined, undefined, undefined, undefined, true)
- .then(res => {
- setMeasurements(res.data.measurements.map(um => UnitMeasurement.any(um, user)));
- })
- .catch(() => {
- setMeasurements([]);
- })
- .finally(() => {
- setLoading(false);
- });
- }, [device, component, startDate, endDate, componentAPI, user]);
-
- const { data, series } = useMemo(
- () => buildChartData(measurements, component, allNodes),
- [measurements, component, allNodes]
- );
-
- return (
-
-
-
- {icon &&
-
- }
- {title}
- {tag && - {tag} }
-
- {onMenuClick &&
-
-
-
- }
-
-
- {loading ? (
-
-
-
- ) : data.length === 0 || series.length === 0 ? (
-
- No measurement data
-
- ) : (
-
-
- {
- const t = moment(timestamp);
- return now.isSame(t, "day") ? t.format("LT") : t.format("MMM DD");
- }}
- scale="time"
- type="number"
- tick={{ fill: theme.palette.text.primary }}
- stroke={theme.palette.divider}
- interval="preserveStartEnd"
- />
- {/* */}
-
-
- {series.map(s => (
-
- ))}
-
-
- )}
-
-
- );
-}
diff --git a/src/cableEstimator/binSVGs/sideView.tsx b/src/cableEstimator/binSVGs/sideView.tsx
index 907d40a..e3a1364 100644
--- a/src/cableEstimator/binSVGs/sideView.tsx
+++ b/src/cableEstimator/binSVGs/sideView.tsx
@@ -2,7 +2,6 @@ import { colors } from "@mui/material";
import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
import { useEffect, useState } from "react";
import { distanceConversion } from "utils";
-import { useGlobalState } from "providers";
interface Props {
bin: jsonBin;
@@ -23,8 +22,6 @@ export default function SideView(props: Props) {
const dimensionFontSize = 7;
const gapSize = svgViewBoxSize * 0.05;
const { bin } = props;
- const [{ user }] = useGlobalState();
- const distanceUnit = user.distanceUnit();
const [scale, setScale] = useState(0); //the scale to multiply the bin dimension by in order to determine how long to draw the line
const [hopperHeight, setHopperHeight] = useState(0);
const [roofHeight, setRoofHeight] = useState(0);
@@ -197,7 +194,7 @@ export default function SideView(props: Props) {
"bottomText",
svgViewBoxSize / 2,
y2,
- distanceConversion(bin.Diameter, distanceUnit).toFixed(1)
+ distanceConversion(bin.Diameter).toFixed(1)
)
);
@@ -234,7 +231,7 @@ export default function SideView(props: Props) {
let y2 = (roofHeight + bin.Sidewall) * scale;
d.push(
- dimensionText("sidewallText", x2, midpoint, distanceConversion(bin.Sidewall, distanceUnit).toFixed(1))
+ dimensionText("sidewallText", x2, midpoint, distanceConversion(bin.Sidewall).toFixed(1))
);
d.push(
dimensionPath(
@@ -273,7 +270,7 @@ export default function SideView(props: Props) {
"peakText",
xText,
midpoint,
- distanceConversion(bin.Sidewall + roofHeight, distanceUnit).toFixed(1)
+ distanceConversion(bin.Sidewall + roofHeight).toFixed(1)
)
);
d.push(
@@ -316,7 +313,7 @@ export default function SideView(props: Props) {
"totalText",
x2,
midpoint,
- distanceConversion(roofHeight + bin.Sidewall + hopperHeight, distanceUnit).toFixed(1)
+ distanceConversion(roofHeight + bin.Sidewall + hopperHeight).toFixed(1)
)
);
d.push(
diff --git a/src/cableEstimator/cableEstimator.tsx b/src/cableEstimator/cableEstimator.tsx
index 153b3f8..871868b 100644
--- a/src/cableEstimator/cableEstimator.tsx
+++ b/src/cableEstimator/cableEstimator.tsx
@@ -18,7 +18,7 @@ import {
//import MaterialTable from "material-table";
//import { getTableIcons } from "common/ResponsiveTable";
import CableDisplay from "./cableDisplay";
-import { distanceConversion } from "utils";
+import { distanceConversion, getDistanceUnit } from "utils";
import { pond } from "protobuf-ts/pond";
import CableQuote from "./cableQuote";
import { useMobile } from "hooks";
@@ -33,7 +33,6 @@ import {
} from "common/TrigFunctions";
import ResponsiveTable, { Column } from "common/ResponsiveTable";
import { makeStyles } from "@mui/styles";
-import { useGlobalState } from "providers";
const useStyles = makeStyles(() => ({
sliderThumb: {
@@ -57,7 +56,6 @@ export default function CableEstimator() {
//the conversion constant to convert cubic feet to bushels
const conversionConstantFT = 0.7786;
const classes = useStyles();
- const [{ user }] = useGlobalState();
const [binOptions, setBinOptions] = useState([]);
const [displayedRows, setDisplayedRows] = useState([]);
const [tablePage, setTablePage] = useState(0)
@@ -69,21 +67,21 @@ export default function CableEstimator() {
const [quoteOpen, setQuoteOpen] = useState(false);
const [useCustomBin, setUseCustomBin] = useState(false);
const [diameterFormEntry, setDiameterFormEntry] = useState(
- user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "9.1" : "30"
+ getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "9.1" : "30"
);
//const [capacityFormEntry, setCapacityFormEntry] = useState("0");
const [ringsFormEntry, setRingsFormEntry] = useState("0");
const [ringHeightFormEntry, setRingHeightFormEntry] = useState("0");
const [ringHeightFt, setRingHeightFt] = useState(0);
const [sidewallFormEntry, setSidewallFormEntry] = useState(
- user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "7" : "23"
+ getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "7" : "23"
);
const [peakFormEntry, setPeakFormEntry] = useState(
- user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "9.1" : "30"
+ getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "9.1" : "30"
);
//const [eaveToPeakFormEntry, setEaveToPeakFormEntry] = useState("0");
const [roofAngleFormEntry, setRoofAngleFormEntry] = useState(
- user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "7.6" : "25"
+ getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "7.6" : "25"
);
const [hopperAngleFormEntry, setHopperAngleFormEntry] = useState("0");
const nodeSpacing = 4;
@@ -131,40 +129,24 @@ export default function CableEstimator() {
render: (row: jsonBin) => {row.HopperAngle}
},
{
- title: "Diameter " + (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
+ title: "Diameter " + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
sortKey: "Diameter",
- render: (row: jsonBin) => (
-
- {distanceConversion(row.Diameter, user.distanceUnit()).toFixed(2)}
-
- )
+ render: (row: jsonBin) => {distanceConversion(row.Diameter).toFixed(2)}
},
{
- title: "Sidewall" + (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
+ title: "Sidewall" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
sortKey: "Sidewall",
- render: (row: jsonBin) => (
-
- {distanceConversion(row.Sidewall, user.distanceUnit()).toFixed(2)}
-
- )
+ render: (row: jsonBin) => {distanceConversion(row.Sidewall).toFixed(2)}
},
{
- title: "Peak Height" + (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
+ title: "Peak Height" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
sortKey: "Peak",
- render: (row: jsonBin) => (
-
- {distanceConversion(row.Peak, user.distanceUnit()).toFixed(2)}
-
- )
+ render: (row: jsonBin) => {distanceConversion(row.Peak).toFixed(2)}
},
{
- title: "Eave To Peak" + (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
+ title: "Eave To Peak" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
sortKey: "EaveToPeak",
- render: (row: jsonBin) => (
-
- {distanceConversion(row.EaveToPeak, user.distanceUnit()).toFixed(2)}
-
- )
+ render: (row: jsonBin) => {distanceConversion(row.EaveToPeak).toFixed(2)}
},
{
title: "Roof Angle",
@@ -563,7 +545,7 @@ export default function CableEstimator() {
InputProps={{
endAdornment: (
- {user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"}
+ {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"}
)
}}
@@ -572,7 +554,7 @@ export default function CableEstimator() {
onChange={e => {
setDiameterFormEntry(e.target.value);
let val = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value);
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
val = val * 3.281;
}
//when the diameter is changed use the current peak height to adjust the roof angle
@@ -595,7 +577,7 @@ export default function CableEstimator() {
InputProps={{
endAdornment: (
- {user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"}
+ {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"}
)
}}
@@ -607,7 +589,7 @@ export default function CableEstimator() {
onChange={e => {
setPeakFormEntry(e.target.value);
let val = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value);
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
val = val * 3.281;
}
//when the peak height is changed use the diameter to calculate the roof angle
@@ -639,7 +621,7 @@ export default function CableEstimator() {
//use the new number of rings and ring height to get the sidewall height
let sidewallFt = val * ringHeightFt;
bin.Sidewall = sidewallFt;
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
setSidewallFormEntry((sidewallFt / 3.281).toString());
} else {
setSidewallFormEntry(sidewallFt.toString());
@@ -668,7 +650,7 @@ export default function CableEstimator() {
onChange={e => {
setRingHeightFormEntry(e.target.value);
let inVal = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value);
- // if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ // if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
// val = val * 3.281;
// }
let ringFt = inVal / 12;
@@ -677,7 +659,7 @@ export default function CableEstimator() {
let bin = cloneDeep(customBin);
let sidewallFt = bin.Rings * ringFt;
bin.Sidewall = sidewallFt;
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
setSidewallFormEntry((sidewallFt / 3.281).toString());
} else {
setSidewallFormEntry(sidewallFt.toString());
@@ -701,7 +683,7 @@ export default function CableEstimator() {
InputProps={{
endAdornment: (
- {user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"}
+ {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"}
)
}}
@@ -712,7 +694,7 @@ export default function CableEstimator() {
setSidewallFormEntry(e.target.value);
let val = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value);
//if the value entered is meters convert it to feet for the bin
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
val = val * 3.281;
}
let bin = cloneDeep(customBin);
@@ -751,7 +733,7 @@ export default function CableEstimator() {
let coneHeight = calcConeHeight(bin.Diameter / 2, bin.RoofAngle);
bin.Peak = bin.Sidewall + coneHeight; //assign it as feet
let peakHeightDisplay = bin.Sidewall + coneHeight;
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
peakHeightDisplay = peakHeightDisplay / 3.281;
}
setPeakFormEntry(peakHeightDisplay.toFixed(1));
@@ -780,7 +762,7 @@ export default function CableEstimator() {
let coneHeight = calcConeHeight(bin.Diameter / 2, bin.RoofAngle);
bin.Peak = bin.Sidewall + coneHeight;
let peakHeightDisplay = bin.Sidewall + coneHeight;
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
peakHeightDisplay = peakHeightDisplay / 3.281;
}
setPeakFormEntry(peakHeightDisplay.toFixed(1));
@@ -905,7 +887,7 @@ export default function CableEstimator() {
setEaveToPeakFormEntry(e.target.value);
let val = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value);
//if the value entered is meters convert it to feet for the bin
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
val = val * 3.281;
}
let bin = cloneDeep(customBin);
diff --git a/src/cableEstimator/cableMounting.tsx b/src/cableEstimator/cableMounting.tsx
index 3ac6f0f..fa14289 100644
--- a/src/cableEstimator/cableMounting.tsx
+++ b/src/cableEstimator/cableMounting.tsx
@@ -4,8 +4,8 @@ import ResponsiveTable, { Column } from "common/ResponsiveTable";
//import { getTableIcons } from "common/ResponsiveTable";
//import MaterialTable from "material-table";
import { pond } from "protobuf-ts/pond";
-import { distanceConversion } from "utils";
-import { useGlobalState } from "providers";
+import { useEffect } from "react";
+import { distanceConversion, getDistanceUnit } from "utils";
interface Props {
bin: jsonBin;
@@ -13,7 +13,6 @@ interface Props {
export default function CableMounting(props: Props) {
const { bin } = props;
- const [{ user }] = useGlobalState();
// const [tablePage, setTablePage] = useState(0)
// const [pageSize, setPageSize] = useState(10)
@@ -27,19 +26,15 @@ export default function CableMounting(props: Props) {
render: (row: CableSum) => {row.Orbit === 0 ? "Center" : row.Orbit}
},
{
- title: "From Center " + (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
+ title: "From Center " + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
render: (row: CableSum) => (
-
- {distanceConversion(row.DistanceFromCenter, user.distanceUnit()).toFixed(2)}
-
+ {distanceConversion(row.DistanceFromCenter).toFixed(2)}
)
},
{
- title: "Along Roof" + (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
+ title: "Along Roof" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
render: (row: CableSum) => (
-
- {distanceConversion(roofDist(row.DistanceFromCenter), user.distanceUnit()).toFixed(2)}
-
+ {distanceConversion(roofDist(row.DistanceFromCenter)).toFixed(2)}
)
},
{
diff --git a/src/cableEstimator/cableQuote.tsx b/src/cableEstimator/cableQuote.tsx
index 5f45b70..ae37870 100644
--- a/src/cableEstimator/cableQuote.tsx
+++ b/src/cableEstimator/cableQuote.tsx
@@ -5,7 +5,6 @@ import ResponsiveDialog from "common/ResponsiveDialog";
import { useMobile } from "hooks";
import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
import PdfContent from "./pdfComponents/pdfContent";
-import { useGlobalState } from "providers";
interface Props {
bin: jsonBin;
@@ -15,7 +14,6 @@ interface Props {
export default function CableQuote(props: Props) {
const { open, close, bin } = props;
- const [{user}] = useGlobalState();
const isMobil = useMobile();
const pdfStyle = StyleSheet.create({
viewerDesktop: {
@@ -29,11 +27,7 @@ export default function CableQuote(props: Props) {
});
const pdfDoc = () => {
- return (
- }
- />
- );
+ return } />;
};
return (
diff --git a/src/cableEstimator/cableTable.tsx b/src/cableEstimator/cableTable.tsx
index cdc68f3..ee73c55 100644
--- a/src/cableEstimator/cableTable.tsx
+++ b/src/cableEstimator/cableTable.tsx
@@ -3,8 +3,7 @@ import { Box, Typography } from "@mui/material";
//import MaterialTable from "material-table";
import { pond } from "protobuf-ts/pond";
import { useEffect, useState } from "react";
-import { distanceConversion } from "utils";
-import { useGlobalState } from "providers";
+import { distanceConversion, getDistanceUnit } from "utils";
import { Cable, jsonBin } from "common/DataImports/BinCables/BinCableImporter";
import { cloneDeep } from "lodash";
import ResponsiveTable, { Column } from "common/ResponsiveTable";
@@ -16,19 +15,17 @@ interface Props {
export default function CableTable(props: Props) {
const { bin, /*cablesChanged*/ } = props;
- const [{ user }] = useGlobalState();
//const nodeSpacing = 4; //space between the nodes in feet
const [cableData, setCableData] = useState(bin.Cables);
useEffect(() => {
let convertedCables: Cable[] = cloneDeep(bin.Cables);
- const du = user.distanceUnit();
convertedCables.forEach(cable => {
- cable.Length = Math.round(distanceConversion(cable.Length, du) * 100) / 100;
+ cable.Length = Math.round(distanceConversion(cable.Length) * 100) / 100;
});
setCableData(convertedCables);
- }, [bin, user]);
+ }, [bin]);
// const table = () => {
// return (
@@ -119,7 +116,7 @@ export default function CableTable(props: Props) {
render: (row) => {row.Count}
},
{
- title: "Length" + (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
+ title: "Length" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
render: (row) => {row.Length}
},
{
diff --git a/src/cableEstimator/pdfComponents/cableTable/cableTableHeader.tsx b/src/cableEstimator/pdfComponents/cableTable/cableTableHeader.tsx
index 806220a..79f95ad 100644
--- a/src/cableEstimator/pdfComponents/cableTable/cableTableHeader.tsx
+++ b/src/cableEstimator/pdfComponents/cableTable/cableTableHeader.tsx
@@ -1,9 +1,9 @@
import { StyleSheet, Text, View } from "@react-pdf/renderer";
import { pond } from "protobuf-ts/pond";
+import { getDistanceUnit } from "utils";
interface Props {
borderColour: string;
- distanceUnit: pond.DistanceUnit;
}
export default function CableTableHeader(props: Props) {
@@ -35,8 +35,7 @@ export default function CableTableHeader(props: Props) {
Type
Qty
- Length{" "}
- {props.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"}
+ Length {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"}
Nodes
diff --git a/src/cableEstimator/pdfComponents/cableTable/cableTableRow.tsx b/src/cableEstimator/pdfComponents/cableTable/cableTableRow.tsx
index 3a22ba4..8e80d6a 100644
--- a/src/cableEstimator/pdfComponents/cableTable/cableTableRow.tsx
+++ b/src/cableEstimator/pdfComponents/cableTable/cableTableRow.tsx
@@ -2,12 +2,10 @@ import { StyleSheet, Text, View } from "@react-pdf/renderer";
import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
import React from "react";
import { distanceConversion } from "utils";
-import { pond } from "protobuf-ts/pond";
interface Props {
bin: jsonBin;
borderColour: string;
- distanceUnit: pond.DistanceUnit;
}
export default function CableTableRow(props: Props) {
@@ -50,9 +48,7 @@ export default function CableTableRow(props: Props) {
{cable.Orbit === 0 ? "Center" : cable.Orbit}
{cable.Type === 2 ? "Moisture" : "Temperature"}
{cable.Count}
-
- {distanceConversion(cable.Length, props.distanceUnit).toFixed(2)}
-
+ {distanceConversion(cable.Length).toFixed(2)}
{cable.Nodes}
);
diff --git a/src/cableEstimator/pdfComponents/mountingTable/mountingTableHeader.tsx b/src/cableEstimator/pdfComponents/mountingTable/mountingTableHeader.tsx
index 67046c1..a7708dd 100644
--- a/src/cableEstimator/pdfComponents/mountingTable/mountingTableHeader.tsx
+++ b/src/cableEstimator/pdfComponents/mountingTable/mountingTableHeader.tsx
@@ -1,9 +1,9 @@
import { StyleSheet, Text, View } from "@react-pdf/renderer";
import { pond } from "protobuf-ts/pond";
+import { getDistanceUnit } from "utils";
interface Props {
borderColour: string;
- distanceUnit: pond.DistanceUnit;
}
export default function MountingTableHeader(props: Props) {
@@ -43,12 +43,10 @@ export default function MountingTableHeader(props: Props) {
Orbit
- Radius{" "}
- {props.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"}
+ Radius {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"}
- Along Roof{" "}
- {props.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"}
+ Along Roof {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"}
Bracket
diff --git a/src/cableEstimator/pdfComponents/mountingTable/mountingTableRows.tsx b/src/cableEstimator/pdfComponents/mountingTable/mountingTableRows.tsx
index 2824de6..b86f1d1 100644
--- a/src/cableEstimator/pdfComponents/mountingTable/mountingTableRows.tsx
+++ b/src/cableEstimator/pdfComponents/mountingTable/mountingTableRows.tsx
@@ -2,12 +2,10 @@ import { StyleSheet, Text, View } from "@react-pdf/renderer";
import { getBracket, jsonBin } from "common/DataImports/BinCables/BinCableImporter";
import React from "react";
import { distanceConversion } from "utils";
-import { pond } from "protobuf-ts/pond";
interface Props {
bin: jsonBin;
borderColour: string;
- distanceUnit: pond.DistanceUnit;
}
export default function MountingTableRow(props: Props) {
@@ -67,10 +65,10 @@ export default function MountingTableRow(props: Props) {
key={i}>
{orbit.Orbit === 0 ? "C" : orbit.Orbit}
- {distanceConversion(orbit.DistanceFromCenter, props.distanceUnit).toFixed(2)}
+ {distanceConversion(orbit.DistanceFromCenter).toFixed(2)}
- {distanceConversion(roofDist(orbit.DistanceFromCenter), props.distanceUnit).toFixed(2)}
+ {distanceConversion(roofDist(orbit.DistanceFromCenter)).toFixed(2)}
{bracketType}
diff --git a/src/cableEstimator/pdfComponents/pdfContent.tsx b/src/cableEstimator/pdfComponents/pdfContent.tsx
index c5bbb8b..bce1f15 100644
--- a/src/cableEstimator/pdfComponents/pdfContent.tsx
+++ b/src/cableEstimator/pdfComponents/pdfContent.tsx
@@ -1,7 +1,6 @@
import { StyleSheet, Text, View } from "@react-pdf/renderer";
import { colors } from "@mui/material";
import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
-import { pond } from "protobuf-ts/pond";
import CableTableHeader from "./cableTable/cableTableHeader";
import CableTableRow from "./cableTable/cableTableRow";
import PDFSideView from "./pdfSVG/pdfSideView";
@@ -11,11 +10,9 @@ import MountingTableRow from "./mountingTable/mountingTableRows";
interface Props {
bin: jsonBin;
- distanceUnit: pond.DistanceUnit;
}
export default function PdfContent(props: Props) {
- const { distanceUnit } = props;
const tableBorderColour = colors.grey[800];
const styles = StyleSheet.create({
@@ -56,7 +53,7 @@ export default function PdfContent(props: Props) {
-
+
@@ -66,12 +63,8 @@ export default function PdfContent(props: Props) {
Install Guide
-
-
+
+
@@ -80,12 +73,8 @@ export default function PdfContent(props: Props) {
Recommended Cables
-
-
+
+
);
diff --git a/src/cableEstimator/pdfComponents/pdfSVG/pdfSideView.tsx b/src/cableEstimator/pdfComponents/pdfSVG/pdfSideView.tsx
index 3a38a9c..cad1209 100644
--- a/src/cableEstimator/pdfComponents/pdfSVG/pdfSideView.tsx
+++ b/src/cableEstimator/pdfComponents/pdfSVG/pdfSideView.tsx
@@ -1,13 +1,11 @@
import { colors } from "@mui/material";
import { Circle, Line, Path, Svg, Text } from "@react-pdf/renderer";
import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
-import { pond } from "protobuf-ts/pond";
import { useEffect, useState } from "react";
import { distanceConversion } from "utils";
interface Props {
bin: jsonBin;
- distanceUnit: pond.DistanceUnit;
}
interface svgPoint {
@@ -24,7 +22,7 @@ export default function PDFSideView(props: Props) {
const nodeSpacing = 4; //space in feet between the nodes
const dimensionFontSize = 18;
const gapSize = svgViewBoxSize * 0.05;
- const { bin, distanceUnit } = props;
+ const { bin } = props;
const [scale, setScale] = useState(0); //the scale to multiply the bin dimension by in order to determine how long to draw the line
const [hopperHeight, setHopperHeight] = useState(0);
const [roofHeight, setRoofHeight] = useState(0);
@@ -196,7 +194,7 @@ export default function PDFSideView(props: Props) {
"bottomText",
svgViewBoxSize / 2,
y2,
- distanceConversion(bin.Diameter, distanceUnit).toFixed(1)
+ distanceConversion(bin.Diameter).toFixed(1)
)
);
@@ -233,12 +231,7 @@ export default function PDFSideView(props: Props) {
let y2 = (roofHeight + bin.Sidewall) * scale;
d.push(
- dimensionText(
- "sidewallText",
- x2,
- midpoint,
- distanceConversion(bin.Sidewall, distanceUnit).toFixed(1)
- )
+ dimensionText("sidewallText", x2, midpoint, distanceConversion(bin.Sidewall).toFixed(1))
);
d.push(
dimensionPath(
@@ -277,7 +270,7 @@ export default function PDFSideView(props: Props) {
"peakText",
xText,
midpoint,
- distanceConversion(bin.Sidewall + roofHeight, distanceUnit).toFixed(1)
+ distanceConversion(bin.Sidewall + roofHeight).toFixed(1)
)
);
d.push(
@@ -320,7 +313,7 @@ export default function PDFSideView(props: Props) {
"totalText",
x2,
midpoint,
- distanceConversion(roofHeight + bin.Sidewall + hopperHeight, distanceUnit).toFixed(1)
+ distanceConversion(roofHeight + bin.Sidewall + hopperHeight).toFixed(1)
)
);
d.push(
diff --git a/src/charts/MeasurementsChart.tsx b/src/charts/MeasurementsChart.tsx
index c4a5d8b..1baeea6 100644
--- a/src/charts/MeasurementsChart.tsx
+++ b/src/charts/MeasurementsChart.tsx
@@ -67,7 +67,7 @@ export default function MeasurementsChart(props: Props) {
const [currentMax, setCurrentMax] = useState("dataMax");
const [newChart, setNewChart] = useState(false);
const [xDomain, setXDomain] = useState(["dataMin", "dataMax"]);
- const [{ showErrors, user }, dispatch] = useGlobalState();
+ const [{ showErrors }, dispatch] = useGlobalState();
const isMobile = useMobile();
// const [zoomStart, setZoomStart] = useState();
// const [zoomEnd, setZoomEnd] = useState();
@@ -123,7 +123,7 @@ export default function MeasurementsChart(props: Props) {
if (filters?.selectedInteractions?.includes(interaction.key())) {
interaction.settings.conditions.forEach((condition, j) => {
if (condition.measurementType === measurementType) {
- let describer = describeMeasurement(condition.measurementType, undefined, undefined, undefined, user);
+ let describer = describeMeasurement(condition.measurementType);
selectedInteractions.push(
void;
colourAboveZero?: ColourData
colourBelowZero?: ColourData
- minY?: string | number
- maxY?: string | number
}
export default function SingleSetAreaChart(props: Props) {
- const { data, maxRef, minRef, newXDomain, multiGraphZoom, yAxisLabel, colourAboveZero, colourBelowZero, tooltipLabel, tooltipUnit, minY, maxY } = props;
+ const { data, maxRef, minRef, newXDomain, multiGraphZoom, yAxisLabel, colourAboveZero, colourBelowZero, tooltipLabel, tooltipUnit } = props;
const [xDomain, setXDomain] = useState(["dataMin", "dataMax"]);
const [refLeft, setRefLeft] = useState();
const [refRight, setRefRight] = useState();
@@ -154,8 +152,7 @@ export default function SingleSetAreaChart(props: Props) {
/>
{
})
interface Props {
- parent: string;
- parentType: string;
+ objectKey: string;
type?: pond.NoteType;
}
export default function Chat(props: Props) {
const [chats, setChats] = useState([]);
- const { parent, parentType, type } = props;
+ const { objectKey, type } = props;
const noteAPI = useNoteAPI();
const [loaded, setLoaded] = useState(false);
const [loading, setLoading] = useState(false);
@@ -39,7 +38,7 @@ export default function Chat(props: Props) {
const loadChats = () => {
setLoading(true)
// console.log("listing chats?")
- noteAPI.listChats(10, chats.length, "desc", "timestamp", parent, [parent], [parentType]).then(resp => {
+ noteAPI.listChats(10, chats.length, "desc", "timestamp", objectKey).then(resp => {
setChats(resp.data.chats ? resp.data.chats.reverse().concat(chats) : [])
setTotalMessages(resp.data.total)
}).finally(() => {
@@ -82,13 +81,13 @@ export default function Chat(props: Props) {
setLoaded(false);
setLoading(false);
setTotalMessages(0);
- }, [parent]);
+ }, [objectKey]);
useEffect(() => {
if (chats.length === 0 && !loaded) {
loadChats();
}
- }, [parent, chats, loaded]);
+ }, [objectKey, chats, loaded]);
const loadMore = () => {
loadChats();
@@ -112,7 +111,7 @@ export default function Chat(props: Props) {
-
+
);
diff --git a/src/chat/ChatDrawer.tsx b/src/chat/ChatDrawer.tsx
index 01eebcd..8394d69 100644
--- a/src/chat/ChatDrawer.tsx
+++ b/src/chat/ChatDrawer.tsx
@@ -7,7 +7,7 @@ import Chat from "./Chat";
import { pond } from "protobuf-ts/pond";
import { useEffect, useState } from "react";
import { useTeamAPI } from "providers";
-import { closeCrispChat, isCrispEnabled, openCrispChat } from './CrispChat';
+import { closeCrispChat, openCrispChat } from './CrispChat';
import RobotIcon from "products/CommonIcons/robotIcon";
const useStyles = makeStyles((theme: Theme) => ({
@@ -68,7 +68,7 @@ export function ChatDrawer(props: Props) {
}
useEffect(() => {
- if (open && isCrispEnabled()) {
+ if (open) {
closeCrispChat()
}
}, [open])
@@ -108,15 +108,13 @@ export function ChatDrawer(props: Props) {
- {isCrispEnabled() && (
-
-
-
-
-
- )}
+
+
+
+
+
{teams.map((t, i)=> {
if (t.settings?.key === team.key()) return null;
@@ -145,7 +143,7 @@ export function ChatDrawer(props: Props) {
{selectedTeam.name()} Chat
-
+
diff --git a/src/chat/ChatInput.tsx b/src/chat/ChatInput.tsx
index db70fde..a3753b8 100644
--- a/src/chat/ChatInput.tsx
+++ b/src/chat/ChatInput.tsx
@@ -40,9 +40,8 @@ const useStyles = makeStyles((theme: Theme) => ({
}))
interface Props {
- parent: string;
+ objectKey: string;
newNoteMethod: (note: Note) => void;
- parentType: string; // the object type the note is for ie "bin", "team" etc.
type?: pond.NoteType;
}
@@ -53,13 +52,13 @@ interface Props {
export default function ChatInput(props: Props) {
const [message, setMessage] = useState("");
const classes = useStyles();
- const { parent, type, parentType } = props;
+ const { objectKey, type } = props;
const [{ user }] = useGlobalState();
const noteAPI = useNoteAPI();
const { openSnack } = useSnackbar();
const [shift, setShift] = useState(false);
const theme = useTheme();
- // const [uploadedFiles, setUploadedFiles] = useState>(new Map());
+ const [uploadedFiles, setUploadedFiles] = useState>(new Map());
const clearEntry = () => {
setMessage("");
@@ -94,13 +93,13 @@ export default function ChatInput(props: Props) {
}
valid ? submit() : openSnack("Invalid Message");
clearEntry();
- // setUploadedFiles(new Map());
+ setUploadedFiles(new Map());
};
const submit = () => {
if (message !== "" && message !== "\n") {
let newNote = Note.create();
- newNote.settings.objectKey = parent;
+ newNote.settings.objectKey = objectKey;
newNote.settings.userId = user.id();
if (type) {
newNote.settings.objectType = type;
@@ -108,14 +107,13 @@ export default function ChatInput(props: Props) {
newNote.settings.timestamp = Date.now();
newNote.settings.content = message;
noteAPI
- .addNote(newNote.settings, undefined, [parent], [parentType])
+ .addNote(newNote.settings, Array.from(uploadedFiles.keys()))
.then(resp => {
newNote.settings.key = resp.data.note
props.newNoteMethod(newNote);
openSnack("Message Sent");
})
.catch(_err => {
- console.error(_err)
openSnack("Message Failed to send");
});
}
@@ -164,7 +162,7 @@ export default function ChatInput(props: Props) {
- {/* {Array.from(uploadedFiles.values()).toString()} */}
+ {Array.from(uploadedFiles.values()).toString()}
);
}
diff --git a/src/chat/ChatOutput.tsx b/src/chat/ChatOutput.tsx
index 765809e..198690b 100644
--- a/src/chat/ChatOutput.tsx
+++ b/src/chat/ChatOutput.tsx
@@ -24,11 +24,11 @@ export default function ChatOutput(props: Props) {
const ScrollToBottom = () => {
const elementRef = useRef(null);
- useEffect(() => {
+ useEffect(() =>
elementRef?.current?.scrollIntoView({
block: "end"
- });
- });
+ })
+ );
let elem =
;
return elem;
};
diff --git a/src/chat/CrispChat.ts b/src/chat/CrispChat.ts
index fe9fc16..a0220fb 100644
--- a/src/chat/CrispChat.ts
+++ b/src/chat/CrispChat.ts
@@ -5,11 +5,6 @@ const ANIMATION_DURATION_MS = 300;
let initialized = false;
-export function isCrispEnabled(): boolean {
- const id = import.meta.env.VITE_CRISP_WEBSITE_ID;
- return typeof id === "string" && id.trim() !== "";
-}
-
/**
* Initialize Crisp and immediately hide the default chat button.
* Call this once on app load (e.g., in UserWrapper after user data is ready).
@@ -22,7 +17,6 @@ export function initCrisp(opts: {
tokenId?: string;
}) {
if (initialized) return;
- if (!opts.websiteId?.trim()) return;
Crisp.configure(opts.websiteId);
Crisp.session.reset();
@@ -77,7 +71,6 @@ function injectCrispStyles() {
* Safe to call from any onClick handler.
*/
export function openCrispChat() {
- if (!initialized) return;
const chatbox = document.getElementById("crisp-chatbox");
if (chatbox) {
chatbox.classList.add("crisp-visible");
@@ -93,7 +86,6 @@ export function openCrispChat() {
* Close the chat window and hide the widget.
*/
export function closeCrispChat() {
- if (!initialized) return;
Crisp.chat.close();
hideCrispChatButton();
}
diff --git a/src/common/PendingChangesChip.tsx b/src/common/PendingChangesChip.tsx
deleted file mode 100644
index f2b393a..0000000
--- a/src/common/PendingChangesChip.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { Chip, CircularProgress, Tooltip } from "@mui/material";
-import { CheckCircleOutline } from "@mui/icons-material";
-
-interface Props {
- pending: boolean;
- accepted: boolean;
- size?: "small" | "medium";
-}
-
-/**
- * Capsule tag shown alongside the other status chips while changes are
- * waiting to be synced to a device or component. Shows a small loading
- * circle while pending and a checkmark once the change is accepted.
- * Derive the flags with usePendingChanges.
- */
-export default function PendingChangesChip(props: Props) {
- const { pending, accepted, size } = props;
-
- if (pending) {
- return (
-
- }
- size={size}
- />
-
- );
- }
-
- if (accepted) {
- return (
-
- }
- size={size}
- />
-
- );
- }
-
- return null;
-}
diff --git a/src/common/PendingChangesIndicator.tsx b/src/common/PendingChangesIndicator.tsx
deleted file mode 100644
index 2d3ff59..0000000
--- a/src/common/PendingChangesIndicator.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { CircularProgress, Grid2 as Grid } from "@mui/material";
-import { CheckCircleOutline } from "@mui/icons-material";
-
-interface Props {
- pending: boolean;
- accepted: boolean;
- text: string;
-}
-
-/**
- * Small inline status line used inside cards: a loading circle beside the
- * caption while changes are pending, and a checkmark once they are accepted.
- * Derive the flags with usePendingChanges (or per-item tracking like
- * InteractionsOverview does).
- */
-export default function PendingChangesIndicator(props: Props) {
- const { pending, accepted, text } = props;
- if (!pending && !accepted) return null;
-
- return (
-
- {pending && }
- {accepted && }
- {text}
-
- );
-}
diff --git a/src/common/ResponsiveTable.tsx b/src/common/ResponsiveTable.tsx
index af03c5b..4c21901 100644
--- a/src/common/ResponsiveTable.tsx
+++ b/src/common/ResponsiveTable.tsx
@@ -235,11 +235,6 @@ export default function ResponsiveTable(props: Props) {
localStorage.setItem(filterKey, JSON.stringify(filterList));
}, [filterList])
- const headerColSpan =
- (columns?.length ?? ((rows?.[0] && Object.keys(rows[0]).length) || 0)) +
- (rowSelect ? 1 : 0) +
- (renderGutter ? 1 : 0);
-
const openColumnMenu = (event: any) => {
setColumnAnchor(event.currentTarget);
// setUserMenuIsOpen(true);
@@ -619,7 +614,7 @@ export default function ResponsiveTable(props: Props) {
{customElement &&
-
+
{customElement}
diff --git a/src/common/StatusChip.tsx b/src/common/StatusChip.tsx
new file mode 100644
index 0000000..c1b16b3
--- /dev/null
+++ b/src/common/StatusChip.tsx
@@ -0,0 +1,20 @@
+import { Chip, Tooltip } from "@mui/material";
+import { getStatusHelper } from "pbHelpers/Status";
+
+interface Props {
+ status: string;
+ size?: "small" | "medium";
+}
+
+export default function StatusChip(props: Props) {
+ const { status, size } = props;
+ const statusHelper = getStatusHelper(status);
+ const icon = statusHelper.icon;
+ const description = statusHelper.description;
+
+ return icon !== null ? (
+
+
+
+ ) : null;
+}
diff --git a/src/common/StatusDust.tsx b/src/common/StatusDust.tsx
index b45e930..c157501 100644
--- a/src/common/StatusDust.tsx
+++ b/src/common/StatusDust.tsx
@@ -5,6 +5,7 @@ import { Device } from "models";
import PulseBox from "./PulseBox";
import RelativeTimestamp from "./RelativeTimestamp";
import { or } from "utils";
+import { useEffect, useState } from "react";
interface Props {
device: Device;
@@ -22,47 +23,56 @@ const useStyles = makeStyles((theme: Theme) => {
marginRight: "auto",
margin: theme.spacing(1),
width: theme.spacing(16),
- minHeight: theme.spacing(11),
- },
- readingsWrapper: {
- position: "relative",
- width: "100%",
- minHeight: theme.spacing(10),
+ height: theme.spacing(11),
},
carouselContainer: {
- position: "relative",
- height: "40px",
- overflow: "hidden",
+ position: 'relative',
+ height: '40px',
+ overflow: 'hidden',
textAlign: "center",
},
carouselItem: {
opacity: 0,
- transform: "translateX(100%)",
- transition: "opacity 0.5s ease, transform 0.5s ease",
- position: "absolute",
- width: "100%",
+ transform: 'translateX(100%)',
+ transition: 'opacity 0.5s ease, transform 0.5s ease',
+ position: 'absolute',
+ width: '100%',
overflow: "hidden",
+ // height: '100%',
},
active: {
opacity: 1,
- transform: "translateX(0)",
+ transform: 'translateX(0)',
},
inactive: {
- transform: "translateX(-100%)",
+ transform: 'translateX(-100%)',
},
- });
-});
+ })
+})
export default function StatusDust(props: Props) {
const { device, showTimestamp } = props;
- const classes = useStyles();
+ const classes = useStyles()
- const colors = or(device.status.sen5x?.overlays.map((overlay) => overlay.color), []);
- const messages = or(device.status.sen5x?.overlays.map((overlay) => overlay.message), []);
+ const colors = or(device.status.sen5x?.overlays.map(overlay => overlay.color), []);
+ const messages = or(device.status.sen5x?.overlays.map(overlay => overlay.message), []);
+
+ // const [currentIndex, setCurrentIndex] = useState(0);
+
+ // Auto-cycle through measurements every 3 seconds
+ // useEffect(() => {
+ // const interval = setInterval(() => {
+ // setCurrentIndex((prevIndex) => (prevIndex + 1) % 2);
+ // }, 3000); // Adjust timing as needed (3000ms = 3s)
+
+ // return () => clearInterval(interval); // Cleanup on unmount
+ // }, []);
const tooltip = () => {
- if (messages.length === 0) return null;
- if (messages.length < 2) return messages[0];
+ if (messages.length === 0) return null
+ if (messages.length < 2) return (
+ messages[0]
+ )
return (
@@ -75,10 +85,10 @@ export default function StatusDust(props: Props) {
@@ -86,20 +96,22 @@ export default function StatusDust(props: Props) {
{message}
- );
+ )
})}
- );
- };
+ )
+ }
function compareTimestamps(timestamp1: string, timestamp2: string): number {
const date1 = new Date(timestamp1);
const date2 = new Date(timestamp2);
+ // Check if dates are valid
if (isNaN(date1.getTime()) || isNaN(date2.getTime())) {
throw new Error("Invalid RFC3339 timestamp format");
}
+ // Compare using getTime() for millisecond precision
return date1.getTime() - date2.getTime();
}
@@ -107,49 +119,47 @@ export default function StatusDust(props: Props) {
const now = new Date();
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
if (compareTimestamps(device.status.sen5x?.timestamp, oneDayAgo.toISOString()) < 0) {
- return null;
- }
+ return null
+ }
} else {
- return null;
+ return null
}
return (
-
-
-
-
-
-
- {"<1um:"} {device.status.sen5x?.dust1ug.toFixed(1)}ug/m3
-
-
-
-
- {"<2.5um:"} {device.status.sen5x?.dust2ug.toFixed(1)}ug/m3
-
-
-
-
- {"<4um: "}
- {device.status.sen5x?.dust4ug.toFixed(1)}ug/m3
-
-
-
-
- {"<10um: "}
- {device.status.sen5x?.dust10ug.toFixed(1)}ug/m3
-
-
+
+
+
+
+
+ {"<1um:"} {device.status.sen5x?.dust1ug.toFixed(1)}ug/m3
+
+
+
+
+ {"<2.5um:"} {device.status.sen5x?.dust2ug.toFixed(1)}ug/m3
+
+
+
+
+ {"<4um: "}{device.status.sen5x?.dust4ug.toFixed(1)}ug/m3
+
+
+
+
+ {"<10um: "}{device.status.sen5x?.dust10ug.toFixed(1)}ug/m3
+
{showTimestamp && device.status.sen5x?.timestamp && (
-
+
)}
- );
-}
+ )
+}
\ No newline at end of file
diff --git a/src/common/StatusSen5x.tsx b/src/common/StatusSen5x.tsx
index 1c34214..4e3d316 100644
--- a/src/common/StatusSen5x.tsx
+++ b/src/common/StatusSen5x.tsx
@@ -24,61 +24,60 @@ const useStyles = makeStyles((theme: Theme) => {
marginRight: "auto",
margin: theme.spacing(1),
width: theme.spacing(16),
- minHeight: theme.spacing(11),
- },
- /** Reserves vertical space so absolute carousel slides do not overlap the timestamp below. */
- readingsWrapper: {
- position: "relative",
- width: "100%",
- minHeight: theme.spacing(10),
+ height: theme.spacing(11),
},
carouselContainer: {
- position: "relative",
- height: "40px",
- overflow: "hidden",
+ position: 'relative',
+ height: '40px',
+ overflow: 'hidden',
textAlign: "center",
},
carouselItem: {
opacity: 0,
- transform: "translateX(100%)",
- transition: "opacity 0.5s ease, transform 0.5s ease",
- position: "absolute",
- width: "100%",
+ transform: 'translateX(100%)',
+ transition: 'opacity 0.5s ease, transform 0.5s ease',
+ position: 'absolute',
+ width: '100%',
overflow: "hidden",
+ // height: '100%',
},
active: {
opacity: 1,
- transform: "translateX(0)",
+ transform: 'translateX(0)',
},
inactive: {
- transform: "translateX(-100%)",
+ transform: 'translateX(-100%)',
},
- });
-});
+ })
+})
export default function StatusSen5x(props: Props) {
const { device, noDust, showTimestamp } = props;
- const classes = useStyles();
- const colors = or(device.status.sen5x?.overlays.map((overlay) => overlay.color), []);
- const messages = or(device.status.sen5x?.overlays.map((overlay) => overlay.message), []);
+ const classes = useStyles()
+ // console.log(noDust)
+ const colors = or(device.status.sen5x?.overlays.map(overlay => overlay.color), []);
+ const messages = or(device.status.sen5x?.overlays.map(overlay => overlay.message), []);
const [currentIndex, setCurrentIndex] = useState(0);
+ // Auto-cycle through measurements every 5 seconds
useEffect(() => {
if (noDust) {
- setCurrentIndex(0);
- return;
+ setCurrentIndex(0)
+ return
}
const interval = setInterval(() => {
if (!noDust) setCurrentIndex((prevIndex) => (prevIndex + 1) % 2);
- }, 5000);
+ }, 5000);
- return () => clearInterval(interval);
+ return () => clearInterval(interval); // Cleanup on unmount
}, [noDust]);
const tooltip = () => {
- if (messages.length === 0) return null;
- if (messages.length < 2) return messages[0];
+ if (messages.length === 0) return null
+ if (messages.length < 2) return (
+ messages[0]
+ )
return (
@@ -91,10 +90,10 @@ export default function StatusSen5x(props: Props) {
@@ -102,20 +101,22 @@ export default function StatusSen5x(props: Props) {
{message}
- );
+ )
})}
- );
- };
+ )
+ }
function compareTimestamps(timestamp1: string, timestamp2: string): number {
const date1 = new Date(timestamp1);
const date2 = new Date(timestamp2);
+ // Check if dates are valid
if (isNaN(date1.getTime()) || isNaN(date2.getTime())) {
throw new Error("Invalid RFC3339 timestamp format");
}
+ // Compare using getTime() for millisecond precision
return date1.getTime() - date2.getTime();
}
@@ -123,82 +124,76 @@ export default function StatusSen5x(props: Props) {
const now = new Date();
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
if (compareTimestamps(device.status.sen5x?.timestamp, oneDayAgo.toISOString()) < 0) {
- return null;
- }
+ return null
+ }
} else {
- return null;
+ return null
}
return (
-
-
-
-
-
-
- Temp: {device.status.sen5x?.temperature.toFixed(1)}°C
-
-
-
-
- Humidity: {device.status.sen5x?.humidity.toFixed(1)}%
-
-
-
-
- Voc: {device.status.sen5x?.voc.toFixed(1)}%
-
-
-
-
- Nox: {device.status.sen5x?.nox.toFixed(1)}%
-
-
+
+
+
+
+
+ Temp: {device.status.sen5x?.temperature.toFixed(1)}°C
+
+
+
+
+ Humidity: {device.status.sen5x?.humidity.toFixed(1)}%
+
+
+
+
+ Voc: {device.status.sen5x?.voc.toFixed(1)}%
+
+
+
+
+ Nox: {device.status.sen5x?.nox.toFixed(1)}%
+
-
- {!noDust && (
-
-
-
- {"<1um:"} {device.status.sen5x?.dust1ug.toFixed(1)}ug/m3
-
-
-
-
- {"<2.5um:"} {device.status.sen5x?.dust2ug.toFixed(1)}ug/m3
-
-
-
-
- {"<4um: "}
- {device.status.sen5x?.dust4ug.toFixed(1)}ug/m3
-
-
-
-
- {"<10um: "}
- {device.status.sen5x?.dust10ug.toFixed(1)}ug/m3
-
-
-
- )}
+
+ {!noDust &&
+
+
+ {"<1um:"} {device.status.sen5x?.dust1ug.toFixed(1)}ug/m3
+
+
+
+
+ {"<2.5um:"} {device.status.sen5x?.dust2ug.toFixed(1)}ug/m3
+
+
+
+
+ {"<4um: "}{device.status.sen5x?.dust4ug.toFixed(1)}ug/m3
+
+
+
+
+ {"<10um: "}{device.status.sen5x?.dust10ug.toFixed(1)}ug/m3
+
+
+ }
{showTimestamp && device.status.sen5x?.timestamp && (
-
+
)}
- );
-}
+ )
+}
\ No newline at end of file
diff --git a/src/component/ComponentCard.tsx b/src/component/ComponentCard.tsx
index 8b9b767..6d430d5 100644
--- a/src/component/ComponentCard.tsx
+++ b/src/component/ComponentCard.tsx
@@ -13,13 +13,11 @@ import {
} from "@mui/material";
import Grid from '@mui/material/Grid2';
import EventBlocker from "common/EventBlocker";
-import PendingChangesIndicator from "common/PendingChangesIndicator";
import MeasurementSummary from "component/MeasurementSummary";
-import { useComponentAPI, usePendingChanges, useSnackbar, useThemeType } from "hooks";
+import { useComponentAPI, useSnackbar, useThemeType } from "hooks";
import InteractionsOverview from "interactions/InteractionsOverview";
import { cloneDeep } from "lodash";
import { Component, Device, Interaction } from "models";
-import moment from "moment";
import { getFriendlyAddressTypeName, getHumanReadableAddress } from "pbHelpers/AddressType";
import { controllerModeLabel } from "pbHelpers/Component";
import {
@@ -123,9 +121,6 @@ export default function ComponentCard(props: Props) {
const location = useLocation()
const [sensors, setSensors] = useState([]);
const [{ user, showErrors, as }] = useGlobalState();
- const { pending: pendingChanges, accepted: changesAccepted } = usePendingChanges(
- component.status.synced
- );
const updateControllerState = (checked: boolean) => {
let updatedComponent = cloneDeep(component);
@@ -138,8 +133,6 @@ export default function ComponentCard(props: Props) {
.update(device.id(), updatedComponent.settings, undefined, undefined, as)
.then(() => {
success(component.name() + "'s mode was set to " + describe);
- updatedComponent.status.synced = false;
- updatedComponent.status.lastUpdate = moment().toISOString();
refreshCallback(updatedComponent);
})
.catch(() => {
@@ -197,18 +190,7 @@ export default function ComponentCard(props: Props) {
cableID = "Cable: " + (component.settings.addressType - 8);
}
return port + " " + cableID;
- } else if (component.settings.addressType === quack.AddressType.ADDRESS_TYPE_I2C) {
- let description = ""
- let type = getFriendlyAddressTypeName(component.settings.addressType);
- description = type
- if(component.settings.expansionLine){
- description = description + ": Exp Line " + component.settings.expansionLine
- }
- if(component.settings.muxLine){
- description = description + " ,Mux Line " + component.settings.muxLine
- }
- return description
- }else{
+ } else {
return getFriendlyAddressTypeName(component.settings.addressType);
}
};
@@ -294,34 +276,19 @@ export default function ComponentCard(props: Props) {
className={classes.cardHeader}
titleTypographyProps={{ variant: "subtitle1" }}
subheader={
-
- {(pendingChanges || changesAccepted) && (
-
-
-
- )}
- {/* !newStructure ? (
-
- ) : ( */}
+ // !newStructure ? (
+ //
+ // ) : (
- {/* ) */}
-
+ // )
}
action={
diff --git a/src/component/ComponentForm.tsx b/src/component/ComponentForm.tsx
index 362b0ae..21ca747 100644
--- a/src/component/ComponentForm.tsx
+++ b/src/component/ComponentForm.tsx
@@ -19,10 +19,9 @@ import {
Tooltip,
Typography
} from "@mui/material";
-import {
+import {
ExpandMore,
- Close as CloseIcon,
- Remove as RemoveIcon
+ Close as CloseIcon
} from "@mui/icons-material";
import PeriodSelect from "common/time/PeriodSelect";
import SearchSelect, { Option } from "common/SearchSelect";
@@ -45,6 +44,7 @@ import { bestUnit, milliToX } from "common/time/duration";
import { useGlobalState } from "providers";
import { green, red } from "@mui/material/colors";
import { makeStyles } from "@mui/styles";
+import { getDistanceUnit } from "utils";
import { GrainOptions } from "grain";
import CompModes from "component/ComponentMode.json";
import FanPicker from "fans/fanPicker";
@@ -98,7 +98,6 @@ interface Props {
component: Component;
componentChanged: (component: Component, formValid: boolean) => void;
canEdit: boolean;
- hideControllerFields?: boolean;
}
interface ComponentData {
@@ -121,7 +120,7 @@ export default function ComponentForm(props: Props) {
const classes = useStyles();
const grainOptions = GrainOptions();
const [{ user }] = useGlobalState();
- const { device, component, componentChanged, canEdit, hideControllerFields } = props;
+ const { device, component, componentChanged, canEdit } = props;
const [reportExpanded, setReportExpanded] = useState(false);
const [dataUsageWarningDismissed, setDataUsageWarningDismissed] = useState(true);
const [form, setForm] = useState({
@@ -139,8 +138,7 @@ export default function ComponentForm(props: Props) {
sensorDistance: "0",
});
const [compMode, setCompMode] = useState();
- const [useCustomGrain, setUseCustomGrain] = useState(false);
- const [controlEmail, setControlEmail] = useState("");
+ const [useCustomGrain, setUseCustomGrain] = useState(false)
//const [numCalibrations, setNumCalibrations] = useState(0)
useEffect(() => {
@@ -148,9 +146,7 @@ export default function ComponentForm(props: Props) {
let describer = describeMeasurement(
primaryMeasurement(formComponent.settings.type),
formComponent.settings.type,
- formComponent.settings.subtype,
- undefined,
- user
+ formComponent.settings.subtype
);
let offset = describer.toDisplay(formComponent.settings.calibrationOffset);
let formCoefficients: string[] = [];
@@ -181,11 +177,11 @@ export default function ComponentForm(props: Props) {
let width = formComponent.settings.containerDimensions?.widthCm ?? 0;
let height = formComponent.settings.containerDimensions?.heightCm ?? 0;
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
length = length / 100;
width = width / 100;
height = height / 100;
- } else if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
+ } else if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
length = length / 30.48;
width = width / 30.48;
height = height / 30.48;
@@ -193,9 +189,9 @@ export default function ComponentForm(props: Props) {
let sensorDistance = formComponent.settings.sensorDistanceCm ?? 0;
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
sensorDistance = sensorDistance / 100;
- } else if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
+ } else if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
sensorDistance = sensorDistance / 30.48;
}
@@ -359,21 +355,6 @@ export default function ComponentForm(props: Props) {
setForm(f);
};
- const addControlEmail = () => {
- const trimmed = controlEmail.trim().toLowerCase();
- if (trimmed === "" || form.component.settings.controlEmails.includes(trimmed)) return;
- let f = cloneDeep(form);
- f.component.settings.controlEmails.push(trimmed);
- setForm(f);
- setControlEmail("");
- };
-
- const removeControlEmail = (index: number) => {
- let f = cloneDeep(form);
- f.component.settings.controlEmails.splice(index, 1);
- setForm(f);
- };
-
const toggleMeasure = (event: any) => {
let f = cloneDeep(form);
f.measure = event.target.checked;
@@ -457,29 +438,11 @@ export default function ComponentForm(props: Props) {
setForm(f);
};
- const modeDictionaryValue = (dictionary: any, key: string): number | undefined => {
- if (!Array.isArray(dictionary)) {
- return undefined;
- }
- let entry = dictionary.find((elem: any) => elem.key === key);
- return typeof entry?.value === "number" ? entry.value : undefined;
- };
-
const isCoefficientValid = () => {
let calibrate = form.component.settings.calibrate;
let coefficient = Number(form.coefficient);
- if (isNaN(coefficient)) {
- return false;
- }
- if (!calibrate) {
- return true;
- }
- if (compMode && compMode.mode.entryA === "number") {
- let minimum = modeDictionaryValue(compMode.mode.dictionaryA, "min") ?? 0;
- let maximum = modeDictionaryValue(compMode.mode.dictionaryA, "max") ?? 100;
- return coefficient >= minimum && coefficient <= maximum;
- }
- return coefficient > 0;
+ let valid = !isNaN(coefficient) && (!calibrate || (calibrate && coefficient > 0));
+ return valid;
};
const changeCoefficient = (event: any) => {
@@ -503,14 +466,10 @@ export default function ComponentForm(props: Props) {
};
const isOffsetValid = (min?: number, max?: number) => {
- const minimum = min ?? modeDictionaryValue(compMode?.mode.dictionaryB, "min") ?? 0;
- const maximum = max ?? modeDictionaryValue(compMode?.mode.dictionaryB, "max") ?? 100;
+ let minimum = min ?? 0;
+ let maximum = max ?? 100;
if (compMode && form.component.settings.calibrate) {
- const offset = Number(form.offset);
- const meetsMinimum = compMode.mode.exclusiveMinimumB
- ? offset > minimum
- : offset >= minimum;
- return !isNaN(offset) && offset <= maximum && meetsMinimum;
+ return Number(form.offset) <= maximum && Number(form.offset) >= minimum;
}
return !isNaN(Number(form.offset));
};
@@ -522,9 +481,7 @@ export default function ComponentForm(props: Props) {
f.component.settings.calibrationOffset = describeMeasurement(
primaryMeasurement(f.component.settings.type),
f.component.settings.type,
- f.component.settings.subtype,
- undefined,
- user
+ f.component.settings.subtype
).toStored(+event.target.value);
}
setForm(f);
@@ -537,9 +494,7 @@ export default function ComponentForm(props: Props) {
f.component.settings.calibrations[index].calibrationOffset = describeMeasurement(
primaryMeasurement(f.component.settings.type),
f.component.settings.type,
- f.component.settings.subtype,
- undefined,
- user
+ f.component.settings.subtype
).toStored(+event.target.value);
}
setForm(f);
@@ -565,9 +520,9 @@ export default function ComponentForm(props: Props) {
if (!isNaN(Number(event.target.value))) {
let dimensions = f.component.settings.containerDimensions;
let value = Number(event.target.value);
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
value = value * 100;
- } else if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
+ } else if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
value = value * 30.48;
}
if (dimensions !== undefined && dimensions !== null) {
@@ -594,9 +549,9 @@ export default function ComponentForm(props: Props) {
if (!isNaN(Number(event.target.value))) {
let dimensions = f.component.settings.containerDimensions;
let value = Number(event.target.value);
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
value = value * 100;
- } else if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
+ } else if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
value = value * 30.48;
}
if (dimensions !== undefined && dimensions !== null) {
@@ -623,9 +578,9 @@ export default function ComponentForm(props: Props) {
if (!isNaN(Number(event.target.value))) {
let dimensions = f.component.settings.containerDimensions;
let value = Number(event.target.value);
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
value = value * 100;
- } else if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
+ } else if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
value = value * 30.48;
}
if (dimensions !== undefined && dimensions !== null) {
@@ -651,9 +606,9 @@ export default function ComponentForm(props: Props) {
f.sensorDistance = event.target.value;
if (!isNaN(Number(event.target.value))) {
let value = Number(event.target.value);
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
value = value * 100;
- } else if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
+ } else if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
value = value * 30.48;
}
f.component.settings.sensorDistanceCm = value;
@@ -777,9 +732,7 @@ export default function ComponentForm(props: Props) {
{describeMeasurement(
primaryMeasurement(component.settings.type),
component.settings.type,
- component.settings.subtype,
- undefined,
- user
+ component.settings.subtype
).unit()}
)
@@ -854,11 +807,6 @@ export default function ComponentForm(props: Props) {
onFocus={selectText}
margin="normal"
fullWidth
- inputProps={
- compMode.mode.stepB !== undefined
- ? { step: compMode.mode.stepB }
- : undefined
- }
type={compMode.mode.entryB}
variant="outlined">
{compMode.mode.entryB === "select" &&
@@ -876,7 +824,7 @@ export default function ComponentForm(props: Props) {
const describeSource = (measurementType: quack.MeasurementType): MeasurementDescriber => {
const { component } = form;
- return describeMeasurement(measurementType, component.type(), component.subType(), undefined, user);
+ return describeMeasurement(measurementType, component.type(), component.subType());
};
const availableMeasurementTypes = (type: quack.ComponentType): quack.MeasurementType[] => {
@@ -1304,7 +1252,7 @@ export default function ComponentForm(props: Props) {
InputProps={{
endAdornment: (
- {user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS
+ {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS
? "m"
: "ft"}
@@ -1328,7 +1276,7 @@ export default function ComponentForm(props: Props) {
InputProps={{
endAdornment: (
- {user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS
+ {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS
? "m"
: "ft"}
@@ -1352,7 +1300,7 @@ export default function ComponentForm(props: Props) {
InputProps={{
endAdornment: (
- {user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS
+ {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS
? "m"
: "ft"}
@@ -1380,13 +1328,13 @@ export default function ComponentForm(props: Props) {
InputProps={{
endAdornment: (
- {user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"}
+ {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"}
)
}}
/>
)}
- {isController(component.settings.type) && !hideControllerFields && (
+ {isController(component.settings.type) && (
-
-
- Authorized Control Emails
-
-
-
- setControlEmail(e.target.value)}
- onKeyDown={e => {
- if (e.key === "Enter") {
- e.preventDefault();
- addControlEmail();
- }
- }}
- disabled={!canEdit}
- />
-
-
-
- Add
-
-
-
- {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}
- />
-
-
-
- Add
-
-
-
- {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/component/ExportDataSettings.tsx b/src/component/ExportDataSettings.tsx
index a17cdc0..0a310ba 100644
--- a/src/component/ExportDataSettings.tsx
+++ b/src/component/ExportDataSettings.tsx
@@ -210,7 +210,7 @@ class ExportDataSettings extends React.Component {
// }
formatMeasurements(measurements: Array): Promise> {
- const { device, component, user } = this.props;
+ const { device, component } = this.props;
return new Promise(function(resolve, reject) {
if (!measurements) reject("Invalid measurements");
@@ -237,9 +237,7 @@ class ExportDataSettings extends React.Component {
let unit = describeMeasurement(
componentMeasurement.measurementType,
component.settings.type,
- component.settings.subtype,
- undefined,
- user
+ component.settings.subtype
).unit();
let key = componentMeasurement.label + " (" + unit + ")";
row[key] = componentMeasurement.extract(measurement.measurement);
diff --git a/src/contract/constractActions.tsx b/src/contract/constractActions.tsx
new file mode 100644
index 0000000..3c1e036
--- /dev/null
+++ b/src/contract/constractActions.tsx
@@ -0,0 +1,107 @@
+import { Box, Card, Typography, useTheme } from "@mui/material";
+import GrainDescriber from "grain/GrainDescriber";
+import { Contract } from "models/Contract";
+import { Label, Pie, PieChart, ResponsiveContainer, Text } from "recharts";
+
+interface Props {
+ contract: Contract;
+}
+
+export default function ContractVisualizer(props: Props) {
+ const { contract } = props;
+ const theme = useTheme();
+
+ return (
+
+
+
+ {GrainDescriber(contract.grain()).name}
+
+
+ {contract.deliveredInPreferredUnit().toFixed(2)}/
+ {contract.sizeInPreferredUnit().toFixed(2)} {contract.unit}
+
+
+
+
+ {/* (
+
+
+
+ )}
+ /> */}
+
+ {
+ const { cx, cy } = props.viewBox as any;
+ return (
+
+
+ {((contract.settings.delivered / contract.settings.size) * 100).toFixed(1) +
+ "%"}
+
+
+ );
+ }}
+ />
+ {
+ const { cx, cy } = props.viewBox as any;
+ return (
+
+
+ Delivered
+
+
+ );
+ }}
+ />
+
+
+
+
+ );
+}
diff --git a/src/contract/contractList.tsx b/src/contract/contractList.tsx
index f266147..aec7497 100644
--- a/src/contract/contractList.tsx
+++ b/src/contract/contractList.tsx
@@ -60,7 +60,7 @@ const useStyles = makeStyles((theme: Theme) => ({
export default function ContractsList(props: Props) {
const { contracts, contractPermissions, updateList } = props;
const theme = useTheme();
- const [{as, user}] = useGlobalState();
+ const [{as}] = useGlobalState();
//will need map for contracts that have supported grain types
const [supportedTypes, setSupportedTypes] = useState>(new Map());
//will need need map for custom contracts
@@ -173,16 +173,16 @@ export default function ContractsList(props: Props) {
{contract.name()}
{contract.settings.contractDate}
{contract.settings.deliveryWindow?.endDate}
- {contract.sizeInPreferredUnit(user) + " " + contract.unit}
- {contract.deliveredInPreferredUnit(user) + " " + contract.unit}
+ {contract.sizeInPreferredUnit() + " " + contract.unit}
+ {contract.deliveredInPreferredUnit() + " " + contract.unit}
{/* */}
{/* {((contract.settings.delivered/contract.settings.size)*100).toFixed(1) + "%"} */}
- {contract.sizeInPreferredUnit(user) -
- contract.deliveredInPreferredUnit(user) +
+ {contract.sizeInPreferredUnit() -
+ contract.deliveredInPreferredUnit() +
" " +
contract.unit}
diff --git a/src/contract/contractSettings.tsx b/src/contract/contractSettings.tsx
index b1050e6..13116b0 100644
--- a/src/contract/contractSettings.tsx
+++ b/src/contract/contractSettings.tsx
@@ -24,6 +24,7 @@ import moment from "moment";
import { pond } from "protobuf-ts/pond";
import { useGlobalState, useTaskAPI, useContractAPI, useSnackbar } from "providers";
import React, { useEffect, useState } from "react";
+import { getGrainUnit } from "utils";
interface Props {
open: boolean;
@@ -44,7 +45,7 @@ export default function ContractSettings(props: Props) {
const { open, close, contract } = props;
const contractAPI = useContractAPI();
const [name, setName] = useState("");
- const [{as, user}] = useGlobalState();
+ const [{as}] = useGlobalState();
const [contractID, setContractID] = useState(""); //id
const [holder, setHolder] = useState(""); //holder
const [deliveryStart, setDeliveryStart] = useState(moment().format("YYYY-MM-DD")); //deliverystart
@@ -71,6 +72,7 @@ export default function ContractSettings(props: Props) {
adornment: ""
});
const taskAPI = useTaskAPI();
+ const [{ user }] = useGlobalState();
const closeSettings = () => {
close();
@@ -85,32 +87,32 @@ export default function ContractSettings(props: Props) {
setDeliveryEnd(contract.settings.deliveryWindow?.endDate ?? "");
setContractType(contract.settings.type);
setCustomCommodity(contract.settings.customCommodity);
- setContractSize(contract.sizeInPreferredUnit(user).toFixed(2));
- setDeliveredAmount(contract.deliveredInPreferredUnit(user).toFixed(2));
+ setContractSize(contract.sizeInPreferredUnit().toFixed(2));
+ setDeliveredAmount(contract.deliveredInPreferredUnit().toFixed(2));
setContractValue(contract.settings.totalValue.toString());
setCommodity(contract.settings.commodity);
setContractDate(contract.settings.contractDate);
let conv = 1
if(contract.settings.conversionValue > 1){
conv = contract.settings.conversionValue
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
conv = conv * 0.907
}
}
setConversionValue(conv.toFixed(2));
setUseCustom(contract.isCustom());
}
- }, [contract, user]);
+ }, [contract]);
useEffect(() => {
switch (contractType) {
case pond.ContractType.CONTRACT_TYPE_GRAIN:
let conversionLabel = "Bushels Per Tonne"
let adornment = "bu"
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
adornment = "mT"
}
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
conversionLabel = "Bushels Per Ton"
adornment = "t"
}
@@ -165,14 +167,14 @@ export default function ContractSettings(props: Props) {
//we will assume that both of those entries are using the same unit ie both US ton or both metric tonne
const deliveredVal = isNaN(parseFloat(deliveredAmount))
? 0
- : Math.round(Contract.toStoredUnit(parseFloat(deliveredAmount), contractType, conVal, user));
+ : Math.round(Contract.toStoredUnit(parseFloat(deliveredAmount), contractType, conVal));
const sizeVal = isNaN(parseFloat(contractSize))
? 0
- : Math.round(Contract.toStoredUnit(parseFloat(contractSize), contractType, conVal, user));
+ : Math.round(Contract.toStoredUnit(parseFloat(contractSize), contractType, conVal));
//then after the conversion to bushels change the conversion value to metric to be stored in the contract
if(contractType === pond.ContractType.CONTRACT_TYPE_GRAIN){
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
conVal = conVal / 0.907 //convert the ton they entered to mT to be stored in the contracts conversion value
}
}
diff --git a/src/contract/contractTransactionGraph.tsx b/src/contract/contractTransactionGraph.tsx
index 22eeeea..5eae1c3 100644
--- a/src/contract/contractTransactionGraph.tsx
+++ b/src/contract/contractTransactionGraph.tsx
@@ -4,8 +4,8 @@ import { Transaction } from "models/Transaction";
import BarGraph, { BarData } from "charts/BarGraph";
import { useEffect, useState } from "react";
import moment, { Moment } from "moment";
+import { getGrainUnit } from "utils";
import { pond } from "protobuf-ts/pond";
-import { useGlobalState } from "providers";
interface Props {
contract: Contract;
@@ -14,7 +14,6 @@ interface Props {
export default function ContractTransactionGraph(props: Props) {
const { contract, transactions } = props;
- const [{ user }] = useGlobalState();
const [data, setData] = useState([]);
useEffect(() => {
@@ -28,13 +27,13 @@ export default function ContractTransactionGraph(props: Props) {
if (transaction?.grainTransaction) {
value = transaction.grainTransaction.bushels;
if (
- user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE &&
+ getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE &&
transaction.grainTransaction.bushelsPerTonne > 1
) {
value = value / transaction.grainTransaction.bushelsPerTonne;
}
if (
- user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON &&
+ getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON &&
transaction.grainTransaction.bushelsPerTonne > 1
) {
value = value / (transaction.grainTransaction.bushelsPerTonne * 0.907);
diff --git a/src/contract/contractTransactionTable.tsx b/src/contract/contractTransactionTable.tsx
index 634993b..ebee9ba 100644
--- a/src/contract/contractTransactionTable.tsx
+++ b/src/contract/contractTransactionTable.tsx
@@ -232,7 +232,6 @@ export default function ContractTransactionTable(props: Props) {
Select Files to Upload
- {contract.deliveredInPreferredUnit(user).toFixed(2)}/
- {contract.sizeInPreferredUnit(user).toFixed(2)} {contract.unit}
+ {contract.deliveredInPreferredUnit().toFixed(2)}/
+ {contract.sizeInPreferredUnit().toFixed(2)} {contract.unit}
diff --git a/src/device/DeviceActions.tsx b/src/device/DeviceActions.tsx
index 5a73d8b..4bf448e 100644
--- a/src/device/DeviceActions.tsx
+++ b/src/device/DeviceActions.tsx
@@ -512,7 +512,7 @@ export default function DeviceActions(props: Props) {
};
const showSupport = () => {
- return user.hasAdmin() && !location.pathname.includes("support")
+ return user.allowedTo("provision") && !location.pathname.includes("support")
}
const buttons = () => {
@@ -523,7 +523,7 @@ export default function DeviceActions(props: Props) {
}
- {canWrite && user.hasAdmin() && }
+ {canWrite && user.allowedTo("provision") && }
{/* {!isShareableLink(match.params.deviceID) && ( */}
();
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/DevicePresetsFromPicker.tsx b/src/device/DevicePresetsFromPicker.tsx
index a22991b..900df87 100644
--- a/src/device/DevicePresetsFromPicker.tsx
+++ b/src/device/DevicePresetsFromPicker.tsx
@@ -33,6 +33,7 @@ import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
import { pond, quack } from "protobuf-ts/pond";
import React, { useEffect, useState } from "react";
import CableTopNodeSummary from "bin/CableTopNodeSummary";
+import { getTemperatureUnit } from "utils";
import { DevicePreset } from "models/DevicePreset";
import { Ambient } from "models/Ambient";
import { useGlobalState } from "providers";
@@ -181,7 +182,7 @@ export default function DevicePresets(props: DevicePresetsProps) {
const [heaters, setHeaters] = useState([]);
const [fans, setFans] = useState([]);
//const [subscribeBinToAutoTopNode, setSubscribeBinToAutoTopNode] = useState(false);
- const [{as, user}] = useGlobalState();
+ const [{as}] = useGlobalState();
useEffect(() => {
if (deviceComponents.get(deviceIndex.toString())) return;
@@ -438,23 +439,19 @@ export default function DevicePresets(props: DevicePresetsProps) {
value: describeMeasurement(
quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
plenum.settings.type,
- plenum.settings.subtype,
- undefined,
- user
+ plenum.settings.subtype
).toStored(humidityPreset)
});
conditions.push(fanConditionOne);
//since the measurement describers function toStored does a conversion into celsius for temperature if the users pref is fahrenheit we need to convert the preset value into fahrenheit
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
tempPreset = Math.round((tempPreset * (9 / 5) + 32) * 100) / 100;
}
let tempVal = describeMeasurement(
quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
plenum.settings.type,
- plenum.settings.subtype,
- undefined,
- user
+ plenum.settings.subtype
).toStored(tempPreset);
let fanConditionTwo = pond.InteractionCondition.create({
@@ -536,9 +533,7 @@ export default function DevicePresets(props: DevicePresetsProps) {
describeMeasurement(
quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
plenum.settings.type,
- plenum.settings.subtype,
- undefined,
- user
+ plenum.settings.subtype
).toStored(hum)
)
});
@@ -546,15 +541,13 @@ export default function DevicePresets(props: DevicePresetsProps) {
}
//since the measurement describers function to stored does a conversion into celsius if the users pref is fahrenheit for temperature we need to convert the preset value into fahrenheit
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
temp = Math.round((temp * (9 / 5) + 32) * 100) / 100;
}
let tempVal = describeMeasurement(
quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
plenum.settings.type,
- plenum.settings.subtype,
- undefined,
- user
+ plenum.settings.subtype
).toStored(temp);
let tempCondition = pond.InteractionCondition.create({
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/DeviceTags.tsx b/src/device/DeviceTags.tsx
index ab1d8a5..7806a59 100644
--- a/src/device/DeviceTags.tsx
+++ b/src/device/DeviceTags.tsx
@@ -8,6 +8,7 @@ import {
ListItem,
ListItemIcon,
ListItemText,
+ Theme,
Typography
} from "@mui/material";
import { makeStyles } from "@mui/styles";
@@ -18,10 +19,10 @@ import TagSettings from "common/TagSettings";
import { Device, Tag } from "models";
import { filterByTag } from "pbHelpers/Tag";
import { useDeviceAPI, useSnackbar, useTagAPI } from "providers";
-import React, { useCallback, useEffect, useRef, useState } from "react";
+import React, { useEffect, useRef, useState } from "react";
import { pond } from "protobuf-ts/pond";
-const useStyles = makeStyles(() => {
+const useStyles = makeStyles((_theme: Theme) => {
return ({
addIcon: {
color: "var(--status-ok)"
@@ -44,19 +45,19 @@ function AddDeviceTag(props: AddDeviceTagProps) {
const [searchValue, setSearchValue] = useState("");
const [tags, setTags] = useState([])
- const loadTags = useCallback(() => {
+ const loadTags = () => {
tagAPI.listTags().then(resp => {
- const newTags: Tag[] = [];
+ let newTags: Tag[] = [];
resp.data.tags.forEach((tag: pond.Tag) => {
newTags.push(Tag.create(tag))
})
setTags(newTags)
})
- }, [tagAPI])
+ }
useEffect(() => {
loadTags()
- }, [loadTags])
+ }, [])
const tagItems = tags
.filter(tag => !deviceTags.some(tagSettings => tagSettings.key === tag.settings.key))
@@ -143,10 +144,10 @@ export default function DeviceTags(props: DeviceTagsProps) {
const [deviceTags, setDeviceTags] = useState(device.status.tags);
const previousDeviceRef = useRef(device);
- const loadTags = useCallback(() => {
+ const loadTags = () => {
// setLoading(true)
tagAPI.listTags().then(resp => {
- const newTags: Tag[] = [];
+ let newTags: Tag[] = [];
resp.data.tags.forEach((tag: pond.Tag) => {
newTags.push(Tag.create(tag))
})
@@ -154,7 +155,7 @@ export default function DeviceTags(props: DeviceTagsProps) {
}).finally(() => {
// setLoading(false)
})
- }, [tagAPI])
+ }
useEffect(() => {
if (previousDeviceRef.current !== device) {
@@ -165,16 +166,14 @@ export default function DeviceTags(props: DeviceTagsProps) {
useEffect(() => {
loadTags()
- }, [loadTags])
+ }, [])
const addTag = (tag: Tag) => {
if (!deviceTags.some(dt => dt.key === tag.settings.key)) {
deviceAPI
.tag(device.id(), tag.settings.key)
.then(() => {
- setDeviceTags(current =>
- current.some(dt => dt.key === tag.settings.key) ? current : [...current, tag.settings]
- );
+ setDeviceTags([...deviceTags, tag.settings]);
})
.catch(() => error("Failed to tag device as " + tag.name));
}
@@ -185,7 +184,7 @@ export default function DeviceTags(props: DeviceTagsProps) {
deviceAPI
.untag(device.id(), tag.key())
.then(() => {
- setDeviceTags(current => current.filter(t => tag.key() !== t.key));
+ setDeviceTags(deviceTags.filter(t => tag.key() !== t.key));
})
.catch(() => error("Failed to remove tag " + tag.name + " from device"));
}
@@ -198,7 +197,7 @@ export default function DeviceTags(props: DeviceTagsProps) {
return (
{deviceTags?.map(tagSettings => {
- const pondTag = pond.Tag.create({ settings: tagSettings })
+ let pondTag = pond.Tag.create({ settings: tagSettings })
return (
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..2be89a9 100644
--- a/src/device/autoDetect/DeviceScannedComponents.tsx
+++ b/src/device/autoDetect/DeviceScannedComponents.tsx
@@ -12,7 +12,6 @@ import ResponsiveDialog from "common/ResponsiveDialog";
import { useComponentAPI, useDeviceAPI, useSnackbar } from "hooks";
import { ConfigurablePin } from "pbHelpers/AddressTypes";
import ScannedOneWirePort from "./OneWire/ScannedOneWirePort";
-import I2CExpander from "./I2CExpander";
interface Props {
scannedComponents: pond.ComponentAddressMap
@@ -28,7 +27,6 @@ interface CompStep {
}
let i2cBlacklistAddresses: number[] = [0x70]
-let i2cExpanderAddresses: number[] = [0x71]
export default function DeviceScannedComponents(props: Props){
const {scannedComponents, device, availablePositions, availableOffsets, refreshCallback} = props
@@ -37,7 +35,6 @@ export default function DeviceScannedComponents(props: Props){
const [scannedI2C, setScannedI2C] = useState() //the unmodified scan of addresses
const [scannedOneWire, setScannedOneWire] = useState([])
const [validI2CCompAddresses, setValidI2CComponentAddresses] = useState([]) //the filtered array of address data
- const [expanderAddresses, setExpanderAddresses] = useState([])
const [components, setComponents] = useState([])
const [steps, setSteps] = useState([]);
const { error, success } = useSnackbar();
@@ -72,21 +69,17 @@ export default function DeviceScannedComponents(props: Props){
useEffect(()=>{
let valid: quack.AddressData[] = []
- let expanders: quack.AddressData[] = []
//filter the address data
let addr = scannedI2C?.settings?.foundAddresses
if(addr){
addr.forEach(addrData => {
if(!i2cBlacklistAddresses.includes(addrData.address)){
- if(i2cExpanderAddresses.includes(addrData.address) && addrData.expansionLine === 0){
- expanders.push(addrData)
- }else{
- valid.push(addrData)
- }
+ if(!(addrData.address === 0x71 && addrData.expansionLine === undefined)){
+ valid.push(addrData)
+ }
}
})
}
- setExpanderAddresses(expanders)
setValidI2CComponentAddresses(valid)
},[scannedI2C])
@@ -211,13 +204,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 +220,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")
@@ -262,49 +256,33 @@ export default function DeviceScannedComponents(props: Props){
{scannedI2C !== undefined &&
-
- I2C Scan
+
+ I2C Sensors
{removeScan(scannedI2C.key, pond.ObjectType.OBJECT_TYPE_DETECT_I2C)}}>Clear Scan
- {expanderAddresses.length > 0 &&
-
- Detected Expanders
- {expanderAddresses.map((expAddr, index) => {
- return (
-
- )
- })}
-
- }
-
- {validI2CCompAddresses.length > 0 ?
-
- Detected Sensors
- {validI2CCompAddresses.map((addr, index) => {
- return (
- ()}/>
- )
- })}
-
- :
+ {validI2CCompAddresses.length > 0 ? validI2CCompAddresses.map((addr, index) => {
+ return (
+ ()}/>
+ )
+ }) :
-
- No Sensors Found
-
-
- }
+ margin: 1,
+ display: "flex",
+ justifyContent: "center"
+ }}>
+
+ No Sensors Found
+
}
+
+ }
{scannedOneWire.length > 0 &&
- Pin Port Scan
+ Pin Port Sensors
{scannedOneWire.map((scan,i) => {
diff --git a/src/device/autoDetect/I2CExpander.tsx b/src/device/autoDetect/I2CExpander.tsx
deleted file mode 100644
index f2a8183..0000000
--- a/src/device/autoDetect/I2CExpander.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Box, Typography } from "@mui/material"
-import { quack } from "protobuf-ts/quack"
-
-interface Props {
- address: quack.AddressData
-}
-
-
-
-export default function I2CExpander(props: Props) {
- const {address} = props
-
- const expanderType = () => {
- switch(address.address){
- case 0x71:
- return "Grain Cable Expander"
- default:
- return "Unknown Expander"
- }
- }
-
- return (
-
- {expanderType()} {address.muxLine ? "Mux Line: " + address.muxLine : ""}
-
- )
-}
\ No newline at end of file
diff --git a/src/device/autoDetect/ScannedI2C.tsx b/src/device/autoDetect/ScannedI2C.tsx
index 3f1b461..4719f66 100644
--- a/src/device/autoDetect/ScannedI2C.tsx
+++ b/src/device/autoDetect/ScannedI2C.tsx
@@ -108,7 +108,6 @@ export default function ScannedI2C(props: Props){
{sensorNum && Sensor {sensorNum} }
{addressData.expansionLine !== 0 && "Expansion Line: " + addressData.expansionLine}
- {/* may also want to specify the mux line, not sure if the sensors will have a mux line though */}
{types.length > 0 ?
diff --git a/src/device/presets/devicePresetCard.tsx b/src/device/presets/devicePresetCard.tsx
index 30cbc63..2839df3 100644
--- a/src/device/presets/devicePresetCard.tsx
+++ b/src/device/presets/devicePresetCard.tsx
@@ -18,9 +18,10 @@ import { cloneDeep } from "lodash";
import { DevicePreset } from "models/DevicePreset";
import { ObjectTypeString } from "objects/ObjectDescriber";
import { pond } from "protobuf-ts/pond";
-import { useGlobalState, useSnackbar } from "providers";
+import { useSnackbar } from "providers";
import { useDevicePresetAPI } from "providers/pond/devicePresetAPI";
import React, { useEffect, useState } from "react";
+import { getTemperatureUnit } from "utils";
interface Props {
preset: DevicePreset;
@@ -53,7 +54,6 @@ export default function DevicePresetCard(props: Props) {
const [humString, setHumString] = useState("0");
const [hum, setHum] = useState(0);
const isMobile = useMobile();
- const [{user}] = useGlobalState();
useEffect(() => {
setPresetName(preset.name);
@@ -63,11 +63,11 @@ export default function DevicePresetCard(props: Props) {
setHum(preset.settings.humidity);
setHumString(preset.settings.humidity.toString());
let displayTemp = preset.settings.temperature.toString();
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
displayTemp = ((preset.settings.temperature * 9) / 5 + 32).toFixed();
}
setTempString(displayTemp);
- }, [preset, user]);
+ }, [preset]);
const saveNewPreset = () => {
let typestring;
@@ -296,7 +296,7 @@ export default function DevicePresetCard(props: Props) {
InputProps={{
endAdornment: (
- {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
+ {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F"
: "°C"}
@@ -307,7 +307,7 @@ export default function DevicePresetCard(props: Props) {
onChange={e => {
if (!isNaN(+e.target.value)) {
let temp = +e.target.value;
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
temp = Math.round((((temp - 32) * 5) / 9) * 10) / 10;
}
setTempC(temp);
@@ -324,7 +324,7 @@ export default function DevicePresetCard(props: Props) {
max={40}
valueLabelDisplay="auto"
valueLabelFormat={value => {
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
return ((value * 9) / 5 + 32).toFixed() + "°F";
}
return value.toFixed() + "°C";
@@ -350,7 +350,7 @@ export default function DevicePresetCard(props: Props) {
max={40}
valueLabelDisplay="auto"
valueLabelFormat={value => {
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
return ((value * 9) / 5 + 32).toFixed() + "°F";
}
return value.toFixed() + "°C";
@@ -371,7 +371,7 @@ export default function DevicePresetCard(props: Props) {
InputProps={{
endAdornment: (
- {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
+ {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F"
: "°C"}
@@ -382,7 +382,7 @@ export default function DevicePresetCard(props: Props) {
onChange={e => {
if (!isNaN(+e.target.value)) {
let temp = +e.target.value;
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
temp = Math.round((((temp - 32) * 5) / 9) * 10) / 10;
}
setTempC(temp);
diff --git a/src/field/FieldTaskList.tsx b/src/field/FieldTaskList.tsx
deleted file mode 100644
index 66a32c3..0000000
--- a/src/field/FieldTaskList.tsx
+++ /dev/null
@@ -1,173 +0,0 @@
-import { Fab, Theme } from "@mui/material"
-import { Task } from "models"
-import { useGlobalState, useTaskAPI } from "providers"
-import React, { useCallback, useEffect, useState } from "react"
-import TaskDrawer from "tasks/TaskDrawer"
-import TaskList from "tasks/TaskList"
-import TaskSettings from "tasks/TaskSettings"
-import AddIcon from "@mui/icons-material/Add";
-import { makeStyles } from "@mui/styles";
-import { openSnackbar } from "providers/Snackbar"
-import { cloneDeep } from "lodash"
-
-
-
-interface Props {
- field: string
-}
-
-const useStyles = makeStyles((theme: Theme) => {
- return ({
- active: {
- color: theme.palette.getContrastText(theme.palette.secondary.main),
- backgroundColor: theme.palette.secondary.main,
- width: theme.spacing(5),
- height: theme.spacing(5),
- border: 0,
- "&:hover": {
- backgroundColor: theme.palette.secondary.main
- }
- },
- fab: {
- zIndex: 20,
- background: theme.palette.primary.main,
- "&:hover": {
- background: theme.palette.primary.main
- },
- "&:focus": {
- background: theme.palette.primary.main
- },
- position: "absolute",
- bottom: theme.spacing(8), //for mobile navigator
- right: theme.spacing(2),
- [theme.breakpoints.up("sm")]: {
- bottom: theme.spacing(2)
- }
- },
- overlayFab: {
- zIndex: 20,
- background: theme.palette.primary.main,
- "&:hover": {
- background: theme.palette.primary.main
- },
- "&:focus": {
- background: theme.palette.primary.main
- },
- position: "fixed",
- bottom: theme.spacing(8), //for mobile navigator
- right: theme.spacing(2),
- [theme.breakpoints.up("sm")]: {
- bottom: theme.spacing(2)
- }
- }
- });
-});
-
-export default function FieldTaskList(props: Props) {
- const { field } = props
- const [{ as }] = useGlobalState()
- const taskAPI = useTaskAPI()
- const classes = useStyles()
- const [selectedTask, setSelectedTask] = useState(undefined)
- const [openTaskDrawer, setOpenTaskDrawer] = useState(false)
- const [opentaskSettings, setOpenTaskSettings] = useState(false)
- const [taskMap, setTaskMap] = useState>(new Map())
-
- //load the tasks
- const loadTasks = useCallback(() => {
- console.log("loading tasks")
- taskAPI.listTasks(50, 0, "asc", "start", undefined, undefined, undefined, undefined, undefined, [field], ["field"]).then(resp => {
- if(resp.data.tasks){
- let newMap: Map = new Map()
- resp.data.tasks.forEach(task => {
- newMap.set(task.key, Task.create(task))
- })
- setTaskMap(newMap)
- }
- }).catch(err => {
-
- })
- },[as, field])
-
- useEffect(()=>{
- loadTasks()
- },[loadTasks])
-
- const deleteTask = (task: Task) => {
- taskAPI.removeTask(task.key).then(resp => {
- openSnackbar("success", "Task has been removed")
- loadTasks()
- }).catch(err => {
- openSnackbar("error", "There was a problem removing the task")
- })
- }
-
- const markComplete = (task: Task) => {
- let settings = cloneDeep(task.settings)
- settings.complete = !task.settings.complete
- taskAPI.updateTask(task.key, settings).then(resp => {
- openSnackbar("success", "Task has been updated")
- loadTasks()
- }).catch(err => {
- openSnackbar("error", "There was a problem updating the task")
- })
- }
- //return the task list
- return (
-
- {selectedTask &&
- {
- setOpenTaskDrawer(false)
- setSelectedTask(undefined)
- if (refresh) {
- loadTasks()
- }
- }}
- keys={[field]}
- types={["field"]}
- />
- }
- {
- let task = taskMap.get(id)
- if(task){
- setSelectedTask(task)
- setOpenTaskDrawer(true)
- }
- }}
- tasks={Array.from(taskMap.values())}
- reLoad={() => {
- loadTasks()
- }}
- keys={[field]}
- types={["field"]}
- />
- {
- setOpenTaskSettings(false)
- if(reload){
- loadTasks()
- }
- }}
- keys={[field]}
- types={["field"]}
- task={selectedTask}
- />
- {setOpenTaskSettings(true)}}
- aria-label="Create Task"
- className={classes.overlayFab}
- size={"medium"}>
-
-
-
- )
-}
\ No newline at end of file
diff --git a/src/gate/GateDeltaTempGraph.tsx b/src/gate/GateDeltaTempGraph.tsx
index d4a4465..d547b1c 100644
--- a/src/gate/GateDeltaTempGraph.tsx
+++ b/src/gate/GateDeltaTempGraph.tsx
@@ -11,6 +11,7 @@ import { pond } from "protobuf-ts/pond"
import { quack } from "protobuf-ts/quack"
import { useGlobalState } from "providers"
import { useEffect, useState } from "react"
+import { getTemperatureUnit } from "utils"
interface Props {
@@ -20,7 +21,6 @@ interface Props {
ambient?: Component
start: Moment;
end: Moment;
- multiGraphZoom?: (domain: number[] | string[]) => void;
}
export default function GateDeltaTempGraph(props: Props){
@@ -29,8 +29,7 @@ export default function GateDeltaTempGraph(props: Props){
ambient,
deviceID,
start,
- end,
- multiGraphZoom
+ end
} = props
const [data, setData] = useState([])
const [{user}] = useGlobalState()
@@ -112,6 +111,7 @@ export default function GateDeltaTempGraph(props: Props){
end.toISOString(),
500
).then(resp => {
+ console.log(resp)
if(resp.data.measurements){
let tempCompMeasurements = resp.data.measurements.map(um => UnitMeasurement.any(um, user));
//if there is an ambient component, then treat the temp component like a single sensor and not a chain, and use the ambient measurements as the inlet
@@ -190,7 +190,7 @@ export default function GateDeltaTempGraph(props: Props){
{"Delta Temp: "}
0 ? warmingColour : coolingColour, fontWeight: 500 }}>
- {recent.value.toFixed(2) + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C")}
+ {recent.value.toFixed(2) + (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C")}
@@ -211,15 +211,12 @@ export default function GateDeltaTempGraph(props: Props){
}
/>
+ colourBelowZero={{colour: coolingColour, label: "Cooling"}}/>
)
diff --git a/src/gate/GateDeviceInteraction.tsx b/src/gate/GateDeviceInteraction.tsx
index aa2e8a0..0303123 100644
--- a/src/gate/GateDeviceInteraction.tsx
+++ b/src/gate/GateDeviceInteraction.tsx
@@ -7,6 +7,8 @@ import moment from "moment";
import { pond, quack } from "protobuf-ts/pond";
import { useGlobalState, useInteractionsAPI, useSnackbar } from "providers";
import React, { useEffect, useState } from "react";
+import { getPressureUnit } from "utils";
+
interface Props {
open: boolean;
close: (canceled: boolean) => void;
@@ -98,6 +100,7 @@ export default function GateDeviceInteraction(props: Props) {
} else if (
gate.preferences[component.key()] === pond.GateComponentType.GATE_COMPONENT_TYPE_PRESSURE
) {
+ console.log(component)
setPressureComponent(component);
setPressureSource(
quack.ComponentID.create({
@@ -183,9 +186,9 @@ export default function GateDeviceInteraction(props: Props) {
//need to make sure the redthreshold is in pascals
let thresholdPascals = 0
if(!isNaN(parseFloat(redThreshold))){
- if(user.pressureUnit() === pond.PressureUnit.PRESSURE_UNIT_KILOPASCALS){
+ if(getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_KILOPASCALS){
thresholdPascals = parseFloat(redThreshold)*1000
- }else if (user.pressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER){
+ }else if (getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER){
thresholdPascals = parseFloat(redThreshold)*249
}
}
@@ -343,8 +346,8 @@ export default function GateDeviceInteraction(props: Props) {
when the difference between the pressures falls within this range and vice versa:
- Upper Limit: {user.pressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? (highDelta/249.089).toFixed(2) + " iwg" : highDelta/1000 + " kPa"}
- Lower Limit: {user.pressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? (lowDelta/249.089).toFixed(2) + " iwg" : lowDelta/1000 + " kPa"}
+ Upper Limit: {getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? (highDelta/249.089).toFixed(2) + " iwg" : highDelta/1000 + " kPa"}
+ Lower Limit: {getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? (lowDelta/249.089).toFixed(2) + " iwg" : lowDelta/1000 + " kPa"}
If you would like to have the interactions set in such a way that if the average pressure falls below a given value set the use off condition and set the value to use
@@ -369,7 +372,7 @@ export default function GateDeviceInteraction(props: Props) {
InputProps={{
endAdornment: (
- {user.pressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? "iwg" : "kPa"}
+ {getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? "iwg" : "kPa"}
)
}}
diff --git a/src/gate/GateFlowGraph.tsx b/src/gate/GateFlowGraph.tsx
index 458d377..b9a85b1 100644
--- a/src/gate/GateFlowGraph.tsx
+++ b/src/gate/GateFlowGraph.tsx
@@ -15,7 +15,7 @@ import { Gate } from "models/Gate";
import moment, { Moment } from "moment";
import { pond } from "protobuf-ts/pond";
import { useGateAPI, useGlobalState } from "providers";
-import React, { useCallback, useEffect, useState } from "react";
+import React, { useEffect, useState } from "react";
interface Props {
gate: Gate;
@@ -25,6 +25,7 @@ interface Props {
ambient?: string;
newXDomain?: number[] | string[];
pressureComponent?: string;
+ // setPCAState: (state: boolean) => void;
multiGraphZoom?: (domain: number[] | string[]) => void;
}
@@ -54,6 +55,7 @@ export default function GateFlowGraph(props: Props) {
device,
ambient,
pressureComponent,
+ // setPCAState,
start,
end,
newXDomain,
@@ -72,13 +74,12 @@ export default function GateFlowGraph(props: Props) {
const eventThreshold = 5; //the threshold that if crossed from one measurement to the next during a flow event will end the current flow event and start a new one
//const idleFlow = 3.5; //the mass air flow that must be crossed in order to start a flow event, anything below this will not start an event but must go below this to end an event
const [idleFlow, setIdleFlow] = useState(3.5)
-
- const loadData = useCallback(()=>{
+
+ useEffect(() => {
if (loadingChartData) return;
if (ambient && pressureComponent) {
let gateIdle = idleFlow
if(gate.settings.idleFlow){
- setIdleFlow(gate.settings.idleFlow)
gateIdle = gate.settings.idleFlow
}
@@ -130,17 +131,22 @@ export default function GateFlowGraph(props: Props) {
}
});
setRuntime(moment.duration(runtime));
+ // let state = false;
+ // if (
+ // data.length > 1 &&
+ // data[data.length - 1].value > gate.lowerFlow() &&
+ // data[data.length - 1].value < gate.upperFlow()
+ // ) {
+ // state = true;
+ // }
+ // setPCAState(state);
}
setFlowData(data);
setLoadingChartData(false);
setRecent(recent);
});
}
- },[gateAPI, gate, ambient, pressureComponent, start, end, device, as])
-
- useEffect(() => {
- loadData()
- }, [loadData]); // eslint-disable-line react-hooks/exhaustive-deps
+ }, [gateAPI, gate, ambient, pressureComponent, start, end, device, as]); // eslint-disable-line react-hooks/exhaustive-deps
const loadFlowEvents = () => {
if (ambient && pressureComponent) {
diff --git a/src/gate/GateGraphs.tsx b/src/gate/GateGraphs.tsx
index 319b137..b2ec455 100644
--- a/src/gate/GateGraphs.tsx
+++ b/src/gate/GateGraphs.tsx
@@ -101,22 +101,16 @@ export default function GateGraphs(props: Props) {
});
}, [gate, endDate, gateAPI, startDate, as]); // eslint-disable-line react-hooks/exhaustive-deps
- const updateDateRange = (newStartDate: moment.Moment, newEndDate: moment.Moment) => {
+ const updateDateRange = (newStartDate: any, newEndDate: any) => {
+ let range = GetDefaultDateRange();
+ range.start = newStartDate;
+ range.end = newEndDate;
setStartDate(newStartDate);
setEndDate(newEndDate);
};
- const zoomGraphs = (domain: string[] | number[]) => {
- setZoomed(true)
- if((domain[0] && !isNaN(parseInt(domain[0] as string))) && (domain[1] && !isNaN(parseInt(domain[1] as string)))){
- updateDateRange(moment(domain[0]),moment(domain[1]))
- }
- }
-
const zoomOut = () => {
- let d = GetDefaultDateRange()
- setStartDate(d.start)
- setEndDate(d.end)
+ setXDomain(["dataMin", "dataMax"]);
setZoomed(false);
};
@@ -181,7 +175,8 @@ export default function GateGraphs(props: Props) {
tooltip
newXDomain={xDomain}
multiGraphZoom={domain => {
- zoomGraphs(domain)
+ setXDomain(domain);
+ setZoomed(true);
}}
multiGraphZoomOut
/>
@@ -244,7 +239,8 @@ export default function GateGraphs(props: Props) {
tooltip
newXDomain={xDomain}
multiGraphZoom={domain => {
- zoomGraphs(domain)
+ setXDomain(domain);
+ setZoomed(true);
}}
multiGraphZoomOut
/>
@@ -254,6 +250,7 @@ export default function GateGraphs(props: Props) {
const graphHeader = (comp: Component) => {
const componentIcon = GetComponentIcon(comp.settings.type, comp.settings.subtype, themeType);
+ console.log(comp)
let measurement = cloneDeep(comp.status.measurement)
let umArray = measurement.map(um => UnitMeasurement.create(um, user))
return (
@@ -285,13 +282,13 @@ export default function GateGraphs(props: Props) {
if (um.values[0] && um.values[0].values.length > 1) {
return areaGraph(
um,
- describeMeasurement(um.type, component.type(), component.subType(), undefined, user),
+ describeMeasurement(um.type, component.type(), component.subType()),
component.settings.smoothingAverages
);
} else {
return lineGraph(
um,
- describeMeasurement(um.type, component.type(), component.subType(), undefined, user),
+ describeMeasurement(um.type, component.type(), component.subType()),
component.settings.smoothingAverages
);
}
@@ -359,22 +356,17 @@ export default function GateGraphs(props: Props) {
gate={gate}
ambient={ambient}
pressureComponent={pressure}
+ // setPCAState={(state: boolean) => {
+ // setPCAState(state);
+ // }}
multiGraphZoom={domain => {
- zoomGraphs(domain)
+ setXDomain(domain);
+ setZoomed(true);
}}
/>
{/* add a new chart here for the delta temp over time */}
{tempComp &&
- {
- zoomGraphs(domain)
- }}
- />
+
}
);
diff --git a/src/gate/GateList.tsx b/src/gate/GateList.tsx
index a2f4f77..5910808 100644
--- a/src/gate/GateList.tsx
+++ b/src/gate/GateList.tsx
@@ -17,6 +17,7 @@ import moment from "moment";
import { pond } from "protobuf-ts/pond";
import { UnitMeasurement } from "models/UnitMeasurement";
import { quack } from "protobuf-ts/quack";
+import { getTemperatureUnit } from "utils";
import { teal } from "@mui/material/colors";
import { react } from "@babel/types";
import { getDeviceStateHelper } from "pbHelpers/DeviceState";
@@ -228,7 +229,7 @@ export default function GateList(props: Props) {
})
if(ambientTemp && outletTemp){
let deltaTemp = outletTemp - ambientTemp
- display = deltaTemp.toFixed(2) + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C")
+ display = deltaTemp.toFixed(2) + (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C")
}else{
display = "Sensor Missing"
}
@@ -272,12 +273,7 @@ export default function GateList(props: Props) {
// cellStyle: {width: 100000},
// sortKey: "state",
render: gate => {
- let deviceStateHelper = getDeviceStateHelper(pond.DeviceState.DEVICE_STATE_UNKNOWN);
- if(moment().isAfter(moment(gate.status.deviceStateExpires).add(1, 'hour'))){
- deviceStateHelper = getDeviceStateHelper(pond.DeviceState.DEVICE_STATE_MISSING)
- }else{
- deviceStateHelper = getDeviceStateHelper(gate.status.pcaDeviceState)
- }
+ const deviceStateHelper = getDeviceStateHelper(gate.status.pcaDeviceState);
return (
{deviceStateHelper.icon}
diff --git a/src/grain/ChangeGrainDialog.tsx b/src/grain/ChangeGrainDialog.tsx
deleted file mode 100644
index 2910b25..0000000
--- a/src/grain/ChangeGrainDialog.tsx
+++ /dev/null
@@ -1,175 +0,0 @@
-import { Box, Button, DialogActions, DialogContent, DialogTitle, Grid2, Switch, TextField } from "@mui/material";
-import ResponsiveDialog from "common/ResponsiveDialog";
-import SearchSelect, { Option } from "common/SearchSelect";
-import { Bin } from "models";
-import { pond } from "protobuf-ts/pond";
-import GrainDescriber, { GrainOptions, ToGrainOption } from "grain/GrainDescriber";
-import { useEffect, useMemo, useState } from "react";
-import React from "react";
-import CustomGrainSelector from "./CustomGrainSelector";
-import { cloneDeep } from "lodash";
-import { useBinAPI, useSnackbar } from "providers";
-
-interface Props {
- bin: Bin
- open: boolean
- closeDialog: () => void
- permissions: pond.Permission[]
- /**
- * this function can be passed in if you want to just return a new bin with the changes and handle the actual update in the parent,
- * it will stop the component from doing the update itself
- * @param bin the bin with the new changes
- * @returns
- */
- updateBin?: (bin: Bin) => void
- /**
- * this function can be passed in as a callback function if the component handles the update
- * @param updated whether the bin api was used to update the bin
- * @returns
- */
- updateCallback?: (bin: Bin) => void
-}
-
-export default function ChangeGrainDialog(props: Props){
- const { bin, open, permissions, closeDialog, updateBin, updateCallback } = props
- const canEdit = useMemo(()=>{
- return permissions ? permissions.includes(pond.Permission.PERMISSION_WRITE) : false;
- },[permissions])
-
- const [isCustom, setIsCustom] = useState(false)
- const grainOptions = GrainOptions();
- const [grainOption, setGrainOption] = useState();
- const [newGrainType, setNewGrainType] = useState(0);
- const [newGrainSettings, setNewGrainSettings] = useState()
- const binAPI = useBinAPI()
- const {openSnack} = useSnackbar()
- const [grainSubtype, setGrainSubtype] = useState("")
-
- useEffect(()=>{
- setGrainOption(ToGrainOption(bin.grain()))
- setIsCustom(bin.grain() === pond.Grain.GRAIN_CUSTOM)
- setGrainSubtype(bin.subtype())
- setNewGrainType(bin.grain())
- },[bin])
-
- /**
- * this function is where the split happens,
- * if update bin is passed in it will just clone the bin, change the settings and pass the updated clone to the parent so that it can handle the api call
- * otherwise the component will update the bin itself through the bin api and upon completion pass that updated bin to the parent if successfull
- * in the event both functions are passed in the parent update will be given priority and the internal update will not occur
- */
- const updateBinSettings = () => {
- let clone = cloneDeep(bin)
- let inventory = clone.settings.inventory ?? pond.BinInventory.create()
- inventory.grainType = newGrainType
- inventory.customGrain = newGrainSettings
- inventory.grainSubtype = grainSubtype
- if(updateBin){
- //simply pass the clone up to the parent and it can deal with it
- updateBin(clone)
- }else if (updateCallback) {
- //use the actual bin api to update the bin inside the component and upon completion return the updated bin
- binAPI.updateBin(bin.key(), clone.settings).then(resp => {
- openSnack("Updated the grain type")
- updateCallback(clone)
- }).catch(err => {
- openSnack("There was a problem changing the grain type")
- })
- }
- }
-
- //closes the dialog
- const close = () => {
- closeDialog()
- }
-
- return (
-
- Change Grain Type
-
-
-
-
- Grain
-
- {
- setIsCustom(checked)
- if(checked){
- setNewGrainType(pond.Grain.GRAIN_CUSTOM)
- }else{
- setNewGrainType(bin.grain())
- setNewGrainSettings(undefined)
- }
- }}
- name="storage"
- />
-
- Custom
-
-
-
- {!isCustom && (
-
- {
- let newGrainType = option
- ? pond.Grain[option.value as keyof typeof pond.Grain]
- : pond.Grain.GRAIN_INVALID;
- setGrainOption(ToGrainOption(newGrainType));
- setNewGrainType(newGrainType);
- //setBushPerTonne(GrainDescriber(newGrainType).bushelsPerTonne.toFixed(2));
- }}
- group
- disabled={!canEdit}
- options={grainOptions}
- />
-
- )}
- {isCustom ? (
-
- {setNewGrainSettings(settings)}}/>
-
- ) : (
- {
- setGrainSubtype(event.target.value);
- }}
- fullWidth
- variant="outlined"
- disabled={!grainOption}
- />
- )}
-
-
- {
- close();
- }}>
- Cancel
-
- {
- updateBinSettings();
- close()
- }}>
- Confirm
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/grain/CustomGrainForm.tsx b/src/grain/CustomGrainForm.tsx
index 8d5fd42..a13478c 100644
--- a/src/grain/CustomGrainForm.tsx
+++ b/src/grain/CustomGrainForm.tsx
@@ -2,7 +2,8 @@ import {MenuItem, TextField } from "@mui/material"
import { cloneDeep } from "lodash"
import { pond } from "protobuf-ts/pond"
import React, { useEffect, useRef, useState } from "react"
-import { useGlobalState } from "providers";
+import { getGrainUnit } from "utils"
+
interface Props {
grainSettings?: pond.GrainSettings
onGrainSettingsChange: (settings: pond.GrainSettings, formValid: boolean) => void
@@ -10,7 +11,6 @@ interface Props {
export default function CustomGrainForm(props: Props) {
const {grainSettings, onGrainSettingsChange} = props
- const [{ user }] = useGlobalState();
const [newGrainSettings, setNewGrainSettings] = useState(pond.GrainSettings.create())
const [name, setName] = useState("")
const [group, setGroup] = useState("")
@@ -204,7 +204,7 @@ export default function CustomGrainForm(props: Props) {
valid.current = validate(name, group, constantA, constantB, constantC, kgPerBushel, val)
let settings = cloneDeep(newGrainSettings)
if(!isNaN(parseFloat(val))){
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
settings.bushelsPerTonne = parseFloat(val)/0.907
}else{
settings.bushelsPerTonne = parseFloat(val)
@@ -212,7 +212,7 @@ export default function CustomGrainForm(props: Props) {
}
settingsChanged(settings)
}}
- label={"Bushels per " + (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE ? " Tonne" : "Ton")}/>
+ label={"Bushels per " + (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE ? " Tonne" : "Ton")}/>
)
}
diff --git a/src/grain/GrainDescriber.ts b/src/grain/GrainDescriber.ts
index 5db2cd5..58a8766 100644
--- a/src/grain/GrainDescriber.ts
+++ b/src/grain/GrainDescriber.ts
@@ -14,11 +14,12 @@ import WheatImg from "assets/grain/wheat.jpg";
import { Option } from "common/SearchSelect";
import { cloneDeep } from "lodash";
import { pond } from "protobuf-ts/pond";
+import { Equation } from "./GrainMoisture";
export interface GrainExtension {
name: string;
group: string;
- equation: pond.MoistureEquation;
+ equation: Equation;
a: number;
b: number;
c: number;
@@ -37,7 +38,7 @@ const defaultSetTemp = 30.0;
const defaultGrain: GrainExtension = {
name: "None",
group: "",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_NONE,
+ equation: Equation.none,
a: 0,
b: 0,
c: 0,
@@ -57,7 +58,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Custom Type",
group: "",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_NONE,
+ equation: Equation.none,
a: 0,
b: 0,
c: 0,
@@ -74,7 +75,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Barley",
group: "Barley",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
+ equation: Equation.chungPfost,
a: 475.12,
b: 0.14843,
c: 71.996,
@@ -92,7 +93,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Buckwheat",
group: "Buckwheat",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
+ equation: Equation.chungPfost,
a: 103540000,
b: 0.1646,
c: 15853000,
@@ -110,7 +111,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Canola",
group: "Canola",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
+ equation: Equation.halsey,
a: 3.489,
b: -0.010553,
c: 1.86,
@@ -129,7 +130,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Rapeseed",
group: "Canola",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
+ equation: Equation.halsey,
a: 3.0026,
b: -0.0048967,
c: 1.7607,
@@ -147,7 +148,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Corn (Henderson)",
group: "Corn",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
+ equation: Equation.henderson,
a: 0.000066612,
b: 1.9677,
c: 42.143,
@@ -165,7 +166,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Corn (Chung-Pfost)",
group: "Corn",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
+ equation: Equation.chungPfost,
a: 374.34,
b: 0.18662,
c: 31.696,
@@ -183,7 +184,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Corn (Oswin)",
group: "Corn",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
+ equation: Equation.oswin,
a: 15.303,
b: -0.10164,
c: 3.0358,
@@ -201,7 +202,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Maize White",
group: "Corn",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
+ equation: Equation.henderson,
a: 0.000066612,
b: 1.9677,
c: 70.143,
@@ -219,7 +220,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Maize Yellow",
group: "Corn",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
+ equation: Equation.henderson,
a: 0.000066612,
b: 1.9677,
c: 65.143,
@@ -237,7 +238,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Oats (Henderson)",
group: "Oats",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
+ equation: Equation.henderson,
a: 0.000085511,
b: 2.0087,
c: 37.811,
@@ -255,7 +256,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Oats (Chung-Pfost)",
group: "Oats",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
+ equation: Equation.chungPfost,
a: 442.85,
b: 0.21228,
c: 35.803,
@@ -273,7 +274,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Oats (Oswin)",
group: "Oats",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
+ equation: Equation.oswin,
a: 12.412,
b: -0.060707,
c: 2.9397,
@@ -291,7 +292,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Peanuts",
group: "Peanuts",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
+ equation: Equation.oswin,
a: 8.6588,
b: -0.057904,
c: 2.6204,
@@ -309,7 +310,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Long Grain Rice",
group: "Rice",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
+ equation: Equation.henderson,
a: 0.000041276,
b: 2.1191,
c: 49.828,
@@ -327,7 +328,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Medium Grain Rice",
group: "Rice",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
+ equation: Equation.henderson,
a: 0.000035502,
b: 2.31,
c: 27.396,
@@ -345,7 +346,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Short Grain Rice",
group: "Rice",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
+ equation: Equation.henderson,
a: 0.000048524,
b: 2.0794,
c: 45.646,
@@ -363,7 +364,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Sorghum",
group: "Sorghum",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
+ equation: Equation.chungPfost,
a: 797.33,
b: 0.18159,
c: 52.238,
@@ -381,7 +382,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Soybeans",
group: "Soybeans",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
+ equation: Equation.chungPfost,
a: 228.2,
b: 0.2072,
c: 30,
@@ -399,7 +400,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Sunflower",
group: "Sunflower",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
+ equation: Equation.henderson,
a: 0.00031,
b: 1.7459,
c: 66.603,
@@ -417,7 +418,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Durum Wheat",
group: "Wheat",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
+ equation: Equation.oswin,
a: 13.101,
b: -0.052626,
c: 2.9987,
@@ -435,7 +436,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Hard Red Wheat",
group: "Wheat",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
+ equation: Equation.chungPfost,
a: 610.34,
b: 0.15526,
c: 93.213,
@@ -453,7 +454,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Wheat SAWOS",
group: "Wheat",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
+ equation: Equation.chungPfost,
a: 610.34,
b: 0.15526,
c: 93.213,
@@ -471,7 +472,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Un-retted Flax",
group: "Flax",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
+ equation: Equation.halsey,
a: 5.11,
b: Math.pow(-8.46 * 10, -3),
c: 2.26,
@@ -489,7 +490,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Dew-retted Flax",
group: "Flax",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
+ equation: Equation.oswin,
a: 6.5,
b: Math.pow(-1.68 * 10, -2),
c: 3.2,
@@ -507,7 +508,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Yellow Peas",
group: "Peas",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
+ equation: Equation.oswin,
a: 14.81,
b: -0.109,
c: 3.019,
@@ -525,7 +526,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Blaze Lentils",
group: "Lentils",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
+ equation: Equation.halsey,
a: 5.39,
b: -0.015,
c: 2.273,
@@ -543,7 +544,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Redberry Lentils",
group: "Lentils",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
+ equation: Equation.halsey,
a: 4.749,
b: -0.0116,
c: 2.066,
@@ -561,7 +562,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Robin Lentils",
group: "Lentils",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
+ equation: Equation.halsey,
a: 5.176,
b: -0.0065,
c: 2.337,
@@ -579,7 +580,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Dry Beans Red",
group: "Dry Beans",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
+ equation: Equation.halsey,
a: 4.2669,
b: -0.013382,
c: 1.6933,
@@ -595,7 +596,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Dry Beans Black",
group: "Dry Beans",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
+ equation: Equation.halsey,
a: 5.2003,
b: -0.022685,
c: 1.9656,
@@ -611,7 +612,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Rye (Henderson)",
group: "Rye",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
+ equation: Equation.henderson,
a: 0.00006343,
b: 2.2060,
c: 13.1810,
@@ -627,7 +628,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Rye (Chung-Pfost)",
group: "Rye",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
+ equation: Equation.chungPfost,
a: 461.0230,
b: 0.1840,
c: 36.7410,
@@ -643,7 +644,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Rye (Halsey)",
group: "Rye",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
+ equation: Equation.halsey,
a: 4.2970,
b: 0.380,
c: 2.2710,
@@ -659,7 +660,7 @@ export const GrainExtensions: Map = new Map([
{
name: "Rye (Oswin)",
group: "Rye",
- equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
+ equation: Equation.oswin,
a: 11.8870,
b: 0.0210,
c: 3.2620,
diff --git a/src/grain/GrainMoisture.ts b/src/grain/GrainMoisture.ts
index 28b6387..808f302 100644
--- a/src/grain/GrainMoisture.ts
+++ b/src/grain/GrainMoisture.ts
@@ -1,6 +1,14 @@
import { pond } from "protobuf-ts/pond";
import GrainDescriber from "./GrainDescriber";
+export enum Equation {
+ "none",
+ "oswin",
+ "halsey",
+ "henderson",
+ "chungPfost"
+}
+
const toERH = (humidity: number): number => {
return humidity >= 100 ? 0.99999 : humidity / 100;
};
diff --git a/src/grain/GrainTransaction.tsx b/src/grain/GrainTransaction.tsx
index d7395e5..5605b3a 100644
--- a/src/grain/GrainTransaction.tsx
+++ b/src/grain/GrainTransaction.tsx
@@ -29,6 +29,7 @@ import {
useTransactionAPI
} from "providers";
import { useState, useEffect } from "react";
+import { getGrainUnit } from "utils";
interface Props {
mainObject: Bin | GrainBag | Field | Contract;
@@ -37,7 +38,7 @@ interface Props {
asDestination?: boolean;
restrictMatching?: boolean;
open: boolean;
- close: (confirmed?: boolean) => void;
+ close: () => void;
callback?: (newTransaction?: pond.Transaction) => void;
allowAttachmentUploads?: boolean;
}
@@ -82,7 +83,7 @@ export default function GrainTransaction(props: Props) {
const [filePermission, setFilePermission] = useState(false);
const closeDialogs = (confirmed: boolean, newTransaction?: pond.Transaction) => {
- close(confirmed);
+ close();
setGrainChangeVal("0");
setGrainEntry("0");
setIsObject("");
@@ -450,7 +451,7 @@ export default function GrainTransaction(props: Props) {
};
const grainUnitDisplay = () => {
- switch (user.grainUnit()){
+ switch (getGrainUnit()){
case pond.GrainUnit.GRAIN_UNIT_TONNE:
return "mT"
case pond.GrainUnit.GRAIN_UNIT_TON:
@@ -524,7 +525,7 @@ export default function GrainTransaction(props: Props) {
onChange={e => {
//if the user is viewing the grain in weight
let grainVal = e.target.value;
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE) {
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE) {
//need to convert what the user input (weight) into what is actually stored (bushels)
//use source of the conversion value if it was selected, otherwise use the destination
if (!isNaN(parseFloat(e.target.value))) {
@@ -534,7 +535,7 @@ export default function GrainTransaction(props: Props) {
grainVal = (+grainVal * selectedDestination.value.bushelsPerTonne()).toFixed(2);
}
}
- }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) {
+ }else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) {
if (!isNaN(parseFloat(e.target.value))) {
if (selectedSource) {
if(selectedSource.value.bushelsPerTonne){
diff --git a/src/grainBag/grainBagInventoryGraph.tsx b/src/grainBag/grainBagInventoryGraph.tsx
index 969ab3c..6a661a2 100644
--- a/src/grainBag/grainBagInventoryGraph.tsx
+++ b/src/grainBag/grainBagInventoryGraph.tsx
@@ -6,6 +6,7 @@ import moment, { Moment } from "moment";
import { pond } from "protobuf-ts/pond";
import { useGlobalState, useGrainBagAPI, useSnackbar } from "providers";
import { useEffect, useState } from "react";
+import { getGrainUnit } from "utils";
interface Props {
grainBag: GrainBag;
@@ -20,7 +21,7 @@ export default function GrainBagInventoryGraph(props: Props) {
const [data, setData] = useState([]);
const [loadingData, setLoadingData] = useState(false);
const { openSnack } = useSnackbar();
- const [{as, user}] = useGlobalState();
+ const [{as}] = useGlobalState();
useEffect(() => {
if (grainBag.key() && !loadingData) {
@@ -35,12 +36,12 @@ export default function GrainBagInventoryGraph(props: Props) {
//let time = hist.timestamp;
let val = hist.object.grainBagSettings.currentBushels ?? 0;
if (
- user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE &&
+ getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE &&
grainBag.bushelsPerTonne() > 1
) {
val = Math.round((val / grainBag.bushelsPerTonne()) * 100) / 100;
}
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){
val = Math.round((val / (grainBag.bushelsPerTonne() * 0.907)) * 100) / 100;
}
if (val !== lastBushels) {
@@ -54,10 +55,10 @@ export default function GrainBagInventoryGraph(props: Props) {
});
if (barData.length === 0) {
let val = grainBag.bushels()
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1){
val = grainBag.bushels() / grainBag.bushelsPerTonne()
}
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){
val = grainBag.bushels() / (grainBag.bushelsPerTonne() * 0.907)
}
barData.push({
@@ -78,10 +79,10 @@ export default function GrainBagInventoryGraph(props: Props) {
const maxYAxis = () => {
let val = grainBag.capacity()
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
val = Math.round((val / grainBag.bushelsPerTonne()) * 100) / 100;
}
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){
val = Math.round((val / (grainBag.bushelsPerTonne() * 0.907)) * 100) / 100;
}
return val
diff --git a/src/grainBag/grainBagSettings.tsx b/src/grainBag/grainBagSettings.tsx
index 1ec5f2d..cf5f703 100644
--- a/src/grainBag/grainBagSettings.tsx
+++ b/src/grainBag/grainBagSettings.tsx
@@ -22,6 +22,7 @@ import { pond } from "protobuf-ts/pond";
import { useGlobalState, useGrainBagAPI, useSnackbar } from "providers";
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
+import { getGrainUnit } from "utils";
const useStyles = makeStyles((theme: Theme) => {
return ({
@@ -83,9 +84,9 @@ export default function GrainBagSettings(props: Props) {
: grainBag.settings.length
);
let grainVal = grainBag.bushels();
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE) {
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE) {
grainVal = grainBag.bushels() / grainBag.bushelsPerTonne();
- }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) {
+ }else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) {
grainVal = grainBag.bushels() / (grainBag.bushelsPerTonne() * 0.907)
}
setGrainFill(grainVal.toFixed(2));
@@ -119,7 +120,7 @@ export default function GrainBagSettings(props: Props) {
const submit = () => {
//if a bag was passed in do an update
let tonneConversion = bushelConversion
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
tonneConversion = bushelConversion / 0.907
}
if (grainBag) {
@@ -254,9 +255,9 @@ export default function GrainBagSettings(props: Props) {
const grainUnitDisplay = () => {
if(bushelConversion > 1){
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
return "mT"
- }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
+ }else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
return "t"
}
}else{
@@ -421,7 +422,7 @@ export default function GrainBagSettings(props: Props) {
value={grainFill}
onChange={e => {
let bushelVal = +e.target.value;
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) {
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) {
//convert the number as a weight into the bushel value, no changing or conversions necessary
// since these are both user entered fields and should be the same unit (ton or tonne)
bushelVal = +e.target.value * (bushelConversion > 0 ? bushelConversion : 1);
diff --git a/src/grainBag/grainBagVisualizer.tsx b/src/grainBag/grainBagVisualizer.tsx
index 319ad39..1d0b5d8 100644
--- a/src/grainBag/grainBagVisualizer.tsx
+++ b/src/grainBag/grainBagVisualizer.tsx
@@ -19,9 +19,9 @@ import moment from "moment";
import { pond } from "protobuf-ts/pond";
import { useEffect, useState } from "react";
import { getThemeType } from "theme";
+import { getGrainUnit } from "utils";
import GrainBagSVG from "./grainBagSVG";
import { makeStyles } from "@mui/styles";
-import { useGlobalState } from "providers";
interface Props {
grainBag: GrainBag;
@@ -68,7 +68,6 @@ export default function GrainBagVisualizer(props: Props) {
const [grainDiff, setGrainDiff] = useState();
const theme = useTheme();
const [openTransaction, setOpenTransaction] = useState(false);
- const [{ user }] = useGlobalState();
useEffect(() => {
setFillLevel((grainBag.bushels() / grainBag.capacity()) * 100);
@@ -84,13 +83,13 @@ export default function GrainBagVisualizer(props: Props) {
const grainOverlay = () => {
let displayPending = pendingGrainAmount;
let displayDiff = grainDiff;
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1) {
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1) {
if (displayPending && displayDiff) {
displayPending = Math.round((displayPending / grainBag.bushelsPerTonne()) * 100) / 100;
displayDiff = Math.round((displayDiff / grainBag.bushelsPerTonne()) * 100) / 100;
}
}
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1) {
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1) {
if (displayPending && displayDiff) {
displayPending = Math.round((displayPending / (grainBag.bushelsPerTonne() * 0.907)) * 100) / 100;
displayDiff = Math.round((displayDiff / (grainBag.bushelsPerTonne()* 0.907)) * 100) / 100;
@@ -149,7 +148,7 @@ export default function GrainBagVisualizer(props: Props) {
};
const grainAmountDisplay = () => {
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1) {
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1) {
return (
@@ -158,7 +157,7 @@ export default function GrainBagVisualizer(props: Props) {
({grainBag.fillPercent()}%)
);
- } else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1) {
+ } else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1) {
return (
diff --git a/src/harvestPlan/HarvestPlanDisplay.tsx b/src/harvestPlan/HarvestPlanDisplay.tsx
index 5f9721b..6917332 100644
--- a/src/harvestPlan/HarvestPlanDisplay.tsx
+++ b/src/harvestPlan/HarvestPlanDisplay.tsx
@@ -151,7 +151,7 @@ export default function HarvestPlanDisplay(props: Props) {
onClose={() => setOpenNote(false)}>
Notes
-
+
);
diff --git a/src/hooks/FullScreenHandle.tsx b/src/hooks/FullScreenHandle.tsx
deleted file mode 100644
index 9e4323b..0000000
--- a/src/hooks/FullScreenHandle.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-import { useCallback, useEffect, useRef, useState } from "react";
-
-/**
- * Drop-in replacement for react-full-screen's useFullScreenHandle + FullScreen component.
- *
- * Motivation: react-full-screen 1.1.1 doesn't debounce fullscreenchange events, so on
- * Windows systems with DPI scaling > 100%, the resize triggered during fullscreen entry
- * fires a fullscreenchange event that the library misinterprets as an exit, immediately
- * popping back out of fullscreen.
- *
- * This hook bypasses the library entirely and calls the native Fullscreen API directly,
- * ignoring fullscreenchange events fired within DEBOUNCE_MS of entry to absorb the
- * DPI-scaling resize blip.
- *
- * Usage — replace in BinVisualizerV2.tsx:
- *
- * // Remove:
- * import { FullScreen, useFullScreenHandle } from "react-full-screen";
- * const fullScreenHandler = useFullScreenHandle();
- * ...
- *
- * // Add:
- * import { useFullScreen } from "hooks/useFullScreen";
- * const { fullScreenHandler, FullScreenWrapper } = useFullScreen();
- * ...
- *
- * // Everything else (fullScreenHandler.active, .enter(), .exit()) stays the same.
- */
-
-const DEBOUNCE_MS = 500;
-
-export interface FullScreenHandle {
- active: boolean;
- enter: () => void;
- exit: () => void;
-}
-
-export function useFullScreen(): {
- fullScreenHandler: FullScreenHandle;
- FullScreenWrapper: React.FC<{ children: React.ReactNode }>;
-} {
- const [active, setActive] = useState(false);
- const containerRef = useRef(null);
- // Timestamp of the last enter() call — used to debounce spurious exit events
- const enterTimeRef = useRef(0);
-
- const enter = useCallback(() => {
- const el = containerRef.current;
- if (!el) return;
- enterTimeRef.current = Date.now();
- if (el.requestFullscreen) {
- el.requestFullscreen().catch(() => {
- // Some browsers (e.g. iOS Safari) reject the promise — silently ignore
- });
- } else if ((el as any).webkitRequestFullscreen) {
- (el as any).webkitRequestFullscreen();
- } else if ((el as any).mozRequestFullScreen) {
- (el as any).mozRequestFullScreen();
- } else if ((el as any).msRequestFullscreen) {
- (el as any).msRequestFullscreen();
- }
- }, []);
-
- const exit = useCallback(() => {
- if (document.exitFullscreen) {
- document.exitFullscreen().catch(() => {});
- } else if ((document as any).webkitExitFullscreen) {
- (document as any).webkitExitFullscreen();
- } else if ((document as any).mozCancelFullScreen) {
- (document as any).mozCancelFullScreen();
- } else if ((document as any).msExitFullscreen) {
- (document as any).msExitFullscreen();
- }
- }, []);
-
- useEffect(() => {
- const handleChange = () => {
- const fullscreenEl =
- document.fullscreenElement ||
- (document as any).webkitFullscreenElement ||
- (document as any).mozFullScreenElement ||
- (document as any).msFullscreenElement;
-
- const isNowFullscreen = fullscreenEl === containerRef.current;
-
- // If we just called enter() and this event fires within DEBOUNCE_MS,
- // and it looks like an exit, ignore it — it's the DPI-scaling resize blip
- if (!isNowFullscreen && Date.now() - enterTimeRef.current < DEBOUNCE_MS) {
- return;
- }
-
- setActive(isNowFullscreen);
- };
-
- document.addEventListener("fullscreenchange", handleChange);
- document.addEventListener("webkitfullscreenchange", handleChange);
- document.addEventListener("mozfullscreenchange", handleChange);
- document.addEventListener("MSFullscreenChange", handleChange);
-
- return () => {
- document.removeEventListener("fullscreenchange", handleChange);
- document.removeEventListener("webkitfullscreenchange", handleChange);
- document.removeEventListener("mozfullscreenchange", handleChange);
- document.removeEventListener("MSFullscreenChange", handleChange);
- };
- }, []);
-
- const FullScreenWrapper: React.FC<{ children: React.ReactNode }> = useCallback(
- ({ children }) => (
-
- {children}
-
- ),
- []
- );
-
- return {
- fullScreenHandler: { active, enter, exit },
- FullScreenWrapper,
- };
-}
\ No newline at end of file
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/useDeviceStatusStreams.ts b/src/hooks/useDeviceStatusStreams.ts
index 76d7c68..24ca8bb 100644
--- a/src/hooks/useDeviceStatusStreams.ts
+++ b/src/hooks/useDeviceStatusStreams.ts
@@ -1,9 +1,18 @@
import { pond } from "protobuf-ts/pond";
-import { useEffect, useRef, useMemo } from "react";
-import { getWsBaseUrl } from "utils/getWsBaseUrl";
+import { useEffect, useRef, useCallback, useMemo } from "react";
-/** Off by default to reduce server load; set VITE_ENABLE_WEBSOCKETS=true to enable live streams. */
-const WEBSOCKETS_ENABLED = import.meta.env.VITE_ENABLE_WEBSOCKETS === "true";
+/**
+ * Derives the WebSocket base URL from VITE_APP_API_URL.
+ * Matches the logic in useWebSocket.ts.
+ */
+function getWsBaseUrl(): string {
+ const apiUrl = import.meta.env.VITE_APP_API_URL;
+ if (apiUrl) {
+ return apiUrl.replace(/^http/, "ws");
+ }
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
+ return `${protocol}//${window.location.host}`;
+}
export interface UseDeviceStatusStreamsOptions {
/** Device IDs to stream status for (e.g. from the visible devices list) */
@@ -12,12 +21,6 @@ export interface UseDeviceStatusStreamsOptions {
onStatusUpdate: (deviceId: string, status: pond.DeviceStatus) => void;
/** Auth token passed as ?token= query param */
token?: string;
- /** Same as REST/listDevices (?keys=) — required when listing under a context path or group tab */
- keys?: string[];
- /** Same as REST (?types=) */
- types?: string[];
- /** View-as / impersonation (?as=), same as device list when not already team-first in URL */
- as?: string;
/** Whether the connections should be active. Default true. */
enabled?: boolean;
}
@@ -28,7 +31,7 @@ export interface UseDeviceStatusStreamsOptions {
* Readings (plenum, sen5x, co, co2, no2, o2, etc.) stream in real time.
*/
export function useDeviceStatusStreams(options: UseDeviceStatusStreamsOptions) {
- const { deviceIds, onStatusUpdate, token, keys, types, as, enabled = true } = options;
+ const { deviceIds, onStatusUpdate, token, enabled = true } = options;
const onStatusUpdateRef = useRef(onStatusUpdate);
onStatusUpdateRef.current = onStatusUpdate;
@@ -36,67 +39,25 @@ export function useDeviceStatusStreams(options: UseDeviceStatusStreamsOptions) {
const wsMapRef = useRef>(new Map());
const retriesRef = useRef>(new Map());
const reconnectTimeoutsRef = useRef>>(new Map());
- /** Bumps only in effect cleanup so late onclose / reconnect timers never run after unmount. */
- const generationRef = useRef(0);
- /** Latest ID set so a device dropped from the list does not reconnect on abnormal close. */
- const allowedIdsRef = useRef>(new Set());
- allowedIdsRef.current = new Set(deviceIds);
- // Normalize for stable comparison: same set of IDs = same string regardless of order
- const deviceIdsKey = useMemo(
- () => [...new Set(deviceIds)].sort().join(","),
- [deviceIds.join(",")]
- );
-
- useEffect(() => {
- const clearReconnect = (deviceId: string) => {
- const t = reconnectTimeoutsRef.current.get(deviceId);
- if (t) {
- clearTimeout(t);
- reconnectTimeoutsRef.current.delete(deviceId);
- }
- };
-
- const shutdownAll = () => {
- reconnectTimeoutsRef.current.forEach((t) => clearTimeout(t));
- reconnectTimeoutsRef.current.clear();
- wsMapRef.current.forEach((ws) => ws.close(1000, "cleanup"));
- wsMapRef.current.clear();
- retriesRef.current.clear();
- };
-
- if (!WEBSOCKETS_ENABLED || !enabled || !token || deviceIds.length === 0) {
- shutdownAll();
- return () => {
- generationRef.current += 1;
- };
- }
-
- const gen = generationRef.current;
-
- const connect = (deviceId: string) => {
+ const connect = useCallback(
+ (deviceId: string) => {
if (!token || !deviceId) return;
- if (gen !== generationRef.current) return;
const params = new URLSearchParams();
params.set("token", token);
- if (keys?.length) params.set("keys", keys.toString());
- if (types?.length) params.set("types", types.toString());
- if (as) params.set("as", as);
- const path = `/v1/live/devices/${deviceId}/status`;
+ const path = `/live/devices/${deviceId}/status`;
const url = `${getWsBaseUrl()}${path}?${params.toString()}`;
const ws = new WebSocket(url);
wsMapRef.current.set(deviceId, ws);
ws.onopen = () => {
- if (gen !== generationRef.current) return;
console.debug(`[ws] connected: ${path}`);
retriesRef.current.set(deviceId, 0);
};
ws.onmessage = (event) => {
- if (gen !== generationRef.current) return;
try {
const raw = JSON.parse(event.data);
const status = pond.DeviceStatus.fromObject(raw ?? {});
@@ -113,15 +74,7 @@ export function useDeviceStatusStreams(options: UseDeviceStatusStreamsOptions) {
ws.onclose = (event) => {
wsMapRef.current.delete(deviceId);
- if (gen !== generationRef.current) {
- return;
- }
-
- if (!allowedIdsRef.current.has(deviceId)) {
- retriesRef.current.delete(deviceId);
- return;
- }
-
+ // Don't reconnect on clean close or auth rejection
if (event.code === 1000 || event.code === 4001 || event.code === 4003) {
return;
}
@@ -131,36 +84,55 @@ export function useDeviceStatusStreams(options: UseDeviceStatusStreamsOptions) {
retriesRef.current.set(deviceId, retries + 1);
console.debug(`[ws] reconnecting ${path} in ${delay}ms...`);
- const timeout = setTimeout(() => {
- if (gen !== generationRef.current) return;
- if (!allowedIdsRef.current.has(deviceId)) return;
- reconnectTimeoutsRef.current.delete(deviceId);
- connect(deviceId);
- }, delay);
+ const timeout = setTimeout(() => connect(deviceId), delay);
reconnectTimeoutsRef.current.set(deviceId, timeout);
};
- };
+ },
+ [token]
+ );
+
+ // Normalize for stable comparison: same set of IDs = same string regardless of order
+ const deviceIdsKey = useMemo(
+ () => [...new Set(deviceIds)].sort().join(","),
+ [deviceIds.join(",")]
+ );
+
+ useEffect(() => {
+ if (!enabled || !token || deviceIds.length === 0) {
+ wsMapRef.current.forEach((ws) => ws.close(1000, "disabled"));
+ wsMapRef.current.clear();
+ reconnectTimeoutsRef.current.forEach((t) => clearTimeout(t));
+ reconnectTimeoutsRef.current.clear();
+ return;
+ }
const currentIds = new Set(deviceIds);
+ // Connect to new devices
deviceIds.forEach((id) => {
if (!wsMapRef.current.has(id)) {
connect(id);
}
});
+ // Disconnect from devices no longer in the list
wsMapRef.current.forEach((ws, id) => {
if (!currentIds.has(id)) {
- clearReconnect(id);
ws.close(1000, "device removed");
wsMapRef.current.delete(id);
- retriesRef.current.delete(id);
+ const t = reconnectTimeoutsRef.current.get(id);
+ if (t) {
+ clearTimeout(t);
+ reconnectTimeoutsRef.current.delete(id);
+ }
}
});
return () => {
- generationRef.current += 1;
- shutdownAll();
+ wsMapRef.current.forEach((ws) => ws.close(1000, "cleanup"));
+ wsMapRef.current.clear();
+ reconnectTimeoutsRef.current.forEach((t) => clearTimeout(t));
+ reconnectTimeoutsRef.current.clear();
};
- }, [enabled, token, deviceIdsKey, deviceIds.length, keys?.join(), types?.join(), as]);
+ }, [enabled, token, deviceIdsKey, deviceIds.length, connect]);
}
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/hooks/useWebSocket.ts b/src/hooks/useWebSocket.ts
index 86f4056..6a4029d 100644
--- a/src/hooks/useWebSocket.ts
+++ b/src/hooks/useWebSocket.ts
@@ -1,11 +1,21 @@
import { useEffect, useRef, useCallback } from "react";
-import { getWsBaseUrl } from "utils/getWsBaseUrl";
-/** Off by default to reduce server load; set VITE_ENABLE_WEBSOCKETS=true to enable live streams. */
-const WEBSOCKETS_ENABLED = import.meta.env.VITE_ENABLE_WEBSOCKETS === "true";
+/**
+ * Derives the WebSocket base URL from VITE_APP_API_URL.
+ * e.g. "https://api.brandxtech.ca" -> "wss://api.brandxtech.ca"
+ * Falls back to current page host for local dev.
+ */
+function getWsBaseUrl(): string {
+ const apiUrl = import.meta.env.VITE_APP_API_URL;
+ if (apiUrl) {
+ return apiUrl.replace(/^http/, "ws");
+ }
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
+ return `${protocol}//${window.location.host}`;
+}
interface UseWebSocketOptions {
- /** The API path including /v1, e.g. "/v1/live/devices/123/components" */
+ /** The API path, e.g. "/v1/live/devices/123/components" */
path: string;
/** Transform the raw MessageEvent into your domain type */
parse: (event: MessageEvent) => T;
@@ -15,12 +25,6 @@ interface UseWebSocketOptions {
token?: string;
/** Minimum seconds between updates, passed as ?rate= to the backend */
rate?: number;
- /** Context keys, same as REST device APIs (?keys=) */
- keys?: string[];
- /** Context types, same as REST device APIs (?types=) */
- types?: string[];
- /** Impersonation / team view key, same as REST (?as=) */
- as?: string;
/** Whether the connection should be active. Default true. */
enabled?: boolean;
}
@@ -37,7 +41,7 @@ interface UseWebSocketOptions {
* });
*/
export function useWebSocket(options: UseWebSocketOptions) {
- const { path, parse, onMessage, token, rate = 0, keys, types, as, enabled = true } = options;
+ const { path, parse, onMessage, token, rate = 0, enabled = true } = options;
// Keep latest callbacks in refs so reconnects don't use stale closures
const onMessageRef = useRef(onMessage);
@@ -48,20 +52,13 @@ export function useWebSocket(options: UseWebSocketOptions) {
const wsRef = useRef(null);
const reconnectTimeoutRef = useRef>();
const retriesRef = useRef(0);
- /** Bumps on each effect cleanup so late onclose / reconnect timers never run after unmount or dependency change. */
- const generationRef = useRef(0);
const connect = useCallback(() => {
if (!token) return;
- const gen = generationRef.current;
-
const params = new URLSearchParams();
params.set("token", token);
if (rate > 0) params.set("rate", rate.toString());
- if (keys?.length) params.set("keys", keys.toString());
- if (types?.length) params.set("types", types.toString());
- if (as) params.set("as", as);
const url = `${getWsBaseUrl()}${path}?${params.toString()}`;
const ws = new WebSocket(url);
@@ -89,10 +86,6 @@ export function useWebSocket(options: UseWebSocketOptions) {
console.debug(`[ws] closed: ${path} (code: ${event.code})`);
wsRef.current = null;
- if (gen !== generationRef.current) {
- return;
- }
-
// Don't reconnect on clean close or auth rejection
if (event.code === 1000 || event.code === 4001 || event.code === 4003) {
return;
@@ -102,33 +95,22 @@ export function useWebSocket(options: UseWebSocketOptions) {
const delay = Math.min(1000 * Math.pow(2, retriesRef.current), 30000);
retriesRef.current += 1;
console.debug(`[ws] reconnecting in ${delay}ms...`);
- reconnectTimeoutRef.current = setTimeout(() => {
- if (gen !== generationRef.current) return;
- connect();
- }, delay);
+ reconnectTimeoutRef.current = setTimeout(connect, delay);
};
- }, [path, token, rate, keys?.join(), types?.join(), as]);
+ }, [path, token, rate]);
useEffect(() => {
- if (!WEBSOCKETS_ENABLED || !enabled || !token) {
- if (reconnectTimeoutRef.current) {
- clearTimeout(reconnectTimeoutRef.current);
- }
+ if (!enabled || !token) {
if (wsRef.current) {
wsRef.current.close(1000, "disabled");
wsRef.current = null;
}
- retriesRef.current = 0;
- return () => {
- generationRef.current += 1;
- };
+ return;
}
- retriesRef.current = 0;
connect();
return () => {
- generationRef.current += 1;
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
}
diff --git a/src/interactions/InteractionSettings.tsx b/src/interactions/InteractionSettings.tsx
index f1f3d8c..87bdf98 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 {
@@ -192,9 +195,7 @@ export default function InteractionSettings(props: Props) {
let value = describeMeasurement(
condition.measurementType,
or(initialComponent, Component.create()).settings.type,
- or(initialComponent, Component.create()).settings.subtype,
- undefined,
- user
+ or(initialComponent, Component.create()).settings.subtype
).toDisplay(condition.value);
rawConditionValues.push(value.toString());
});
@@ -219,7 +220,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 +230,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;
@@ -413,7 +425,7 @@ export default function InteractionSettings(props: Props) {
mappedComponents.get(componentIDToString(interaction.settings.source)),
Component.create()
);
- return describeMeasurement(measurementType, source.settings.type, source.settings.subtype, undefined, user);
+ return describeMeasurement(measurementType, source.settings.type, source.settings.subtype);
};
const describeSink = (): MeasurementDescriber => {
@@ -421,9 +433,7 @@ export default function InteractionSettings(props: Props) {
return describeMeasurement(
Measurement.boolean,
or(sink, Component.create()).settings.type,
- or(sink, Component.create()).settings.subtype,
- undefined,
- user
+ or(sink, Component.create()).settings.subtype
);
};
@@ -851,7 +861,7 @@ export default function InteractionSettings(props: Props) {
const conditionGroup = (index: number) => {
if (index >= numConditions) return;
let measurementType = interaction.settings.conditions[index].measurementType;
- let source = describeSource(measurementType, );
+ let source = describeSource(measurementType);
let isBoolean = isBooleanValue(index);
return (
diff --git a/src/interactions/InteractionsOverview.tsx b/src/interactions/InteractionsOverview.tsx
index 929a58d..127f2a6 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";
@@ -72,7 +71,7 @@ interface Props {
export default function InteractionsOverview(props: Props) {
const classes = useStyles();
- const [{as, user}] = useGlobalState();
+ const [{as}] = useGlobalState();
const interactionsAPI = useInteractionsAPI();
const { success, error } = useSnackbar();
const { device, component, components, permissions, refreshCallback } = props;
@@ -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]);
@@ -133,12 +118,12 @@ export default function InteractionsOverview(props: Props) {
if (!conditions) return items;
conditions.forEach((condition: pond.IInteractionCondition, conditionIndex: number) => {
let measurementType = condition.measurementType;
- let measurement = describeMeasurement(condition.measurementType, sourceType, sourceSubtype, undefined, user);
+ let measurement = describeMeasurement(condition.measurementType, sourceType, sourceSubtype);
items.push(
- {interactionConditionText(source, condition, conditionIndex > 0, user)}
+ {interactionConditionText(source, condition, conditionIndex > 0)}
@@ -200,7 +185,7 @@ export default function InteractionsOverview(props: Props) {
let condition = conditions[conditionIndex];
let sourceType = source && source.settings.type;
let sourceSubtype = source && source.settings.subtype;
- let describer = describeMeasurement(condition.measurementType, sourceType, sourceSubtype, undefined, user);
+ let describer = describeMeasurement(condition.measurementType, sourceType, sourceSubtype);
condition.value = describer.toStored(value);
updatedDirtyInteractions.set(interactionIndex, true);
}
@@ -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/maps/MapBase.tsx b/src/maps/MapBase.tsx
index a5cc0c7..fa4c9c2 100644
--- a/src/maps/MapBase.tsx
+++ b/src/maps/MapBase.tsx
@@ -34,6 +34,7 @@ import { FeatureCollection, Feature } from "geojson";
import DrawController from "./mapControllers/drawController";
//import { Geometry } from "geojson";
import Map, { MapRef, Marker, MarkerDragEvent } from "react-map-gl/mapbox";
+import { getDistanceUnit } from "utils";
import { MapMouseEvent } from "mapbox-gl";
import { makeStyles } from "@mui/styles";
import { Result } from "@mapbox/mapbox-gl-geocoder";
@@ -498,7 +499,7 @@ export default function MapBase(props: Props) {
lineWidth: 5,
origin: pond.DataOrigin.DATA_ORIGIN_ADAPTIVE,
totalDist:
- user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS
+ getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS
? (data.totalDistanceKm * 1000).toFixed(2)
: (data.totalDistanceKm * 3280.8398950131).toFixed(2)
};
diff --git a/src/maps/mapDrawers/FieldDrawer.tsx b/src/maps/mapDrawers/FieldDrawer.tsx
index 07adbf9..724cf5d 100644
--- a/src/maps/mapDrawers/FieldDrawer.tsx
+++ b/src/maps/mapDrawers/FieldDrawer.tsx
@@ -14,6 +14,7 @@ import DisplayDrawer from "common/DisplayDrawer";
import HarvestPlanDisplay from "harvestPlan/HarvestPlanDisplay";
import { Field, fieldScope, HarvestPlan, teamScope } from "models";
import React, { useEffect, useState } from "react";
+import TaskViewer from "tasks/TaskViewer";
import Weather from "weather/weather";
import { getThemeType } from "theme";
import GrainDescriber from "grain/GrainDescriber";
@@ -23,7 +24,6 @@ import { useMobile } from "hooks";
import { useGlobalState, useHarvestPlanAPI, useUserAPI } from "providers";
import { makeStyles } from "@mui/styles";
import FieldActions from "field/FieldActions";
-import FieldTaskList from "field/FieldTaskList";
interface TabPanelProps {
children?: React.ReactNode;
@@ -218,7 +218,7 @@ export default function FieldDrawer(props: Props) {
TabIndicatorProps={{ style: { background: "rgba(255,255,0,255)" } }}>
-
+
@@ -273,7 +273,7 @@ export default function FieldDrawer(props: Props) {
-
+
);
@@ -287,7 +287,7 @@ export default function FieldDrawer(props: Props) {
onClose={() => setOpenNote(false)}>
Notes
-
+
);
diff --git a/src/models/Ambient.ts b/src/models/Ambient.ts
index d2c7577..4fd906a 100644
--- a/src/models/Ambient.ts
+++ b/src/models/Ambient.ts
@@ -10,7 +10,6 @@ export class Ambient {
public status: pond.ComponentStatus = pond.ComponentStatus.create();
public temperature: number = -127;
public humidity: number = 200;
- public lastReading: string = ""
public static create(comp: Component): Ambient {
let my = new Ambient();
@@ -19,9 +18,6 @@ export class Ambient {
if (comp.status.measurement.length > 0) {
comp.status.measurement.forEach(um => {
- if (um.timestamps[0]){
- my.lastReading = um.timestamps[0]
- }
if (um.values[0]) {
if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
my.temperature = or(um.values[0].values[0], -127);
@@ -43,7 +39,6 @@ export class Ambient {
hum = humAndTemp["relativeHumidityTimes100"];
}
}
- my.lastReading = comp.status.lastMeasurement?.timestamp ?? ""
my.temperature = or(temp, -1270) / 10;
my.humidity = or(hum, 20000) / 100;
}
diff --git a/src/models/Bin.ts b/src/models/Bin.ts
index 6f67212..e720fec 100644
--- a/src/models/Bin.ts
+++ b/src/models/Bin.ts
@@ -1,8 +1,8 @@
import GrainDescriber from "grain/GrainDescriber";
import { cloneDeep } from "lodash";
import { MarkerData } from "maps/mapMarkers/Markers";
-import { User } from "models";
import { pond } from "protobuf-ts/pond";
+import { getGrainUnit } from "utils";
import { stringToMaterialColour } from "utils/strings";
import { or } from "utils/types";
@@ -52,34 +52,6 @@ export class Bin {
return this.settings.specs?.shape;
}
- public diameter(): number {
- return this.settings.specs?.diameterCm ?? 0;
- }
-
- public height(): number {
- return this.settings.specs?.heightCm ?? 0;
- }
-
- public sidewallHeight(): number {
- return this.settings.specs?.advancedDimensions?.sidewallHeight ?? 0;
- }
-
- public roofHeight() : number {
- return this.settings.specs?.advancedDimensions?.topConeHeight ?? 0;
- }
-
- public hopperHeight() : number {
- return this.settings.specs?.advancedDimensions?.hopperHeight ?? 0;
- }
-
- public roofAngle() : number {
- return this.settings.specs?.advancedDimensions?.roofAngle ?? 0;
- }
-
- public hopperAngle() : number {
- return this.settings.specs?.advancedDimensions?.hopperAngle ?? 0;
- }
-
public key(): string {
return this.settings.key;
}
@@ -162,11 +134,6 @@ export class Bin {
return colour;
}
- /**
- * this function returns the number of bushels in the bin, if using the automatic lidar or libracart to control inventory it will use the bushels in status
- * otherwise will use the bushels in the inventory found in settings
- * @returns (number) the current bushels in the bin
- */
public bushels(): number {
let control = this.settings.inventory?.inventoryControl;
let bushels = this.settings.inventory?.grainBushels || 0
@@ -177,14 +144,6 @@ export class Bin {
return bushels
}
- /**
- * this function returns the bushelCapacity in the bins specs as it is stored or 0 if the specs are undefined
- * @returns (number) the capacity of the bin in bushels
- */
- public bushelCapacity(): number {
- return this.settings.specs?.bushelCapacity ?? 0
- }
-
public fillPercent(): number {
let fill = 0;
if (this.settings.inventory && this.settings.specs) {
@@ -203,12 +162,7 @@ export class Bin {
public grainName(): string {
if (this.grain() !== pond.Grain.GRAIN_INVALID) {
if (this.grain() === pond.Grain.GRAIN_CUSTOM) {
- //this will prioritize the new style of custom grain types
- let cg = this.customGrain()
- if(cg !== undefined){
- return cg.name
- }
- return this.customType(); //this is still here for bins that are using the old custom grain where it was just a string
+ return this.customType();
} else {
return GrainDescriber(this.grain()).name;
}
@@ -217,18 +171,6 @@ export class Bin {
}
}
- public grainGroup(): string {
- if (this.grain() !== pond.Grain.GRAIN_INVALID) {
- let cg = this.customGrain()
- if (this.grain() === pond.Grain.GRAIN_CUSTOM && cg) {
- return cg.group
- } else {
- return GrainDescriber(this.grain()).group;
- }
- }
- return "None";
- }
-
public binFillCap(): string {
let fillCap = "";
if (this.settings.specs && this.settings.inventory) {
@@ -294,38 +236,16 @@ export class Bin {
return bpt;
}
- /**
- * this function returns a bins stored inventory in the unit of the passed in user,
- * it can return it in bushels, US tons, or metric Tonnes
- * @param user the user object
- * @returns (number) bins current stored inventory
- */
- public grainInventory(user: User): number {
+ public grainInventory(): number {
let grain = this.bushels()
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.bushelsPerTonne() > 1){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.bushelsPerTonne() > 1){
grain = this.bushels() / this.bushelsPerTonne()
- }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.bushelsPerTonne() > 1){
+ }else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.bushelsPerTonne() > 1){
grain = this.bushels() / (this.bushelsPerTonne() * 0.907)
}
return Math.round(grain*100)/100
}
- /**
- * this function returns a bins capacity in the unit of the passed in user,
- * it can return it in bushels, US tons, or metric Tonnes
- * @param user the user object
- * @returns (number) bins max capacity
- */
- public grainCapacity(user: User): number {
- let capacity = this.bushelCapacity()
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.bushelsPerTonne() > 1){
- capacity = capacity / this.bushelsPerTonne()
- }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.bushelsPerTonne() > 1){
- capacity = capacity / (this.bushelsPerTonne() * 0.907)
- }
- return Math.round(capacity*100)/100
- }
-
/**
* gets the enum value for the inventory control in the bins inventory
*/
@@ -336,17 +256,4 @@ export class Bin {
}
return c;
}
-
- //changed this to just use the target temp rather than the upper limit as we are deprecating the limits
- public targetTemp(): number {
- return this.settings.inventory?.targetTemperature ?? 0
- }
-
- public lowerTempThreshold(): number {
- return this.settings.lowTemp
- }
-
- public targetMoisture(): number {
- return this.settings.inventory?.targetMoisture ?? 0
- }
}
diff --git a/src/models/CO2.ts b/src/models/CO2.ts
index fcb1ed5..62e5d73 100644
--- a/src/models/CO2.ts
+++ b/src/models/CO2.ts
@@ -3,6 +3,8 @@ import { quack } from "protobuf-ts/pond";
import { or } from "utils/types";
import { clone, cloneDeep } from "lodash";
import { Component } from "models";
+import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
+import { extractGrainCable } from "pbHelpers/ComponentTypes";
export class CO2 {
public settings: pond.ComponentSettings = pond.ComponentSettings.create();
diff --git a/src/models/Contract.ts b/src/models/Contract.ts
index 81bc001..ce55093 100644
--- a/src/models/Contract.ts
+++ b/src/models/Contract.ts
@@ -2,9 +2,8 @@ import { Option } from "common/SearchSelect";
import { GrainOptions, ToGrainOption } from "grain";
import GrainDescriber from "grain/GrainDescriber";
import { cloneDeep } from "lodash";
-import { User } from "models";
import { pond } from "protobuf-ts/pond";
-import { stringToMaterialColour } from "utils";
+import { getGrainUnit, stringToMaterialColour } from "utils";
import { or } from "utils/types";
export class Contract {
@@ -15,28 +14,26 @@ export class Contract {
public label: string = "";
private objKey: string = "";
- public static create(pb?: pond.Contract, user?: User): Contract {
+ public static create(pb?: pond.Contract): Contract {
let my = new Contract();
if (pb) {
my.settings = pond.ContractSettings.fromObject(cloneDeep(or(pb.settings, {})));
my.title = pb.name;
my.objKey = pb.key;
- my.unit = my.measurementUnit(user);
+ my.unit = my.measurementUnit();
my.colour = my.commodityColour();
my.label = my.commodityLabel();
}
return my;
}
- private measurementUnit(user?: User): string {
+ private measurementUnit(): string {
if (this.settings.type === pond.ContractType.CONTRACT_TYPE_GRAIN) {
- if( user ){
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1){
- return "mT"
- }
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){
- return "t"
- }
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1){
+ return "mT"
+ }
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){
+ return "t"
}
return "bu";
}
@@ -66,22 +63,21 @@ export class Contract {
return label;
}
- public static clone(other?: Contract, user?: User): Contract {
+ public static clone(other?: Contract): Contract {
if (other) {
return Contract.create(
pond.Contract.fromObject({
title: other.title,
key: other.objKey,
settings: cloneDeep(other.settings)
- }),
- user
+ })
);
}
- return Contract.create(undefined, user);
+ return Contract.create();
}
- public static any(data: any, user?: User): Contract {
- return Contract.create(pond.Contract.fromObject(cloneDeep(data)), user);
+ public static any(data: any): Contract {
+ return Contract.create(pond.Contract.fromObject(cloneDeep(data)));
}
public key(): string {
@@ -208,28 +204,28 @@ export class Contract {
return this.conversionValue();
}
- public sizeInPreferredUnit(user: User): number {
+ public sizeInPreferredUnit(): number {
let size = this.settings.size;
switch (this.settings.type) {
case pond.ContractType.CONTRACT_TYPE_GRAIN:
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1) {
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1) {
size = size / this.conversionValue();
}
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){
size = size / (this.conversionValue() * 0.907); //the conversion for grain will be stored in metric tonnes, this will convert it to US tons
}
}
return Math.round(size * 100) / 100;
}
- public deliveredInPreferredUnit(user: User): number {
+ public deliveredInPreferredUnit(): number {
let del = this.settings.delivered;
switch (this.settings.type) {
case pond.ContractType.CONTRACT_TYPE_GRAIN:
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1) {
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1) {
del = del / this.conversionValue();
}
- if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){
+ if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){
del = del / (this.conversionValue() * 0.907); //the conversion for grain will be stored in metric tonnes, this will convert it to US tons
}
}
@@ -241,15 +237,14 @@ export class Contract {
public static toStoredUnit(
secondaryUnitVal: number,
contractType: pond.ContractType,
- conversionValue: number,
- user: User
+ conversionValue: number
): number {
let storedUnitVal = secondaryUnitVal;
switch (contractType) {
//use the value and conversion they entered directly, it is safe to assume they are both the same unit (ton vs tonne) so dont need to convert anything
//before converting to bushels
case pond.ContractType.CONTRACT_TYPE_GRAIN:
- if ((user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) && conversionValue > 1) {
+ if ((getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) && conversionValue > 1) {
storedUnitVal = secondaryUnitVal * conversionValue;
}
}
diff --git a/src/models/Controller.ts b/src/models/Controller.ts
index 4e7170c..0ee12af 100644
--- a/src/models/Controller.ts
+++ b/src/models/Controller.ts
@@ -9,30 +9,13 @@ export class Controller {
public settings: pond.ComponentSettings = pond.ComponentSettings.create();
public status: pond.ComponentStatus = pond.ComponentStatus.create();
public on = false;
- public mode: quack.OutputMode | undefined
- public lastReading: string = "";
public static create(comp: Component): Controller {
let my = new Controller();
my.settings = comp.settings;
my.status = comp.status;
- my.mode = comp.settings.defaultOutputState
- //this is deprecated because it is our old measurement structure
- // my.on = Boolean(my.status.lastMeasurement?.measurement?.booleanOutput?.value);
-
- if (comp.status.lastGoodMeasurement.length > 0) {
- comp.status.measurement.forEach(um => {
- if (um.timestamps[0]){
- my.lastReading = um.timestamps[0]
- }
- if (um.values[0]) {
- if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN) {
- my.on = Boolean(um.values[0].values[0]);
- }
- }
- });
- }
+ my.on = Boolean(my.status.lastMeasurement?.measurement?.booleanOutput?.value);
return my;
}
@@ -42,7 +25,6 @@ export class Controller {
my.settings = comp.settings ? comp.settings : pond.ComponentSettings.create();
my.status = comp.status ? comp.status : pond.ComponentStatus.create();
-
my.on = Boolean(my.status.lastMeasurement?.measurement?.booleanOutput?.value);
return my;
diff --git a/src/models/GrainCable.ts b/src/models/GrainCable.ts
index 4de1ae4..70dc257 100644
--- a/src/models/GrainCable.ts
+++ b/src/models/GrainCable.ts
@@ -132,43 +132,21 @@ export class GrainCable {
return describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE).colour();
}
- public minTemp(unit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS, inGrain?: boolean) {
- if(inGrain){
- let grainMax = Math.min(...this.filteredNodes(true).temps)
- if (unit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT)
- return this.farenheit(grainMax)
- return grainMax
- }
+ public minTemp(unit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS) {
if (unit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT)
return this.farenheit(Math.min(...this.temperatures));
return Math.min(...this.temperatures);
}
- public minHumidity(inGrain?: boolean) {
- if(inGrain){
- return Math.max(...this.filteredNodes(true).humids)
- }
+ public minHumidity() {
return Math.min(...this.humidities);
}
- public minMoisture(inGrain?: boolean) {
- if(inGrain){
- return Math.min(...this.filteredNodes(true).moistures)
- }
+ public minMoisture() {
return Math.min(...this.grainMoistures);
}
- /**
- * Averages the temperatures of the grain cable, if filtered is true it will filter the excluded nodes and nodes above the top node before averaging
- * @param unit the unit of the temperature
- * @param inGrain whether to filter the excluded nodes and nodes above the top node before averaging
- * @returns
- */
- public aveTemp(unit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS, inGrain?: boolean) {
- if(inGrain){
- let filteredNodes = this.filteredNodes(true)
- return filteredNodes.temps.reduce((p: any, c: any) => p + c, 0) / filteredNodes.temps.length
- }
+ public aveTemp(unit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS) {
if (unit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT)
return this.farenheit(
this.temperatures.reduce((p: any, c: any) => p + c, 0) / this.temperatures.length
@@ -176,45 +154,21 @@ export class GrainCable {
return this.temperatures.reduce((p: any, c: any) => p + c, 0) / this.temperatures.length;
}
- public aveHumidity(inGrain?: boolean) {
- if(inGrain){
- let filteredNodes = this.filteredNodes(true)
- return filteredNodes.humids.reduce((p: any, c: any) => p + c, 0) / filteredNodes.humids.length
- }
+ public aveHumidity() {
return this.humidities.reduce((p: any, c: any) => p + c, 0) / this.humidities.length;
}
- public aveMoisture(inGrain?: boolean) {
- if(inGrain){
- let filteredNodes = this.filteredNodes(true)
- return filteredNodes.moistures.reduce((p: any, c: any) => p + c, 0) / filteredNodes.moistures.length
- }
- return this.grainMoistures.reduce((p: any, c: any) => p + c, 0) / this.grainMoistures.length;
- }
-
- public maxTemp(unit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS, inGrain?: boolean) {
- if(inGrain){
- let grainMax = Math.max(...this.filteredNodes(true).temps)
- if (unit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT)
- return this.farenheit(grainMax)
- return grainMax
- }
+ public maxTemp(unit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS) {
if (unit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT)
return this.farenheit(Math.max(...this.temperatures));
return Math.max(...this.temperatures);
}
- public maxHumidity(inGrain?: boolean) {
- if(inGrain){
- return Math.max(...this.filteredNodes(true).humids)
- }
+ public maxHumidity() {
return Math.max(...this.humidities);
}
- public maxMoisture(inGrain?: boolean) {
- if(inGrain){
- return Math.max(...this.filteredNodes(true).moistures)
- }
+ public maxMoisture() {
return Math.min(...this.grainMoistures);
}
diff --git a/src/models/Headspace.ts b/src/models/Headspace.ts
index 51bd7fa..bb4342b 100644
--- a/src/models/Headspace.ts
+++ b/src/models/Headspace.ts
@@ -10,7 +10,6 @@ export class Headspace {
public status: pond.ComponentStatus = pond.ComponentStatus.create();
public temperature: number = -127;
public humidity: number = 200;
- public lastReading: string = "";
public static create(comp: Component): Headspace {
let my = new Headspace();
@@ -19,9 +18,6 @@ export class Headspace {
if (comp.status.measurement.length > 0) {
comp.status.measurement.forEach(um => {
- if (um.timestamps[0]){
- my.lastReading = um.timestamps[0]
- }
if (um.values[0]) {
if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
my.temperature = um.values[0].values[0];
@@ -43,7 +39,6 @@ export class Headspace {
hum = humAndTemp["relativeHumidityTimes100"];
}
}
- my.lastReading = comp.status.lastMeasurement?.timestamp ?? ""
my.temperature = or(temp, -1270) / 10;
my.humidity = or(hum, 20000) / 100;
}
diff --git a/src/models/Plenum.ts b/src/models/Plenum.ts
index b88b8a0..24378d3 100644
--- a/src/models/Plenum.ts
+++ b/src/models/Plenum.ts
@@ -10,7 +10,6 @@ export class Plenum {
public status: pond.ComponentStatus = pond.ComponentStatus.create();
public temperature: number = -127;
public humidity: number = 200;
- public lastReading: string = ""
public static create(comp: Component): Plenum {
let my = new Plenum();
@@ -19,9 +18,6 @@ export class Plenum {
if (comp.status.measurement.length > 0) {
comp.status.measurement.forEach(um => {
- if (um.timestamps[0]) {
- my.lastReading = um.timestamps[0];
- }
if (um.values[0]) {
if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
my.temperature = or(um.values[0].values[0], -127);
@@ -45,7 +41,6 @@ export class Plenum {
}
my.temperature = or(temp, -1270) / 10;
my.humidity = or(hum, 20000) / 100;
- my.lastReading = comp.status.lastMeasurement?.timestamp ?? ""
}
return my;
diff --git a/src/models/Pressure.ts b/src/models/Pressure.ts
index de0b189..fc07dfe 100644
--- a/src/models/Pressure.ts
+++ b/src/models/Pressure.ts
@@ -9,9 +9,7 @@ export class Pressure {
public settings: pond.ComponentSettings = pond.ComponentSettings.create();
public status: pond.ComponentStatus = pond.ComponentStatus.create();
public pascals: number = 0;
- public airflow: number | undefined
public fanId: number = 0;
- public lastReading: string = ""
public static create(comp: Component): Pressure {
let my = new Pressure();
@@ -21,23 +19,19 @@ export class Pressure {
//getting the value from the unitmeasurements in status instead of the old style measurements in the status
if (comp.status.measurement.length > 0) {
comp.status.measurement.forEach(um => {
- if (um.timestamps[0]){
- my.lastReading = um.timestamps[0]
- }
if (um.values[0] && um.values[0].values.length > 0) {
if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE) {
my.pascals = um.values[0].values[0];
}
//TODO-CS: could expand this to have the fan cfm in the pressure model as well
- if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_CFM){
- my.airflow = um.values[0].values[0]
- }
+ // if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_CFM){
+ // my.fanCFM = um.values[0].values[0]
+ // }
}
});
} else {
//if no unit measurements in status use the old measurements in status
let pre = comp.status?.lastMeasurement?.measurement?.pressure?.pascals;
- my.lastReading = comp.status.lastMeasurement?.timestamp ?? ""
if (pre) {
my.pascals = pre;
}
diff --git a/src/models/UnitMeasurement.ts b/src/models/UnitMeasurement.ts
index 9fc3a42..d33c17d 100644
--- a/src/models/UnitMeasurement.ts
+++ b/src/models/UnitMeasurement.ts
@@ -32,7 +32,7 @@ export class UnitMeasurement {
public static create(pb?: pond.UnitMeasurementsForComponent, user?: User): UnitMeasurement {
let my = new UnitMeasurement();
if (pb) {
- let describer = describeMeasurement(pb.type, pb.componentType, undefined, undefined, user);
+ let describer = describeMeasurement(pb.type, pb.componentType);
let values = pb.values;
if (user) {
values = unitConversion(pb.values, pb.type, user);
diff --git a/src/models/user.ts b/src/models/user.ts
index 22bdb26..6c46a31 100644
--- a/src/models/user.ts
+++ b/src/models/user.ts
@@ -101,20 +101,4 @@ export class User {
}
return this.settings.features.includes(flag);
}
-
- public tempUnit(): pond.TemperatureUnit {
- return this.settings.temperatureUnit
- }
-
- public pressureUnit(): pond.PressureUnit {
- return this.settings.pressureUnit
- }
-
- public distanceUnit(): pond.DistanceUnit {
- return this.settings.distanceUnit
- }
-
- public grainUnit(): pond.GrainUnit {
- return this.settings.grainUnit
- }
}
diff --git a/src/navigation/BottomNavigator.tsx b/src/navigation/BottomNavigator.tsx
index 60019a6..b03a153 100644
--- a/src/navigation/BottomNavigator.tsx
+++ b/src/navigation/BottomNavigator.tsx
@@ -13,14 +13,14 @@ import BinsIcon from "products/Bindapt/BinsIcon";
import { useGlobalState } from "providers";
import { useCallback, useEffect, useState } from "react";
import { useNavigate, useLocation } from "react-router-dom";
-import { getFeatures, getWhitelabel } from "services/whiteLabel";
+import { IsAdaptiveAgriculture, isBXT, IsMiVent, IsAdCon, IsOmniAir, IsMiPCA } from "services/whiteLabel";
import FieldsIcon from "products/AgIcons/FieldsIcon";
import NexusSTIcon from "products/Construction/NexusSTIcon";
import OmniAirDeviceIcon from "products/AviationIcons/OmniAirDeviceIcon";
import AirportMapIcon from "products/AviationIcons/AirportMapIcon";
import PlaneIcon from "products/AviationIcons/PlaneIcon";
import JobsiteIcon from "products/Construction/JobSiteIcon";
-import { useAuthContext } from "providers/authContext";
+import { useAuth0 } from "@auth0/auth0-react";
interface Props {
sideIsOpen: boolean;
@@ -33,19 +33,22 @@ export default function BottomNavigator(props: Props) {
const navigate = useNavigate();
const location = useLocation();
const prevLocation = usePrevious(location);
- const { isAuthenticated } = useAuthContext();
+ const { isAuthenticated } = useAuth0();
const [{ user }] = useGlobalState();
const [route, setRoute] = useState(sideIsOpen ? "side" : "");
- const wl = getWhitelabel();
- const f = getFeatures();
+ const isAdaptive = IsAdaptiveAgriculture();
+ const isMiVent = IsMiVent();
+ const isAdCon = IsAdCon();
+ const isOmni = IsOmniAir();
+ const isMiPCA = IsMiPCA();
const reRoute = useCallback(
(path: string) => {
if (path === "") {
- if (f.bins) {
+ if (isAdaptive) {
return "bins";
}
- if (f.ventilation) {
+ if (isMiVent) {
return "ventilation";
}
return "devices";
@@ -71,7 +74,7 @@ export default function BottomNavigator(props: Props) {
}
return path;
},
- [f]
+ [isAdaptive, isMiVent]
);
const autoDetectRoute = useCallback(() => {
@@ -102,27 +105,27 @@ export default function BottomNavigator(props: Props) {
const authenticatedNavigation = () => {
return (
handleRouteChange(newValue)}>
- {f.visualFarm && (
+ {isAdaptive && (
} value="visualFarm" />
)}
- {f.bins && (
+ {isAdaptive && (
} value="bins" />
)}
- {f.constructionMap && (
+ {isAdCon && (
}
value="constructionMap"
/>
)}
- {f.aviationMap && (
+ {(isOmni || isMiPCA) && (
}
value="aviationMap"
/>
)}
- {f.terminals && (
+ {(isOmni || isMiPCA) && (
}
@@ -133,11 +136,11 @@ export default function BottomNavigator(props: Props) {
- ) : f.constructionMap ? (
+ ) : isAdCon ? (
- ) : f.aviationMap ? (
+ ) : (isOmni || isMiPCA) ? (
) : (
@@ -145,15 +148,22 @@ export default function BottomNavigator(props: Props) {
}
value="devices"
/>
- {f.jobsites && (
+ {isAdCon && (
}
value="jobsites"
/>
)}
+ {/* {isMiVent && (
+ }
+ value="ventilation"
+ />
+ )} */}
- {f.security && user.hasFeature("security") && (
+ {isBXT() && user.hasFeature("security") && (
} value="security" />
)}
} value="more" />
diff --git a/src/navigation/Router.tsx b/src/navigation/Router.tsx
index 0326217..94ce101 100644
--- a/src/navigation/Router.tsx
+++ b/src/navigation/Router.tsx
@@ -1,7 +1,7 @@
import { lazy, Suspense } from "react";
import LoadingScreen from "../app/LoadingScreen";
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
-import { useAuthContext } from "providers/authContext";
+import { useAuth0 } from "@auth0/auth0-react";
import Header from "app/Header";
import Logout from "pages/Logout";
import { ErrorBoundary } from "react-error-boundary";
@@ -20,7 +20,6 @@ const Teams = lazy(() => import("pages/Teams"));
const Users = lazy(() => import("pages/Users"));
const TeamPage = lazy(() => import("pages/Team"));
const BinPage = lazy(() => import("pages/Bin"));
-const BinPageV2 = lazy(()=> import("pages/BinV2"));
const Bins = lazy(() => import("pages/Bins"));
const Mines = lazy(() => import("pages/Mines"));
const DeviceComponent = lazy(() => import("pages/DeviceComponent"));
@@ -58,7 +57,7 @@ export const appendToUrl = (appendage: number | string) => {
export default function Router() {
- const { isAuthenticated } = useAuthContext();
+ const { /*isAuthenticated, loginWithRedirect,*/ isLoading } = useAuth0();
const whiteLabel = getWhitelabel();
const [{ user }] = useGlobalState();
@@ -140,17 +139,10 @@ export default function Router() {
path="" // "/settings/basic"
element={ }
/>
- {user.hasFeature("beta") ?
- }
- />
- :
}
/>
- }
{/* }
@@ -314,7 +306,13 @@ export default function Router() {
);
}
- if (!isAuthenticated) return null;
+ if (isLoading) return null;
+ // if (!isAuthenticated) {
+ // loginWithRedirect()
+ // return (
+ // null
+ // )
+ // }
function ErrorFallback({ error }: { error: Error }) {
return Something went wrong: {error.stack}
;
diff --git a/src/navigation/SideNavigator.tsx b/src/navigation/SideNavigator.tsx
index d07e21b..6bab36c 100644
--- a/src/navigation/SideNavigator.tsx
+++ b/src/navigation/SideNavigator.tsx
@@ -1,21 +1,20 @@
-import { ChevronRight, Code, Grain, HelpOutline, Memory, People, Person, SyncAlt, TapAndPlay } from "@mui/icons-material";
+import { ChevronRight, Code, Grain, Memory, People, Person, SyncAlt, TapAndPlay } from "@mui/icons-material";
import ChevronLeft from "@mui/icons-material/ChevronLeft";
-import {
- Box,
- darken,
- Divider,
- Grid2 as Grid,
- IconButton,
- lighten,
- List,
- ListItemButton,
- ListItemIcon,
- ListItemText,
- SwipeableDrawer,
- Theme,
- Toolbar,
- Tooltip,
- useTheme
+import {
+ darken,
+ Divider,
+ Grid2 as Grid,
+ IconButton,
+ lighten,
+ List,
+ ListItemButton,
+ ListItemIcon,
+ ListItemText,
+ SwipeableDrawer,
+ Theme,
+ Toolbar,
+ Tooltip,
+ useTheme
} from "@mui/material";
import classNames from "classnames";
import { useMobile, useWidth } from "../hooks/useWidth";
@@ -24,15 +23,23 @@ import BindaptIcon from "../products/Bindapt/BindaptIcon";
import { useLocation, useNavigate } from "react-router-dom";
import BinsIcon from "products/Bindapt/BinsIcon";
import { useGlobalState } from "providers";
-import { getFeatures } from "services/whiteLabel";
+import {
+ IsAdaptiveAgriculture,
+ // hasTutorialPlaylist,
+ IsAdCon,
+ IsMiPCA,
+ // isBXT,
+ IsMiVent,
+ IsOmniAir,
+ IsStreamline,
+} from "services/whiteLabel";
import MiningIcon from "products/ventilation/MiningIcon";
-import { useAuthContext } from "providers/authContext";
+import { useAuth0 } from "@auth0/auth0-react";
import FieldsIcon from "products/AgIcons/FieldsIcon";
import PlaneIcon from "products/AviationIcons/PlaneIcon";
import AirportMapIcon from "products/AviationIcons/AirportMapIcon";
import JobsiteIcon from "products/Construction/JobSiteIcon";
import { getThemeType } from "theme";
-import { isCrispEnabled, openCrispChat } from "chat/CrispChat";
import ObjectHeaterIcon from "products/Construction/ObjectHeaterIcon";
import TasksIcon from "products/Construction/TasksIcon";
import CableIcon from "products/Bindapt/CableIcon";
@@ -45,7 +52,6 @@ import CNHiIcon from "products/CommonIcons/cnhiIcon";
import LibraCartIcon from "products/CommonIcons/libracartIcon";
const drawerWidth = 230;
-const closedDrawerWidth = 9.25;
const useStyles = makeStyles((theme: Theme) => ({
sideMenu: {
@@ -59,8 +65,6 @@ const useStyles = makeStyles((theme: Theme) => ({
sideMenuOpened: {
zIndex: theme.zIndex.drawer + 2,
width: drawerWidth,
- minWidth: drawerWidth,
- maxWidth: drawerWidth,
transition: theme.transitions.create(["width"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen
@@ -71,16 +75,12 @@ const useStyles = makeStyles((theme: Theme) => ({
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
}),
- overflowX: "hidden",
+ // overflowX: "hidden",
width: theme.spacing(0),
- minWidth: theme.spacing(0),
- maxWidth: theme.spacing(0),
// zIndex: theme.zIndex.drawer,
// opacity: 0,
[theme.breakpoints.up("md")]: {
- width: theme.spacing(closedDrawerWidth),
- minWidth: theme.spacing(closedDrawerWidth),
- maxWidth: theme.spacing(closedDrawerWidth),
+ width: theme.spacing(9.25),
opacity: 1
}
},
@@ -97,7 +97,6 @@ const useStyles = makeStyles((theme: Theme) => ({
listItem: {
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(2),
- maxHeight: 40,
[theme.breakpoints.up("md")]: {
paddingLeft: theme.spacing(3),
paddingRight: theme.spacing(3)
@@ -130,7 +129,7 @@ interface Props {
export default function SideNavigator(props: Props) {
const { open, onOpen, onClose } = props;
- const { isAuthenticated } = useAuthContext()
+ const { isAuthenticated } = useAuth0()
const theme = useTheme();
const width = useWidth();
const classes = useStyles();
@@ -165,10 +164,15 @@ export default function SideNavigator(props: Props) {
}
const authenticatedSideMenu = () => {
- const f = getFeatures();
+ const isMiVent = IsMiVent();
+ const isAg = IsAdaptiveAgriculture()
+ const isStreamline = IsStreamline()
+ const isOmni = IsOmniAir()
+ const isMiPCA = IsMiPCA()
+ const isAdCon = IsAdCon()
return (
- {(f.visualFarm || user.hasFeature("admin")) && (
+ {(isAg || isStreamline || user.hasFeature("admin")) && (
)}
- {(f.aviationMap || user.hasFeature("admin")) && (
+ {((isOmni || isMiPCA) || user.hasFeature("admin")) && (
)}
- {(f.constructionMap || user.hasFeature("admin")) && (
+ {(isAdCon || user.hasFeature("admin")) && (
)}
- {(f.contracts || user.hasFeature("admin")) && (
+ {(isAg || isStreamline || user.hasFeature("admin")) && (
)}
- {(f.bins || user.hasFeature("admin")) && (
+ {(isAg || isStreamline || user.hasFeature("admin")) && (
)}
- {(f.terminals || user.hasFeature("admin")) && (
+ {((isOmni || isMiPCA) || user.hasFeature("admin")) && (
)}
- {(f.cableEstimator && user.hasFeature("installer") || user.hasFeature("admin")) &&
+ {(user.hasFeature("installer") && isAg || user.hasFeature("admin")) &&
}
- {(f.transactions || user.hasFeature("admin")) && (
+ {(isAg || isStreamline || user.hasFeature("admin")) && (
)}
- {(f.mines || user.hasFeature("admin")) && (
+ {(isMiVent || user.hasFeature("admin")) && (
}
- {(f.fields || user.hasFeature("admin")) && (
+ {(isAg || isStreamline || user.hasFeature("admin")) && (
)}
- {(f.grainTypes || user.hasFeature("admin")) && (
+ {(isAg || isStreamline || user.hasFeature("admin")) && (
)}
- {(f.jobsites || user.hasFeature("admin")) && (
+ {(isAdCon || user.hasFeature("admin")) && (
)}
- {(f.heaters || user.hasFeature("admin")) && (
+ {(isAdCon || user.hasFeature("admin")) && (
}
- {(f.marketplace || user.hasFeature("admin")) &&
+ {(isAg || isStreamline || user.hasFeature("admin")) &&
-
-
-
-
-
- {open
- ? theme.direction === "rtl" ? :
- : theme.direction === "rtl" ? : }
-
-
+
+
+
+
+ {theme.direction === "rtl" ? : }
+
-
-
-
- {isAuthenticated && authenticatedSideMenu()}
-
- {isCrispEnabled() && (<>
-
-
-
- openCrispChat()}>
-
-
-
- {open && }
-
-
-
- >)}
-
+
+
+
+ {/* {isAuthenticated || isOffline() ? authenticatedSideMenu() : unauthenticatedSideMenu()} */}
+ {isAuthenticated && authenticatedSideMenu()}
);
-}
+}
\ No newline at end of file
diff --git a/src/objectHeater/ObjectHeaterCharts.tsx b/src/objectHeater/ObjectHeaterCharts.tsx
index 04e2e9a..dd117e9 100644
--- a/src/objectHeater/ObjectHeaterCharts.tsx
+++ b/src/objectHeater/ObjectHeaterCharts.tsx
@@ -216,16 +216,12 @@ export default function ObjectHeaterCharts(props: Props) {
let tempDescriber = describeMeasurement(
quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
tempComponent.type(),
- tempComponent.subType(),
- undefined,
- user
+ tempComponent.subType()
);
let heaterDescriber = describeMeasurement(
quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN,
heaterComponent.type(),
- heaterComponent.subType(),
- undefined,
- user
+ heaterComponent.subType()
);
let tempData: LineData[] = [];
heaterTemps.forEach(ht => {
@@ -592,9 +588,7 @@ export default function ObjectHeaterCharts(props: Props) {
describer={describeMeasurement(
quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
tempHum.type(),
- tempHum.subType(),
- undefined,
- user
+ tempHum.subType()
)}
lineData={ambientTemps}
numLines={1}
diff --git a/src/objects/ObjectDescriber.tsx b/src/objects/ObjectDescriber.tsx
index ccd29ac..643482a 100644
--- a/src/objects/ObjectDescriber.tsx
+++ b/src/objects/ObjectDescriber.tsx
@@ -2,11 +2,13 @@ import { Column } from "common/ResponsiveTable";
import { Option } from "common/SearchSelect";
import GrainDescriber from "grain/GrainDescriber";
//import { Column } from "material-table";
-import { Bin, BinYard, Field, User } from "models";
+import { Bin, BinYard, Field } from "models";
//import { Gate } from "models/Gate";
import { GrainBag } from "models/GrainBag";
//import { ObjectHeater } from "models/ObjectHeater";
import { pond } from "protobuf-ts/pond";
+import { getDistanceUnit, getTemperatureUnit } from "utils";
+
interface Sort {
order: string; //what to send to the backend list to sort by
numerical: boolean; //whether to use a numerical or text sort
@@ -18,8 +20,6 @@ export interface ObjectExtension {
isTransactionObject: boolean;
//this will define the columns to be used for the object in a material table
tableColumns: Column[];
- // Optional factory so columns can depend on the active user settings (units, formatting, etc.)
- getTableColumns?: (user?: User) => Column[];
//this map will match the title of the column to what the backend needs to perform the ordering
tableSort: Map;
}
@@ -32,202 +32,6 @@ const defaultObject: ObjectExtension = {
tableSort: new Map()
};
-const binColumns = (user?: User): Column[] => {
- const temperatureUnit =
- user?.tempUnit() ?? pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS;
-
- return [
- {
- title: "Name",
- cellStyle: {padding: "16px"},
- render: row => {
- return ({row.settings.name}
);
- }
- },
- {
- title: "Grain",
- cellStyle: {padding: "16px"},
- render: row => {
- let grain = row.settings.inventory?.grainType;
- if (grain) {
- return GrainDescriber(grain).name;
- }
- }
- },
- {
- title: "Bin Height",
- cellStyle: {padding: "16px"},
- render: row => {
- let d = row.settings.specs?.heightCm;
- if (d) {
- if (
- (user?.distanceUnit() ?? pond.DistanceUnit.DISTANCE_UNIT_FEET) ===
- pond.DistanceUnit.DISTANCE_UNIT_METERS
- ) {
- return (d / 100).toFixed(2) + " m";
- } else {
- return (d / 30.48).toFixed(2) + " ft";
- }
- }
- }
- },
- {
- title: "Bin Diameter",
- cellStyle: {padding: "16px"},
- render: row => {
- let d = row.settings.specs?.diameterCm;
- if (d) {
- if (
- (user?.distanceUnit() ?? pond.DistanceUnit.DISTANCE_UNIT_FEET) ===
- pond.DistanceUnit.DISTANCE_UNIT_METERS
- ) {
- return (d / 100).toFixed(2) + " m";
- } else {
- return (d / 30.48).toFixed(2) + " ft";
- }
- }
- }
- },
- {
- title: "Custom Grain Name",
- cellStyle: {padding: "16px"},
- render: row => {
- return row.settings.inventory?.customTypeName;
- }
- },
- {
- title: "Grain Variant",
- cellStyle: {padding: "16px"},
- render: row => {
- return row.settings.inventory?.grainSubtype;
- }
- },
- {
- title: "Grain Bushels",
- cellStyle: {padding: "16px"},
- render: row => {
- return ({row.settings.inventory ? isNaN(row.settings.inventory.grainBushels) ? 0 : row.settings.inventory.grainBushels : 0}
);
- }
- },
- {
- title: "Grain Capacity",
- cellStyle: {padding: "16px"},
- render: row => {
- return row.settings.specs?.bushelCapacity;
- }
- },
- {
- title: "High Temp Warning",
- cellStyle: {padding: "16px"},
- render: row => {
- let t = row.settings.highTemp;
- if (t) {
- if (temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
- return (t * 1.8 + 32).toFixed(2) + " °F";
- }
- return t.toFixed(2) + " °C";
- }
- }
- },
- {
- title: "Low Temp Warning",
- cellStyle: {padding: "16px"},
- render: row => {
- let t = row.settings.lowTemp;
- if (t) {
- if (temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
- return (t * 1.8 + 32).toFixed(2) + " °F";
- }
- return t.toFixed(2) + " °C";
- }
- }
- }
- ] as Column[];
-};
-
-const grainBagColumns = (user?: User): Column[] => {
- const distanceUnit =
- user?.distanceUnit() ?? pond.DistanceUnit.DISTANCE_UNIT_FEET;
- return [
- {
- title: "Name",
- render: row => {
- if (row.title) {
- return ({row.title.toString()}
);
- }
- }
- },
- {
- title: "Length",
- render: row => {
- if (row.settings.length) {
- if (distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
- return row.settings.length.toFixed(2) + " m";
- } else {
- return (row.settings.length * 3.281).toFixed(2) + " ft";
- }
- }
- }
- },
- {
- title: "Diameter",
- render: row => {
- if (row.settings.diameter) {
- if (distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
- return row.settings.diameter.toFixed(2) + " m";
- } else {
- return (row.settings.diameter * 3.281).toFixed(2) + " ft";
- }
- }
- }
- },
- {
- title: "Grain",
- render: row => {
- if (row.settings.supportedGrain) {
- return GrainDescriber(row.settings.supportedGrain).name;
- }
- }
- },
- {
- title: "Custom Grain",
- render: row => {
- return row.settings.customGrain;
- }
- },
- {
- title: "Grain Variant",
- render: row => {
- return row.settings.grainSubtype;
- }
- },
- {
- title: "Capacity",
- render: row => {
- return row.settings.bushelCapacity + " bu";
- }
- },
- {
- title: "Bushels",
- render: row => {
- return row.settings.currentBushels + " bu";
- }
- },
- {
- title: "Fill Date",
- render: row => {
- return row.settings.fillDate;
- }
- },
- {
- title: "Initial Moisture",
- render: row => {
- return row.settings.initialMoisture + "%";
- }
- }
- ] as Column[];
-};
-
export const ObjectExtensions: Map = new Map([
[pond.ObjectType.OBJECT_TYPE_UNKNOWN, defaultObject],
[
@@ -246,8 +50,109 @@ export const ObjectExtensions: Map = new Map([
name: "Bin",
inventoryGroup: "grain",
isTransactionObject: true,
- tableColumns: binColumns(),
- getTableColumns: (user?: User) => binColumns(user),
+ tableColumns: [
+ {
+ title: "Name",
+ cellStyle: {padding: "16px"},
+ render: row => {
+ return ({row.settings.name}
);
+ }
+ },
+ {
+ title: "Grain",
+ cellStyle: {padding: "16px"},
+ render: row => {
+ let grain = row.settings.inventory?.grainType;
+ if (grain) {
+ return GrainDescriber(grain).name;
+ }
+ }
+ },
+ {
+ title: "Bin Height",
+ cellStyle: {padding: "16px"},
+ render: row => {
+ let d = row.settings.specs?.heightCm;
+ if (d) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ return (d / 100).toFixed(2) + " m";
+ } else {
+ return (d / 30.48).toFixed(2) + " ft";
+ }
+ }
+ }
+ },
+ {
+ title: "Bin Diameter",
+ cellStyle: {padding: "16px"},
+ render: row => {
+ let d = row.settings.specs?.diameterCm;
+ if (d) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ return (d / 100).toFixed(2) + " m";
+ } else {
+ return (d / 30.48).toFixed(2) + " ft";
+ }
+ }
+ }
+ },
+ {
+ title: "Custom Grain Name",
+ cellStyle: {padding: "16px"},
+ render: row => {
+ return row.settings.inventory?.customTypeName;
+ }
+ },
+ {
+ title: "Grain Variant",
+ cellStyle: {padding: "16px"},
+ render: row => {
+ return row.settings.inventory?.grainSubtype;
+ }
+ },
+ {
+ title: "Grain Bushels",
+ cellStyle: {padding: "16px"},
+ render: row => {
+ return ({row.settings.inventory ? isNaN(row.settings.inventory.grainBushels) ? 0 : row.settings.inventory.grainBushels : 0}
);
+ }
+ },
+ {
+ title: "Grain Capacity",
+ cellStyle: {padding: "16px"},
+ render: row => {
+ return row.settings.specs?.bushelCapacity;
+ }
+ },
+ {
+ title: "High Temp Warning",
+ cellStyle: {padding: "16px"},
+ render: row => {
+ let t = row.settings.highTemp;
+ if (t) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ return (t * 1.8 + 32).toFixed(2) + " °F";
+ } else {
+ return t.toFixed(2) + " °C";
+ }
+ }
+ }
+ },
+ {
+ title: "Low Temp Warning",
+ cellStyle: {padding: "16px"},
+ render: row => {
+ let t = row.settings.lowTemp;
+ if (t) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ return (t * 1.8 + 32).toFixed(2) + " °F";
+ } else {
+ return t.toFixed(2) + " °C";
+ }
+ }
+ }
+ }
+ ] as Column[],
tableSort: new Map([
["Name", { order: "name", numerical: false }],
["Grain", { order: "inventory.grainType", numerical: false }],
@@ -490,8 +395,84 @@ export const ObjectExtensions: Map = new Map([
name: "Grainbag",
inventoryGroup: "grain",
isTransactionObject: true,
- tableColumns: grainBagColumns(),
- getTableColumns: (user?: User) => grainBagColumns(user),
+ tableColumns: [
+ {
+ title: "Name",
+ render: row => {
+ if (row.title) {
+ return ({row.title.toString()}
);
+ }
+ }
+ },
+ {
+ title: "Length",
+ render: row => {
+ if (row.settings.length) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ return row.settings.length.toFixed(2) + " m";
+ } else {
+ return (row.settings.length * 3.281).toFixed(2) + " ft";
+ }
+ }
+ }
+ },
+ {
+ title: "Diameter",
+ render: row => {
+ if (row.settings.diameter) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ return row.settings.diameter.toFixed(2) + " m";
+ } else {
+ return (row.settings.diameter * 3.281).toFixed(2) + " ft";
+ }
+ }
+ }
+ },
+ {
+ title: "Grain",
+ render: row => {
+ if (row.settings.supportedGrain) {
+ return GrainDescriber(row.settings.supportedGrain).name;
+ }
+ }
+ },
+ {
+ title: "Custom Grain",
+ render: row => {
+ return row.settings.customGrain;
+ }
+ },
+ {
+ title: "Grain Variant",
+ render: row => {
+ return row.settings.grainSubtype;
+ }
+ },
+ {
+ title: "Capacity",
+ render: row => {
+ return row.settings.bushelCapacity + " bu";
+ }
+ },
+ {
+ title: "Bushels",
+ render: row => {
+ return row.settings.currentBushels + " bu";
+ }
+ },
+ {
+ title: "Fill Date",
+ render: row => {
+ return row.settings.fillDate;
+ }
+ },
+ {
+ title: "Initial Moisture",
+ render: row => {
+ return row.settings.initialMoisture + "%";
+ }
+ }
+ ] as Column[],
tableSort: new Map([
["Name", { order: "name", numerical: false }],
["Length", { order: "length", numerical: true }],
@@ -727,7 +708,8 @@ export const ObjectExtensions: Map = new Map([
]);
export default function ObjectDescriber(type: pond.ObjectType): ObjectExtension {
- return ObjectExtensions.get(type) ?? defaultObject;
+ let describer = ObjectExtensions.get(type);
+ return describer ? describer : defaultObject;
}
/**
@@ -763,8 +745,7 @@ export function SearchableObjects(): Option[] {
Object.values(pond.ObjectType).forEach(obj => {
if (typeof obj !== "string") {
let ext = ObjectDescriber(obj);
- const columns = ext.getTableColumns ? ext.getTableColumns() : ext.tableColumns;
- if (columns.length > 0) {
+ if (ext.tableColumns.length > 0) {
options.push({
label: ext.name,
value: obj
diff --git a/src/objects/ObjectTable.tsx b/src/objects/ObjectTable.tsx
index 2ef6c86..7a7b677 100644
--- a/src/objects/ObjectTable.tsx
+++ b/src/objects/ObjectTable.tsx
@@ -1,5 +1,6 @@
import { Box, Button, Grid2 as Grid } from "@mui/material";
// import { getTableIcons } from "common/ResponsiveTable";
+import SearchBar from "common/SearchBar";
import SearchSelect, { Option } from "common/SearchSelect";
// import MaterialTable, { MTableToolbar } from "material-table";
// import { Bin, BinYard, Field } from "models";
@@ -21,8 +22,9 @@ import {
import { useCallback, useEffect, useState } from "react";
import TeamSearch from "teams/TeamSearch";
import BulkBinSettings from "./bulkEditForms/bulkBinSettings";
+import BulkGrainBagSettings from "./bulkEditForms/bulkGrainBagSettings";
import ResponsiveTable from "common/ResponsiveTable";
-import { cloneDeep } from "lodash";
+import { cloneDeep, indexOf } from "lodash";
//import BulkGateSettings from "./bulkEditForms/bulkGateSettings";
interface customButton {
@@ -59,9 +61,6 @@ export default function ObjectTable(props: Props) {
const [selectedUser, setSelectedUser] = useState(as ?? user.id());
const [selectedPageRows, setSelectedPageRows] = useState>(new Map())//key is the page value is an array of row indexes for that page
const [tableLoading, setTableLoading] = useState(false);
-
- const objectExtension = ObjectDescriber(currentType);
- const columns = objectExtension.getTableColumns ? objectExtension.getTableColumns(user) : objectExtension.tableColumns;
//the api's to load all the objects available to look at in this table
const binAPI = useBinAPI();
const binyardAPI = useBinYardAPI();
@@ -367,7 +366,7 @@ export default function ObjectTable(props: Props) {
{
if (by !== -1) {
- let colName = ObjectDescriber(currentType, user).tableColumns[by].title?.toString();
+ let colName = ObjectDescriber(currentType).tableColumns[by].title?.toString();
let order;
if (colName) {
- order = ObjectDescriber(currentType, user).tableSort.get(colName);
+ order = ObjectDescriber(currentType).tableSort.get(colName);
setCurrentOrder(order?.order);
setCurrentDirection(direction);
setIsNumerical(order?.numerical);
diff --git a/src/objects/bulkEditForms/bulkBinSettings.tsx b/src/objects/bulkEditForms/bulkBinSettings.tsx
index f953def..f3e01a9 100644
--- a/src/objects/bulkEditForms/bulkBinSettings.tsx
+++ b/src/objects/bulkEditForms/bulkBinSettings.tsx
@@ -4,7 +4,7 @@ import { GrainOptions } from "grain";
import { pond } from "protobuf-ts/pond";
import { useBinAPI, useGlobalState, useSnackbar } from "providers";
import React, { useState } from "react";
-import { fahrenheitToCelsius } from "utils";
+import { fahrenheitToCelsius, getDistanceUnit, getTemperatureUnit } from "utils";
interface Props {
selectedBins: pond.Bin[];
@@ -19,7 +19,7 @@ export default function BulkBinSettings(props: Props) {
const gridItemWidth = 3;
// bin settings variables
const [name, setName] = useState();
- const [{as, user}] = useGlobalState();
+ const [{as}] = useGlobalState();
const [grainType, setGrainType] = useState();
const [grainOption, setGrainOption] = useState();
const [height, setHeight] = useState();
@@ -32,10 +32,10 @@ export default function BulkBinSettings(props: Props) {
const [lowTemp, setLowTemp] = useState();
const convertedDistance = (enteredDistance: number) => {
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
return enteredDistance * 100;
}
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
return enteredDistance * 30.48;
}
return enteredDistance;
@@ -43,7 +43,7 @@ export default function BulkBinSettings(props: Props) {
const convertedTemp = (enteredTemp: number) => {
let t = enteredTemp;
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
t = fahrenheitToCelsius(enteredTemp);
}
return t;
@@ -168,7 +168,7 @@ export default function BulkBinSettings(props: Props) {
InputProps={{
endAdornment: (
- {user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET ? "m" : "ft"}
+ {getDistanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET ? "m" : "ft"}
)
}}
@@ -186,7 +186,7 @@ export default function BulkBinSettings(props: Props) {
InputProps={{
endAdornment: (
- {user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET ? "m" : "ft"}
+ {getDistanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET ? "m" : "ft"}
)
}}
@@ -204,7 +204,7 @@ export default function BulkBinSettings(props: Props) {
InputProps={{
endAdornment: (
- {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
+ {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F"
: "°C"}
@@ -224,7 +224,7 @@ export default function BulkBinSettings(props: Props) {
InputProps={{
endAdornment: (
- {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
+ {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F"
: "°C"}
diff --git a/src/objects/bulkEditForms/bulkGrainBagSettings.tsx b/src/objects/bulkEditForms/bulkGrainBagSettings.tsx
index 5373c12..5d226f6 100644
--- a/src/objects/bulkEditForms/bulkGrainBagSettings.tsx
+++ b/src/objects/bulkEditForms/bulkGrainBagSettings.tsx
@@ -5,6 +5,7 @@ import { GrainBag } from "models/GrainBag";
import { pond } from "protobuf-ts/pond";
import { useGlobalState, useGrainBagAPI, useSnackbar } from "providers";
import React, { useState } from "react";
+import { getDistanceUnit } from "utils";
interface Props {
selectedBags: GrainBag[];
@@ -27,10 +28,10 @@ export default function BulkGrainBagSettings(props: Props) {
const [bushels, setBushels] = useState();
const [fillDate, setFillDate] = useState();
const [initialMoisture, setInitialMoisture] = useState();
- const [{ as, user }] = useGlobalState();
+ const [{as}] = useGlobalState();
const convertedDistance = (enteredDistance: number) => {
- if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
return enteredDistance / 3.281;
}
return enteredDistance;
diff --git a/src/objects/objectInteractions/Alerts.tsx b/src/objects/objectInteractions/Alerts.tsx
index 01f5bec..8546363 100644
--- a/src/objects/objectInteractions/Alerts.tsx
+++ b/src/objects/objectInteractions/Alerts.tsx
@@ -6,7 +6,6 @@ import { pond, quack } from "protobuf-ts/pond";
import React, { useState } from "react";
import NewObjectInteraction from "./NewObjectInteraction";
import { Option } from "common/SearchSelect";
-import { useGlobalState } from "providers";
export interface Alert {
conditions: pond.InteractionCondition[];
@@ -21,11 +20,10 @@ interface Props {
export default function Alerts(props: Props){
const {alerts, addNew, permissions} = props
- const [{user}] = useGlobalState();
// const [openNewAlert, setOpenNewAlert] = useState(false)
const readableCondition = (condition: pond.InteractionCondition) => {
- let describer = describeMeasurement(condition.measurementType, undefined, undefined, undefined, user);
+ let describer = describeMeasurement(condition.measurementType);
let type = describer.label();
let comparison =
condition.comparison === quack.RelationalOperator.RELATIONAL_OPERATOR_EQUAL_TO
diff --git a/src/objects/objectInteractions/Controls.tsx b/src/objects/objectInteractions/Controls.tsx
index 6f3f817..0346c87 100644
--- a/src/objects/objectInteractions/Controls.tsx
+++ b/src/objects/objectInteractions/Controls.tsx
@@ -4,7 +4,6 @@ import { Component, Device, Interaction } from "models";
import { interactionResultText } from "pbHelpers/Interaction";
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
import { pond, quack } from "protobuf-ts/pond";
-import { useGlobalState } from "providers";
import React, { useEffect, useState } from "react";
export interface Control {
@@ -24,7 +23,6 @@ interface Props {
export default function Controls(props: Props){
const { devices, controls, addNew, permissions } = props
- const [{user}] = useGlobalState();
const [deviceControls, setDeviceControls] = useState>(new Map())
const [openDeviceMenu, setOpenDeviceMenu] = useState(false)
const [anchor, setAnchor] = useState(null)
@@ -47,7 +45,7 @@ export default function Controls(props: Props){
let conditions: string = ""
let firstSource = control.sourceComponents[0]
control.conditions.forEach((condition, index) => {
- let describer = describeMeasurement(condition.measurementType, firstSource.type(), firstSource.subType(), undefined, user);
+ let describer = describeMeasurement(condition.measurementType, firstSource.type(), firstSource.subType());
let type = describer.label();
let comparison =
condition.comparison === quack.RelationalOperator.RELATIONAL_OPERATOR_EQUAL_TO
diff --git a/src/objects/objectInteractions/NewObjectInteraction.tsx b/src/objects/objectInteractions/NewObjectInteraction.tsx
index 9fb8887..64a63b0 100644
--- a/src/objects/objectInteractions/NewObjectInteraction.tsx
+++ b/src/objects/objectInteractions/NewObjectInteraction.tsx
@@ -64,7 +64,7 @@ const useStyles = makeStyles((theme: Theme) => {
export default function NewObjectInteraction(props: Props){
const { open, onClose, device, linkedComponents, componentDevices } = props
const classes = useStyles()
- const [{ as, user }] = useGlobalState();
+ const [{ as }] = useGlobalState();
const {openSnack} = useSnackbar();
const interactionsAPI = useInteractionsAPI();
const [newAlertComponentType, setNewAlertComponentType] = useState(
@@ -179,6 +179,7 @@ export default function NewObjectInteraction(props: Props){
setNewAlertComponentType(quack.ComponentType.COMPONENT_TYPE_INVALID);
setConditions([]);
setSelectedAlertComponents([]);
+ setValidComponents([])
setValStrings(["0", "0"]);
setNumConditions(0)
setNodeOptions([]);
@@ -189,6 +190,7 @@ export default function NewObjectInteraction(props: Props){
setResultType(quack.InteractionResultType.INTERACTION_RESULT_TYPE_REPORT)
setSinkMotor(false)
setSelectedSink("")//the key of the component to be the sink
+ setSinkOptions([])
setResultMode(0)
setResultValue("")
setDutyCycleEnabled(false)
@@ -247,7 +249,7 @@ export default function NewObjectInteraction(props: Props){
const measurementTypeMenuItems = () => {
return availableMeasurementTypes(newAlertComponentType).map(measurementType => (
- {describeMeasurement(measurementType, undefined, undefined, undefined, user).label()}
+ {describeMeasurement(measurementType).label()}
));
};
@@ -260,7 +262,7 @@ export default function NewObjectInteraction(props: Props){
const conditionGroup = (condition: pond.InteractionCondition, index: number) => {
let measurementType = condition.measurementType;
- let describer = describeMeasurement(measurementType, newAlertComponentType, undefined, undefined, user);
+ let describer = describeMeasurement(measurementType, newAlertComponentType);
let isBoolean = condition.measurementType === Measurement.boolean;
return (
@@ -529,9 +531,7 @@ export default function NewObjectInteraction(props: Props){
return describeMeasurement(
Measurement.boolean,
or(sink, Component.create()).settings.type,
- or(sink, Component.create()).settings.subtype,
- undefined,
- user
+ or(sink, Component.create()).settings.subtype
);
};
@@ -1016,7 +1016,6 @@ export default function NewObjectInteraction(props: Props){
displayEmpty
value={newAlertComponentType}
onChange={e => {
- console.log("type change")
setNewAlertComponentType(e.target.value as quack.ComponentType);
setSelectedAlertComponents([]);
setConditions(initialConditions(e.target.value as quack.ComponentType));
diff --git a/src/objects/objectInteractions/ObjectInteractions.tsx b/src/objects/objectInteractions/ObjectInteractions.tsx
index da9ba77..d50adb9 100644
--- a/src/objects/objectInteractions/ObjectInteractions.tsx
+++ b/src/objects/objectInteractions/ObjectInteractions.tsx
@@ -23,11 +23,6 @@ interface Props {
permissions: pond.Permission[]
}
-/**
- * combines the alerts and the controls together to display them simultaneously, if you just want one or the other just use the specific component you want
- * @param props
- * @returns
- */
export default function ObjectInteractions(props: Props) {
const { linkedComponents, componentDevices, devices, objectKey, objectType, permissions } = props
const [{as}] = useGlobalState()
@@ -91,7 +86,7 @@ export default function ObjectInteractions(props: Props) {
}
const load = useCallback(()=>{
- const newInteractions: Interaction[] = [];
+const newInteractions: Interaction[] = [];
const interactionSourceMap = new Map();
const sinkOptions: Component[] = [];
const alerts: Alert[] = [];
diff --git a/src/pages/Bin.tsx b/src/pages/Bin.tsx
index df3a4a2..076dc37 100644
--- a/src/pages/Bin.tsx
+++ b/src/pages/Bin.tsx
@@ -21,10 +21,6 @@ import {
AccordionSummary,
AccordionDetails,
Typography,
- TextField,
- RadioGroup,
- Checkbox,
- FormControlLabel,
} from "@mui/material";
import BinActions from "bin/BinActions";
import BinHistory from "bin/BinHistory";
@@ -273,7 +269,7 @@ export default function Bin(props: Props) {
resp.data.devices.forEach((dev: any) => {
let device = Device.create(dev);
if (attachedDevIds.includes(device.id())) {
- devs.push(device);
+ devs.push(Device.create(dev));
}
});
setDevices(devs);
@@ -500,7 +496,7 @@ export default function Bin(props: Props) {
-
+
@@ -516,7 +512,7 @@ export default function Bin(props: Props) {
if (showTasks()) {
return (
-
+
);
}
@@ -678,8 +674,6 @@ export default function Bin(props: Props) {
userID={user.id()}
components={components}
setComponents={setComponents}
- componentDevices={componentDevices}
- setComponentDevices={setComponentDevices}
updateBinStatus={updateStatus}
/>
@@ -1162,7 +1156,7 @@ export default function Bin(props: Props) {
setTaskDrawer(false)}>
-
+
)}
diff --git a/src/pages/BinV2.tsx b/src/pages/BinV2.tsx
deleted file mode 100644
index a0f9135..0000000
--- a/src/pages/BinV2.tsx
+++ /dev/null
@@ -1,563 +0,0 @@
-import { useCallback, useEffect, useRef, useState } from "react";
-import PageContainer from "./PageContainer";
-import { DevicePreset } from "models/DevicePreset";
-import { Controller } from "models/Controller";
-import { useNavigate, useParams } from "react-router-dom";
-import { useMobile, useSnackbar } from "hooks";
-import { Component, Device, Bin as IBin, Interaction } from "models";
-import { useBinAPI, useGlobalState } from "providers";
-import { pond } from "protobuf-ts/pond";
-import { Plenum } from "models/Plenum";
-import { Ambient } from "models/Ambient";
-import { GrainCable } from "models/GrainCable";
-import { Pressure } from "models/Pressure";
-import { CO2 } from "models/CO2";
-import moment from "moment";
-import { quack } from "protobuf-ts/quack";
-import { Box, CircularProgress, Drawer, Grid2 as Grid, IconButton, ListItemIcon, ListItemText, Menu, MenuItem, Tab, Tabs, Theme, Tooltip } from "@mui/material";
-import { makeStyles } from "@mui/styles";
-import NotesIcon from "@mui/icons-material/Notes";
-import TasksIcon from "products/Construction/TasksIcon";
-import BindaptIcon from "products/Bindapt/BindaptIcon";
-import FieldsIcon from "products/AgIcons/FieldsIcon";
-import BinActions from "bin/BinActions";
-import BinSummary from "bin/binSummary/BinSummary";
-import { Close } from "@mui/icons-material";
-import React from "react";
-import BinHistory from "bin/BinHistory";
-import Chat from "chat/Chat";
-import TaskViewer from "tasks/TaskViewer";
-
-interface TabPanelProps {
- children?: React.ReactNode;
- index: any;
- value: any;
-}
-
-function TabPanelMine(props: TabPanelProps) {
- const { children, value, index, ...other } = props;
-
- return (
-
- {value === index && {children} }
-
- );
-}
-
-const useStyles = makeStyles((theme: Theme) => {
- const themeType = theme.palette.mode;
- return ({
- spacer: {
- width: "32px",
- height: "32px",
- padding: "auto",
- display: "flex",
- justifyContent: "center",
- alignItems: "center"
- },
- avatar: {
- color: themeType === "light" ? theme.palette.common.black : theme.palette.common.white,
- backgroundColor: "transparent",
- width: theme.spacing(5),
- height: theme.spacing(5),
- border: "1px solid",
- borderColor: theme.palette.divider
- },
- menuPaper: {
- borderRadius: "20px"
- },
- drawerPaperDesktop: {
- height: "100%",
- width: "30%"
- },
- drawerPaperMobile: {
- height: "50%",
- width: "100%"
- },
- })
-})
-
-
-export default function BinV2(){
- const isMobile = useMobile();
- const binID = useParams<{ binID: string }>()?.binID ?? "";
- const binAPI = useBinAPI();
- const classes = useStyles()
- const navigate = useNavigate()
- const [{ user, as, showErrors }] = useGlobalState();
- const [binLoading, setBinLoading] = useState(false);
- const loadRef = useRef(false);
- const [bin, setBin] = useState(IBin.create());
- const [devices, setDevices] = useState([]);
- const [components, setComponents] = useState>(new Map());
- // const [interactions, setInteractions] = useState([]);
- const [permissions, setPermissions] = useState([]);
- const [preferences, setPreferences] = useState>();
- const [plenums, setPlenums] = useState([]);
- // const [ambients, setAmbients] = useState([]);
- const [grainCables, setGrainCables] = useState([]);
- // const [pressures, setPressures] = useState([]);
- // const [headspaceCO2, setHeadspaceCO2] = useState([]);
- const [heaters, setHeaters] = useState([]);
- const {openSnack} = useSnackbar()
- const [fans, setFans] = useState([]);
- // const [compositionNameMap, setCompositionNameMap] = useState>(new Map());
- const [anchorEl, setAnchorEl] = useState(null);
- const [noteTab, setNoteTab] = useState(0)
-
-
- const [componentDevices, setComponentDevices] = useState>(
- new Map()
- );
- const [interactionDevices, setInteractionDevices] = useState>(
- new Map()
- );
- const [binPresets, setBinPresets] = useState([]);
- const [missedReadings, setMissedReadings] = useState(0);
-
- //Drawer states
- const [noteDrawerOpen, setNoteDrawerOpen] = useState(false)
- const [taskDrawerOpen, setTaskDrawerOpen] = useState(false)
-
- //DATA LOAD/UPDATE FUNCTIONS
-
- const load = useCallback(() => {
- if (loadRef.current || user.id() === "") return;
- setBinLoading(true);
- loadRef.current = true;
- //add the presets to the bin page data load
- binAPI
- .getBinPageData(binID, user.id(), showErrors, as)
- .then(resp => {
- // if (resp.data.grainCompositionNames) {
- // let tempMap: Map = new Map();
- // Object.keys(resp.data.grainCompositionNames).forEach(key => {
- // tempMap.set(key, resp.data.grainCompositionNames[key]);
- // });
- // tempMap.set("correction", "Unknown Source");
- // setCompositionNameMap(tempMap);
- // }
-
- let devs: Device[] = [];
- let p = new Map();
- Object.keys(resp.data.preferences).forEach(k => {
- let prefKey = k.split(":")[1] ? k.split(":")[1] : k;
- p.set(prefKey, pond.BinComponentPreferences.fromObject(resp.data.preferences[k]));
- });
- setPreferences(p);
- let r: pond.GetBinPageDataResponse = pond.GetBinPageDataResponse.fromObject(resp.data);
- setBin(IBin.any(resp.data.bin));
- let newComponentDevices = new Map();
- let attachedDevIds: number[] = [];
- if (resp.data.componentDevices)
- Object.keys(resp.data.componentDevices).forEach(k => {
- newComponentDevices.set(k, resp.data.componentDevices[k]);
- //make a list of all of the ids for devices that components are currently attached
- if (!attachedDevIds.includes(resp.data.componentDevices[k])) {
- attachedDevIds.push(resp.data.componentDevices[k]);
- }
- });
-
- setComponentDevices(newComponentDevices);
-
- let newInteractionDevices = new Map();
- if (r.interactionDevices)
- Object.keys(r.interactionDevices).forEach(k => {
- newInteractionDevices.set(k, r.interactionDevices[k]);
- });
- setInteractionDevices(newInteractionDevices);
-
- // if (resp.data.interactions) {
- // setInteractions(resp.data.interactions.map((i: pond.Interaction) => Interaction.any(i)));
- // } else {
- // setInteractions([]);
- // }
- //go through the devices and if the device has an attached component include it in the device list
- if (resp.data.devices && resp.data.devices[0]) {
- resp.data.devices.forEach((dev: any) => {
- let device = Device.create(dev);
- if (attachedDevIds.includes(device.id())) {
- devs.push(device);
- }
- });
- setDevices(devs);
- }
- if (resp.data.components) {
- let compMap: Map = new Map();
- resp.data.components.forEach((comp: pond.Component) => {
- if (comp && comp.settings) {
- let c = Component.any(comp);
- compMap.set(comp.settings.key, c);
- }
- });
- setComponents(compMap);
- }
- if (resp.data.presets) {
- setBinPresets(
- resp.data.presets.map((preset: pond.DevicePreset) => DevicePreset.create(preset))
- );
- }
- setPermissions(r.permissions ? r.permissions : []);
- setBinLoading(false);
- loadRef.current = false;
- })
- .finally(() => {
- setBinLoading(false);
- loadRef.current = false;
- });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [binID, user.id(), binAPI, showErrors, as]);
-
- useEffect(() => {
- load();
- }, [load]);
-
- const setBinComponents = useCallback(() => {
- if (!preferences) return;
- var unassigned: Component[] = [];
- var plenums: Plenum[] = [];
- var ambients: Ambient[] = [];
- var grainCables: GrainCable[] = [];
- var fans: Controller[] = [];
- var heaters: Controller[] = [];
- var pressures: Pressure[] = [];
- var headspaceCo2: CO2[] = [];
- var mostMissed: number = 0;
- let now = moment();
-
- components.forEach(comp => {
- let pref = preferences.get(comp.key());
- if (pref) {
- if (pref.type) {
- if (pref.type === pond.BinComponent.BIN_COMPONENT_PLENUM)
- plenums.push(Plenum.create(comp));
- if (pref.type === pond.BinComponent.BIN_COMPONENT_AMBIENT)
- ambients.push(Ambient.create(comp));
- if (pref.type === pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE) {
- //check cable for missed measurements
- let cable = GrainCable.create(comp);
- let lastRead = moment(cable.lastReading);
- let elapsedMS = now.diff(lastRead);
- if (elapsedMS > cable.settings.measurementPeriodMs) {
- let missedReadings = Math.floor(elapsedMS / cable.settings.measurementPeriodMs);
- if (missedReadings > mostMissed) {
- mostMissed = missedReadings;
- }
- }
- grainCables.push(cable);
- }
- if (pref.type === pond.BinComponent.BIN_COMPONENT_HEATER)
- heaters.push(Controller.create(comp));
- if (pref.type === pond.BinComponent.BIN_COMPONENT_FAN) fans.push(Controller.create(comp));
- if (pref.type === pond.BinComponent.BIN_COMPONENT_PRESSURE)
- pressures.push(Pressure.create(comp));
- if (pref.type === pond.BinComponent.BIN_COMPONENT_HEADSPACE) {
- if (comp.type() === quack.ComponentType.COMPONENT_TYPE_CO2) {
- //check if there are missed readings from the co2 and compare them to what we already had
- let coComp = CO2.create(comp)
- let lastRead = moment(coComp.lastReading);
- let elapsedMS = now.diff(lastRead);
- if (elapsedMS > coComp.settings.measurementPeriodMs) {
- let missedReadings = Math.floor(elapsedMS / coComp.settings.measurementPeriodMs);
- if (missedReadings > mostMissed) {
- mostMissed = missedReadings;
- }
- }
- headspaceCo2.push(coComp);
- }
- }
- } else {
- unassigned.push(comp);
- }
- }
- });
- setMissedReadings(mostMissed);
- setPlenums(plenums);
- setGrainCables(grainCables);
- // setPressures(pressures);
- // setAmbients(ambients);
- setHeaters(heaters);
- setFans(fans);
- // setHeadspaceCO2(headspaceCo2);
- }, [components, preferences]);
-
- useEffect(() => {
- setBinComponents();
- }, [setBinComponents]);
-
- const updateStatus = (componentKeys: string[], removed?: boolean) => {
- componentKeys.forEach(compKey => {
- let comp = components.get(compKey);
- if (comp) {
- //determine what part of the status to update based on the component
- //for lidar
- if (comp.type() === quack.ComponentType.COMPONENT_TYPE_LIDAR) {
- //determine how to update the status based on if added or removed
- if (removed) {
- bin.status.distance = 0;
- } else {
- if (
- comp.lastMeasurement[0] &&
- comp.lastMeasurement[0].values[0] &&
- comp.lastMeasurement[0].values[0].values[0]
- ) {
- bin.status.distance = comp.lastMeasurement[0].values[0].values[0];
- }
- }
- }
- }
- });
- //update the bins status with the new values based on the components changed
- binAPI.updateBinStatus(bin.key(), bin.status, as);
- };
-
- //NAVIGATION FUNCTIONS
- const goToDevice = (dev: Device) => {
- navigate(window.location.pathname + "/devices/" + dev.id(), { replace: true });
- };
-
- const goToYard = (bin: IBin) => {
- navigate("/visualFarm", { state: {
- long: bin.getLocation()?.longitude,
- lat: bin.getLocation()?.latitude
- }
- });
- };
-
-
- /**
- * UI STUFF STARTS HERE
- */
-
- const deviceMenu = () => {
- return (
-
- );
- };
-
- /**
- * Header - consists of the actions and things that are always visible like
- * opening the note/history and tasks drawers, navigating to the device and map pages, opening binsensors and settings etc.
- * also has the bin name/model and activity and the dropdown for changing sections
- */
- const pageHeader = () => {
- return (
-
-
-
-
- setNoteDrawerOpen(true)
- }
- size="small"
- style={{
- marginRight: "0.5rem"
- }}
- className={classes.avatar}
- component="span">
-
-
-
-
-
- setTaskDrawerOpen(true)
- }
- size="small"
- style={{
- marginRight: "0.5rem"
- }}
- className={classes.avatar}
- component="span">
-
-
-
-
- {devices.length > 1 ? (
-
- ) =>
- setAnchorEl(event.currentTarget)
- }
- size="small"
- style={{
- marginRight: "0.5rem"
- }}
- className={classes.avatar}
- component="span">
-
-
-
- ) : (
- devices.map(dev => (
-
- goToDevice(dev)}
- size="small"
- style={{
- marginRight: "0.5rem"
- }}
- className={classes.avatar}
- component="span">
-
-
-
-
-
- ))
- )}
- {bin.binMapped() && (
-
- {
- goToYard(bin);
- }}
- size="small"
- style={{
- marginRight: "0.5rem"
- }}
- className={classes.avatar}
- component="span">
-
-
-
- )}
-
-
- {
- load();
- }}
- userID={user.id()}
- components={components}
- setComponents={setComponents}
- componentDevices={componentDevices}
- setComponentDevices={setComponentDevices}
- updateBinStatus={updateStatus}
- />
-
-
-
- )
- }
-
- const handleChange = (_event: React.ChangeEvent<{}>, newValue: number) => {
- setNoteTab(newValue);
- };
-
-
- //DRAWERS
- const noteDrawer = () => {
- return (
- setNoteDrawerOpen(false)}>
- setNoteDrawerOpen(false)}>
-
-
-
-
-
-
-
- {/* */}
-
- {/* */}
-
-
-
-
-
- )
- }
-
- const taskDrawer = () => {
- return (
- setTaskDrawerOpen(false)}>
-
- setTaskDrawerOpen(false)}>
-
-
-
-
-
- )
- }
-
- return (
-
- {deviceMenu()}
- {pageHeader()}
- {
- setBin(bin)
- binAPI.updateBin(bin.key(), bin.settings).then(resp => {
- openSnack("Bin has Been Updated")
- })
- }}
- setBin={setBin}
- />
- {/* render drawers */}
- {noteDrawer()}
- {taskDrawer()}
-
- )
-}
\ No newline at end of file
diff --git a/src/pages/Bins.tsx b/src/pages/Bins.tsx
index 7adde4e..1b9c75e 100644
--- a/src/pages/Bins.tsx
+++ b/src/pages/Bins.tsx
@@ -58,12 +58,12 @@ import {
// useBinYardAPI,
useGlobalState,
useInteractionsAPI,
- useSnackbar
+ useSnackbar,
+ useTeamAPI
} from "providers";
-import { useTeamAPI } from "providers/pond/teamAPI";
import React, { SetStateAction, useCallback, useEffect, useState } from "react";
// import { getThemeType } from "theme";
-import { stringToMaterialColour } from "utils";
+import { getGrainUnit, stringToMaterialColour } from "utils";
//import InfiniteScroll from "react-infinite-scroller";
import PageContainer from "./PageContainer";
import ObjectTable from "objects/ObjectTable";
@@ -569,11 +569,7 @@ export default function Bins(props: Props) {
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN,
value: describeMeasurement(
- quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
- undefined,
- undefined,
- undefined,
- user
+ quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE
).toStored(bin.settings.highTemp)
}),
componentLocation: quack.ComponentID.create({
@@ -938,7 +934,7 @@ export default function Bins(props: Props) {
return " bu";
}
- switch (user.grainUnit()) {
+ switch (getGrainUnit()) {
case pond.GrainUnit.GRAIN_UNIT_TONNE:
return " mT";
case pond.GrainUnit.GRAIN_UNIT_TON:
@@ -951,9 +947,9 @@ export default function Bins(props: Props) {
const customQuantityDisplay = (customInventory: pond.CustomInventory, bushels: number) => {
let amount = bushels
if(customInventory.bushelsPerTonne > 1){
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
amount = bushels/customInventory.bushelsPerTonne
- }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
+ }else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
amount = bushels/(customInventory.bushelsPerTonne*0.907)
}
}
@@ -963,9 +959,9 @@ export default function Bins(props: Props) {
const supportedGrainDisplay = (grain: pond.Grain, bushels: number) => {
const describer = GrainDescriber(grain)
let amount = bushels
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
amount = bushels/describer.bushelsPerTonne
- }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
+ }else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
amount = bushels/describer.bushelsPerTon
}
return Math.round(amount*100)/100
diff --git a/src/pages/Contract.tsx b/src/pages/Contract.tsx
index b14796b..c91f9ea 100644
--- a/src/pages/Contract.tsx
+++ b/src/pages/Contract.tsx
@@ -281,7 +281,7 @@ export default function Contract() {
{contract.name()} - Notes
-
+
@@ -316,7 +316,7 @@ export default function Contract() {
Notes
-
+
diff --git a/src/pages/Contracts.tsx b/src/pages/Contracts.tsx
index fee4834..e93850b 100644
--- a/src/pages/Contracts.tsx
+++ b/src/pages/Contracts.tsx
@@ -11,7 +11,7 @@ import PageContainer from "./PageContainer";
export default function Contracts() {
const [openSettings, setOpenSettings] = useState(false);
- const [{ as, user }] = useGlobalState()
+ const [{ as }] = useGlobalState()
const contractAPI = useContractAPI();
const [contracts, setContracts] = useState
([]);
const [loading, setLoading] = useState(false);
@@ -42,7 +42,7 @@ export default function Contracts() {
let contractPermissions: Map = new Map();
if (resp.data.contracts){
resp.data.contracts.forEach(contract => {
- let c = Contract.create(contract, user);
+ let c = Contract.create(contract);
contracts.push(c);
let p = pond.EvaluatePermissionsResponse.fromObject(
resp.data.contractPermissions[c.key()]
@@ -56,7 +56,7 @@ export default function Contracts() {
})
.catch(err => {});
}
- }, [contractAPI, year, as, user]); // eslint-disable-line react-hooks/exhaustive-deps
+ }, [contractAPI, year, as]); // eslint-disable-line react-hooks/exhaustive-deps
return (
diff --git a/src/pages/Device.tsx b/src/pages/Device.tsx
index 801c1f1..db9914e 100644
--- a/src/pages/Device.tsx
+++ b/src/pages/Device.tsx
@@ -3,12 +3,10 @@ import Grid from '@mui/material/Grid2';
import { Component, Device, Interaction, User } from "models";
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
import { useDeviceAPI, useGlobalState, useSnackbar } from "providers";
-import { useDeviceStatusStreams } from "hooks/useDeviceStatusStreams";
-import { useCallback, useEffect, useMemo, useState } from "react";
+import { useEffect, useState } from "react";
import { useLocation, useParams } from "react-router-dom";
import PageContainer from "./PageContainer";
-import { useMobile } from "hooks";
-import { useHTTP } from "providers/http";
+import { useHTTP, useMobile } from "hooks";
import LoadingScreen from "app/LoadingScreen";
import SmartBreadcrumb from "common/SmartBreadcrumb";
import DeviceActions from "device/DeviceActions";
@@ -19,25 +17,12 @@ 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";
import { useWebSocket } from "hooks/useWebSocket";
-type TimestampedReading = { timestamp?: string };
-
-function pickNewestReading(
- current: T | null | undefined,
- incoming: T | null | undefined
-): T | null | undefined {
- const currentTs = getTimestampMillis(current?.timestamp);
- const incomingTs = getTimestampMillis(incoming?.timestamp);
- if (incomingTs === undefined) return current ?? incoming;
- if (currentTs === undefined) return incoming ?? current;
- return incomingTs >= currentTs ? incoming : current;
-}
-
export interface DevicePageData {
device: Device;
components: Component[];
@@ -53,7 +38,7 @@ export default function DevicePage() {
const isMobile = useMobile()
const deviceID = useParams<{ deviceID: string }>()?.deviceID ?? "";
const groupID = useParams<{ groupID: string }>()?.groupID ?? "";
- const { state, pathname: locationPathname } = useLocation();
+ const { state } = useLocation();
const [{ as, team, user }] = useGlobalState()
// console.log(state)
const [device, setDevice] = useState(state?.device ? Device.create(state.device) : Device.create())
@@ -83,63 +68,6 @@ export default function DevicePage() {
const { token } = useHTTP();
- const liveContextKeys = useMemo(() => getContextKeys(), [locationPathname]);
- const liveContextTypes = useMemo(() => getContextTypes(), [locationPathname]);
- /** Matches getDevicePageData: omit ?as= when first context type is "team". */
- const liveAs =
- as && !(liveContextTypes.length > 0 && liveContextTypes[0] === "team")
- ? as
- : undefined;
-
- const liveStatusDeviceIds = useMemo(
- () => (deviceID ? [deviceID] : []),
- [deviceID]
- );
-
- const handleLiveDeviceStatus = useCallback(
- (streamDeviceId: string, status: pond.DeviceStatus) => {
- setDevice((prev) => {
- if (prev.id().toString() !== streamDeviceId) return prev;
- const updated = Device.clone(prev);
- const incomingStatus = pond.DeviceStatus.fromObject(status);
- const currentStatus = prev.status ?? pond.DeviceStatus.create();
-
- incomingStatus.plenum = pickNewestReading(currentStatus.plenum, incomingStatus.plenum);
- incomingStatus.sen5x = pickNewestReading(currentStatus.sen5x, incomingStatus.sen5x);
- incomingStatus.co = pickNewestReading(currentStatus.co, incomingStatus.co);
- incomingStatus.co2 = pickNewestReading(currentStatus.co2, incomingStatus.co2);
- incomingStatus.no2 = pickNewestReading(currentStatus.no2, incomingStatus.no2);
- incomingStatus.o2 = pickNewestReading(currentStatus.o2, incomingStatus.o2);
- incomingStatus.lel = pickNewestReading(currentStatus.lel, incomingStatus.lel);
- incomingStatus.h2s = pickNewestReading(currentStatus.h2s, incomingStatus.h2s);
-
- const currentLastActive = getTimestampMillis(currentStatus.lastActive);
- const incomingLastActive = getTimestampMillis(incomingStatus.lastActive);
- if (
- currentLastActive !== undefined &&
- incomingLastActive !== undefined &&
- incomingLastActive < currentLastActive
- ) {
- incomingStatus.lastActive = currentStatus.lastActive;
- }
-
- updated.status = incomingStatus;
- return updated;
- });
- },
- []
- );
-
- useDeviceStatusStreams({
- deviceIds: liveStatusDeviceIds,
- onStatusUpdate: handleLiveDeviceStatus,
- token,
- keys: liveContextKeys,
- types: liveContextTypes,
- as: liveAs,
- enabled: Boolean(deviceID && token),
- });
-
const loadDevice = () => {
if (loading) return
if (state?.devicePageData) {
@@ -245,7 +173,7 @@ export default function DevicePage() {
// When the backend receives a new reading and updates a component,
// this fires and updates the component in state without a page refresh.
useWebSocket<{ key: string; component: Component } | null>({
- path: `/v1/live/devices/${deviceID}/components`,
+ path: `/live/devices/${deviceID}/components`,
parse: (e) => {
try {
const raw = JSON.parse(e.data);
@@ -264,66 +192,37 @@ 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;
});
},
- keys: liveContextKeys,
- types: liveContextTypes,
- as: liveAs,
token,
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`,
+ // --- Real-time device updates (optional) ---
+ // Updates device-level info (name, status, lastActive, etc.)
+ useWebSocket({
+ path: `/live/devices/${deviceID}`,
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);
+ const dev = Device.any(raw);
+ if (!dev?.settings) {
+ console.warn("[ws] received device without settings:", raw);
return null;
}
- return { key: interaction.key(), interaction };
+ return dev;
} catch (err) {
- console.warn("[ws] failed to parse interaction:", err);
+ console.warn("[ws] failed to parse device:", 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;
- });
+ onMessage: (updatedDevice) => {
+ if (!updatedDevice) return;
+ setDevice(updatedDevice);
},
- keys: liveContextKeys,
- types: liveContextTypes,
- as: liveAs,
token,
enabled: !!deviceID,
});
@@ -441,7 +340,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 +357,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()
}
@@ -670,4 +569,4 @@ export default function DevicePage() {
{componentCards()}
);
-}
+}
\ No newline at end of file
diff --git a/src/pages/DeviceComponent.tsx b/src/pages/DeviceComponent.tsx
index e6ab9fa..ad92c3a 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";
@@ -41,12 +38,10 @@ import {
} from "pbHelpers/DeviceAvailability";
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 { useGlobalState, useTeamAPI } from "providers";
+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 +141,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 +388,9 @@ export default function DeviceComponent() {
alignItems="center"
wrap="nowrap"
className={classes.overviewContainer}>
- {(componentPending.pending || componentPending.accepted) && (
+ {!component.status.synced && (
-
+
)}
diff --git a/src/pages/Devices.tsx b/src/pages/Devices.tsx
index 54ff584..0a6b9af 100644
--- a/src/pages/Devices.tsx
+++ b/src/pages/Devices.tsx
@@ -5,9 +5,8 @@ import { makeStyles } from "@mui/styles";
import ResponsiveTable, { Column } from "common/ResponsiveTable";
import ProvisionDevice from "device/ProvisionDevice";
import { pond } from "protobuf-ts/pond";
-import { useDeviceAPI, useGlobalState, useGroupAPI } from "providers";
-import { useHTTP } from "providers/http";
-import { useTeamAPI } from "providers/pond/teamAPI";
+import { useDeviceAPI, useGlobalState, useGroupAPI, useTeamAPI } from "providers";
+import { useHTTP } from "hooks";
import { useDeviceStatusStreams } from "hooks/useDeviceStatusStreams";
import { useCallback, useEffect, useMemo, useState } from "react";
import PageContainer from "./PageContainer";
@@ -265,50 +264,10 @@ export default function Devices() {
)
}, [])
- /** Same context chain as deviceAPI.list / GetContext on the backend (path + group tab + ?as=). */
- const statusStreamKeys = useMemo(() => {
- if (tab !== "all") {
- const g = groups[parseInt(tab)]
- if (g) {
- const keys = [...getContextKeys()]
- keys.push(g.id().toString())
- return keys
- }
- }
- return getContextKeys()
- }, [tab, groups, location.pathname])
-
- const statusStreamTypes = useMemo(() => {
- if (tab !== "all") {
- const g = groups[parseInt(tab)]
- if (g) {
- const types = [...getContextTypes()]
- types.push("group")
- return types
- }
- }
- return getContextTypes()
- }, [tab, groups, location.pathname])
-
- /** Match Device page / getDevicePageData: omit ?as= when URL context already starts with team. */
- const statusStreamAs = useMemo(() => {
- const types =
- tab !== "all" && groups[parseInt(tab)]
- ? [...getContextTypes(), "group"]
- : getContextTypes()
- if (as && !(types.length > 0 && types[0] === "team")) {
- return as
- }
- return undefined
- }, [as, tab, groups, location.pathname])
-
useDeviceStatusStreams({
deviceIds,
onStatusUpdate: handleStatusUpdate,
token,
- keys: statusStreamKeys,
- types: statusStreamTypes,
- as: statusStreamAs,
enabled: devices.length > 0 && !!token,
})
@@ -1205,4 +1164,4 @@ export default function Devices() {
/>
)
-}
+}
\ No newline at end of file
diff --git a/src/pages/Field.tsx b/src/pages/Field.tsx
index 94159fc..15c29db 100644
--- a/src/pages/Field.tsx
+++ b/src/pages/Field.tsx
@@ -10,12 +10,12 @@ import { Settings } from "@mui/icons-material";
import { pond } from "protobuf-ts/pond";
import FieldMinimap from "field/Fieldminimap";
import Weather from "weather/weather";
+import TaskViewer from "tasks/TaskViewer";
import HarvestPlanDisplay from "harvestPlan/HarvestPlanDisplay";
import { cloneDeep } from "lodash";
import HarvestPlanTable from "harvestPlan/HarvestPlanTable";
import { makeStyles } from "@mui/styles";
import FieldSettings from "field/FieldSettings";
-import FieldTaskList from "field/FieldTaskList";
const useStyles = makeStyles((theme: Theme) => {
return ({
@@ -185,9 +185,12 @@ export default function FieldPage() {
}
const tasks = () => {
- return (
-
- )
+ let taskLoadKeys: string[] = [];
+ if (!planLoading) {
+ field.key() !== "" && taskLoadKeys.push(field.key());
+ //hPlan.key() !== "" && taskLoadKeys.push(hPlan.key());
+ }
+ return ()
}
const desktopView = () => {
diff --git a/src/pages/Gate.tsx b/src/pages/Gate.tsx
index c32cb84..db876e4 100644
--- a/src/pages/Gate.tsx
+++ b/src/pages/Gate.tsx
@@ -3,7 +3,7 @@ import { Gate as IGate } from "models/Gate";
//import { Redirect, useHistory, useRouteMatch } from "react-router";
import React, { useCallback, useEffect, useState } from "react";
import PageContainer from "./PageContainer";
-import { useGlobalState, useGateAPI } from "providers";
+import { useGlobalState, useGateAPI, useUserAPI } from "providers";
import {
Box,
ButtonBase,
@@ -20,7 +20,7 @@ import {
} from "@mui/material";
import { pond } from "protobuf-ts/pond";
import DeviceLinkDrawer from "common/DeviceLinkDrawer";
-import { Component, Device } from "models";
+import { Component, Device, Scope } from "models";
import GateActions from "gate/GateActions";
import GateDevice from "gate/GateDevice";
import ObjectControls from "common/ObjectControls";
@@ -28,7 +28,7 @@ import { Link } from "@mui/icons-material";
import Chat from "chat/Chat";
import PlaneIcon from "products/AviationIcons/PlaneIcon";
import NotesIcon from "@mui/icons-material/Notes";
-import { useMobile, usePermissionAPI, useSnackbar, useThemeType } from "hooks";
+import { useMobile, useSnackbar, useThemeType } from "hooks";
import { clone } from "lodash";
import { useNavigate, useParams } from "react-router-dom";
import { makeStyles } from "@mui/styles";
@@ -90,7 +90,7 @@ export default function Gate(props: Props) {
const gateID = gateKey ?? useParams<{ gateKey: string }>()?.gateKey ?? "";
const classes = useStyles();
const gateAPI = useGateAPI();
- const permissionAPI = usePermissionAPI();
+ const userAPI = useUserAPI();
const [gate, setGate] = useState(IGate.create());
const [devices, setDevices] = useState>(
new Map()
@@ -115,10 +115,16 @@ export default function Gate(props: Props) {
};
useEffect(() => {
- permissionAPI.getPermissions(user.id(), [gateID], ["gate"]).then(resp => {
- setPermissions(pond.EvaluatePermissionsResponse.fromObject(resp.data).permissions)
- })
- }, [as, gateID, permissionAPI, user]);
+ let key = gateID;
+ let kind = "gate";
+ if (as) {
+ key = as;
+ kind = "team";
+ }
+ userAPI.getUser(user.id(), { key: key, kind: kind } as Scope).then(resp => {
+ setPermissions(resp.permissions);
+ });
+ }, [as, gateID, userAPI, user]);
const loadGate = useCallback(() => {
let id = gateID;
@@ -394,7 +400,7 @@ export default function Gate(props: Props) {
Notes
-
+
@@ -459,7 +465,7 @@ export default function Gate(props: Props) {
{/* tab for notes on mobile and the map drawer */}
-
+
{/* drawer is for displaying notes on desktop */}
diff --git a/src/pages/Heater.tsx b/src/pages/Heater.tsx
index 582985c..938ec68 100644
--- a/src/pages/Heater.tsx
+++ b/src/pages/Heater.tsx
@@ -32,6 +32,7 @@ import TemperatureIcon from "component/TemperatureIcon";
import FuelIcon from "products/CommonIcons/fuelIcon";
import moment from "moment";
import Warning from "@mui/icons-material/Warning";
+import { getTemperatureUnit } from "utils";
import ResponsiveDialog from "common/ResponsiveDialog";
import { useParams } from "react-router-dom";
@@ -285,7 +286,7 @@ export default function Heater() {
const tempUnit = () => {
let unit = "°C";
- if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
+ if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
unit = "°F";
}
return unit;
diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx
index ed56673..3dc7d2f 100644
--- a/src/pages/Login.tsx
+++ b/src/pages/Login.tsx
@@ -1,11 +1,10 @@
-import { RedirectLoginOptions } from "@auth0/auth0-react";
+import { RedirectLoginOptions, useAuth0 } from "@auth0/auth0-react";
// import { useAuth } from "hooks";
import queryString from "query-string";
import { useEffect } from "react";
import { useLocation } from "react-router";
// import Loading from "./Loading";
import LoadingScreen from "app/LoadingScreen";
-import { useAuthContext } from "providers/authContext";
// interface Props {
// prevPath?: string;
@@ -14,7 +13,7 @@ import { useAuthContext } from "providers/authContext";
export default function Login() {
// const { prevPath } = props;
const location = useLocation();
- const { loginWithRedirect } = useAuthContext();
+ const { loginWithRedirect } = useAuth0();
// const setRouteBeforeLogin = useCallback((): Promise => {
// return new Promise(function(resolve) {
diff --git a/src/pages/Logout.tsx b/src/pages/Logout.tsx
index 79e4538..337c7b6 100644
--- a/src/pages/Logout.tsx
+++ b/src/pages/Logout.tsx
@@ -1,9 +1,9 @@
-import { useAuthContext } from "providers/authContext";
+import { useAuth0 } from "@auth0/auth0-react";
import LoadingScreen from "app/LoadingScreen";
import { useEffect } from "react";
export default function Logout() {
- const { logout } = useAuthContext();
+ const { logout } = useAuth0();
useEffect(() => {
logout();
diff --git a/src/pages/PageContainer.tsx b/src/pages/PageContainer.tsx
index 5ff0a9d..3f0e605 100644
--- a/src/pages/PageContainer.tsx
+++ b/src/pages/PageContainer.tsx
@@ -1,4 +1,4 @@
-import { Container, SxProps, Theme, Typography } from "@mui/material";
+import { Container, SxProps, Theme } from "@mui/material";
import { makeStyles } from "@mui/styles";
import classNames from "classnames";
// import { useMobile } from "hooks";
@@ -47,23 +47,6 @@ const useStyles = makeStyles((theme: Theme) => ({
flexDirection: "column",
justifyContent: "center",
alignItems: "center"
- },
- buildInfo: {
- position: "fixed" as const,
- bottom: 56,
- left: 8,
- fontSize: 10,
- lineHeight: 1.3,
- opacity: 0.35,
- color: theme.palette.text.secondary,
- pointerEvents: "none" as const,
- zIndex: 1,
- [theme.breakpoints.up("sm")]: {
- bottom: 8,
- },
- [theme.breakpoints.up("md")]: {
- left: `calc(${theme.spacing(9)} + 8px)`,
- }
}
}))
@@ -95,13 +78,8 @@ export const PageContainer: React.FunctionComponent = (props: Props) => {
}}
disableGutters
maxWidth={false}
- >
- {children}
-
- {new Date(__BUILD_DATE__).toLocaleDateString()}
- {__GIT_HASH__}
-
-
+ children={<>{children}>}
+ />
);
};
diff --git a/src/pages/SignupCallback.tsx b/src/pages/SignupCallback.tsx
index a8863f8..e315eda 100644
--- a/src/pages/SignupCallback.tsx
+++ b/src/pages/SignupCallback.tsx
@@ -7,7 +7,7 @@ import { CheckCircleOutline } from "@mui/icons-material";
import { green } from "@mui/material/colors";
import Tour, { TourStep } from "common/Tour";
import Emoji from "react-emoji-render";
-import { getWhitelabel, getFeatures } from "services/whiteLabel";
+import { getWhitelabel, IsAdaptiveAgriculture } from "services/whiteLabel";
import { pond } from "protobuf-ts/pond";
import { User } from "models";
// import { color } from "framer-motion";
@@ -154,7 +154,7 @@ export default function SignupCallback () {
Feet (ft)
- {getFeatures().grainUnit && (
+ {IsAdaptiveAgriculture() && (
@@ -247,12 +247,11 @@ export default function TeamPage() {
closeDialogCallback={() => setUserDialog(false)}
refreshCallback={() => {}}
useImitation={false}
- shareLabel="Add Team Member +"
/>
-
+
@@ -266,7 +265,7 @@ export default function TeamPage() {
Groups
- {(f.bins || user.hasFeature("admin")) &&
+ {((isAg || isStreamline) || user.hasFeature("admin")) &&
<>
diff --git a/src/pbHelpers/Component.ts b/src/pbHelpers/Component.ts
index b357813..e809649 100644
--- a/src/pbHelpers/Component.ts
+++ b/src/pbHelpers/Component.ts
@@ -65,20 +65,10 @@ export function stringToComponentId(componentIDString: string): quack.ComponentI
return componentID;
}
- //first split on a colon to get the mux line seperated
- const componentSplit = componentIDString.split(":", 2);
- if(componentSplit[1]){
- componentID.muxLine = Number(componentSplit[1])
- }
-
- const componentIDFragments = componentIDString.split("-", 4);
+ const componentIDFragments = componentIDString.split("-", 3);
let type: string = quack.ComponentType[Number(componentIDFragments[0])];
let addressType: string = quack.AddressType[Number(componentIDFragments[1])];
let address: number = Number(componentIDFragments[2]);
- if(componentIDFragments[3]){
- componentID.expansionLine = Number(componentIDFragments[3])
- }
-
componentID.type = quack.ComponentType[type as keyof typeof quack.ComponentType];
componentID.addressType = quack.AddressType[addressType as keyof typeof quack.AddressType];
componentID.address = address;
diff --git a/src/pbHelpers/ComponentType.tsx b/src/pbHelpers/ComponentType.tsx
index 860e2aa..03be638 100644
--- a/src/pbHelpers/ComponentType.tsx
+++ b/src/pbHelpers/ComponentType.tsx
@@ -39,8 +39,7 @@ import {
Voltage,
VPD,
Weight,
- CapacitorCable,
- AnalogPressure
+ CapacitorCable
} from "pbHelpers/ComponentTypes";
//import { multilineGrainCableData } from "pbHelpers/ComponentTypes/GrainCable";
//import { multilineCapCableData } from "./ComponentTypes/CapacitorCable";
@@ -97,8 +96,7 @@ const COMPONENT_TYPE_MAP = new Map([
[quack.ComponentType.COMPONENT_TYPE_SEN5X, Sen5x],
[quack.ComponentType.COMPONENT_TYPE_VIBRATION_CHAIN, VibrationCable],
[quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, dragerGasDongle],
- [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, Airflow],
- [quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE, AnalogPressure]
+ [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, Airflow]
]);
export interface Subtype {
@@ -238,16 +236,16 @@ export function unitMeasurementSummary(
): Summary {
let vals: string[] = [];
convertedMeasurement.values.forEach(val => {
- vals.push(val + convertedMeasurement.unit);
+ vals.push(val + describer.unit());
});
let v = vals.join(", ");
if (describer.enumerations().length > 0) {
v = describer.enumerations()[parseFloat(vals[0])];
}
return {
- colour: convertedMeasurement.colour,
- label: convertedMeasurement.label,
- type: convertedMeasurement.type,
+ colour: describer.colour(),
+ label: describer.label(),
+ type: describer.type(),
value: v,
error: convertedMeasurement.error
};
@@ -260,8 +258,6 @@ export function unitMeasurementSummaries(
): Summary[] {
let summaries: Summary[] = [];
measurements.measurementsFor.forEach(measurement => {
- //the only reason the user doesnt need to be passed in here is because it is only used for enumerations,
- //things like the unit and colour are in the measurement itself at this point
let describer = describeMeasurement(measurement.type, compType, subtype);
summaries.push(unitMeasurementSummary(measurement, describer));
});
@@ -534,7 +530,6 @@ export function simpleLineChartData(
}
});
}
- // console.log(lineData)
return lineData;
}
@@ -950,15 +945,6 @@ function getOverlayAreas(
return overlayAreas;
}
-
-/**
- * this function adds lines to victory graphs for interactions on the component
- * DEPRECATED: we now use Recharts for our graphs
- * @param componentType
- * @param subtype
- * @param interactionCondition
- * @returns
- */
function createInteractionLine(
componentType: quack.ComponentType,
subtype: number,
diff --git a/src/pbHelpers/ComponentTypes/AnalogPressure.ts b/src/pbHelpers/ComponentTypes/AnalogPressure.ts
deleted file mode 100644
index 7921b38..0000000
--- a/src/pbHelpers/ComponentTypes/AnalogPressure.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import {
- ComponentTypeExtension,
- Summary,
- Subtype,
- simpleMeasurements,
- simpleSummaries,
- unitMeasurementSummaries,
- AreaChartData,
- GraphFilters,
- simpleAreaChartData,
- LineChartData,
- simpleLineChartData
- } from "pbHelpers/ComponentType";
- import PressureDarkIcon from "assets/components/pressureDark.png";
- import PressureLightIcon from "assets/components/pressureLight.png";
- import { quack } from "protobuf-ts/quack";
- import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
- import { convertedUnitMeasurement } from "models/UnitMeasurement";
- import { pond } from "protobuf-ts/pond";
-
- export function AnalogPressure(subtype: number = 0): ComponentTypeExtension {
- let pressure = describeMeasurement(
- quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE,
- quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE,
- subtype
- );
- let voltage = describeMeasurement(
- quack.MeasurementType.MEASUREMENT_TYPE_VOLTAGE,
- quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE,
- subtype
- )
- let addressTypes = [quack.AddressType.ADDRESS_TYPE_I2C];
- return {
- type: quack.ComponentType.COMPONENT_TYPE_PRESSURE,
- subtypes: [
- {
- key: quack.AnalogPressureSubtype.ANALOG_PRESSURE_SUBTYPE_PM1618,
- value: "ANALOG_PRESSURE_SUBTYPE_PM1618",
- friendlyName: "PM 1618"
- } as Subtype
- ],
- friendlyName: "Analog Pressure",
- description: "Measures the atmospheric pressure or absolute pressure",
- isController: false,
- isSource: true,
- isCalibratable: true,
- hasFan: true,
- addressTypes: addressTypes,
- interactionResultTypes: [],
- states: [],
- measurements: simpleMeasurements(pressure, voltage),
- measurementSummary: async function(measurement: quack.Measurement): Promise> {
- return simpleSummaries(measurement, pressure);
- },
- unitMeasurementSummary: (
- measurements: convertedUnitMeasurement,
- ): Summary[] => {
- return unitMeasurementSummaries(
- measurements,
- quack.ComponentType.COMPONENT_TYPE_PRESSURE,
- subtype,
- );
- },
- areaChartData: (
- measurement: pond.UnitMeasurementsForComponent,
- smoothingAverages?: number,
- filters?: GraphFilters
- ): AreaChartData => {
- return simpleAreaChartData(measurement, smoothingAverages, filters);
- },
- lineChartData: (
- measurement: pond.UnitMeasurementsForComponent,
- smoothingAverages?: number,
- filters?: GraphFilters
- ): LineChartData => {
- return simpleLineChartData(
- quack.ComponentType.COMPONENT_TYPE_PRESSURE,
- measurement,
- smoothingAverages,
- filters
- );
- },
- minMeasurementPeriodMs: 1000,
- icon: (theme?: "light" | "dark"): string | undefined => {
- return theme === "light" ? PressureDarkIcon : PressureLightIcon;
- }
- };
- }
\ No newline at end of file
diff --git a/src/pbHelpers/ComponentTypes/index.ts b/src/pbHelpers/ComponentTypes/index.ts
index 39b1560..349bbbe 100644
--- a/src/pbHelpers/ComponentTypes/index.ts
+++ b/src/pbHelpers/ComponentTypes/index.ts
@@ -29,4 +29,3 @@ export * from "./Voltage";
export * from "./VPD";
export * from "./Weight";
export * from "./CapacitorCable";
-export * from "./AnalogPressure";
diff --git a/src/pbHelpers/DeviceAvailability.ts b/src/pbHelpers/DeviceAvailability.ts
index da39022..972c2e2 100644
--- a/src/pbHelpers/DeviceAvailability.ts
+++ b/src/pbHelpers/DeviceAvailability.ts
@@ -64,8 +64,7 @@ const DefaultAvailability: DeviceAvailabilityMap = new Map ;
const oldFirmwareIcon = ;
const currentFirmwareIcon = ;
- const unknownFirmwareIcon = ;
let helper = {} as FirmwareVersionHelper;
if (!version || version === "") {
helper.description = "Unknown version";
helper.icon = oldFirmwareIcon;
helper.tooltip = "We don't know what version your device is, it may behave unexpectedly";
- helper.colour = oldFirmwareColour;
- } else if (version.startsWith("!")) {
- const cleanVersion = version.substring(1);
- helper.description = cleanVersion;
- helper.icon = unknownFirmwareIcon;
- helper.tooltip = "Firmware " + cleanVersion + " was reported by the device but is missing from the firmware list";
- helper.colour = unknownFirmwareColour;
} else if (available !== "" && version !== available) {
helper.description = version;
helper.icon = oldFirmwareIcon;
helper.tooltip =
"A firmware upgrade is available, some features may be disabled for out-of-date devices";
- helper.colour = oldFirmwareColour;
} else if (available === "") {
helper.description = version;
helper.icon = firmwareIcon;
helper.tooltip = "Running version " + version;
- helper.colour = "";
} else {
helper.description = version;
helper.icon = currentFirmwareIcon;
helper.tooltip = "Your device is running the latest firmware";
- helper.colour = currentFirmwareColour;
}
return helper;
}
diff --git a/src/pbHelpers/Interaction.ts b/src/pbHelpers/Interaction.ts
index f48bd73..5b91d00 100644
--- a/src/pbHelpers/Interaction.ts
+++ b/src/pbHelpers/Interaction.ts
@@ -1,4 +1,4 @@
-import { Interaction, Component, User } from "models";
+import { Interaction, Component } from "models";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import { or, notNull } from "utils/types";
@@ -172,8 +172,7 @@ export const interactionResultText = (interaction: Interaction, sink?: Component
export const interactionConditionText = (
source: Component | undefined,
condition: pond.IInteractionCondition,
- and: boolean,
- user?: User
+ and: boolean
) => {
let comparison = "is exactly";
switch (condition.comparison) {
@@ -190,7 +189,7 @@ export const interactionConditionText = (
if (!condition.measurementType) return "Unknown " + comparison + " " + condition.value;
let sourceType = source && source.settings.type;
let sourceSubtype = source && source.settings.subtype;
- let measurement = describeMeasurement(condition.measurementType, sourceType, sourceSubtype, undefined, user);
+ let measurement = describeMeasurement(condition.measurementType, sourceType, sourceSubtype);
return (
(and ? "and " : "") +
measurement.label() +
diff --git a/src/pbHelpers/MeasurementDescriber.ts b/src/pbHelpers/MeasurementDescriber.ts
index ccc50a7..67a5b38 100644
--- a/src/pbHelpers/MeasurementDescriber.ts
+++ b/src/pbHelpers/MeasurementDescriber.ts
@@ -13,13 +13,12 @@ import {
yellow
} from "@mui/material/colors";
import { GraphType } from "common/Graph";
-import { User } from "models";
import { IsCardController } from "products/DeviceProduct";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import { getTextSecondary } from "theme/text";
// import { getTextSecondary } from "theme";
-import { roundTo } from "utils";
+import { getDistanceUnit, getPressureUnit, getTemperatureUnit, roundTo } from "utils";
import { extract } from "utils/types";
interface Enumeration {
@@ -65,8 +64,7 @@ export class MeasurementDescriber {
measurementType: quack.MeasurementType,
componentType: quack.ComponentType,
componentSubtype: number,
- product?: pond.DeviceProduct,
- user?: User
+ product?: pond.DeviceProduct
) {
this.measurementType = measurementType;
this.details = {
@@ -89,7 +87,7 @@ export class MeasurementDescriber {
case quack.MeasurementType.MEASUREMENT_TYPE_INVALID:
break;
case quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE:
- let temperatureUnit = user?.tempUnit() ?? pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS;
+ let temperatureUnit = getTemperatureUnit();
let isFahrenheit = temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT;
this.details.label = "Temperature";
this.details.unit = isFahrenheit ? "°F" : "°C";
@@ -174,9 +172,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"],
@@ -234,8 +233,7 @@ export class MeasurementDescriber {
}
break;
case quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE:
- let pressureUnit =
- user?.pressureUnit() ?? pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER;
+ let pressureUnit = getPressureUnit();
let isIWG = pressureUnit === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER;
this.details.label = "Pressure";
this.details.unit = isIWG ? "iwg" : "kPa";
@@ -439,8 +437,7 @@ export class MeasurementDescriber {
this.details.max = 100000;
break;
case quack.MeasurementType.MEASUREMENT_TYPE_DISTANCE_CM:
- let distanceUnit =
- user?.distanceUnit() ?? pond.DistanceUnit.DISTANCE_UNIT_FEET;
+ let distanceUnit = getDistanceUnit();
this.details.label = "Distance";
this.details.unit = "cm";
if (distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
@@ -454,8 +451,7 @@ export class MeasurementDescriber {
this.details.max = 100000;
break;
case quack.MeasurementType.MEASUREMENT_TYPE_SPEED:
- let speedUnit =
- user?.distanceUnit() ?? pond.DistanceUnit.DISTANCE_UNIT_FEET;
+ let speedUnit = getDistanceUnit()
this.details.label = "Speed";
this.details.unit = speedUnit === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m/s" : "ft/m"; //yes this is meters per second and feet per minute
this.details.colour = green["500"];
@@ -693,12 +689,11 @@ export function describeMeasurement(
componentType: quack.ComponentType | null | undefined = quack.ComponentType
.COMPONENT_TYPE_INVALID,
componentSubtype: number = 0,
- product?: pond.DeviceProduct,
- user?: User
+ product?: pond.DeviceProduct
): MeasurementDescriber {
measurementType = measurementType
? measurementType
: quack.MeasurementType.MEASUREMENT_TYPE_INVALID;
componentType = componentType ? componentType : quack.ComponentType.COMPONENT_TYPE_INVALID;
- return new MeasurementDescriber(measurementType, componentType, componentSubtype, product, user);
+ return new MeasurementDescriber(measurementType, componentType, componentSubtype, product);
}
diff --git a/src/pbHelpers/Status.tsx b/src/pbHelpers/Status.tsx
new file mode 100644
index 0000000..54d6f70
--- /dev/null
+++ b/src/pbHelpers/Status.tsx
@@ -0,0 +1,67 @@
+import { cyan } from "@mui/material/colors";
+import AcceptedIcon from "@mui/icons-material/CloudDone";
+import PendingIcon from "@mui/icons-material/CloudQueueTwoTone";
+import RejectedIcon from "@mui/icons-material/SmsFailed";
+
+export interface StatusHelper {
+ description: string;
+ icon: any;
+}
+
+const Unknown: StatusHelper = {
+ description: "Status unknown",
+ icon: null
+};
+
+const Pending: StatusHelper = {
+ description: "Pending changes",
+ icon:
+};
+
+const Stale: StatusHelper = {
+ description: "Stale update",
+ icon: null
+};
+
+const Accepted: StatusHelper = {
+ description: "Settings synced",
+ icon:
+};
+
+const Rejected: StatusHelper = {
+ description: "Update failed",
+ icon:
+};
+
+const Received: StatusHelper = {
+ description: "Settings synced",
+ icon:
+};
+
+const STATUS_MAP = new Map([
+ ["pending", Pending],
+ ["stale", Stale],
+ ["accepted", Accepted],
+ ["rejected", Rejected],
+ ["received", Received]
+]);
+
+export function getStatusHelper(status: string): StatusHelper {
+ const statuses = Array.from(STATUS_MAP.keys());
+ for (var i = 0; i < statuses.length; i++) {
+ let key = statuses[i];
+ if (status === key) {
+ return STATUS_MAP.get(key) as StatusHelper;
+ }
+ }
+
+ return Unknown;
+}
+
+export function getStatusDescription(status: string): string {
+ return getStatusHelper(status).description;
+}
+
+export function getStatusIcon(status: string): any {
+ return getStatusHelper(status).icon;
+}
diff --git a/src/products/MiVent/MiVentAvailability.ts b/src/products/MiVent/MiVentAvailability.ts
index 87d0db2..0108806 100644
--- a/src/products/MiVent/MiVentAvailability.ts
+++ b/src/products/MiVent/MiVentAvailability.ts
@@ -10,7 +10,6 @@ const MiVentV1Pins: ConfigurablePin[] = [
{ address: 1024, label: "C2" }
];
-//no longer in development so if you are adding new component types DO NOT add the I2C address here because the firmware of the device will not support it
export const MiVentV1Availability: DeviceAvailabilityMap = new Map<
quack.AddressType,
DevicePositions
@@ -54,8 +53,7 @@ export const MiVentV2Availability: DeviceAvailabilityMap = new Map<
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
[quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x6c]],
- [quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, [0x6d]],
- [quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE, [0x6e]]
+ [quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, [0x6d]]
])
],
diff --git a/src/providers/LoginButton.tsx b/src/providers/LoginButton.tsx
index e9e04a8..c8f9aac 100644
--- a/src/providers/LoginButton.tsx
+++ b/src/providers/LoginButton.tsx
@@ -1,7 +1,7 @@
-import { useAuthContext } from './authContext';
+import { useAuth0 } from '@auth0/auth0-react';
const LoginButton = () => {
- const { loginWithRedirect } = useAuthContext();
+ const { loginWithRedirect } = useAuth0();
return (
loginWithRedirect()}>
@@ -10,4 +10,4 @@ const LoginButton = () => {
);
};
-export default LoginButton;
+export default LoginButton;
\ No newline at end of file
diff --git a/src/providers/authContext.tsx b/src/providers/authContext.tsx
deleted file mode 100644
index f5bf790..0000000
--- a/src/providers/authContext.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { RedirectLoginOptions, useAuth0 } from "@auth0/auth0-react"
-import { createContext, PropsWithChildren, useContext } from "react"
-
-interface IAuthContext {
- isAuthenticated: boolean
- loginWithRedirect: (options?: RedirectLoginOptions) => void | Promise
- logout: () => void
-}
-
-const AuthContext = createContext({
- isAuthenticated: false,
- loginWithRedirect: () => {},
- logout: () => {},
-})
-
-export function LocalAuthProvider(props: PropsWithChildren<{ token: string }>) {
- const { children, token } = props
- const doLogout = () => {
- localStorage.removeItem('local_auth_token')
- window.location.href = '/'
- }
- return (
-
- {children}
-
- )
-}
-
-export function Auth0AuthBridge(props: PropsWithChildren<{}>) {
- const { isAuthenticated, loginWithRedirect, logout } = useAuth0()
- return (
- logout({ logoutParams: { returnTo: window.location.origin } }) }}>
- {props.children}
-
- )
-}
-
-export const useAuthContext = () => useContext(AuthContext)
diff --git a/src/providers/http.tsx b/src/providers/http.tsx
index 86ba8b5..3e93f04 100644
--- a/src/providers/http.tsx
+++ b/src/providers/http.tsx
@@ -2,7 +2,7 @@ import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
import moment from "moment";
import { createContext, PropsWithChildren, useContext } from "react";
import PondProvider from "./pond/pond";
-import { useAuthContext } from "./authContext";
+import { useAuth0 } from "@auth0/auth0-react";
import SnackbarProvider from "./Snackbar";
interface IHTTPContext {
@@ -30,7 +30,7 @@ export const HTTPContext = createContext({} as IHTTPContext);
export default function HTTPProvider(props: Props) {
const { children, token } = props;
- const { isAuthenticated, loginWithRedirect } = useAuthContext();
+ const { isAuthenticated, loginWithRedirect } = useAuth0();
const defaultOptions = (demo: boolean = false) => {
if (demo || !isAuthenticated || !token) {
@@ -42,7 +42,7 @@ export default function HTTPProvider(props: Props) {
}
const config = {
- headers: {
+ headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json"
},
@@ -50,24 +50,31 @@ export default function HTTPProvider(props: Props) {
return config;
};
- function isTokenExpired(token: string | undefined): boolean {
- if (!token) return true;
+ function isTokenExpired(token: string): boolean {
try {
+ // Split the token and decode the payload (second part)
const payloadBase64 = token.split('.')[1];
- const payload = JSON.parse(atob(payloadBase64));
+ const decodedPayload = atob(payloadBase64); // Decode base64
+ const payload = JSON.parse(decodedPayload);
+
+ // Get expiration time (in seconds)
const exp = payload.exp;
- if (!exp) return true;
- return Math.floor(Date.now() / 1000) >= exp;
- } catch {
- return true;
+
+ if (!exp) return true; // No exp field? Treat as expired
+
+ // Current time in seconds
+ const now = Math.floor(Date.now() / 1000);
+
+ // Compare
+ return now >= exp;
+ } catch (error) {
+ console.error("Invalid token format:", error);
+ return true; // Err on the side of caution if decoding fails
}
}
function get(url: string, spreadOptions?: AxiosRequestConfig): Promise> {
- if (isTokenExpired(token)) {
- loginWithRedirect();
- return Promise.reject(new Error("token expired"));
- }
+ if (isTokenExpired(token)) loginWithRedirect()
return axios.get(url, {...defaultOptions(), ...spreadOptions});
}
@@ -76,10 +83,6 @@ export default function HTTPProvider(props: Props) {
data?: any,
spreadOptions?: AxiosRequestConfig
): Promise> {
- if (isTokenExpired(token)) {
- loginWithRedirect();
- return Promise.reject(new Error("token expired"));
- }
return axios.put(url, data, { ...defaultOptions(), ...spreadOptions });
}
@@ -88,18 +91,10 @@ export default function HTTPProvider(props: Props) {
data?: any,
spreadOptions?: AxiosRequestConfig
): Promise> {
- if (isTokenExpired(token)) {
- loginWithRedirect();
- return Promise.reject(new Error("token expired"));
- }
return axios.post(url, data, { ...defaultOptions(), ...spreadOptions });
}
function del(url: string, spreadOptions?: AxiosRequestConfig): Promise> {
- if (isTokenExpired(token)) {
- loginWithRedirect();
- return Promise.reject(new Error("token expired"));
- }
return axios.delete(url, { ...defaultOptions(), ...spreadOptions });
}
diff --git a/src/providers/pond/ObjectHeaterAPI.tsx b/src/providers/pond/ObjectHeaterAPI.tsx
index 64e174b..e4f9d2f 100644
--- a/src/providers/pond/ObjectHeaterAPI.tsx
+++ b/src/providers/pond/ObjectHeaterAPI.tsx
@@ -1,7 +1,7 @@
import { AxiosResponse } from "axios";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
import React, { createContext, PropsWithChildren, useContext } from "react";
import { or } from "utils";
import { pondURL } from "./pond";
diff --git a/src/providers/pond/backpackAPI.tsx b/src/providers/pond/backpackAPI.tsx
index c533637..0c05589 100644
--- a/src/providers/pond/backpackAPI.tsx
+++ b/src/providers/pond/backpackAPI.tsx
@@ -1,5 +1,5 @@
import { AxiosResponse } from "axios";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond";
diff --git a/src/providers/pond/binAPI.tsx b/src/providers/pond/binAPI.tsx
index 7c6f267..def6699 100644
--- a/src/providers/pond/binAPI.tsx
+++ b/src/providers/pond/binAPI.tsx
@@ -1,5 +1,4 @@
-import { useHTTP } from "../http";
-import { usePermissionAPI } from "./permissionAPI";
+import { useHTTP, usePermissionAPI } from "hooks";
import { createContext, PropsWithChildren, useContext } from "react";
import { pond } from "protobuf-ts/pond";
import { has, or } from "utils/types";
@@ -7,7 +6,7 @@ import { User, binScope } from "models";
import { pondURL } from "./pond";
import { AxiosResponse } from "axios";
import { dateRange } from "providers/http";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
import { quack } from "protobuf-ts/quack";
export interface IBinAPIContext {
@@ -31,7 +30,6 @@ export interface IBinAPIContext {
removeComponent: (
bin: string,
component: string,
- device?: number,
otherTeam?: string
) => Promise>;
removeAllComponents: (bin: string, otherTeam?: string) => Promise>;
@@ -256,11 +254,10 @@ export default function BinProvider(props: PropsWithChildren) {
})
};
- const removeComponent = (bin: string, component: string, device?: number, otherTeam?: string) => {
+ const removeComponent = (bin: string, component: string, otherTeam?: string) => {
const view = otherTeam ? otherTeam : as
let url = "/bins/" + bin + "/removeComponent/" + component;
- if (device) url = url + "?deviceToRemove=" + device
- if (view) url = url + (device ? "&as=" : "?as=") + view;
+ if (view) url = url + "?as=" + view;
return new Promise>((resolve, reject)=>{
post(pondURL(url)).then(resp => {
resp.data = pond.RemoveBinComponentResponse.fromObject(resp.data)
diff --git a/src/providers/pond/binYardAPI.tsx b/src/providers/pond/binYardAPI.tsx
index f3a64a4..b8d270e 100644
--- a/src/providers/pond/binYardAPI.tsx
+++ b/src/providers/pond/binYardAPI.tsx
@@ -1,10 +1,10 @@
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { createContext, PropsWithChildren, useContext } from "react";
import { pond } from "protobuf-ts/pond";
import { or } from "utils/types";
import { pondURL } from "./pond";
import { AxiosResponse } from "axios";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
export interface IBinYardAPIContext {
addBinYard: (bin: pond.BinYardSettings, otherTeam?: string) => Promise>;
diff --git a/src/providers/pond/cnhiProxyAPI.tsx b/src/providers/pond/cnhiProxyAPI.tsx
index 8b70746..3642783 100644
--- a/src/providers/pond/cnhiProxyAPI.tsx
+++ b/src/providers/pond/cnhiProxyAPI.tsx
@@ -1,5 +1,5 @@
import { AxiosResponse } from "axios";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
import { createContext, PropsWithChildren, useContext } from "react";
//import { or } from "utils";
diff --git a/src/providers/pond/componentAPI.tsx b/src/providers/pond/componentAPI.tsx
index e78b75a..d073b83 100644
--- a/src/providers/pond/componentAPI.tsx
+++ b/src/providers/pond/componentAPI.tsx
@@ -1,4 +1,4 @@
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
// import { useWebsocket } from "websocket";
import { pond } from "protobuf-ts/pond";
import { createContext, PropsWithChildren, useContext } from "react";
@@ -7,7 +7,7 @@ import { getComponentIDString } from "pbHelpers/Component";
import { Component } from "models";
import { pondURL } from "./pond";
import { AxiosResponse } from "axios";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
import { quack } from "protobuf-ts/quack";
export interface IComponentAPIContext {
@@ -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/providers/pond/contractAPI.tsx b/src/providers/pond/contractAPI.tsx
index 60d17fe..3569b52 100644
--- a/src/providers/pond/contractAPI.tsx
+++ b/src/providers/pond/contractAPI.tsx
@@ -1,9 +1,9 @@
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { createContext, PropsWithChildren, useContext } from "react";
import { pond } from "protobuf-ts/pond";
import { pondURL } from "./pond";
import { AxiosResponse } from "axios";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
export interface IContractInterface {
addContract: (
diff --git a/src/providers/pond/datadogProxyAPI.tsx b/src/providers/pond/datadogProxyAPI.tsx
index 8afe849..ec957a4 100644
--- a/src/providers/pond/datadogProxyAPI.tsx
+++ b/src/providers/pond/datadogProxyAPI.tsx
@@ -1,5 +1,5 @@
import { AxiosResponse } from "axios";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond";
diff --git a/src/providers/pond/deviceAPI.tsx b/src/providers/pond/deviceAPI.tsx
index 8b778d5..241781b 100644
--- a/src/providers/pond/deviceAPI.tsx
+++ b/src/providers/pond/deviceAPI.tsx
@@ -1,11 +1,10 @@
-import { useHTTP } from "../http";
-import { usePermissionAPI } from "./permissionAPI";
+import { useHTTP, usePermissionAPI } from "hooks";
import { Component, deviceScope, User } from "models";
import { pond } from "protobuf-ts/pond";
import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond";
import { AxiosResponse } from "axios";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
import moment from "moment";
import { or } from "utils/types";
import { dateRange } from "providers/http";
@@ -700,12 +699,8 @@ export default function DeviceProvider(props: PropsWithChildren) {
};
const setTags = (id: number, tags: string[]) => {
- const keys = getContextKeys()
- const types = getContextTypes()
- let url = "/devices/" + id + "/tags" + "?keys=" + keys + "&types=" + types
- if (as && !keys.includes(as)) url += "&as=" + as
return new Promise((resolve, reject) => {
- put(pondURL(url), { tags }).then(resp => {
+ put(pondURL("/devices/" + id + "/tags"), { tags }).then(resp => {
return resolve(resp)
}).catch(err => {
return reject(err)
@@ -750,12 +745,8 @@ export default function DeviceProvider(props: PropsWithChildren) {
};
const untag = (id: number, tag: string) => {
- const keys = getContextKeys()
- const types = getContextTypes()
- let url = "/devices/" + id + "/tags/" + tag + "?keys=" + keys + "&types=" + types
- if (as && !keys.includes(as)) url += "&as=" + as
return new Promise((resolve, reject) => {
- del(pondURL(url)).then(resp => {
+ del(pondURL("/devices/" + id + "/tags/" + tag)).then(resp => {
return resolve(resp)
}).catch(err => {
return reject(err)
diff --git a/src/providers/pond/devicePresetAPI.tsx b/src/providers/pond/devicePresetAPI.tsx
index 13ed37c..e24d97c 100644
--- a/src/providers/pond/devicePresetAPI.tsx
+++ b/src/providers/pond/devicePresetAPI.tsx
@@ -1,9 +1,9 @@
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { createContext, PropsWithChildren, useContext } from "react";
import { pond } from "protobuf-ts/pond";
import { pondURL } from "./pond";
import { AxiosResponse } from "axios";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
export interface IDevicePresetInterface {
//add
diff --git a/src/providers/pond/fieldAPI.tsx b/src/providers/pond/fieldAPI.tsx
index 4f8cc76..be980eb 100644
--- a/src/providers/pond/fieldAPI.tsx
+++ b/src/providers/pond/fieldAPI.tsx
@@ -1,7 +1,7 @@
import { AxiosResponse } from "axios";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
import React, { createContext, PropsWithChildren, useContext } from "react";
import { or } from "utils";
import { pondURL } from "./pond";
diff --git a/src/providers/pond/fieldMarkerAPI.tsx b/src/providers/pond/fieldMarkerAPI.tsx
index 42b3b4e..f66f616 100644
--- a/src/providers/pond/fieldMarkerAPI.tsx
+++ b/src/providers/pond/fieldMarkerAPI.tsx
@@ -1,7 +1,7 @@
import { AxiosResponse } from "axios";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond";
diff --git a/src/providers/pond/fileControllerAPI.tsx b/src/providers/pond/fileControllerAPI.tsx
index 8b85887..0208584 100644
--- a/src/providers/pond/fileControllerAPI.tsx
+++ b/src/providers/pond/fileControllerAPI.tsx
@@ -1,5 +1,5 @@
import { AxiosResponse } from "axios";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers/StateContainer";
import { createContext, PropsWithChildren, useContext } from "react";
diff --git a/src/providers/pond/firmwareAPI.tsx b/src/providers/pond/firmwareAPI.tsx
index 7bba5ce..43049c9 100644
--- a/src/providers/pond/firmwareAPI.tsx
+++ b/src/providers/pond/firmwareAPI.tsx
@@ -1,4 +1,4 @@
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers/StateContainer";
import { createContext, PropsWithChildren, useContext } from "react";
diff --git a/src/providers/pond/gateAPI.tsx b/src/providers/pond/gateAPI.tsx
index fa6de0f..5b07606 100644
--- a/src/providers/pond/gateAPI.tsx
+++ b/src/providers/pond/gateAPI.tsx
@@ -1,9 +1,9 @@
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { createContext, PropsWithChildren, useContext } from "react";
import { pond } from "protobuf-ts/pond";
import { pondURL } from "./pond";
import { AxiosResponse } from "axios";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
export interface IGateInterface {
addGate: (
diff --git a/src/providers/pond/grainAPI.tsx b/src/providers/pond/grainAPI.tsx
index 93e33c6..0df0055 100644
--- a/src/providers/pond/grainAPI.tsx
+++ b/src/providers/pond/grainAPI.tsx
@@ -1,9 +1,9 @@
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { createContext, PropsWithChildren, useContext } from "react";
import { pond } from "protobuf-ts/pond";
import { pondURL } from "./pond";
import { AxiosResponse } from "axios";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
import { or } from "utils";
export interface IGrainInterface {
diff --git a/src/providers/pond/grainBagAPI.tsx b/src/providers/pond/grainBagAPI.tsx
index 71a052b..7dbdfd7 100644
--- a/src/providers/pond/grainBagAPI.tsx
+++ b/src/providers/pond/grainBagAPI.tsx
@@ -1,9 +1,9 @@
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import React, { createContext, PropsWithChildren, useContext } from "react";
import { pond } from "protobuf-ts/pond";
import { pondURL } from "./pond";
import { AxiosResponse } from "axios";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
export interface IGrainBagInterface {
addGrainBag: (
diff --git a/src/providers/pond/groupAPI.tsx b/src/providers/pond/groupAPI.tsx
index 96c578e..45d140c 100644
--- a/src/providers/pond/groupAPI.tsx
+++ b/src/providers/pond/groupAPI.tsx
@@ -1,12 +1,11 @@
-import { useHTTP } from "../http";
-import { usePermissionAPI } from "./permissionAPI";
+import { useHTTP, usePermissionAPI } from "hooks";
import { createContext, PropsWithChildren, useContext } from "react";
import { pond } from "protobuf-ts/pond";
import { or } from "utils/types";
import { User, groupScope } from "models";
import { pondURL } from "./pond";
import { AxiosResponse } from "axios";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
export interface IGroupAPIContext {
diff --git a/src/providers/pond/harvestPlanAPI.tsx b/src/providers/pond/harvestPlanAPI.tsx
index 14aa571..6c0fbb4 100644
--- a/src/providers/pond/harvestPlanAPI.tsx
+++ b/src/providers/pond/harvestPlanAPI.tsx
@@ -1,5 +1,5 @@
import { AxiosResponse } from "axios";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers/StateContainer";
import { createContext, PropsWithChildren, useContext } from "react";
diff --git a/src/providers/pond/homeMarkerAPI.tsx b/src/providers/pond/homeMarkerAPI.tsx
index 82bb9b3..165cf73 100644
--- a/src/providers/pond/homeMarkerAPI.tsx
+++ b/src/providers/pond/homeMarkerAPI.tsx
@@ -1,7 +1,7 @@
import { AxiosResponse } from "axios";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond";
diff --git a/src/providers/pond/imagekitAPI.tsx b/src/providers/pond/imagekitAPI.tsx
index a827b83..d387241 100644
--- a/src/providers/pond/imagekitAPI.tsx
+++ b/src/providers/pond/imagekitAPI.tsx
@@ -1,6 +1,6 @@
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond";
import { AxiosResponse } from "axios";
diff --git a/src/providers/pond/interactionsAPI.tsx b/src/providers/pond/interactionsAPI.tsx
index 2dab41f..5fa3705 100644
--- a/src/providers/pond/interactionsAPI.tsx
+++ b/src/providers/pond/interactionsAPI.tsx
@@ -1,5 +1,5 @@
import { AxiosResponse } from "axios";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { Interaction } from "models";
import { componentIDToString } from "pbHelpers/Component";
import { pond } from "protobuf-ts/pond";
diff --git a/src/providers/pond/johnDeereProxyAPI.tsx b/src/providers/pond/johnDeereProxyAPI.tsx
index 7382cb7..4042292 100644
--- a/src/providers/pond/johnDeereProxyAPI.tsx
+++ b/src/providers/pond/johnDeereProxyAPI.tsx
@@ -1,5 +1,5 @@
import { AxiosResponse } from "axios";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
import { createContext, PropsWithChildren, useContext } from "react";
//import { or } from "utils";
diff --git a/src/providers/pond/keyManagerAPI.tsx b/src/providers/pond/keyManagerAPI.tsx
index fb65725..68993b7 100644
--- a/src/providers/pond/keyManagerAPI.tsx
+++ b/src/providers/pond/keyManagerAPI.tsx
@@ -1,5 +1,5 @@
import { AxiosResponse } from "axios";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond";
diff --git a/src/providers/pond/libracartProxyAPI.tsx b/src/providers/pond/libracartProxyAPI.tsx
index f470481..b56f65b 100644
--- a/src/providers/pond/libracartProxyAPI.tsx
+++ b/src/providers/pond/libracartProxyAPI.tsx
@@ -1,5 +1,5 @@
import { AxiosResponse } from "axios";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
import React, { createContext, PropsWithChildren, useContext } from "react";
//import { or } from "utils";
diff --git a/src/providers/pond/mineAPI.tsx b/src/providers/pond/mineAPI.tsx
index fe6c2e9..ad55e52 100644
--- a/src/providers/pond/mineAPI.tsx
+++ b/src/providers/pond/mineAPI.tsx
@@ -1,5 +1,5 @@
import { AxiosResponse } from "axios";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { permissionToString } from "pbHelpers/Permission";
import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers/StateContainer";
diff --git a/src/providers/pond/mutationAPI.tsx b/src/providers/pond/mutationAPI.tsx
index b8dd641..ffe4693 100644
--- a/src/providers/pond/mutationAPI.tsx
+++ b/src/providers/pond/mutationAPI.tsx
@@ -1,9 +1,9 @@
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { createContext, PropsWithChildren, useContext } from "react";
import { pond } from "protobuf-ts/pond";
import { pondURL } from "./pond";
import { AxiosResponse } from "axios";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
export interface IMutationAPIContext {
linearMutation: (
diff --git a/src/providers/pond/noteAPI.tsx b/src/providers/pond/noteAPI.tsx
index 8f385da..3cd4f91 100644
--- a/src/providers/pond/noteAPI.tsx
+++ b/src/providers/pond/noteAPI.tsx
@@ -1,12 +1,12 @@
import { AxiosResponse } from "axios";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
-// import { useGlobalState } from "../StateContainer";
+// import { useGlobalState } from "providers";
import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond";
export interface INoteAPIContext {
- addNote: (note: pond.NoteSettings, attachments?: string[], keys?: string[], types?: string[]) => Promise;
+ addNote: (note: pond.NoteSettings, attachments?: string[]) => Promise;
getNote: (noteID: string) => Promise>;
updateNote: (note: pond.NoteSettings) => Promise;
listNotes: (
@@ -16,8 +16,7 @@ export interface INoteAPIContext {
orderBy?: string,
search?: string,
asRoot?: boolean,
- // otherTeam?: string,
- keys?: string[], types?: string[]
+ otherTeam?: string
) => Promise>;
listChats: (
limit: number,
@@ -25,9 +24,8 @@ export interface INoteAPIContext {
order?: "asc" | "desc",
orderBy?: string,
search?: string,
- // asRoot?: boolean,
- // otherTeam?: string
- keys?: string[], types?: string[]
+ asRoot?: boolean,
+ otherTeam?: string
) => Promise>;
removeNote: (noteID: string) => Promise>;
}
@@ -41,13 +39,8 @@ export default function NoteProvider(props: PropsWithChildren) {
const { get, del, post, put } = useHTTP();
// const [{as}] = useGlobalState()
- const addNote = (note: pond.NoteSettings, attachments?: string[], keys?: string[], types?: string[]) => {
- let url = pondURL(
- "/notes" +
- // (attachments ? "?attachments=" + attachments.toString() : "") +
- (keys ? "?keys=" + keys.join(",") : "") +
- (types ? "&types=" + types.join(",") : "")
- )
+ const addNote = (note: pond.NoteSettings, attachments?: string[]) => {
+ let url = pondURL("/notes" + (attachments ? "?attachments=" + attachments.toString() : ""))
return new Promise((resolve, reject) => {
post(url, note).then(resp => {
return resolve(resp)
@@ -96,8 +89,6 @@ export default function NoteProvider(props: PropsWithChildren) {
search?: string,
asRoot?: boolean,
// otherTeam?: string
- keys?: string[],
- types?: string[]
) => {
// const view = otherTeam ? otherTeam : as
let url = pondURL(
@@ -109,9 +100,7 @@ export default function NoteProvider(props: PropsWithChildren) {
("&order=" + (order ? order : "asc")) +
("&by=" + (orderBy ? orderBy : "key")) +
(search ? "&search=" + search : "") +
- (asRoot ? "&asRoot=" + asRoot.toString() : "") +
- (keys ? "&keys=" + keys.join(",") : "") +
- (types ? "&types=" + types.join(",") : "")
+ (asRoot ? "&asRoot=" + asRoot.toString() : ""),
// (view ? "&as=" + "" : "")
)
// console.log(url)
@@ -133,8 +122,6 @@ export default function NoteProvider(props: PropsWithChildren) {
search?: string,
// asRoot?: boolean,
// otherTeam?: string
- keys?: string[],
- types?: string[]
) => {
// const view = otherTeam ? otherTeam : as
let url = pondURL(
@@ -145,9 +132,7 @@ export default function NoteProvider(props: PropsWithChildren) {
offset +
("&order=" + (order ? order : "asc")) +
("&by=" + (orderBy ? orderBy : "key")) +
- (search ? "&search=" + search : "") +
- (keys ? "&keys=" + keys.join(",") : "") +
- (types ? "&types=" + types.join(",") : "")
+ (search ? "&search=" + search : ""),
// (asRoot ? "&asRoot=" + asRoot.toString() : ""),
// (view ? "&as=" + view : "")
)
diff --git a/src/providers/pond/notificationAPI.tsx b/src/providers/pond/notificationAPI.tsx
index 6827ce8..7af3bfe 100644
--- a/src/providers/pond/notificationAPI.tsx
+++ b/src/providers/pond/notificationAPI.tsx
@@ -1,10 +1,10 @@
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import React, { createContext, PropsWithChildren, useContext } from "react";
import { pond } from "protobuf-ts/pond";
import { or } from "utils/types";
import { pondURL } from "./pond";
import { AxiosResponse } from "axios";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
export interface INotificationAPIContext {
listNotifications: (
diff --git a/src/providers/pond/permissionAPI.tsx b/src/providers/pond/permissionAPI.tsx
index 9ec405a..91b7d6a 100644
--- a/src/providers/pond/permissionAPI.tsx
+++ b/src/providers/pond/permissionAPI.tsx
@@ -1,22 +1,16 @@
import { AxiosResponse } from "axios";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { Scope, Team, User } from "models";
import { permissionToString } from "pbHelpers/Permission";
import { pond } from "protobuf-ts/pond";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
import { createContext, PropsWithChildren, useContext } from "react";
import { objectQueryParams, pondURL } from "./pond";
-export interface PermissionChanges {
- key: string; //the key of the object having its permissions changed (parent)
- permissions: string[] //the new permissions the parent will have to the child
-}
-
export interface IPermissionAPIContext {
- getPermissions: (user: string, keys: string[], types: string[]) => Promise>;
+ getPermissions: (user: string, scope: Scope) => Promise>;
removePermissions: (user: string, scope: Scope) => Promise>;
updatePermissions: (scope: Scope, users: User[] | Team[]) => Promise>;
- updateObjectPermissions: (key: string, type: string, parentType: string, changes: PermissionChanges[], keys: string[], types: string[]) => Promise>;
updateRelativePermissions: (
parentScope: Scope,
childScope: Scope,
@@ -31,10 +25,7 @@ export interface IPermissionAPIContext {
shareObjectByKey: (
scope: Scope,
key: string,
- kind: string,
- permissions: pond.Permission[],
- keys?: string[],
- type?: string[]
+ permissions: pond.Permission[]
) => Promise>;
addShareableLink: (scope: Scope, expiration: string) => Promise>;
removeShareableLink: (scope: Scope, code: string) => Promise>;
@@ -53,12 +44,9 @@ export default function PermissionProvider(props: PropsWithChildren) {
const { get, del, put, post } = useHTTP();
const [{ as }] = useGlobalState();
- const getPermissions = (user: string, keys: string[], types: string[]) => {
+ const getPermissions = (user: string, scope: Scope) => {
return new Promise((resolve, reject) => {
- get(pondURL(
- "/users/" + user + "/permissions?types=" + types.toString() + "&keys=" + keys.toString() +
- (as && "&as=" + as)
- )).then(resp => {
+ get(pondURL("/users/" + user + "/permissions" + objectQueryParams(scope))).then(resp => {
return resolve(resp)
}).catch(err => {
return reject(err)
@@ -66,8 +54,6 @@ export default function PermissionProvider(props: PropsWithChildren) {
})
};
-
-
const removePermissions = (user: string, scope: Scope) => {
return new Promise((resolve, reject) => {
del(pondURL("/users/" + user + "/permissions" + objectQueryParams(scope))).then(resp => {
@@ -102,28 +88,6 @@ export default function PermissionProvider(props: PropsWithChildren) {
})
};
- const updateObjectPermissions = (key: string, type: string, parentType: string, changes: PermissionChanges[], keys: string[], types: string[]) => {
- let body = {
- childKey: key,
- childType: type,
- parentType: parentType,
- permissionChanges: changes
- };
-
- if (as) {
- keys.unshift(as)
- types.unshift("team")
- }
-
- return new Promise((resolve, reject) => {
- put(pondURL("/" + type + "s/" + key + "/objectPermissions?keys=" + keys + "&types=" + types ), body).then(resp => {
- return resolve(resp)
- }).catch(err => {
- return reject(err)
- })
- })
- }
-
const shareObject = (
scope: Scope,
email: string,
@@ -167,15 +131,12 @@ export default function PermissionProvider(props: PropsWithChildren) {
})
};
- const shareObjectByKey = (scope: Scope, key: string, kind: string, permissions: pond.Permission[], keys?: string[], types?: string[]) => {
- let url = pondURL("/" + scope.kind + "s/" + scope.key + "/shareByKey") + (keys ? "?keys=" + keys.join(",") : "") +
- (types ? "&types=" + types.join(",") : "")
- if (as) url = pondURL(`/${scope.kind}s/${scope.key}/shareByKey?as=${as}`) + (keys ? "&keys=" + keys.join(",") : "") +
- (types ? "&types=" + types.join(",") : "")
+ const shareObjectByKey = (scope: Scope, key: string, permissions: pond.Permission[]) => {
+ let url = pondURL("/" + scope.kind + "s/" + scope.key + "/shareByKey")
+ if (as) url = pondURL(`/${scope.kind}s/${scope.key}/shareByKey?as=${as}`)
return new Promise((resolve, reject) => {
post(url, {
key: key,
- kind: kind,
permissions: permissions.map(permission => permissionToString(permission))
}).then(resp => {
return resolve(resp)
@@ -235,7 +196,6 @@ export default function PermissionProvider(props: PropsWithChildren) {
getPermissions,
removePermissions,
updatePermissions,
- updateObjectPermissions,
updateRelativePermissions,
shareObject,
shareObjectByKey,
diff --git a/src/providers/pond/preferenceAPI.tsx b/src/providers/pond/preferenceAPI.tsx
index e51776a..91ed9b7 100644
--- a/src/providers/pond/preferenceAPI.tsx
+++ b/src/providers/pond/preferenceAPI.tsx
@@ -1,4 +1,4 @@
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond";
diff --git a/src/providers/pond/siteAPI.tsx b/src/providers/pond/siteAPI.tsx
index 596056e..95f0a45 100644
--- a/src/providers/pond/siteAPI.tsx
+++ b/src/providers/pond/siteAPI.tsx
@@ -1,5 +1,5 @@
import { AxiosResponse } from "axios";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers/StateContainer";
import { createContext, PropsWithChildren, useContext } from "react";
diff --git a/src/providers/pond/tagAPI.tsx b/src/providers/pond/tagAPI.tsx
index 7f09461..071591b 100644
--- a/src/providers/pond/tagAPI.tsx
+++ b/src/providers/pond/tagAPI.tsx
@@ -1,4 +1,4 @@
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond";
diff --git a/src/providers/pond/taskAPI.tsx b/src/providers/pond/taskAPI.tsx
index 912d87a..126f012 100644
--- a/src/providers/pond/taskAPI.tsx
+++ b/src/providers/pond/taskAPI.tsx
@@ -1,12 +1,12 @@
import { AxiosResponse } from "axios";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond";
export interface ITaskAPIContext {
- addTask: (task: pond.TaskSettings, otherTeam?: string, keys?: string[], types?: string[]) => Promise>;
+ addTask: (task: pond.TaskSettings, otherTeam?: string) => Promise>;
getTask: (taskID: string, otherTeam?: string) => Promise>;
listTasks: (
limit: number,
@@ -17,9 +17,7 @@ export interface ITaskAPIContext {
asRoot?: boolean,
otherTeam?: string,
from?: string,
- to?: string,
- keys?: string[],
- types?: string[]
+ to?: string
) => Promise>;
removeTask: (taskID: string, otherTeam?: string) => Promise>;
updateTask: (
@@ -40,20 +38,10 @@ export default function TaskProvider(props: PropsWithChildren) {
const { get, del, post, put } = useHTTP();
const [{ as }] = useGlobalState();
- const addTask = (task: pond.TaskSettings, otherTeam?: string, keys?: string[], types?: string[]) => {
+ const addTask = (task: pond.TaskSettings, otherTeam?: string) => {
const view = otherTeam ? otherTeam : as
- if (view) return post(
- pondURL(
- "/tasks?as=" + view +
- (keys ? "&keys=" + keys.join(",") : "") +
- (types ? "&types=" + types.join(",") : "")
- ), task);
- return post(
- pondURL(
- "/tasks" +
- (keys ? "?keys=" + keys.join(",") : "") +
- (types ? "&types=" + types.join(",") : "")
- ), task);
+ if (view) return post(pondURL("/tasks?as=" + view), task);
+ return post(pondURL("/tasks"), task);
};
const getTask = (taskID: string, otherTeam?: string) => {
@@ -77,9 +65,7 @@ export default function TaskProvider(props: PropsWithChildren) {
asRoot?: boolean,
otherTeam?: string,
from?: string,
- to?: string,
- keys?: string[],
- types?: string[]
+ to?: string
) => {
const view = otherTeam ? otherTeam : as
return get(
@@ -95,9 +81,7 @@ export default function TaskProvider(props: PropsWithChildren) {
(asRoot ? "&asRoot=" + asRoot.toString() : "") +
(view ? "&as=" + view : "") +
(from ? "&from=" + from : "") +
- (to ? "&to=" + to : "") +
- (keys ? "&keys=" + keys.join(",") : "") +
- (types ? "&types=" + types.join(",") : "")
+ (to ? "&to=" + to : "")
)
);
};
diff --git a/src/providers/pond/teamAPI.tsx b/src/providers/pond/teamAPI.tsx
index 1c1a267..32f8a4a 100644
--- a/src/providers/pond/teamAPI.tsx
+++ b/src/providers/pond/teamAPI.tsx
@@ -1,11 +1,11 @@
import { AxiosResponse } from "axios";
import { Scope, Team } from "models";
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
import { createContext, PropsWithChildren, useContext } from "react";
import { or } from "utils/types";
import { pondURL } from "./pond";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
export interface ITeamAPIContext {
addTeam: (team: pond.TeamSettings) => Promise>;
@@ -32,7 +32,7 @@ export interface ITeamAPIContext {
prefixSearch?: string,
) => Promise>;
listAllTeams: () => Promise>;
- listObjectTeams: (scope: Scope, otherTeam?: string, keys?: string[], types?: string[]) => Promise;
+ listObjectTeams: (scope: Scope, otherTeam?: string) => Promise;
removeTeam: (key: string) => Promise>;
}
@@ -156,12 +156,10 @@ export default function TeamProvider(props: PropsWithChildren) {
})
};
- const listObjectTeams = (scope: Scope, otherTeam?: string, keys?: string[], types?: string[]) => {
- let url = "/" + scope.kind + "s/" + scope.key + "/teams" + (keys ? "?keys=" + keys.join(",") : "") +
- (types ? "&types=" + types.join(",") : "")
+ const listObjectTeams = (scope: Scope, otherTeam?: string) => {
+ let url = "/" + scope.kind + "s/" + scope.key + "/teams"
const view = otherTeam ? otherTeam : as
- if (view) url = `/${scope.kind}s/${scope.key}/teams?as=${view}` + (keys ? "&keys=" + keys.join(",") : "") +
- (types ? "&types=" + types.join(",") : "")
+ if (view) url = `/${scope.kind}s/${scope.key}/teams?as=${view}`
return new Promise((resolve, reject) => {
get(pondURL(url)).then(resp => {
return resolve(resp)
diff --git a/src/providers/pond/terminalAPI.tsx b/src/providers/pond/terminalAPI.tsx
index 46ecf30..26297c3 100644
--- a/src/providers/pond/terminalAPI.tsx
+++ b/src/providers/pond/terminalAPI.tsx
@@ -1,9 +1,9 @@
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { createContext, PropsWithChildren, useContext } from "react";
import { pond } from "protobuf-ts/pond";
import { pondURL } from "./pond";
import { AxiosResponse } from "axios";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
export interface ITerminalAPIContext {
addTerminal: (
diff --git a/src/providers/pond/transactionAPI.tsx b/src/providers/pond/transactionAPI.tsx
index 48904bf..fc1db72 100644
--- a/src/providers/pond/transactionAPI.tsx
+++ b/src/providers/pond/transactionAPI.tsx
@@ -1,9 +1,9 @@
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import { createContext, PropsWithChildren, useContext } from "react";
import { pond } from "protobuf-ts/pond";
import { pondURL } from "./pond";
import { AxiosResponse } from "axios";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
import { or } from "utils";
export interface ITransactionAPIContext {
diff --git a/src/providers/pond/usageAPI.tsx b/src/providers/pond/usageAPI.tsx
index 4d9d516..92de655 100644
--- a/src/providers/pond/usageAPI.tsx
+++ b/src/providers/pond/usageAPI.tsx
@@ -1,6 +1,6 @@
-import { useHTTP } from "../http";
+import { useHTTP } from "hooks";
import moment from "moment";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond";
diff --git a/src/providers/pond/userAPI.tsx b/src/providers/pond/userAPI.tsx
index dfd2529..0d33b2e 100644
--- a/src/providers/pond/userAPI.tsx
+++ b/src/providers/pond/userAPI.tsx
@@ -1,10 +1,10 @@
-// import { useHTTP } from "../http";
+// import { useHTTP } from "hooks";
// import { Scope, User } from "models";
// import { pond } from "protobuf-ts/pond";
import { createContext, PropsWithChildren, useContext } from "react";
import { objectQueryParams, pondURL } from "./pond";
// import { or } from "utils";
-import { useGlobalState } from "../StateContainer";
+import { useGlobalState } from "providers";
// import { AxiosResponse } from "axios";
import { useHTTP } from "../http";
import { AxiosResponse } from "axios";
diff --git a/src/services/restAPI.js b/src/services/restAPI.js
index 8539729..ba073f4 100644
--- a/src/services/restAPI.js
+++ b/src/services/restAPI.js
@@ -1,4 +1,4 @@
-import axios from "axios";
+import * as axios from "axios";
export var defaultOptions = {
headers: {
diff --git a/src/services/whiteLabel.ts b/src/services/whiteLabel.ts
index 90cf4ed..29c4427 100644
--- a/src/services/whiteLabel.ts
+++ b/src/services/whiteLabel.ts
@@ -1,3 +1,4 @@
+// import { isOffline } from "utils/environment";
import DefaultDarkLogo from "../assets/whitelabels/darkLogo.png";
import DefaultLightLogo from "../assets/whitelabels/lightLogo.png";
import AdapativeAgLogo from "../assets/whitelabels/AdaptiveAgriculture/logo.png";
@@ -8,10 +9,10 @@ import BXTDarkLogo from "../assets/whitelabels/BXT/darkLogo.png";
import AeroGrowDarkLogo from "../assets/whitelabels/AeroGrow/darkLogo.png";
import AeroGrowLightLogo from "../assets/whitelabels/AeroGrow/lightLogo.png";
import MiVentLightLogo from "../assets/whitelabels/MiVent/lightLogo.png";
+// import OmniAirLogo from "../assets/whitelabels/OmniAir/OmniAirLogo.png";
import MiPCALogo from "../assets/whitelabels/MiPCA/MiPCALogo.png";
import StreamlineLogo from "../assets/whitelabels/Streamline/stream-logo.png"
-import IntellifarmsLogo from "../assets/whitelabels/Intellifarms/IFND-2023-Logo.png"
-import IntellifarmsLogoWhite from "../assets/whitelabels/Intellifarms/IFND-2023-Logo-White.png"
+// import { green, yellow } from "@mui/material/colors";
const protips: string[] = [
"You can see the latest measurements for a device by starring its components!",
@@ -28,411 +29,327 @@ 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;
- contracts: boolean;
- transactions: boolean;
- fields: boolean;
- grainTypes: boolean;
- grainUnit: boolean;
- marketplace: boolean;
- cableEstimator: boolean;
- ventilation: boolean;
- mines: boolean;
- constructionMap: boolean;
- jobsites: boolean;
- heaters: boolean;
- aviationMap: boolean;
- terminals: boolean;
- security: boolean;
-}
-
interface WhiteLabel {
- slug: string;
- name: string;
- primaryColour: any;
- secondaryColour: any;
- signatureColour: any;
- signatureAccentColour: any;
- headerColour?: any;
- auth0ClientId: string;
+ name: any;
+ primaryColour: any; //must be a MATERIAL UI colour, used for various UI elements
+ secondaryColour: any; //must be a MATERIAL UI colour, used for a few UI elements (less importance)
+ signatureColour: any; //hex or RGB
+ signatureAccentColour: any; //hex or RGB
+ auth0ClientId: any;
redirectOnLogout: boolean;
logoutRedirectTarget: string;
darkLogo: any;
lightLogo: any;
- transparentLogoBG: boolean;
+ transparentLogoBG: boolean; //determines whether the background of the logo should be transparent or not
blacklist: string[];
- features: WhiteLabelFeatures;
hotjarID?: string;
docs: string;
protips: string[];
tutorialPlaylistID?: string;
thumbnail?: string;
tutorialFiles?: { name: string; url: string }[];
- homePage: string;
+ homePage?: string;
}
-const AG_FEATURES: WhiteLabelFeatures = {
- bins: true,
- visualFarm: true,
- contracts: true,
- transactions: true,
- fields: true,
- grainTypes: true,
- grainUnit: true,
- marketplace: true,
- cableEstimator: true,
- ventilation: false,
- mines: false,
- constructionMap: false,
- jobsites: false,
- heaters: false,
- aviationMap: false,
- terminals: false,
- security: false,
+// const DEFAULT_WHITELABEL: WhiteLabel = {
+// name: import.meta.env.REACT_APP_WEBSITE_TITLE,
+// primaryColour: import.meta.env.REACT_APP_PRIMARY_COLOUR,
+// secondaryColour: import.meta.env.REACT_APP_SECONDARY_COLOUR,
+// signatureColour: import.meta.env.REACT_APP_SIGNATURE_COLOUR,
+// signatureAccentColour: "#fff",
+// auth0ClientId: import.meta.env.REACT_APP_AUTH0_CLIENT_ID,
+// redirectOnLogout: false,
+// logoutRedirectTarget: "",
+// darkLogo: DefaultDarkLogo,
+// lightLogo: DefaultLightLogo,
+// transparentLogoBG: false,
+// blacklist: [],
+// hotjarID: import.meta.env.REACT_APP_HOTJAR_ID_BXT,
+// docs: "Platform",
+// protips: protips,
+// tutorialPlaylistID: ""
+// };
+
+const STAGING_WHITELABEL: WhiteLabel = {
+ name: "Staging",
+ primaryColour: import.meta.env.VITE_APP_PRIMARY_COLOUR,
+ secondaryColour: import.meta.env.VITE_APP_SECONDARY_COLOUR,
+ signatureColour: import.meta.env.VITE_APP_SIGNATURE_COLOUR,
+ signatureAccentColour: "#fff",
+ auth0ClientId: import.meta.env.VITE_AUTH0_STAGING_CLIENT_ID,
+ redirectOnLogout: false,
+ logoutRedirectTarget: "",
+ darkLogo: DefaultDarkLogo,
+ lightLogo: DefaultLightLogo,
+ transparentLogoBG: false,
+ blacklist: [],
+ hotjarID: import.meta.env.REACT_APP_HOTJAR_ID_BXT,
+ docs: "Platform",
+ protips: protips,
+ tutorialPlaylistID: "",
+ homePage: "/devices"
};
-const CONSTRUCTION_FEATURES: WhiteLabelFeatures = {
- bins: false,
- visualFarm: false,
- contracts: false,
- transactions: false,
- fields: false,
- grainTypes: false,
- grainUnit: false,
- marketplace: false,
- cableEstimator: false,
- ventilation: false,
- mines: false,
- constructionMap: true,
- jobsites: true,
- heaters: true,
- aviationMap: false,
- terminals: false,
- security: false,
+const BXT_WHITE_LABEL: WhiteLabel = {
+ name: "Brand X Technologies",
+ primaryColour: "blue",
+ // primaryColour: "#0000FF",
+ secondaryColour: "yellow",
+ // secondaryColour: "#FFFF00",
+ // signatureColour: "#272727",
+ signatureColour: "#005bb0",
+ signatureAccentColour: "#fff",
+ auth0ClientId: import.meta.env.VITE_AUTH0_DEV_CLIENT_ID,
+ redirectOnLogout: false,
+ logoutRedirectTarget: "",
+ darkLogo: BXTDarkLogo,
+ lightLogo: BXTLightLogo,
+ transparentLogoBG: false,
+ blacklist: [],
+ hotjarID: import.meta.env.REACT_APP_HOTJAR_ID_BXT,
+ docs: "Platform",
+ protips: protips,
+ homePage: "/devices"
};
-const AVIATION_FEATURES: WhiteLabelFeatures = {
- bins: false,
- visualFarm: false,
- contracts: false,
- transactions: false,
- fields: false,
- grainTypes: false,
- grainUnit: false,
- marketplace: false,
- cableEstimator: false,
- ventilation: false,
- mines: false,
- constructionMap: false,
- jobsites: false,
- heaters: false,
- aviationMap: true,
- terminals: true,
- security: false,
+const STREAMLINE_WHITE_LABEL: WhiteLabel = {
+ name: "Streamline",
+ primaryColour: "#FFFF",
+ // primaryColour: "#0000FF",
+ secondaryColour: "yellow",
+ // secondaryColour: "#FFFF00",
+ // signatureColour: "#272727",
+ signatureColour: "#005bb0",
+ signatureAccentColour: "#fff",
+ auth0ClientId: import.meta.env.VITE_AUTH0_STREAMLINE_CLIENT_ID,
+ redirectOnLogout: false,
+ logoutRedirectTarget: "",
+ darkLogo: StreamlineLogo,
+ lightLogo: StreamlineLogo,
+ transparentLogoBG: false,
+ blacklist: [],
+ hotjarID: import.meta.env.REACT_APP_HOTJAR_ID_BXT,
+ docs: "Platform",
+ protips: protips,
+ homePage: "/devices"
};
-const MIVENT_FEATURES: WhiteLabelFeatures = {
- bins: false,
- visualFarm: false,
- contracts: false,
- transactions: false,
- fields: false,
- grainTypes: false,
- grainUnit: false,
- marketplace: false,
- cableEstimator: false,
- ventilation: true,
- mines: true,
- constructionMap: false,
- jobsites: false,
- heaters: false,
- aviationMap: false,
- terminals: false,
- security: false,
-};
-
-const BASE_FEATURES: WhiteLabelFeatures = {
- bins: false,
- visualFarm: false,
- contracts: false,
- transactions: false,
- fields: false,
- grainTypes: false,
- grainUnit: false,
- marketplace: false,
- cableEstimator: false,
- ventilation: false,
- mines: false,
- constructionMap: false,
- jobsites: false,
- heaters: false,
- aviationMap: false,
- terminals: false,
- security: false,
-};
-
-const registry: Record = {
- "bxt": {
- slug: "bxt",
- name: "Brand X Technologies",
- primaryColour: "blue",
- secondaryColour: "yellow",
- signatureColour: "#005bb0",
- signatureAccentColour: "#fff",
- auth0ClientId: import.meta.env.VITE_AUTH0_DEV_CLIENT_ID,
-
- redirectOnLogout: false,
- logoutRedirectTarget: "",
- darkLogo: BXTDarkLogo,
- lightLogo: BXTLightLogo,
- transparentLogoBG: false,
- blacklist: [],
- features: { ...BASE_FEATURES, security: true },
- hotjarID: import.meta.env.REACT_APP_HOTJAR_ID_BXT,
- docs: "Platform",
- protips: protips,
- homePage: "/devices",
- },
- "staging": {
- slug: "staging",
- name: "Staging",
- primaryColour: import.meta.env.VITE_APP_PRIMARY_COLOUR,
- secondaryColour: import.meta.env.VITE_APP_SECONDARY_COLOUR,
- signatureColour: import.meta.env.VITE_APP_SIGNATURE_COLOUR,
- signatureAccentColour: "#fff",
- auth0ClientId: import.meta.env.VITE_AUTH0_STAGING_CLIENT_ID,
-
- redirectOnLogout: false,
- logoutRedirectTarget: "",
- darkLogo: DefaultDarkLogo,
- lightLogo: DefaultLightLogo,
- transparentLogoBG: false,
- blacklist: [],
- features: AG_FEATURES,
- hotjarID: import.meta.env.REACT_APP_HOTJAR_ID_BXT,
- docs: "Platform",
- protips: protips,
- homePage: "/devices",
- },
- "adaptive-ag": {
- slug: "adaptive-ag",
- name: "Adaptive Agriculture",
- primaryColour: "green",
- secondaryColour: "yellow",
- signatureColour: "#272727",
- signatureAccentColour: "#fff",
- auth0ClientId: import.meta.env.VITE_AUTH0_ADAPTIVE_AGRICULTURE_CLIENT_ID,
-
- redirectOnLogout: true,
- logoutRedirectTarget: "https://adaptiveagriculture.ca",
- darkLogo: AdapativeAgLogo,
- lightLogo: AdapativeAgLogo,
- transparentLogoBG: true,
- blacklist: ["cost"],
- features: AG_FEATURES,
- hotjarID: import.meta.env.REACT_APP_HOTJAR_ID_ADAPTIVE_AGRICULTURE,
- docs: "AdaptiveAg",
- protips: protips,
- tutorialPlaylistID: "PLpLmJnI66Jfl5FXME31ckGam-sD8gB1s2",
- thumbnail: AdaptiveAgThumbnail,
- tutorialFiles: [
- {
- name: "Bindapt+",
- url: "https://adaptiveagriculture.ca/wp-content/uploads/2023/08/Bindapt-Set-Up-Guide.pdf"
- },
- {
- name: "Adapter Plate",
- url: "https://adaptiveagriculture.ca/wp-content/uploads/2023/08/Bindapt-Adapter-Plate-Set-Up-Guide.pdf"
- }
- ],
- homePage: "/bins",
- },
- "intellifarms": {
- slug: "intellifarms",
- name: "Intellifarms",
- primaryColour: "#9E1B32",
- secondaryColour: "#4A4F55",
- signatureColour: "#272727",
- signatureAccentColour: "#fff",
- auth0ClientId: import.meta.env.VITE_AUTH0_INTELLIFARMS_CLIENT_ID,
-
- redirectOnLogout: true,
- logoutRedirectTarget: "https://myintellifarms.com",
- darkLogo: IntellifarmsLogo,
- lightLogo: IntellifarmsLogoWhite,
- transparentLogoBG: true,
- blacklist: [],
- features: AG_FEATURES,
- docs: "Platform",
- protips: protips,
- homePage: "/bins",
- },
- "streamline": {
- slug: "streamline",
- name: "Streamline",
- primaryColour: "#FFFF",
- secondaryColour: "yellow",
- signatureColour: "#005bb0",
- signatureAccentColour: "#fff",
- auth0ClientId: import.meta.env.VITE_AUTH0_STREAMLINE_CLIENT_ID,
-
- redirectOnLogout: false,
- logoutRedirectTarget: "",
- darkLogo: StreamlineLogo,
- lightLogo: StreamlineLogo,
- transparentLogoBG: false,
- blacklist: [],
- features: AG_FEATURES,
- docs: "Platform",
- protips: protips,
- homePage: "/devices",
- },
- "aerogrow": {
- slug: "aerogrow",
- name: "AeroGrow",
- primaryColour: "green",
- secondaryColour: "cyan",
- signatureColour: "#fff",
- signatureAccentColour: "#000",
- auth0ClientId: import.meta.env.VITE_AUTH0_AEROGROW_CLIENT_ID,
-
- redirectOnLogout: true,
- logoutRedirectTarget: "https://www.aerogrowmanufacturing.com",
- darkLogo: AeroGrowDarkLogo,
- lightLogo: AeroGrowLightLogo,
- transparentLogoBG: true,
- blacklist: [],
- features: BASE_FEATURES,
- hotjarID: import.meta.env.REACT_APP_HOTJAR_ID_AEROGROW,
- docs: "Platform",
- protips: protips,
- homePage: "/devices",
- },
- "mivent": {
- slug: "mivent",
- name: "MiVent",
- primaryColour: "green",
- secondaryColour: "yellow",
- signatureColour: "#272727",
- signatureAccentColour: "#fff",
- auth0ClientId: import.meta.env.VITE_AUTH0_MIVENT_CLIENT_ID,
-
- redirectOnLogout: true,
- logoutRedirectTarget: "https://mivent.ca",
- darkLogo: MiVentLightLogo,
- lightLogo: MiVentLightLogo,
- transparentLogoBG: true,
- blacklist: ["cost"],
- features: MIVENT_FEATURES,
- hotjarID: import.meta.env.REACT_APP_HOTJAR_ID_MIVENT,
- docs: "Platform",
- protips: protips,
- homePage: "/devices",
- },
- "adaptive-construction": {
- slug: "adaptive-construction",
- name: "Adaptive Construction",
- primaryColour: "blue",
- secondaryColour: "blue",
- signatureColour: "#272727",
- signatureAccentColour: "#fff",
- auth0ClientId: import.meta.env.VITE_AUTH0_ADAPTIVE_CONSTRUCTION_CLIENT_ID,
-
- redirectOnLogout: true,
- logoutRedirectTarget: "https://adaptiveconstruction.ca",
- darkLogo: AdConLogo,
- lightLogo: AdConLogo,
- transparentLogoBG: true,
- blacklist: ["cost"],
- features: CONSTRUCTION_FEATURES,
- hotjarID: import.meta.env.REACT_APP_HOTJAR_ID_ADAPTIVE_CONSTRUCTION,
- docs: "AdaptiveConstruction",
- protips: protips,
- homePage: "/devices",
- },
- "omniair": {
- slug: "omniair",
- name: "OmniAir",
- primaryColour: "#004f9b",
- secondaryColour: "yellow",
- signatureColour: "#272727",
- signatureAccentColour: "#fff",
- auth0ClientId: import.meta.env.VITE_AUTH0_OMNIAIR_CLIENT_ID,
-
- redirectOnLogout: true,
- logoutRedirectTarget: "https://omniairsystems.com",
- darkLogo: MiPCALogo,
- lightLogo: MiPCALogo,
- transparentLogoBG: true,
- blacklist: ["cost"],
- features: AVIATION_FEATURES,
- docs: "OmniAir",
- protips: protips,
- homePage: "/devices",
- },
- "mipca": {
- slug: "mipca",
- name: "MiPCA",
- primaryColour: "#004f9b",
- secondaryColour: "yellow",
- signatureColour: "#272727",
- signatureAccentColour: "#fff",
- auth0ClientId: import.meta.env.VITE_AUTH0_OMNIAIR_CLIENT_ID,
-
- redirectOnLogout: true,
- logoutRedirectTarget: "https://mionetech.com",
- darkLogo: MiPCALogo,
- lightLogo: MiPCALogo,
- transparentLogoBG: true,
- blacklist: ["cost"],
- features: AVIATION_FEATURES,
- docs: "MiPCA",
- protips: protips,
- homePage: "/devices",
- },
-};
-
-// Ordered most-specific to least-specific
-const DOMAIN_RULES: [RegExp, string][] = [
- [/bxt-dev/, "bxt"],
- [/staging\.brandxtech/, "staging"],
- [/streamline\./, "streamline"],
- [/brandxtech|brandxducks/, "bxt"],
- [/adaptiveagriculture|adaptiveag/, "adaptive-ag"],
- [/adaptiveconstruction/, "adaptive-construction"],
- [/aerogrowmanufacturing/, "aerogrow"],
- [/mivent/, "mivent"],
- [/omniair/, "omniair"],
- [/mionetech|mipca/, "mipca"],
- [/myintellifarms|intellifarms/, "intellifarms"],
-];
-
-function resolveSlug(): string {
- const override = import.meta.env.VITE_WHITELABEL;
- if (override && registry[override]) return override;
-
- const host = window.location.hostname;
- for (const [pattern, slug] of DOMAIN_RULES) {
- if (pattern.test(host)) return slug;
- }
- return "adaptive-ag";
+export function isBXT(): boolean {
+ return getName() === "Brand X Technologies";
}
-let cached: WhiteLabel | null = null;
+const ADAPTIVE_AGRICULTURE_WHITE_LABEL: WhiteLabel = {
+ name: "Adaptive Agriculture",
+ primaryColour: "green",
+ // primaryColour: "#008000",
+ secondaryColour: "yellow",
+ // secondaryColour: "#FFFF00",
+ signatureColour: "#272727",
+ signatureAccentColour: "#fff",
+ auth0ClientId: import.meta.env.VITE_AUTH0_ADAPTIVE_AGRICULTURE_CLIENT_ID,
+ redirectOnLogout: true,
+ logoutRedirectTarget: "https://adaptiveagriculture.ca",
+ darkLogo: AdapativeAgLogo,
+ lightLogo: AdapativeAgLogo,
+ transparentLogoBG: true,
+ blacklist: ["cost"],
+ hotjarID: import.meta.env.REACT_APP_HOTJAR_ID_ADAPTIVE_AGRICULTURE,
+ docs: "AdaptiveAg",
+ protips: protips.concat([]),
+ tutorialPlaylistID: "PLpLmJnI66Jfl5FXME31ckGam-sD8gB1s2",
+ thumbnail: AdaptiveAgThumbnail,
+ tutorialFiles: [
+ {
+ name: "Bindapt+",
+ url: "https://adaptiveagriculture.ca/wp-content/uploads/2023/08/Bindapt-Set-Up-Guide.pdf"
+ },
+ {
+ name: "Adapter Plate",
+ url:
+ "https://adaptiveagriculture.ca/wp-content/uploads/2023/08/Bindapt-Adapter-Plate-Set-Up-Guide.pdf"
+ }
+ ],
+ homePage: "/bins"
+};
+
+export function IsAdaptiveAgriculture(): boolean {
+ return (
+ getName() === "Adaptive Agriculture" ||
+ window.location.origin.includes("staging") ||
+ window.location.origin.includes("localhost")
+ );
+}
+
+export function IsStreamline(): boolean {
+ return (
+ getName() === "Streamline"
+ // window.location.origin.includes("staging") ||
+ // window.location.origin.includes("localhost")
+ );
+}
+
+const AEROGROW_WHITE_LABEL: WhiteLabel = {
+ name: "AeroGrow",
+ primaryColour: "green",
+ secondaryColour: "cyan",
+ signatureColour: "#fff",
+ signatureAccentColour: "#000",
+ auth0ClientId: import.meta.env.VITE_AUTH0_AEROGROW_CLIENT_ID,
+ redirectOnLogout: true,
+ logoutRedirectTarget: "https://www.aerogrowmanufacturing.com",
+ darkLogo: AeroGrowDarkLogo,
+ lightLogo: AeroGrowLightLogo,
+ transparentLogoBG: true,
+ blacklist: [],
+ hotjarID: import.meta.env.REACT_APP_HOTJAR_ID_AEROGROW,
+ docs: "Platform",
+ protips: protips
+};
+
+export function IsMiVent(): boolean {
+ return (
+ getName() === "MiVent" ||
+ window.location.origin.includes("staging") ||
+ window.location.origin.includes("localhost")
+ );
+}
+
+const MIVENT_WHITE_LABEL: WhiteLabel = {
+ name: "MiVent",
+ primaryColour: "green",
+ secondaryColour: "yellow",
+ signatureColour: "#272727",
+ signatureAccentColour: "#fff",
+ auth0ClientId: import.meta.env.VITE_AUTH0_MIVENT_CLIENT_ID,
+ redirectOnLogout: true,
+ logoutRedirectTarget: "https://mivent.ca",
+ darkLogo: MiVentLightLogo,
+ lightLogo: MiVentLightLogo,
+ transparentLogoBG: true,
+ blacklist: ["cost"],
+ hotjarID: import.meta.env.REACT_APP_HOTJAR_ID_MIVENT,
+ docs: "Platform",
+ protips: protips.concat([])
+};
+
+const ADAPTIVE_CONSTRUCTION_WHITE_LABEL: WhiteLabel = {
+ name: "Adaptive Construction",
+ primaryColour: "blue",
+ secondaryColour: "blue",
+ signatureColour: "#272727",
+ signatureAccentColour: "#fff",
+ auth0ClientId: import.meta.env.VITE_AUTH0_ADAPTIVE_CONSTRUCTION_CLIENT_ID,
+ redirectOnLogout: true,
+ logoutRedirectTarget: "https://adaptiveconstruction.ca",
+ darkLogo: AdConLogo,
+ lightLogo: AdConLogo,
+ transparentLogoBG: true,
+ blacklist: ["cost"],
+ hotjarID: import.meta.env.REACT_APP_HOTJAR_ID_ADAPTIVE_CONSTRUCTION,
+ docs: "AdaptiveConstruction",
+ protips: protips.concat([])
+};
+
+export function IsAdCon(): boolean {
+ return (
+ getName() === "Adaptive Construction" ||
+ window.location.origin.includes("staging") ||
+ window.location.origin.includes("localhost")
+ );
+}
+
+const OMNIAIR_WHITE_LABEL: WhiteLabel = {
+ name: "OmniAir",
+ primaryColour: "#004f9b",
+ secondaryColour: "yellow",
+ signatureColour: "#272727",
+ signatureAccentColour: "#fff",
+ auth0ClientId: import.meta.env.VITE_AUTH0_OMNIAIR_CLIENT_ID,
+ redirectOnLogout: true,
+ logoutRedirectTarget: "https://omniairsystems.com",
+ darkLogo: MiPCALogo,
+ lightLogo: MiPCALogo,
+ transparentLogoBG: true,
+ blacklist: ["cost"],
+ //hotjarID: import.meta.env.REACT_APP_HOTJAR_ID_OMNIAIR, testing what happens if this is excluded
+ docs: "OmniAir",
+ protips: protips.concat([])
+};
+
+export function IsOmniAir(): boolean {
+ return (
+ getName() === "OmniAir" ||
+ window.location.origin.includes("staging") ||
+ window.location.origin.includes("localhost")
+ );
+}
+
+const MIPCA_WHITE_LABEL: WhiteLabel = {
+ name: "MiPCA",
+ primaryColour: "#004f9b",
+ secondaryColour: "yellow",
+ signatureColour: "#272727",
+ signatureAccentColour: "#fff",
+ //omni air and MiPCA are the same client ID in Auth0, it is to replace it, once omniair gets removed we can re-name this
+ auth0ClientId: import.meta.env.VITE_AUTH0_OMNIAIR_CLIENT_ID,
+ redirectOnLogout: true,
+ logoutRedirectTarget: "https://mionetech.com",
+ darkLogo: MiPCALogo,
+ lightLogo: MiPCALogo,
+ transparentLogoBG: true,
+ blacklist: ["cost"],
+ docs: "MiPCA",
+ protips: protips.concat([])
+};
+
+export function IsMiPCA(): boolean {
+ return (
+ getName() === "MiPCA" ||
+ window.location.origin.includes("staging") ||
+ window.location.origin.includes("localhost")
+ );
+}
+
+const whitelabels = new Map([
+ ["streamline", STREAMLINE_WHITE_LABEL],
+ ["adaptiveag", ADAPTIVE_AGRICULTURE_WHITE_LABEL],
+ ["adaptiveagriculture", ADAPTIVE_AGRICULTURE_WHITE_LABEL],
+ ["brandxducks", BXT_WHITE_LABEL],
+ ["brandxtech", BXT_WHITE_LABEL],
+ ["aerogrowmanufacturing", AEROGROW_WHITE_LABEL],
+ ["localhost", BXT_WHITE_LABEL],
+ ["staging", STAGING_WHITELABEL],
+ ["10.0", ADAPTIVE_CONSTRUCTION_WHITE_LABEL],
+ ["mivent", MIVENT_WHITE_LABEL],
+ ["adaptiveconstruction", ADAPTIVE_CONSTRUCTION_WHITE_LABEL],
+ ["omniair", OMNIAIR_WHITE_LABEL],
+ ["mipca", MIPCA_WHITE_LABEL],
+ ["mionetech", MIPCA_WHITE_LABEL]
+]);
export function getWhitelabel(): WhiteLabel {
- if (!cached) {
- cached = registry[resolveSlug()];
- }
- return cached;
-}
+// if (isOffline()) {
+// return DEFAULT_WHITELABEL;
+// }
-export function getFeatures(): WhiteLabelFeatures {
- return getWhitelabel().features;
+ const hostname = window.location.hostname;
+ if (window.location.origin.includes("bxt-dev")) {
+ return BXT_WHITE_LABEL;
+ }
+ if (window.location.origin.includes("localhost")) {
+ return STREAMLINE_WHITE_LABEL;
+ }
+ if (window.location.origin.includes("staging") || import.meta.env.VITE_LOCAL_STAGING=='true') {
+ return STAGING_WHITELABEL;
+ }
+ const whiteLabelKeys = Array.from(whitelabels.keys());
+ for (var i = 0; i < whiteLabelKeys.length; i++) {
+ let key = whiteLabelKeys[i];
+ if (hostname.includes(key)) {
+ return whitelabels.get(key) as WhiteLabel;
+ }
+ }
+ return ADAPTIVE_AGRICULTURE_WHITE_LABEL;
}
export function getPrimaryColour(): any {
@@ -447,19 +364,11 @@ 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;
}
-export function getAuth0ClientId(): string {
+export function getAuth0ClientId(): any {
return getWhitelabel().auth0ClientId;
}
@@ -480,6 +389,10 @@ export function getLightLogo(): any {
}
export function hideLogo(): boolean {
+ // if (isOffline()) {
+ // return false;
+ // }
+
return getWhitelabel().name === "";
}
diff --git a/src/tasks/TaskCard.tsx b/src/tasks/TaskCard.tsx
index 2cae4d3..bac70e0 100644
--- a/src/tasks/TaskCard.tsx
+++ b/src/tasks/TaskCard.tsx
@@ -3,26 +3,33 @@ import {
Card,
CardActionArea,
Grid2 as Grid,
+ IconButton,
+ ListItemIcon,
+ ListItemText,
+ Menu,
+ MenuItem,
Typography
} from "@mui/material";
import { makeStyles } from "@mui/styles";
import { useCallback, useEffect, useState } from "react";
import { Task } from "models";
import { useGlobalState } from "providers";
-import { usePermissionAPI } from "hooks";
+import { useUserAPI } from "hooks";
+import { Done, MoreVert } from "@mui/icons-material";
+import Edit from "@mui/icons-material/Edit";
+import DeleteIcon from "@mui/icons-material/Delete";
import { red } from "@mui/material/colors";
import { pond } from "protobuf-ts/pond";
+import { taskScope, teamScope } from "models/Scope";
import EventBlocker from "common/EventBlocker";
-import TaskActions from "./taskActions";
interface Props {
task: Task;
reLoad: () => void;
+ editTaskMethod: (task: Task) => void;
markComplete: (task: Task) => void;
deleteTask: (task: Task) => void;
openTaskPage: (taskId: string) => void;
- keys: string[]
- types: string[]
}
const useStyles = makeStyles(() => ({
@@ -52,14 +59,21 @@ export default function TaskCard(props: Props) {
const classes = useStyles();
const [permissions, setPermissions] = useState([]);
const [{ user, as }] = useGlobalState();
- const permissionAPI = usePermissionAPI();
+ const userAPI = useUserAPI();
+ const [menuAnchorEl, setMenuAnchorEl] = useState(null);
const loadPermissions = useCallback(() => {
- permissionAPI.getPermissions(user.id(), props.keys, props.types).then(resp => {
- setPermissions(pond.EvaluatePermissionsResponse.fromObject(resp.data).permissions)
- })
-
- }, [props.task, permissionAPI, user, as]);
+ let scope = taskScope(props.task.key);
+ if(as){//if viewing as a team use the permissions to the team, and not the task itself
+ scope = teamScope(as)
+ }
+ userAPI
+ .getUser(user.id(), scope)
+ .then(resp => {
+ setPermissions(resp.permissions);
+ })
+ .catch(err => {});
+ }, [props.task, userAPI, user, as]);
useEffect(() => {
loadPermissions();
@@ -75,51 +89,51 @@ export default function TaskCard(props: Props) {
setDay(date.getUTCDate());
}, [props.task]);
- // const taskActions = () => {
- // return (
- //
- // );
- // };
+ const taskActions = () => {
+ return (
+
+ );
+ };
return (
@@ -150,31 +164,18 @@ export default function TaskCard(props: Props) {
- {/*
+
setMenuAnchorEl(event.currentTarget)}>
- */}
-
- {
- props.reLoad()
- }}
- keys={props.keys}
- types={props.types}
- />
- {/* {taskActions()} */}
+ {taskActions()}
);
}
diff --git a/src/tasks/TaskDrawer.tsx b/src/tasks/TaskDrawer.tsx
index 0eade2d..46ffd3a 100644
--- a/src/tasks/TaskDrawer.tsx
+++ b/src/tasks/TaskDrawer.tsx
@@ -22,21 +22,17 @@ import NotesIcon from "@mui/icons-material/Notes";
import DeleteIcon from "@mui/icons-material/Delete";
import Chat from "chat/Chat";
import { pond } from "protobuf-ts/pond";
-import { useMobile, usePermissionAPI, useUserAPI } from "hooks";
+import { useMobile, useUserAPI } from "hooks";
import { getThemeType } from "theme";
import { makeStyles } from "@mui/styles";
-import ObjectTeamsIcon from "@mui/icons-material/SupervisedUserCircle";
-import TaskActions from "./taskActions";
-import { useGlobalState } from "providers";
interface Props {
task: Task;
open: boolean;
- closeCallback: (refresh: boolean) => void;
+ closeCallback: () => void;
+ editTask: (task: Task) => void;
deleteTask: (task: Task) => void;
completeTask: (task: Task) => void;
- keys: string[]
- types: string[]
}
// interface TabPanelProps {
@@ -107,23 +103,17 @@ export default function TaskDrawer(props: Props) {
"Nov",
"Dec"
];
- const { task, open, closeCallback, completeTask, deleteTask } = props;
+ const { task, open, closeCallback, completeTask, deleteTask, editTask } = props;
const [month, setMonth] = useState(0);
const [day, setDay] = useState(0);
- const [permissions, setPermissions] = useState([])
- const [{user}] = useGlobalState();
- // const [menuAnchorEl, setMenuAnchorEl] = useState(null);
+ const [menuAnchorEl, setMenuAnchorEl] = useState(null);
const [openNote, setOpenNote] = useState(false);
const isMobile = useMobile();
const classes = useStyles();
const userAPI = useUserAPI();
- const permissionAPI = usePermissionAPI();
const [worker, setWorker] = useState();
useEffect(() => {
- permissionAPI.getPermissions(user.id(), props.keys, props.types).then(resp => {
- setPermissions(pond.EvaluatePermissionsResponse.fromObject(resp.data).permissions)
- })
if (task.start()) {
let date = new Date(task.start());
setMonth(date.getUTCMonth());
@@ -132,7 +122,40 @@ export default function TaskDrawer(props: Props) {
userAPI.getProfile(task.worker()).then(resp => {
setWorker(resp);
});
- }, [user, task, userAPI, permissionAPI]);
+ }, [task, userAPI]);
+
+ const taskActions = () => {
+ return (
+
+ );
+ };
const bodyButtons = () => {
return (
@@ -152,22 +175,11 @@ export default function TaskDrawer(props: Props) {
- {/* setMenuAnchorEl(event.currentTarget)}>
- */}
- completeTask(t)}
- permissions={permissions}
- task={task}
- removeTask={deleteTask}
- refreshCallback={() => {
- closeCallback(true)
- }}
- keys={props.keys}
- types={props.types}
- />
+
@@ -182,7 +194,7 @@ export default function TaskDrawer(props: Props) {
onClose={() => setOpenNote(false)}>
Notes
-
+
);
@@ -291,7 +303,7 @@ export default function TaskDrawer(props: Props) {
{taskTime()}
{assignees()}
- {/* {taskActions()} */}
+ {taskActions()}
{noteDrawer()}
);
@@ -304,9 +316,7 @@ export default function TaskDrawer(props: Props) {
displayNext={() => {}}
displayPrev={() => {}}
drawerBody={drawerBody()}
- onClose={() => {
- closeCallback(false)}
- }
+ onClose={closeCallback}
open={open}
/>
);
diff --git a/src/tasks/TaskList.tsx b/src/tasks/TaskList.tsx
index f94f838..6803831 100644
--- a/src/tasks/TaskList.tsx
+++ b/src/tasks/TaskList.tsx
@@ -12,6 +12,7 @@ import ButtonGroup from "common/ButtonGroup";
interface Props {
tasks: Task[];
+ editTaskMethod: (task: Task) => void;
markComplete: (task: Task) => void;
deleteTask: (task: Task) => void;
openTask: (taskId: string) => void;
@@ -19,21 +20,18 @@ interface Props {
listHeight?: string | number;
label?: string;
dateToView?: Date;
- keys: string[]
- types: string[]
}
export default function TaskList(props: Props) {
const {
+ editTaskMethod,
markComplete,
deleteTask,
openTask,
reLoad,
listHeight,
label,
- dateToView,
- keys,
- types
+ dateToView
} = props;
const [tasks, setTasks] = useState(props.tasks);
const [incomplete, setIncomplete] = useState([]);
@@ -72,16 +70,64 @@ export default function TaskList(props: Props) {
setComplete(complete);
}, [tasks, dateToView]);
+ // const StyledToggleButtonGroup = withStyles(theme => ({
+ // grouped: {
+ // margin: theme.spacing(-0.5),
+ // border: "none",
+ // padding: theme.spacing(1),
+ // "&:not(:first-child):not(:last-child)": {
+ // borderRadius: 24,
+ // marginRight: theme.spacing(0.5),
+ // marginLeft: theme.spacing(0.5)
+ // },
+ // "&:first-child": {
+ // borderRadius: 24,
+ // marginLeft: theme.spacing(0.25)
+ // },
+ // "&:last-child": {
+ // borderRadius: 24,
+ // marginRight: theme.spacing(0.25)
+ // }
+ // },
+ // root: {
+ // backgroundColor: darken(
+ // theme.palette.background.paper,
+ // getThemeType() === "light" ? 0.05 : 0.25
+ // ),
+ // borderRadius: 24,
+ // content: "border-box"
+ // }
+ // }))(ToggleButtonGroup);
+
+ // const StyledToggle = withStyles({
+ // root: {
+ // backgroundColor: "transparent",
+ // overflow: "visible",
+ // content: "content-box",
+ // "&$selected": {
+ // backgroundColor: "gold",
+ // color: "black",
+ // borderRadius: 24,
+ // fontWeight: "bold"
+ // },
+ // "&$selected:hover": {
+ // backgroundColor: "rgb(255, 255, 0)",
+ // color: "black",
+ // borderRadius: 24
+ // }
+ // },
+ // selected: {}
+ // })(ToggleButton);
+
const incompleteTasks = incomplete.map((task, index) => (
markComplete(task)}
deleteTask={(task: Task) => deleteTask(task)}
reLoad={reLoad}
openTaskPage={(taskId: string) => openTask(taskId)}
- keys={keys}
- types={types}
/>
));
@@ -90,12 +136,11 @@ export default function TaskList(props: Props) {
markComplete(task)}
deleteTask={(task: Task) => deleteTask(task)}
reLoad={reLoad}
openTaskPage={(taskId: string) => openTask(taskId)}
- keys={keys}
- types={types}
/>
));
@@ -117,6 +162,20 @@ export default function TaskList(props: Props) {
}
]}
/>
+ {/*
+ setViewing("upcoming")}>
+ Upcoming
+
+ setViewing("complete")}
+ value={"complete"}
+ aria-label="complete">
+ Complete
+
+ */}
{location !== "/tasks" && (
diff --git a/src/tasks/TaskSettings.tsx b/src/tasks/TaskSettings.tsx
index c2b92ee..9f92548 100644
--- a/src/tasks/TaskSettings.tsx
+++ b/src/tasks/TaskSettings.tsx
@@ -2,12 +2,10 @@ import {
Avatar,
Box,
Button,
- Checkbox,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
- FormControlLabel,
Grid2 as Grid,
InputAdornment,
MenuItem,
@@ -17,24 +15,23 @@ import moment from "moment";
import React, { useState } from "react";
import { pond } from "protobuf-ts/pond";
import { useGlobalState, useTaskAPI } from "providers";
-import { usePermissionAPI, useSnackbar, useUserAPI } from "hooks";
+import { useSnackbar, useUserAPI } from "hooks";
import { useEffect } from "react";
-import { Task, teamScope, User, userScope } from "models";
+import { Task, teamScope, User } from "models";
import ColourPicker from "common/ColourPicker";
import ResponsiveDialog from "common/ResponsiveDialog";
interface Props {
open: boolean;
onClose: (reLoad?: boolean) => void;
+ markComplete?: boolean;
startDate?: any;
task?: Task;
- objectKey?: string; //deprecated, with the new permission structure keys and types will be what determine what a task is connected to
+ objectKey?: string;
type?: string;
hasCost?: boolean;
costTitle?: string;
secondaryCostTitle?: string;
- keys?: string[]
- types?: string[]
}
export default function TaskSettings(props: Props) {
@@ -48,15 +45,13 @@ export default function TaskSettings(props: Props) {
const [worker, setWorker] = useState(user.id());
const [colour, setColour] = useState("");
const taskAPI = useTaskAPI();
- const userAPI = useUserAPI();
- const permissionAPI = usePermissionAPI();
const { openSnack } = useSnackbar();
const [cost, setCost] = useState("");
const [secondaryCost, setSecondaryCost] = useState("");
const [seedCost, setSeedCost] = useState("");
const [poundPerAcre, setPoundPerAcre] = useState(0);
+ const userAPI = useUserAPI();
const [users, setUsers] = useState([]);
- const [shareToCreator, setShareToCreator] = useState(true)
useEffect(() => {
if (props.task) {
@@ -109,7 +104,7 @@ export default function TaskSettings(props: Props) {
startTime: startTime,
end: endDate,
endTime: endTime,
- objectKey: props.objectKey ? props.objectKey : user.id(), //deprecated
+ objectKey: props.objectKey ? props.objectKey : user.id(),
description: taskDescription,
worker: worker,
complete: false,
@@ -125,24 +120,8 @@ export default function TaskSettings(props: Props) {
returnTask.settings = taskSettings;
taskAPI
- .addTask(taskSettings, as, props.keys, props.types)
+ .addTask(taskSettings, as)
.then(resp => {
- console.log(resp)
- if(shareToCreator && resp.data.task){
- let parentScope = userScope(user.id())
- if(as){
- parentScope = teamScope(as)
- }
-
- permissionAPI.shareObjectByKey(
- {key: resp.data.task, kind: "task"},
- parentScope.key,
- parentScope.kind,
- [pond.Permission.PERMISSION_READ, pond.Permission.PERMISSION_WRITE, pond.Permission.PERMISSION_USERS, pond.Permission.PERMISSION_SHARE],
- props.keys,
- props.types
- )
- }
props.onClose(true);
})
.catch(err => {
@@ -339,18 +318,6 @@ export default function TaskSettings(props: Props) {
value={endTime}
onChange={e => setEndTime(e.target.value)}
/>
- {props.keys && props.types && !props.task &&
- setShareToCreator(e.target.checked)}
- color="primary"
- />
- }
- label={"Share task with " + (as ? "team" : "user")}
- />
- }
Colour
setColour(color)} />
diff --git a/src/tasks/TaskViewer.tsx b/src/tasks/TaskViewer.tsx
index b283c5f..12e82d7 100644
--- a/src/tasks/TaskViewer.tsx
+++ b/src/tasks/TaskViewer.tsx
@@ -26,12 +26,10 @@ import moment from "moment";
import { makeStyles } from "@mui/styles";
interface ViewProps {
- // objectKey?: string; //only used to save it for the object --Deprecated with new permission structure--
- keys?: string[]//the keys and types are used to give th object permissions to the task, that is how they will be linked now
- types?: string[]
+ objectKey?: string; //only used to save it for the object
label?: string;
drawerView?: boolean;
- // loadKeys?: string[]; //if you want to load tasks for a specific object(s) --Deprecated with new permission structure-- it should use the keys and types now
+ loadKeys?: string[]; //if you want to load tasks for a specific object(s)
overlayButton?: boolean;
}
@@ -89,16 +87,9 @@ const useStyles = makeStyles((theme: Theme) => {
});
export default function TaskViewer(props: ViewProps) {
- const {
- //objectKey,
- label,
- drawerView,
- //loadKeys,
- keys,
- types,
- overlayButton
- } = props;
+ const { objectKey, label, drawerView, loadKeys, overlayButton } = props;
const [{ user, as }] = useGlobalState();
+ //const [{ as }] = useGlobalState();
const taskAPI = useTaskAPI();
const { openSnack } = useSnackbar();
const [tasks, setTasks] = useState>(new Map([]));
@@ -146,12 +137,42 @@ export default function TaskViewer(props: ViewProps) {
}
};
+ const setTaskToEdit = (task: Task) => {
+ setEditTask(task);
+ openDialog();
+ };
+
+
+ const loadMultitask = useCallback(() => {
+ if (!loadKeys) return;
+ if (loadKeys.length > 0) {
+ let temp = new Map();
+ setLoaded(false);
+ taskAPI
+ .getMultiTasks(loadKeys, as)
+ .then(resp => {
+ if(resp.data.tasks){
+ resp.data.tasks.forEach(task => {
+ if (task.settings) {
+ temp.set(task.key, Task.any(task));
+ }
+ });
+ }
+ setTasks(temp);
+ setLoaded(true);
+ })
+ .catch(err => {
+ openSnack("Failed to load");
+ });
+ }
+ }, [loadKeys, openSnack, taskAPI, as]);
+
//loads tasks from the backend database
const loadTasks = useCallback(() => {
if (!user.id()) return;
let temp = new Map();
taskAPI
- .listTasks(50, 0, "asc", "start", undefined, undefined, as, prevMonth, nextMonth, keys, types)
+ .listTasks(50, 0, "asc", "start", undefined, undefined, as, prevMonth, nextMonth)
.then(resp => {
if(resp.data.tasks){
resp.data.tasks.forEach(task => {
@@ -164,20 +185,20 @@ export default function TaskViewer(props: ViewProps) {
setLoaded(true);
})
.catch(err => {
+ console.log(err)
openSnack("Failed to load tasks");
});
- }, [taskAPI, as, user, openSnack, nextMonth, prevMonth, keys, types]);
+ }, [taskAPI, as, user, openSnack, nextMonth, prevMonth]);
useEffect(() => {
- // if (drawerView) {
- // setTasks(new Map());
- // setValue(1);
- // loadMultitask();
- // } else {
- // loadTasks();
- // }
- loadTasks()
- }, [loadTasks, as, drawerView]);
+ if (drawerView) {
+ setTasks(new Map());
+ setValue(1);
+ loadMultitask();
+ } else {
+ loadTasks();
+ }
+ }, [loadTasks, loadMultitask, as, drawerView]);
const markComplete = (task: Task) => {
task.settings.complete = !task.settings.complete;
@@ -293,13 +314,12 @@ export default function TaskViewer(props: ViewProps) {
loadTasks()}
dateToView={drawerView ? undefined : currentDate}
openTask={(taskId: string) => openSelectedTask(taskId)}
- keys={[]}
- types={[]}
/>
loadTasks()}
listHeight={"40vh"}
dateToView={currentDate}
openTask={(taskId: string) => openSelectedTask(taskId)}
- keys={[]}
- types={[]}
/>
@@ -404,11 +423,16 @@ export default function TaskViewer(props: ViewProps) {
{
- loadTasks();
+ if (r) {
+ if (drawerView) {
+ loadMultitask();
+ } else {
+ loadTasks();
+ }
+ }
setEditTask(undefined);
setNewTaskDialog(false);
}}
@@ -417,14 +441,10 @@ export default function TaskViewer(props: ViewProps) {
{
- setOpenDrawer(false)
- loadTasks()
- }}
+ closeCallback={() => setOpenDrawer(false)}
completeTask={markComplete}
deleteTask={deleteTask}
- keys={keys ?? []}
- types={types ?? []}
+ editTask={setTaskToEdit}
/>
}
diff --git a/src/tasks/taskActions.tsx b/src/tasks/taskActions.tsx
deleted file mode 100644
index 116cdec..0000000
--- a/src/tasks/taskActions.tsx
+++ /dev/null
@@ -1,237 +0,0 @@
-import {
- IconButton,
- ListItemIcon,
- ListItemText,
- Menu,
- MenuItem,
- Theme
-} from "@mui/material";
-import { blue } from "@mui/material/colors";
-import MoreIcon from "@mui/icons-material/MoreVert";
-import ObjectTeamsIcon from "@mui/icons-material/SupervisedUserCircle";
-import { pond } from "protobuf-ts/pond";
-import React, { useState } from "react";
-import { isOffline } from "utils/environment";
-import ObjectTeams from "teams/ObjectTeams";
-import { Task, taskScope } from "models";
-import { makeStyles } from "@mui/styles";
-import { Done, Edit } from "@mui/icons-material";
-import DeleteIcon from "@mui/icons-material/Delete";
-import TaskSettings from "./TaskSettings";
-
-const useStyles = makeStyles((theme: Theme) => ({
- shareIcon: {
- color: blue["500"],
- "&:hover": {
- color: blue["600"]
- }
- },
- removeIcon: {
- color: "var(--status-alert)"
- },
- red: {
- color: "var(--status-alert)"
- }
-}));
-
-interface Props {
- task: Task;
- permissions: pond.Permission[];
- keys: string[]
- types: string[]
- refreshCallback: () => void;
- markComplete: (task: Task) => void;
- removeTask: (task: Task) => void;
-}
-
-interface OpenState {
- // share: boolean;
- // users: boolean;
- teams: boolean;
- settings: boolean;
- // removeSelf: boolean;
- delete: boolean
-}
-
-export default function TaskActions(props: Props) {
- const classes = useStyles();
- const { task, permissions, refreshCallback, keys, types } = props;
- const [anchorEl, setAnchorEl] = React.useState(null);
- const [openState, setOpenState] = useState({
- // share: false,
- // users: false,
- teams: false,
- settings: false,
- // removeSelf: false
- delete: false
- });
-
- const groupMenu = () => {
- // const canShare = permissions.includes(pond.Permission.PERMISSION_SHARE);
- const canEdit = permissions.includes(pond.Permission.PERMISSION_WRITE);
- const canManageUsers = permissions.includes(pond.Permission.PERMISSION_USERS);
- return (
-
- );
- };
-
- const dialogs = () => {
- const key = task.key;
- // const label = task.title();
- return (
-
- {/* setOpenState({ ...openState, share: false })}
- /> */}
- {/* setOpenState({ ...openState, users: false })}
- refreshCallback={refreshCallback}
- /> */}
- {/* setOpenState({ ...openState, removeSelf: false })}
- /> */}
- setOpenState({ ...openState, teams: false })}
- keys={keys}
- types={types}
- />
- {
- setOpenState({ ...openState, settings: false})
- if(r){
- refreshCallback()
- }
- }}
- />
-
- );
- };
-
- return (
-
- ) => setAnchorEl(event.currentTarget)}>
-
-
- {groupMenu()}
- {dialogs()}
-
- );
-}
\ No newline at end of file
diff --git a/src/teams/ObjectTeams.tsx b/src/teams/ObjectTeams.tsx
index 4221939..d327291 100644
--- a/src/teams/ObjectTeams.tsx
+++ b/src/teams/ObjectTeams.tsx
@@ -41,8 +41,6 @@ import { useNavigate } from "react-router-dom";
import { makeStyles } from "@mui/styles";
import { Share, RemoveCircle as RemoveUserIcon } from "@mui/icons-material";
import CancelSubmit from "common/CancelSubmit";
-import { permissionToString } from "pbHelpers/Permission";
-import { PermissionChanges } from "providers/pond/permissionAPI";
const useStyles = makeStyles((theme: Theme) => {
const avatarBG = theme.palette.secondary["700" as keyof PaletteColor];
@@ -102,9 +100,6 @@ interface Props {
userCallback?: (users?: User[] | Team[] | undefined) => void;
dialog?: string;
cardMode?: boolean;
- keys?: string[];
- types?: string[];
- shareLabel?: string; // a custom label to pass in that will replace the share icon
}
export default function ObjectTeams(props: Props) {
@@ -113,7 +108,7 @@ export default function ObjectTeams(props: Props) {
const theme = useTheme();
const permissionAPI = usePermissionAPI();
const [{ user, as }] = useGlobalState();
- const isAdmin = user.hasAdmin();
+ const canProvision = user.allowedTo("provision");
const teamAPI = useTeamAPI();
const { error, success, warning } = useSnackbar();
const {
@@ -125,10 +120,7 @@ export default function ObjectTeams(props: Props) {
refreshCallback,
userCallback,
dialog,
- cardMode,
- keys,
- types,
- shareLabel
+ cardMode
} = props;
const prevPermissions = usePrevious(permissions);
const prevIsDialogOpen = usePrevious(isDialogOpen);
@@ -154,7 +146,7 @@ export default function ObjectTeams(props: Props) {
const load = useCallback(() => {
setIsLoading(true);
teamAPI
- .listObjectTeams(scope, as, keys, types)
+ .listObjectTeams(scope, as)
.then((response: any) => {
let rTeams: Team[] = [];
or(response.data, { teams: [] }).teams.forEach((user: any) => {
@@ -192,21 +184,7 @@ export default function ObjectTeams(props: Props) {
};
const submit = () => {
- if(keys && types){
- let changes: PermissionChanges[] = []
- users.forEach((user: Team) => {
- changes.push({
- key: user.id(),
- permissions: user.permissions.map(permission => permissionToString(permission))
- });
- });
- permissionAPI.updateObjectPermissions(scope.key, scope.kind, "team", changes, keys, types).then(resp => {
- console.log("no error")
- }).catch(err => {
- console.log("error")
- })
- }else{
- permissionAPI
+ permissionAPI
.updatePermissions(scope, users)
.then((_response: any) => {
success("Users were sucessfully updated for " + label);
@@ -219,13 +197,10 @@ export default function ObjectTeams(props: Props) {
})
.catch((err: any) => {
err.response.data.error
- ? warning(err.response.data.error)
- : error("Error occured when updating users for " + label);
+ ? warning(err.response.data.error)
+ : error("Error occured when updating users for " + label);
close();
});
-
- }
-
};
const changeUserPermissions = (user: pond.ITeam) => (event: any) => {
@@ -324,18 +299,12 @@ export default function ObjectTeams(props: Props) {
{canShare && (
- {shareLabel ?
-
- {shareLabel}
-
- :
-
-
-
- }
+
+
+
)}
@@ -513,7 +482,7 @@ export default function ObjectTeams(props: Props) {
) : (
canManageUsers &&
!isRemoved &&
- (!permissions.includes(pond.Permission.PERMISSION_USERS) || isAdmin) && (
+ (!permissions.includes(pond.Permission.PERMISSION_USERS) || canProvision) && (
);
diff --git a/src/teams/ShareWithTeam.tsx b/src/teams/ShareWithTeam.tsx
index ffda3f0..070a8c7 100644
--- a/src/teams/ShareWithTeam.tsx
+++ b/src/teams/ShareWithTeam.tsx
@@ -88,8 +88,6 @@ interface Props {
permissions: pond.Permission[];
isDialogOpen: boolean;
closeDialogCallback: Function;
- keys?: string[],
- types?: string[]
}
export default function ShareObject(props: Props) {
@@ -98,7 +96,7 @@ export default function ShareObject(props: Props) {
const classes = useStyles();
const permissionAPI = usePermissionAPI();
const { info, success } = useSnackbar();
- const { scope, label, permissions, isDialogOpen, closeDialogCallback, keys, types } = props;
+ const { scope, label, permissions, isDialogOpen, closeDialogCallback } = props;
const [sharedPermissions, setSharedPermissions] = useState([
pond.Permission.PERMISSION_READ
]);
@@ -111,8 +109,7 @@ export default function ShareObject(props: Props) {
const [teamKey, setTeamKey] = useState("");
const share = () => {
-
- permissionAPI.shareObjectByKey(scope, teamKey, "team", sharedPermissions, keys, types).then(resp => {
+ permissionAPI.shareObjectByKey(scope, teamKey, sharedPermissions).then(resp => {
let shareBins = true;
if (resp && resp.data && resp.data.existing) {
success(label + " was shared with team");
@@ -126,7 +123,7 @@ export default function ShareObject(props: Props) {
resp.data.bins.forEach(bin => {
if (bin.settings) {
let newScope = binScope(bin.settings?.key);
- permissionAPI.shareObjectByKey(newScope, teamKey, "team", sharedPermissions, keys, types).catch(_err => {
+ permissionAPI.shareObjectByKey(newScope, teamKey, sharedPermissions).catch(_err => {
successBins = false;
});
}
@@ -146,7 +143,7 @@ export default function ShareObject(props: Props) {
resp.data.gates.forEach(gate => {
if (gate) {
let newScope = { key: gate.key, kind: "gate" } as Scope;
- permissionAPI.shareObjectByKey(newScope, teamKey, "team", sharedPermissions, keys, types).catch(_err => {
+ permissionAPI.shareObjectByKey(newScope, teamKey, sharedPermissions).catch(_err => {
successGates = false;
});
}
diff --git a/src/teams/TeamList.tsx b/src/teams/TeamList.tsx
index f45262c..7742033 100644
--- a/src/teams/TeamList.tsx
+++ b/src/teams/TeamList.tsx
@@ -12,8 +12,7 @@ import {
Grid2,
} from "@mui/material";
import { pond } from "protobuf-ts/pond";
-import { useGlobalState, useSnackbar, useUserAPI } from "providers";
-import { useTeamAPI } from "providers/pond/teamAPI";
+import { useGlobalState, useSnackbar, useTeamAPI, useUserAPI } from "providers";
import { Team, teamScope } from "models";
import { useNavigate } from "react-router";
import { cloneDeep } from "lodash";
diff --git a/src/teams/TeamSettings.tsx b/src/teams/TeamSettings.tsx
index 58f9b25..8e99e7e 100644
--- a/src/teams/TeamSettings.tsx
+++ b/src/teams/TeamSettings.tsx
@@ -14,8 +14,7 @@ import {
import ResponsiveDialog from "common/ResponsiveDialog";
import { Team } from "models";
import { pond } from "protobuf-ts/pond";
-import { useSnackbar } from "providers";
-import { useTeamAPI } from "providers/pond/teamAPI";
+import { useSnackbar, useTeamAPI } from "providers";
import React, { useEffect, useState } from "react";
import DeleteButton from "common/DeleteButton";
import { userRoleFromPermissions } from "pbHelpers/User";
@@ -37,7 +36,6 @@ export default function TeamSettings(props: Props) {
const [loading, setLoading] = useState(false);
const [nameField, setNameField] = useState("");
const [infoField, setInfoField] = useState("");
- const [whitelabelField, setWhitelabelField] = useState("");
const [url, setUrl] = useState("");
const snackbar = useSnackbar();
const [tab, setTab] = useState(0);
@@ -56,7 +54,6 @@ export default function TeamSettings(props: Props) {
if (team) {
setNameField(team.name());
setInfoField(team.settings.info);
- setWhitelabelField(team.settings.whitelabel);
setUrl(team.settings.avatar);
}
}, [team]);
@@ -79,7 +76,6 @@ export default function TeamSettings(props: Props) {
let team = pond.TeamSettings.create();
team.name = nameField;
team.info = infoField;
- team.whitelabel = whitelabelField;
setLoading(true);
teamAPI
.addTeam(team)
@@ -104,7 +100,6 @@ export default function TeamSettings(props: Props) {
let newTeam = pond.TeamSettings.create();
newTeam.name = nameField;
newTeam.info = infoField;
- newTeam.whitelabel = whitelabelField;
newTeam.avatar = url;
if (teamKey) {
teamAPI
@@ -135,7 +130,6 @@ export default function TeamSettings(props: Props) {
const close = () => {
setNameField("");
setInfoField("");
- setWhitelabelField("");
setUrl("");
closeTeamDialogCallback();
};
@@ -199,15 +193,6 @@ export default function TeamSettings(props: Props) {
style={{ margin: theme.spacing(1) }}
fullWidth
/>
- setWhitelabelField(event?.target.value)}
- variant="outlined"
- style={{ margin: theme.spacing(1) }}
- placeholder="e.g. mivent, adaptive-ag, intellifarms"
- fullWidth
- />
);
};
diff --git a/src/theme/theme.ts b/src/theme/theme.ts
index ce44c5d..3b2883a 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
@@ -59,29 +48,17 @@ function makePaletteColor(name: keyof typeof Colours) {
export const getTheme = (mode: 'light' | 'dark') =>
createTheme({
- components: {
- MuiPaper: {
- styleOverrides: {
- root: {
- backgroundImage: 'none', // disables MUI's elevation overlay in dark mode
- },
- },
- },
- },
...baseTheme,
palette: {
...baseTheme.palette,
primary: makePaletteColor(getPrimaryColour()),
secondary: makePaletteColor(getSecondaryColour()),
- header: {
- main: getHeaderColor(),
- },
mode,
...(mode === 'dark'
? {
background: {
- default: '#070F17', // was #0F1923 — push it darker
- paper: '#0D1820', // was #1A2530 — slightly darker, less grey/blue
+ default: '#121212',
+ paper: '#1e1e1e',
},
}
: {
diff --git a/src/transactions/transactionDataDisplay.tsx b/src/transactions/transactionDataDisplay.tsx
index 3bd6220..b64db64 100644
--- a/src/transactions/transactionDataDisplay.tsx
+++ b/src/transactions/transactionDataDisplay.tsx
@@ -2,8 +2,8 @@ import { Box, Typography } from "@mui/material";
import GrainDescriber from "grain/GrainDescriber";
import { Transaction } from "models/Transaction";
import { pond } from "protobuf-ts/pond";
-import { useGlobalState } from "providers";
import React from "react";
+import { getGrainUnit } from "utils";
interface Props {
transaction: Transaction;
@@ -11,14 +11,13 @@ interface Props {
export default function TransactionDataDisplay(props: Props) {
const { transaction } = props;
- const [{ user }] = useGlobalState();
console.log(transaction)
const grainDisplay = (gt: pond.GrainTransaction) => {
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && gt.bushelsPerTonne > 1){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && gt.bushelsPerTonne > 1){
return "Weight: " + Math.round(gt.bushels / gt.bushelsPerTonne*100)/100 + " mT"
}
- if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && gt.bushelsPerTonne > 1){
+ if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && gt.bushelsPerTonne > 1){
return "Weight: " + Math.round(gt.bushels / (gt.bushelsPerTonne*0.907)*100)/100 + " t"
}
return "Bushels: " + gt.bushels
diff --git a/src/user/ObjectUsers.tsx b/src/user/ObjectUsers.tsx
index 4facea6..e3221c2 100644
--- a/src/user/ObjectUsers.tsx
+++ b/src/user/ObjectUsers.tsx
@@ -108,7 +108,6 @@ interface Props {
dialog?: string;
cardMode?: boolean;
useImitation?: boolean;
- shareLabel?: string; // s string to replace the share icon button with a labelled button
}
export default function ObjectUsers(props: Props) {
@@ -116,7 +115,7 @@ export default function ObjectUsers(props: Props) {
const theme = useTheme();
const permissionAPI = usePermissionAPI();
const [{ user, team }, dispatch] = useGlobalState();
- const isAdmin = user.hasAdmin();
+ const canProvision = user.allowedTo("provision");
const userAPI = useUserAPI();
const { error, success, warning } = useSnackbar();
const {
@@ -128,9 +127,8 @@ export default function ObjectUsers(props: Props) {
refreshCallback,
userCallback,
dialog,
- cardMode,
+ cardMode
//useImitation
- shareLabel
} = props;
const prevPermissions = usePrevious(permissions);
const prevIsDialogOpen = usePrevious(isDialogOpen);
@@ -369,17 +367,11 @@ export default function ObjectUsers(props: Props) {
{canShare && (
- {shareLabel ?
-
- {shareLabel}
-
- :
-
-
-
- }
+
+
)}
@@ -560,7 +552,7 @@ export default function ObjectUsers(props: Props) {
- ) : (canManageUsers || isAdmin) && !isRemoved && !cardMode ? (
+ ) : (canManageUsers || canProvision) && !isRemoved && !cardMode ? (
) : (
- (canManageUsers || isAdmin) && (
+ (canManageUsers || canProvision) && (
(false);
const [user, setUser] = useState(globalState.user);
@@ -113,7 +113,7 @@ export default function UserSettings(props: Props) {
setDistanceUnit(user.settings.distanceUnit);
setGrainUnit(user.settings.grainUnit);
})
- .catch(() => {
+ .catch((error: any) => {
error("Error occurred while updating your profile");
})
.finally(() => {
@@ -529,7 +529,7 @@ export default function UserSettings(props: Props) {
Feet (ft)
- {getFeatures().grainUnit && (
+ {IsAdaptiveAgriculture() && (
{
let list: ProductDetails[] = [];
- if (getFeatures().marketplace || user.hasFeature("admin")) {
+ if (IsAdaptiveAgriculture() || user.hasFeature("admin")) {
list = list.concat(agFeatureList);
}
+ // if(IsAdCon()){
+ // list = list.concat(constructionFeatureList)
+ // }
+ // if(IsOmniAir()){
+ // list = list.concat(aviationFeatureList)
+ // }
+ // if(IsMiVent()){
+ // list = list.concat(miningFeatureList)
+ // }
+ // list = list.concat(universalFeatureList)
setFeaturList(list);
}, []);
diff --git a/src/utils/auth0Config.ts b/src/utils/auth0Config.ts
deleted file mode 100644
index 1c5e568..0000000
--- a/src/utils/auth0Config.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { getWhitelabel } from 'services/whiteLabel'
-
-/** True when Auth0 env + whitelabel client ID are present; avoids mounting Auth0Provider on offline / local LAN builds. */
-export function isAuth0Configured(): boolean {
- const wl = getWhitelabel()
- const domain = String(import.meta.env.VITE_AUTH0_CLIENT_DOMAIN ?? '').trim()
- const clientRaw = wl.auth0ClientId ?? import.meta.env.VITE_AUTH0_CLIENT_ID
- const clientId = String(clientRaw ?? '').trim()
- return domain.length > 0 && clientId.length > 0
-}
-
-/**
- * auth0-spa-js only runs in a "secure context" (HTTPS, http://localhost, http://127.0.0.1, etc.).
- * Plain http://192.168.x.x fails — same check as `window.isSecureContext`.
- */
-export function isAuth0SpaOriginAllowed(): boolean {
- if (typeof window === 'undefined') return true
- return window.isSecureContext
-}
-
-/** Mount Auth0 only when credentials exist and the browser will let the SDK run. */
-export function shouldMountAuth0Provider(): boolean {
- return isAuth0Configured() && isAuth0SpaOriginAllowed()
-}
diff --git a/src/utils/getWsBaseUrl.ts b/src/utils/getWsBaseUrl.ts
deleted file mode 100644
index 331b78a..0000000
--- a/src/utils/getWsBaseUrl.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * Origin for Pond WebSocket URLs.
- *
- * VITE_APP_API_URL usually ends with /v1 (same base axios uses for REST). WS paths are
- * written as /v1/live/..., so we strip a trailing /v1 from the env URL to avoid /v1/v1/.
- */
-export function getWsBaseUrl(): string {
- const apiUrl = import.meta.env.VITE_APP_API_URL;
- if (apiUrl) {
- let ws = apiUrl.replace(/^http/, "ws");
- ws = ws.replace(/\/v1\/?$/, "");
- return ws.replace(/\/$/, "") || ws;
- }
- const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
- return `${protocol}//${window.location.host}`;
-}
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;
-}
diff --git a/src/utils/units.ts b/src/utils/units.ts
index 075a9e1..41b3601 100644
--- a/src/utils/units.ts
+++ b/src/utils/units.ts
@@ -1,6 +1,5 @@
import { pond } from "protobuf-ts/pond";
-//function is deprecated, use User.tempUnit() instead
export function getTemperatureUnit(): pond.TemperatureUnit {
return localStorage.getItem("temperature") === "f"
? pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
@@ -32,7 +31,6 @@ export const fahrenheitToCelsius = (fahrenheit: number) => {
return (fahrenheit - 32) * (5 / 9);
};
-// function is deprecated, use User.pressureUnit() instead
export function getPressureUnit(): pond.PressureUnit {
return localStorage.getItem("pressure") === "kpa"
? pond.PressureUnit.PRESSURE_UNIT_KILOPASCALS
@@ -46,7 +44,6 @@ export function setPressureUnit(unit: pond.PressureUnit) {
);
}
-// function is deprecated, use User.distanceUnit() instead
export function getDistanceUnit(): pond.DistanceUnit {
return localStorage.getItem("distance") === "m"
? pond.DistanceUnit.DISTANCE_UNIT_METERS
@@ -57,7 +54,6 @@ export function setDistanceUnit(unit: pond.DistanceUnit) {
localStorage.setItem("distance", unit === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft");
}
-// function is deprecated, use User.grainUnit() instead
export function getGrainUnit(): pond.GrainUnit {
switch(localStorage.getItem("grainUnit")){
case "mT":
@@ -85,11 +81,13 @@ export function setGrainUnit(unit: pond.GrainUnit) {
default:
localStorage.setItem("grainUnit", "bu")
}
+ // localStorage.setItem("grainUnit", unit === pond.GrainUnit.GRAIN_UNIT_WEIGHT ? "mT" : "bu");
}
-export const distanceConversion = (val: number, distanceUnit: pond.DistanceUnit) => {
+export const distanceConversion = (val: number) => {
let converted = val;
- if (distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
+
+ if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
converted = val / 3.281;
}
return converted;
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
index db728ea..11f02fe 100644
--- a/src/vite-env.d.ts
+++ b/src/vite-env.d.ts
@@ -1,4 +1 @@
///
-
-declare const __BUILD_DATE__: string
-declare const __GIT_HASH__: string
diff --git a/vite.config.ts b/vite.config.ts
index 7ff8324..f48db4a 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,71 +1,12 @@
-import { defineConfig, type Plugin, type UserConfig } from 'vite'
+import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
import { VitePWA } from 'vite-plugin-pwa';
import * as path from 'path' // ✅ Import path module
-import { readFileSync, renameSync, existsSync, unlinkSync } from 'node:fs'
-import { fileURLToPath } from 'node:url'
-import { execSync } from 'node:child_process'
-
-const rootDir = path.dirname(fileURLToPath(import.meta.url))
-
-const LOCALNET_MODE = 'localnet'
-
-/** Dev server: serve the minimal LAN shell (indexLocal.html) instead of the full index with third-party bootstraps. */
-function useLocalnetShellHtml (mode: string, command: string): Plugin {
- if (mode !== LOCALNET_MODE || command !== 'serve') {
- return { name: 'localnet-shell-html-noop' }
- }
- return {
- name: 'localnet-shell-html',
- apply: 'serve',
- transformIndexHtml: {
- order: 'pre',
- handler () {
- return readFileSync(path.join(rootDir, 'indexLocal.html'), 'utf-8')
- },
- },
- }
-}
-
-/** After build, LAN shell is emitted as indexLocal.html; rename to index.html so nginx and PWA still use /. */
-function emitLocalnetShellAsIndexHtml (mode: string): Plugin {
- if (mode !== LOCALNET_MODE) {
- return { name: 'emit-localnet-shell-as-index-noop' }
- }
- return {
- name: 'emit-localnet-shell-as-index-html',
- closeBundle () {
- const outDir = path.join(rootDir, 'build')
- const from = path.join(outDir, 'indexLocal.html')
- const to = path.join(outDir, 'index.html')
- if (existsSync(from)) {
- if (existsSync(to)) unlinkSync(to)
- renameSync(from, to)
- }
- },
- }
-}
// https://vitejs.dev/config/
-export default defineConfig(({ command, mode }): UserConfig => {
- const useLocalnetShell = mode === LOCALNET_MODE
-
- let gitHash = 'unknown'
- try {
- gitHash = execSync('git rev-parse --short HEAD').toString().trim()
- } catch {}
-
- const buildDate = new Date().toISOString()
-
- return {
- define: {
- __BUILD_DATE__: JSON.stringify(buildDate),
- __GIT_HASH__: JSON.stringify(gitHash),
- },
+export default defineConfig({
plugins: [
- useLocalnetShellHtml(mode, command),
- emitLocalnetShellAsIndexHtml(mode),
react(),
tsconfigPaths(),
VitePWA({
@@ -112,15 +53,12 @@ export default defineConfig(({ command, mode }): UserConfig => {
target: 'esnext',
rollupOptions: {
input: {
- main: path.join(
- rootDir,
- useLocalnetShell ? 'indexLocal.html' : 'index.html'
- ),
- },
+ main: path.resolve(__dirname, 'index.html')
+ }
}
},
esbuild: {
keepNames: true, // Prevent function name mangling
},
- }
+
})