diff options
author | Iván Ávalos <avalos@disroot.org> | 2024-06-14 21:01:24 -0600 |
---|---|---|
committer | Iván Ávalos <avalos@disroot.org> | 2024-06-14 21:01:24 -0600 |
commit | e9cdc61de90ef44dc2edd1c82d10f2f29803715e (patch) | |
tree | a1a06704904d4322396908ff93881b2d61af2669 /src/common | |
parent | 1a7f95baad1939589e6a23bd31b42b8e094a736a (diff) | |
parent | e4840d26a26dd298dbd97be74373b85607a84add (diff) | |
download | trackermap-web-e9cdc61de90ef44dc2edd1c82d10f2f29803715e.tar.gz trackermap-web-e9cdc61de90ef44dc2edd1c82d10f2f29803715e.tar.bz2 trackermap-web-e9cdc61de90ef44dc2edd1c82d10f2f29803715e.zip |
Merge tag 'v6.2'
Diffstat (limited to 'src/common')
-rw-r--r-- | src/common/attributes/useServerAttributes.js | 8 | ||||
-rw-r--r-- | src/common/attributes/useUserAttributes.js | 4 | ||||
-rw-r--r-- | src/common/components/LocalizationProvider.jsx | 6 | ||||
-rw-r--r-- | src/common/components/PositionValue.jsx | 9 | ||||
-rw-r--r-- | src/common/components/SelectField.jsx | 6 | ||||
-rw-r--r-- | src/common/components/StatusCard.jsx | 2 | ||||
-rw-r--r-- | src/common/components/TermsDialog.jsx | 36 | ||||
-rw-r--r-- | src/common/theme/index.js | 5 | ||||
-rw-r--r-- | src/common/util/formatter.js | 18 |
9 files changed, 74 insertions, 20 deletions
diff --git a/src/common/attributes/useServerAttributes.js b/src/common/attributes/useServerAttributes.js index 80ac3c7d..f46f715c 100644 --- a/src/common/attributes/useServerAttributes.js +++ b/src/common/attributes/useServerAttributes.js @@ -39,6 +39,14 @@ export default (t) => useMemo(() => ({ name: t('settingsDarkMode'), type: 'boolean', }, + termsUrl: { + name: t('userTerms'), + type: 'string', + }, + privacyUrl: { + name: t('userPrivacy'), + type: 'string', + }, totpEnable: { name: t('settingsTotpEnable'), type: 'boolean', diff --git a/src/common/attributes/useUserAttributes.js b/src/common/attributes/useUserAttributes.js index 81230884..e819412c 100644 --- a/src/common/attributes/useUserAttributes.js +++ b/src/common/attributes/useUserAttributes.js @@ -57,4 +57,8 @@ export default (t) => useMemo(() => ({ name: t('attributeMailSmtpPassword'), type: 'string', }, + termsAccepted: { + name: t('userTermsAccepted'), + type: 'boolean', + }, }), [t]); diff --git a/src/common/components/LocalizationProvider.jsx b/src/common/components/LocalizationProvider.jsx index 4104c773..3af97cf0 100644 --- a/src/common/components/LocalizationProvider.jsx +++ b/src/common/components/LocalizationProvider.jsx @@ -152,8 +152,9 @@ const LocalizationContext = createContext({ export const LocalizationProvider = ({ children }) => { const [language, setLanguage] = usePersistedState('language', getDefaultLanguage()); + const direction = /^(ar|he|fa)$/.test(language) ? 'rtl' : 'ltr'; - const value = useMemo(() => ({ languages, language, setLanguage }), [languages, language, setLanguage]); + const value = useMemo(() => ({ languages, language, setLanguage, direction }), [languages, language, setLanguage, direction]); useEffect(() => { let selected; @@ -163,7 +164,8 @@ export const LocalizationProvider = ({ children }) => { selected = language; } dayjs.locale(selected); - }, [language]); + document.dir = direction; + }, [language, direction]); return ( <LocalizationContext.Provider value={value}> diff --git a/src/common/components/PositionValue.jsx b/src/common/components/PositionValue.jsx index 8be686e3..cad3132c 100644 --- a/src/common/components/PositionValue.jsx +++ b/src/common/components/PositionValue.jsx @@ -22,7 +22,7 @@ import { import { speedToKnots } from '../util/converter'; import { useAttributePreference, usePreference } from '../util/preferences'; import { useTranslation } from './LocalizationProvider'; -import { useAdministrator } from '../util/permissions'; +import { useDeviceReadonly } from '../util/permissions'; import AddressValue from './AddressValue'; import GeofencesValue from './GeofencesValue'; import DriverValue from './DriverValue'; @@ -30,7 +30,7 @@ import DriverValue from './DriverValue'; const PositionValue = ({ position, property, attribute }) => { const t = useTranslation(); - const admin = useAdministrator(); + const deviceReadonly = useDeviceReadonly(); const device = useSelector((state) => state.devices.items[position.deviceId]); @@ -42,14 +42,13 @@ const PositionValue = ({ position, property, attribute }) => { const speedUnit = useAttributePreference('speedUnit'); const volumeUnit = useAttributePreference('volumeUnit'); const coordinateFormat = usePreference('coordinateFormat'); - const hours12 = usePreference('twelveHourFormat'); const formatValue = () => { switch (key) { case 'fixTime': case 'deviceTime': case 'serverTime': - return formatTime(value, 'seconds', hours12); + return formatTime(value, 'seconds'); case 'latitude': return formatCoordinate('latitude', value, coordinateFormat); case 'longitude': @@ -108,7 +107,7 @@ const PositionValue = ({ position, property, attribute }) => { <> {formatValue(value)} - {admin && <Link component={RouterLink} underline="none" to={`/settings/accumulators/${position.deviceId}`}>⚙</Link>} + {!deviceReadonly && <Link component={RouterLink} underline="none" to={`/settings/accumulators/${position.deviceId}`}>⚙</Link>} </> ); case 'address': diff --git a/src/common/components/SelectField.jsx b/src/common/components/SelectField.jsx index db8c30b0..629af9e1 100644 --- a/src/common/components/SelectField.jsx +++ b/src/common/components/SelectField.jsx @@ -1,7 +1,7 @@ +import React, { useEffect, useState } from 'react'; import { FormControl, InputLabel, MenuItem, Select, Autocomplete, TextField, } from '@mui/material'; -import React, { useState } from 'react'; import { useEffectAsync } from '../../reactHelper'; const SelectField = ({ @@ -17,7 +17,7 @@ const SelectField = ({ keyGetter = (item) => item.id, titleGetter = (item) => item.name, }) => { - const [items, setItems] = useState(data); + const [items, setItems] = useState(); const getOptionLabel = (option) => { if (typeof option !== 'object') { @@ -26,6 +26,8 @@ const SelectField = ({ return option ? titleGetter(option) : emptyTitle; }; + useEffect(() => setItems(data), [data]); + useEffectAsync(async () => { if (endpoint) { const response = await fetch(endpoint); diff --git a/src/common/components/StatusCard.jsx b/src/common/components/StatusCard.jsx index a63d0f80..fd92c658 100644 --- a/src/common/components/StatusCard.jsx +++ b/src/common/components/StatusCard.jsx @@ -127,7 +127,7 @@ const StatusCard = ({ deviceId, position, onClose, disableActions, desktopPaddin const deviceImage = device?.attributes?.deviceImage; const positionAttributes = usePositionAttributes(t); - const positionItems = useAttributePreference('positionItems', 'speed,address,totalDistance,course'); + const positionItems = useAttributePreference('positionItems', 'fixTime,address,speed,totalDistance'); const [anchorEl, setAnchorEl] = useState(null); diff --git a/src/common/components/TermsDialog.jsx b/src/common/components/TermsDialog.jsx new file mode 100644 index 00000000..2ac74f2e --- /dev/null +++ b/src/common/components/TermsDialog.jsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { + Button, Dialog, DialogActions, DialogContent, DialogContentText, Link, +} from '@mui/material'; +import { useTranslation } from './LocalizationProvider'; + +const TermsDialog = ({ open, onCancel, onAccept }) => { + const t = useTranslation(); + + const termsUrl = useSelector((state) => state.session.server.attributes.termsUrl); + const privacyUrl = useSelector((state) => state.session.server.attributes.privacyUrl); + + return ( + <Dialog + open={open} + onClose={onCancel} + > + <DialogContent> + <DialogContentText> + {t('userTermsPrompt')} + <ul> + <li><Link href={termsUrl} target="_blank">{t('userTerms')}</Link></li> + <li><Link href={privacyUrl} target="_blank">{t('userPrivacy')}</Link></li> + </ul> + </DialogContentText> + </DialogContent> + <DialogActions> + <Button onClick={onCancel}>{t('sharedCancel')}</Button> + <Button onClick={onAccept}>{t('sharedAccept')}</Button> + </DialogActions> + </Dialog> + ); +}; + +export default TermsDialog; diff --git a/src/common/theme/index.js b/src/common/theme/index.js index e8ce698b..00958497 100644 --- a/src/common/theme/index.js +++ b/src/common/theme/index.js @@ -4,8 +4,9 @@ import palette from './palette'; import dimensions from './dimensions'; import components from './components'; -export default (server, darkMode) => useMemo(() => createTheme({ +export default (server, darkMode, direction) => useMemo(() => createTheme({ palette: palette(server, darkMode), + direction, dimensions, components, -}), [server, darkMode]); +}), [server, darkMode, direction]); diff --git a/src/common/util/formatter.js b/src/common/util/formatter.js index 7b7fc96d..b10d737a 100644 --- a/src/common/util/formatter.js +++ b/src/common/util/formatter.js @@ -1,6 +1,7 @@ import dayjs from 'dayjs'; import duration from 'dayjs/plugin/duration'; import relativeTime from 'dayjs/plugin/relativeTime'; +import localizedFormat from 'dayjs/plugin/localizedFormat'; import { altitudeFromMeters, @@ -16,6 +17,7 @@ import { prefixString } from './stringUtils'; dayjs.extend(duration); dayjs.extend(relativeTime); +dayjs.extend(localizedFormat); export const formatBoolean = (value, t) => (value ? t('sharedYes') : t('sharedNo')); @@ -23,24 +25,24 @@ export const formatNumber = (value, precision = 1) => Number(value.toFixed(preci export const formatPercentage = (value) => `${value}%`; -export const formatTemperature = (value) => `${value}°C`; +export const formatTemperature = (value) => `${value.toFixed(1)}°C`; -export const formatVoltage = (value, t) => `${value} ${t('sharedVoltAbbreviation')}`; +export const formatVoltage = (value, t) => `${value.toFixed(2)} ${t('sharedVoltAbbreviation')}`; -export const formatConsumption = (value, t) => `${value} ${t('sharedLiterPerHourAbbreviation')}`; +export const formatConsumption = (value, t) => `${value.toFixed(2)} ${t('sharedLiterPerHourAbbreviation')}`; -export const formatTime = (value, format, hours12) => { +export const formatTime = (value, format) => { if (value) { const d = dayjs(value); switch (format) { case 'date': - return d.format('YYYY-MM-DD'); + return d.format('L'); case 'time': - return d.format(hours12 ? 'hh:mm:ss A' : 'HH:mm:ss'); + return d.format('LTS'); case 'minutes': - return d.format(hours12 ? 'YYYY-MM-DD hh:mm A' : 'YYYY-MM-DD HH:mm'); + return d.format('L LT'); default: - return d.format(hours12 ? 'YYYY-MM-DD hh:mm:ss A' : 'YYYY-MM-DD HH:mm:ss'); + return d.format('L LTS'); } } return ''; |