implemented access object but didn't add the file to git haha silly me.

This commit is contained in:
Carter 2025-03-06 11:55:27 -06:00
parent eff8a544ba
commit b0274ad639
2 changed files with 535 additions and 0 deletions

474
src/user/AccessObject.tsx Normal file
View file

@ -0,0 +1,474 @@
import {
Avatar,
AppBar,
Box,
CircularProgress,
IconButton,
List,
ListItem,
ListItemText,
ListItemAvatar,
Toolbar,
Tooltip,
Typography,
ToggleButtonGroup,
ToggleButton
} from "@mui/material";
import {Close as CloseIcon} from "@mui/icons-material";
// import { ToggleButton, ToggleButtonGroup } from "@mui";
import ResponsiveDialog from "common/ResponsiveDialog";
import SearchBar from "common/SearchBar";
import { or } from "utils";
import DeviceOverview from "device/DeviceOverview";
import GroupOverview from "group/GroupOverview";
import { useMobile, usePrevious, useSnackbar } from "hooks";
import { Device, Group, Team, newScope } from "models";
import { pond } from "protobuf-ts/pond";
import {
useDeviceAPI,
useGlobalState,
useGroupAPI,
usePermissionAPI,
useTeamAPI,
useUserAPI
} from "providers";
import { useCallback, useEffect, useState } from "react";
import InfiniteScroll from "react-infinite-scroller";
// import { useHistory } from "react-router";
import { capitalize } from "utils/strings";
import { SupervisedUserCircle as TeamIcon } from "@mui/icons-material";
import { useNavigate } from "react-router-dom";
interface Props {
isOpen: boolean;
closeDialogCallback: () => void;
}
const REQUEST_LIMIT = 25;
export default function AccessObject(props: Props) {
const [{ user }, dispatch] = useGlobalState();
// const history = useHistory();
const navigate = useNavigate()
const isMobile = useMobile();
const { error, info } = useSnackbar();
const permissionAPI = usePermissionAPI();
const deviceAPI = useDeviceAPI();
const groupAPI = useGroupAPI();
const teamAPI = useTeamAPI();
const userAPI = useUserAPI();
const { isOpen, closeDialogCallback } = props;
const prevOpen = usePrevious(isOpen);
const [kind, setKind] = useState<string>("device");
const prevKind = usePrevious(kind);
const [searchValue, setSearchValue] = useState<string>("");
const prevSearchValue = usePrevious(searchValue);
const [loading, setLoading] = useState<boolean>(false);
const [devices, setDevices] = useState<Device[]>([]);
const [groups, setGroups] = useState<Group[]>([]);
const [teams, setTeams] = useState<Team[]>([]);
const [groupsOffset, setGroupsOffset] = useState<number>(0);
const [groupsTotal, setGroupsTotal] = useState<number>(0);
const [teamsOffset, setTeamsOffset] = useState<number>(0);
const [teamsTotal, setTeamsTotal] = useState<number>(0);
const [devicesOffset, setDevicesOffset] = useState<number>(0);
const [devicesTotal, setDevicesTotal] = useState<number>(0);
const close = () => {
closeDialogCallback();
};
const getAccess = (key: number | string, name: string, url: string, useImitation = false) => {
user.settings.useTeam = false;
dispatch({ key: "as", value: "" });
userAPI.updateUser(user.id(), user.protobuf()).then(() => {
info("Will no longer view as team by default");
permissionAPI
.shareObject(
newScope(kind, key.toString()),
user.settings.email,
[
pond.Permission.PERMISSION_USERS,
pond.Permission.PERMISSION_READ,
pond.Permission.PERMISSION_WRITE,
pond.Permission.PERMISSION_SHARE
],
useImitation
)
.then((_response: any) => {
// history.push(url);
navigate(url)
close();
})
.catch((_err: any) => {
error("Failed to grant you access to " + name);
});
});
};
const load = useCallback(() => {
setLoading(true);
kind === "group"
? groupAPI
.listGroups(REQUEST_LIMIT, 0, "asc", undefined, searchValue, true)
.then((response: any) => {
let rGroupsOffset: number = response.data.nextOffset ? response.data.nextOffset : 0;
let rGroupsTotal: number = response.data.total ? response.data.total : 0;
let groups: Group[] = or(response.data.groups, []).map((g: any) => {
return Group.create(pond.Group.fromObject(g));
});
setGroups(groups);
setGroupsOffset(rGroupsOffset);
setGroupsTotal(rGroupsTotal);
})
.catch((_error: any) => {
setGroups([]);
setGroupsOffset(0);
setGroupsTotal(0);
})
.finally(() => setLoading(false))
: kind === "team"
? teamAPI
.listTeams(REQUEST_LIMIT, 0, "asc", undefined, searchValue, true)
.then(response => {
let rTeamsOffset: number = response.data.nextOffset ? response.data.nextOffset : 0;
let rTeamsTotal: number = response.data.total ? response.data.total : 0;
let teams: Team[] = or(response.data.teams, []).map((g: any) => {
return Team.create(pond.Team.fromObject(g));
});
setTeams(teams);
setTeamsOffset(rTeamsOffset);
setTeamsTotal(rTeamsTotal);
})
.finally(() => setLoading(false))
: deviceAPI
.list(
REQUEST_LIMIT,
0,
"asc",
undefined,
searchValue,
undefined,
undefined,
undefined,
true
)
.then((response: any) => {
let rDevicesOffset: number = response.data.nextOffset ? response.data.nextOffset : 0;
let rDevicesTotal: number = response.data.total ? response.data.total : 0;
let devices: Device[] = or(response.data.devices, []).map((d: any) => {
return Device.create(pond.Device.fromObject(d));
});
setDevices(devices);
setDevicesTotal(rDevicesTotal);
setDevicesOffset(rDevicesOffset);
})
.catch((_error: any) => {
setDevices([]);
setDevicesOffset(0);
setDevicesTotal(0);
})
.finally(() => setLoading(false));
}, [deviceAPI, groupAPI, teamAPI, kind, searchValue]);
const loadMoreDevices = useCallback(() => {
deviceAPI
.list(
REQUEST_LIMIT,
devicesOffset,
"asc",
undefined,
searchValue,
undefined,
undefined,
undefined,
true
)
.then((response: any) => {
let rDevicesOffset: number = response.data.nextOffset ? response.data.nextOffset : 0;
let rDevicesTotal: number = response.data.total ? response.data.total : 0;
let moreDevices: Device[] = or(response.data.devices, []).map((d: any) => {
return Device.create(pond.Device.fromObject(d));
});
setDevices([...new Set([...devices, ...moreDevices])]);
setDevicesTotal(rDevicesTotal);
setDevicesOffset(rDevicesOffset);
})
.catch((_error: any) => {
setDevices([]);
setDevicesOffset(0);
setDevicesTotal(0);
});
}, [deviceAPI, devicesOffset, searchValue, devices]);
const loadMoreGroups = useCallback(() => {
groupAPI
.listGroups(REQUEST_LIMIT, groupsOffset, "asc", undefined, searchValue, true)
.then((response: any) => {
let rGroupsOffset: number = response.data.nextOffset ? response.data.nextOffset : 0;
let rGroupsTotal: number = response.data.total ? response.data.total : 0;
let moreGroups: Group[] = or(response.data.groups, []).map((g: any) => {
return Group.create(pond.Group.fromObject(g));
});
setGroups([...new Set([...groups, ...moreGroups])]);
setGroupsOffset(rGroupsOffset);
setGroupsTotal(rGroupsTotal);
})
.catch((_error: any) => {
setGroups([]);
setGroupsOffset(0);
setGroupsTotal(0);
});
}, [groupAPI, groupsOffset, searchValue, groups]);
const loadMoreTeams = useCallback(() => {
teamAPI
.listTeams(REQUEST_LIMIT, teamsOffset, "asc", undefined, searchValue, true)
.then((response: any) => {
let rTeamsOffset: number = response.data.nextOffset ? response.data.nextOffset : 0;
let rTeamsTotal: number = response.data.total ? response.data.total : 0;
let moreTeams: Team[] = or(response.data.teams, []).map((g: any) => {
return Team.create(pond.Team.fromObject(g));
});
setTeams([...new Set([...teams, ...moreTeams])]);
setTeamsOffset(rTeamsOffset);
setTeamsTotal(rTeamsTotal);
})
.catch((_error: any) => {
setTeams([]);
setTeamsOffset(0);
setTeamsTotal(0);
});
}, [teamAPI, teamsOffset, searchValue, teams]);
useEffect(() => {
if (isOpen && (prevSearchValue !== searchValue || prevKind !== kind || isOpen !== prevOpen)) {
load();
}
}, [
prevSearchValue,
searchValue,
load,
kind,
prevKind,
loadMoreDevices,
loadMoreGroups,
isOpen,
prevOpen
]);
const groupURL = (group: Group) => {
return "/groups/" + group.id().toString();
};
const groupItem = (g: Group) => {
return (
<ListItem
key={g.id()}
divider
onClick={() => getAccess(g.id(), g.name(), groupURL(g))}>
<ListItemText
primary={
<Tooltip title={"ID: " + g.id()}>
<span>{g.name()}</span>
</Tooltip>
}
secondaryTypographyProps={{ component: "span" }}
secondary={<GroupOverview group={g} variant="row" />}
/>
</ListItem>
);
};
const deviceURL = (device: Device) => {
return "/devices/" + device.id().toString();
};
const teamURL = (team: Team) => {
return "/teams/" + team.key();
};
const deviceItem = (d: Device) => {
return (
<ListItem
key={d.id()}
divider
onClick={() => getAccess(d.id(), d.name(), deviceURL(d))}>
<ListItemText
primary={
<Tooltip title={"ID: " + d.id()}>
<span>{d.name()}</span>
</Tooltip>
}
secondaryTypographyProps={{ component: "span" }}
secondary={<DeviceOverview device={d} components={[]} disableAddTag />}
/>
</ListItem>
);
};
const teamItem = (d: Team) => {
return (
<ListItem
key={d.key()}
divider
onClick={() => getAccess(d.key(), d.name(), teamURL(d), false)}>
<ListItemAvatar>
<Avatar src={d.settings.avatar}>
{/* Draw the team icon id src icon errors */}
<TeamIcon style={{ fontSize: 38 }} />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={
<Tooltip title={"Key: " + d.key()}>
<span>{d.name()}</span>
</Tooltip>
}
secondaryTypographyProps={{ component: "span" }}
secondary={d.settings.info}
/>
</ListItem>
);
};
return (
<ResponsiveDialog
open={isOpen}
onClose={close}
aria-labelledby="access-object-dialog"
fullScreen>
<AppBar position="sticky">
<Toolbar>
<IconButton edge="start" color="inherit" onClick={close} aria-label="close">
<CloseIcon />
</IconButton>
<Typography variant="h6" noWrap>
Access {capitalize(kind)}
</Typography>
<Box flex={1} display="flex" justifyContent="flex-end" marginLeft={2}>
<ToggleButtonGroup
value={kind}
size="small"
exclusive
onChange={(_, newKind) => newKind !== null && setKind(newKind)}
aria-label="object-kind">
<ToggleButton value="device" aria-label="device-kind">
Devices
</ToggleButton>
<ToggleButton value="group" aria-label="group-kind">
Groups
</ToggleButton>
<ToggleButton value="team" aria-label="team-kind">
Teams
</ToggleButton>
</ToggleButtonGroup>
</Box>
{!isMobile && (
<Box marginLeft={2}>
<SearchBar value={searchValue} onChange={value => setSearchValue(value)} />
</Box>
)}
</Toolbar>
{isMobile && (
<Toolbar>
<Box flex={1} display="flex" justifyContent="flex-end">
<SearchBar value={searchValue} onChange={value => setSearchValue(value)} />
</Box>
</Toolbar>
)}
</AppBar>
{loading ? (
<Box padding={4} height="100%" display="flex" justifyContent="center" alignItems="center">
<CircularProgress />
</Box>
) : kind === "group" ? (
<InfiniteScroll
pageStart={0}
loadMore={() => loadMoreGroups()}
hasMore={groups.length < groupsTotal}
useWindow={false}
loader={
<Box
display="flex"
justifyContent="center"
alignContent="center"
marginTop={2}
key="load-devices">
<CircularProgress />
</Box>
}>
<List style={{ height: "100%", minHeight: "250px" }}>
{groups.length === 0 ? (
<ListItem style={{ justifyContent: "center", paddingTop: "16px" }}>
<Typography color="textSecondary" variant="h5">
No groups found
</Typography>
</ListItem>
) : (
groups.map(g => groupItem(g))
)}
</List>
</InfiniteScroll>
) : kind === "team" ? (
<InfiniteScroll
pageStart={0}
loadMore={() => loadMoreTeams()}
hasMore={teams.length < teamsTotal}
useWindow={false}
loader={
<Box
display="flex"
justifyContent="center"
alignContent="center"
marginTop={2}
key="load-devices">
<CircularProgress />
</Box>
}>
<List style={{ height: "100%", minHeight: "250px" }}>
{teams.length === 0 ? (
<ListItem style={{ justifyContent: "center", paddingTop: "16px" }}>
<Typography color="textSecondary" variant="h5">
No teams found
</Typography>
</ListItem>
) : (
teams.map(t => teamItem(t))
)}
</List>
</InfiniteScroll>
) : (
<InfiniteScroll
pageStart={0}
loadMore={() => loadMoreDevices()}
hasMore={devices.length < devicesTotal}
useWindow={false}
loader={
<Box
display="flex"
justifyContent="center"
alignContent="center"
marginTop={2}
key="load-devices">
<CircularProgress />
</Box>
}>
<List style={{ height: "100%", minHeight: "250px" }}>
{devices.length === 0 ? (
<ListItem style={{ justifyContent: "center", paddingTop: "16px" }}>
<Typography color="textSecondary" variant="h5">
No devices found
</Typography>
</ListItem>
) : (
devices.map(d => deviceItem(d))
)}
</List>
</InfiniteScroll>
)}
</ResponsiveDialog>
);
}