import React, { useState } from 'react'; import { 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, } from '@mui/material'; 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 PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; import usePositionAttributes from '../common/attributes/usePositionAttributes'; import { prefixString, unprefixString } from '../common/util/stringUtils'; import SelectField from '../common/components/SelectField'; import useMapStyles from '../map/core/useMapStyles'; import useMapOverlays from '../map/overlay/useMapOverlays'; import { useCatch } from '../reactHelper'; const useStyles = makeStyles((theme) => ({ container: { marginTop: theme.spacing(2), }, details: { display: 'flex', flexDirection: 'column', gap: theme.spacing(2), paddingBottom: theme.spacing(3), }, tokenActions: { display: 'flex', flexDirection: 'column', }, })); const PreferencesPage = () => { const classes = useStyles(); const navigate = useNavigate(); const t = useTranslation(); const userId = useSelector((state) => state.session.user.id); const { languages, language, setLanguage } = useLocalization(); const languageList = Object.entries(languages).map((values) => ({ code: values[0], name: values[1].name })); const [token, setToken] = useState(); 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 [mapGeofences, setMapGeofences] = usePersistedState('mapGeofences', true); 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 filter = createFilterOptions(); const generateToken = useCatch(async () => { const response = await fetch('/api/session/token', { method: 'POST', body: new URLSearchParams(), }); if (response.ok) { setToken(await response.text()); } else { throw Error(await response.text()); } }); const alarms = useTranslationKeys((it) => it.startsWith('alarm')).map((it) => ({ key: unprefixString('alarm', it), name: t(it), })); const [soundEvents, setSoundEvents] = usePersistedState('soundEvents', []); const [soundAlarms, setSoundAlarms] = usePersistedState('soundAlarms', ['sos']); return ( } breadcrumbs={['settingsTitle', 'sharedPreferences']}> }> {t('sharedPreferences')} {t('loginLanguage')} setLanguage(e.target.value)} > {languageList.map((it) => {it.name})} {t('userToken')} navigator.clipboard.writeText(token)} disabled={!token}> )} /> }> {t('mapTitle')} {t('mapActive')} { const clicked = mapStyles.find((s) => s.id === child.props.value); if (clicked.available) { setActiveMapStyles(e.target.value); } else if (clicked.id !== 'custom') { const query = new URLSearchParams({ attribute: clicked.attribute }); navigate(`/settings/user/${userId}?${query.toString()}`); } }} multiple > {mapStyles.map((style) => ( {style.title} ))} {t('mapOverlay')} { const clicked = mapOverlays.find((o) => o.id === e.target.value); if (!clicked || clicked.available) { setSelectedMapOverlay(e.target.value); } else if (clicked.id !== 'custom') { const query = new URLSearchParams({ attribute: clicked.attribute }); navigate(`/settings/user/${userId}?${query.toString()}`); } }} > {'\u00a0'} {mapOverlays.map((overlay) => ( {overlay.title} ))} (positionAttributes.hasOwnProperty(option) ? positionAttributes[option].name : option)} value={positionItems} onChange={(_, option) => { setPositionItems(option); }} filterOptions={(options, params) => { const filtered = filter(options, params); if (params.inputValue && !filtered.includes(params.inputValue)) { filtered.push(params.inputValue); } return filtered; }} renderInput={(params) => ( )} /> setMapGeofences(e.target.checked)} />} label={t('sharedGeofences')} /> setMapLiveRoutes(e.target.checked)} />} label={t('mapLiveRoutes')} /> setMapFollow(e.target.checked)} />} label={t('deviceFollow')} /> setMapCluster(e.target.checked)} />} label={t('mapClustering')} /> setMapOnSelect(e.target.checked)} />} label={t('mapOnSelect')} /> }> {t('sharedSound')} setSoundEvents(e.target.value)} endpoint="/api/notifications/types" keyGetter={(it) => it.type} titleGetter={(it) => t(prefixString('event', it.type))} label={t('reportEventTypes')} /> setSoundAlarms(e.target.value)} data={alarms} keyGetter={(it) => it.key} label={t('sharedAlarms')} /> ); }; export default PreferencesPage;