diff options
author | Anton Tananaev <anton@traccar.org> | 2022-10-28 13:28:28 -0700 |
---|---|---|
committer | Anton Tananaev <anton@traccar.org> | 2022-10-28 13:28:28 -0700 |
commit | fac80024e0956d543b762296e0ee49cd72035b93 (patch) | |
tree | 41074dddc57d0c4fdb1b7162b1c3d74633e4ea31 /modern/src | |
parent | ef315b8e10329db80da1a97e96b3cc82481370ae (diff) | |
download | trackermap-web-fac80024e0956d543b762296e0ee49cd72035b93.tar.gz trackermap-web-fac80024e0956d543b762296e0ee49cd72035b93.tar.bz2 trackermap-web-fac80024e0956d543b762296e0ee49cd72035b93.zip |
Persist user preferences
Diffstat (limited to 'modern/src')
-rw-r--r-- | modern/src/App.js | 40 | ||||
-rw-r--r-- | modern/src/SocketController.js | 14 | ||||
-rw-r--r-- | modern/src/common/attributes/useCommonUserAttributes.js | 44 | ||||
-rw-r--r-- | modern/src/common/components/StatusCard.js | 6 | ||||
-rw-r--r-- | modern/src/main/DeviceRow.js | 6 | ||||
-rw-r--r-- | modern/src/main/MainMap.js | 4 | ||||
-rw-r--r-- | modern/src/main/MainPage.js | 3 | ||||
-rw-r--r-- | modern/src/map/MapPositions.js | 4 | ||||
-rw-r--r-- | modern/src/map/core/MapView.js | 2 | ||||
-rw-r--r-- | modern/src/map/main/MapSelectedDevice.js | 4 | ||||
-rw-r--r-- | modern/src/map/overlay/MapOverlay.js | 4 | ||||
-rw-r--r-- | modern/src/resources/l10n/en.json | 6 | ||||
-rw-r--r-- | modern/src/settings/PreferencesPage.js | 106 |
13 files changed, 138 insertions, 105 deletions
diff --git a/modern/src/App.js b/modern/src/App.js index 74a9acc0..e21cf1bf 100644 --- a/modern/src/App.js +++ b/modern/src/App.js @@ -1,12 +1,14 @@ import React from 'react'; -import { Outlet } from 'react-router-dom'; -import { useSelector } from 'react-redux'; +import { Outlet, useNavigate } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; import { LinearProgress, useMediaQuery } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import theme from './common/theme'; import BottomMenu from './common/components/BottomMenu'; import SocketController from './SocketController'; import CachingController from './CachingController'; +import { useEffectAsync } from './reactHelper'; +import { sessionActions } from './store'; const useStyles = makeStyles(() => ({ page: { @@ -20,26 +22,36 @@ const useStyles = makeStyles(() => ({ const App = () => { const classes = useStyles(); + const dispatch = useDispatch(); + const navigate = useNavigate(); const desktop = useMediaQuery(theme.breakpoints.up('md')); const initialized = useSelector((state) => !!state.session.user); - return ( + useEffectAsync(async () => { + if (!initialized) { + const response = await fetch('/api/session'); + if (response.ok) { + dispatch(sessionActions.updateUser(await response.json())); + } else { + navigate('/login'); + } + } + return null; + }, [initialized]); + + return !initialized ? (<LinearProgress />) : ( <> <SocketController /> <CachingController /> - {!initialized ? (<LinearProgress />) : ( - <> - <div className={classes.page}> - <Outlet /> - </div> - {!desktop && ( - <div className={classes.menu}> - <BottomMenu /> - </div> - )} - </> + <div className={classes.page}> + <Outlet /> + </div> + {!desktop && ( + <div className={classes.menu}> + <BottomMenu /> + </div> )} </> ); diff --git a/modern/src/SocketController.js b/modern/src/SocketController.js index 87b842fa..970298c3 100644 --- a/modern/src/SocketController.js +++ b/modern/src/SocketController.js @@ -1,23 +1,21 @@ import React, { useEffect, useRef, useState } from 'react'; import { useDispatch, useSelector, connect } from 'react-redux'; import { Snackbar } from '@mui/material'; -import { useNavigate } from 'react-router-dom'; import { positionsActions, devicesActions, sessionActions } from './store'; import { useEffectAsync } from './reactHelper'; import { useTranslation } from './common/components/LocalizationProvider'; import { snackBarDurationLongMs } from './common/util/duration'; -import usePersistedState from './common/util/usePersistedState'; import alarm from './resources/alarm.mp3'; import { eventsActions } from './store/events'; import useFeatures from './common/util/useFeatures'; +import { useAttributePreference } from './common/util/preferences'; const logoutCode = 4000; const SocketController = () => { const dispatch = useDispatch(); - const navigate = useNavigate(); const t = useTranslation(); const authenticated = useSelector((state) => !!state.session.user); @@ -28,8 +26,8 @@ const SocketController = () => { const [events, setEvents] = useState([]); const [notifications, setNotifications] = useState([]); - const [soundEvents] = usePersistedState('soundEvents', []); - const [soundAlarms] = usePersistedState('soundAlarms', ['sos']); + const soundEvents = useAttributePreference('soundEvents', ''); + const soundAlarms = useAttributePreference('soundAlarms', 'sos'); const features = useFeatures(); @@ -96,12 +94,6 @@ const SocketController = () => { } }; } - const response = await fetch('/api/session'); - if (response.ok) { - dispatch(sessionActions.updateUser(await response.json())); - } else { - navigate('/login'); - } return null; }, [authenticated]); diff --git a/modern/src/common/attributes/useCommonUserAttributes.js b/modern/src/common/attributes/useCommonUserAttributes.js index 7fe2fcdf..845ab799 100644 --- a/modern/src/common/attributes/useCommonUserAttributes.js +++ b/modern/src/common/attributes/useCommonUserAttributes.js @@ -5,6 +5,50 @@ export default (t) => useMemo(() => ({ name: t('attributeShowGeofences'), type: 'boolean', }, + mapLiveRoutes: { + name: t('mapLiveRoutes'), + type: 'boolean', + }, + mapFollow: { + name: t('deviceFollow'), + type: 'boolean', + }, + mapCluster: { + name: t('mapClustering'), + type: 'boolean', + }, + mapOnSelect: { + name: t('mapOnSelect'), + type: 'boolean', + }, + activeMapStyles: { + name: t('mapActive'), + type: 'string', + }, + selectedMapStyle: { + name: t('mapDefault'), + type: 'string', + }, + devicePrimary: { + name: t('devicePrimaryInfo'), + type: 'string', + }, + deviceSecondary: { + name: t('deviceSecondaryInfo'), + type: 'string', + }, + soundEvents: { + name: t('eventsSoundEvents'), + type: 'string', + }, + soundAlarms: { + name: t('eventsSoundAlarms'), + type: 'string', + }, + positionItems: { + name: t('attributePopupInfo'), + type: 'string', + }, locationIqKey: { name: t('mapLocationIqKey'), type: 'string', diff --git a/modern/src/common/components/StatusCard.js b/modern/src/common/components/StatusCard.js index c6ab1f51..7ff5769d 100644 --- a/modern/src/common/components/StatusCard.js +++ b/modern/src/common/components/StatusCard.js @@ -28,10 +28,10 @@ import { useTranslation } from './LocalizationProvider'; import RemoveDialog from './RemoveDialog'; import PositionValue from './PositionValue'; import { useDeviceReadonly, useRestriction } from '../util/permissions'; -import usePersistedState from '../util/usePersistedState'; import usePositionAttributes from '../attributes/usePositionAttributes'; import { devicesActions } from '../../store'; import { useCatch, useCatchCallback } from '../../reactHelper'; +import { useAttributePreference } from '../util/preferences'; const useStyles = makeStyles((theme) => ({ card: { @@ -122,7 +122,7 @@ const StatusCard = ({ deviceId, position, onClose, disableActions, desktopPaddin const deviceImage = device?.attributes?.deviceImage; const positionAttributes = usePositionAttributes(t); - const [positionItems] = usePersistedState('positionItems', ['speed', 'address', 'totalDistance', 'course']); + const positionItems = useAttributePreference('positionItems', 'speed,address,totalDistance,course'); const [anchorEl, setAnchorEl] = useState(null); @@ -204,7 +204,7 @@ const StatusCard = ({ deviceId, position, onClose, disableActions, desktopPaddin <CardContent className={classes.content}> <Table size="small" classes={{ root: classes.table }}> <TableBody> - {positionItems.filter((key) => position.hasOwnProperty(key) || position.attributes.hasOwnProperty(key)).map((key) => ( + {positionItems.split(',').filter((key) => position.hasOwnProperty(key) || position.attributes.hasOwnProperty(key)).map((key) => ( <StatusRow key={key} name={positionAttributes[key].name} diff --git a/modern/src/main/DeviceRow.js b/modern/src/main/DeviceRow.js index 11dbfdcb..5699c274 100644 --- a/modern/src/main/DeviceRow.js +++ b/modern/src/main/DeviceRow.js @@ -19,8 +19,8 @@ import { 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'; +import { useAttributePreference } from '../common/util/preferences'; const useStyles = makeStyles((theme) => ({ icon: { @@ -59,8 +59,8 @@ const DeviceRow = ({ data, index, style }) => { const geofences = useSelector((state) => state.geofences.items); - const [devicePrimary] = usePersistedState('devicePrimary', 'name'); - const [deviceSecondary] = usePersistedState('deviceSecondary', ''); + const devicePrimary = useAttributePreference('devicePrimary', 'name'); + const deviceSecondary = useAttributePreference('deviceSecondary', ''); const formatProperty = (key) => { if (key === 'geofenceIds') { diff --git a/modern/src/main/MainMap.js b/modern/src/main/MainMap.js index 81d214f9..279f3a85 100644 --- a/modern/src/main/MainMap.js +++ b/modern/src/main/MainMap.js @@ -11,7 +11,6 @@ import PoiMap from '../map/main/PoiMap'; import MapPadding from '../map/MapPadding'; import { devicesActions } from '../store'; import MapDefaultCamera from '../map/main/MapDefaultCamera'; -import usePersistedState from '../common/util/usePersistedState'; import MapLiveRoutes from '../map/main/MapLiveRoutes'; import MapPositions from '../map/MapPositions'; import MapOverlay from '../map/overlay/MapOverlay'; @@ -19,6 +18,7 @@ import MapGeocoder from '../map/geocoder/MapGeocoder'; import MapScale from '../map/MapScale'; import MapNotification from '../map/notification/MapNotification'; import useFeatures from '../common/util/useFeatures'; +import { useAttributePreference } from '../common/util/preferences'; const MainMap = ({ filteredPositions, selectedPosition, onEventsClick }) => { const theme = useTheme(); @@ -30,7 +30,7 @@ const MainMap = ({ filteredPositions, selectedPosition, onEventsClick }) => { const features = useFeatures(); - const [mapLiveRoutes] = usePersistedState('mapLiveRoutes', false); + const mapLiveRoutes = useAttributePreference('mapLiveRoutes', false); const onMarkerClick = useCallback((_, deviceId) => { dispatch(devicesActions.select(deviceId)); diff --git a/modern/src/main/MainPage.js b/modern/src/main/MainPage.js index 9a21d570..0655fa92 100644 --- a/modern/src/main/MainPage.js +++ b/modern/src/main/MainPage.js @@ -15,6 +15,7 @@ import EventsDrawer from './EventsDrawer'; import useFilter from './useFilter'; import MainToolbar from './MainToolbar'; import MainMap from './MainMap'; +import { useAttributePreference } from '../common/util/preferences'; const useStyles = makeStyles((theme) => ({ root: { @@ -63,7 +64,7 @@ const MainPage = () => { const desktop = useMediaQuery(theme.breakpoints.up('md')); - const [mapOnSelect] = usePersistedState('mapOnSelect', true); + const mapOnSelect = useAttributePreference('mapOnSelect', true); const selectedDeviceId = useSelector((state) => state.devices.selectedId); const positions = useSelector((state) => state.positions.items); diff --git a/modern/src/map/MapPositions.js b/modern/src/map/MapPositions.js index e4365fab..e334800c 100644 --- a/modern/src/map/MapPositions.js +++ b/modern/src/map/MapPositions.js @@ -2,7 +2,6 @@ import { useId, useCallback, useEffect } from 'react'; import { useSelector } from 'react-redux'; import { map } from './core/MapView'; import { formatTime, getStatusColor } from '../common/util/formatter'; -import usePersistedState from '../common/util/usePersistedState'; import { mapIconKey } from './core/preloadImages'; import { findFonts } from './core/mapUtil'; import { useAttributePreference } from '../common/util/preferences'; @@ -15,8 +14,7 @@ const MapPositions = ({ positions, onClick, showStatus, selectedPosition, titleF const devices = useSelector((state) => state.devices.items); const iconScale = useAttributePreference('iconScale', 1); - - const [mapCluster] = usePersistedState('mapCluster', true); + const mapCluster = useAttributePreference('mapCluster', true); const createFeature = (devices, position, selectedPositionId) => { const device = devices[position.deviceId]; diff --git a/modern/src/map/core/MapView.js b/modern/src/map/core/MapView.js index e91d0cc5..7ed5eae6 100644 --- a/modern/src/map/core/MapView.js +++ b/modern/src/map/core/MapView.js @@ -75,7 +75,7 @@ const MapView = ({ children }) => { const [mapReady, setMapReady] = useState(false); const mapStyles = useMapStyles(); - const [activeMapStyles] = usePersistedState('activeMapStyles', ['locationIqStreets', 'osm', 'carto']); + const activeMapStyles = useAttributePreference('activeMapStyles', 'locationIqStreets,osm,carto'); const [defaultMapStyle] = usePersistedState('selectedMapStyle', 'locationIqStreets'); const mapboxAccessToken = useAttributePreference('mapboxAccessToken'); const maxZoom = useAttributePreference('web.maxZoom'); diff --git a/modern/src/map/main/MapSelectedDevice.js b/modern/src/map/main/MapSelectedDevice.js index c52b8df0..3f444409 100644 --- a/modern/src/map/main/MapSelectedDevice.js +++ b/modern/src/map/main/MapSelectedDevice.js @@ -4,7 +4,6 @@ import { useSelector } from 'react-redux'; import dimensions from '../../common/theme/dimensions'; import { map } from '../core/MapView'; import { usePrevious } from '../../reactHelper'; -import usePersistedState from '../../common/util/usePersistedState'; import { useAttributePreference } from '../../common/util/preferences'; const MapSelectedDevice = () => { @@ -12,11 +11,10 @@ const MapSelectedDevice = () => { const previousDeviceId = usePrevious(selectedDeviceId); const selectZoom = useAttributePreference('web.selectZoom', 10); + const mapFollow = useAttributePreference('mapFollow', false); const position = useSelector((state) => state.positions.items[selectedDeviceId]); - const [mapFollow] = usePersistedState('mapFollow', false); - useEffect(() => { if ((selectedDeviceId !== previousDeviceId || mapFollow) && position) { map.easeTo({ diff --git a/modern/src/map/overlay/MapOverlay.js b/modern/src/map/overlay/MapOverlay.js index 85797d4b..4db2153d 100644 --- a/modern/src/map/overlay/MapOverlay.js +++ b/modern/src/map/overlay/MapOverlay.js @@ -1,5 +1,5 @@ import { useId, useEffect } from 'react'; -import usePersistedState from '../../common/util/usePersistedState'; +import { useAttributePreference } from '../../common/util/preferences'; import { map } from '../core/MapView'; import useMapOverlays from './useMapOverlays'; @@ -7,7 +7,7 @@ const MapOverlay = () => { const id = useId(); const mapOverlays = useMapOverlays(); - const [selectedMapOverlay] = usePersistedState('selectedMapOverlay'); + const selectedMapOverlay = useAttributePreference('selectedMapOverlay'); const activeOverlay = mapOverlays.filter((overlay) => overlay.available).find((overlay) => overlay.id === selectedMapOverlay); diff --git a/modern/src/resources/l10n/en.json b/modern/src/resources/l10n/en.json index dd1f6723..e20faddb 100644 --- a/modern/src/resources/l10n/en.json +++ b/modern/src/resources/l10n/en.json @@ -153,6 +153,7 @@ "attributeUiHidePositionAttributes": "UI: Hide Position Attributes", "attributeUiDisableLoginLanguage": "UI: Disable Login Language", "attributeNotificationTokens": "Notification Tokens", + "attributePopupInfo": "Popup Info", "errorTitle": "Error", "errorGeneral": "Invalid parameters or constraints violation", "errorConnection": "Connection error", @@ -184,6 +185,8 @@ "loginLogo": "Logo", "devicesAndState": "Devices and State", "deviceTitle": "Devices", + "devicePrimaryInfo": "Device Title", + "deviceSecondaryInfo": "Device Detail", "deviceIdentifier": "Identifier", "deviceModel": "Model", "deviceContact": "Contact", @@ -345,6 +348,7 @@ "mapPoiLayer": "POI Layer", "mapClustering": "Markers Clustering", "mapOnSelect": "Show Map on Selection", + "mapDefault": "Default Map", "stateTitle": "State", "stateName": "Attribute", "stateValue": "Value", @@ -427,6 +431,8 @@ "eventDriverChanged": "Driver changed", "eventMedia": "Media", "eventsScrollToLast": "Scroll To Last", + "eventsSoundEvents": "Sound Events", + "eventsSoundAlarms": "Sound Alarms", "alarmGeneral": "General", "alarmSos": "SOS", "alarmVibration": "Vibration", diff --git a/modern/src/settings/PreferencesPage.js b/modern/src/settings/PreferencesPage.js index 9a2b37e7..5229a03c 100644 --- a/modern/src/settings/PreferencesPage.js +++ b/modern/src/settings/PreferencesPage.js @@ -9,8 +9,7 @@ import makeStyles from '@mui/styles/makeStyles'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import CachedIcon from '@mui/icons-material/Cached'; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; -import { useLocalization, useTranslation, useTranslationKeys } from '../common/components/LocalizationProvider'; -import usePersistedState from '../common/util/usePersistedState'; +import { useTranslation, useTranslationKeys } from '../common/components/LocalizationProvider'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; import usePositionAttributes from '../common/attributes/usePositionAttributes'; @@ -64,25 +63,13 @@ const PreferencesPage = () => { const user = useSelector((state) => state.session.user); const [attributes, setAttributes] = useState(user.attributes); - const { languages, language, setLanguage } = useLocalization(); - const languageList = Object.entries(languages).map((values) => ({ code: values[0], name: values[1].name })); - const [token, setToken] = useState(null); const [tokenExpiration, setTokenExpiration] = useState(moment().add(1, 'week').locale('en').format(moment.HTML5_FMT.DATE)); const mapStyles = useMapStyles(); - const [activeMapStyles, setActiveMapStyles] = usePersistedState('activeMapStyles', ['locationIqStreets', 'osm', 'carto']); - const mapOverlays = useMapOverlays(); - const [selectedMapOverlay, setSelectedMapOverlay] = usePersistedState('selectedMapOverlay'); const positionAttributes = usePositionAttributes(t); - const [positionItems, setPositionItems] = usePersistedState('positionItems', ['speed', 'address', 'totalDistance', 'course']); - - const [mapLiveRoutes, setMapLiveRoutes] = usePersistedState('mapLiveRoutes', false); - const [mapFollow, setMapFollow] = usePersistedState('mapFollow', false); - const [mapCluster, setMapCluster] = usePersistedState('mapCluster', true); - const [mapOnSelect, setMapOnSelect] = usePersistedState('mapOnSelect', true); const filter = createFilterOptions(); @@ -104,12 +91,6 @@ const PreferencesPage = () => { name: t(it), })); - const [devicePrimary, setDevicePrimary] = usePersistedState('devicePrimary', 'name'); - const [deviceSecondary, setDeviceSecondary] = usePersistedState('deviceSecondary', ''); - - const [soundEvents, setSoundEvents] = usePersistedState('soundEvents', []); - const [soundAlarms, setSoundAlarms] = usePersistedState('soundAlarms', ['sos']); - const handleSave = useCatch(async () => { const response = await fetch(`/api/users/${user.id}`, { method: 'PUT', @@ -130,25 +111,6 @@ const PreferencesPage = () => { <Accordion defaultExpanded> <AccordionSummary expandIcon={<ExpandMoreIcon />}> <Typography variant="subtitle1"> - {t('sharedPreferences')} - </Typography> - </AccordionSummary> - <AccordionDetails className={classes.details}> - <FormControl> - <InputLabel>{t('loginLanguage')}</InputLabel> - <Select - label={t('loginLanguage')} - value={language} - onChange={(e) => setLanguage(e.target.value)} - > - {languageList.map((it) => <MenuItem key={it.code} value={it.code}>{it.name}</MenuItem>)} - </Select> - </FormControl> - </AccordionDetails> - </Accordion> - <Accordion defaultExpanded> - <AccordionSummary expandIcon={<ExpandMoreIcon />}> - <Typography variant="subtitle1"> {t('userToken')} </Typography> </AccordionSummary> @@ -196,11 +158,11 @@ const PreferencesPage = () => { <InputLabel>{t('mapActive')}</InputLabel> <Select label={t('mapActive')} - value={activeMapStyles} + value={attributes.activeMapStyles?.split(',') || ['locationIqStreets', 'osm', 'carto']} onChange={(e, child) => { const clicked = mapStyles.find((s) => s.id === child.props.value); if (clicked.available) { - setActiveMapStyles(e.target.value); + setAttributes({ ...attributes, activeMapStyles: e.target.value.join(',') }); } else if (clicked.id !== 'custom') { const query = new URLSearchParams({ attribute: clicked.attribute }); navigate(`/settings/user/${user.id}?${query.toString()}`); @@ -219,11 +181,11 @@ const PreferencesPage = () => { <InputLabel>{t('mapOverlay')}</InputLabel> <Select label={t('mapOverlay')} - value={selectedMapOverlay} + value={attributes.selectedMapOverlay} onChange={(e) => { const clicked = mapOverlays.find((o) => o.id === e.target.value); if (!clicked || clicked.available) { - setSelectedMapOverlay(e.target.value); + setAttributes({ ...attributes, selectedMapOverlay: e.target.value }); } else if (clicked.id !== 'custom') { const query = new URLSearchParams({ attribute: clicked.attribute }); navigate(`/settings/user/${user.id}?${query.toString()}`); @@ -243,9 +205,9 @@ const PreferencesPage = () => { freeSolo options={Object.keys(positionAttributes)} getOptionLabel={(option) => (positionAttributes.hasOwnProperty(option) ? positionAttributes[option].name : option)} - value={positionItems} + value={attributes.positionItems?.split(',') || ['speed', 'address', 'totalDistance', 'course']} onChange={(_, option) => { - setPositionItems(option); + setAttributes({ ...attributes, positionItems: option.join(',') }); }} filterOptions={(options, params) => { const filtered = filter(options, params); @@ -257,7 +219,7 @@ const PreferencesPage = () => { renderInput={(params) => ( <TextField {...params} - placeholder={t('sharedAttributes')} + label={t('attributePopupInfo')} /> )} /> @@ -272,19 +234,39 @@ const PreferencesPage = () => { label={t('attributeShowGeofences')} /> <FormControlLabel - control={<Checkbox checked={mapLiveRoutes} onChange={(e) => setMapLiveRoutes(e.target.checked)} />} + control={( + <Checkbox + checked={attributes.hasOwnProperty('mapLiveRoutes') ? attributes.mapLiveRoutes : false} + onChange={(e) => setAttributes({ ...attributes, mapLiveRoutes: e.target.checked })} + /> + )} label={t('mapLiveRoutes')} /> <FormControlLabel - control={<Checkbox checked={mapFollow} onChange={(e) => setMapFollow(e.target.checked)} />} + control={( + <Checkbox + checked={attributes.hasOwnProperty('mapFollow') ? attributes.mapFollow : false} + onChange={(e) => setAttributes({ ...attributes, mapFollow: e.target.checked })} + /> + )} label={t('deviceFollow')} /> <FormControlLabel - control={<Checkbox checked={mapCluster} onChange={(e) => setMapCluster(e.target.checked)} />} + control={( + <Checkbox + checked={attributes.hasOwnProperty('mapCluster') ? attributes.mapCluster : true} + onChange={(e) => setAttributes({ ...attributes, mapCluster: e.target.checked })} + /> + )} label={t('mapClustering')} /> <FormControlLabel - control={<Checkbox checked={mapOnSelect} onChange={(e) => setMapOnSelect(e.target.checked)} />} + control={( + <Checkbox + checked={attributes.hasOwnProperty('mapOnSelect') ? attributes.mapOnSelect : true} + onChange={(e) => setAttributes({ ...attributes, mapOnSelect: e.target.checked })} + /> + )} label={t('mapOnSelect')} /> </FormGroup> @@ -299,19 +281,19 @@ const PreferencesPage = () => { <AccordionDetails className={classes.details}> <SelectField emptyValue={null} - value={devicePrimary} - onChange={(e) => setDevicePrimary(e.target.value)} + value={attributes.devicePrimary || 'name'} + onChange={(e) => setAttributes({ ...attributes, devicePrimary: e.target.value })} data={deviceFields} titleGetter={(it) => t(it.name)} - label={t('sharedPrimary')} + label={t('devicePrimaryInfo')} /> <SelectField emptyValue="" - value={deviceSecondary} - onChange={(e) => setDeviceSecondary(e.target.value)} + value={attributes.deviceSecondary || ''} + onChange={(e) => setAttributes({ ...attributes, deviceSecondary: e.target.value })} data={deviceFields} titleGetter={(it) => t(it.name)} - label={t('sharedSecondary')} + label={t('deviceSecondaryInfo')} /> </AccordionDetails> </Accordion> @@ -324,20 +306,20 @@ const PreferencesPage = () => { <AccordionDetails className={classes.details}> <SelectField multiple - value={soundEvents} - onChange={(e) => setSoundEvents(e.target.value)} + value={attributes.soundEvents?.split(',') || []} + onChange={(e) => setAttributes({ ...attributes, soundEvents: e.target.value.join(',') })} endpoint="/api/notifications/types" keyGetter={(it) => it.type} titleGetter={(it) => t(prefixString('event', it.type))} - label={t('reportEventTypes')} + label={t('eventsSoundEvents')} /> <SelectField multiple - value={soundAlarms} - onChange={(e) => setSoundAlarms(e.target.value)} + value={attributes.soundAlarms?.split(',') || ['sos']} + onChange={(e) => setAttributes({ ...attributes, soundAlarms: e.target.value.join(',') })} data={alarms} keyGetter={(it) => it.key} - label={t('sharedAlarms')} + label={t('eventsSoundAlarms')} /> </AccordionDetails> </Accordion> |