aboutsummaryrefslogtreecommitdiff
path: root/modern/src
diff options
context:
space:
mode:
authorAnton Tananaev <anton@traccar.org>2022-05-06 14:15:23 -0700
committerAnton Tananaev <anton@traccar.org>2022-05-06 14:15:23 -0700
commit3812292cc4413efff1358eb34cc5bccab9d1cbae (patch)
tree8aa26184ec58a7f231869d427a28f8e999fa2093 /modern/src
parent1b513d2cbf4dde335f9a2822beaaeeb171816c8d (diff)
downloadtrackermap-web-3812292cc4413efff1358eb34cc5bccab9d1cbae.tar.gz
trackermap-web-3812292cc4413efff1358eb34cc5bccab9d1cbae.tar.bz2
trackermap-web-3812292cc4413efff1358eb34cc5bccab9d1cbae.zip
Handle unit attributes
Diffstat (limited to 'modern/src')
-rw-r--r--modern/src/DevicePage.js13
-rw-r--r--modern/src/attributes/EditAttributesView.js77
-rw-r--r--modern/src/attributes/useDeviceAttributes.js3
-rw-r--r--modern/src/common/converter.js58
-rw-r--r--modern/src/common/formatter.js37
5 files changed, 134 insertions, 54 deletions
diff --git a/modern/src/DevicePage.js b/modern/src/DevicePage.js
index e4b853e7..560bbd20 100644
--- a/modern/src/DevicePage.js
+++ b/modern/src/DevicePage.js
@@ -13,6 +13,7 @@ import LinkField from './form/LinkField';
import { prefixString } from './common/stringUtils';
import { useTranslation } from './LocalizationProvider';
import useDeviceAttributes from './attributes/useDeviceAttributes';
+import { useAdministrator } from './common/permissions';
const useStyles = makeStyles(() => ({
details: {
@@ -24,6 +25,8 @@ const DevicePage = () => {
const classes = useStyles();
const t = useTranslation();
+ const admin = useAdministrator();
+
const deviceAttributes = useDeviceAttributes(t);
const [item, setItem] = useState();
@@ -105,10 +108,12 @@ const DevicePage = () => {
label={t('deviceCategory')}
variant="filled"
/>
- <FormControlLabel
- control={<Checkbox checked={item.disabled} onChange={(event) => setItem({ ...item, disabled: event.target.checked })} />}
- label={t('sharedDisabled')}
- />
+ {admin && (
+ <FormControlLabel
+ control={<Checkbox checked={item.disabled} onChange={(event) => setItem({ ...item, disabled: event.target.checked })} />}
+ label={t('sharedDisabled')}
+ />
+ )}
</AccordionDetails>
</Accordion>
<Accordion>
diff --git a/modern/src/attributes/EditAttributesView.js b/modern/src/attributes/EditAttributesView.js
index 3a18a9a3..56fdff5a 100644
--- a/modern/src/attributes/EditAttributesView.js
+++ b/modern/src/attributes/EditAttributesView.js
@@ -7,6 +7,8 @@ import CloseIcon from '@material-ui/icons/Close';
import AddIcon from '@material-ui/icons/Add';
import AddAttributeDialog from './AddAttributeDialog';
import { useTranslation } from '../LocalizationProvider';
+import { useAttributePreference } from '../common/preferences';
+import { distanceFromMeters, distanceToMeters, distanceUnitString, speedFromKnots, speedToKnots, speedUnitString, volumeFromLiters, volumeToLiters, volumeUnitString } from '../common/converter';
const useStyles = makeStyles((theme) => ({
addButton: {
@@ -22,11 +24,28 @@ 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) => {
+ const updateAttribute = (key, value, type, subtype) => {
const updatedAttributes = { ...attributes };
- updatedAttributes[key] = value;
+ 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);
};
@@ -36,9 +55,19 @@ const EditAttributesView = ({ attributes, setAttributes, definitions }) => {
setAttributes(updatedAttributes);
};
- const getAttributeName = (key) => {
+ const getAttributeName = (key, subtype) => {
const definition = definitions[key];
- return definition ? definition.name : 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) => {
@@ -50,19 +79,41 @@ const EditAttributesView = ({ attributes, setAttributes, definitions }) => {
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 = [];
Object.keys(attributes || []).forEach((key) => {
const value = attributes[key];
const type = getAttributeType(value);
+ const subtype = getAttributeSubtype(key);
if (type === 'boolean') {
- booleanList.push({ key, value, type });
+ booleanList.push({ key, value, type, subtype });
} else {
- otherList.push({ key, value, type });
+ otherList.push({ key, value, type, subtype });
}
});
- return otherList.concat(booleanList);
+ return [...otherList, ...booleanList];
};
const handleAddResult = (definition) => {
@@ -84,10 +135,10 @@ const EditAttributesView = ({ attributes, setAttributes, definitions }) => {
return (
<>
- {convertToList(attributes).map(({ key, value, type }) => {
+ {convertToList(attributes).map(({ key, value, type, subtype }) => {
if (type === 'boolean') {
return (
- <Grid container direction="row" justify="space-between">
+ <Grid container direction="row" justify="space-between" key={key}>
<FormControlLabel
control={(
<Checkbox
@@ -95,7 +146,7 @@ const EditAttributesView = ({ attributes, setAttributes, definitions }) => {
onChange={(e) => updateAttribute(key, e.target.checked)}
/>
)}
- label={getAttributeName(key)}
+ label={getAttributeName(key, subtype)}
/>
<IconButton className={classes.removeButton} onClick={() => deleteAttribute(key)}>
<CloseIcon />
@@ -105,11 +156,11 @@ const EditAttributesView = ({ attributes, setAttributes, definitions }) => {
}
return (
<FormControl variant="filled" margin="normal" key={key}>
- <InputLabel>{getAttributeName(key)}</InputLabel>
+ <InputLabel>{getAttributeName(key, subtype)}</InputLabel>
<FilledInput
type={type === 'number' ? 'number' : 'text'}
- value={value || ''}
- onChange={(e) => updateAttribute(key, e.target.value)}
+ value={getDisplayValue(value, subtype)}
+ onChange={(e) => updateAttribute(key, e.target.value, type, subtype)}
endAdornment={(
<InputAdornment position="end">
<IconButton onClick={() => deleteAttribute(key)}>
diff --git a/modern/src/attributes/useDeviceAttributes.js b/modern/src/attributes/useDeviceAttributes.js
index f33fc065..8a4d886c 100644
--- a/modern/src/attributes/useDeviceAttributes.js
+++ b/modern/src/attributes/useDeviceAttributes.js
@@ -3,7 +3,8 @@ import { useMemo } from 'react';
export default (t) => useMemo(() => ({
speedLimit: {
name: t('attributeSpeedLimit'),
- type: 'string',
+ type: 'number',
+ subtype: 'speed',
},
'report.ignoreOdometer': {
name: t('attributeReportIgnoreOdometer'),
diff --git a/modern/src/common/converter.js b/modern/src/common/converter.js
index d45db5d6..e70473ca 100644
--- a/modern/src/common/converter.js
+++ b/modern/src/common/converter.js
@@ -10,6 +10,22 @@ const speedConverter = (unit) => {
}
};
+export const speedUnitString = (unit, t) => {
+ switch (unit) {
+ case 'kmh':
+ return t('sharedKmh');
+ case 'mph':
+ return t('sharedMph');
+ case 'kn':
+ default:
+ return t('sharedKn');
+ }
+}
+
+export const speedFromKnots = (value, unit) => value * speedConverter(unit);
+
+export const speedToKnots = (value, unit) => value / speedConverter(unit);
+
const distanceConverter = (unit) => {
switch (unit) {
case 'mi':
@@ -22,10 +38,46 @@ const distanceConverter = (unit) => {
}
};
-export const speedFromKnots = (value, unit) => value * speedConverter(unit);
-
-export const speedToKnots = (value, unit) => value / speedConverter(unit);
+export const distanceUnitString = (unit, t) => {
+ switch (unit) {
+ case 'mi':
+ return t('sharedMi');
+ case 'nmi':
+ return t('sharedNmi');
+ case 'km':
+ default:
+ return t('sharedKm');
+ }
+};
export const distanceFromMeters = (value, unit) => value * distanceConverter(unit);
export const distanceToMeters = (value, unit) => value / distanceConverter(unit);
+
+const volumeConverter = (unit) => {
+ switch (unit) {
+ case 'impGal':
+ return 4.546;
+ case 'usGal':
+ return 3.785;
+ case 'ltr':
+ default:
+ return 1;
+ }
+};
+
+export const volumeUnitString = (value, unit, t) => {
+ switch (unit) {
+ case 'impGal':
+ return t('sharedGallonAbbreviation');
+ case 'usGal':
+ return t('sharedGallonAbbreviation');
+ case 'ltr':
+ default:
+ return t('sharedLiterAbbreviation');
+ }
+};
+
+export const volumeFromLiters = (value, unit) => value / volumeConverter(unit);
+
+export const volumeToLiters = (value, unit) => value * volumeConverter(unit);
diff --git a/modern/src/common/formatter.js b/modern/src/common/formatter.js
index bc3af6fb..06e3b12f 100644
--- a/modern/src/common/formatter.js
+++ b/modern/src/common/formatter.js
@@ -1,4 +1,5 @@
import moment from 'moment';
+import { distanceFromMeters, distanceUnitString, speedFromKnots, speedUnitString, volumeFromLiters, volumeUnitString } from './converter';
import { prefixString } from './stringUtils';
export const formatBoolean = (value, t) => (value ? t('sharedYes') : t('sharedNo'));
@@ -18,41 +19,11 @@ export const formatCourse = (value) => {
return courseValues[Math.floor(value / 45)];
};
-export const formatDistance = (value, unit, t) => {
- switch (unit) {
- case 'mi':
- return `${(value * 0.000621371).toFixed(2)} ${t('sharedMi')}`;
- case 'nmi':
- return `${(value * 0.000539957).toFixed(2)} ${t('sharedNmi')}`;
- case 'km':
- default:
- return `${(value * 0.001).toFixed(2)} ${t('sharedKm')}`;
- }
-};
+export const formatDistance = (value, unit, t) => `${distanceFromMeters(value, unit).toFixed(2)} ${distanceUnitString(unit, t)}`;
-export const formatSpeed = (value, unit, t) => {
- switch (unit) {
- case 'kmh':
- return `${(value * 1.852).toFixed(2)} ${t('sharedKmh')}`;
- case 'mph':
- return `${(value * 1.15078).toFixed(2)} ${t('sharedMph')}`;
- case 'kn':
- default:
- return `${(value * 1).toFixed(2)} ${t('sharedKn')}`;
- }
-};
+export const formatSpeed = (value, unit, t) => `${speedFromKnots(value, unit).toFixed(2)} ${speedUnitString(unit, t)}`;
-export const formatVolume = (value, unit, t) => {
- switch (unit) {
- case 'impGal':
- return `${(value / 4.546).toFixed(2)} ${t('sharedGallonAbbreviation')}`;
- case 'usGal':
- return `${(value / 3.785).toFixed(2)} ${t('sharedGallonAbbreviation')}`;
- case 'ltr':
- default:
- return `${(value / 1).toFixed(2)} ${t('sharedLiterAbbreviation')}`;
- }
-};
+export const formatVolume = (value, unit, t) => `${volumeFromLiters(value, unit).toFixed(2)} ${volumeUnitString(unit, t)}`;
export const formatHours = (value) => moment.duration(value).humanize();