implemented access object but didn't add the file to git haha silly me.
This commit is contained in:
parent
eff8a544ba
commit
b0274ad639
2 changed files with 535 additions and 0 deletions
61
src/group/GroupOverview.tsx
Normal file
61
src/group/GroupOverview.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { createStyles, List, ListItem, ListItemText, Theme } from "@mui/material";
|
||||||
|
// import { Theme } from "@material-ui/core/styles/createMuiTheme";
|
||||||
|
// import withStyles, { WithStyles } from "@material-ui/core/styles/withStyles";
|
||||||
|
import React from "react";
|
||||||
|
import { Group } from "models";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { withStyles, WithStyles } from "@mui/styles";
|
||||||
|
|
||||||
|
const styles = (theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
listDirectionRow: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
padding: 0,
|
||||||
|
overflow: "hidden",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
},
|
||||||
|
listDirectionColumn: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
padding: 0,
|
||||||
|
overflow: "hidden",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Props extends WithStyles<typeof styles> {
|
||||||
|
group: Group;
|
||||||
|
devices?: pond.ComprehensiveDevice[];
|
||||||
|
variant?: "row" | "column";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {}
|
||||||
|
|
||||||
|
class GroupOverview extends React.Component<Props, State> {
|
||||||
|
render() {
|
||||||
|
const { classes, variant, group, devices } = this.props;
|
||||||
|
const numDevices = devices?.length ? devices.length : 0;
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
disablePadding
|
||||||
|
dense
|
||||||
|
className={variant === "row" ? classes.listDirectionRow : classes.listDirectionColumn}>
|
||||||
|
{numDevices > 0 && (
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText primary={"# of Devices: " + numDevices.toString()} />
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
{group.settings.description && (
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText primary="Description" secondary={group.settings.description} />
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)(GroupOverview);
|
||||||
474
src/user/AccessObject.tsx
Normal file
474
src/user/AccessObject.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue