From 23f2332c7ce6cea72d91df102d35c03d0d3ce49e Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Fri, 21 Oct 2022 18:14:12 -0700 Subject: Extract device row --- modern/src/main/DeviceList.js | 69 ++++++++++ modern/src/main/DeviceRow.js | 163 ++++++++++++++++++++++++ modern/src/main/DevicesList.js | 221 --------------------------------- modern/src/main/MainPage.js | 6 +- modern/src/settings/PreferencesPage.js | 2 +- 5 files changed, 236 insertions(+), 225 deletions(-) create mode 100644 modern/src/main/DeviceList.js create mode 100644 modern/src/main/DeviceRow.js delete mode 100644 modern/src/main/DevicesList.js (limited to 'modern') diff --git a/modern/src/main/DeviceList.js b/modern/src/main/DeviceList.js new file mode 100644 index 00000000..80104b00 --- /dev/null +++ b/modern/src/main/DeviceList.js @@ -0,0 +1,69 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import makeStyles from '@mui/styles/makeStyles'; +import { List } from '@mui/material'; +import { FixedSizeList } from 'react-window'; +import AutoSizer from 'react-virtualized-auto-sizer'; +import { devicesActions } from '../store'; +import { useEffectAsync } from '../reactHelper'; +import DeviceRow from './DeviceRow'; + +const useStyles = makeStyles((theme) => ({ + list: { + maxHeight: '100%', + }, + listInner: { + position: 'relative', + margin: theme.spacing(1.5, 0), + }, +})); + +const DeviceList = ({ devices }) => { + const classes = useStyles(); + const dispatch = useDispatch(); + const listInnerEl = useRef(null); + + if (listInnerEl.current) { + listInnerEl.current.className = classes.listInner; + } + + const [, setTime] = useState(Date.now()); + + useEffect(() => { + const interval = setInterval(() => setTime(Date.now()), 60000); + return () => { + clearInterval(interval); + }; + }, []); + + useEffectAsync(async () => { + const response = await fetch('/api/devices'); + if (response.ok) { + dispatch(devicesActions.refresh(await response.json())); + } else { + throw Error(await response.text()); + } + }, []); + + return ( + + {({ height, width }) => ( + + + {DeviceRow} + + + )} + + ); +}; + +export default DeviceList; diff --git a/modern/src/main/DeviceRow.js b/modern/src/main/DeviceRow.js new file mode 100644 index 00000000..440e6f5c --- /dev/null +++ b/modern/src/main/DeviceRow.js @@ -0,0 +1,163 @@ +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import makeStyles from '@mui/styles/makeStyles'; +import { + IconButton, Tooltip, Avatar, ListItemAvatar, ListItemText, ListItemButton, +} from '@mui/material'; +import BatteryFullIcon from '@mui/icons-material/BatteryFull'; +import BatteryChargingFullIcon from '@mui/icons-material/BatteryChargingFull'; +import Battery60Icon from '@mui/icons-material/Battery60'; +import BatteryCharging60Icon from '@mui/icons-material/BatteryCharging60'; +import Battery20Icon from '@mui/icons-material/Battery20'; +import BatteryCharging20Icon from '@mui/icons-material/BatteryCharging20'; +import ErrorIcon from '@mui/icons-material/Error'; +import moment from 'moment'; +import { devicesActions } from '../store'; +import { + formatAlarm, formatBoolean, formatPercentage, formatStatus, getStatusColor, +} from '../common/util/formatter'; +import { useTranslation } from '../common/components/LocalizationProvider'; +import { mapIconKey, mapIcons } from '../map/core/preloadImages'; +import { useAdministrator } from '../common/util/permissions'; +import usePersistedState from '../common/util/usePersistedState'; +import { ReactComponent as EngineIcon } from '../resources/images/data/engine.svg'; + +const useStyles = makeStyles((theme) => ({ + icon: { + width: '25px', + height: '25px', + filter: 'brightness(0) invert(1)', + }, + listItem: { + backgroundColor: 'white', + '&:hover': { + backgroundColor: 'white', + }, + }, + batteryText: { + fontSize: '0.75rem', + fontWeight: 'normal', + lineHeight: '0.875rem', + }, + positive: { + color: theme.palette.colors.positive, + }, + medium: { + color: theme.palette.colors.medium, + }, + negative: { + color: theme.palette.colors.negative, + }, + neutral: { + color: theme.palette.colors.neutral, + }, +})); + +const DeviceRow = ({ data, index, style }) => { + const classes = useStyles(); + const dispatch = useDispatch(); + const t = useTranslation(); + + const admin = useAdministrator(); + + const { items } = data; + const item = items[index]; + const position = useSelector((state) => state.positions.items[item.id]); + + const geofences = useSelector((state) => state.geofences.items); + + const [devicePrimary] = usePersistedState('devicePrimary', 'name'); + const [deviceSecondary] = usePersistedState('deviceSecondary', ''); + + const formatProperty = (key) => { + if (key === 'geofenceIds') { + const geofenceIds = item[key] || []; + return geofenceIds + .filter((id) => geofences.hasOwnProperty(id)) + .map((id) => geofences[id].name) + .join(', '); + } + return item[key]; + }; + + const secondaryText = () => { + let status; + if (item.status === 'online' || !item.lastUpdate) { + status = formatStatus(item.status, t); + } else { + status = moment(item.lastUpdate).fromNow(); + } + return ( + <> + {deviceSecondary && item[deviceSecondary] && `${formatProperty(deviceSecondary)} • `} + {status} + + ); + }; + + return ( +
+ dispatch(devicesActions.select(item.id))} + disabled={!admin && item.disabled} + > + + + + + + + {position && ( + <> + {position.attributes.hasOwnProperty('alarm') && ( + + + + + + )} + {position.attributes.hasOwnProperty('ignition') && ( + + + {position.attributes.ignition ? ( + + ) : ( + + )} + + + )} + {position.attributes.hasOwnProperty('batteryLevel') && ( + + + {position.attributes.batteryLevel > 70 ? ( + position.attributes.charge + ? () + : () + ) : position.attributes.batteryLevel > 30 ? ( + position.attributes.charge + ? () + : () + ) : ( + position.attributes.charge + ? () + : () + )} + + + )} + + )} + +
+ ); +}; + +export default DeviceRow; diff --git a/modern/src/main/DevicesList.js b/modern/src/main/DevicesList.js deleted file mode 100644 index 054cf509..00000000 --- a/modern/src/main/DevicesList.js +++ /dev/null @@ -1,221 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import makeStyles from '@mui/styles/makeStyles'; -import { - IconButton, Tooltip, Avatar, List, ListItemAvatar, ListItemText, ListItemButton, -} from '@mui/material'; -import { FixedSizeList } from 'react-window'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import BatteryFullIcon from '@mui/icons-material/BatteryFull'; -import BatteryChargingFullIcon from '@mui/icons-material/BatteryChargingFull'; -import Battery60Icon from '@mui/icons-material/Battery60'; -import BatteryCharging60Icon from '@mui/icons-material/BatteryCharging60'; -import Battery20Icon from '@mui/icons-material/Battery20'; -import BatteryCharging20Icon from '@mui/icons-material/BatteryCharging20'; -import ErrorIcon from '@mui/icons-material/Error'; -import moment from 'moment'; -import { devicesActions } from '../store'; -import { useEffectAsync } from '../reactHelper'; -import { - formatAlarm, formatBoolean, formatPercentage, formatStatus, getStatusColor, -} from '../common/util/formatter'; -import { useTranslation } from '../common/components/LocalizationProvider'; -import { mapIconKey, mapIcons } from '../map/core/preloadImages'; -import { useAdministrator } from '../common/util/permissions'; -import usePersistedState from '../common/util/usePersistedState'; -import { ReactComponent as EngineIcon } from '../resources/images/data/engine.svg'; - -const useStyles = makeStyles((theme) => ({ - list: { - maxHeight: '100%', - }, - listInner: { - position: 'relative', - margin: theme.spacing(1.5, 0), - }, - icon: { - width: '25px', - height: '25px', - filter: 'brightness(0) invert(1)', - }, - listItem: { - backgroundColor: 'white', - '&:hover': { - backgroundColor: 'white', - }, - }, - batteryText: { - fontSize: '0.75rem', - fontWeight: 'normal', - lineHeight: '0.875rem', - }, - positive: { - color: theme.palette.colors.positive, - }, - medium: { - color: theme.palette.colors.medium, - }, - negative: { - color: theme.palette.colors.negative, - }, - neutral: { - color: theme.palette.colors.neutral, - }, -})); - -const DeviceRow = ({ data, index, style }) => { - const classes = useStyles(); - const dispatch = useDispatch(); - const t = useTranslation(); - - const admin = useAdministrator(); - - const { items } = data; - const item = items[index]; - const position = useSelector((state) => state.positions.items[item.id]); - - const geofences = useSelector((state) => state.geofences.items); - - const [devicePrimary] = usePersistedState('devicePrimary', 'name'); - const [deviceSecondary] = usePersistedState('deviceSecondary', ''); - - const formatProperty = (key) => { - if (key === 'geofenceIds') { - const geofenceIds = item[key] || []; - return geofenceIds - .filter((id) => geofences.hasOwnProperty(id)) - .map((id) => geofences[id].name) - .join(', '); - } - return item[key]; - }; - - const secondaryText = () => { - let status; - if (item.status === 'online' || !item.lastUpdate) { - status = formatStatus(item.status, t); - } else { - status = moment(item.lastUpdate).fromNow(); - } - return ( - <> - {deviceSecondary && item[deviceSecondary] && `${formatProperty(deviceSecondary)} • `} - {status} - - ); - }; - - return ( -
- dispatch(devicesActions.select(item.id))} - disabled={!admin && item.disabled} - > - - - - - - - {position && ( - <> - {position.attributes.hasOwnProperty('alarm') && ( - - - - - - )} - {position.attributes.hasOwnProperty('ignition') && ( - - - {position.attributes.ignition ? ( - - ) : ( - - )} - - - )} - {position.attributes.hasOwnProperty('batteryLevel') && ( - - - {position.attributes.batteryLevel > 70 ? ( - position.attributes.charge - ? () - : () - ) : position.attributes.batteryLevel > 30 ? ( - position.attributes.charge - ? () - : () - ) : ( - position.attributes.charge - ? () - : () - )} - - - )} - - )} - -
- ); -}; - -const DevicesList = ({ devices }) => { - const classes = useStyles(); - const dispatch = useDispatch(); - const listInnerEl = useRef(null); - - if (listInnerEl.current) { - listInnerEl.current.className = classes.listInner; - } - - const [, setTime] = useState(Date.now()); - - useEffect(() => { - const interval = setInterval(() => setTime(Date.now()), 60000); - return () => { - clearInterval(interval); - }; - }, []); - - useEffectAsync(async () => { - const response = await fetch('/api/devices'); - if (response.ok) { - dispatch(devicesActions.refresh(await response.json())); - } else { - throw Error(await response.text()); - } - }, []); - - return ( - - {({ height, width }) => ( - - - {DeviceRow} - - - )} - - ); -}; - -export default DevicesList; diff --git a/modern/src/main/MainPage.js b/modern/src/main/MainPage.js index 2713c81e..a296d5f6 100644 --- a/modern/src/main/MainPage.js +++ b/modern/src/main/MainPage.js @@ -6,7 +6,7 @@ import { makeStyles } from '@mui/styles'; import { useTheme } from '@mui/material/styles'; import useMediaQuery from '@mui/material/useMediaQuery'; import { useDispatch, useSelector } from 'react-redux'; -import DevicesList from './DevicesList'; +import DeviceList from './DeviceList'; import BottomMenu from '../common/components/BottomMenu'; import StatusCard from '../common/components/StatusCard'; import { devicesActions } from '../store'; @@ -63,7 +63,7 @@ const MainPage = () => { const desktop = useMediaQuery(theme.breakpoints.up('md')); - const [mapOnSelect] = usePersistedState('mapOnSelect', false); + const [mapOnSelect] = usePersistedState('mapOnSelect', true); const selectedDeviceId = useSelector((state) => state.devices.selectedId); const positions = useSelector((state) => state.positions.items); @@ -126,7 +126,7 @@ const MainPage = () => { )} - + {desktop && ( diff --git a/modern/src/settings/PreferencesPage.js b/modern/src/settings/PreferencesPage.js index 403ea156..02c457a6 100644 --- a/modern/src/settings/PreferencesPage.js +++ b/modern/src/settings/PreferencesPage.js @@ -71,7 +71,7 @@ const PreferencesPage = () => { const [mapLiveRoutes, setMapLiveRoutes] = usePersistedState('mapLiveRoutes', false); const [mapFollow, setMapFollow] = usePersistedState('mapFollow', false); const [mapCluster, setMapCluster] = usePersistedState('mapCluster', true); - const [mapOnSelect, setMapOnSelect] = usePersistedState('mapOnSelect', false); + const [mapOnSelect, setMapOnSelect] = usePersistedState('mapOnSelect', true); const filter = createFilterOptions(); -- cgit v1.2.3