aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Tananaev <anton@traccar.org>2022-10-28 06:48:33 -0700
committerAnton Tananaev <anton@traccar.org>2022-10-28 06:48:33 -0700
commitef315b8e10329db80da1a97e96b3cc82481370ae (patch)
tree3a3e6366a98daf2e7c499422bdbcf94965917b28
parentc000fa00d3a4b71a814864b9c805885f85bdd8dc (diff)
downloadtrackermap-web-ef315b8e10329db80da1a97e96b3cc82481370ae.tar.gz
trackermap-web-ef315b8e10329db80da1a97e96b3cc82481370ae.tar.bz2
trackermap-web-ef315b8e10329db80da1a97e96b3cc82481370ae.zip
Persist map geofences
-rw-r--r--modern/src/common/attributes/useCommonUserAttributes.js4
-rw-r--r--modern/src/common/util/preferences.js32
-rw-r--r--modern/src/map/MapGeofence.js4
-rw-r--r--modern/src/resources/l10n/en.json1
-rw-r--r--modern/src/settings/PreferencesPage.js64
5 files changed, 91 insertions, 14 deletions
diff --git a/modern/src/common/attributes/useCommonUserAttributes.js b/modern/src/common/attributes/useCommonUserAttributes.js
index e64299bd..7fe2fcdf 100644
--- a/modern/src/common/attributes/useCommonUserAttributes.js
+++ b/modern/src/common/attributes/useCommonUserAttributes.js
@@ -1,6 +1,10 @@
import { useMemo } from 'react';
export default (t) => useMemo(() => ({
+ mapGeofences: {
+ name: t('attributeShowGeofences'),
+ type: 'boolean',
+ },
locationIqKey: {
name: t('mapLocationIqKey'),
type: 'string',
diff --git a/modern/src/common/util/preferences.js b/modern/src/common/util/preferences.js
index aba3c82c..fb8bb4f2 100644
--- a/modern/src/common/util/preferences.js
+++ b/modern/src/common/util/preferences.js
@@ -2,14 +2,38 @@ import { useSelector } from 'react-redux';
export const usePreference = (key, defaultValue) => useSelector((state) => {
if (state.session.server.forceSettings) {
- return state.session.server[key] || state.session.user[key] || defaultValue;
+ if (state.session.server.hasOwnProperty(key)) {
+ return state.session.server[key];
+ }
+ if (state.session.user.hasOwnProperty(key)) {
+ return state.session.user[key];
+ }
+ return defaultValue;
}
- return state.session.user[key] || state.session.server[key] || defaultValue;
+ if (state.session.user.hasOwnProperty(key)) {
+ return state.session.user[key];
+ }
+ if (state.session.server.hasOwnProperty(key)) {
+ return state.session.server[key];
+ }
+ return defaultValue;
});
export const useAttributePreference = (key, defaultValue) => useSelector((state) => {
if (state.session.server.forceSettings) {
- return state.session.server.attributes[key] || state.session.user.attributes[key] || defaultValue;
+ if (state.session.server.attributes.hasOwnProperty(key)) {
+ return state.session.server.attributes[key];
+ }
+ if (state.session.user.attributes.hasOwnProperty(key)) {
+ return state.session.user.attributes[key];
+ }
+ return defaultValue;
+ }
+ if (state.session.user.attributes.hasOwnProperty(key)) {
+ return state.session.user.attributes[key];
+ }
+ if (state.session.server.attributes.hasOwnProperty(key)) {
+ return state.session.server.attributes[key];
}
- return state.session.user.attributes[key] || state.session.server.attributes[key] || defaultValue;
+ return defaultValue;
});
diff --git a/modern/src/map/MapGeofence.js b/modern/src/map/MapGeofence.js
index 3bfbca24..6208795c 100644
--- a/modern/src/map/MapGeofence.js
+++ b/modern/src/map/MapGeofence.js
@@ -3,14 +3,14 @@ import { useSelector } from 'react-redux';
import { useTheme } from '@mui/styles';
import { map } from './core/MapView';
import { findFonts, geofenceToFeature } from './core/mapUtil';
-import usePersistedState from '../common/util/usePersistedState';
+import { useAttributePreference } from '../common/util/preferences';
const MapGeofence = () => {
const id = useId();
const theme = useTheme();
- const [mapGeofences] = usePersistedState('mapGeofences', true);
+ const mapGeofences = useAttributePreference('mapGeofences', true);
const geofences = useSelector((state) => state.geofences.items);
diff --git a/modern/src/resources/l10n/en.json b/modern/src/resources/l10n/en.json
index f6d43e7a..dd1f6723 100644
--- a/modern/src/resources/l10n/en.json
+++ b/modern/src/resources/l10n/en.json
@@ -112,6 +112,7 @@
"calendarThursday": "Thursday",
"calendarFriday": "Friday",
"calendarSaturday": "Saturday",
+ "attributeShowGeofences": "Show Geofences",
"attributeSpeedLimit": "Speed Limit",
"attributeFuelDropThreshold": "Fuel Drop Threshold",
"attributeFuelIncreaseThreshold": "Fuel Increase Threshold",
diff --git a/modern/src/settings/PreferencesPage.js b/modern/src/settings/PreferencesPage.js
index 02c457a6..9a2b37e7 100644
--- a/modern/src/settings/PreferencesPage.js
+++ b/modern/src/settings/PreferencesPage.js
@@ -1,9 +1,9 @@
import React, { useState } from 'react';
import moment from 'moment';
-import { useSelector } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import {
- Accordion, AccordionSummary, AccordionDetails, Typography, Container, FormControl, InputLabel, Select, MenuItem, Checkbox, FormControlLabel, FormGroup, InputAdornment, IconButton, OutlinedInput, Autocomplete, TextField, createFilterOptions,
+ Accordion, AccordionSummary, AccordionDetails, Typography, Container, FormControl, InputLabel, Select, MenuItem, Checkbox, FormControlLabel, FormGroup, InputAdornment, IconButton, OutlinedInput, Autocomplete, TextField, createFilterOptions, Button,
} from '@mui/material';
import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
@@ -19,6 +19,7 @@ import SelectField from '../common/components/SelectField';
import useMapStyles from '../map/core/useMapStyles';
import useMapOverlays from '../map/overlay/useMapOverlays';
import { useCatch } from '../reactHelper';
+import { sessionActions } from '../store';
const deviceFields = [
{ id: 'name', name: 'sharedName' },
@@ -33,6 +34,15 @@ const useStyles = makeStyles((theme) => ({
container: {
marginTop: theme.spacing(2),
},
+ buttons: {
+ marginTop: theme.spacing(2),
+ marginBottom: theme.spacing(2),
+ display: 'flex',
+ justifyContent: 'space-evenly',
+ '& > *': {
+ flexBasis: '33%',
+ },
+ },
details: {
display: 'flex',
flexDirection: 'column',
@@ -47,10 +57,12 @@ const useStyles = makeStyles((theme) => ({
const PreferencesPage = () => {
const classes = useStyles();
+ const dispatch = useDispatch();
const navigate = useNavigate();
const t = useTranslation();
- const userId = useSelector((state) => state.session.user.id);
+ 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 }));
@@ -67,7 +79,6 @@ const PreferencesPage = () => {
const positionAttributes = usePositionAttributes(t);
const [positionItems, setPositionItems] = usePersistedState('positionItems', ['speed', 'address', 'totalDistance', 'course']);
- const [mapGeofences, setMapGeofences] = usePersistedState('mapGeofences', true);
const [mapLiveRoutes, setMapLiveRoutes] = usePersistedState('mapLiveRoutes', false);
const [mapFollow, setMapFollow] = usePersistedState('mapFollow', false);
const [mapCluster, setMapCluster] = usePersistedState('mapCluster', true);
@@ -99,6 +110,20 @@ const PreferencesPage = () => {
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',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ ...user, attributes }),
+ });
+ if (response.ok) {
+ dispatch(sessionActions.updateUser(await response.json()));
+ navigate(-1);
+ } else {
+ throw Error(await response.text());
+ }
+ });
+
return (
<PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedPreferences']}>
<Container maxWidth="xs" className={classes.container}>
@@ -178,7 +203,7 @@ const PreferencesPage = () => {
setActiveMapStyles(e.target.value);
} else if (clicked.id !== 'custom') {
const query = new URLSearchParams({ attribute: clicked.attribute });
- navigate(`/settings/user/${userId}?${query.toString()}`);
+ navigate(`/settings/user/${user.id}?${query.toString()}`);
}
}}
multiple
@@ -201,7 +226,7 @@ const PreferencesPage = () => {
setSelectedMapOverlay(e.target.value);
} else if (clicked.id !== 'custom') {
const query = new URLSearchParams({ attribute: clicked.attribute });
- navigate(`/settings/user/${userId}?${query.toString()}`);
+ navigate(`/settings/user/${user.id}?${query.toString()}`);
}
}}
>
@@ -238,8 +263,13 @@ const PreferencesPage = () => {
/>
<FormGroup>
<FormControlLabel
- control={<Checkbox checked={mapGeofences} onChange={(e) => setMapGeofences(e.target.checked)} />}
- label={t('sharedGeofences')}
+ control={(
+ <Checkbox
+ checked={attributes.hasOwnProperty('mapGeofences') ? attributes.mapGeofences : true}
+ onChange={(e) => setAttributes({ ...attributes, mapGeofences: e.target.checked })}
+ />
+ )}
+ label={t('attributeShowGeofences')}
/>
<FormControlLabel
control={<Checkbox checked={mapLiveRoutes} onChange={(e) => setMapLiveRoutes(e.target.checked)} />}
@@ -311,6 +341,24 @@ const PreferencesPage = () => {
/>
</AccordionDetails>
</Accordion>
+ <div className={classes.buttons}>
+ <Button
+ type="button"
+ color="primary"
+ variant="outlined"
+ onClick={() => navigate(-1)}
+ >
+ {t('sharedCancel')}
+ </Button>
+ <Button
+ type="button"
+ color="primary"
+ variant="contained"
+ onClick={handleSave}
+ >
+ {t('sharedSave')}
+ </Button>
+ </div>
</Container>
</PageLayout>
);