diff options
author | Anton Tananaev <anton.tananaev@gmail.com> | 2021-08-07 07:42:21 -1000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-07 07:42:21 -1000 |
commit | db406fcc787df2fa865e5c5fe333f152e533cda1 (patch) | |
tree | f8a0ca5cf0b897106c618a8b2cb83f31f155d86d | |
parent | 59c60f8fcfabe855231e3dfecf9b363b7488024f (diff) | |
parent | 1521abf0372dd1f91d690e2931cc87d71642acb6 (diff) | |
download | trackermap-web-db406fcc787df2fa865e5c5fe333f152e533cda1.tar.gz trackermap-web-db406fcc787df2fa865e5c5fe333f152e533cda1.tar.bz2 trackermap-web-db406fcc787df2fa865e5c5fe333f152e533cda1.zip |
Merge pull request #888 from mail2bishnoi/quick_device_menu
Quick device menu
-rw-r--r-- | modern/public/styles.css | 4 | ||||
-rw-r--r-- | modern/src/common/formatter.js | 22 | ||||
-rw-r--r-- | modern/src/map/PositionsMap.js | 11 | ||||
-rw-r--r-- | modern/src/map/SelectedDeviceMap.js | 50 | ||||
-rw-r--r-- | modern/src/map/StatusView.js | 170 | ||||
-rw-r--r-- | modern/src/theme/palette.js | 11 |
6 files changed, 210 insertions, 58 deletions
diff --git a/modern/public/styles.css b/modern/public/styles.css index e7aee866..147c3e3f 100644 --- a/modern/public/styles.css +++ b/modern/public/styles.css @@ -10,3 +10,7 @@ canvas { .mapboxgl-ctrl { margin: 10px; } + +.maplibregl-popup { + max-width: 330px !important; +} diff --git a/modern/src/common/formatter.js b/modern/src/common/formatter.js index 3c0341b7..f0bb2e11 100644 --- a/modern/src/common/formatter.js +++ b/modern/src/common/formatter.js @@ -98,3 +98,25 @@ export const formatCoordinate = (key, value, unit) => { return `${value.toFixed(6)}°`; } }; + +export const getStatusColor = (status) => { + switch (status) { + case 'online': + return 'green'; + case 'offline': + return 'red'; + case 'unknown': + default: + return 'gray'; + } +}; + +export const getBatteryStatus = (batteryLevel) => { + if (batteryLevel >= 70) { + return 'green'; + } + if (batteryLevel > 30) { + return 'gray'; + } + return 'red'; +}; diff --git a/modern/src/map/PositionsMap.js b/modern/src/map/PositionsMap.js index 2de01d2c..9719b45b 100644 --- a/modern/src/map/PositionsMap.js +++ b/modern/src/map/PositionsMap.js @@ -1,5 +1,6 @@ import React, { useCallback, useEffect } from 'react'; import ReactDOM from 'react-dom'; +import { ThemeProvider } from '@material-ui/core/styles'; import maplibregl from 'maplibre-gl'; import { Provider, useSelector } from 'react-redux'; @@ -7,6 +8,7 @@ import { useHistory } from 'react-router-dom'; import { map } from './Map'; import store from '../store'; import StatusView from './StatusView'; +import theme from '../theme'; const PositionsMap = ({ positions }) => { const id = 'positions'; @@ -49,7 +51,14 @@ const PositionsMap = ({ positions }) => { const placeholder = document.createElement('div'); ReactDOM.render( <Provider store={store}> - <StatusView deviceId={feature.properties.deviceId} onShowDetails={(positionId) => history.push(`/position/${positionId}`)} /> + <ThemeProvider theme={theme}> + <StatusView + deviceId={feature.properties.deviceId} + onShowDetails={(positionId) => history.push(`/position/${positionId}`)} + onShowHistory={() => history.push('/replay')} + onEditClick={(deviceId) => history.push(`/device/${deviceId}`)} + /> + </ThemeProvider> </Provider>, placeholder, ); diff --git a/modern/src/map/SelectedDeviceMap.js b/modern/src/map/SelectedDeviceMap.js index e6c5f58f..e55ee10c 100644 --- a/modern/src/map/SelectedDeviceMap.js +++ b/modern/src/map/SelectedDeviceMap.js @@ -1,21 +1,63 @@ -import { useEffect } from 'react'; -import { useSelector } from 'react-redux'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; +import { ThemeProvider } from '@material-ui/core/styles'; +import maplibregl from 'maplibre-gl'; +import { Provider, useSelector } from 'react-redux'; +import { useHistory } from 'react-router-dom'; import { map } from './Map'; +import store from '../store'; +import StatusView from './StatusView'; +import theme from '../theme'; + +let popup; const SelectedDeviceMap = () => { + const history = useHistory(); + const mapCenter = useSelector((state) => { if (state.devices.selectedId) { const position = state.positions.items[state.devices.selectedId] || null; if (position) { - return [position.longitude, position.latitude]; + return { deviceId: state.devices.selectedId, position: [position.longitude, position.latitude] }; } } return null; }); + const showStatus = (deviceId, coordinates) => { + const placeholder = document.createElement('div'); + ReactDOM.render( + <Provider store={store}> + <ThemeProvider theme={theme}> + <StatusView + deviceId={deviceId} + onShowDetails={(positionId) => history.push(`/position/${positionId}`)} + onShowHistory={() => history.push('/replay')} + onEditClick={(deviceId) => history.push(`/device/${deviceId}`)} + /> + </ThemeProvider> + </Provider>, + placeholder, + ); + + if (popup) { + popup.remove(); + } + popup = new maplibregl.Popup({ + offset: 25, + anchor: 'top', + closeOnClick: true, + }); + + popup.setDOMContent(placeholder).setLngLat(coordinates).addTo(map); + }; + useEffect(() => { - map.easeTo({ center: mapCenter }); + if (mapCenter) { + map.easeTo({ center: mapCenter.position }); + showStatus(mapCenter.deviceId, mapCenter.position); + } }, [mapCenter]); return null; diff --git a/modern/src/map/StatusView.js b/modern/src/map/StatusView.js index c0f723d2..00d36c50 100644 --- a/modern/src/map/StatusView.js +++ b/modern/src/map/StatusView.js @@ -1,70 +1,134 @@ -import React from 'react'; +import React, { useState } from 'react'; +import { + makeStyles, Paper, IconButton, Grid, Button, +} from '@material-ui/core'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; +import ListItemText from '@material-ui/core/ListItemText'; import { useSelector } from 'react-redux'; + +import ReplayIcon from '@material-ui/icons/Replay'; +import ExitToAppIcon from '@material-ui/icons/ExitToApp'; +import EditIcon from '@material-ui/icons/Edit'; +import DeleteIcon from '@material-ui/icons/Delete'; + +import { + formatPosition, getStatusColor, getBatteryStatus, formatDistance, formatSpeed, +} from '../common/formatter'; +import { useAttributePreference } from '../common/preferences'; +import RemoveDialog from '../RemoveDialog'; import t from '../common/localization'; -import { formatPosition } from '../common/formatter'; import { getPosition } from '../common/selectors'; -const StatusView = ({ deviceId, onShowDetails }) => { +const useStyles = makeStyles((theme) => ({ + paper: { + width: '300px', + }, + ...theme.palette.colors, + listItemContainer: { + maxWidth: '240px', + }, +})); + +const StatusView = ({ + deviceId, onShowDetails, onShowHistory, onEditClick, +}) => { + const classes = useStyles(); + + const [removeDialogShown, setRemoveDialogShown] = useState(false); const device = useSelector((state) => state.devices.items[deviceId]); const position = useSelector(getPosition(deviceId)); + const distanceUnit = useAttributePreference('distanceUnit'); + const speedUnit = useAttributePreference('speedUnit'); + const handleClick = (e) => { e.preventDefault(); onShowDetails(position.id); }; + const handleEditClick = (e) => { + e.preventDefault(); + onEditClick(deviceId); + }; + + const handleRemove = () => { + setRemoveDialogShown(true); + }; + + const handleRemoveResult = () => { + setRemoveDialogShown(false); + }; + return ( <> - <b> - {t('deviceStatus')} - : - </b> - {' '} - {formatPosition(device.status, 'status')} - <br /> - <b> - {t('sharedLocation')} - : - </b> - {' '} - {formatPosition(position, 'latitude')} - {' '} - {formatPosition(position, 'longitude')} - <br /> - <b> - {t('positionSpeed')} - : - </b> - {' '} - {formatPosition(position.speed, 'speed')} - <br /> - <b> - {t('positionCourse')} - : - </b> - {' '} - {formatPosition(position.course, 'course')} - <br /> - <b> - {t('positionDistance')} - : - </b> - {' '} - {formatPosition(position.attributes.totalDistance, 'distance')} - <br /> - {position.attributes.batteryLevel - && ( - <> - <b> - {t('positionBattery')} - : - </b> - {' '} - {formatPosition(position.attributes.batteryLevel, 'batteryLevel')} - <br /> - </> - )} - <a href="/" onClick={handleClick}>{t('sharedShowDetails')}</a> + <Paper className={classes.paper} elevation={0} square> + <Grid container direction="column"> + <Grid item> + <List> + <ListItem classes={{ container: classes.listItemContainer }}> + <ListItemText primary={t('deviceStatus')} /> + <ListItemSecondaryAction> + <span className={classes[getStatusColor(device.status)]}>{device.status}</span> + </ListItemSecondaryAction> + </ListItem> + <ListItem classes={{ container: classes.listItemContainer }}> + <ListItemText primary={t('positionSpeed')} /> + <ListItemSecondaryAction> + <span>{formatSpeed(position.speed, speedUnit)}</span> + </ListItemSecondaryAction> + </ListItem> + {position.attributes.batteryLevel && ( + <ListItem classes={{ container: classes.listItemContainer }}> + <ListItemText primary={t('positionBattery')} /> + <ListItemSecondaryAction> + <span className={classes[getBatteryStatus(position.attributes.batteryLevel)]}>{formatPosition(position.attributes.batteryLevel, 'batteryLevel')}</span> + </ListItemSecondaryAction> + </ListItem> + )} + <ListItem classes={{ container: classes.listItemContainer }}> + <ListItemText primary={t('positionDistance')} /> + <ListItemSecondaryAction> + <span>{formatDistance(position.attributes.totalDistance, distanceUnit)}</span> + </ListItemSecondaryAction> + </ListItem> + <ListItem classes={{ container: classes.listItemContainer }}> + <ListItemText primary={t('positionCourse')} /> + <ListItemSecondaryAction> + <span>{formatPosition(position.course, 'course')}</span> + </ListItemSecondaryAction> + </ListItem> + </List> + </Grid> + <Grid item container> + <Grid item> + <Button color="secondary" onClick={handleClick}>More Info</Button> + </Grid> + <Grid item> + <IconButton onClick={onShowHistory}> + <ReplayIcon /> + </IconButton> + </Grid> + <Grid item> + <IconButton> + <ExitToAppIcon /> + </IconButton> + </Grid> + <Grid item> + <IconButton onClick={handleEditClick}> + <EditIcon /> + </IconButton> + </Grid> + <Grid item> + <IconButton onClick={handleRemove} className={classes.red}> + <DeleteIcon /> + </IconButton> + </Grid> + </Grid> + </Grid> + </Paper> + <RemoveDialog open={removeDialogShown} endpoint="devices" itemId={deviceId} onResult={handleRemoveResult} /> </> ); }; diff --git a/modern/src/theme/palette.js b/modern/src/theme/palette.js index ac26e57b..622e49b0 100644 --- a/modern/src/theme/palette.js +++ b/modern/src/theme/palette.js @@ -18,4 +18,15 @@ export default { main: traccarGreen, contrastText: traccarWhite, }, + colors: { + red: { + color: traccarRed, + }, + green: { + color: traccarGreen, + }, + gray: { + color: traccarGray, + }, + }, }; |