aboutsummaryrefslogtreecommitdiff
path: root/src/common
diff options
context:
space:
mode:
authorIván Ávalos <avalos@disroot.org>2024-06-14 21:01:24 -0600
committerIván Ávalos <avalos@disroot.org>2024-06-14 21:01:24 -0600
commite9cdc61de90ef44dc2edd1c82d10f2f29803715e (patch)
treea1a06704904d4322396908ff93881b2d61af2669 /src/common
parent1a7f95baad1939589e6a23bd31b42b8e094a736a (diff)
parente4840d26a26dd298dbd97be74373b85607a84add (diff)
downloadtrackermap-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.js8
-rw-r--r--src/common/attributes/useUserAttributes.js4
-rw-r--r--src/common/components/LocalizationProvider.jsx6
-rw-r--r--src/common/components/PositionValue.jsx9
-rw-r--r--src/common/components/SelectField.jsx6
-rw-r--r--src/common/components/StatusCard.jsx2
-rw-r--r--src/common/components/TermsDialog.jsx36
-rw-r--r--src/common/theme/index.js5
-rw-r--r--src/common/util/formatter.js18
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)}
&nbsp;&nbsp;
- {admin && <Link component={RouterLink} underline="none" to={`/settings/accumulators/${position.deviceId}`}>&#9881;</Link>}
+ {!deviceReadonly && <Link component={RouterLink} underline="none" to={`/settings/accumulators/${position.deviceId}`}>&#9881;</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 '';