teams list now renders
This commit is contained in:
parent
17c559bdfc
commit
e95b654d7f
81 changed files with 6132 additions and 40 deletions
271
package-lock.json
generated
271
package-lock.json
generated
|
|
@ -14,10 +14,14 @@
|
||||||
"@mui/icons-material": "^6.1.6",
|
"@mui/icons-material": "^6.1.6",
|
||||||
"@mui/material": "^6.1.6",
|
"@mui/material": "^6.1.6",
|
||||||
"@mui/styles": "^6.1.6",
|
"@mui/styles": "^6.1.6",
|
||||||
|
"@mui/x-date-pickers": "^7.22.2",
|
||||||
|
"@sentry/react": "^8.38.0",
|
||||||
"@types/classnames": "^2.3.0",
|
"@types/classnames": "^2.3.0",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
|
"moment-timezone": "^0.5.46",
|
||||||
|
"notistack": "^3.0.1",
|
||||||
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging",
|
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
|
@ -1448,6 +1452,92 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mui/x-date-pickers": {
|
||||||
|
"version": "7.22.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.22.2.tgz",
|
||||||
|
"integrity": "sha512-1KHSlIlnSoY3oHm820By8X344pIdGYqPvCCvfVHrEeeIQ/pHdxDD8tjZFWkFl4Jgm9oVFK90fMcqNZAzc+WaCw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.25.7",
|
||||||
|
"@mui/utils": "^5.16.6 || ^6.0.0",
|
||||||
|
"@mui/x-internals": "7.21.0",
|
||||||
|
"@types/react-transition-group": "^4.4.11",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"react-transition-group": "^4.4.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/react": "^11.9.0",
|
||||||
|
"@emotion/styled": "^11.8.1",
|
||||||
|
"@mui/material": "^5.15.14 || ^6.0.0",
|
||||||
|
"@mui/system": "^5.15.14 || ^6.0.0",
|
||||||
|
"date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0",
|
||||||
|
"date-fns-jalali": "^2.13.0-0 || ^3.2.0-0",
|
||||||
|
"dayjs": "^1.10.7",
|
||||||
|
"luxon": "^3.0.2",
|
||||||
|
"moment": "^2.29.4",
|
||||||
|
"moment-hijri": "^2.1.2 || ^3.0.0",
|
||||||
|
"moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^17.0.0 || ^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@emotion/styled": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"date-fns": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"date-fns-jalali": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"dayjs": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"luxon": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"moment": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"moment-hijri": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"moment-jalaali": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/x-internals": {
|
||||||
|
"version": "7.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.21.0.tgz",
|
||||||
|
"integrity": "sha512-94YNyZ0BhK5Z+Tkr90RKf47IVCW8R/1MvdUhh6MCQg6sZa74jsX+x+gEZ4kzuCqOsuyTyxikeQ8vVuCIQiP7UQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.25.7",
|
||||||
|
"@mui/utils": "^5.16.6 || ^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
|
|
@ -1793,6 +1883,135 @@
|
||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@sentry-internal/browser-utils": {
|
||||||
|
"version": "8.38.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.38.0.tgz",
|
||||||
|
"integrity": "sha512-5QMVcssrAcmjKT0NdFYcX0b0wwZovGAZ9L2GajErXtHkBenjI2sgR2+5J7n+QZGuk2SC1qhGmT1O9i3p3UEwew==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/core": "8.38.0",
|
||||||
|
"@sentry/types": "8.38.0",
|
||||||
|
"@sentry/utils": "8.38.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry-internal/feedback": {
|
||||||
|
"version": "8.38.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.38.0.tgz",
|
||||||
|
"integrity": "sha512-AW5HCCAlc3T1jcSuNhbFVNO1CHyJ5g5tsGKEP4VKgu+D1Gg2kZ5S2eFatLBUP/BD5JYb1A7p6XPuzYp1XfMq0A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/core": "8.38.0",
|
||||||
|
"@sentry/types": "8.38.0",
|
||||||
|
"@sentry/utils": "8.38.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry-internal/replay": {
|
||||||
|
"version": "8.38.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.38.0.tgz",
|
||||||
|
"integrity": "sha512-mQPShKnIab7oKwkwrRxP/D8fZYHSkDY+cvqORzgi+wAwgnunytJQjz9g6Ww2lJu98rHEkr5SH4V4rs6PZYZmnQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry-internal/browser-utils": "8.38.0",
|
||||||
|
"@sentry/core": "8.38.0",
|
||||||
|
"@sentry/types": "8.38.0",
|
||||||
|
"@sentry/utils": "8.38.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry-internal/replay-canvas": {
|
||||||
|
"version": "8.38.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.38.0.tgz",
|
||||||
|
"integrity": "sha512-OxmlWzK9J8mRM+KxdSnQ5xuxq+p7TiBzTz70FT3HltxmeugvDkyp6803UcFqHOPHR35OYeVLOalym+FmvNn9kw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry-internal/replay": "8.38.0",
|
||||||
|
"@sentry/core": "8.38.0",
|
||||||
|
"@sentry/types": "8.38.0",
|
||||||
|
"@sentry/utils": "8.38.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/browser": {
|
||||||
|
"version": "8.38.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.38.0.tgz",
|
||||||
|
"integrity": "sha512-AZR+b0EteNZEGv6JSdBD22S9VhQ7nrljKsSnzxobBULf3BpwmhmCzTbDrqWszKDAIDYmL+yQJIR2glxbknneWQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry-internal/browser-utils": "8.38.0",
|
||||||
|
"@sentry-internal/feedback": "8.38.0",
|
||||||
|
"@sentry-internal/replay": "8.38.0",
|
||||||
|
"@sentry-internal/replay-canvas": "8.38.0",
|
||||||
|
"@sentry/core": "8.38.0",
|
||||||
|
"@sentry/types": "8.38.0",
|
||||||
|
"@sentry/utils": "8.38.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/core": {
|
||||||
|
"version": "8.38.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.38.0.tgz",
|
||||||
|
"integrity": "sha512-sGD+5TEHU9G7X7zpyaoJxpOtwjTjvOd1f/MKBrWW2vf9UbYK+GUJrOzLhMoSWp/pHSYgvObkJkDb/HwieQjvhQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/types": "8.38.0",
|
||||||
|
"@sentry/utils": "8.38.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/react": {
|
||||||
|
"version": "8.38.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-8.38.0.tgz",
|
||||||
|
"integrity": "sha512-5396tewO00wbJFHUkmU+ikmp4A+wuBpStNc7UDyAm642jfbPajj51+GWld/ZYNFiQaZ/8I9tvvpHqVLnUh21gg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/browser": "8.38.0",
|
||||||
|
"@sentry/core": "8.38.0",
|
||||||
|
"@sentry/types": "8.38.0",
|
||||||
|
"@sentry/utils": "8.38.0",
|
||||||
|
"hoist-non-react-statics": "^3.3.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.14.0 || 17.x || 18.x || 19.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/types": {
|
||||||
|
"version": "8.38.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.38.0.tgz",
|
||||||
|
"integrity": "sha512-fP5H9ZX01W4Z/EYctk3mkSHi7d06cLcX2/UWqwdWbyPWI+pL2QpUPICeO/C+8SnmYx//wFj3qWDhyPCh1PdFAA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/utils": {
|
||||||
|
"version": "8.38.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.38.0.tgz",
|
||||||
|
"integrity": "sha512-3X7MgIKIx+2q5Al7QkhaRB4wV6DvzYsaeIwdqKUzGLuRjXmNgJrLoU87TAwQRmZ6Wr3IoEpThZZMNrzYPXxArw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/types": "8.38.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
|
|
@ -3285,6 +3504,15 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/goober": {
|
||||||
|
"version": "2.1.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz",
|
||||||
|
"integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"csstype": "^3.0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/graphemer": {
|
"node_modules/graphemer": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
|
||||||
|
|
@ -3769,6 +3997,18 @@
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/moment-timezone": {
|
||||||
|
"version": "0.5.46",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.46.tgz",
|
||||||
|
"integrity": "sha512-ZXm9b36esbe7OmdABqIWJuBBiLLwAjrN7CE+7sYdCCx82Nabt1wHDj8TVseS59QIlfFPbOoiBPm6ca9BioG4hw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"moment": "^2.29.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
|
@ -3808,6 +4048,37 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/notistack": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/notistack/-/notistack-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-ntVZXXgSQH5WYfyU+3HfcXuKaapzAJ8fBLQ/G618rn3yvSzEbnOB8ZSOwhX+dAORy/lw+GC2N061JA0+gYWTVA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": "^1.1.0",
|
||||||
|
"goober": "^2.0.33"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0",
|
||||||
|
"npm": ">=6.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/notistack"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/notistack/node_modules/clsx": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object-assign": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,14 @@
|
||||||
"@mui/icons-material": "^6.1.6",
|
"@mui/icons-material": "^6.1.6",
|
||||||
"@mui/material": "^6.1.6",
|
"@mui/material": "^6.1.6",
|
||||||
"@mui/styles": "^6.1.6",
|
"@mui/styles": "^6.1.6",
|
||||||
|
"@mui/x-date-pickers": "^7.22.2",
|
||||||
|
"@sentry/react": "^8.38.0",
|
||||||
"@types/classnames": "^2.3.0",
|
"@types/classnames": "^2.3.0",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
|
"moment-timezone": "^0.5.46",
|
||||||
|
"notistack": "^3.0.1",
|
||||||
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging",
|
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
|
|
||||||
35
src/common/DeleteButton.tsx
Normal file
35
src/common/DeleteButton.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { Delete } from "@mui/icons-material";
|
||||||
|
import { Theme, Button, ButtonProps } from "@mui/material";
|
||||||
|
import { red } from "@mui/material/colors";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
deleteButton: {
|
||||||
|
color: "#fff",
|
||||||
|
background: red["500"],
|
||||||
|
"&:hover": {
|
||||||
|
background: red["600"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rightIcon: {
|
||||||
|
marginLeft: theme.spacing(1)
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const DeleteButton = (props: ButtonProps) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
{...props}
|
||||||
|
className={classes.deleteButton}
|
||||||
|
aria-label="Delete">
|
||||||
|
{props.children}
|
||||||
|
<Delete className={classes.rightIcon} fontSize="small" />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteButton;
|
||||||
230
src/common/IconPicker.tsx
Normal file
230
src/common/IconPicker.tsx
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
CircularProgress,
|
||||||
|
TextField,
|
||||||
|
Theme,
|
||||||
|
Typography,
|
||||||
|
useTheme
|
||||||
|
} from "@mui/material";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useImagekitAPI } from "providers";
|
||||||
|
import { SupervisedUserCircle as TeamIcon } from "@mui/icons-material";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
|
||||||
|
const defaults = [
|
||||||
|
"",
|
||||||
|
"https://ik.imagekit.io/adaptive/defaults/bird_ty8BTljNA.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1663103520016",
|
||||||
|
"https://ik.imagekit.io/adaptive/defaults/leaf_rF3z5r2tZ.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1663212283945",
|
||||||
|
"https://ik.imagekit.io/adaptive/defaults/flower_Uc7QzEQs4R.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1663214712629",
|
||||||
|
"https://ik.imagekit.io/adaptive/defaults/snowleopard_KbnacTZMB.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1663103172262",
|
||||||
|
"https://ik.imagekit.io/adaptive/defaults/car_r5hNMcv7Y.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1663212283392",
|
||||||
|
"https://ik.imagekit.io/adaptive/defaults/sunflower_Pg8sac7NK.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1663214713051",
|
||||||
|
"https://ik.imagekit.io/adaptive/defaults/deer__O5DKbszp.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1663102676111",
|
||||||
|
"https://ik.imagekit.io/adaptive/defaults/polarbear_5PMEyQvwH.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1663208045638",
|
||||||
|
"https://ik.imagekit.io/adaptive/defaults/beach_i2CM4_uB3.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1663212283346",
|
||||||
|
"https://ik.imagekit.io/adaptive/defaults/bunny_ofnih4I6k.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1663208045524",
|
||||||
|
"https://ik.imagekit.io/adaptive/defaults/fox_4CPQjzeSw.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1663208044904",
|
||||||
|
"https://ik.imagekit.io/adaptive/defaults/frog_An4L9CIIj.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1663208044711",
|
||||||
|
"https://ik.imagekit.io/adaptive/defaults/lighthouse_U56RsSQws.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1663212280873",
|
||||||
|
"https://ik.imagekit.io/adaptive/defaults/ball_fpYXIQarg.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1663214713217",
|
||||||
|
"https://ik.imagekit.io/adaptive/defaults/dog_bpolRB5HP.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1663208041090",
|
||||||
|
"https://ik.imagekit.io/adaptive/defaults/turtle__kcJnFkt1.jpg?ik-sdk-version=javascript-1.4.3&updatedAt=1663207995148"
|
||||||
|
];
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
icon: {
|
||||||
|
textAlign: "center",
|
||||||
|
margin: "1px"
|
||||||
|
},
|
||||||
|
img: {
|
||||||
|
height: "100%",
|
||||||
|
width: "auto !important"
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
color: "#fff",
|
||||||
|
[theme.breakpoints.down("xs")]: {
|
||||||
|
width: "32px",
|
||||||
|
height: "32px"
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down("lg")]: {
|
||||||
|
width: "64px",
|
||||||
|
height: "64px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
avatarBig: {
|
||||||
|
[theme.breakpoints.down("lg")]: {
|
||||||
|
width: "128px",
|
||||||
|
height: "128px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
backgroundColor: "rgba(150, 150, 150, 0.25)"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
url: string;
|
||||||
|
setUrl: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
id: string;
|
||||||
|
style?: any;
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IconPicker(props: Props) {
|
||||||
|
const { setUrl, id, title, url } = props;
|
||||||
|
const classes = useStyles();
|
||||||
|
const theme = useTheme();
|
||||||
|
const imagekitAPI = useImagekitAPI();
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const onChangeFile = (event: any) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
var file = event.target.files[0];
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = event => {
|
||||||
|
resolve(event?.target?.result);
|
||||||
|
};
|
||||||
|
reader.onerror = err => {
|
||||||
|
reject(err);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}).then((resp: any) => {
|
||||||
|
let r: string = resp.toString();
|
||||||
|
imagekitAPI
|
||||||
|
.uploadImage("icon", r, "/" + id + "/")
|
||||||
|
.then(resp => {
|
||||||
|
setUrl(resp.data.url + "?v=" + Math.random());
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const top = () => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexDirection: "row"
|
||||||
|
}}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexDirection: "column",
|
||||||
|
marginRight: theme.spacing(1)
|
||||||
|
}}>
|
||||||
|
<Typography variant="h6">{title ? title : "Icon"}</Typography>
|
||||||
|
<Avatar
|
||||||
|
src={url}
|
||||||
|
style={{
|
||||||
|
margin: theme.spacing(1)
|
||||||
|
}}
|
||||||
|
className={classes.avatarBig}>
|
||||||
|
<TeamIcon style={{ fontSize: 56 }} />
|
||||||
|
</Avatar>
|
||||||
|
</div>
|
||||||
|
{sources()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const picker = () => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexDirection: "row",
|
||||||
|
flexFlow: "row wrap"
|
||||||
|
}}>
|
||||||
|
{defaults.map((src, index) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
padding: theme.spacing(1),
|
||||||
|
borderRadius: "4px",
|
||||||
|
display: "inline-block"
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setUrl(src);
|
||||||
|
}}
|
||||||
|
className={url === src ? classes.selected : undefined}>
|
||||||
|
<Avatar src={src} variant="rounded" className={classes.avatar}>
|
||||||
|
<TeamIcon style={{ fontSize: 48 }} />
|
||||||
|
</Avatar>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const sources = () => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center"
|
||||||
|
}}>
|
||||||
|
<input
|
||||||
|
name="Upload Image"
|
||||||
|
type="file"
|
||||||
|
onChange={event => onChangeFile(event)}
|
||||||
|
style={{
|
||||||
|
margin: theme.spacing(1)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{loading && (
|
||||||
|
<CircularProgress
|
||||||
|
style={{
|
||||||
|
marginLeft: theme.spacing(1),
|
||||||
|
width: theme.spacing(3),
|
||||||
|
height: theme.spacing(3)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<TextField
|
||||||
|
id="outlined-basic"
|
||||||
|
label="Image URL"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
style={{ margin: theme.spacing(1) }}
|
||||||
|
onChange={event => {
|
||||||
|
setUrl(event.currentTarget.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{top()}
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
style={{
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
textAlign: "center"
|
||||||
|
}}>
|
||||||
|
Pick one
|
||||||
|
</Typography>
|
||||||
|
{picker()}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
41
src/common/NotificationButton.tsx
Normal file
41
src/common/NotificationButton.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { createStyles, IconButton, Tooltip } from "@mui/material";
|
||||||
|
// import { Theme } from "@material-ui/core/styles/createMuiTheme";
|
||||||
|
// import withStyles, { WithStyles } from "@material-ui/core/styles/withStyles";
|
||||||
|
import NotificationsEnabledIcon from "@mui/icons-material/NotificationsActive";
|
||||||
|
import NotificationsDisabledIcon from "@mui/icons-material/NotificationsOff";
|
||||||
|
import { WithStyles } from "@mui/styles";
|
||||||
|
|
||||||
|
const styles = () => createStyles({});
|
||||||
|
|
||||||
|
interface Props extends WithStyles<typeof styles> {
|
||||||
|
notify: boolean;
|
||||||
|
hidden?: boolean;
|
||||||
|
onChange: Function;
|
||||||
|
disabled?: boolean;
|
||||||
|
tooltip: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// interface State {}
|
||||||
|
|
||||||
|
export default function NotificationButton (props: Props) {
|
||||||
|
|
||||||
|
const { notify, onChange, hidden, tooltip, disabled } = props;
|
||||||
|
|
||||||
|
if (hidden) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip title={tooltip}>
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
aria-label="toggle notifactions"
|
||||||
|
onClick={() => onChange()}
|
||||||
|
disabled={disabled}>
|
||||||
|
{notify ? <NotificationsEnabledIcon /> : <NotificationsDisabledIcon />}
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
38
src/common/ResponsiveDialog.tsx
Normal file
38
src/common/ResponsiveDialog.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { Dialog, Theme } from "@mui/material";
|
||||||
|
import { DialogProps } from "@mui/material/Dialog";
|
||||||
|
// import { TransitionProps } from "@mui/material/transitions";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import { useMobile } from "hooks";
|
||||||
|
// import React from "react";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
bg: {
|
||||||
|
background: theme.palette.background.default
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// const Transition = React.forwardRef(function Transition(
|
||||||
|
// props: TransitionProps & { children?: React.ReactElement<any, any> },
|
||||||
|
// ref: React.Ref<unknown>
|
||||||
|
// ) {
|
||||||
|
// return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
// });
|
||||||
|
|
||||||
|
//renders a fullscren dialog when a mobile viewport is detected
|
||||||
|
export default function ResponsiveDialog(props: DialogProps) {
|
||||||
|
const classes = useStyles();
|
||||||
|
const isMobile = useMobile();
|
||||||
|
const fullScreen = props.fullScreen ?? isMobile;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
// TransitionComponent={Transition}
|
||||||
|
{...props}
|
||||||
|
PaperProps={{
|
||||||
|
classes: { root: classes.bg }
|
||||||
|
}}>
|
||||||
|
{props.children}
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -15,18 +15,18 @@ export {
|
||||||
// useMeasurementsWebsocket,
|
// useMeasurementsWebsocket,
|
||||||
// useMetricAPI,
|
// useMetricAPI,
|
||||||
// useMineAPI,
|
// useMineAPI,
|
||||||
// usePermissionAPI,
|
usePermissionAPI,
|
||||||
// usePreferenceAPI,
|
// usePreferenceAPI,
|
||||||
// useSecurity,
|
// useSecurity,
|
||||||
// useSnackbar,
|
useSnackbar,
|
||||||
// useTagAPI,
|
// useTagAPI,
|
||||||
// useUsageAPI,
|
// useUsageAPI,
|
||||||
// useUserAPI
|
useUserAPI
|
||||||
} from "providers";
|
} from "providers";
|
||||||
// export * from "./useDebounce";
|
// export * from "./useDebounce";
|
||||||
// export * from "./useForceUpdate";
|
// export * from "./useForceUpdate";
|
||||||
// export * from "./useInterval";
|
// export * from "./useInterval";
|
||||||
// export * from "./usePrevious";
|
export * from "./usePrevious";
|
||||||
export * from "./useThemeType";
|
export * from "./useThemeType";
|
||||||
export * from "./useWidth";
|
export * from "./useWidth";
|
||||||
// export * from "./useViewport";
|
// export * from "./useViewport";
|
||||||
9
src/hooks/usePrevious.ts
Normal file
9
src/hooks/usePrevious.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
export const usePrevious = <T extends any>(value: T) => {
|
||||||
|
const ref = useRef<T>();
|
||||||
|
useEffect(() => {
|
||||||
|
ref.current = value;
|
||||||
|
});
|
||||||
|
return ref.current;
|
||||||
|
};
|
||||||
32
src/models/ShareableLink.ts
Normal file
32
src/models/ShareableLink.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { or } from "utils/types";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
|
||||||
|
export class ShareableLink {
|
||||||
|
public settings: pond.ShareableLinkSettings = pond.ShareableLinkSettings.create();
|
||||||
|
public status: pond.ShareableLinkStatus = pond.ShareableLinkStatus.create();
|
||||||
|
|
||||||
|
public static create(pb?: pond.ShareableLink): ShareableLink {
|
||||||
|
let my = new ShareableLink();
|
||||||
|
if (pb) {
|
||||||
|
my.settings = pond.ShareableLinkSettings.fromObject(cloneDeep(or(pb.settings, {})));
|
||||||
|
my.status = pond.ShareableLinkStatus.fromObject(cloneDeep(or(pb.status, {})));
|
||||||
|
}
|
||||||
|
return my;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static any(data: any): ShareableLink {
|
||||||
|
return ShareableLink.create(pond.ShareableLink.fromObject(cloneDeep(data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static clone(link: ShareableLink): ShareableLink {
|
||||||
|
let my = new ShareableLink();
|
||||||
|
my.settings = pond.ShareableLinkSettings.fromObject(cloneDeep(link.settings));
|
||||||
|
my.status = pond.ShareableLinkStatus.fromObject(cloneDeep(link.status));
|
||||||
|
return my;
|
||||||
|
}
|
||||||
|
|
||||||
|
public key(): string {
|
||||||
|
return this.settings.key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
// export * from "./Group";
|
// export * from "./Group";
|
||||||
// export * from "./Interaction";
|
// export * from "./Interaction";
|
||||||
export * from "./Scope";
|
export * from "./Scope";
|
||||||
// export * from "./ShareableLink";
|
export * from "./ShareableLink";
|
||||||
// export * from "./Site";
|
// export * from "./Site";
|
||||||
// export * from "./Tag";
|
// export * from "./Tag";
|
||||||
// export * from "./Task";
|
// export * from "./Task";
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import { useMobile } from "hooks";
|
import { useMobile } from "hooks";
|
||||||
import PageContainer from "pages/PageContainer";
|
import PageContainer from "pages/PageContainer";
|
||||||
// import TeamList from "teams/TeamList";
|
import TeamList from "teams/TeamList";
|
||||||
|
|
||||||
export default function Teams() {
|
export default function Teams() {
|
||||||
const isMobile = useMobile();
|
const isMobile = useMobile();
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<Box paddingY={isMobile ? 0.5 : 1} paddingX={isMobile ? 1 : 2}>
|
<Box paddingY={isMobile ? 0.5 : 1} paddingX={isMobile ? 1 : 2}>
|
||||||
{/* <TeamList /> */}
|
<TeamList />
|
||||||
Teams!
|
{/* Teams! */}
|
||||||
</Box>
|
</Box>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
27
src/pbHelpers/Context.ts
Normal file
27
src/pbHelpers/Context.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
export function getContextKeys(): string[] {
|
||||||
|
// Get the url entries without any zero length strings
|
||||||
|
let entries = document.location.pathname.split("/").filter(key => key.length > 0);
|
||||||
|
let cutTail = entries.length % 2 === 0;
|
||||||
|
|
||||||
|
// Filter out every other entry
|
||||||
|
entries = entries.filter((_, index) => index % 2 === 1);
|
||||||
|
|
||||||
|
if (cutTail) return entries.slice(0, -1);
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getContextTypes(): string[] {
|
||||||
|
// Get the url entries without any zero length strings
|
||||||
|
let entries = document.location.pathname.split("/").filter(key => key.length > 0);
|
||||||
|
|
||||||
|
// Filter out every other entry
|
||||||
|
entries = entries.filter((_, index) => index % 2 === 0);
|
||||||
|
|
||||||
|
// jobsite should just be site and none should end in s anymore
|
||||||
|
entries = entries.slice(0, -1).map(str => (str.endsWith("s") ? str.slice(0, -1) : str));
|
||||||
|
entries = entries.map(item => (item === "jobsite" ? "site" : item));
|
||||||
|
|
||||||
|
// Return all types but the last (it is the object being loaded, not the context)
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
21
src/pbHelpers/Permission.ts
Normal file
21
src/pbHelpers/Permission.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { or } from "utils/types";
|
||||||
|
|
||||||
|
export function permissionToString(permission: pond.Permission): string {
|
||||||
|
return or(
|
||||||
|
new Map<pond.Permission, string>([
|
||||||
|
[pond.Permission.PERMISSION_INVALID, "PERMISSION_INVALID"],
|
||||||
|
[pond.Permission.PERMISSION_READ, "PERMISSION_READ"],
|
||||||
|
[pond.Permission.PERMISSION_SHARE, "PERMISSION_SHARE"],
|
||||||
|
[pond.Permission.PERMISSION_USERS, "PERMISSION_USERS"],
|
||||||
|
[pond.Permission.PERMISSION_WRITE, "PERMISSION_WRITE"],
|
||||||
|
[pond.Permission.PERMISSION_FILE_MANAGEMENT, "PERMISSION_FILE_MANAGEMENT"],
|
||||||
|
[pond.Permission.PERMISSION_BILLING, "PERMISSION_BILLING"]
|
||||||
|
]).get(permission),
|
||||||
|
"PERMISSION_INVALID"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canWrite(permissions: pond.Permission[]): boolean {
|
||||||
|
return permissions.includes(pond.Permission.PERMISSION_WRITE);
|
||||||
|
}
|
||||||
41
src/pbHelpers/User.ts
Normal file
41
src/pbHelpers/User.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import moment from "moment-timezone";
|
||||||
|
import { User } from "models";
|
||||||
|
|
||||||
|
export function userRoleFromPermissions(permissions: Array<pond.Permission>): string {
|
||||||
|
//only be an owner if they have all 5
|
||||||
|
if (
|
||||||
|
permissions.includes(pond.Permission.PERMISSION_USERS) &&
|
||||||
|
permissions.includes(pond.Permission.PERMISSION_READ) &&
|
||||||
|
permissions.includes(pond.Permission.PERMISSION_WRITE) &&
|
||||||
|
permissions.includes(pond.Permission.PERMISSION_SHARE) &&
|
||||||
|
permissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT)
|
||||||
|
)
|
||||||
|
return "Owner";
|
||||||
|
if (permissions.includes(pond.Permission.PERMISSION_WRITE)) return "Admin";
|
||||||
|
return "User";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sortUsersByRole(users: User[]): User[] {
|
||||||
|
let sortedUsers: User[] = users.slice(0).sort((a: User, b: User) => {
|
||||||
|
if (a.permissions.length === b.permissions.length) {
|
||||||
|
let aName: string = a.name().toLowerCase();
|
||||||
|
let bName: string = b.name().toLowerCase();
|
||||||
|
return aName > bName ? 1 : bName > aName ? -1 : 0;
|
||||||
|
}
|
||||||
|
return a.permissions.length - b.permissions.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
return sortedUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultUser(): User {
|
||||||
|
return User.any({
|
||||||
|
settings: {
|
||||||
|
notifyByDefault: true,
|
||||||
|
notificationMethods: [pond.NotificationMethod.NOTIFICATION_METHOD_EMAIL],
|
||||||
|
phoneNumber: "",
|
||||||
|
timezone: moment.tz.guess()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
24
src/products/AgIcons/AddField.tsx
Normal file
24
src/products/AgIcons/AddField.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import AddFieldWhite from "assets/products/Ag/addFieldWhite.png";
|
||||||
|
import AddFieldBlack from "assets/products/Ag/addFieldBlack.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AddFieldIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? AddFieldWhite : AddFieldBlack;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? AddFieldBlack : AddFieldWhite;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="fields" src={src()} />;
|
||||||
|
}
|
||||||
24
src/products/AgIcons/AddMarker.tsx
Normal file
24
src/products/AgIcons/AddMarker.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import AddMarkerWhite from "assets/products/Ag/addMarkerWhite.png";
|
||||||
|
import AddMarkerBlack from "assets/products/Ag/addMarkerBlack.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AddMarkerIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? AddMarkerWhite : AddMarkerBlack;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? AddMarkerBlack : AddMarkerWhite;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="fields" src={src()} />;
|
||||||
|
}
|
||||||
29
src/products/AgIcons/AerationFanIcon.tsx
Normal file
29
src/products/AgIcons/AerationFanIcon.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import AerationFanDarkIcon from "assets/components/aerationFanDark.png";
|
||||||
|
import AerationFanLightIcon from "assets/components/aerationFanLight.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AerationFanIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type, size } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? AerationFanLightIcon : AerationFanDarkIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? AerationFanDarkIcon : AerationFanLightIcon;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (size) {
|
||||||
|
return <img width={size} height={size} alt="fan" src={src()} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ImgIcon alt="fan" src={src()} />;
|
||||||
|
}
|
||||||
24
src/products/AgIcons/Delete.tsx
Normal file
24
src/products/AgIcons/Delete.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import DeleteWhite from "assets/products/Ag/deleteWhite.png";
|
||||||
|
import DeleteBlack from "assets/products/Ag/deleteBlack.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DeleteIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? DeleteWhite : DeleteBlack;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? DeleteBlack : DeleteWhite;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="fields" src={src()} />;
|
||||||
|
}
|
||||||
25
src/products/AgIcons/DiseaseIcon.tsx
Normal file
25
src/products/AgIcons/DiseaseIcon.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import DiseaseLight from "assets/products/Ag/diseaseLight.png";
|
||||||
|
import DiseaseDark from "assets/products/Ag/diseaseDark.png";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DiseaseIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { width, height, type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? DiseaseLight : DiseaseDark;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? DiseaseDark : DiseaseLight;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <img width={width} height={height} alt="diseaseIcon" src={src()} />;
|
||||||
|
}
|
||||||
24
src/products/AgIcons/Edit.tsx
Normal file
24
src/products/AgIcons/Edit.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import EditWhite from "assets/products/Ag/editWhite.png";
|
||||||
|
import EditBlack from "assets/products/Ag/editBlack.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EditIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? EditWhite : EditBlack;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? EditBlack : EditWhite;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="fields" src={src()} />;
|
||||||
|
}
|
||||||
24
src/products/AgIcons/FieldList.tsx
Normal file
24
src/products/AgIcons/FieldList.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import FieldListWhite from "assets/products/Ag/fieldListWhite.png";
|
||||||
|
import FieldListBlack from "assets/products/Ag/fieldListBlack.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FieldListIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? FieldListWhite : FieldListBlack;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? FieldListBlack : FieldListWhite;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="fields" src={src()} />;
|
||||||
|
}
|
||||||
24
src/products/AgIcons/FieldMap.tsx
Normal file
24
src/products/AgIcons/FieldMap.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import FieldMapWhite from "assets/products/Ag/fieldMapWhite.png";
|
||||||
|
import FieldMapBlack from "assets/products/Ag/fieldMapBlack.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FieldMapIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? FieldMapWhite : FieldMapBlack;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? FieldMapBlack : FieldMapWhite;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="fields" src={src()} />;
|
||||||
|
}
|
||||||
24
src/products/AgIcons/FieldNames.tsx
Normal file
24
src/products/AgIcons/FieldNames.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import FieldNamesWhite from "assets/products/Ag/fieldNamesWhite.png";
|
||||||
|
import FieldNamesBlack from "assets/products/Ag/fieldNamesBlack.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FieldNamesIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? FieldNamesWhite : FieldNamesBlack;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? FieldNamesBlack : FieldNamesWhite;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="fields" src={src()} />;
|
||||||
|
}
|
||||||
29
src/products/AgIcons/FieldsIcon.tsx
Normal file
29
src/products/AgIcons/FieldsIcon.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import FieldsIconLight from "assets/products/Ag/FieldsIconLight.png";
|
||||||
|
import FieldsIconDark from "assets/products/Ag/FieldsIconDark.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FieldsIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type, size } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? FieldsIconLight : FieldsIconDark;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? FieldsIconDark : FieldsIconLight;
|
||||||
|
};
|
||||||
|
|
||||||
|
return size ? (
|
||||||
|
<img alt="fields" height={size} width={size} src={src()} />
|
||||||
|
) : (
|
||||||
|
<ImgIcon alt="fields" src={src()} />
|
||||||
|
);
|
||||||
|
}
|
||||||
24
src/products/AgIcons/GearMarker.tsx
Normal file
24
src/products/AgIcons/GearMarker.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import GearMarkerLight from "assets/products/Ag/gearMarkerLight.png";
|
||||||
|
import GearMarkerDark from "assets/products/Ag/gearMarkerDark.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GearMarkerIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? GearMarkerLight : GearMarkerDark;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? GearMarkerDark : GearMarkerLight;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="markerSettings" src={src()} />;
|
||||||
|
}
|
||||||
24
src/products/AgIcons/HomeIcon.tsx
Normal file
24
src/products/AgIcons/HomeIcon.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import HomeIconLight from "assets/products/Ag/HomeIconLight.png";
|
||||||
|
import HomeIconDark from "assets/products/Ag/HomeIconDark.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function HomeIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? HomeIconLight : HomeIconDark;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? HomeIconDark : HomeIconLight;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="fields" src={src()} />;
|
||||||
|
}
|
||||||
24
src/products/AgIcons/MarkerMove.tsx
Normal file
24
src/products/AgIcons/MarkerMove.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import MarkerMoveLight from "assets/products/Ag/markerMoveLight.png";
|
||||||
|
import MarkerMoveDark from "assets/products/Ag/markerMoveDark.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MarkerMoveIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? MarkerMoveLight : MarkerMoveDark;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? MarkerMoveDark : MarkerMoveLight;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="markerMoveToggle" src={src()} />;
|
||||||
|
}
|
||||||
25
src/products/AgIcons/PestsIcon.tsx
Normal file
25
src/products/AgIcons/PestsIcon.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import PestsLight from "assets/products/Ag/pestsLight.png";
|
||||||
|
import PestsDark from "assets/products/Ag/pestsDark.png";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PestsIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { width, height, type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? PestsLight : PestsDark;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? PestsDark : PestsLight;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <img width={width} height={height} alt="pestsIcon" src={src()} />;
|
||||||
|
}
|
||||||
37
src/products/AgIcons/PressureIcon.tsx
Normal file
37
src/products/AgIcons/PressureIcon.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import PressureDarkIcon from "assets/components/pressureDark.png";
|
||||||
|
import PressureLightIcon from "assets/components/pressureLight.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
heightWidth?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TemperatureIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type, heightWidth } = props;
|
||||||
|
|
||||||
|
const lightIcon = () => {
|
||||||
|
return PressureLightIcon;
|
||||||
|
};
|
||||||
|
|
||||||
|
const darkIcon = () => {
|
||||||
|
return PressureDarkIcon;
|
||||||
|
};
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? lightIcon() : darkIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? darkIcon() : lightIcon();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (heightWidth) {
|
||||||
|
return <img alt="pressure" src={src()} height={heightWidth} width={heightWidth} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ImgIcon alt="pressure" src={src()} />;
|
||||||
|
}
|
||||||
25
src/products/AgIcons/RocksIcon.tsx
Normal file
25
src/products/AgIcons/RocksIcon.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import RocksLight from "assets/products/Ag/rocksLight.png";
|
||||||
|
import RocksDark from "assets/products/Ag/rocksDark.png";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RocksIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { width, height, type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? RocksLight : RocksDark;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? RocksDark : RocksLight;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <img width={width} height={height} alt="rocksIcon" src={src()} />;
|
||||||
|
}
|
||||||
24
src/products/AgIcons/ScoutIcon.tsx
Normal file
24
src/products/AgIcons/ScoutIcon.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import ScoutLight from "assets/products/Ag/scoutLight.png";
|
||||||
|
import ScoutDark from "assets/products/Ag/scoutDark.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ScoutIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? ScoutLight : ScoutDark;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? ScoutDark : ScoutLight;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="fields" src={src()} />;
|
||||||
|
}
|
||||||
25
src/products/AgIcons/WeedsIcon.tsx
Normal file
25
src/products/AgIcons/WeedsIcon.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import WeedsLight from "assets/products/Ag/weedsLight.png";
|
||||||
|
import WeedsDark from "assets/products/Ag/weedsDark.png";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function WeedsIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { width, height, type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? WeedsLight : WeedsDark;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? WeedsDark : WeedsLight;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <img width={width} height={height} alt="weedsIcon" src={src()} />;
|
||||||
|
}
|
||||||
25
src/products/AviationIcons/AddPlaneIcon.tsx
Normal file
25
src/products/AviationIcons/AddPlaneIcon.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import White from "assets/products/Aviation/AddPlaneIconWhite.png";
|
||||||
|
import Black from "assets/products/Aviation/AddPlaneIconBlack.png";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AddPlaneIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type, width, height } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? White : Black;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? Black : White;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <img alt="addPlane" src={src()} width={width ?? 25} height={height ?? 25} />;
|
||||||
|
}
|
||||||
25
src/products/AviationIcons/AirportMapIcon.tsx
Normal file
25
src/products/AviationIcons/AirportMapIcon.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import White from "assets/products/Aviation/AirportMapWhite.png";
|
||||||
|
import Black from "assets/products/Aviation/AirportMapBlack.png";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AirportMapIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type, width, height } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? White : Black;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? Black : White;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <img alt="airportMap" src={src()} width={width ?? 25} height={height ?? 25} />;
|
||||||
|
}
|
||||||
24
src/products/AviationIcons/OmniAirDeviceIcon.tsx
Normal file
24
src/products/AviationIcons/OmniAirDeviceIcon.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import White from "assets/products/Aviation/OmniAirDeviceIconWhite.png";
|
||||||
|
import Black from "assets/products/Aviation/OmniAirDeviceIconBlack.png";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
size?: number;
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function OmniAirDeviceIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type, size } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? White : Black;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? Black : White;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <img alt="OmniAirDevice" src={src()} width={size ?? 25} height={size ?? 25} />;
|
||||||
|
}
|
||||||
25
src/products/AviationIcons/PlaneIcon.tsx
Normal file
25
src/products/AviationIcons/PlaneIcon.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import White from "assets/products/Aviation/PlaneWhite.png";
|
||||||
|
import Black from "assets/products/Aviation/PlaneBlack.png";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PlaneIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type, width, height } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? White : Black;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? Black : White;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <img alt="plane" src={src()} width={width ?? 25} height={height ?? 25} />;
|
||||||
|
}
|
||||||
24
src/products/Bindapt/AddBinIcon.tsx
Normal file
24
src/products/Bindapt/AddBinIcon.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import AddBinDark from "assets/products/bindapt/addBin.png";
|
||||||
|
import AddBinLight from "assets/products/bindapt/addBinLight.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AddBinIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? AddBinLight : AddBinDark;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? AddBinDark : AddBinLight;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="newBin" src={src()} />;
|
||||||
|
}
|
||||||
292
src/products/Bindapt/BindaptAvailability.ts
Normal file
292
src/products/Bindapt/BindaptAvailability.ts
Normal file
|
|
@ -0,0 +1,292 @@
|
||||||
|
import { ConfigurablePin } from "pbHelpers/AddressTypes";
|
||||||
|
import { DeviceAvailabilityMap, DevicePositions } from "pbHelpers/DeviceAvailability";
|
||||||
|
import { quack } from "protobuf-ts/pond";
|
||||||
|
|
||||||
|
const BindaptPlusPins: ConfigurablePin[] = [
|
||||||
|
{ address: 4, label: "1" },
|
||||||
|
{ address: 8, label: "2" },
|
||||||
|
{ address: 512, label: "C1" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const BindaptPlusAvailability: DeviceAvailabilityMap = new Map<
|
||||||
|
quack.AddressType,
|
||||||
|
DevicePositions
|
||||||
|
>([
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_INVALID, []],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, BindaptPlusPins],
|
||||||
|
[
|
||||||
|
quack.AddressType.ADDRESS_TYPE_I2C,
|
||||||
|
new Map<quack.ComponentType, number[]>([
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]]
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_GPS, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_MODEM, [0]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
const BindaptPlusModPins: ConfigurablePin[] = [
|
||||||
|
{ address: 4, label: "1" },
|
||||||
|
{ address: 8, label: "2" },
|
||||||
|
{ address: 16, label: "3" },
|
||||||
|
{ address: 512, label: "C1" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const BindaptPlusModAvailability: DeviceAvailabilityMap = new Map<
|
||||||
|
quack.AddressType,
|
||||||
|
DevicePositions
|
||||||
|
>([
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_INVALID, []],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, BindaptPlusModPins],
|
||||||
|
[
|
||||||
|
quack.AddressType.ADDRESS_TYPE_I2C,
|
||||||
|
new Map<quack.ComponentType, number[]>([
|
||||||
|
//[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]]
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_GPS, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_MODEM, [0]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
const BindaptPlusProPins: ConfigurablePin[] = [
|
||||||
|
{ address: 4, label: "1" },
|
||||||
|
{ address: 8, label: "2" },
|
||||||
|
{ address: 16, label: "3" },
|
||||||
|
{ address: 512, label: "C1" },
|
||||||
|
{ address: 1024, label: "C2" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const BindaptPlusProAvailability: DeviceAvailabilityMap = new Map<
|
||||||
|
quack.AddressType,
|
||||||
|
DevicePositions
|
||||||
|
>([
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_INVALID, []],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, BindaptPlusProPins],
|
||||||
|
[
|
||||||
|
quack.AddressType.ADDRESS_TYPE_I2C,
|
||||||
|
new Map<quack.ComponentType, number[]>([
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]]
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_GPS, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_MODEM, [0]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
const BindaptPlusProModPins: ConfigurablePin[] = [
|
||||||
|
{ address: 4, label: "1" },
|
||||||
|
{ address: 8, label: "2" },
|
||||||
|
{ address: 16, label: "3" },
|
||||||
|
{ address: 32, label: "4" },
|
||||||
|
{ address: 512, label: "C1" },
|
||||||
|
{ address: 1024, label: "C2" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const BindaptPlusProModAvailability: DeviceAvailabilityMap = new Map<
|
||||||
|
quack.AddressType,
|
||||||
|
DevicePositions
|
||||||
|
>([
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_INVALID, []],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, BindaptPlusProModPins],
|
||||||
|
[
|
||||||
|
quack.AddressType.ADDRESS_TYPE_I2C,
|
||||||
|
new Map<quack.ComponentType, number[]>([
|
||||||
|
//[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]]
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_GPS, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_MODEM, [0]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
const BindaptPlusLitePins: ConfigurablePin[] = [
|
||||||
|
{ address: 4, label: "1" },
|
||||||
|
{ address: 8, label: "2" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const BindaptPlusLiteAvailability: DeviceAvailabilityMap = new Map<
|
||||||
|
quack.AddressType,
|
||||||
|
DevicePositions
|
||||||
|
>([
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_INVALID, []],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, BindaptPlusLitePins],
|
||||||
|
[
|
||||||
|
quack.AddressType.ADDRESS_TYPE_I2C,
|
||||||
|
new Map<quack.ComponentType, number[]>([
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]]
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_GPS, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_MODEM, [0]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
const BindaptMiniPins: ConfigurablePin[] = [
|
||||||
|
{ address: 256, label: "1" },
|
||||||
|
{ address: 512, label: "2" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const BindaptMiniAvailability: DeviceAvailabilityMap = new Map<
|
||||||
|
quack.AddressType,
|
||||||
|
DevicePositions
|
||||||
|
>([
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_INVALID, []],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, BindaptMiniPins],
|
||||||
|
[
|
||||||
|
quack.AddressType.ADDRESS_TYPE_I2C,
|
||||||
|
new Map<quack.ComponentType, number[]>([[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]]])
|
||||||
|
],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_GPS, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_MODEM, [0]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
const BindaptUltimatePins: ConfigurablePin[] = [
|
||||||
|
{ address: 1, label: "P1" },
|
||||||
|
{ address: 4, label: "P2" },
|
||||||
|
{ address: 8, label: "P3" },
|
||||||
|
{ address: 8192, label: "P4" },
|
||||||
|
{ address: 16, label: "P5" },
|
||||||
|
//{ address: 32, label: "P6"}, //for possible expansion if we want a sixth input port on the box
|
||||||
|
{ address: 128, label: "C1" },
|
||||||
|
{ address: 256, label: "C2" },
|
||||||
|
{ address: 512, label: "C3" },
|
||||||
|
{ address: 1024, label: "C4" }
|
||||||
|
//{ address: 2048, label: "C5"} //for possible expansion if we want a fifth control port on the box
|
||||||
|
];
|
||||||
|
|
||||||
|
export const BindaptUltimateAvailability: DeviceAvailabilityMap = new Map<
|
||||||
|
quack.AddressType,
|
||||||
|
DevicePositions
|
||||||
|
>([
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_INVALID, []],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, BindaptUltimatePins],
|
||||||
|
[
|
||||||
|
quack.AddressType.ADDRESS_TYPE_I2C,
|
||||||
|
new Map<quack.ComponentType, number[]>([
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]]
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_GPS, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_MODEM, [0]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
const BinMonitorPins: ConfigurablePin[] = [
|
||||||
|
{ address: 4, label: "1" },
|
||||||
|
{ address: 8, label: "2" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const BinMonitorAvailability: DeviceAvailabilityMap = new Map<
|
||||||
|
quack.AddressType,
|
||||||
|
DevicePositions
|
||||||
|
>([
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_INVALID, []],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, BinMonitorPins],
|
||||||
|
[
|
||||||
|
quack.AddressType.ADDRESS_TYPE_I2C,
|
||||||
|
new Map<quack.ComponentType, number[]>([
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]]
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_GPS, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_MODEM, [0]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** V2 product availability maps */
|
||||||
|
|
||||||
|
//TODO: change these pins to match the V2 pin map
|
||||||
|
const BindaptV2MonitorPins: ConfigurablePin[] = [
|
||||||
|
{ address: 1, label: "1" },
|
||||||
|
{ address: 2, label: "2" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const BindaptV2MonitorAvailability: DeviceAvailabilityMap = new Map<
|
||||||
|
quack.AddressType,
|
||||||
|
DevicePositions
|
||||||
|
>([
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_INVALID, []],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, BindaptV2MonitorPins],
|
||||||
|
[
|
||||||
|
quack.AddressType.ADDRESS_TYPE_I2C,
|
||||||
|
new Map<quack.ComponentType, number[]>([
|
||||||
|
//[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]]
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_GPS, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_MODEM, [0]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
const BindaptV2AutomatePins: ConfigurablePin[] = [
|
||||||
|
{ address: 1, label: "1" },
|
||||||
|
{ address: 2, label: "2" },
|
||||||
|
{ address: 4, label: "3" },
|
||||||
|
{ address: 8, label: "4" },
|
||||||
|
{ address: 256, label: "C1" },
|
||||||
|
{ address: 512, label: "C2" },
|
||||||
|
{ address: 1024, label: "C3" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const BindaptV2AutomateAvailability: DeviceAvailabilityMap = new Map<
|
||||||
|
quack.AddressType,
|
||||||
|
DevicePositions
|
||||||
|
>([
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_INVALID, []],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, BindaptV2AutomatePins],
|
||||||
|
[
|
||||||
|
quack.AddressType.ADDRESS_TYPE_I2C,
|
||||||
|
new Map<quack.ComponentType, number[]>([
|
||||||
|
//[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]]
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_GPS, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_MODEM, [0]]
|
||||||
|
]);
|
||||||
210
src/products/Bindapt/BindaptDescriber.ts
Normal file
210
src/products/Bindapt/BindaptDescriber.ts
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
import BindaptCellularDarkIcon from "assets/products/bindapt/bindaptCellularDark.png";
|
||||||
|
import BindaptCellularLightIcon from "assets/products/bindapt/bindaptCellularLight.png";
|
||||||
|
import BindaptPlusDarkIcon from "assets/products/bindapt/bindaptPlusDark.png";
|
||||||
|
import BindaptPlusLightIcon from "assets/products/bindapt/bindaptPlusLight.png";
|
||||||
|
import BindaptPlusProDarkIcon from "assets/products/bindapt/bindaptPlusProDark.png";
|
||||||
|
import BindaptPlusProLightIcon from "assets/products/bindapt/bindaptPlusProLight.png";
|
||||||
|
import BindaptWifiDarkIcon from "assets/products/bindapt/bindaptWifiDark.png";
|
||||||
|
import BindaptWifiLightIcon from "assets/products/bindapt/bindaptWifiLight.png";
|
||||||
|
import { DeviceProductDescriber, ProductTab } from "products/DeviceProduct";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import {
|
||||||
|
BindaptPlusAvailability,
|
||||||
|
BindaptPlusProAvailability,
|
||||||
|
BindaptPlusLiteAvailability,
|
||||||
|
BindaptMiniAvailability,
|
||||||
|
BindaptUltimateAvailability,
|
||||||
|
BinMonitorAvailability,
|
||||||
|
BindaptPlusModAvailability,
|
||||||
|
BindaptPlusProModAvailability,
|
||||||
|
BindaptV2MonitorAvailability,
|
||||||
|
BindaptV2AutomateAvailability
|
||||||
|
} from "./BindaptAvailability";
|
||||||
|
|
||||||
|
const getBindaptPlatformIcon = (
|
||||||
|
platform: pond.DevicePlatform,
|
||||||
|
theme?: "light" | "dark"
|
||||||
|
): string | undefined => {
|
||||||
|
switch (platform) {
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_PHOTON:
|
||||||
|
return theme === "light" ? BindaptWifiDarkIcon : BindaptWifiLightIcon;
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_ELECTRON:
|
||||||
|
return theme === "light" ? BindaptCellularDarkIcon : BindaptCellularLightIcon;
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBindaptTabs = (): ProductTab[] => {
|
||||||
|
return ["presets", "components"];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BindaptPlus: DeviceProductDescriber = {
|
||||||
|
product: pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS,
|
||||||
|
label: "Bindapt+",
|
||||||
|
icon: (_, theme?: "light" | "dark") => {
|
||||||
|
return theme === "light" ? BindaptPlusDarkIcon : BindaptPlusLightIcon;
|
||||||
|
},
|
||||||
|
view: "controller",
|
||||||
|
tabs: getBindaptTabs(),
|
||||||
|
availability: BindaptPlusAvailability,
|
||||||
|
defaultAddressMap: new Map<string, string[]>([
|
||||||
|
["plenum", ["9-2-64"]],
|
||||||
|
["pressure", ["6-2-24"]],
|
||||||
|
["heatersFans", ["3-1-512"]] //refers to heaters and fans
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BindaptPlusMod: DeviceProductDescriber = {
|
||||||
|
product: pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS_MOD,
|
||||||
|
label: "Bindapt+ V1.5",
|
||||||
|
icon: (_, theme?: "light" | "dark") => {
|
||||||
|
return theme === "light" ? BindaptPlusDarkIcon : BindaptPlusLightIcon;
|
||||||
|
},
|
||||||
|
view: "controller",
|
||||||
|
tabs: getBindaptTabs(),
|
||||||
|
availability: BindaptPlusModAvailability
|
||||||
|
//TODO not sure what the default address map for the modded products should be yet
|
||||||
|
// defaultAddressMap: new Map<string, string[]>([
|
||||||
|
// ["plenum", ["9-2-64"]],
|
||||||
|
// ["heatersFans", ["3-1-512"]] //refers to heaters and fans
|
||||||
|
// ])
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BindaptPlusPro: DeviceProductDescriber = {
|
||||||
|
product: pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS_PRO,
|
||||||
|
label: "Bindapt+ Pro",
|
||||||
|
icon: (_, theme?: "light" | "dark") => {
|
||||||
|
return theme === "light" ? BindaptPlusProDarkIcon : BindaptPlusProLightIcon;
|
||||||
|
},
|
||||||
|
view: "controller",
|
||||||
|
tabs: getBindaptTabs(),
|
||||||
|
availability: BindaptPlusProAvailability,
|
||||||
|
defaultAddressMap: new Map<string, string[]>([
|
||||||
|
["plenum", ["9-2-64"]],
|
||||||
|
["pressure", ["6-2-24"]],
|
||||||
|
["heatersFans", ["3-1-512", "3-1-1024"]] //refers to heaters and fans
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BindaptPlusProMod: DeviceProductDescriber = {
|
||||||
|
product: pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS_PRO_MOD,
|
||||||
|
label: "Bindapt+ Pro V1.5",
|
||||||
|
icon: (_, theme?: "light" | "dark") => {
|
||||||
|
return theme === "light" ? BindaptPlusProDarkIcon : BindaptPlusProLightIcon;
|
||||||
|
},
|
||||||
|
view: "controller",
|
||||||
|
tabs: getBindaptTabs(),
|
||||||
|
availability: BindaptPlusProModAvailability
|
||||||
|
//TODO not sure what the default address map should be yet
|
||||||
|
// defaultAddressMap: new Map<string, string[]>([
|
||||||
|
// ["plenum", ["9-2-64"]],
|
||||||
|
// ["heatersFans", ["3-1-512", "3-1-1024"]] //refers to heaters and fans
|
||||||
|
// ])
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BindaptPlusLite: DeviceProductDescriber = {
|
||||||
|
product: pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS_LITE,
|
||||||
|
label: "Bindapt+ Lite",
|
||||||
|
icon: getBindaptPlatformIcon,
|
||||||
|
tabs: getBindaptTabs(),
|
||||||
|
availability: BindaptPlusLiteAvailability
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BindaptMini: DeviceProductDescriber = {
|
||||||
|
product: pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_MINI,
|
||||||
|
label: "Bindapt Mini",
|
||||||
|
icon: getBindaptPlatformIcon,
|
||||||
|
tabs: getBindaptTabs(),
|
||||||
|
availability: BindaptMiniAvailability
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BinUltimate: DeviceProductDescriber = {
|
||||||
|
product: pond.DeviceProduct.DEVICE_PRODUCT_BIN_ULTIMATE,
|
||||||
|
label: "Bindapt Ultimate",
|
||||||
|
icon: getBindaptPlatformIcon,
|
||||||
|
tabs: getBindaptTabs(),
|
||||||
|
view: "controller",
|
||||||
|
availability: BindaptUltimateAvailability,
|
||||||
|
defaultAddressMap: new Map<string, string[]>([
|
||||||
|
["headspace", ["9-2-64", "28-2-98"]], //is both the dht and lidar components in the halo
|
||||||
|
["pressure", ["6-2-24"]],
|
||||||
|
["heatersFans", ["3-1-512", "3-1-1024", "3-1-128", "3-1-256"]] //refers to heaters and fans
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BinMonitor: DeviceProductDescriber = {
|
||||||
|
product: pond.DeviceProduct.DEVICE_PRODUCT_BIN_MONITOR,
|
||||||
|
label: "Bin Monitor",
|
||||||
|
icon: getBindaptPlatformIcon,
|
||||||
|
tabs: getBindaptTabs(),
|
||||||
|
availability: BinMonitorAvailability,
|
||||||
|
defaultAddressMap: new Map<string, string[]>([
|
||||||
|
["headspace", ["9-2-64", "28-2-98"]], //is both the dht and lidar components in the halo
|
||||||
|
["pressure", ["6-2-24"]]
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BinHalo: DeviceProductDescriber = {
|
||||||
|
product: pond.DeviceProduct.DEVICE_PRODUCT_BIN_HALO,
|
||||||
|
label: "Bin Halo",
|
||||||
|
icon: getBindaptPlatformIcon,
|
||||||
|
tabs: getBindaptTabs(),
|
||||||
|
view: "controller",
|
||||||
|
availability: BindaptPlusAvailability,
|
||||||
|
defaultAddressMap: new Map<string, string[]>([
|
||||||
|
["headspace", ["9-2-64", "28-2-98"]], //is both the dht and lidar components in the halo
|
||||||
|
["pressure", ["6-2-24"]],
|
||||||
|
["heatersFans", ["3-1-512"]] //refers to heaters and fans
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BindaptV2Monitor: DeviceProductDescriber = {
|
||||||
|
product: pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_V2_MONITOR,
|
||||||
|
label: "Bindapt+ V2 Monitor",
|
||||||
|
icon: (_, theme?: "light" | "dark") => {
|
||||||
|
return theme === "light" ? BindaptPlusDarkIcon : BindaptPlusLightIcon;
|
||||||
|
},
|
||||||
|
view: "controller",
|
||||||
|
tabs: getBindaptTabs(),
|
||||||
|
availability: BindaptV2MonitorAvailability
|
||||||
|
//TODO not sure what the default address map for these should be yet
|
||||||
|
// defaultAddressMap: new Map<string, string[]>([
|
||||||
|
// ["plenum", ["9-2-64"]],
|
||||||
|
// ["heatersFans", ["3-1-512"]] //refers to heaters and fans
|
||||||
|
// ])
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BindaptV2Automate: DeviceProductDescriber = {
|
||||||
|
product: pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_V2_AUTOMATE,
|
||||||
|
label: "Bindapt+ V2 Automate",
|
||||||
|
icon: (_, theme?: "light" | "dark") => {
|
||||||
|
return theme === "light" ? BindaptPlusDarkIcon : BindaptPlusLightIcon;
|
||||||
|
},
|
||||||
|
view: "controller",
|
||||||
|
tabs: getBindaptTabs(),
|
||||||
|
availability: BindaptV2AutomateAvailability
|
||||||
|
//TODO not sure what the default address map for these should be yet
|
||||||
|
// defaultAddressMap: new Map<string, string[]>([
|
||||||
|
// ["plenum", ["9-2-64"]],
|
||||||
|
// ["heatersFans", ["3-1-512"]] //refers to heaters and fans
|
||||||
|
// ])
|
||||||
|
};
|
||||||
|
|
||||||
|
export function IsBindaptDevice(product?: pond.DeviceProduct): boolean {
|
||||||
|
const bindaptProducts: pond.DeviceProduct[] = [
|
||||||
|
pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_MINI,
|
||||||
|
pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS,
|
||||||
|
pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS_PRO,
|
||||||
|
pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS_LITE,
|
||||||
|
pond.DeviceProduct.DEVICE_PRODUCT_BIN_ULTIMATE,
|
||||||
|
pond.DeviceProduct.DEVICE_PRODUCT_BIN_MONITOR,
|
||||||
|
pond.DeviceProduct.DEVICE_PRODUCT_BIN_HALO,
|
||||||
|
pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS_MOD,
|
||||||
|
pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS_PRO_MOD,
|
||||||
|
pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_V2_MONITOR,
|
||||||
|
pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_V2_AUTOMATE
|
||||||
|
];
|
||||||
|
|
||||||
|
return product ? bindaptProducts.includes(product) : false;
|
||||||
|
}
|
||||||
39
src/products/Bindapt/BinsIcon.tsx
Normal file
39
src/products/Bindapt/BinsIcon.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import BinDarkIcon from "../../assets/products/bindapt/binDark.png";
|
||||||
|
import BinLightIcon from "../../assets/products/bindapt/binLight.png";
|
||||||
|
import BinsDarkIcon from "assets/products/bindapt/binsDark.png";
|
||||||
|
import BinsLightIcon from "assets/products/bindapt/binsLight.png";
|
||||||
|
import { ImgIcon } from "../../common/ImgIcon";
|
||||||
|
import { useThemeType } from "../../hooks/useThemeType";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
singleBin?: boolean;
|
||||||
|
type?: "light" | "dark";
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BinsIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type, singleBin, size } = props;
|
||||||
|
|
||||||
|
const lightIcon = () => {
|
||||||
|
return singleBin ? BinLightIcon : BinsLightIcon;
|
||||||
|
};
|
||||||
|
|
||||||
|
const darkIcon = () => {
|
||||||
|
return singleBin ? BinDarkIcon : BinsDarkIcon;
|
||||||
|
};
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? lightIcon() : darkIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? darkIcon() : lightIcon();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (size) {
|
||||||
|
return <img width={size} height={size} alt="bins" src={src()} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ImgIcon alt="bins" src={src()} />;
|
||||||
|
}
|
||||||
24
src/products/Bindapt/CableIcon.tsx
Normal file
24
src/products/Bindapt/CableIcon.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import GrainCableDarkIcon from "assets/components/grainCableDark.png";
|
||||||
|
import GrainCableLightIcon from "assets/components/grainCableLight.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CableIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? GrainCableLightIcon : GrainCableDarkIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? GrainCableDarkIcon : GrainCableLightIcon;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="bins" src={src()} />;
|
||||||
|
}
|
||||||
24
src/products/Bindapt/DataDuckIcon.tsx
Normal file
24
src/products/Bindapt/DataDuckIcon.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import DataDuckDark from "assets/common/dataDuckDark.png";
|
||||||
|
import DataDuckLight from "assets/common/dataDuckLight.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DataDuckIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? DataDuckLight : DataDuckDark;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? DataDuckDark : DataDuckLight;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="dataDuck" src={src()} />;
|
||||||
|
}
|
||||||
11
src/products/Bindapt/constructionIcon.tsx
Normal file
11
src/products/Bindapt/constructionIcon.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import ConstructionIconGold from "assets/products/bindapt/constructionIcon.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function ConstIcon() {
|
||||||
|
const src = () => {
|
||||||
|
return ConstructionIconGold;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="construction" src={src()} />;
|
||||||
|
}
|
||||||
4
src/products/Bindapt/index.ts
Normal file
4
src/products/Bindapt/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export * from "./BindaptAvailability";
|
||||||
|
export * from "./BindaptDescriber";
|
||||||
|
export * from "./BindaptIcon";
|
||||||
|
export * from "./BinsIcon";
|
||||||
24
src/products/CommonIcons/cnhiIcon.tsx
Normal file
24
src/products/CommonIcons/cnhiIcon.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import CNHiLogoWhite from "assets/marketplaceImages/CNHiWhite.png";
|
||||||
|
import CNHiLogoBlack from "assets/marketplaceImages/CNHiBlack.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CNHiIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? CNHiLogoWhite : CNHiLogoBlack;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? CNHiLogoBlack : CNHiLogoWhite;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="case-new-holland" src={src()} />;
|
||||||
|
}
|
||||||
29
src/products/CommonIcons/contractIcon.tsx
Normal file
29
src/products/CommonIcons/contractIcon.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import ContractIconLight from "assets/common/contractWhite.png";
|
||||||
|
import ContractIconDark from "assets/common/contractBlack.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ContractsIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type, size } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? ContractIconLight : ContractIconDark;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? ContractIconDark : ContractIconLight;
|
||||||
|
};
|
||||||
|
|
||||||
|
return size ? (
|
||||||
|
<img alt="fields" height={size} width={size} src={src()} />
|
||||||
|
) : (
|
||||||
|
<ImgIcon alt="fields" src={src()} />
|
||||||
|
);
|
||||||
|
}
|
||||||
24
src/products/CommonIcons/fuelIcon.tsx
Normal file
24
src/products/CommonIcons/fuelIcon.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import FuelDark from "assets/components/fuelDark.png";
|
||||||
|
import FuelLight from "assets/components/fuelLight.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FuelIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? FuelLight : FuelDark;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? FuelDark : FuelLight;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="marketplace" src={src()} />;
|
||||||
|
}
|
||||||
24
src/products/CommonIcons/graphIcon.tsx
Normal file
24
src/products/CommonIcons/graphIcon.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import graphDark from "assets/common/graphDark.png";
|
||||||
|
import graphLight from "assets/common/graphLight.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GraphIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? graphLight : graphDark;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? graphDark : graphLight;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="graph" src={src()} />;
|
||||||
|
}
|
||||||
24
src/products/CommonIcons/johnDeereIcon.tsx
Normal file
24
src/products/CommonIcons/johnDeereIcon.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import JohnDeereLogoWhite from "assets/marketplaceImages/JDWhite.png";
|
||||||
|
import JohnDeereLogoBlack from "assets/marketplaceImages/JDBlack.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function JohnDeereIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? JohnDeereLogoWhite : JohnDeereLogoBlack;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? JohnDeereLogoBlack : JohnDeereLogoWhite;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="john-deere" src={src()} />;
|
||||||
|
}
|
||||||
24
src/products/CommonIcons/marketplaceIcon.tsx
Normal file
24
src/products/CommonIcons/marketplaceIcon.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import MarketWhite from "assets/marketplaceImages/marketplaceIconLight.png";
|
||||||
|
import MarketBlack from "assets/marketplaceImages/marketplaceIconDark.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MarketplaceIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? MarketWhite : MarketBlack;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? MarketBlack : MarketWhite;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="marketplace" src={src()} />;
|
||||||
|
}
|
||||||
24
src/products/Construction/ACHomeIcon.tsx
Normal file
24
src/products/Construction/ACHomeIcon.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import ACHomeLightIcon from "assets/products/Construction/acHomeIconLight.png";
|
||||||
|
import ACHomeDarkIcon from "assets/products/Construction/acHomeIconDark.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ACHomeIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? ACHomeLightIcon : ACHomeDarkIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? ACHomeDarkIcon : ACHomeLightIcon;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="home" src={src()} />;
|
||||||
|
}
|
||||||
29
src/products/Construction/DeviceGroupIcon.tsx
Normal file
29
src/products/Construction/DeviceGroupIcon.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import DeviceGroupLightIcon from "assets/products/Construction/deviceGroupIconLight.png";
|
||||||
|
import DeviceGroupDarkIcon from "assets/products/Construction/deviceGroupIconDark.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
size?: number;
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DeviceGroupIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type, size } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? DeviceGroupLightIcon : DeviceGroupDarkIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? DeviceGroupDarkIcon : DeviceGroupLightIcon;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (size) {
|
||||||
|
return <img width={size} height={size} alt="bins" src={src()} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ImgIcon alt="group" src={src()} />;
|
||||||
|
}
|
||||||
28
src/products/Construction/JobSiteIcon.tsx
Normal file
28
src/products/Construction/JobSiteIcon.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import JobSiteLightIcon from "assets/products/Construction/jobSiteIconLight.png";
|
||||||
|
import JobSiteDarkIcon from "assets/products/Construction/jobSiteIconDark.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function JobsiteIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type, size } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? JobSiteLightIcon : JobSiteDarkIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? JobSiteDarkIcon : JobSiteLightIcon;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (size) {
|
||||||
|
return <img width={size} height={size} alt="jobsite" src={src()}></img>;
|
||||||
|
}
|
||||||
|
return <ImgIcon alt="jobsite" src={src()} />;
|
||||||
|
}
|
||||||
29
src/products/Construction/NexusSTIcon.tsx
Normal file
29
src/products/Construction/NexusSTIcon.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import NexusLightIcon from "assets/products/Construction/nexusIconLight.png";
|
||||||
|
import NexusDarkIcon from "assets/products/Construction/nexusIconDark.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
size?: number;
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function NexusSTIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type, size } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? NexusLightIcon : NexusDarkIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? NexusDarkIcon : NexusLightIcon;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (size) {
|
||||||
|
return <img width={size} height={size} alt="bins" src={src()} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ImgIcon alt="nexus" src={src()} />;
|
||||||
|
}
|
||||||
24
src/products/Construction/ObjectHeaterIcon.tsx
Normal file
24
src/products/Construction/ObjectHeaterIcon.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import HeaterDarkIcon from "assets/components/heaterDark.png";
|
||||||
|
import HeaterLightIcon from "assets/components/heaterLight.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ObjectHeaterIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? HeaterLightIcon : HeaterDarkIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? HeaterDarkIcon : HeaterLightIcon;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="group" src={src()} />;
|
||||||
|
}
|
||||||
29
src/products/Construction/SitesIcon.tsx
Normal file
29
src/products/Construction/SitesIcon.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import SitesDarkIcon from "assets/products/Construction/sitesIconDark.png";
|
||||||
|
import SitesLightIcon from "assets/products/Construction/sitesIconLight.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
size?: number;
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SitesIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type, size } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? SitesLightIcon : SitesDarkIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? SitesDarkIcon : SitesLightIcon;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (size) {
|
||||||
|
return <img width={size} height={size} alt="bins" src={src()} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ImgIcon alt="sites" src={src()} />;
|
||||||
|
}
|
||||||
29
src/products/Construction/TasksIcon.tsx
Normal file
29
src/products/Construction/TasksIcon.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import TasksDarkIcon from "assets/products/Construction/tasksIconDark.png";
|
||||||
|
import TasksLightIcon from "assets/products/Construction/tasksIconLight.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TasksIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type, size } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? TasksLightIcon : TasksDarkIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? TasksDarkIcon : TasksLightIcon;
|
||||||
|
};
|
||||||
|
|
||||||
|
return size ? (
|
||||||
|
<img alt="tasks" height={size} width={size} src={src()} />
|
||||||
|
) : (
|
||||||
|
<ImgIcon alt="tasks" src={src()} />
|
||||||
|
);
|
||||||
|
}
|
||||||
140
src/products/DeviceProduct.ts
Normal file
140
src/products/DeviceProduct.ts
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
import { DeviceAvailabilityMap, DevicePositions } from "pbHelpers/DeviceAvailability";
|
||||||
|
import { pond, quack } from "protobuf-ts/pond";
|
||||||
|
import {
|
||||||
|
BindaptMini,
|
||||||
|
BindaptPlus,
|
||||||
|
BindaptPlusLite,
|
||||||
|
BindaptPlusMod,
|
||||||
|
BindaptPlusPro,
|
||||||
|
BindaptPlusProMod,
|
||||||
|
BindaptV2Monitor,
|
||||||
|
BindaptV2Automate,
|
||||||
|
BinHalo,
|
||||||
|
BinMonitor,
|
||||||
|
BinUltimate
|
||||||
|
} from "./Bindapt/BindaptDescriber";
|
||||||
|
import { NexusST } from "./Nexus/NexusDescriber";
|
||||||
|
import { MiPCAV2, OmniAir } from "./OmniAir/OmniAirDescriber";
|
||||||
|
import { ConfigurablePin } from "pbHelpers/AddressTypes";
|
||||||
|
|
||||||
|
export type ProductTab = "components" | "presets";
|
||||||
|
|
||||||
|
export interface DeviceProductDescriber {
|
||||||
|
product: pond.DeviceProduct;
|
||||||
|
label: string;
|
||||||
|
icon: (platform: pond.DevicePlatform, theme?: "light" | "dark") => string | undefined;
|
||||||
|
tabs: ProductTab[];
|
||||||
|
view?: "default" | "controller";
|
||||||
|
availability?: DeviceAvailabilityMap;
|
||||||
|
componentExtension?: Boolean;
|
||||||
|
defaultAddressMap?: Map<string, string[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const None: DeviceProductDescriber = {
|
||||||
|
product: pond.DeviceProduct.DEVICE_PRODUCT_NONE,
|
||||||
|
label: "None",
|
||||||
|
icon: () => undefined,
|
||||||
|
tabs: ["components"]
|
||||||
|
};
|
||||||
|
|
||||||
|
//think about making weather describer
|
||||||
|
const weatherStationPins: ConfigurablePin[] = [
|
||||||
|
{ address: 4, label: "1" },
|
||||||
|
{ address: 8, label: "2" },
|
||||||
|
{ address: 16, label: "3" },
|
||||||
|
{ address: 32, label: "4" }
|
||||||
|
];
|
||||||
|
|
||||||
|
const Weather: DeviceProductDescriber = {
|
||||||
|
product: pond.DeviceProduct.DEVICE_PRODUCT_WEATHER_STATION,
|
||||||
|
label: "Weather Station",
|
||||||
|
icon: () => undefined,
|
||||||
|
tabs: ["components"],
|
||||||
|
availability: new Map<quack.AddressType, DevicePositions>([
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_INVALID, []],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, weatherStationPins],
|
||||||
|
[
|
||||||
|
quack.AddressType.ADDRESS_TYPE_I2C,
|
||||||
|
new Map<quack.ComponentType, number[]>([
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]]
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_GPS, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_MODEM, [0]]
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEVICE_PRODUCT_MAP = new Map<pond.DeviceProduct, DeviceProductDescriber>([
|
||||||
|
[pond.DeviceProduct.DEVICE_PRODUCT_NONE, None],
|
||||||
|
[pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS, BindaptPlus],
|
||||||
|
[pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS_PRO, BindaptPlusPro],
|
||||||
|
[pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS_LITE, BindaptPlusLite],
|
||||||
|
[pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_MINI, BindaptMini],
|
||||||
|
[pond.DeviceProduct.DEVICE_PRODUCT_WEATHER_STATION, Weather],
|
||||||
|
[pond.DeviceProduct.DEVICE_PRODUCT_NEXUS_ST, NexusST],
|
||||||
|
[pond.DeviceProduct.DEVICE_PRODUCT_OMNIAIR, OmniAir],
|
||||||
|
[pond.DeviceProduct.DEVICE_PRODUCT_BIN_HALO, BinHalo],
|
||||||
|
[pond.DeviceProduct.DEVICE_PRODUCT_BIN_ULTIMATE, BinUltimate],
|
||||||
|
[pond.DeviceProduct.DEVICE_PRODUCT_BIN_MONITOR, BinMonitor],
|
||||||
|
[pond.DeviceProduct.DEVICE_PRODUCT_MIPCA_V2, MiPCAV2],
|
||||||
|
[pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS_MOD, BindaptPlusMod],
|
||||||
|
[pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS_PRO_MOD, BindaptPlusProMod],
|
||||||
|
[pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_V2_MONITOR, BindaptV2Monitor],
|
||||||
|
[pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_V2_AUTOMATE, BindaptV2Automate]
|
||||||
|
]);
|
||||||
|
|
||||||
|
export function ListDeviceProductDescribers(): DeviceProductDescriber[] {
|
||||||
|
return [...DEVICE_PRODUCT_MAP.values()];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDescriber(product: pond.DeviceProduct): DeviceProductDescriber {
|
||||||
|
let describer = DEVICE_PRODUCT_MAP.get(product);
|
||||||
|
return describer ? describer : None;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetDeviceProductLabel(product: pond.DeviceProduct): string {
|
||||||
|
return getDescriber(product).label;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetDeviceProductIcon(
|
||||||
|
product: pond.DeviceProduct,
|
||||||
|
platform: pond.DevicePlatform,
|
||||||
|
theme?: "dark" | "light"
|
||||||
|
): string | undefined {
|
||||||
|
return getDescriber(product).icon(platform, theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IsCardController(product?: pond.DeviceProduct): boolean {
|
||||||
|
return product ? getDescriber(product).view === "controller" : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetDeviceProductViews(product?: pond.DeviceProduct): boolean {
|
||||||
|
return product ? getDescriber(product).view === "controller" : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetDeviceProductTabs(product?: pond.DeviceProduct): ProductTab[] {
|
||||||
|
return product ? getDescriber(product).tabs : ["components"];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetProductAvailability(
|
||||||
|
product?: pond.DeviceProduct
|
||||||
|
): DeviceAvailabilityMap | undefined {
|
||||||
|
return product ? getDescriber(product).availability : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IsExtended(product: pond.DeviceProduct): boolean {
|
||||||
|
let isExtended = getDescriber(product).componentExtension;
|
||||||
|
if (isExtended) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetProductDefaults(product: pond.DeviceProduct): Map<string, string[]> | undefined {
|
||||||
|
return getDescriber(product).defaultAddressMap;
|
||||||
|
}
|
||||||
31
src/products/Nexus/NexusAvailability.ts
Normal file
31
src/products/Nexus/NexusAvailability.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { ConfigurablePin } from "pbHelpers/AddressTypes";
|
||||||
|
import { DeviceAvailabilityMap, DevicePositions } from "pbHelpers/DeviceAvailability";
|
||||||
|
import { quack } from "protobuf-ts/pond";
|
||||||
|
|
||||||
|
const NexusSTPins: ConfigurablePin[] = [
|
||||||
|
{ address: 4, label: "1" },
|
||||||
|
{ address: 8, label: "2" },
|
||||||
|
{ address: 16, label: "3" },
|
||||||
|
{ address: 512, label: "C1" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const NexusSTAvailability: DeviceAvailabilityMap = new Map<
|
||||||
|
quack.AddressType,
|
||||||
|
DevicePositions
|
||||||
|
>([
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_INVALID, []],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, NexusSTPins],
|
||||||
|
[
|
||||||
|
quack.AddressType.ADDRESS_TYPE_I2C,
|
||||||
|
new Map<quack.ComponentType, number[]>([
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]]
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_GPS, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_MODEM, [0]]
|
||||||
|
]);
|
||||||
17
src/products/Nexus/NexusDescriber.ts
Normal file
17
src/products/Nexus/NexusDescriber.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { DeviceProductDescriber } from "products/DeviceProduct";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { NexusSTAvailability } from "./NexusAvailability";
|
||||||
|
import NexusLightIcon from "assets/products/Construction/nexusIconLight.png";
|
||||||
|
import NexusDarkIcon from "assets/products/Construction/nexusIconDark.png";
|
||||||
|
|
||||||
|
export const NexusST: DeviceProductDescriber = {
|
||||||
|
product: pond.DeviceProduct.DEVICE_PRODUCT_NEXUS_ST,
|
||||||
|
label: "Nexus ST",
|
||||||
|
icon: (_, theme?: "light" | "dark") => {
|
||||||
|
return theme === "light" ? NexusDarkIcon : NexusLightIcon;
|
||||||
|
},
|
||||||
|
view: "controller",
|
||||||
|
tabs: ["components"],
|
||||||
|
availability: NexusSTAvailability,
|
||||||
|
componentExtension: true
|
||||||
|
};
|
||||||
59
src/products/OmniAir/OmniAirAvailability.ts
Normal file
59
src/products/OmniAir/OmniAirAvailability.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { ConfigurablePin } from "pbHelpers/AddressTypes";
|
||||||
|
import { DeviceAvailabilityMap, DevicePositions } from "pbHelpers/DeviceAvailability";
|
||||||
|
import { quack } from "protobuf-ts/pond";
|
||||||
|
|
||||||
|
const OmniAirPins: ConfigurablePin[] = [
|
||||||
|
{ address: 4, label: "1" },
|
||||||
|
{ address: 8, label: "2" },
|
||||||
|
{ address: 16, label: "3" },
|
||||||
|
{ address: 512, label: "C1" },
|
||||||
|
{ address: 1024, label: "C2" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const OmniAirAvailability: DeviceAvailabilityMap = new Map<
|
||||||
|
quack.AddressType,
|
||||||
|
DevicePositions
|
||||||
|
>([
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_INVALID, []],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, OmniAirPins],
|
||||||
|
[
|
||||||
|
quack.AddressType.ADDRESS_TYPE_I2C,
|
||||||
|
new Map<quack.ComponentType, number[]>([
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]]
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_GPS, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_MODEM, [0]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
/*---V2 Stuff starts here---*/
|
||||||
|
|
||||||
|
const MiPCAV2Pins: ConfigurablePin[] = [
|
||||||
|
{ address: 1, label: "1" },
|
||||||
|
{ address: 2, label: "2" },
|
||||||
|
{ address: 4, label: "3" },
|
||||||
|
{ address: 256, label: "L1" },
|
||||||
|
{ address: 512, label: "L2" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const MiPCAV2Availability: DeviceAvailabilityMap = new Map<
|
||||||
|
quack.AddressType,
|
||||||
|
DevicePositions
|
||||||
|
>([
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_INVALID, []],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, MiPCAV2Pins],
|
||||||
|
[
|
||||||
|
quack.AddressType.ADDRESS_TYPE_I2C,
|
||||||
|
new Map<quack.ComponentType, number[]>([
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]]
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_GPS, [0]],
|
||||||
|
[quack.AddressType.ADDRESS_TYPE_MODEM, [0]]
|
||||||
|
]);
|
||||||
31
src/products/OmniAir/OmniAirDescriber.ts
Normal file
31
src/products/OmniAir/OmniAirDescriber.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { DeviceProductDescriber } from "products/DeviceProduct";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { MiPCAV2Availability, OmniAirAvailability } from "./OmniAirAvailability";
|
||||||
|
import OmniAirLightIcon from "assets/products/Aviation/OmniAirDeviceIconWhite.png";
|
||||||
|
import OmniAirDarkIcon from "assets/products/Aviation/OmniAirDeviceIconBlack.png";
|
||||||
|
|
||||||
|
export const OmniAir: DeviceProductDescriber = {
|
||||||
|
product: pond.DeviceProduct.DEVICE_PRODUCT_OMNIAIR,
|
||||||
|
label: "MiPCA V1",
|
||||||
|
icon: (_, theme?: "light" | "dark") => {
|
||||||
|
return theme === "light" ? OmniAirDarkIcon : OmniAirLightIcon;
|
||||||
|
},
|
||||||
|
view: "controller",
|
||||||
|
tabs: ["components"],
|
||||||
|
availability: OmniAirAvailability,
|
||||||
|
componentExtension: true
|
||||||
|
};
|
||||||
|
|
||||||
|
/*---V2 Stuff starts here---*/
|
||||||
|
|
||||||
|
export const MiPCAV2: DeviceProductDescriber = {
|
||||||
|
product: pond.DeviceProduct.DEVICE_PRODUCT_MIPCA_V2,
|
||||||
|
label: "MiPCA V2",
|
||||||
|
icon: (_, theme?: "light" | "dark") => {
|
||||||
|
return theme === "light" ? OmniAirDarkIcon : OmniAirLightIcon;
|
||||||
|
},
|
||||||
|
view: "controller",
|
||||||
|
tabs: ["components"],
|
||||||
|
availability: MiPCAV2Availability,
|
||||||
|
componentExtension: true
|
||||||
|
};
|
||||||
86
src/products/ProductTabChip.tsx
Normal file
86
src/products/ProductTabChip.tsx
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
import { Avatar, ButtonBase, createStyles, makeStyles, Theme, Tooltip } from "@material-ui/core";
|
||||||
|
import { Tune as PresetsIcon, ViewList as ComponentsIcon } from "@material-ui/icons";
|
||||||
|
import classnames from "classnames";
|
||||||
|
import { capitalize } from "lodash";
|
||||||
|
import React from "react";
|
||||||
|
import { ProductTab } from "./DeviceProduct";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
const themeType = theme.palette.type;
|
||||||
|
const activeBG = theme.palette.secondary.main;
|
||||||
|
return createStyles({
|
||||||
|
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
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
color: theme.palette.getContrastText(activeBG),
|
||||||
|
backgroundColor: activeBG,
|
||||||
|
border: 0
|
||||||
|
},
|
||||||
|
customIcon: {
|
||||||
|
padding: theme.spacing(0.5)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
tab: ProductTab;
|
||||||
|
onClick: (selected: ProductTab) => void;
|
||||||
|
active?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ProductTabChip(props: Props) {
|
||||||
|
const classes = useStyles();
|
||||||
|
const { tab, active, onClick } = props;
|
||||||
|
|
||||||
|
const name = () => {
|
||||||
|
return capitalize(tab);
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasCustomIcon = () => {
|
||||||
|
switch (tab) {
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const customIconSrc = () => {
|
||||||
|
switch (tab) {
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const icon = () => {
|
||||||
|
switch (tab) {
|
||||||
|
case "presets":
|
||||||
|
return <PresetsIcon fontSize="small" />;
|
||||||
|
case "components":
|
||||||
|
return <ComponentsIcon fontSize="small" />;
|
||||||
|
default:
|
||||||
|
return <React.Fragment />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip title={name()}>
|
||||||
|
<Avatar
|
||||||
|
alt={name()}
|
||||||
|
src={hasCustomIcon() ? customIconSrc() : undefined}
|
||||||
|
className={classnames(
|
||||||
|
classes.avatar,
|
||||||
|
active && classes.active,
|
||||||
|
hasCustomIcon() && classes.customIcon
|
||||||
|
)}
|
||||||
|
component={ButtonBase}
|
||||||
|
onClick={() => onClick(tab)}>
|
||||||
|
{!hasCustomIcon() && icon()}
|
||||||
|
</Avatar>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
24
src/products/ventilation/MiningIcon.tsx
Normal file
24
src/products/ventilation/MiningIcon.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import MineDarkIcon from "assets/components/mineIconDark.png";
|
||||||
|
import MineLightIcon from "assets/components/mineIconLight.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function VentilationIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? MineLightIcon : MineDarkIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? MineDarkIcon : MineLightIcon;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="mine" src={src()} />;
|
||||||
|
}
|
||||||
24
src/products/ventilation/VentilationIcon.tsx
Normal file
24
src/products/ventilation/VentilationIcon.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import VentilationDarkIcon from "assets/products/ventilation/ventIconDark.png";
|
||||||
|
import VentilationLightIcon from "assets/products/ventilation/ventIconLight.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function VentilationIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? VentilationLightIcon : VentilationDarkIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? VentilationDarkIcon : VentilationLightIcon;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="ventilation" src={src()} />;
|
||||||
|
}
|
||||||
141
src/providers/Snackbar.tsx
Normal file
141
src/providers/Snackbar.tsx
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
import { Button, Theme } from "@mui/material";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import {
|
||||||
|
ProviderContext as NotistackProviderContext,
|
||||||
|
SnackbarAction,
|
||||||
|
SnackbarProvider as NotistackSnackbarProvider,
|
||||||
|
useSnackbar as useNotistackSnackbar
|
||||||
|
} from "notistack";
|
||||||
|
import React, { createContext, PropsWithChildren, useContext } from "react";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
bottomLeft: {
|
||||||
|
position: "absolute",
|
||||||
|
bottom: theme.spacing(7),
|
||||||
|
left: theme.spacing(1),
|
||||||
|
[theme.breakpoints.up("sm")]: {
|
||||||
|
bottom: theme.spacing(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface ISnackbarContext {
|
||||||
|
success: (message: string, action?: SnackbarAction) => void;
|
||||||
|
warning: (message: string, action?: SnackbarAction) => void;
|
||||||
|
error: (message: string, action?: SnackbarAction) => void;
|
||||||
|
info: (message: string, action?: SnackbarAction) => void;
|
||||||
|
openSnack: (message: string, action?: SnackbarAction) => void;
|
||||||
|
closeSnackbar: (key?: string | number | undefined) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Status = "error" | "success" | "warning" | "info" | "default";
|
||||||
|
let openExternalSnack: (status: Status, message: string) => void;
|
||||||
|
export const SnackbarContext = createContext<ISnackbarContext>({} as ISnackbarContext);
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
function SnackbarHelper(props: PropsWithChildren<Props>) {
|
||||||
|
const { children } = props;
|
||||||
|
const { enqueueSnackbar, closeSnackbar } = useNotistackSnackbar();
|
||||||
|
|
||||||
|
const openNewSnack = (status: Status, message: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case "success":
|
||||||
|
success(message);
|
||||||
|
break;
|
||||||
|
case "warning":
|
||||||
|
warning(message);
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
error(message);
|
||||||
|
break;
|
||||||
|
case "info":
|
||||||
|
info(message);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
openSnack(message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
openExternalSnack = openNewSnack;
|
||||||
|
|
||||||
|
const openSnack = (message: string, action?: SnackbarAction) => {
|
||||||
|
enqueueSnackbar(message, { variant: "default", action });
|
||||||
|
};
|
||||||
|
|
||||||
|
const success = (message: string, action?: SnackbarAction) => {
|
||||||
|
enqueueSnackbar(message, { variant: "success", action });
|
||||||
|
};
|
||||||
|
|
||||||
|
const warning = (message: string, action?: SnackbarAction) => {
|
||||||
|
enqueueSnackbar(message, { variant: "warning", action });
|
||||||
|
};
|
||||||
|
|
||||||
|
const error = (message: string, action?: SnackbarAction) => {
|
||||||
|
let msg = message as any;
|
||||||
|
if (typeof message !== "string") {
|
||||||
|
if (Object.keys(message).includes("response")) {
|
||||||
|
msg = msg.response.data.error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enqueueSnackbar(msg, { variant: "error", action });
|
||||||
|
};
|
||||||
|
|
||||||
|
const info = (message: string, action?: SnackbarAction) => {
|
||||||
|
enqueueSnackbar(message, { variant: "info", action });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SnackbarContext.Provider value={{ success, warning, error, info, openSnack, closeSnackbar }}>
|
||||||
|
{children}
|
||||||
|
</SnackbarContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SnackbarProvider(props: PropsWithChildren<Props>) {
|
||||||
|
const { children } = props;
|
||||||
|
const classes = useStyles();
|
||||||
|
const notistackRef = React.createRef<NotistackProviderContext>();
|
||||||
|
|
||||||
|
const onClickDismiss = (key: React.ReactText) => () => {
|
||||||
|
if (notistackRef && notistackRef.current) notistackRef.current.closeSnackbar(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NotistackSnackbarProvider
|
||||||
|
// ref={notistackRef}
|
||||||
|
maxSnack={1}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: "bottom",
|
||||||
|
horizontal: "left"
|
||||||
|
}}
|
||||||
|
hideIconVariant
|
||||||
|
autoHideDuration={7000}
|
||||||
|
action={key => (
|
||||||
|
<Button
|
||||||
|
key={"snackbar-" + key}
|
||||||
|
size="small"
|
||||||
|
aria-label="close"
|
||||||
|
color="inherit"
|
||||||
|
onClick={onClickDismiss(key)}>
|
||||||
|
Dismiss
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
classes={{
|
||||||
|
containerAnchorOriginBottomLeft: classes.bottomLeft
|
||||||
|
}}>
|
||||||
|
<SnackbarHelper>{children}</SnackbarHelper>
|
||||||
|
</NotistackSnackbarProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSnackbar = () => useContext(SnackbarContext);
|
||||||
|
|
||||||
|
// Snackbar access for external sources that are not React components
|
||||||
|
export const openSnackbar = (status: Status, message: string) => {
|
||||||
|
if (openExternalSnack === undefined) {
|
||||||
|
console.warn("openSnackbar is undefined - most likely not loaded into state yet");
|
||||||
|
} else {
|
||||||
|
openExternalSnack(status, message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
export { HTTPContext, useHTTP } from "./http";
|
export { HTTPContext, useHTTP } from "./http";
|
||||||
export {
|
export {
|
||||||
// useBackpackAPI,
|
// useBackpackAPI,
|
||||||
// useBinAPI,
|
useBinAPI,
|
||||||
// useBinYardAPI,
|
// useBinYardAPI,
|
||||||
// useNoteAPI,
|
// useNoteAPI,
|
||||||
// useComponentAPI,
|
// useComponentAPI,
|
||||||
|
|
@ -22,11 +22,12 @@ export {
|
||||||
// useInteractionsAPI,
|
// useInteractionsAPI,
|
||||||
// useMeasurementsWebsocket,
|
// useMeasurementsWebsocket,
|
||||||
// useMetricAPI,
|
// useMetricAPI,
|
||||||
// usePermissionAPI,
|
usePermissionAPI,
|
||||||
// usePreferenceAPI,
|
// usePreferenceAPI,
|
||||||
// useSiteAPI,
|
// useSiteAPI,
|
||||||
// useTagAPI,
|
// useTagAPI,
|
||||||
// useTeamAPI,
|
useTeamAPI,
|
||||||
|
useImagekitAPI,
|
||||||
// useTaskAPI,
|
// useTaskAPI,
|
||||||
// useUsageAPI,
|
// useUsageAPI,
|
||||||
useUserAPI,
|
useUserAPI,
|
||||||
|
|
@ -35,7 +36,7 @@ export {
|
||||||
// useMineAPI,
|
// useMineAPI,
|
||||||
// useKeyManagerAPI,
|
// useKeyManagerAPI,
|
||||||
// useTerminalAPI,
|
// useTerminalAPI,
|
||||||
// useGateAPI,
|
useGateAPI,
|
||||||
// useObjectHeaterAPI,
|
// useObjectHeaterAPI,
|
||||||
// useMutationAPI,
|
// useMutationAPI,
|
||||||
// useGrainBagAPI,
|
// useGrainBagAPI,
|
||||||
|
|
@ -43,7 +44,7 @@ export {
|
||||||
// useFileControllerAPI
|
// useFileControllerAPI
|
||||||
} from "./pond/pond";
|
} from "./pond/pond";
|
||||||
// export { SecurityContext, useSecurity } from "./security";
|
// export { SecurityContext, useSecurity } from "./security";
|
||||||
// export { SnackbarContext, useSnackbar } from "./Snackbar";
|
export { SnackbarContext, useSnackbar } from "./Snackbar";
|
||||||
export * from "./StateContainer";
|
export * from "./StateContainer";
|
||||||
// export { TagsContext, TagsProvider, useTags } from "./tags";
|
// export { TagsContext, TagsProvider, useTags } from "./tags";
|
||||||
// export { UsersContext, UsersProvider, useUsers } from "./users";
|
// export { UsersContext, UsersProvider, useUsers } from "./users";
|
||||||
499
src/providers/pond/binAPI.tsx
Normal file
499
src/providers/pond/binAPI.tsx
Normal file
|
|
@ -0,0 +1,499 @@
|
||||||
|
import { useHTTP, usePermissionAPI } from "hooks";
|
||||||
|
import { createContext, PropsWithChildren, useContext } from "react";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { or } from "utils/types";
|
||||||
|
import { User, binScope } from "models";
|
||||||
|
import { pondURL } from "./pond";
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
import { dateRange } from "providers/http";
|
||||||
|
import { useGlobalState } from "providers";
|
||||||
|
import { quack } from "protobuf-ts/quack";
|
||||||
|
|
||||||
|
export interface IBinAPIContext {
|
||||||
|
addBin: (bin: pond.BinSettings) => Promise<AxiosResponse<pond.AddBinResponse>>;
|
||||||
|
updateBin: (key: string, bin: pond.BinSettings) => Promise<AxiosResponse<pond.UpdateBinResponse>>;
|
||||||
|
bulkBinUpdate: (bins: pond.BinSettings[]) => Promise<AxiosResponse<pond.BulkBinUpdateResponse>>;
|
||||||
|
updateBinStatus: (
|
||||||
|
key: string,
|
||||||
|
binStatus: pond.BinStatus
|
||||||
|
) => Promise<AxiosResponse<pond.UpdateBinResponse>>;
|
||||||
|
removeBin: (key: string) => Promise<AxiosResponse<pond.RemoveBinResponse>>;
|
||||||
|
getBin: (key: string) => Promise<AxiosResponse<pond.Bin>>;
|
||||||
|
addComponent: (
|
||||||
|
bin: string,
|
||||||
|
device: number,
|
||||||
|
component: string,
|
||||||
|
preferences?: pond.BinComponentPreferences
|
||||||
|
) => Promise<AxiosResponse<pond.AddBinComponentResponse>>;
|
||||||
|
removeComponent: (
|
||||||
|
bin: string,
|
||||||
|
component: string
|
||||||
|
) => Promise<AxiosResponse<pond.RemoveBinComponentResponse>>;
|
||||||
|
removeAllComponents: (bin: string) => Promise<AxiosResponse<pond.RemoveAllBinComponentsResponse>>;
|
||||||
|
updateComponentPreferences: (
|
||||||
|
bin: string,
|
||||||
|
component: string,
|
||||||
|
preferences: pond.BinComponentPreferences
|
||||||
|
) => Promise<AxiosResponse<pond.UpdateBinComponentPreferencesResponse>>;
|
||||||
|
getBinPageData: (binKey: string, userKey: string, showErrors?: boolean) => Promise<AxiosResponse>;
|
||||||
|
listBins: (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
orderBy?: string,
|
||||||
|
search?: string,
|
||||||
|
as?: string,
|
||||||
|
asRoot?: boolean,
|
||||||
|
numerical?: boolean,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[]
|
||||||
|
) => Promise<AxiosResponse<pond.ListBinsResponse>>;
|
||||||
|
listBinsAndData: (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
orderBy?: string,
|
||||||
|
search?: string,
|
||||||
|
as?: string,
|
||||||
|
asRoot?: boolean,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[]
|
||||||
|
) => Promise<AxiosResponse<pond.ListBinsAndDataResponse>>;
|
||||||
|
listHistory: (
|
||||||
|
id: string,
|
||||||
|
limit: number,
|
||||||
|
offset: number
|
||||||
|
) => Promise<AxiosResponse<pond.ListBinHistoryResponse>>;
|
||||||
|
listHistoryBetween: (
|
||||||
|
id: string,
|
||||||
|
limit: number,
|
||||||
|
start: string,
|
||||||
|
end: string
|
||||||
|
) => Promise<AxiosResponse<pond.ListBinHistoryResponse>>;
|
||||||
|
listBinStatus: (
|
||||||
|
key: string,
|
||||||
|
startDate: any,
|
||||||
|
endDate: any,
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
asRoot?: boolean
|
||||||
|
) => Promise<AxiosResponse<pond.ListBinStatusResponse>>;
|
||||||
|
listBinComponents: (key: string) => Promise<AxiosResponse<pond.ListBinComponentsResponse>>;
|
||||||
|
listBinComponentsMeasurements: (
|
||||||
|
key: string,
|
||||||
|
start: string,
|
||||||
|
end: string,
|
||||||
|
showErrors?: boolean
|
||||||
|
) => Promise<AxiosResponse<pond.ListBinComponentsMeasurementsResponse>>;
|
||||||
|
updateBinPermissions: (
|
||||||
|
key: string,
|
||||||
|
users: User[]
|
||||||
|
) => Promise<AxiosResponse<pond.UpdateBinResponse>>;
|
||||||
|
getBinMetrics: (search?: string) => Promise<AxiosResponse<pond.GetBinMetricsResponse>>;
|
||||||
|
listBinLiters: (
|
||||||
|
key: string,
|
||||||
|
start: string,
|
||||||
|
end: string,
|
||||||
|
moisture: number,
|
||||||
|
plenum: string,
|
||||||
|
cable: string,
|
||||||
|
pressure: string,
|
||||||
|
fan?: string // the fan controller is optional so that if they don't have one the fan is assumed on
|
||||||
|
) => Promise<AxiosResponse<pond.ListBinLiterResponse>>;
|
||||||
|
listBinPrefs: (key: string) => Promise<AxiosResponse<pond.ListBinComponentPreferencesResponse>>;
|
||||||
|
updateTopNodes: (
|
||||||
|
key: string,
|
||||||
|
newTopNodes: pond.NewTopNode[]
|
||||||
|
) => Promise<AxiosResponse<pond.UpdateTopNodesResponse>>;
|
||||||
|
listBinMeasurements: (
|
||||||
|
key: string,
|
||||||
|
startDate: any,
|
||||||
|
endDate: any,
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order: string,
|
||||||
|
measurementType?: number | quack.MeasurementType,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[],
|
||||||
|
showErrors?: boolean
|
||||||
|
) => Promise<AxiosResponse<pond.ListObjectMeasurementsResponse>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BinAPIContext = createContext<IBinAPIContext>({} as IBinAPIContext);
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
export default function DeviceProvider(props: PropsWithChildren<Props>) {
|
||||||
|
const { children } = props;
|
||||||
|
const { get, post, put, del } = useHTTP();
|
||||||
|
const permissionAPI = usePermissionAPI();
|
||||||
|
const [{ as }] = useGlobalState();
|
||||||
|
|
||||||
|
const addBin = (bin: pond.BinSettings) => {
|
||||||
|
if (as) return post<pond.AddBinResponse>(pondURL("/bins?as=" + as), bin);
|
||||||
|
return post<pond.AddBinResponse>(pondURL("/bins"), bin);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateBin = (key: string, bin: pond.BinSettings) => {
|
||||||
|
if (as) return put<pond.UpdateBinResponse>(pondURL("/bins/" + key + "?as=" + as), bin);
|
||||||
|
return put<pond.UpdateBinResponse>(pondURL("/bins/" + key), bin);
|
||||||
|
};
|
||||||
|
|
||||||
|
const bulkBinUpdate = (bins: pond.BinSettings[]) => {
|
||||||
|
if (as)
|
||||||
|
return put<pond.BulkBinUpdateResponse>(pondURL("/bulkBins/update?as=" + as), { bins: bins });
|
||||||
|
return put<pond.BulkBinUpdateResponse>(pondURL("/bulkBins/update"), { bins: bins });
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateBinStatus = (key: string, binStatus: pond.BinStatus) => {
|
||||||
|
if (as)
|
||||||
|
return put<pond.UpdateBinStatusResponse>(
|
||||||
|
pondURL("/bins/" + key + "/status?as=" + as),
|
||||||
|
binStatus
|
||||||
|
);
|
||||||
|
return put<pond.UpdateBinStatusResponse>(pondURL("/bins/" + key + "/status"), binStatus);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeBin = (key: string) => {
|
||||||
|
if (as) return del<pond.RemoveBinResponse>(pondURL("/bins/" + key + "?as=" + as));
|
||||||
|
return del<pond.RemoveBinResponse>(pondURL("/bins/" + key));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBin = (key: string) => {
|
||||||
|
if (as) return get<pond.Bin>(pondURL("/bins/" + key + "?as=" + as));
|
||||||
|
return get<pond.Bin>(pondURL("/bins/" + key));
|
||||||
|
};
|
||||||
|
|
||||||
|
const addComponent = (
|
||||||
|
bin: string,
|
||||||
|
device: number,
|
||||||
|
component: string,
|
||||||
|
preferences?: pond.BinComponentPreferences
|
||||||
|
) => {
|
||||||
|
let url = "/bins/" + bin + "/addComponent/" + device + "/" + component;
|
||||||
|
if (as) {
|
||||||
|
url =
|
||||||
|
url +
|
||||||
|
"?as=" +
|
||||||
|
as +
|
||||||
|
(preferences ? "&binPref=" + preferences.type + "&fillNode=" + preferences.node : "");
|
||||||
|
} else if (preferences) {
|
||||||
|
url = url + "?binPref=" + preferences.type + "&fillNode=" + preferences.node;
|
||||||
|
}
|
||||||
|
return post<pond.AddBinComponentResponse>(pondURL(url));
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeComponent = (bin: string, component: string) => {
|
||||||
|
let url = "/bins/" + bin + "/removeComponent/" + component;
|
||||||
|
if (as) url = url + "?as=" + as;
|
||||||
|
return post<pond.RemoveBinComponentResponse>(pondURL(url));
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeAllComponents = (bin: string) => {
|
||||||
|
let url = "/bins/" + bin + "/removeAllComponents";
|
||||||
|
if (as) url = url + "?as=" + as;
|
||||||
|
return post<pond.RemoveAllBinComponentsResponse>(pondURL(url));
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateComponentPreferences = (
|
||||||
|
bin: string,
|
||||||
|
component: string,
|
||||||
|
preferences: pond.BinComponentPreferences
|
||||||
|
) => {
|
||||||
|
let url = "/bins/" + bin + "/updateComponent/" + component + "/preferences";
|
||||||
|
if (as) url = url + "/?as=" + as;
|
||||||
|
interface request {
|
||||||
|
preferences: Object;
|
||||||
|
}
|
||||||
|
let body: request = { preferences: pond.BinComponentPreferences.toObject(preferences) };
|
||||||
|
return put<pond.UpdateBinComponentPreferencesResponse>(pondURL(url), body);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBinPageData = (binKey: string, userKey: string, showErrors = true) => {
|
||||||
|
return get(
|
||||||
|
pondURL(
|
||||||
|
"/bins/" +
|
||||||
|
binKey +
|
||||||
|
"/users/" +
|
||||||
|
userKey +
|
||||||
|
"?as=" +
|
||||||
|
as +
|
||||||
|
(showErrors ? "&showErrors=true" : "&showErrors=false")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listBins = (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
orderBy?: string,
|
||||||
|
search?: string,
|
||||||
|
as?: string,
|
||||||
|
asRoot?: boolean,
|
||||||
|
numerical?: boolean,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[]
|
||||||
|
) => {
|
||||||
|
return get<pond.ListBinsResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/bins" +
|
||||||
|
"?limit=" +
|
||||||
|
limit +
|
||||||
|
"&offset=" +
|
||||||
|
offset +
|
||||||
|
("&order=" + or(order, "asc")) +
|
||||||
|
("&by=" + or(orderBy, "key")) +
|
||||||
|
(numerical ? "&numerical=true" : "") +
|
||||||
|
(as ? "&as=" + as : "") +
|
||||||
|
(asRoot ? "&asRoot=" + asRoot.toString() : "") +
|
||||||
|
(search ? "&search=" + search : "") +
|
||||||
|
(keys ? "&keys=" + keys.join(",") : "") +
|
||||||
|
(types ? "&types=" + types.join(",") : "")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listBinsAndData = (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
orderBy?: string,
|
||||||
|
search?: string,
|
||||||
|
as?: string,
|
||||||
|
asRoot?: boolean,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[]
|
||||||
|
) => {
|
||||||
|
return get<pond.ListBinsAndDataResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/binsAndData" +
|
||||||
|
"?limit=" +
|
||||||
|
limit +
|
||||||
|
"&offset=" +
|
||||||
|
offset +
|
||||||
|
("&order=" + or(order, "asc")) +
|
||||||
|
("&by=" + or(orderBy, "key")) +
|
||||||
|
(as ? "&as=" + as : "") +
|
||||||
|
(asRoot ? "&asRoot=" + asRoot.toString() : "") +
|
||||||
|
(search ? "&search=" + search : "") +
|
||||||
|
(keys ? "&keys=" + keys.join(",") : "") +
|
||||||
|
(types ? "&types=" + types.join(",") : "")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listBinStatus = (
|
||||||
|
key: string,
|
||||||
|
startDate: any,
|
||||||
|
endDate: any,
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
asRoot?: boolean
|
||||||
|
) => {
|
||||||
|
return get<pond.ListBinStatusResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/bins/" +
|
||||||
|
key +
|
||||||
|
"/status" +
|
||||||
|
dateRange(startDate, endDate) +
|
||||||
|
"?limit=" +
|
||||||
|
limit +
|
||||||
|
"&offset=" +
|
||||||
|
offset +
|
||||||
|
("&order=" + or(order, "asc")) +
|
||||||
|
(asRoot ? "&asRoot=" + asRoot.toString() : "")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listBinComponents = (key: string) => {
|
||||||
|
return get<pond.ListBinComponentsResponse>(pondURL("/bins/" + key + "/components"));
|
||||||
|
};
|
||||||
|
|
||||||
|
const listBinComponentsMeasurements = (
|
||||||
|
key: string,
|
||||||
|
start: string,
|
||||||
|
end: string,
|
||||||
|
showErrors = false
|
||||||
|
) => {
|
||||||
|
return get<pond.ListBinComponentsMeasurementsResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/bins/" +
|
||||||
|
key +
|
||||||
|
"/components/measurements?start=" +
|
||||||
|
start +
|
||||||
|
"&end=" +
|
||||||
|
end +
|
||||||
|
"&as=" +
|
||||||
|
as +
|
||||||
|
(showErrors ? "&showErrors=true" : "&showErrors=false")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listHistory = (id: string, limit: number, offset: number) => {
|
||||||
|
return get<pond.ListBinHistoryResponse>(
|
||||||
|
pondURL("/bins/" + id + "/history?limit=" + limit + "&offset=" + offset)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listHistoryBetween = (id: string, limit: number, start: string, end: string) => {
|
||||||
|
return get<pond.ListBinHistoryResponse>(
|
||||||
|
pondURL("/bins/" + id + "/historyBetween?limit=" + limit + "&start=" + start + "&end=" + end)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateBinPermissions = (key: string, users: User[]) => {
|
||||||
|
return permissionAPI.updatePermissions(binScope(key.toString()), users);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBinMetrics = (search?: string) => {
|
||||||
|
if (as) {
|
||||||
|
if (search)
|
||||||
|
return get<pond.GetBinMetricsResponse>(
|
||||||
|
pondURL("/metrics/bins/?search=" + search + "&as=" + as)
|
||||||
|
);
|
||||||
|
return get<pond.GetBinMetricsResponse>(pondURL("/metrics/bins/?as=" + as));
|
||||||
|
}
|
||||||
|
return get<pond.GetBinMetricsResponse>(
|
||||||
|
pondURL("/metrics/bins" + (search ? "?search=" + search : ""))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listBinLiters = (
|
||||||
|
key: string,
|
||||||
|
start: string,
|
||||||
|
end: string,
|
||||||
|
moisture: number,
|
||||||
|
plenum: string,
|
||||||
|
cable: string,
|
||||||
|
pressure: string,
|
||||||
|
fan?: string
|
||||||
|
) => {
|
||||||
|
if (as) {
|
||||||
|
return get<pond.ListBinLiterResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/bins/" +
|
||||||
|
key +
|
||||||
|
"/liters?start=" +
|
||||||
|
start +
|
||||||
|
"&end=" +
|
||||||
|
end +
|
||||||
|
"&moisture=" +
|
||||||
|
moisture +
|
||||||
|
"&plenum=" +
|
||||||
|
plenum +
|
||||||
|
"&cable=" +
|
||||||
|
cable +
|
||||||
|
"&pressure=" +
|
||||||
|
pressure +
|
||||||
|
(fan ? "&fan=" + fan : "") +
|
||||||
|
"&as=" +
|
||||||
|
as
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return get<pond.ListBinLiterResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/bins/" +
|
||||||
|
key +
|
||||||
|
"/liters?start=" +
|
||||||
|
start +
|
||||||
|
"&end=" +
|
||||||
|
end +
|
||||||
|
"&moisture=" +
|
||||||
|
moisture +
|
||||||
|
"&plenum=" +
|
||||||
|
plenum +
|
||||||
|
"&cable=" +
|
||||||
|
cable +
|
||||||
|
"&pressure=" +
|
||||||
|
pressure +
|
||||||
|
(fan ? "&fan=" + fan : "")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listBinPrefs = (key: string) => {
|
||||||
|
return get<pond.ListBinComponentPreferencesResponse>(
|
||||||
|
pondURL("/bins/" + key + "/componentPreferences?as=" + as)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateTopNodes = (key: string, newTopNodes: pond.NewTopNode[]) => {
|
||||||
|
let body: pond.TopNodeList = pond.TopNodeList.create({ newTopNodes: newTopNodes });
|
||||||
|
return post<pond.UpdateTopNodesResponse>(
|
||||||
|
pondURL("/bins/" + key + "/updateTopNodes" + (as ? "?as=" + as : "")),
|
||||||
|
body
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listBinMeasurements = (
|
||||||
|
key: string,
|
||||||
|
startDate: any,
|
||||||
|
endDate: any,
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order: string,
|
||||||
|
measurementType?: number | quack.MeasurementType,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[],
|
||||||
|
showErrors?: boolean
|
||||||
|
) => {
|
||||||
|
const url = pondURL(
|
||||||
|
"/objects/" +
|
||||||
|
key +
|
||||||
|
"/1/unitMeasurements" +
|
||||||
|
dateRange(startDate, endDate) +
|
||||||
|
"&limit=" +
|
||||||
|
limit +
|
||||||
|
"&offset=" +
|
||||||
|
offset +
|
||||||
|
"&order=" +
|
||||||
|
order +
|
||||||
|
(measurementType ? "&type=" + measurementType : "") +
|
||||||
|
(as ? "&as=" + as : "") +
|
||||||
|
(keys ? "&keys=" + keys : "") +
|
||||||
|
(types ? "&types=" + types : "") +
|
||||||
|
(showErrors ? "&showErrors=true" : "&showErrors=false")
|
||||||
|
);
|
||||||
|
return get<pond.ListObjectMeasurementsResponse>(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BinAPIContext.Provider
|
||||||
|
value={{
|
||||||
|
addBin,
|
||||||
|
updateBin,
|
||||||
|
bulkBinUpdate,
|
||||||
|
updateBinStatus,
|
||||||
|
removeBin,
|
||||||
|
listBinComponents,
|
||||||
|
listBinComponentsMeasurements,
|
||||||
|
listBinStatus,
|
||||||
|
getBin,
|
||||||
|
addComponent,
|
||||||
|
removeComponent,
|
||||||
|
removeAllComponents,
|
||||||
|
updateComponentPreferences,
|
||||||
|
getBinPageData,
|
||||||
|
listBins,
|
||||||
|
listBinsAndData,
|
||||||
|
listHistory,
|
||||||
|
updateBinPermissions,
|
||||||
|
getBinMetrics,
|
||||||
|
listBinLiters,
|
||||||
|
listHistoryBetween,
|
||||||
|
listBinPrefs,
|
||||||
|
updateTopNodes,
|
||||||
|
listBinMeasurements
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</BinAPIContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useBinAPI = () => useContext(BinAPIContext);
|
||||||
267
src/providers/pond/gateAPI.tsx
Normal file
267
src/providers/pond/gateAPI.tsx
Normal file
|
|
@ -0,0 +1,267 @@
|
||||||
|
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 "providers";
|
||||||
|
|
||||||
|
export interface IGateInterface {
|
||||||
|
addGate: (
|
||||||
|
name: string,
|
||||||
|
settings: pond.GateSettings
|
||||||
|
) => Promise<AxiosResponse<pond.AddGateResponse>>;
|
||||||
|
listGates: (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
orderBy?: string,
|
||||||
|
search?: string,
|
||||||
|
pcaStatus?: boolean,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[],
|
||||||
|
numerical?: boolean,
|
||||||
|
specificUser?: string
|
||||||
|
) => Promise<AxiosResponse<pond.ListGatesResponse>>;
|
||||||
|
updateGate: (
|
||||||
|
key: string,
|
||||||
|
name: string,
|
||||||
|
settings: pond.GateSettings
|
||||||
|
) => Promise<AxiosResponse<pond.UpdateGateResponse>>;
|
||||||
|
getGate: (key: string) => Promise<AxiosResponse<pond.GetGateResponse>>;
|
||||||
|
removeGate: (key: string) => Promise<AxiosResponse<pond.RemoveGateResponse>>;
|
||||||
|
updateLink: (
|
||||||
|
parentKey: string,
|
||||||
|
parentType: string,
|
||||||
|
objectID: string,
|
||||||
|
objectType: string,
|
||||||
|
permissions: string[]
|
||||||
|
) => Promise<any>;
|
||||||
|
updatePrefs: (
|
||||||
|
gateKey: string,
|
||||||
|
childType: string,
|
||||||
|
childKey: string,
|
||||||
|
gatePref: pond.GateComponentType | pond.GateDeviceType,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[]
|
||||||
|
) => Promise<AxiosResponse<pond.UpdateGatePreferencesResponse>>;
|
||||||
|
getGatePageData: (key: string) => Promise<AxiosResponse<pond.GetGatePageDataResponse>>;
|
||||||
|
listGateAirflow: (
|
||||||
|
gate: string,
|
||||||
|
device: number | string,
|
||||||
|
ambientComponent: string,
|
||||||
|
pressureComponent: string,
|
||||||
|
start: string,
|
||||||
|
end: string,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[]
|
||||||
|
) => Promise<AxiosResponse<pond.ListGateAirflowResponse>>;
|
||||||
|
listGateMeasurements: (
|
||||||
|
key: string,
|
||||||
|
start: string,
|
||||||
|
end: string
|
||||||
|
) => Promise<AxiosResponse<pond.ListGateMeasurementsResponse>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GateAPIcontext = createContext<IGateInterface>({} as IGateInterface);
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
export default function GateProvider(props: PropsWithChildren<Props>) {
|
||||||
|
const { children } = props;
|
||||||
|
const { get, del, post, put } = useHTTP();
|
||||||
|
const [{ as }] = useGlobalState();
|
||||||
|
|
||||||
|
const addGate = (name: string, settings: pond.GateSettings) => {
|
||||||
|
if (as) {
|
||||||
|
return post<pond.AddGateResponse>(pondURL("/gates?name=" + name + "&as=" + as), settings);
|
||||||
|
}
|
||||||
|
return post<pond.AddGateResponse>(pondURL("/gates?name=" + name + "&as=" + as), settings);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listGates = (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
orderBy?: string,
|
||||||
|
search?: string,
|
||||||
|
pcaStatus?: boolean,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[],
|
||||||
|
numerical?: boolean,
|
||||||
|
specificUser?: string
|
||||||
|
) => {
|
||||||
|
let asText = "";
|
||||||
|
if (as) asText = "&as=" + as;
|
||||||
|
if (specificUser) asText = "&as=" + specificUser;
|
||||||
|
return get<pond.ListGatesResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/gates?limit=" +
|
||||||
|
limit +
|
||||||
|
"&offset=" +
|
||||||
|
offset +
|
||||||
|
("&order=" + (order ? order : "asc")) +
|
||||||
|
("&by=" + (orderBy ? orderBy : "key")) +
|
||||||
|
(numerical ? "&numerical=true" : "") +
|
||||||
|
(search ? "&search=" + search : "") +
|
||||||
|
(pcaStatus ? "&getPCA=" + pcaStatus.toString() : "") +
|
||||||
|
(keys ? "&keys=" + keys.toString() : "") +
|
||||||
|
(types ? "&types=" + types.toString() : "") +
|
||||||
|
asText
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateGate = (key: string, name: string, settings: pond.GateSettings) => {
|
||||||
|
if (as) {
|
||||||
|
return put<pond.UpdateGateResponse>(
|
||||||
|
pondURL("/gates/" + key + "?as=" + as + "&name=" + name),
|
||||||
|
settings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return put<pond.UpdateGateResponse>(pondURL("/gates/" + key + "?name=" + name), settings);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeGate = (key: string) => {
|
||||||
|
if (as) {
|
||||||
|
return del<pond.RemoveGateResponse>(pondURL("/gates/" + key + "?as=" + as));
|
||||||
|
}
|
||||||
|
return del<pond.RemoveGateResponse>(pondURL("/gates/" + key + "?as=" + as));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGate = (key: string) => {
|
||||||
|
if (as) {
|
||||||
|
return get<pond.GetGateResponse>(pondURL("/gates/" + key + "?as=" + as));
|
||||||
|
}
|
||||||
|
return get<pond.GetGateResponse>(pondURL("/gates/" + key));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGatePageData = (key: string) => {
|
||||||
|
if (as) {
|
||||||
|
return get<pond.GetGatePageDataResponse>(pondURL("/gatePage/" + key + "?as=" + as));
|
||||||
|
}
|
||||||
|
return get<pond.GetGatePageDataResponse>(pondURL("/gatePage/" + key));
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateLink = (
|
||||||
|
parentID: string,
|
||||||
|
parentType: string,
|
||||||
|
objectID: string,
|
||||||
|
objectType: string,
|
||||||
|
permissions: string[]
|
||||||
|
) => {
|
||||||
|
if (as)
|
||||||
|
return post(pondURL(`/gates/` + parentID + `/link?as=${as}`), {
|
||||||
|
Key: objectID,
|
||||||
|
Type: objectType,
|
||||||
|
Parent: parentID,
|
||||||
|
ParentType: parentType,
|
||||||
|
Permissions: permissions
|
||||||
|
});
|
||||||
|
return post(pondURL(`/gates/` + parentID + `/link`), {
|
||||||
|
Key: objectID,
|
||||||
|
Type: objectType,
|
||||||
|
Parent: parentID,
|
||||||
|
ParentType: parentType,
|
||||||
|
Permissions: permissions
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatePrefs = (
|
||||||
|
gateKey: string,
|
||||||
|
childType: string,
|
||||||
|
childKey: string,
|
||||||
|
gatePref: pond.GateComponentType | pond.GateDeviceType,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[]
|
||||||
|
) => {
|
||||||
|
if (as) {
|
||||||
|
return put<pond.UpdateGatePreferencesResponse>(
|
||||||
|
pondURL(
|
||||||
|
`/gatePrefs/` +
|
||||||
|
gateKey +
|
||||||
|
"?childType=" +
|
||||||
|
childType +
|
||||||
|
"&childKey=" +
|
||||||
|
childKey +
|
||||||
|
"&gatePreference=" +
|
||||||
|
gatePref +
|
||||||
|
`&as=` +
|
||||||
|
as +
|
||||||
|
(keys ? "&keys=" + keys.toString() : "") +
|
||||||
|
(types ? "&types=" + types.toString() : "")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return put<pond.UpdateGatePreferencesResponse>(
|
||||||
|
pondURL(
|
||||||
|
`/gatePrefs/` +
|
||||||
|
gateKey +
|
||||||
|
"?childType=" +
|
||||||
|
childType +
|
||||||
|
"&childKey=" +
|
||||||
|
childKey +
|
||||||
|
"&gatePreference=" +
|
||||||
|
gatePref +
|
||||||
|
(keys ? "&keys=" + keys.toString() : "") +
|
||||||
|
(types ? "&types=" + types.toString() : "")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listGateAirflow = (
|
||||||
|
gate: string,
|
||||||
|
device: number | string,
|
||||||
|
ambientComponent: string,
|
||||||
|
pressureComponent: string,
|
||||||
|
start: string,
|
||||||
|
end: string,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[]
|
||||||
|
) => {
|
||||||
|
return get<pond.ListGateAirflowResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/gates/" +
|
||||||
|
gate +
|
||||||
|
"/airflow?device=" +
|
||||||
|
device +
|
||||||
|
"&ambient=" +
|
||||||
|
ambientComponent +
|
||||||
|
"&pressure=" +
|
||||||
|
pressureComponent +
|
||||||
|
"&start=" +
|
||||||
|
start +
|
||||||
|
"&end=" +
|
||||||
|
end +
|
||||||
|
(as ? "&as=" + as : "") +
|
||||||
|
(keys ? "&keys=" + keys.toString() : "") +
|
||||||
|
(types ? "&types=" + types.toString() : "")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listGateMeasurements = (key: string, start: string, end: string) => {
|
||||||
|
return get<pond.ListGateMeasurementsResponse>(
|
||||||
|
pondURL("/gates/" + key + "/measurements?start=" + start + "&end=" + end + "&as=" + as)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GateAPIcontext.Provider
|
||||||
|
value={{
|
||||||
|
addGate,
|
||||||
|
listGates,
|
||||||
|
updateGate,
|
||||||
|
removeGate,
|
||||||
|
getGate,
|
||||||
|
updateLink,
|
||||||
|
getGatePageData,
|
||||||
|
updatePrefs,
|
||||||
|
listGateAirflow,
|
||||||
|
listGateMeasurements
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</GateAPIcontext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGateAPI = () => useContext(GateAPIcontext);
|
||||||
45
src/providers/pond/imagekitAPI.tsx
Normal file
45
src/providers/pond/imagekitAPI.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { useHTTP } from "hooks";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useGlobalState } from "providers";
|
||||||
|
import { createContext, PropsWithChildren, useContext } from "react";
|
||||||
|
import { pondURL } from "./pond";
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
|
||||||
|
export interface IImagekitAPIContext {
|
||||||
|
uploadImage: (
|
||||||
|
filename: string,
|
||||||
|
file: any,
|
||||||
|
filepath?: string
|
||||||
|
) => Promise<AxiosResponse<pond.UploadImageResponse>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ImagekitAPIContext = createContext<IImagekitAPIContext>({} as IImagekitAPIContext);
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
export default function ImagekitProvider(props: PropsWithChildren<Props>) {
|
||||||
|
const { children } = props;
|
||||||
|
const { post } = useHTTP();
|
||||||
|
const [{ as }] = useGlobalState();
|
||||||
|
|
||||||
|
const uploadImage = (filename: string, file: any, filepath = "/") => {
|
||||||
|
let request = new pond.UploadImageRequest();
|
||||||
|
request.file = file;
|
||||||
|
request.fileName = filename;
|
||||||
|
request.filePath = filepath;
|
||||||
|
|
||||||
|
if (as) return post<pond.UploadImageResponse>(pondURL("/images/upload?as=" + as), request);
|
||||||
|
return post<pond.UploadImageResponse>(pondURL("/images/upload"), request);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ImagekitAPIContext.Provider
|
||||||
|
value={{
|
||||||
|
uploadImage
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</ImagekitAPIContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useImagekitAPI = () => useContext(ImagekitAPIContext);
|
||||||
153
src/providers/pond/permissionAPI.tsx
Normal file
153
src/providers/pond/permissionAPI.tsx
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
import { useHTTP } from "hooks";
|
||||||
|
import { Scope, Team, User } from "models";
|
||||||
|
import { permissionToString } from "pbHelpers/Permission";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useGlobalState } from "providers";
|
||||||
|
import { createContext, PropsWithChildren, useContext } from "react";
|
||||||
|
import { objectQueryParams, pondURL } from "./pond";
|
||||||
|
|
||||||
|
export interface IPermissionAPIContext {
|
||||||
|
getPermissions: (user: string, scope: Scope) => Promise<AxiosResponse<any>>;
|
||||||
|
removePermissions: (user: string, scope: Scope) => Promise<AxiosResponse<any>>;
|
||||||
|
updatePermissions: (scope: Scope, users: User[] | Team[]) => Promise<AxiosResponse<any>>;
|
||||||
|
updateRelativePermissions: (
|
||||||
|
parentScope: Scope,
|
||||||
|
childScope: Scope,
|
||||||
|
permissions: pond.Permission[]
|
||||||
|
) => Promise<AxiosResponse<any>>;
|
||||||
|
shareObject: (
|
||||||
|
scope: Scope,
|
||||||
|
email: string,
|
||||||
|
permissions: pond.Permission[],
|
||||||
|
useImitation?: boolean
|
||||||
|
) => Promise<AxiosResponse<any>>;
|
||||||
|
shareObjectByKey: (
|
||||||
|
scope: Scope,
|
||||||
|
key: string,
|
||||||
|
permissions: pond.Permission[]
|
||||||
|
) => Promise<AxiosResponse<any>>;
|
||||||
|
addShareableLink: (scope: Scope, expiration: string) => Promise<AxiosResponse<any>>;
|
||||||
|
removeShareableLink: (scope: Scope, code: string) => Promise<AxiosResponse<any>>;
|
||||||
|
listShareableLinks: (scope: Scope) => Promise<AxiosResponse<any>>;
|
||||||
|
getShareableLink: (scope: Scope, code: string) => Promise<AxiosResponse<any>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PermissionAPIContext = createContext<IPermissionAPIContext>(
|
||||||
|
{} as IPermissionAPIContext
|
||||||
|
);
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
export default function PermissionProvider(props: PropsWithChildren<Props>) {
|
||||||
|
const { children } = props;
|
||||||
|
const { get, del, put, post } = useHTTP();
|
||||||
|
const [{ as }] = useGlobalState();
|
||||||
|
|
||||||
|
const getPermissions = (user: string, scope: Scope) => {
|
||||||
|
return get(pondURL("/users/" + user + "/permissions" + objectQueryParams(scope)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const removePermissions = (user: string, scope: Scope) => {
|
||||||
|
return del(pondURL("/users/" + user + "/permissions" + objectQueryParams(scope)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatePermissions = (scope: Scope, users: User[] | Team[]) => {
|
||||||
|
interface user {
|
||||||
|
id: string;
|
||||||
|
permissions: string[];
|
||||||
|
}
|
||||||
|
interface request {
|
||||||
|
users: user[];
|
||||||
|
}
|
||||||
|
let body: request = { users: [] };
|
||||||
|
users.forEach((user: User | Team) => {
|
||||||
|
body.users.push({
|
||||||
|
id: user.id(),
|
||||||
|
permissions: user.permissions.map(permission => permissionToString(permission))
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return put(pondURL("/" + scope.kind + "s/" + scope.key + "/permissions"), body);
|
||||||
|
};
|
||||||
|
|
||||||
|
const shareObject = (
|
||||||
|
scope: Scope,
|
||||||
|
email: string,
|
||||||
|
permissions: pond.Permission[],
|
||||||
|
useImitation = true
|
||||||
|
) => {
|
||||||
|
let sql = "/" + scope.kind + "s/" + scope.key + "/share";
|
||||||
|
if (as && useImitation) sql = sql + "?as=" + as;
|
||||||
|
return post(pondURL(sql), {
|
||||||
|
email: email,
|
||||||
|
permissions: permissions.map(permission => permissionToString(permission))
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateRelativePermissions = (
|
||||||
|
parentScope: Scope,
|
||||||
|
childScope: Scope,
|
||||||
|
permissions: pond.Permission[]
|
||||||
|
) => {
|
||||||
|
let sql = "/updateRelativePermissions";
|
||||||
|
if (as) sql = sql + "?as=" + as;
|
||||||
|
return put(pondURL(sql), {
|
||||||
|
parent: parentScope.key,
|
||||||
|
parentType: parentScope.kind,
|
||||||
|
child: childScope.key,
|
||||||
|
childType: childScope.kind,
|
||||||
|
permissions: permissions.map(permission => permissionToString(permission))
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const shareObjectByKey = (scope: Scope, key: string, permissions: pond.Permission[]) => {
|
||||||
|
if (as)
|
||||||
|
return post(pondURL(`/${scope.kind}s/${scope.key}/shareByKey?as=${as}`), {
|
||||||
|
key: key,
|
||||||
|
permissions: permissions.map(permission => permissionToString(permission))
|
||||||
|
});
|
||||||
|
return post(pondURL("/" + scope.kind + "s/" + scope.key + "/shareByKey"), {
|
||||||
|
key: key,
|
||||||
|
permissions: permissions.map(permission => permissionToString(permission))
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const addShareableLink = (scope: Scope, expiration: string) => {
|
||||||
|
let shareBody: pond.ShareableLinkSettings = pond.ShareableLinkSettings.create({
|
||||||
|
expiration: expiration
|
||||||
|
});
|
||||||
|
return post(pondURL("/" + scope.kind + "s/" + scope.key + "/links"), shareBody);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeShareableLink = (scope: Scope, code: string) => {
|
||||||
|
return del(pondURL("/" + scope.kind + "s/" + scope.key + "/links/" + code));
|
||||||
|
};
|
||||||
|
|
||||||
|
const listShareableLinks = (scope: Scope) => {
|
||||||
|
return get(pondURL("/" + scope.kind + "s/" + scope.key + "/links"));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getShareableLink = (scope: Scope, code: string) => {
|
||||||
|
return get(pondURL("/" + scope.kind + "s/" + scope.key + "/links/" + code));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PermissionAPIContext.Provider
|
||||||
|
value={{
|
||||||
|
getPermissions,
|
||||||
|
removePermissions,
|
||||||
|
updatePermissions,
|
||||||
|
updateRelativePermissions,
|
||||||
|
shareObject,
|
||||||
|
shareObjectByKey,
|
||||||
|
addShareableLink,
|
||||||
|
removeShareableLink,
|
||||||
|
listShareableLinks,
|
||||||
|
getShareableLink
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</PermissionAPIContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usePermissionAPI = () => useContext(PermissionAPIContext);
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
import { PropsWithChildren } from "react";
|
import { PropsWithChildren } from "react";
|
||||||
import UserProvider, { useUserAPI } from "./userAPI";
|
import UserProvider, { useUserAPI } from "./userAPI";
|
||||||
|
import TeamProvider, { useTeamAPI } from "./teamAPI";
|
||||||
|
import { useImagekitAPI } from "./imagekitAPI";
|
||||||
|
import { usePermissionAPI } from "./permissionAPI";
|
||||||
|
import { Scope } from "models";
|
||||||
|
import { useBinAPI } from "./binAPI";
|
||||||
|
import { useGateAPI } from "./gateAPI";
|
||||||
|
|
||||||
export const pondURL = (partial: string, demo: boolean = false): string => {
|
export const pondURL = (partial: string, demo: boolean = false): string => {
|
||||||
let url = import.meta.env.VITE_APP_API_URL + (demo ? "/demo" : "") + partial;
|
let url = import.meta.env.VITE_APP_API_URL + (demo ? "/demo" : "") + partial;
|
||||||
|
|
@ -8,20 +14,27 @@ export const pondURL = (partial: string, demo: boolean = false): string => {
|
||||||
return url;
|
return url;
|
||||||
};
|
};
|
||||||
|
|
||||||
// export const objectQueryParams = (scope: Scope) => {
|
export const objectQueryParams = (scope: Scope) => {
|
||||||
// return "?" + scope.kind + "_id=" + scope.key;
|
return "?" + scope.kind + "_id=" + scope.key;
|
||||||
// };
|
};
|
||||||
|
|
||||||
export default function PondProvider(props: PropsWithChildren<any>) {
|
export default function PondProvider(props: PropsWithChildren<any>) {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserProvider>
|
<UserProvider>
|
||||||
{children}
|
<TeamProvider>
|
||||||
|
{children}
|
||||||
|
</TeamProvider>
|
||||||
</UserProvider>
|
</UserProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
useUserAPI,
|
useUserAPI,
|
||||||
|
useTeamAPI,
|
||||||
|
useImagekitAPI,
|
||||||
|
usePermissionAPI,
|
||||||
|
useBinAPI,
|
||||||
|
useGateAPI
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -73,8 +73,8 @@ export default function TeamProvider(props: PropsWithChildren<Props>) {
|
||||||
const updatePreferences = (
|
const updatePreferences = (
|
||||||
key: string,
|
key: string,
|
||||||
preferences: pond.TeamPreferences,
|
preferences: pond.TeamPreferences,
|
||||||
keys?: string[],
|
// keys?: string[],
|
||||||
types?: string[]
|
// types?: string[]
|
||||||
) => {
|
) => {
|
||||||
return put(pondURL("/teams/" + key + "/preferences"), preferences);
|
return put(pondURL("/teams/" + key + "/preferences"), preferences);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,15 @@
|
||||||
// import { Scope, User } from "models";
|
// import { Scope, User } from "models";
|
||||||
// import { pond } from "protobuf-ts/pond";
|
// import { pond } from "protobuf-ts/pond";
|
||||||
import { createContext, PropsWithChildren, useContext } from "react";
|
import { createContext, PropsWithChildren, useContext } from "react";
|
||||||
// import { objectQueryParams, pondURL } from "./pond";
|
import { objectQueryParams, pondURL } from "./pond";
|
||||||
// import { or } from "utils";
|
// import { or } from "utils";
|
||||||
// import { useGlobalState } from "providers";
|
import { useGlobalState } from "providers";
|
||||||
// import { AxiosResponse } from "axios";
|
// import { AxiosResponse } from "axios";
|
||||||
import { useHTTP } from "../http";
|
import { useHTTP } from "../http";
|
||||||
import { pondURL } from "./pond";
|
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import { User } from "../../models/user";
|
import { User } from "../../models/user";
|
||||||
|
import { Scope } from "models";
|
||||||
|
|
||||||
// export interface ListUsersResponse {
|
// export interface ListUsersResponse {
|
||||||
// users: User[];
|
// users: User[];
|
||||||
|
|
@ -26,9 +26,9 @@ export interface IUserAPIContext {
|
||||||
// search?: string
|
// search?: string
|
||||||
// ) => Promise<ListUsersResponse>;
|
// ) => Promise<ListUsersResponse>;
|
||||||
// listProfiles: () => Promise<pond.UserProfile[]>;
|
// listProfiles: () => Promise<pond.UserProfile[]>;
|
||||||
// listObjectUsers: (scope: Scope) => Promise<any>;
|
listObjectUsers: (scope: Scope) => Promise<any>;
|
||||||
// updateObjectUsers: (scope: Scope, users: pond.IUser[]) => Promise<any>;
|
updateObjectUsers: (scope: Scope, users: pond.IUser[]) => Promise<any>;
|
||||||
getUser: (id: string) => Promise<User>;
|
getUser: (id: string, scope?: Scope) => Promise<User>;
|
||||||
getUserWithTeam: (
|
getUserWithTeam: (
|
||||||
id: string,
|
id: string,
|
||||||
team?: string,
|
team?: string,
|
||||||
|
|
@ -53,8 +53,8 @@ export const UserAPIContext = createContext<IUserAPIContext>({} as IUserAPIConte
|
||||||
|
|
||||||
export default function UserProvider(props: PropsWithChildren<any>) {
|
export default function UserProvider(props: PropsWithChildren<any>) {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
const { get, /*put*/ } = useHTTP();
|
const { get, put } = useHTTP();
|
||||||
// const [{ as }] = useGlobalState();
|
const [{ as }] = useGlobalState();
|
||||||
|
|
||||||
// const listUsers = (
|
// const listUsers = (
|
||||||
// limit: number,
|
// limit: number,
|
||||||
|
|
@ -104,20 +104,20 @@ export default function UserProvider(props: PropsWithChildren<any>) {
|
||||||
// });
|
// });
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// const listObjectUsers = (scope: Scope) => {
|
const listObjectUsers = (scope: Scope) => {
|
||||||
// let sql = "/" + scope.kind + "s/" + scope.key + "/users";
|
let sql = "/" + scope.kind + "s/" + scope.key + "/users";
|
||||||
// if (as) sql = sql + "?as=" + as;
|
if (as) sql = sql + "?as=" + as;
|
||||||
// return get(pondURL(sql));
|
return get(pondURL(sql));
|
||||||
// };
|
};
|
||||||
|
|
||||||
// const updateObjectUsers = (scope: Scope, users: pond.IUser[]) => {
|
const updateObjectUsers = (scope: Scope, users: pond.IUser[]) => {
|
||||||
// return put(pondURL("/users" + objectQueryParams(scope)), { users });
|
return put(pondURL("/users" + objectQueryParams(scope)), { users });
|
||||||
// };
|
};
|
||||||
|
|
||||||
// const getUser = (id: string): Promise<User> => {
|
// const getUser = (id: string): Promise<User> => {
|
||||||
const getUser = (id: string): Promise<any> => {
|
const getUser = (id: string, scope?: Scope): Promise<any> => {
|
||||||
let partial = "/users/" + id;
|
let partial = "/users/" + id;
|
||||||
// if (scope) partial += "?kind=" + scope.kind + "&key=" + scope.key;
|
if (scope) partial += "?kind=" + scope.kind + "&key=" + scope.key;
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
get(pondURL(partial))
|
get(pondURL(partial))
|
||||||
.then((response: any) => resolve(response.data))
|
.then((response: any) => resolve(response.data))
|
||||||
|
|
@ -218,8 +218,8 @@ export default function UserProvider(props: PropsWithChildren<any>) {
|
||||||
value={{
|
value={{
|
||||||
// listUsers,
|
// listUsers,
|
||||||
// listProfiles,
|
// listProfiles,
|
||||||
// listObjectUsers,
|
listObjectUsers,
|
||||||
// updateObjectUsers,
|
updateObjectUsers,
|
||||||
getUser,
|
getUser,
|
||||||
getUserWithTeam,
|
getUserWithTeam,
|
||||||
// getProfile,
|
// getProfile,
|
||||||
|
|
|
||||||
197
src/teams/TeamActions.tsx
Normal file
197
src/teams/TeamActions.tsx
Normal file
|
|
@ -0,0 +1,197 @@
|
||||||
|
import {
|
||||||
|
IconButton,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Tooltip
|
||||||
|
} from "@mui/material";
|
||||||
|
import RemoveSelfIcon from "@mui/icons-material/ExitToApp";
|
||||||
|
import MoreIcon from "@mui/icons-material/MoreVert";
|
||||||
|
import GroupSettingsIcon from "@mui/icons-material/Settings";
|
||||||
|
import ShareObjectIcon from "@mui/icons-material/Share";
|
||||||
|
import ObjectUsersIcon from "@mui/icons-material/AccountCircle";
|
||||||
|
import { Team, teamScope } from "models";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import ObjectUsers from "user/ObjectUsers";
|
||||||
|
import RemoveSelfFromObject from "user/RemoveSelfFromObject";
|
||||||
|
import ShareObject from "user/ShareObject";
|
||||||
|
import { isOffline } from "utils/environment";
|
||||||
|
import TeamSettings from "./TeamSettings";
|
||||||
|
import NotificationButton from "common/NotificationButton";
|
||||||
|
import { blue } from "@mui/material/colors";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
|
||||||
|
const useStyles = makeStyles(() => ({
|
||||||
|
shareIcon: {
|
||||||
|
color: blue["500"],
|
||||||
|
"&:hover": {
|
||||||
|
color: blue["600"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeIcon: {
|
||||||
|
color: "var(--status-alert)"
|
||||||
|
},
|
||||||
|
red: {
|
||||||
|
color: "var(--status-alert)"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
team: Team;
|
||||||
|
permissions: pond.Permission[];
|
||||||
|
refreshCallback: () => void;
|
||||||
|
preferences: pond.TeamPreferences;
|
||||||
|
toggleNotificationPreference: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OpenState {
|
||||||
|
share: boolean;
|
||||||
|
users: boolean;
|
||||||
|
teams: boolean;
|
||||||
|
settings: boolean;
|
||||||
|
removeSelf: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TeamActions(props: Props) {
|
||||||
|
const classes = useStyles();
|
||||||
|
const { team, permissions, refreshCallback, preferences, toggleNotificationPreference } = props;
|
||||||
|
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||||
|
const [openState, setOpenState] = useState<OpenState>({
|
||||||
|
share: false,
|
||||||
|
users: false,
|
||||||
|
teams: false,
|
||||||
|
settings: false,
|
||||||
|
removeSelf: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupMenu = () => {
|
||||||
|
const canShare = permissions.includes(pond.Permission.PERMISSION_SHARE);
|
||||||
|
const canManageUsers = permissions.includes(pond.Permission.PERMISSION_USERS);
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
id="groupMenu"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={() => setAnchorEl(null)}
|
||||||
|
keepMounted
|
||||||
|
disableAutoFocusItem>
|
||||||
|
{!isOffline() && canShare && (
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, share: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}
|
||||||
|
// button
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ShareObjectIcon className={classes.shareIcon} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Share" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{!isOffline() && canManageUsers && (
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, users: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}
|
||||||
|
// button
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ObjectUsersIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Users" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, removeSelf: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}
|
||||||
|
// button
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<RemoveSelfIcon className={classes.red} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Leave" />
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dialogs = () => {
|
||||||
|
const key = team.key();
|
||||||
|
const label = team.name();
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<TeamSettings
|
||||||
|
team={team}
|
||||||
|
permissions={permissions}
|
||||||
|
teamDialogOpen={openState.settings}
|
||||||
|
closeTeamDialogCallback={() => {
|
||||||
|
setOpenState({ ...openState, settings: false });
|
||||||
|
}}
|
||||||
|
refreshCallback={refreshCallback}
|
||||||
|
/>
|
||||||
|
<ShareObject
|
||||||
|
scope={teamScope(key)}
|
||||||
|
label={label}
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={openState.share}
|
||||||
|
closeDialogCallback={() => setOpenState({ ...openState, share: false })}
|
||||||
|
/>
|
||||||
|
<ObjectUsers
|
||||||
|
scope={teamScope(key)}
|
||||||
|
label={label}
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={openState.users}
|
||||||
|
closeDialogCallback={() => setOpenState({ ...openState, users: false })}
|
||||||
|
refreshCallback={refreshCallback}
|
||||||
|
/>
|
||||||
|
<RemoveSelfFromObject
|
||||||
|
scope={teamScope(key)}
|
||||||
|
label={label}
|
||||||
|
isDialogOpen={openState.removeSelf}
|
||||||
|
closeDialogCallback={() => setOpenState({ ...openState, removeSelf: false })}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const canWrite = permissions.includes(pond.Permission.PERMISSION_WRITE);
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{/* <NotificationButton
|
||||||
|
notify={preferences.notify}
|
||||||
|
onChange={toggleNotificationPreference}
|
||||||
|
tooltip={
|
||||||
|
"Notifications for " +
|
||||||
|
team.name() +
|
||||||
|
" are " +
|
||||||
|
(preferences.notify ? "enabled" : "disabled")
|
||||||
|
}
|
||||||
|
// hidden={isLoading}
|
||||||
|
/> */}
|
||||||
|
{canWrite && (
|
||||||
|
<Tooltip title="Team Settings">
|
||||||
|
<IconButton onClick={() => setOpenState({ ...openState, settings: true })}>
|
||||||
|
<GroupSettingsIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
<IconButton
|
||||||
|
aria-owns={anchorEl ? "groupMenu" : undefined}
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={(event: React.MouseEvent<HTMLButtonElement>) => setAnchorEl(event.currentTarget)}>
|
||||||
|
<MoreIcon />
|
||||||
|
</IconButton>
|
||||||
|
{groupMenu()}
|
||||||
|
{dialogs()}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
360
src/teams/TeamList.tsx
Normal file
360
src/teams/TeamList.tsx
Normal file
|
|
@ -0,0 +1,360 @@
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
Theme,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemAvatar,
|
||||||
|
Avatar,
|
||||||
|
ListItemText,
|
||||||
|
Typography,
|
||||||
|
ListItemSecondaryAction,
|
||||||
|
Divider,
|
||||||
|
useTheme,
|
||||||
|
TextField,
|
||||||
|
InputAdornment,
|
||||||
|
CircularProgress,
|
||||||
|
IconButton,
|
||||||
|
MenuItem,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useGlobalState, useTeamAPI, useUserAPI } from "providers";
|
||||||
|
import { Team, teamScope } from "models";
|
||||||
|
import { useNavigate } from "react-router";
|
||||||
|
// import TeamActions from "./TeamActions";
|
||||||
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
|
import NextIcon from "@mui/icons-material/NavigateNext";
|
||||||
|
import PrevIcon from "@mui/icons-material/NavigateBefore";
|
||||||
|
import LastIcon from "@mui/icons-material/SkipNext";
|
||||||
|
import FirstIcon from "@mui/icons-material/SkipPrevious";
|
||||||
|
import TeamSettings from "./TeamSettings";
|
||||||
|
import { Add } from "@mui/icons-material";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
|
||||||
|
import { makeStyles, styled } from "@mui/styles";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
cardContent: {
|
||||||
|
padding: theme.spacing(1)
|
||||||
|
},
|
||||||
|
clickable: {
|
||||||
|
"&:hover": {
|
||||||
|
textDecoration: "underline",
|
||||||
|
cursor: "pointer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
marginLeft: "auto",
|
||||||
|
marginRight: theme.spacing(2),
|
||||||
|
marginTop: theme.spacing(1)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const limits = [
|
||||||
|
{
|
||||||
|
value: 5,
|
||||||
|
label: "5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 10,
|
||||||
|
label: "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 15,
|
||||||
|
label: "15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 20,
|
||||||
|
label: "20"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
export default function TeamList(props: Props) {
|
||||||
|
const classes = useStyles();
|
||||||
|
const [teams, setTeams] = useState<pond.Team[]>([]);
|
||||||
|
const teamAPI = useTeamAPI();
|
||||||
|
const userAPI = useUserAPI();
|
||||||
|
const history = useNavigate();
|
||||||
|
const theme = useTheme();
|
||||||
|
const [{ user, as }] = useGlobalState();
|
||||||
|
const [teamPerms, setTeamPerms] = useState<pond.Permission[][]>([]);
|
||||||
|
const [teamPrefs, setTeamPrefs] = useState<pond.TeamPreferences[]>([]);
|
||||||
|
const [total, setTotal] = useState<number>(0);
|
||||||
|
const [limit, setLimit] = useState<number>(5);
|
||||||
|
const [page, setPage] = useState<number>(0);
|
||||||
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
const [search, setSearch] = useState<string>("");
|
||||||
|
const [teamDialogOpen, setTeamDialogOpen] = useState<boolean>(false);
|
||||||
|
const [asUser, setAsUser] = useState<string | undefined>(undefined);
|
||||||
|
const [userId, setUserId] = useState<string | undefined>(undefined);
|
||||||
|
const [isUser, setIsUser] = useState<boolean>(true);
|
||||||
|
|
||||||
|
// const ColorAdd = styled(Add)((theme: Theme) => ({
|
||||||
|
// color: theme.palette.getContrastText(theme.palette.primary.main),
|
||||||
|
// backgroundColor: theme.palette.primary.main,
|
||||||
|
// marginTop: 'auto',
|
||||||
|
// marginBottom: 'auto',
|
||||||
|
// borderRadius: '4px',
|
||||||
|
// '&:hover': {
|
||||||
|
// backgroundColor: theme.palette.primary.light,
|
||||||
|
// cursor: 'pointer',
|
||||||
|
// },
|
||||||
|
// '&:disabled': {
|
||||||
|
// backgroundColor: 'rgba(0,0,0,0)',
|
||||||
|
// color: theme.palette.getContrastText(theme.palette.primary.main),
|
||||||
|
// },
|
||||||
|
// }));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (as.includes("auth0")) setAsUser(as);
|
||||||
|
}, [as]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setUserId(user.id());
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!asUser) {
|
||||||
|
setIsUser(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsUser(userId === asUser);
|
||||||
|
}, [userId, asUser]);
|
||||||
|
|
||||||
|
const loadTeams = useCallback(() => {
|
||||||
|
setLoading(true);
|
||||||
|
let newTeamPerms: pond.Permission[][] = [];
|
||||||
|
let newTeamPrefs: pond.TeamPreferences[] = [];
|
||||||
|
let searchString = search;
|
||||||
|
teamAPI
|
||||||
|
.listTeams(limit, limit * page, undefined, "name", searchString, undefined, asUser)
|
||||||
|
.then(resp => {
|
||||||
|
setTotal(resp.data.total);
|
||||||
|
resp.data.teams.forEach((team, index) => {
|
||||||
|
let u = asUser ? asUser : user.id();
|
||||||
|
if (team.settings && u !== "") {
|
||||||
|
userAPI
|
||||||
|
.getUser(u, teamScope(team.settings?.key))
|
||||||
|
.then(resp => {
|
||||||
|
newTeamPerms[index] = resp.permissions;
|
||||||
|
newTeamPrefs[index] = resp.preferences;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (newTeamPerms[index]) setTeamPerms([...newTeamPerms]);
|
||||||
|
if (newTeamPrefs[index]) setTeamPrefs(newTeamPrefs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setTeams(resp.data.teams);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}, [user, teamAPI, userAPI, limit, page, search, asUser]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadTeams();
|
||||||
|
}, [user, teamAPI, userAPI, limit, page, search, asUser, loadTeams]);
|
||||||
|
|
||||||
|
const drawTeams = () => {
|
||||||
|
return (
|
||||||
|
<List>
|
||||||
|
{teams.map((team, index) => {
|
||||||
|
let permissions = teamPerms[index];
|
||||||
|
if (!permissions) {
|
||||||
|
permissions = [];
|
||||||
|
}
|
||||||
|
let preferences = teamPrefs[index];
|
||||||
|
if (!preferences) {
|
||||||
|
preferences = pond.TeamPreferences.create();
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<React.Fragment key={index}>
|
||||||
|
<ListItem alignItems="flex-start">
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar
|
||||||
|
onClick={() => {
|
||||||
|
if (team.settings) history("teams/" + team.settings.key);
|
||||||
|
}}
|
||||||
|
src={team.settings?.avatar}
|
||||||
|
alt=""
|
||||||
|
className={classes.clickable}
|
||||||
|
/>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
<div
|
||||||
|
className={classes.clickable}
|
||||||
|
onClick={() => {
|
||||||
|
if (team.settings) history("teams/" + team.settings.key);
|
||||||
|
}}>
|
||||||
|
{team.settings?.name}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
secondary={
|
||||||
|
<React.Fragment>
|
||||||
|
<Typography component="span" variant="body2" color="textPrimary">
|
||||||
|
{team.settings?.info}
|
||||||
|
</Typography>
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{/* <ListItemSecondaryAction>
|
||||||
|
<TeamActions
|
||||||
|
team={Team.create(team)}
|
||||||
|
permissions={permissions}
|
||||||
|
refreshCallback={loadTeams}
|
||||||
|
preferences={preferences}
|
||||||
|
toggleNotificationPreference={() => {
|
||||||
|
if (preferences) {
|
||||||
|
let newTeamPrefs = cloneDeep(teamPrefs);
|
||||||
|
newTeamPrefs[index].notify = !newTeamPrefs[index].notify;
|
||||||
|
console.log(newTeamPrefs[index]);
|
||||||
|
teamAPI
|
||||||
|
.updatePreferences(
|
||||||
|
Team.create(team).key(),
|
||||||
|
newTeamPrefs[index],
|
||||||
|
getContextKeys(),
|
||||||
|
getContextTypes()
|
||||||
|
)
|
||||||
|
.then(resp => {
|
||||||
|
setTeamPrefs(newTeamPrefs);
|
||||||
|
});
|
||||||
|
// setTeamPrefs(newTeamPrefs)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListItemSecondaryAction> */}
|
||||||
|
</ListItem>
|
||||||
|
<Divider variant="inset" component="li" />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showProgress = () => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
textAlign: "center"
|
||||||
|
}}>
|
||||||
|
<CircularProgress
|
||||||
|
style={{
|
||||||
|
textAlign: "center",
|
||||||
|
marginTop: theme.spacing(12),
|
||||||
|
marginBottom: theme.spacing(12)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const firstPage = () => {
|
||||||
|
setPage(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const lastPage = () => {
|
||||||
|
setPage(Math.round(total / limit));
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextPage = () => {
|
||||||
|
if (limit * (page + 1) < total) setPage(page + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const prevPage = () => {
|
||||||
|
if (page > 0) setPage(page - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (event: any) => {
|
||||||
|
setLimit(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showPageButtons = () => {
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex", float: "right", alignItems: "center" }}>
|
||||||
|
<Typography style={{ marginRight: theme.spacing(2) }}>Teams per page:</Typography>
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
value={limit}
|
||||||
|
onChange={handleChange}
|
||||||
|
style={{ marginRight: theme.spacing(1) }}>
|
||||||
|
{limits.map(option => (
|
||||||
|
<MenuItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
<IconButton disabled={page < 1} size="small" color="inherit" onClick={firstPage}>
|
||||||
|
<FirstIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton disabled={page < 1} size="small" color="inherit" onClick={prevPage}>
|
||||||
|
<PrevIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Typography
|
||||||
|
variant="subtitle1"
|
||||||
|
style={{ marginRight: theme.spacing(1), marginLeft: theme.spacing(1) }}>
|
||||||
|
{limit * page + 1} ‐ {limit * page + limit <= limit ? limit * page + limit : limit + 1} of{" "}
|
||||||
|
{total}
|
||||||
|
</Typography>
|
||||||
|
<IconButton
|
||||||
|
disabled={limit * (page + 1) >= total}
|
||||||
|
size="small"
|
||||||
|
color="inherit"
|
||||||
|
onClick={nextPage}>
|
||||||
|
<NextIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
disabled={limit * (page + 1) >= total}
|
||||||
|
size="small"
|
||||||
|
color="inherit"
|
||||||
|
onClick={lastPage}>
|
||||||
|
<LastIcon />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={classes.cardContent}>
|
||||||
|
<div style={{ display: "flex", flexDirection: "row" }}>
|
||||||
|
<Typography
|
||||||
|
variant={"h6"}
|
||||||
|
style={{
|
||||||
|
marginLeft: theme.spacing(1),
|
||||||
|
marginTop: "auto",
|
||||||
|
marginBottom: "auto",
|
||||||
|
marginRight: theme.spacing(2)
|
||||||
|
}}>
|
||||||
|
{isUser ? user.name() + "'s " : "Someone's "} Teams
|
||||||
|
</Typography>
|
||||||
|
<Add onClick={() => setTeamDialogOpen(true)} />
|
||||||
|
<TextField
|
||||||
|
className={classes.input}
|
||||||
|
value={search}
|
||||||
|
onChange={event => setSearch(event?.target.value)}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<SearchIcon />
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}></TextField>
|
||||||
|
</div>
|
||||||
|
<Typography variant={"subtitle2"} style={{ marginLeft: theme.spacing(2) }}>
|
||||||
|
{isUser ? "Viewing all teams that you are a part of" : "These are someone else's teams"}
|
||||||
|
</Typography>
|
||||||
|
{!loading ? drawTeams() : showProgress()}
|
||||||
|
{showPageButtons()}
|
||||||
|
<TeamSettings
|
||||||
|
teamDialogOpen={teamDialogOpen}
|
||||||
|
closeTeamDialogCallback={() => setTeamDialogOpen(false)}
|
||||||
|
refreshCallback={loadTeams}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
265
src/teams/TeamSettings.tsx
Normal file
265
src/teams/TeamSettings.tsx
Normal file
|
|
@ -0,0 +1,265 @@
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogContentText,
|
||||||
|
DialogTitle,
|
||||||
|
Tab,
|
||||||
|
Tabs,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
|
import { Team } from "models";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useSnackbar, useTeamAPI } from "providers";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import DeleteButton from "common/DeleteButton";
|
||||||
|
import { userRoleFromPermissions } from "pbHelpers/User";
|
||||||
|
import { IconPicker } from "common/IconPicker";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
teamDialogOpen: boolean;
|
||||||
|
closeTeamDialogCallback: () => void;
|
||||||
|
refreshCallback?: () => void;
|
||||||
|
team?: Team;
|
||||||
|
permissions?: pond.Permission[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TeamSettings(props: Props) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const teamAPI = useTeamAPI();
|
||||||
|
const { teamDialogOpen, closeTeamDialogCallback, permissions, refreshCallback } = props;
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [nameField, setNameField] = useState<string>("");
|
||||||
|
const [infoField, setInfoField] = useState<string>("");
|
||||||
|
const [url, setUrl] = useState<string>("");
|
||||||
|
const snackbar = useSnackbar();
|
||||||
|
const [tab, setTab] = useState(0);
|
||||||
|
const [team, setTeam] = useState<Team>();
|
||||||
|
const [teamKey, setTeamKey] = useState<string>();
|
||||||
|
const [openDelete, setOpenDelete] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.team) {
|
||||||
|
setTeam(props.team);
|
||||||
|
setTeamKey(props.team.key());
|
||||||
|
}
|
||||||
|
}, [props.team]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (team) {
|
||||||
|
setNameField(team.name());
|
||||||
|
setInfoField(team.settings.info);
|
||||||
|
setUrl(team.settings.avatar);
|
||||||
|
}
|
||||||
|
}, [team]);
|
||||||
|
|
||||||
|
// const ColorButton = withStyles((theme: Theme) => ({
|
||||||
|
// root: {
|
||||||
|
// color: theme.palette.getContrastText(theme.palette.primary.main),
|
||||||
|
// backgroundColor: theme.palette.primary.main,
|
||||||
|
// "&:hover": {
|
||||||
|
// backgroundColor: theme.palette.primary.light
|
||||||
|
// },
|
||||||
|
// "&:disabled": {
|
||||||
|
// backgroundColor: "rgba(0,0,0,0)",
|
||||||
|
// color: theme.palette.getContrastText(theme.palette.primary.main)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }))(Button);
|
||||||
|
|
||||||
|
const removeTeam = () => {
|
||||||
|
if (team?.key())
|
||||||
|
teamAPI
|
||||||
|
.removeTeam(team?.key())
|
||||||
|
.then(() => {
|
||||||
|
snackbar.info("Team removed successfully.");
|
||||||
|
if (refreshCallback) refreshCallback();
|
||||||
|
closeTeamDialogCallback();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const addTeam = () => {
|
||||||
|
let team = pond.TeamSettings.create();
|
||||||
|
team.name = nameField;
|
||||||
|
team.info = infoField;
|
||||||
|
setLoading(true);
|
||||||
|
teamAPI
|
||||||
|
.addTeam(team)
|
||||||
|
.then(resp => {
|
||||||
|
snackbar.success("Team added successfully!");
|
||||||
|
setTab(1);
|
||||||
|
let t = pond.Team.create();
|
||||||
|
t.settings = team;
|
||||||
|
setTeam(Team.create(t));
|
||||||
|
setTeamKey(resp.data.team);
|
||||||
|
if (refreshCallback) refreshCallback();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
snackbar.error(err);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateTeam = () => {
|
||||||
|
let newTeam = pond.TeamSettings.create();
|
||||||
|
newTeam.name = nameField;
|
||||||
|
newTeam.info = infoField;
|
||||||
|
newTeam.avatar = url;
|
||||||
|
if (teamKey) {
|
||||||
|
teamAPI
|
||||||
|
.updateTeam(teamKey, newTeam)
|
||||||
|
.then(() => {
|
||||||
|
snackbar.success("Team update successfully!");
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
snackbar.error(err);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (refreshCallback) refreshCallback();
|
||||||
|
setNameField("");
|
||||||
|
setInfoField("");
|
||||||
|
setUrl("");
|
||||||
|
setTeamKey(undefined);
|
||||||
|
setTab(0);
|
||||||
|
closeTeamDialogCallback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const disableButton = () => {
|
||||||
|
if (loading) return true;
|
||||||
|
if (nameField === "" || infoField === "") return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
setNameField("");
|
||||||
|
setInfoField("");
|
||||||
|
setUrl("");
|
||||||
|
closeTeamDialogCallback();
|
||||||
|
};
|
||||||
|
|
||||||
|
const isOwner = () => {
|
||||||
|
if (permissions) {
|
||||||
|
return userRoleFromPermissions(permissions) === "Owner";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteConfirmation = () => {
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog
|
||||||
|
open={openDelete}
|
||||||
|
onClose={() => {
|
||||||
|
setOpenDelete(false);
|
||||||
|
}}>
|
||||||
|
<DialogTitle>Delete Team</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography>
|
||||||
|
Performing this action will delete this team and and any file attachments associated
|
||||||
|
with it.
|
||||||
|
</Typography>
|
||||||
|
<Typography>Are you sure you wish to continue.</Typography>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setOpenDelete(false);
|
||||||
|
}}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<DeleteButton onClick={removeTeam}>Confirm</DeleteButton>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const teamSettings = () => {
|
||||||
|
return (
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText style={{ margin: theme.spacing(1) }}>
|
||||||
|
Add more info about your team, business, or company. You can have multiple teams if
|
||||||
|
needed.
|
||||||
|
</DialogContentText>
|
||||||
|
<TextField
|
||||||
|
label="Name"
|
||||||
|
value={nameField}
|
||||||
|
onChange={event => setNameField(event?.target.value)}
|
||||||
|
variant="outlined"
|
||||||
|
style={{ margin: theme.spacing(1) }}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<div style={{ marginTop: theme.spacing(1) }} />
|
||||||
|
<TextField
|
||||||
|
label="Info"
|
||||||
|
value={infoField}
|
||||||
|
onChange={event => setInfoField(event?.target.value)}
|
||||||
|
variant="outlined"
|
||||||
|
style={{ margin: theme.spacing(1) }}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let disable = disableButton();
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<ResponsiveDialog open={teamDialogOpen} onClose={close}>
|
||||||
|
<DialogTitle>{team ? "Update " + team.name() + "?" : "Create New Team?"}</DialogTitle>
|
||||||
|
<Box style={{ borderBottom: 1, borderColor: "divider" }}>
|
||||||
|
<Tabs
|
||||||
|
value={tab}
|
||||||
|
onChange={(_, value) => setTab(value)}
|
||||||
|
aria-label="basic tabs example"
|
||||||
|
centered>
|
||||||
|
<Tab label="Name & Info" key={1} />
|
||||||
|
<Tab
|
||||||
|
label="Team Icon"
|
||||||
|
key={2}
|
||||||
|
disabled={team?.key() === undefined || team?.key() === ""}
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
{tab === 0 && teamSettings()}
|
||||||
|
{tab === 1 && teamKey !== undefined && (
|
||||||
|
<DialogContent>
|
||||||
|
<IconPicker setUrl={setUrl} url={url} id={teamKey} />
|
||||||
|
</DialogContent>
|
||||||
|
)}
|
||||||
|
<DialogActions>
|
||||||
|
{isOwner() && (
|
||||||
|
<DeleteButton
|
||||||
|
onClick={() => {
|
||||||
|
setOpenDelete(true);
|
||||||
|
}}
|
||||||
|
aria-label="Cancel"
|
||||||
|
style={{ marginRight: "auto", marginLeft: theme.spacing(2) }}>
|
||||||
|
Delete
|
||||||
|
</DeleteButton>
|
||||||
|
)}
|
||||||
|
<Button onClick={close} color="primary" aria-label="Cancel">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={disable}
|
||||||
|
onClick={teamKey ? updateTeam : addTeam}
|
||||||
|
style={{ marginRight: theme.spacing(2) }}>
|
||||||
|
<Typography variant="subtitle2" color="textPrimary">
|
||||||
|
{teamKey ? "Update" : "Submit"}
|
||||||
|
</Typography>
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
{deleteConfirmation()}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
719
src/user/ObjectUsers.tsx
Normal file
719
src/user/ObjectUsers.tsx
Normal file
|
|
@ -0,0 +1,719 @@
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
CircularProgress,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Divider,
|
||||||
|
FormControl,
|
||||||
|
FormControlLabel,
|
||||||
|
FormGroup,
|
||||||
|
FormLabel,
|
||||||
|
Grid,
|
||||||
|
Grid2,
|
||||||
|
IconButton,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemAvatar,
|
||||||
|
ListItemSecondaryAction,
|
||||||
|
ListItemText,
|
||||||
|
PaletteColor,
|
||||||
|
Stack,
|
||||||
|
Theme,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
useTheme
|
||||||
|
} from "@mui/material";
|
||||||
|
// import { Theme } from "@material-ui/core/styles/createMuiTheme";
|
||||||
|
// import { PaletteColor } from "@material-ui/core/styles/createPalette";
|
||||||
|
import RemoveUserIcon from "@mui/icons-material/RemoveCircle";
|
||||||
|
import EditIcon from "@mui/icons-material/EditOutlined";
|
||||||
|
import ShareIcon from "@mui/icons-material/Share";
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
|
import { usePermissionAPI, usePrevious, useSnackbar, useUserAPI } from "hooks";
|
||||||
|
import { cloneDeep, isEqual } from "lodash";
|
||||||
|
import { Scope, User } from "models";
|
||||||
|
import { sortUsersByRole, userRoleFromPermissions } from "pbHelpers/User";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import { or } from "utils/types";
|
||||||
|
import RemoveSelfFromObject from "user/RemoveSelfFromObject";
|
||||||
|
import ShareObject from "user/ShareObject";
|
||||||
|
import { useGlobalState } from "providers";
|
||||||
|
import { red, blue } from "@mui/material/colors";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
const avatarBG = theme.palette.secondary["700" as keyof PaletteColor];
|
||||||
|
return {
|
||||||
|
dialogContent: {
|
||||||
|
padding: `${theme.spacing(1)}px ${theme.spacing(2)}px`,
|
||||||
|
[theme.breakpoints.up("sm")]: {
|
||||||
|
padding: `${theme.spacing(1)}px ${theme.spacing(3)}px`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
userAvatar: {
|
||||||
|
color: "#fff",
|
||||||
|
backgroundColor: avatarBG
|
||||||
|
},
|
||||||
|
userPermissions: {
|
||||||
|
paddingLeft: theme.spacing(4),
|
||||||
|
paddingBottom: theme.spacing(1)
|
||||||
|
},
|
||||||
|
textOverflowEllipsis: {
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
},
|
||||||
|
removeIcon: {
|
||||||
|
color: red["500"],
|
||||||
|
"&:hover": {
|
||||||
|
color: red["600"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
verticalSpacing: {
|
||||||
|
paddingTop: theme.spacing(4),
|
||||||
|
paddingBottom: theme.spacing(3)
|
||||||
|
},
|
||||||
|
lessSpacing: {
|
||||||
|
paddingTop: theme.spacing(1),
|
||||||
|
paddingBottom: theme.spacing(1)
|
||||||
|
},
|
||||||
|
iconButton: {
|
||||||
|
margin: theme.spacing(1)
|
||||||
|
},
|
||||||
|
shareIcon: {
|
||||||
|
color: blue["600"],
|
||||||
|
"&:hover": {
|
||||||
|
color: blue["700"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editIcon: {
|
||||||
|
color: blue["300"],
|
||||||
|
"&:hover": {
|
||||||
|
color: blue["400"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
scope: Scope;
|
||||||
|
label: string;
|
||||||
|
permissions: pond.Permission[];
|
||||||
|
isDialogOpen: boolean;
|
||||||
|
closeDialogCallback: Function;
|
||||||
|
refreshCallback: Function;
|
||||||
|
userCallback?: (users?: User[] | undefined) => void;
|
||||||
|
dialog?: string;
|
||||||
|
cardMode?: boolean;
|
||||||
|
useImitation?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ObjectUsers(props: Props) {
|
||||||
|
const classes = useStyles();
|
||||||
|
const theme = useTheme();
|
||||||
|
const permissionAPI = usePermissionAPI();
|
||||||
|
const [{ user, team }, dispatch] = useGlobalState();
|
||||||
|
const canProvision = user.allowedTo("provision");
|
||||||
|
const userAPI = useUserAPI();
|
||||||
|
const { error, success, warning } = useSnackbar();
|
||||||
|
const {
|
||||||
|
scope,
|
||||||
|
label,
|
||||||
|
permissions,
|
||||||
|
isDialogOpen,
|
||||||
|
closeDialogCallback,
|
||||||
|
refreshCallback,
|
||||||
|
userCallback,
|
||||||
|
dialog,
|
||||||
|
cardMode
|
||||||
|
//useImitation
|
||||||
|
} = props;
|
||||||
|
const prevPermissions = usePrevious(permissions);
|
||||||
|
const prevIsDialogOpen = usePrevious(isDialogOpen);
|
||||||
|
const [initialUsers, setInitialUsers] = useState<User[]>([]);
|
||||||
|
const [users, setUsers] = useState<User[]>([]);
|
||||||
|
const [canManageUsers, setCanManageUsers] = useState<boolean>(
|
||||||
|
permissions.includes(pond.Permission.PERMISSION_USERS)
|
||||||
|
);
|
||||||
|
const [canShare, setCanShare] = useState<boolean>(
|
||||||
|
permissions.includes(pond.Permission.PERMISSION_SHARE)
|
||||||
|
);
|
||||||
|
const [canShareBilling, setCanShareBilling] = useState<boolean>(
|
||||||
|
permissions.includes(pond.Permission.PERMISSION_SHARE) &&
|
||||||
|
permissions.includes(pond.Permission.PERMISSION_USERS) &&
|
||||||
|
permissions.includes(pond.Permission.PERMISSION_WRITE)
|
||||||
|
);
|
||||||
|
const [removedUsers, setRemovedUsers] = useState<string[]>([]);
|
||||||
|
const [removeSelfDialogIsOpen, setRemoveSelfDialogIsOpen] = useState<boolean>(false);
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
const [isShareObjectDialogOpen, setIsShareObjectDialogOpen] = useState<boolean>(false);
|
||||||
|
const [numOwners, setNumOwners] = useState(0);
|
||||||
|
const [useImitation] = useState<boolean>(
|
||||||
|
props.useImitation !== undefined ? props.useImitation : true
|
||||||
|
);
|
||||||
|
// const [editUserDialog, setEditUserDialog] = useState(false);
|
||||||
|
const [userForDialog, setUserForDialog] = useState<User | undefined>(undefined);
|
||||||
|
|
||||||
|
const setDefaultState = () => {
|
||||||
|
setInitialUsers([]);
|
||||||
|
setUsers([]);
|
||||||
|
setRemovedUsers([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const load = useCallback(() => {
|
||||||
|
setIsLoading(true);
|
||||||
|
userAPI
|
||||||
|
.listObjectUsers(scope)
|
||||||
|
.then((response: any) => {
|
||||||
|
let rUsers: User[] = [];
|
||||||
|
or(response.data, { users: [] }).users.forEach((user: any) => {
|
||||||
|
rUsers.push(User.any(user));
|
||||||
|
});
|
||||||
|
rUsers = rUsers.filter(u => u.permissions.length > 0);
|
||||||
|
setInitialUsers(cloneDeep(rUsers));
|
||||||
|
let numOwners = 0;
|
||||||
|
rUsers.forEach(user => {
|
||||||
|
if (userRoleFromPermissions(user.permissions) === "Owner") numOwners++;
|
||||||
|
});
|
||||||
|
setNumOwners(numOwners);
|
||||||
|
setUsers(rUsers);
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
console.log(err);
|
||||||
|
setInitialUsers([]);
|
||||||
|
setUsers([]);
|
||||||
|
})
|
||||||
|
.finally(() => setIsLoading(false));
|
||||||
|
}, [scope, userAPI]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!prevIsDialogOpen && isDialogOpen) {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
if (prevPermissions !== permissions) {
|
||||||
|
setCanManageUsers(permissions.includes(pond.Permission.PERMISSION_USERS));
|
||||||
|
setCanShare(permissions.includes(pond.Permission.PERMISSION_SHARE));
|
||||||
|
setCanShareBilling(
|
||||||
|
permissions.includes(pond.Permission.PERMISSION_SHARE) &&
|
||||||
|
permissions.includes(pond.Permission.PERMISSION_USERS) &&
|
||||||
|
permissions.includes(pond.Permission.PERMISSION_WRITE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [isDialogOpen, load, permissions, prevIsDialogOpen, prevPermissions]);
|
||||||
|
|
||||||
|
const closeRemoveSelfDialog = () => {
|
||||||
|
setRemoveSelfDialogIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
closeRemoveSelfDialog();
|
||||||
|
closeDialogCallback();
|
||||||
|
setDefaultState();
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
console.log(users);
|
||||||
|
permissionAPI
|
||||||
|
.updatePermissions(scope, users)
|
||||||
|
.then((response: any) => {
|
||||||
|
success("Users were sucessfully updated for " + label);
|
||||||
|
close();
|
||||||
|
refreshCallback();
|
||||||
|
userCallback && userCallback(users);
|
||||||
|
users.forEach(u => {
|
||||||
|
if (u.id() === user.id()) {
|
||||||
|
if (scope.kind === "team" && scope.key === team.key()) {
|
||||||
|
dispatch({ key: "userTeamPermissions", value: u.permissions });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (cardMode) {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
err.response.data.error
|
||||||
|
? warning(err.response.data.error)
|
||||||
|
: error("Error occured when updating users for " + label);
|
||||||
|
close();
|
||||||
|
})
|
||||||
|
.finally(() => setUserForDialog(undefined));
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeUserPermissions = (user: pond.IUser) => (event: any) => {
|
||||||
|
let updatedUsers = cloneDeep(users);
|
||||||
|
let permissionMapping = new Map<string, pond.Permission>([
|
||||||
|
["2", pond.Permission.PERMISSION_READ],
|
||||||
|
["3", pond.Permission.PERMISSION_WRITE],
|
||||||
|
["4", pond.Permission.PERMISSION_SHARE],
|
||||||
|
["1", pond.Permission.PERMISSION_USERS],
|
||||||
|
["5", pond.Permission.PERMISSION_BILLING],
|
||||||
|
["6", pond.Permission.PERMISSION_FILE_MANAGEMENT]
|
||||||
|
]);
|
||||||
|
let permission = or(
|
||||||
|
permissionMapping.get(event.target.value),
|
||||||
|
pond.Permission.PERMISSION_INVALID
|
||||||
|
);
|
||||||
|
for (let i = 0; i < updatedUsers.length; i++) {
|
||||||
|
let currUser = updatedUsers[i];
|
||||||
|
if (
|
||||||
|
user &&
|
||||||
|
user.settings &&
|
||||||
|
currUser &&
|
||||||
|
currUser.settings &&
|
||||||
|
currUser.settings.id === user.settings.id
|
||||||
|
) {
|
||||||
|
let permissions = user.permissions ? cloneDeep(user.permissions) : [];
|
||||||
|
if (permissions.includes(permission)) {
|
||||||
|
permissions = permissions.filter(
|
||||||
|
(curPermission: pond.Permission) => curPermission !== permission
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
permissions.push(permission);
|
||||||
|
}
|
||||||
|
updatedUsers[i].permissions = permissions;
|
||||||
|
user.permissions = permissions;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setUsers(updatedUsers);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeUser = (user: string) => {
|
||||||
|
let updatedUsers = cloneDeep(users);
|
||||||
|
let updatedRemovedUsers = cloneDeep(removedUsers);
|
||||||
|
for (let i = 0; i < updatedUsers.length; i++) {
|
||||||
|
let currUser = updatedUsers[i];
|
||||||
|
if (currUser && currUser.settings && currUser.settings.id === user) {
|
||||||
|
updatedRemovedUsers.push(currUser.settings.id);
|
||||||
|
updatedUsers[i].permissions = [];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setUsers(updatedUsers);
|
||||||
|
setRemovedUsers(updatedRemovedUsers);
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkIsSelf = (checkUser: pond.IUser): boolean => {
|
||||||
|
const id = checkUser && checkUser.settings ? checkUser.settings.id : "";
|
||||||
|
return user.id() === id;
|
||||||
|
};
|
||||||
|
|
||||||
|
const userIsRemoved = (user: pond.IUser): boolean => {
|
||||||
|
const id = user && user.settings ? user.settings.id : "";
|
||||||
|
return removedUsers.includes(or(id, ""));
|
||||||
|
};
|
||||||
|
|
||||||
|
const objectUsersUnchanged = (): boolean => {
|
||||||
|
return isEqual(initialUsers, users);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openRemoveSelfDialog = () => {
|
||||||
|
setRemoveSelfDialogIsOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openShareObjectDialog = () => {
|
||||||
|
setIsShareObjectDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeShareObjectDialog = (shared: boolean | undefined) => {
|
||||||
|
setIsShareObjectDialogOpen(false);
|
||||||
|
if (shared) {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UI BEGINS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
const title = () => {
|
||||||
|
return (
|
||||||
|
// <Grid container direction="column" justify="space-between">
|
||||||
|
// <Grid container direction="row" justify="space-between">
|
||||||
|
// <Grid item xs={8}>
|
||||||
|
// Users
|
||||||
|
// <Typography variant="body2" color="textSecondary">
|
||||||
|
// {label}
|
||||||
|
// </Typography>
|
||||||
|
// </Grid>
|
||||||
|
// <Grid item xs={4} container justify="flex-end">
|
||||||
|
// {canShare && (
|
||||||
|
// <Tooltip title={"Share " + label}>
|
||||||
|
// <IconButton
|
||||||
|
// aria-label="Share"
|
||||||
|
// className={classes.iconButton}
|
||||||
|
// onClick={openShareObjectDialog}>
|
||||||
|
// <ShareIcon className={classes.shareIcon} />
|
||||||
|
// </IconButton>
|
||||||
|
// </Tooltip>
|
||||||
|
// )}
|
||||||
|
// </Grid>
|
||||||
|
// </Grid>
|
||||||
|
// {dialog && (
|
||||||
|
// <Typography variant="body2" color="textSecondary" style={{ marginTop: "4px" }}>
|
||||||
|
// {dialog}
|
||||||
|
// </Typography>
|
||||||
|
// )}
|
||||||
|
// </Grid>
|
||||||
|
<Stack direction="column" spacing={2}>
|
||||||
|
<Stack direction="row" spacing={2} justifyContent="space-between">
|
||||||
|
<Box>
|
||||||
|
Users
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
{label}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box display="flex" justifyContent="flex-end">
|
||||||
|
{canShare && (
|
||||||
|
<Tooltip title={`Share ${label}`}>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Share"
|
||||||
|
onClick={openShareObjectDialog}>
|
||||||
|
<ShareIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
{dialog && (
|
||||||
|
<Typography variant="body2" color="textSecondary" sx={{ mt: 1 }}>
|
||||||
|
{dialog}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const userPermissionManagement = (user: User) => {
|
||||||
|
let permissions = user.permissions;
|
||||||
|
if (
|
||||||
|
!canManageUsers ||
|
||||||
|
(checkIsSelf(user) && !canShareBilling) ||
|
||||||
|
userIsRemoved(user) ||
|
||||||
|
(permissions.includes(pond.Permission.PERMISSION_USERS) &&
|
||||||
|
permissions.includes(pond.Permission.PERMISSION_READ) &&
|
||||||
|
permissions.includes(pond.Permission.PERMISSION_WRITE) &&
|
||||||
|
permissions.includes(pond.Permission.PERMISSION_SHARE) &&
|
||||||
|
permissions.includes(pond.Permission.PERMISSION_BILLING) &&
|
||||||
|
permissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT) &&
|
||||||
|
!cardMode)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const canRead = permissions.includes(pond.Permission.PERMISSION_READ);
|
||||||
|
const xs = cardMode ? 12 : 4;
|
||||||
|
const sm = cardMode ? 4 : 3;
|
||||||
|
const justify = cardMode ? "flex-start" : "center";
|
||||||
|
return (
|
||||||
|
<FormControl component="fieldset" fullWidth className={classes.userPermissions}>
|
||||||
|
<FormLabel component="legend"></FormLabel>
|
||||||
|
<FormGroup>
|
||||||
|
<Grid2 container direction="row" justifyContent={justify} spacing={2}>
|
||||||
|
<Grid2 sx={{ gridColumn: `span ${xs} / span ${sm}` }}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={permissions.includes(pond.Permission.PERMISSION_READ)}
|
||||||
|
onChange={changeUserPermissions(user)}
|
||||||
|
value={pond.Permission.PERMISSION_READ}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="View"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
<Grid2 sx={{ gridColumn: `span ${xs} / span ${sm}` }}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
disabled={!canRead}
|
||||||
|
checked={permissions.includes(pond.Permission.PERMISSION_WRITE)}
|
||||||
|
onChange={changeUserPermissions(user)}
|
||||||
|
value={pond.Permission.PERMISSION_WRITE}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Edit"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
<Grid2 sx={{ gridColumn: `span ${xs} / span ${sm}` }}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
disabled={!canRead}
|
||||||
|
checked={permissions.includes(pond.Permission.PERMISSION_SHARE)}
|
||||||
|
onChange={changeUserPermissions(user)}
|
||||||
|
value={pond.Permission.PERMISSION_SHARE}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Share"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
<Grid2 sx={{ gridColumn: `span ${xs} / span ${sm}` }}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
disabled={!canRead}
|
||||||
|
checked={permissions.includes(pond.Permission.PERMISSION_USERS)}
|
||||||
|
onChange={changeUserPermissions(user)}
|
||||||
|
value={pond.Permission.PERMISSION_USERS}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Manage Users"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
<Grid2 sx={{ gridColumn: 'span 6 / span 3' }}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={permissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT)}
|
||||||
|
onChange={changeUserPermissions(user)}
|
||||||
|
value={pond.Permission.PERMISSION_FILE_MANAGEMENT}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Files"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
<Grid2 sx={{ gridColumn: `span ${xs} / span ${sm}` }}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
disabled={!canShareBilling}
|
||||||
|
checked={permissions.includes(pond.Permission.PERMISSION_BILLING)}
|
||||||
|
onChange={changeUserPermissions(user)}
|
||||||
|
value={pond.Permission.PERMISSION_BILLING}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Billing"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
</FormGroup>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const userListItem = (user: User) => {
|
||||||
|
let isSelf = checkIsSelf(user);
|
||||||
|
let isRemoved = userIsRemoved(user);
|
||||||
|
let name = user.name();
|
||||||
|
let permissions = user.permissions;
|
||||||
|
let userSettings = pond.UserSettings.create(user.settings !== null ? user.settings : undefined);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItem alignItems="flex-start" >
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar
|
||||||
|
alt={name}
|
||||||
|
src={
|
||||||
|
userSettings.avatar && userSettings.avatar !== "" ? userSettings.avatar : undefined
|
||||||
|
}
|
||||||
|
className={classes.userAvatar}>
|
||||||
|
{!(userSettings.avatar && userSettings.avatar !== "") && name}
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
className={classes.textOverflowEllipsis}
|
||||||
|
disableTypography
|
||||||
|
primary={
|
||||||
|
<Typography component="div" variant="body1" color="textPrimary">
|
||||||
|
{name + (isSelf ? " (you)" : "")}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
secondary={
|
||||||
|
<React.Fragment>
|
||||||
|
<Typography component="div" variant="caption" color="textSecondary">
|
||||||
|
{user.settings.email}
|
||||||
|
</Typography>
|
||||||
|
<Typography component="div" variant="caption" color="textSecondary">
|
||||||
|
{isRemoved ? "REMOVED " : userRoleFromPermissions(permissions)}
|
||||||
|
</Typography>
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{isSelf && !canShareBilling ? (
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Tooltip title={"Remove yourself from " + label}>
|
||||||
|
<IconButton
|
||||||
|
edge="end"
|
||||||
|
aria-label={"Remove yourself from " + label}
|
||||||
|
onClick={openRemoveSelfDialog}>
|
||||||
|
<RemoveUserIcon className={classes.removeIcon} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
) : (canManageUsers || canProvision) && !isRemoved && !cardMode ? (
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Tooltip title="Revoke user's access">
|
||||||
|
<IconButton
|
||||||
|
edge="end"
|
||||||
|
aria-label="Remove user from device"
|
||||||
|
onClick={() => removeUser(or(user.settings, { id: "" }).id)}>
|
||||||
|
<RemoveUserIcon className={classes.removeIcon} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
) : (
|
||||||
|
(canManageUsers || canProvision) && (
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Tooltip title="Edit user's permissions">
|
||||||
|
<IconButton
|
||||||
|
edge="end"
|
||||||
|
aria-label="Edit user permissions"
|
||||||
|
disabled={!canManageUsers}
|
||||||
|
onClick={() => setUserForDialog(user)}>
|
||||||
|
<EditIcon className={classes.editIcon} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const objectUsersList = () => {
|
||||||
|
const sortedUsers = sortUsersByRole(users);
|
||||||
|
|
||||||
|
let userListItems: any = [];
|
||||||
|
for (var i = 0; i < sortedUsers.length; i++) {
|
||||||
|
let user = sortedUsers[i];
|
||||||
|
if (user) {
|
||||||
|
userListItems.push(
|
||||||
|
<React.Fragment key={i}>
|
||||||
|
{userListItem(user)}
|
||||||
|
{!cardMode && userPermissionManagement(user)}
|
||||||
|
<Divider />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <List>{userListItems}</List>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = () => {
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<Grid2 container justifyContent="center" alignContent="center" className={classes.verticalSpacing}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Grid2>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (users.length < 1) {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
align="center"
|
||||||
|
color="textSecondary"
|
||||||
|
className={!cardMode ? classes.verticalSpacing : ""}
|
||||||
|
style={{ padding: cardMode ? 0 : "" }}>
|
||||||
|
No users found.
|
||||||
|
</Typography>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return objectUsersList();
|
||||||
|
};
|
||||||
|
|
||||||
|
const actions = () => {
|
||||||
|
return (
|
||||||
|
<Grid2 container direction="row" justifyContent="space-between">
|
||||||
|
<Grid2 sx={{ gridColumn: `span ${4} ` }} ></Grid2>
|
||||||
|
<Grid2 sx={{ gridColumn: `span ${8} ` }} container justifyContent="flex-end">
|
||||||
|
{!cardMode && (
|
||||||
|
<Button onClick={close} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{canManageUsers && !cardMode && (
|
||||||
|
<Button onClick={submit} color="primary" disabled={objectUsersUnchanged()}>
|
||||||
|
{cardMode ? "Update" : "Submit"}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dialogs = () => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<RemoveSelfFromObject
|
||||||
|
scope={scope}
|
||||||
|
label={label}
|
||||||
|
isDialogOpen={removeSelfDialogIsOpen}
|
||||||
|
closeDialogCallback={closeRemoveSelfDialog}
|
||||||
|
numberOfOwners={numOwners}
|
||||||
|
/>
|
||||||
|
<ShareObject
|
||||||
|
scope={scope}
|
||||||
|
label={label}
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={isShareObjectDialogOpen}
|
||||||
|
closeDialogCallback={closeShareObjectDialog}
|
||||||
|
useImitation={useImitation}
|
||||||
|
sharedUsers={users}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (cardMode) {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Typography variant="h6">{title()}</Typography>
|
||||||
|
<div style={{ overflowY: "scroll" }}>
|
||||||
|
<Divider />
|
||||||
|
{content()}
|
||||||
|
{actions()}
|
||||||
|
{dialogs()}
|
||||||
|
<div style={{ marginTop: theme.spacing(2) }} />
|
||||||
|
</div>
|
||||||
|
<ResponsiveDialog
|
||||||
|
open={userForDialog !== undefined}
|
||||||
|
onClose={() => setUserForDialog(undefined)}>
|
||||||
|
<DialogTitle>Change {userForDialog?.name()}'s Permissions?</DialogTitle>
|
||||||
|
<DialogContent>{userForDialog && userPermissionManagement(userForDialog)}</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setUserForDialog(undefined)}>Close</Button>
|
||||||
|
<Button color="primary" onClick={submit}>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog
|
||||||
|
fullWidth
|
||||||
|
maxWidth="xs"
|
||||||
|
open={props.isDialogOpen}
|
||||||
|
onClose={close}
|
||||||
|
aria-labelledby="object-users-dialog">
|
||||||
|
<DialogTitle id="object-users-title">{title()}</DialogTitle>
|
||||||
|
<Divider />
|
||||||
|
<DialogContent className={classes.dialogContent}>{content()}</DialogContent>
|
||||||
|
<DialogActions>{actions()}</DialogActions>
|
||||||
|
{dialogs()}
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
111
src/user/RemoveSelfFromObject.tsx
Normal file
111
src/user/RemoveSelfFromObject.tsx
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Typography
|
||||||
|
} from "@mui/material";
|
||||||
|
import { Scope, User } from "models";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useGlobalState, usePermissionAPI, useSnackbar } from "providers";
|
||||||
|
import React from "react";
|
||||||
|
import { useNavigate } from "react-router";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
scope: Scope;
|
||||||
|
label: string;
|
||||||
|
isDialogOpen: boolean;
|
||||||
|
closeDialogCallback: (removed: boolean) => void;
|
||||||
|
path?: string;
|
||||||
|
numberOfOwners?: number; //including the number of owners will prevent the user from leaving if they are the final owner
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RemoveSelfFromObject(props: Props) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const permissionAPI = usePermissionAPI();
|
||||||
|
const [{ user }] = useGlobalState();
|
||||||
|
const { error, success, warning } = useSnackbar();
|
||||||
|
const { scope, label, isDialogOpen, closeDialogCallback, path, numberOfOwners } = props;
|
||||||
|
const userID = user.id()
|
||||||
|
|
||||||
|
const close = (removed: boolean) => {
|
||||||
|
closeDialogCallback(removed);
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
permissionAPI
|
||||||
|
.updatePermissions(scope, [
|
||||||
|
User.create(
|
||||||
|
pond.User.create({
|
||||||
|
settings: pond.UserSettings.create({
|
||||||
|
id: userID
|
||||||
|
}),
|
||||||
|
permissions: []
|
||||||
|
})
|
||||||
|
)
|
||||||
|
])
|
||||||
|
.then((response: any) => {
|
||||||
|
success("Successfully left " + label);
|
||||||
|
close(true);
|
||||||
|
navigate("/" + (path !== undefined ? path : scope.kind) + "s");
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
err.response.data.error
|
||||||
|
? warning(err.response.data.error)
|
||||||
|
: error("Error occured when trying to remove yourself from " + label);
|
||||||
|
close(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UI BEGINS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
const removeSelfDialog = () => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<DialogTitle id="remove-self-from-object">{"Leave " + label + "?"}</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography color="textSecondary" align="left" variant="subtitle1">
|
||||||
|
You will no longer be able to access {label}.
|
||||||
|
</Typography>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => close(false)} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={submit} color="primary" autoFocus>
|
||||||
|
Leave
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onlyOwnerDialog = () => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<DialogTitle>{"Cannot Leave"}</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography color="textSecondary" align="left" variant="subtitle1">
|
||||||
|
You are currently the only owner. To leave, share it to another user with full
|
||||||
|
permissions to make them an owner first and then leave.
|
||||||
|
</Typography>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => close(false)} color="primary">
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={isDialogOpen}
|
||||||
|
onClose={() => close(false)}
|
||||||
|
aria-labelledby="remove-self-from-object">
|
||||||
|
{numberOfOwners && numberOfOwners <= 1 ? onlyOwnerDialog() : removeSelfDialog()}
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
601
src/user/ShareObject.tsx
Normal file
601
src/user/ShareObject.tsx
Normal file
|
|
@ -0,0 +1,601 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
CircularProgress,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Divider,
|
||||||
|
FormControl,
|
||||||
|
FormControlLabel,
|
||||||
|
FormGroup,
|
||||||
|
FormLabel,
|
||||||
|
IconButton,
|
||||||
|
Link,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemSecondaryAction,
|
||||||
|
ListItemText,
|
||||||
|
Tab,
|
||||||
|
Tabs,
|
||||||
|
TextField,
|
||||||
|
Theme,
|
||||||
|
Tooltip,
|
||||||
|
Typography
|
||||||
|
} from "@mui/material";
|
||||||
|
import { green, red } from "@mui/material/colors";
|
||||||
|
// import { Theme } from "@mui/material/styles/createMuiTheme";
|
||||||
|
import {
|
||||||
|
Add as AddIcon,
|
||||||
|
Link as LinkIcon,
|
||||||
|
LinkOff,
|
||||||
|
RemoveCircle as RemoveCircleIcon
|
||||||
|
} from "@mui/icons-material";
|
||||||
|
// import { MobileDateTimePicker } from "@mui/utils/pi";
|
||||||
|
import { MobileDateTimePicker } from '@mui/x-date-pickers';
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
|
import { usePermissionAPI, usePrevious, useSnackbar } from "hooks";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
import { binScope, Scope, ShareableLink, User } from "models";
|
||||||
|
import moment, { Moment } from "moment";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import { openSnackbar } from "providers/Snackbar";
|
||||||
|
// import { Status } from "@sentry/react";
|
||||||
|
import { useBinAPI, useGateAPI } from "providers";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
dialogContent: {
|
||||||
|
marginTop: theme.spacing(2)
|
||||||
|
},
|
||||||
|
formContainer: {
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap"
|
||||||
|
},
|
||||||
|
formControl: {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
minWidth: 160,
|
||||||
|
width: "100%"
|
||||||
|
},
|
||||||
|
emailForm: {
|
||||||
|
paddingBottom: theme.spacing(2)
|
||||||
|
},
|
||||||
|
fabContainer: {
|
||||||
|
position: "relative",
|
||||||
|
minHeight: "64px"
|
||||||
|
},
|
||||||
|
addIcon: {
|
||||||
|
color: green[500],
|
||||||
|
"&:hover": {
|
||||||
|
color: green[600]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeIcon: {
|
||||||
|
color: red[500],
|
||||||
|
"&:hover": {
|
||||||
|
color: red[600]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grey: {
|
||||||
|
color: theme.palette.text.secondary
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
scope: Scope;
|
||||||
|
label: string;
|
||||||
|
permissions: pond.Permission[];
|
||||||
|
isDialogOpen: boolean;
|
||||||
|
closeDialogCallback: Function;
|
||||||
|
useImitation?: boolean;
|
||||||
|
sharedUsers?: User[]; //including the users that the object/team is already shared to will prevent users from sharing again to an existing user
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ShareObject(props: Props) {
|
||||||
|
const binAPI = useBinAPI();
|
||||||
|
const classes = useStyles();
|
||||||
|
const permissionAPI = usePermissionAPI();
|
||||||
|
const { info, success, error } = useSnackbar();
|
||||||
|
const { scope, label, permissions, isDialogOpen, closeDialogCallback, sharedUsers } = props;
|
||||||
|
const [email, setEmail] = useState<string>("");
|
||||||
|
const [sharedPermissions, setSharedPermissions] = useState<pond.Permission[]>([
|
||||||
|
pond.Permission.PERMISSION_READ
|
||||||
|
]);
|
||||||
|
const [tab, setTab] = useState<number>(0);
|
||||||
|
const prevTab = usePrevious(tab);
|
||||||
|
const [isLoadingLinks, setIsLoadingLinks] = useState<boolean>(false);
|
||||||
|
const [shareableLinks, setShareableLinks] = useState<ShareableLink[]>([]);
|
||||||
|
const [newLinkExpiration, setNewLinkExpiration] = useState<Moment>(moment().add(1, "week"));
|
||||||
|
const [isNewLinkInfinite, setIsNewLinkInfinite] = useState<boolean>(false);
|
||||||
|
const [openExistingUser, setOpenExistingUser] = useState(false);
|
||||||
|
const [useImitation] = useState<boolean>(
|
||||||
|
props.useImitation !== undefined ? props.useImitation : true
|
||||||
|
);
|
||||||
|
const gateAPI = useGateAPI();
|
||||||
|
|
||||||
|
const share = () => {
|
||||||
|
permissionAPI
|
||||||
|
.shareObject(scope, email, sharedPermissions, useImitation)
|
||||||
|
.then((result: any) => {
|
||||||
|
let shareBins = true;
|
||||||
|
if (result && result.data && result.data.existing) {
|
||||||
|
success(label + " was shared with " + email);
|
||||||
|
} else {
|
||||||
|
success(email + " was sent an email to sign up");
|
||||||
|
shareBins = false;
|
||||||
|
}
|
||||||
|
let successBins = true;
|
||||||
|
if (scope.kind === "binyard" && shareBins) {
|
||||||
|
binAPI.listBins(1000, 0, "asc", "name", scope.key).then(resp => {
|
||||||
|
resp.data.bins.forEach(bin => {
|
||||||
|
if (bin.settings) {
|
||||||
|
let newScope = binScope(bin.settings?.key);
|
||||||
|
permissionAPI.shareObject(newScope, email, sharedPermissions).catch(err => {
|
||||||
|
successBins = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (!successBins) {
|
||||||
|
openSnackbar("error", "One or more bins failed to share with " + email);
|
||||||
|
} else {
|
||||||
|
success("Bins shared with " + email);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let successGates = true;
|
||||||
|
if (scope.kind === "terminal" && shareBins) {
|
||||||
|
gateAPI
|
||||||
|
.listGates(
|
||||||
|
1000,
|
||||||
|
0,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
[scope.key],
|
||||||
|
[scope.kind]
|
||||||
|
)
|
||||||
|
.then(resp => {
|
||||||
|
resp.data.gates.forEach(gate => {
|
||||||
|
if (gate) {
|
||||||
|
let newScope = { key: gate.key, kind: "gate" } as Scope;
|
||||||
|
permissionAPI.shareObject(newScope, email, sharedPermissions).catch(err => {
|
||||||
|
successGates = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (!successGates) {
|
||||||
|
openSnackbar("error", "One or more Gates failed to share");
|
||||||
|
} else {
|
||||||
|
success("Gates shared");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closeAfterShare();
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
error("Unable to share " + label + " with " + email);
|
||||||
|
close();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadShareableLinks = useCallback(() => {
|
||||||
|
setIsLoadingLinks(true);
|
||||||
|
permissionAPI
|
||||||
|
.listShareableLinks(scope)
|
||||||
|
.then((response: any) => {
|
||||||
|
let rawShareableLinks = response.data.links ? response.data.links : [];
|
||||||
|
let rShareableLinks = rawShareableLinks.map((rawShareableLink: any) =>
|
||||||
|
ShareableLink.any(rawShareableLink)
|
||||||
|
);
|
||||||
|
setShareableLinks(rShareableLinks);
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
setShareableLinks([]);
|
||||||
|
console.log("Error occured while loading shareable links");
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsLoadingLinks(false);
|
||||||
|
});
|
||||||
|
}, [permissionAPI, scope]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tab === 1 && prevTab !== tab) {
|
||||||
|
loadShareableLinks();
|
||||||
|
}
|
||||||
|
}, [loadShareableLinks, prevTab, tab]);
|
||||||
|
|
||||||
|
const getNeverExpires = () => {
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const createShareableLink = () => {
|
||||||
|
const expiration = isNewLinkInfinite ? getNeverExpires() : newLinkExpiration.toISOString();
|
||||||
|
permissionAPI
|
||||||
|
.addShareableLink(scope, expiration)
|
||||||
|
.then((response: any) => {
|
||||||
|
loadShareableLinks();
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.log("Error occured while creating shareable link");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const revokeShareableLink = (code: string) => {
|
||||||
|
permissionAPI
|
||||||
|
.removeShareableLink(scope, code)
|
||||||
|
.then((response: any) => {
|
||||||
|
loadShareableLinks();
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.log("Error occured while removing the shareable link");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getShareableLinkURL = (code: string): string => {
|
||||||
|
const base = window.location.origin;
|
||||||
|
return base + "/" + scope.kind + "s/" + code;
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyShareableLinkToClipboard = (code: string) => {
|
||||||
|
const url = getShareableLinkURL(code);
|
||||||
|
navigator.clipboard.writeText(url);
|
||||||
|
info("Copied link to clipboard");
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeAfterShare = () => {
|
||||||
|
closeDialogCallback(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
closeDialogCallback(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeEmail = (event: any) => {
|
||||||
|
setEmail(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changePermissions = (event: any) => {
|
||||||
|
let updatedSharedPermissions: Array<pond.Permission> = cloneDeep(sharedPermissions);
|
||||||
|
let permission: pond.Permission;
|
||||||
|
switch (event.target.value) {
|
||||||
|
case "1":
|
||||||
|
permission = pond.Permission.PERMISSION_USERS;
|
||||||
|
break;
|
||||||
|
case "2":
|
||||||
|
permission = pond.Permission.PERMISSION_READ;
|
||||||
|
break;
|
||||||
|
case "3":
|
||||||
|
permission = pond.Permission.PERMISSION_WRITE;
|
||||||
|
break;
|
||||||
|
case "4":
|
||||||
|
permission = pond.Permission.PERMISSION_SHARE;
|
||||||
|
break;
|
||||||
|
case "5":
|
||||||
|
permission = pond.Permission.PERMISSION_BILLING;
|
||||||
|
break;
|
||||||
|
case "6":
|
||||||
|
permission = pond.Permission.PERMISSION_FILE_MANAGEMENT;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
permission = pond.Permission.PERMISSION_INVALID;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updatedSharedPermissions.includes(permission)) {
|
||||||
|
updatedSharedPermissions = updatedSharedPermissions.filter(
|
||||||
|
(curPermission: pond.Permission) => curPermission !== permission
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
updatedSharedPermissions.push(permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSharedPermissions(updatedSharedPermissions);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeTab = (event: any, newTab: number) => {
|
||||||
|
setTab(newTab);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isValid = () => {
|
||||||
|
return email.trim() !== "" && sharedPermissions.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// UI BEGINS
|
||||||
|
|
||||||
|
const emailForm = () => {
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
label="Email"
|
||||||
|
value={email}
|
||||||
|
onChange={changeEmail}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
className={classes.emailForm}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const permissionsForm = () => {
|
||||||
|
return (
|
||||||
|
<FormControl component="fieldset" fullWidth variant="outlined">
|
||||||
|
<FormLabel component="legend">Permissions</FormLabel>
|
||||||
|
<FormGroup>
|
||||||
|
{permissions.includes(pond.Permission.PERMISSION_READ) && (
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
disabled={sharedPermissions.includes(pond.Permission.PERMISSION_READ)}
|
||||||
|
checked={sharedPermissions.includes(pond.Permission.PERMISSION_READ)}
|
||||||
|
onChange={changePermissions}
|
||||||
|
value={pond.Permission.PERMISSION_READ as pond.Permission}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="View"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{permissions.includes(pond.Permission.PERMISSION_WRITE) && (
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={sharedPermissions.includes(pond.Permission.PERMISSION_WRITE)}
|
||||||
|
onChange={changePermissions}
|
||||||
|
value={pond.Permission.PERMISSION_WRITE as pond.Permission}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Edit"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{permissions.includes(pond.Permission.PERMISSION_SHARE) && (
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={sharedPermissions.includes(pond.Permission.PERMISSION_SHARE)}
|
||||||
|
onChange={changePermissions}
|
||||||
|
value={pond.Permission.PERMISSION_SHARE as pond.Permission}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Share"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{permissions.includes(pond.Permission.PERMISSION_USERS) && (
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={sharedPermissions.includes(pond.Permission.PERMISSION_USERS)}
|
||||||
|
onChange={changePermissions}
|
||||||
|
value={pond.Permission.PERMISSION_USERS as pond.Permission}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Manage Users"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{permissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT) && (
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={sharedPermissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT)}
|
||||||
|
onChange={changePermissions}
|
||||||
|
value={pond.Permission.PERMISSION_FILE_MANAGEMENT as pond.Permission}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Files"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{permissions.includes(pond.Permission.PERMISSION_BILLING) && (
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={sharedPermissions.includes(pond.Permission.PERMISSION_BILLING)}
|
||||||
|
onChange={changePermissions}
|
||||||
|
value={pond.Permission.PERMISSION_BILLING as pond.Permission}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Billing"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</FormGroup>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const emailSharing = () => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{emailForm()}
|
||||||
|
{permissionsForm()}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const existingUserDialog = () => {
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog
|
||||||
|
open={openExistingUser}
|
||||||
|
onClose={() => {
|
||||||
|
setOpenExistingUser(false);
|
||||||
|
close();
|
||||||
|
}}>
|
||||||
|
<DialogTitle>User Already exists</DialogTitle>
|
||||||
|
<DialogContent>This user is already part of {label}.</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setOpenExistingUser(false);
|
||||||
|
close();
|
||||||
|
}}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const shareableLinksList = () => {
|
||||||
|
const now = moment();
|
||||||
|
let items: any[] = [];
|
||||||
|
shareableLinks.forEach((link: ShareableLink) => {
|
||||||
|
const expiration = moment(link.settings.expiration);
|
||||||
|
const neverExpires = link.settings.expiration === getNeverExpires();
|
||||||
|
const expired = !neverExpires && expiration < now;
|
||||||
|
items.push(
|
||||||
|
<ListItem key={link.key()}>
|
||||||
|
{expired ? (
|
||||||
|
<IconButton disabled>
|
||||||
|
<LinkOff />
|
||||||
|
</IconButton>
|
||||||
|
) : (
|
||||||
|
<Tooltip title="Click to copy link">
|
||||||
|
<IconButton onClick={() => copyShareableLinkToClipboard(link.key())}>
|
||||||
|
<LinkIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
expired ? (
|
||||||
|
<span className={classes.grey}>{"/devices/" + link.key()}</span>
|
||||||
|
) : (
|
||||||
|
<Link href={getShareableLinkURL(link.key())} target="_blank">
|
||||||
|
{"/devices/" + link.key()}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
secondary={
|
||||||
|
neverExpires ? (
|
||||||
|
"Never expires"
|
||||||
|
) : (
|
||||||
|
<Tooltip title={expiration.calendar()}>
|
||||||
|
<span>{(expired ? "Expired " : "Expires ") + expiration.fromNow()}</span>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Tooltip title="Revoke shareable link">
|
||||||
|
<IconButton onClick={() => revokeShareableLink(link.key())}>
|
||||||
|
<RemoveCircleIcon className={classes.removeIcon} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<List>
|
||||||
|
{isLoadingLinks ? <CircularProgress /> : <React.Fragment>{items}</React.Fragment>}
|
||||||
|
{shareableLinks.length > 0 && <Divider variant="middle" />}
|
||||||
|
<ListItem>
|
||||||
|
<FormGroup row>
|
||||||
|
<MobileDateTimePicker
|
||||||
|
disabled={isNewLinkInfinite}
|
||||||
|
// inputRef={props => <TextField {...props} helperText="" />}
|
||||||
|
label="Expiration Date"
|
||||||
|
value={newLinkExpiration}
|
||||||
|
onChange={date => setNewLinkExpiration(date as Moment)}
|
||||||
|
disablePast
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={isNewLinkInfinite}
|
||||||
|
onChange={(_, checked) => setIsNewLinkInfinite(checked)}
|
||||||
|
value="isNewLinkInfinite"
|
||||||
|
color="secondary"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Never expires"
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Tooltip title="Create new shareable link">
|
||||||
|
<IconButton
|
||||||
|
className={classes.addIcon}
|
||||||
|
color="default"
|
||||||
|
onClick={() => createShareableLink()}>
|
||||||
|
<AddIcon className={classes.addIcon} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = () => {
|
||||||
|
switch (tab) {
|
||||||
|
case 1:
|
||||||
|
return <React.Fragment>{shareableLinksList()}</React.Fragment>;
|
||||||
|
default:
|
||||||
|
return <React.Fragment>{emailSharing()}</React.Fragment>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{existingUserDialog()}
|
||||||
|
<ResponsiveDialog
|
||||||
|
maxWidth="sm"
|
||||||
|
fullWidth
|
||||||
|
open={isDialogOpen}
|
||||||
|
onClose={close}
|
||||||
|
aria-labelledby="share-dialog-title">
|
||||||
|
<DialogTitle id="share-dialog-title">
|
||||||
|
Share
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
{label}
|
||||||
|
</Typography>
|
||||||
|
</DialogTitle>
|
||||||
|
{scope.kind === "device" && (
|
||||||
|
<Tabs value={tab} onChange={changeTab}>
|
||||||
|
<Tab label="Share by Email" />
|
||||||
|
<Tab label="Shareable Links" />
|
||||||
|
</Tabs>
|
||||||
|
)}
|
||||||
|
<Divider />
|
||||||
|
<DialogContent className={classes.dialogContent}>{content()}</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={close} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
{tab === 0 && (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
//check here if the email they entered is already in the list of shared users if it was passed in
|
||||||
|
let hasUser = false;
|
||||||
|
sharedUsers?.forEach(user => {
|
||||||
|
if (user.settings.email === email) hasUser = true;
|
||||||
|
});
|
||||||
|
if (hasUser) {
|
||||||
|
setOpenExistingUser(true);
|
||||||
|
} else {
|
||||||
|
share();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
disabled={!isValid()}>
|
||||||
|
Share
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
32
src/utils/environment.ts
Normal file
32
src/utils/environment.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
function getEnvironment() {
|
||||||
|
switch (process.env.NODE_ENV) {
|
||||||
|
case "development":
|
||||||
|
return "development";
|
||||||
|
case "production":
|
||||||
|
if (window.location.hostname.includes("staging")) {
|
||||||
|
return "staging";
|
||||||
|
} else {
|
||||||
|
return "production";
|
||||||
|
}
|
||||||
|
case "test":
|
||||||
|
return "test";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isOffline() {
|
||||||
|
return process.env.REACT_APP_IS_OFFLINE === "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDevelopment() {
|
||||||
|
return getEnvironment() === "development";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isProduction() {
|
||||||
|
return getEnvironment() === "production";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isStaging() {
|
||||||
|
return getEnvironment() === "staging";
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue