teams list now renders
This commit is contained in:
parent
17c559bdfc
commit
e95b654d7f
81 changed files with 6132 additions and 40 deletions
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,
|
||||
// useMetricAPI,
|
||||
// useMineAPI,
|
||||
// usePermissionAPI,
|
||||
usePermissionAPI,
|
||||
// usePreferenceAPI,
|
||||
// useSecurity,
|
||||
// useSnackbar,
|
||||
useSnackbar,
|
||||
// useTagAPI,
|
||||
// useUsageAPI,
|
||||
// useUserAPI
|
||||
useUserAPI
|
||||
} from "providers";
|
||||
// export * from "./useDebounce";
|
||||
// export * from "./useForceUpdate";
|
||||
// export * from "./useInterval";
|
||||
// export * from "./usePrevious";
|
||||
export * from "./usePrevious";
|
||||
export * from "./useThemeType";
|
||||
export * from "./useWidth";
|
||||
// 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 "./Interaction";
|
||||
export * from "./Scope";
|
||||
// export * from "./ShareableLink";
|
||||
export * from "./ShareableLink";
|
||||
// export * from "./Site";
|
||||
// export * from "./Tag";
|
||||
// export * from "./Task";
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import { Box } from "@mui/material";
|
||||
import { useMobile } from "hooks";
|
||||
import PageContainer from "pages/PageContainer";
|
||||
// import TeamList from "teams/TeamList";
|
||||
import TeamList from "teams/TeamList";
|
||||
|
||||
export default function Teams() {
|
||||
const isMobile = useMobile();
|
||||
return (
|
||||
<PageContainer>
|
||||
<Box paddingY={isMobile ? 0.5 : 1} paddingX={isMobile ? 1 : 2}>
|
||||
{/* <TeamList /> */}
|
||||
Teams!
|
||||
<TeamList />
|
||||
{/* Teams! */}
|
||||
</Box>
|
||||
</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 {
|
||||
// useBackpackAPI,
|
||||
// useBinAPI,
|
||||
useBinAPI,
|
||||
// useBinYardAPI,
|
||||
// useNoteAPI,
|
||||
// useComponentAPI,
|
||||
|
|
@ -22,11 +22,12 @@ export {
|
|||
// useInteractionsAPI,
|
||||
// useMeasurementsWebsocket,
|
||||
// useMetricAPI,
|
||||
// usePermissionAPI,
|
||||
usePermissionAPI,
|
||||
// usePreferenceAPI,
|
||||
// useSiteAPI,
|
||||
// useTagAPI,
|
||||
// useTeamAPI,
|
||||
useTeamAPI,
|
||||
useImagekitAPI,
|
||||
// useTaskAPI,
|
||||
// useUsageAPI,
|
||||
useUserAPI,
|
||||
|
|
@ -35,7 +36,7 @@ export {
|
|||
// useMineAPI,
|
||||
// useKeyManagerAPI,
|
||||
// useTerminalAPI,
|
||||
// useGateAPI,
|
||||
useGateAPI,
|
||||
// useObjectHeaterAPI,
|
||||
// useMutationAPI,
|
||||
// useGrainBagAPI,
|
||||
|
|
@ -43,7 +44,7 @@ export {
|
|||
// useFileControllerAPI
|
||||
} from "./pond/pond";
|
||||
// export { SecurityContext, useSecurity } from "./security";
|
||||
// export { SnackbarContext, useSnackbar } from "./Snackbar";
|
||||
export { SnackbarContext, useSnackbar } from "./Snackbar";
|
||||
export * from "./StateContainer";
|
||||
// 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 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 => {
|
||||
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;
|
||||
};
|
||||
|
||||
// export const objectQueryParams = (scope: Scope) => {
|
||||
// return "?" + scope.kind + "_id=" + scope.key;
|
||||
// };
|
||||
export const objectQueryParams = (scope: Scope) => {
|
||||
return "?" + scope.kind + "_id=" + scope.key;
|
||||
};
|
||||
|
||||
export default function PondProvider(props: PropsWithChildren<any>) {
|
||||
const { children } = props;
|
||||
|
||||
return (
|
||||
<UserProvider>
|
||||
{children}
|
||||
<TeamProvider>
|
||||
{children}
|
||||
</TeamProvider>
|
||||
</UserProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
useUserAPI,
|
||||
useTeamAPI,
|
||||
useImagekitAPI,
|
||||
usePermissionAPI,
|
||||
useBinAPI,
|
||||
useGateAPI
|
||||
};
|
||||
|
|
|
|||
|
|
@ -73,8 +73,8 @@ export default function TeamProvider(props: PropsWithChildren<Props>) {
|
|||
const updatePreferences = (
|
||||
key: string,
|
||||
preferences: pond.TeamPreferences,
|
||||
keys?: string[],
|
||||
types?: string[]
|
||||
// keys?: string[],
|
||||
// types?: string[]
|
||||
) => {
|
||||
return put(pondURL("/teams/" + key + "/preferences"), preferences);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@
|
|||
// import { Scope, User } from "models";
|
||||
// import { pond } from "protobuf-ts/pond";
|
||||
import { createContext, PropsWithChildren, useContext } from "react";
|
||||
// import { objectQueryParams, pondURL } from "./pond";
|
||||
import { objectQueryParams, pondURL } from "./pond";
|
||||
// import { or } from "utils";
|
||||
// import { useGlobalState } from "providers";
|
||||
import { useGlobalState } from "providers";
|
||||
// import { AxiosResponse } from "axios";
|
||||
import { useHTTP } from "../http";
|
||||
import { pondURL } from "./pond";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { User } from "../../models/user";
|
||||
import { Scope } from "models";
|
||||
|
||||
// export interface ListUsersResponse {
|
||||
// users: User[];
|
||||
|
|
@ -26,9 +26,9 @@ export interface IUserAPIContext {
|
|||
// search?: string
|
||||
// ) => Promise<ListUsersResponse>;
|
||||
// listProfiles: () => Promise<pond.UserProfile[]>;
|
||||
// listObjectUsers: (scope: Scope) => Promise<any>;
|
||||
// updateObjectUsers: (scope: Scope, users: pond.IUser[]) => Promise<any>;
|
||||
getUser: (id: string) => Promise<User>;
|
||||
listObjectUsers: (scope: Scope) => Promise<any>;
|
||||
updateObjectUsers: (scope: Scope, users: pond.IUser[]) => Promise<any>;
|
||||
getUser: (id: string, scope?: Scope) => Promise<User>;
|
||||
getUserWithTeam: (
|
||||
id: string,
|
||||
team?: string,
|
||||
|
|
@ -53,8 +53,8 @@ export const UserAPIContext = createContext<IUserAPIContext>({} as IUserAPIConte
|
|||
|
||||
export default function UserProvider(props: PropsWithChildren<any>) {
|
||||
const { children } = props;
|
||||
const { get, /*put*/ } = useHTTP();
|
||||
// const [{ as }] = useGlobalState();
|
||||
const { get, put } = useHTTP();
|
||||
const [{ as }] = useGlobalState();
|
||||
|
||||
// const listUsers = (
|
||||
// limit: number,
|
||||
|
|
@ -104,20 +104,20 @@ export default function UserProvider(props: PropsWithChildren<any>) {
|
|||
// });
|
||||
// };
|
||||
|
||||
// const listObjectUsers = (scope: Scope) => {
|
||||
// let sql = "/" + scope.kind + "s/" + scope.key + "/users";
|
||||
// if (as) sql = sql + "?as=" + as;
|
||||
// return get(pondURL(sql));
|
||||
// };
|
||||
const listObjectUsers = (scope: Scope) => {
|
||||
let sql = "/" + scope.kind + "s/" + scope.key + "/users";
|
||||
if (as) sql = sql + "?as=" + as;
|
||||
return get(pondURL(sql));
|
||||
};
|
||||
|
||||
// const updateObjectUsers = (scope: Scope, users: pond.IUser[]) => {
|
||||
// return put(pondURL("/users" + objectQueryParams(scope)), { users });
|
||||
// };
|
||||
const updateObjectUsers = (scope: Scope, users: pond.IUser[]) => {
|
||||
return put(pondURL("/users" + objectQueryParams(scope)), { users });
|
||||
};
|
||||
|
||||
// const getUser = (id: string): Promise<User> => {
|
||||
const getUser = (id: string): Promise<any> => {
|
||||
const getUser = (id: string, scope?: Scope): Promise<any> => {
|
||||
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) {
|
||||
get(pondURL(partial))
|
||||
.then((response: any) => resolve(response.data))
|
||||
|
|
@ -218,8 +218,8 @@ export default function UserProvider(props: PropsWithChildren<any>) {
|
|||
value={{
|
||||
// listUsers,
|
||||
// listProfiles,
|
||||
// listObjectUsers,
|
||||
// updateObjectUsers,
|
||||
listObjectUsers,
|
||||
updateObjectUsers,
|
||||
getUser,
|
||||
getUserWithTeam,
|
||||
// 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