From 9bfda9e131ddda3076b4094a94795db41072a39c Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 27 Sep 2020 19:49:47 -0700 Subject: Implement attributes editing --- modern/package.json | 1 + modern/src/DevicePage.js | 4 +- modern/src/EditItemView.js | 24 +++-- modern/src/MainToolbar.js | 6 -- modern/src/RemoveDialog.js | 24 +++-- modern/src/UserPage.js | 16 ++++ modern/src/attributes/AddAttributeDialog.js | 83 ++++++++++++++++ modern/src/attributes/EditAttributesView.js | 141 ++++++++++++++++++++++++++++ modern/src/attributes/userAttributes.js | 68 ++++++++++++++ modern/src/reports/RouteReportPage.js | 6 +- web/l10n/en.json | 1 + 11 files changed, 337 insertions(+), 37 deletions(-) create mode 100644 modern/src/attributes/AddAttributeDialog.js create mode 100644 modern/src/attributes/EditAttributesView.js create mode 100644 modern/src/attributes/userAttributes.js diff --git a/modern/package.json b/modern/package.json index e14c074c..ff701a30 100644 --- a/modern/package.json +++ b/modern/package.json @@ -6,6 +6,7 @@ "@craco/craco": "^5.6.4", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "^4.0.0-alpha.56", "@reduxjs/toolkit": "^1.4.0", "mapbox-gl": "^1.12.0", "moment": "^2.28.0", diff --git a/modern/src/DevicePage.js b/modern/src/DevicePage.js index da41e37a..f2985408 100644 --- a/modern/src/DevicePage.js +++ b/modern/src/DevicePage.js @@ -91,7 +91,7 @@ const DevicePage = () => { onChange={event => setItem({...item, groupId: Number(event.target.value)})}> {groups.map(group => ( - + ))} @@ -121,7 +121,7 @@ const DevicePage = () => { defaultValue={item.category} onChange={event => setItem({...item, category: event.target.value})}> {deviceCategories.map(category => ( - + ))} diff --git a/modern/src/EditItemView.js b/modern/src/EditItemView.js index f67927aa..a6f1d229 100644 --- a/modern/src/EditItemView.js +++ b/modern/src/EditItemView.js @@ -59,19 +59,17 @@ const EditItemView = ({ children, endpoint, setItem, getItem }) => { <> -
- {children} - -
- - -
-
-
+ {children} + +
+ + +
+
); diff --git a/modern/src/MainToolbar.js b/modern/src/MainToolbar.js index 71e92c3b..9b6005d9 100644 --- a/modern/src/MainToolbar.js +++ b/modern/src/MainToolbar.js @@ -146,12 +146,6 @@ const MainToolbar = () => { - - - - - - diff --git a/modern/src/RemoveDialog.js b/modern/src/RemoveDialog.js index a8107357..8e7d97f4 100644 --- a/modern/src/RemoveDialog.js +++ b/modern/src/RemoveDialog.js @@ -15,19 +15,17 @@ const RemoveDialog = ({ open, endpoint, itemId, onResult }) => { }; return ( - <> - { onResult(false) }}> - - {t('sharedRemoveConfirm')} - - - - - - - + { onResult(false) }}> + + {t('sharedRemoveConfirm')} + + + + + + ); }; diff --git a/modern/src/UserPage.js b/modern/src/UserPage.js index 8bef99c2..81e33893 100644 --- a/modern/src/UserPage.js +++ b/modern/src/UserPage.js @@ -2,9 +2,11 @@ import React, { useState } from 'react'; import TextField from '@material-ui/core/TextField'; import t from './common/localization'; +import userAttributes from './attributes/userAttributes'; import EditItemView from './EditItemView'; import { Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography } from '@material-ui/core'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import EditAttributesView from './attributes/EditAttributesView'; const useStyles = makeStyles(() => ({ details: { @@ -48,6 +50,20 @@ const UserPage = () => { variant="filled" /> + + }> + + {t('sharedAttributes')} + + + + setItem({...item, attributes})} + definitions={userAttributes} + /> + + } diff --git a/modern/src/attributes/AddAttributeDialog.js b/modern/src/attributes/AddAttributeDialog.js new file mode 100644 index 00000000..ee4c48c1 --- /dev/null +++ b/modern/src/attributes/AddAttributeDialog.js @@ -0,0 +1,83 @@ +import React, { useState } from 'react'; +import { Button, Dialog, DialogActions, DialogContent, FormControl, InputLabel, MenuItem, Select, TextField } from "@material-ui/core"; + +import t from '../common/localization'; +import { Autocomplete, createFilterOptions } from '@material-ui/lab'; + +const AddAttributeDialog = ({ open, onResult, definitions }) => { + 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 ( + + + { + 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 => { + return option && typeof option === 'object' ? option.name : option; + }} + renderOption={option => option.name} + freeSolo + renderInput={(params) => ( + + )} + /> + + {t('sharedType')} + + + + + + + + + ) +} + +export default AddAttributeDialog; diff --git a/modern/src/attributes/EditAttributesView.js b/modern/src/attributes/EditAttributesView.js new file mode 100644 index 00000000..9491acc5 --- /dev/null +++ b/modern/src/attributes/EditAttributesView.js @@ -0,0 +1,141 @@ +import React, { useState } from 'react'; + +import t from '../common/localization'; + +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'; + +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 [addDialogShown, setAddDialogShown] = useState(false); + + const convertToList = (attributes) => { + let booleanList = []; + let otherList = []; + for (const key in attributes) { + const value = attributes[key]; + const type = getAttributeType(value); + if (type === 'boolean') { + booleanList.push({ key, value, type }); + } else { + otherList.push({ key, value, type }); + } + } + return otherList.concat(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; + } + } + } + + const updateAttribute = (key, value) => { + let updatedAttributes = {...attributes}; + updatedAttributes[key] = value; + setAttributes(updatedAttributes); + }; + + const deleteAttribute = (key) => { + let updatedAttributes = {...attributes}; + delete updatedAttributes[key]; + setAttributes(updatedAttributes); + }; + + const getAttributeName = (key) => { + const definition = definitions[key]; + return definition ? definition.name : key; + }; + + const getAttributeType = (value) => { + if (typeof value === 'number') { + return 'number'; + } else if (typeof value === 'boolean') { + return 'boolean'; + } else { + return 'string'; + } + }; + + return ( + <> + {convertToList(attributes).map(({ key, value, type }) => { + if (type === 'boolean') { + return ( + + updateAttribute(key, e.target.checked)} + /> + } + label={getAttributeName(key)} /> + deleteAttribute(key)}> + + + + ); + } else { + return ( + + {getAttributeName(key)} + updateAttribute(key, e.target.value)} + endAdornment={ + + deleteAttribute(key)}> + + + + } + /> + + ); + } + })} + + + + ); +} + +export default EditAttributesView; diff --git a/modern/src/attributes/userAttributes.js b/modern/src/attributes/userAttributes.js new file mode 100644 index 00000000..bcec29f2 --- /dev/null +++ b/modern/src/attributes/userAttributes.js @@ -0,0 +1,68 @@ +import t from '../common/localization' + +export default { + '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.disableReport': { + name: t('attributeUiDisableReport'), + type: 'boolean', + }, + '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', + }, + 'distanceUnit': { + name: t('settingsDistanceUnit'), + type: 'string', + }, + 'speedUnit': { + name: t('settingsSpeedUnit'), + type: 'string', + }, + 'volumeUnit': { + name: t('settingsVolumeUnit'), + type: 'string', + }, + 'timezone': { + name: t('sharedTimezone'), + type: 'string', + }, +}; diff --git a/modern/src/reports/RouteReportPage.js b/modern/src/reports/RouteReportPage.js index a478ddbd..37e74f46 100644 --- a/modern/src/reports/RouteReportPage.js +++ b/modern/src/reports/RouteReportPage.js @@ -86,7 +86,7 @@ const RouteReportPage = () => { {t('reportDevice')} - setDeviceId(e.target.value)}> {devices.map((device) => ( {device.name} ))} @@ -94,7 +94,7 @@ const RouteReportPage = () => { {t('reportPeriod')} - setPeriod(e.target.value)}> {t('reportToday')} {t('reportYesterday')} {t('reportThisWeek')} @@ -111,7 +111,7 @@ const RouteReportPage = () => { label={t('reportFrom')} type='datetime-local' value={from.format(moment.HTML5_FMT.DATETIME_LOCAL)} - onChange={(e) => setFrom(moment(e.target.value, moment.HTML5_FMT.DATETIME_LOCAL))} + onChange={e => setFrom(moment(e.target.value, moment.HTML5_FMT.DATETIME_LOCAL))} fullWidth /> } {period === 'custom' && diff --git a/web/l10n/en.json b/web/l10n/en.json index 27043437..6e0c62f1 100644 --- a/web/l10n/en.json +++ b/web/l10n/en.json @@ -111,6 +111,7 @@ "attributeUiDisableCalendars": "UI: Disable Calendars", "attributeUiDisableMaintenance": "UI: Disable Maintenance", "attributeUiHidePositionAttributes": "UI: Hide Position Attributes", + "attributeNotificationTokens": "Notification Tokens", "errorTitle": "Error", "errorGeneral": "Invalid parameters or constraints violation", "errorConnection": "Connection error", -- cgit v1.2.3