diff options
author | Anton Tananaev <anton@traccar.org> | 2022-05-08 13:16:57 -0700 |
---|---|---|
committer | Anton Tananaev <anton@traccar.org> | 2022-05-08 13:16:57 -0700 |
commit | 2cd374bb9fa941d7e2a6fd8aa5079893a158c98f (patch) | |
tree | f4ee48130592fed5de25dce7af4ac0cbeb017680 /modern/src/common/attributes | |
parent | 2352071211b61c10fa5bf5736baaff7809d18bf0 (diff) | |
download | trackermap-web-2cd374bb9fa941d7e2a6fd8aa5079893a158c98f.tar.gz trackermap-web-2cd374bb9fa941d7e2a6fd8aa5079893a158c98f.tar.bz2 trackermap-web-2cd374bb9fa941d7e2a6fd8aa5079893a158c98f.zip |
Reorganize remaining files
Diffstat (limited to 'modern/src/common/attributes')
-rw-r--r-- | modern/src/common/attributes/AddAttributeDialog.js | 89 | ||||
-rw-r--r-- | modern/src/common/attributes/EditAttributesView.js | 203 | ||||
-rw-r--r-- | modern/src/common/attributes/useCommandAttributes.js | 11 | ||||
-rw-r--r-- | modern/src/common/attributes/useDeviceAttributes.js | 13 | ||||
-rw-r--r-- | modern/src/common/attributes/useGeofenceAttributes.js | 8 | ||||
-rw-r--r-- | modern/src/common/attributes/usePositionAttributes.js | 21 | ||||
-rw-r--r-- | modern/src/common/attributes/useUserAttributes.js | 48 |
7 files changed, 393 insertions, 0 deletions
diff --git a/modern/src/common/attributes/AddAttributeDialog.js b/modern/src/common/attributes/AddAttributeDialog.js new file mode 100644 index 00000000..37b36c76 --- /dev/null +++ b/modern/src/common/attributes/AddAttributeDialog.js @@ -0,0 +1,89 @@ +import React, { useState } from 'react'; +import { + Button, Dialog, DialogActions, DialogContent, FormControl, InputLabel, MenuItem, Select, TextField, +} from '@material-ui/core'; + +import { Autocomplete, createFilterOptions } from '@material-ui/lab'; +import { useTranslation } from '../components/LocalizationProvider'; + +const AddAttributeDialog = ({ open, onResult, definitions }) => { + const t = useTranslation(); + + const filter = createFilterOptions({ + stringify: (option) => option.name, + }); + + const options = Object.entries(definitions).map(([key, value]) => ({ + key, + name: value.name, + type: value.type, + })); + + const [key, setKey] = useState(); + const [type, setType] = useState('string'); + + return ( + <Dialog open={open} fullWidth maxWidth="xs"> + <DialogContent> + <Autocomplete + onChange={(_, option) => { + setKey(option && typeof option === 'object' ? option.key : option); + if (option && option.type) { + setType(option.type); + } + }} + filterOptions={(options, params) => { + const filtered = filter(options, params); + if (params.inputValue) { + filtered.push({ + key: params.inputValue, + name: params.inputValue, + }); + } + return filtered; + }} + options={options} + getOptionLabel={(option) => (option && typeof option === 'object' ? option.name : option)} + renderOption={(option) => option.name} + freeSolo + renderInput={(params) => ( + <TextField {...params} label={t('sharedAttribute')} variant="filled" margin="normal" /> + )} + /> + <FormControl + variant="filled" + margin="normal" + fullWidth + disabled={key in definitions} + > + <InputLabel>{t('sharedType')}</InputLabel> + <Select + value={type} + onChange={(e) => setType(e.target.value)} + > + <MenuItem value="string">{t('sharedTypeString')}</MenuItem> + <MenuItem value="number">{t('sharedTypeNumber')}</MenuItem> + <MenuItem value="boolean">{t('sharedTypeBoolean')}</MenuItem> + </Select> + </FormControl> + </DialogContent> + <DialogActions> + <Button + color="primary" + disabled={!key} + onClick={() => onResult({ key, type })} + > + {t('sharedAdd')} + </Button> + <Button + autoFocus + onClick={() => onResult(null)} + > + {t('sharedCancel')} + </Button> + </DialogActions> + </Dialog> + ); +}; + +export default AddAttributeDialog; diff --git a/modern/src/common/attributes/EditAttributesView.js b/modern/src/common/attributes/EditAttributesView.js new file mode 100644 index 00000000..4bc34b19 --- /dev/null +++ b/modern/src/common/attributes/EditAttributesView.js @@ -0,0 +1,203 @@ +import React, { useState } from 'react'; + +import { + Button, Checkbox, FilledInput, FormControl, FormControlLabel, Grid, IconButton, InputAdornment, InputLabel, makeStyles, +} from '@material-ui/core'; +import CloseIcon from '@material-ui/icons/Close'; +import AddIcon from '@material-ui/icons/Add'; +import AddAttributeDialog from './AddAttributeDialog'; +import { useTranslation } from '../components/LocalizationProvider'; +import { useAttributePreference } from '../util/preferences'; +import { + distanceFromMeters, distanceToMeters, distanceUnitString, speedFromKnots, speedToKnots, speedUnitString, volumeFromLiters, volumeToLiters, volumeUnitString, +} from '../util/converter'; + +const useStyles = makeStyles((theme) => ({ + addButton: { + marginTop: theme.spacing(2), + marginBottom: theme.spacing(1), + }, + removeButton: { + marginRight: theme.spacing(1.5), + }, +})); + +const EditAttributesView = ({ attributes, setAttributes, definitions }) => { + const classes = useStyles(); + const t = useTranslation(); + + const speedUnit = useAttributePreference('speedUnit'); + const distanceUnit = useAttributePreference('distanceUnit'); + const volumeUnit = useAttributePreference('volumeUnit'); + + const [addDialogShown, setAddDialogShown] = useState(false); + + const updateAttribute = (key, value, type, subtype) => { + const updatedAttributes = { ...attributes }; + switch (subtype) { + case 'speed': + updatedAttributes[key] = speedToKnots(Number(value), speedUnit); + break; + case 'distance': + updatedAttributes[key] = distanceToMeters(Number(value), distanceUnit); + break; + case 'volume': + updatedAttributes[key] = volumeToLiters(Number(value), volumeUnit); + break; + default: + updatedAttributes[key] = type === 'number' ? Number(value) : value; + break; + } + setAttributes(updatedAttributes); + }; + + const deleteAttribute = (key) => { + const updatedAttributes = { ...attributes }; + delete updatedAttributes[key]; + setAttributes(updatedAttributes); + }; + + const getAttributeName = (key, subtype) => { + const definition = definitions[key]; + const name = definition ? definition.name : key; + switch (subtype) { + case 'speed': + return `${name} (${speedUnitString(speedUnit, t)})`; + case 'distance': + return `${name} (${distanceUnitString(distanceUnit, t)})`; + case 'volume': + return `${name} (${volumeUnitString(volumeUnit, t)})`; + default: + return name; + } + }; + + const getAttributeType = (value) => { + if (typeof value === 'number') { + return 'number'; + } if (typeof value === 'boolean') { + return 'boolean'; + } + return 'string'; + }; + + const getAttributeSubtype = (key) => { + const definition = definitions[key]; + return definition && definition.subtype; + }; + + const getDisplayValue = (value, subtype) => { + if (value) { + switch (subtype) { + case 'speed': + return speedFromKnots(value, speedUnit); + case 'distance': + return distanceFromMeters(value, distanceUnit); + case 'volume': + return volumeFromLiters(value, volumeUnit); + default: + return value; + } + } + return ''; + }; + + const convertToList = (attributes) => { + const booleanList = []; + const otherList = []; + const excludeAttributes = ['speedUnit', 'distanceUnit', 'volumeUnit', 'timezone']; + Object.keys(attributes || []).filter((key) => !excludeAttributes.includes(key)).forEach((key) => { + const value = attributes[key]; + const type = getAttributeType(value); + const subtype = getAttributeSubtype(key); + if (type === 'boolean') { + booleanList.push({ + key, value, type, subtype, + }); + } else { + otherList.push({ + key, value, type, subtype, + }); + } + }); + return [...otherList, ...booleanList]; + }; + + const handleAddResult = (definition) => { + setAddDialogShown(false); + if (definition) { + switch (definition.type) { + case 'number': + updateAttribute(definition.key, 0); + break; + case 'boolean': + updateAttribute(definition.key, false); + break; + default: + updateAttribute(definition.key, ''); + break; + } + } + }; + + return ( + <> + {convertToList(attributes).map(({ + key, value, type, subtype, + }) => { + if (type === 'boolean') { + return ( + <Grid container direction="row" justifyContent="space-between" key={key}> + <FormControlLabel + control={( + <Checkbox + checked={value} + onChange={(e) => updateAttribute(key, e.target.checked)} + /> + )} + label={getAttributeName(key, subtype)} + /> + <IconButton className={classes.removeButton} onClick={() => deleteAttribute(key)}> + <CloseIcon /> + </IconButton> + </Grid> + ); + } + return ( + <FormControl variant="filled" margin="normal" key={key}> + <InputLabel>{getAttributeName(key, subtype)}</InputLabel> + <FilledInput + type={type === 'number' ? 'number' : 'text'} + value={getDisplayValue(value, subtype)} + onChange={(e) => updateAttribute(key, e.target.value, type, subtype)} + endAdornment={( + <InputAdornment position="end"> + <IconButton onClick={() => deleteAttribute(key)}> + <CloseIcon /> + </IconButton> + </InputAdornment> + )} + /> + </FormControl> + ); + })} + <Button + size="large" + variant="outlined" + color="primary" + onClick={() => setAddDialogShown(true)} + startIcon={<AddIcon />} + className={classes.addButton} + > + {t('sharedAdd')} + </Button> + <AddAttributeDialog + open={addDialogShown} + onResult={handleAddResult} + definitions={definitions} + /> + </> + ); +}; + +export default EditAttributesView; diff --git a/modern/src/common/attributes/useCommandAttributes.js b/modern/src/common/attributes/useCommandAttributes.js new file mode 100644 index 00000000..1212d283 --- /dev/null +++ b/modern/src/common/attributes/useCommandAttributes.js @@ -0,0 +1,11 @@ +import { useMemo } from 'react'; + +export default (t) => useMemo(() => ({ + custom: [ + { + key: 'data', + name: t('commandData'), + type: 'string', + }, + ], +}), [t]); diff --git a/modern/src/common/attributes/useDeviceAttributes.js b/modern/src/common/attributes/useDeviceAttributes.js new file mode 100644 index 00000000..8a4d886c --- /dev/null +++ b/modern/src/common/attributes/useDeviceAttributes.js @@ -0,0 +1,13 @@ +import { useMemo } from 'react'; + +export default (t) => useMemo(() => ({ + speedLimit: { + name: t('attributeSpeedLimit'), + type: 'number', + subtype: 'speed', + }, + 'report.ignoreOdometer': { + name: t('attributeReportIgnoreOdometer'), + type: 'boolean', + }, +}), [t]); diff --git a/modern/src/common/attributes/useGeofenceAttributes.js b/modern/src/common/attributes/useGeofenceAttributes.js new file mode 100644 index 00000000..89908aa5 --- /dev/null +++ b/modern/src/common/attributes/useGeofenceAttributes.js @@ -0,0 +1,8 @@ +import { useMemo } from 'react'; + +export default (t) => useMemo(() => ({ + speedLimit: { + name: t('attributeSpeedLimit'), + type: 'string', + }, +}), [t]); diff --git a/modern/src/common/attributes/usePositionAttributes.js b/modern/src/common/attributes/usePositionAttributes.js new file mode 100644 index 00000000..7b33720a --- /dev/null +++ b/modern/src/common/attributes/usePositionAttributes.js @@ -0,0 +1,21 @@ +import { useMemo } from 'react'; + +export default (t) => useMemo(() => ({ + raw: { + name: t('positionRaw'), + type: 'string', + }, + index: { + name: t('positionIndex'), + type: 'number', + }, + ignition: { + name: t('positionIgnition'), + type: 'boolean', + }, + odometer: { + name: t('positionOdometer'), + type: 'number', + dataType: 'distance', + }, +}), [t]); diff --git a/modern/src/common/attributes/useUserAttributes.js b/modern/src/common/attributes/useUserAttributes.js new file mode 100644 index 00000000..fa6d7d8f --- /dev/null +++ b/modern/src/common/attributes/useUserAttributes.js @@ -0,0 +1,48 @@ +import { useMemo } from 'react'; + +export default (t) => useMemo(() => ({ + notificationTokens: { + name: t('attributeNotificationTokens'), + type: 'string', + }, + /* 'web.liveRouteLength': { + name: t('attributeWebLiveRouteLength'), + type: 'number', + }, + 'web.selectZoom': { + name: t('attributeWebSelectZoom'), + type: 'number', + }, + 'web.maxZoom': { + name: t('attributeWebMaxZoom'), + type: 'number', + }, + 'ui.disableEvents': { + name: t('attributeUiDisableEvents'), + type: 'boolean', + }, + 'ui.disableVehicleFetures': { + name: t('attributeUiDisableVehicleFetures'), + type: 'boolean', + }, + 'ui.disableDrivers': { + name: t('attributeUiDisableDrivers'), + type: 'boolean', + }, + 'ui.disableComputedAttributes': { + name: t('attributeUiDisableComputedAttributes'), + type: 'boolean', + }, + 'ui.disableCalendars': { + name: t('attributeUiDisableCalendars'), + type: 'boolean', + }, + 'ui.disableMaintenance': { + name: t('attributeUiDisableMaintenance'), + type: 'boolean', + }, + 'ui.hidePositionAttributes': { + name: t('attributeUiHidePositionAttributes'), + type: 'string', + }, */ +}), [t]); |