diff options
author | Anton Tananaev <anton@traccar.org> | 2024-04-06 09:22:10 -0700 |
---|---|---|
committer | Anton Tananaev <anton@traccar.org> | 2024-04-06 09:22:10 -0700 |
commit | f418231b6b2f5e030a0d2dcc390c314602b1f740 (patch) | |
tree | 10326adf3792bc2697e06bb5f2b8ef2a8f7e55fe /src/common/util | |
parent | b392a4af78e01c8e0f50aad5468e9583675b24be (diff) | |
download | trackermap-web-f418231b6b2f5e030a0d2dcc390c314602b1f740.tar.gz trackermap-web-f418231b6b2f5e030a0d2dcc390c314602b1f740.tar.bz2 trackermap-web-f418231b6b2f5e030a0d2dcc390c314602b1f740.zip |
Move modern to the top
Diffstat (limited to 'src/common/util')
-rw-r--r-- | src/common/util/converter.js | 107 | ||||
-rw-r--r-- | src/common/util/deviceCategories.js | 24 | ||||
-rw-r--r-- | src/common/util/duration.js | 2 | ||||
-rw-r--r-- | src/common/util/formatter.js | 143 | ||||
-rw-r--r-- | src/common/util/permissions.js | 28 | ||||
-rw-r--r-- | src/common/util/preferences.js | 41 | ||||
-rw-r--r-- | src/common/util/stringUtils.js | 3 | ||||
-rw-r--r-- | src/common/util/useFeatures.js | 44 | ||||
-rw-r--r-- | src/common/util/usePersistedState.js | 22 | ||||
-rw-r--r-- | src/common/util/useQuery.js | 7 |
10 files changed, 421 insertions, 0 deletions
diff --git a/src/common/util/converter.js b/src/common/util/converter.js new file mode 100644 index 00000000..cb21566b --- /dev/null +++ b/src/common/util/converter.js @@ -0,0 +1,107 @@ +const speedConverter = (unit) => { + switch (unit) { + case 'kmh': + return 1.852; + case 'mph': + return 1.15078; + case 'kn': + default: + return 1; + } +}; + +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': + return 0.000621371; + case 'nmi': + return 0.000539957; + case 'km': + default: + return 0.001; + } +}; + +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 altitudeConverter = (unit) => { + switch (unit) { + case 'ft': + return 3.28084; + case 'm': + default: + return 1; + } +}; + +export const altitudeUnitString = (unit, t) => { + switch (unit) { + case 'ft': + return t('sharedFeet'); + case 'm': + default: + return t('sharedMeters'); + } +}; + +export const altitudeFromMeters = (value, unit) => value * altitudeConverter(unit); + +export const altitudeToMeters = (value, unit) => value / altitudeConverter(unit); + +const volumeConverter = (unit) => { + switch (unit) { + case 'impGal': + return 4.546; + case 'usGal': + return 3.785; + case 'ltr': + default: + return 1; + } +}; + +export const volumeUnitString = (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/src/common/util/deviceCategories.js b/src/common/util/deviceCategories.js new file mode 100644 index 00000000..a991e505 --- /dev/null +++ b/src/common/util/deviceCategories.js @@ -0,0 +1,24 @@ +export default [ + 'default', + 'animal', + 'bicycle', + 'boat', + 'bus', + 'car', + 'camper', + 'crane', + 'helicopter', + 'motorcycle', + 'offroad', + 'person', + 'pickup', + 'plane', + 'ship', + 'tractor', + 'train', + 'tram', + 'trolleybus', + 'truck', + 'van', + 'scooter', +]; diff --git a/src/common/util/duration.js b/src/common/util/duration.js new file mode 100644 index 00000000..aae74868 --- /dev/null +++ b/src/common/util/duration.js @@ -0,0 +1,2 @@ +export const snackBarDurationShortMs = 1500; +export const snackBarDurationLongMs = 2750; diff --git a/src/common/util/formatter.js b/src/common/util/formatter.js new file mode 100644 index 00000000..7b7fc96d --- /dev/null +++ b/src/common/util/formatter.js @@ -0,0 +1,143 @@ +import dayjs from 'dayjs'; +import duration from 'dayjs/plugin/duration'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +import { + altitudeFromMeters, + altitudeUnitString, + distanceFromMeters, + distanceUnitString, + speedFromKnots, + speedUnitString, + volumeFromLiters, + volumeUnitString, +} from './converter'; +import { prefixString } from './stringUtils'; + +dayjs.extend(duration); +dayjs.extend(relativeTime); + +export const formatBoolean = (value, t) => (value ? t('sharedYes') : t('sharedNo')); + +export const formatNumber = (value, precision = 1) => Number(value.toFixed(precision)); + +export const formatPercentage = (value) => `${value}%`; + +export const formatTemperature = (value) => `${value}°C`; + +export const formatVoltage = (value, t) => `${value} ${t('sharedVoltAbbreviation')}`; + +export const formatConsumption = (value, t) => `${value} ${t('sharedLiterPerHourAbbreviation')}`; + +export const formatTime = (value, format, hours12) => { + if (value) { + const d = dayjs(value); + switch (format) { + case 'date': + return d.format('YYYY-MM-DD'); + case 'time': + return d.format(hours12 ? 'hh:mm:ss A' : 'HH:mm:ss'); + case 'minutes': + return d.format(hours12 ? 'YYYY-MM-DD hh:mm A' : 'YYYY-MM-DD HH:mm'); + default: + return d.format(hours12 ? 'YYYY-MM-DD hh:mm:ss A' : 'YYYY-MM-DD HH:mm:ss'); + } + } + return ''; +}; + +export const formatStatus = (value, t) => t(prefixString('deviceStatus', value)); +export const formatAlarm = (value, t) => (value ? t(prefixString('alarm', value)) : ''); + +export const formatCourse = (value) => { + const courseValues = ['\u2191', '\u2197', '\u2192', '\u2198', '\u2193', '\u2199', '\u2190', '\u2196']; + let normalizedValue = (value + 45 / 2) % 360; + if (normalizedValue < 0) { + normalizedValue += 360; + } + return courseValues[Math.floor(normalizedValue / 45)]; +}; + +export const formatDistance = (value, unit, t) => `${distanceFromMeters(value, unit).toFixed(2)} ${distanceUnitString(unit, t)}`; + +export const formatAltitude = (value, unit, t) => `${altitudeFromMeters(value, unit).toFixed(2)} ${altitudeUnitString(unit, t)}`; + +export const formatSpeed = (value, unit, t) => `${speedFromKnots(value, unit).toFixed(2)} ${speedUnitString(unit, t)}`; + +export const formatVolume = (value, unit, t) => `${volumeFromLiters(value, unit).toFixed(2)} ${volumeUnitString(unit, t)}`; + +export const formatNumericHours = (value, t) => { + const hours = Math.floor(value / 3600000); + const minutes = Math.floor((value % 3600000) / 60000); + return `${hours} ${t('sharedHourAbbreviation')} ${minutes} ${t('sharedMinuteAbbreviation')}`; +}; + +export const formatCoordinate = (key, value, unit) => { + let hemisphere; + let degrees; + let minutes; + let seconds; + + if (key === 'latitude') { + hemisphere = value >= 0 ? 'N' : 'S'; + } else { + hemisphere = value >= 0 ? 'E' : 'W'; + } + + switch (unit) { + case 'ddm': + value = Math.abs(value); + degrees = Math.floor(value); + minutes = (value - degrees) * 60; + return `${degrees}° ${minutes.toFixed(6)}' ${hemisphere}`; + case 'dms': + value = Math.abs(value); + degrees = Math.floor(value); + minutes = Math.floor((value - degrees) * 60); + seconds = Math.round((value - degrees - minutes / 60) * 3600); + return `${degrees}° ${minutes}' ${seconds}" ${hemisphere}`; + default: + return `${value.toFixed(6)}°`; + } +}; + +export const getStatusColor = (status) => { + switch (status) { + case 'online': + return 'success'; + case 'offline': + return 'error'; + case 'unknown': + default: + return 'neutral'; + } +}; + +export const getBatteryStatus = (batteryLevel) => { + if (batteryLevel >= 70) { + return 'success'; + } + if (batteryLevel > 30) { + return 'warning'; + } + return 'error'; +}; + +export const formatNotificationTitle = (t, notification, includeId) => { + let title = t(prefixString('event', notification.type)); + if (notification.type === 'alarm') { + const alarmString = notification.attributes.alarms; + if (alarmString) { + const alarms = alarmString.split(','); + if (alarms.length > 1) { + title += ` (${alarms.length})`; + } else { + title += ` ${formatAlarm(alarms[0], t)}`; + } + } + } + if (includeId) { + title += ` [${notification.id}]`; + } + return title; +}; diff --git a/src/common/util/permissions.js b/src/common/util/permissions.js new file mode 100644 index 00000000..8a63b5a1 --- /dev/null +++ b/src/common/util/permissions.js @@ -0,0 +1,28 @@ +import { useSelector } from 'react-redux'; + +export const useAdministrator = () => useSelector((state) => { + const admin = state.session.user.administrator; + return admin; +}); + +export const useManager = () => useSelector((state) => { + const admin = state.session.user.administrator; + const manager = (state.session.user.userLimit || 0) !== 0; + return admin || manager; +}); + +export const useDeviceReadonly = () => useSelector((state) => { + const admin = state.session.user.administrator; + const serverReadonly = state.session.server.readonly; + const userReadonly = state.session.user.readonly; + const serverDeviceReadonly = state.session.server.deviceReadonly; + const userDeviceReadonly = state.session.user.deviceReadonly; + return !admin && (serverReadonly || userReadonly || serverDeviceReadonly || userDeviceReadonly); +}); + +export const useRestriction = (key) => useSelector((state) => { + const admin = state.session.user.administrator; + const serverValue = state.session.server[key]; + const userValue = state.session.user[key]; + return !admin && (serverValue || userValue); +}); diff --git a/src/common/util/preferences.js b/src/common/util/preferences.js new file mode 100644 index 00000000..229b6f17 --- /dev/null +++ b/src/common/util/preferences.js @@ -0,0 +1,41 @@ +import { useSelector } from 'react-redux'; + +const containsProperty = (object, key) => object.hasOwnProperty(key) && object[key] !== null; + +export const usePreference = (key, defaultValue) => useSelector((state) => { + if (state.session.server.forceSettings) { + if (containsProperty(state.session.server, key)) { + return state.session.server[key]; + } + if (containsProperty(state.session.user, key)) { + return state.session.user[key]; + } + return defaultValue; + } + if (containsProperty(state.session.user, key)) { + return state.session.user[key]; + } + if (containsProperty(state.session.server, key)) { + return state.session.server[key]; + } + return defaultValue; +}); + +export const useAttributePreference = (key, defaultValue) => useSelector((state) => { + if (state.session.server.forceSettings) { + if (containsProperty(state.session.server.attributes, key)) { + return state.session.server.attributes[key]; + } + if (containsProperty(state.session.user.attributes, key)) { + return state.session.user.attributes[key]; + } + return defaultValue; + } + if (containsProperty(state.session.user.attributes, key)) { + return state.session.user.attributes[key]; + } + if (containsProperty(state.session.server.attributes, key)) { + return state.session.server.attributes[key]; + } + return defaultValue; +}); diff --git a/src/common/util/stringUtils.js b/src/common/util/stringUtils.js new file mode 100644 index 00000000..fc997fe0 --- /dev/null +++ b/src/common/util/stringUtils.js @@ -0,0 +1,3 @@ +export const prefixString = (prefix, value) => prefix + value.charAt(0).toUpperCase() + value.slice(1); + +export const unprefixString = (prefix, value) => value.charAt(prefix.length).toLowerCase() + value.slice(prefix.length + 1); diff --git a/src/common/util/useFeatures.js b/src/common/util/useFeatures.js new file mode 100644 index 00000000..30361589 --- /dev/null +++ b/src/common/util/useFeatures.js @@ -0,0 +1,44 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { useSelector } from 'react-redux'; + +const get = (server, user, key) => { + if (server && user) { + if (user.administrator) { + return false; + } + if (server.forceSettings) { + return server.attributes[key] || user.attributes[key] || false; + } + return user.attributes[key] || server.attributes[key] || false; + } + return false; +}; + +const featureSelector = createSelector( + (state) => state.session.server, + (state) => state.session.user, + (server, user) => { + const disableSavedCommands = get(server, user, 'ui.disableSavedCommands'); + const disableAttributes = get(server, user, 'ui.disableAttributes'); + const disableVehicleFeatures = get(server, user, 'ui.disableVehicleFeatures'); + const disableDrivers = disableVehicleFeatures || get(server, user, 'ui.disableDrivers'); + const disableMaintenance = disableVehicleFeatures || get(server, user, 'ui.disableMaintenance'); + const disableGroups = get(server, user, 'ui.disableGroups'); + const disableEvents = get(server, user, 'ui.disableEvents'); + const disableComputedAttributes = get(server, user, 'ui.disableComputedAttributes'); + const disableCalendars = get(server, user, 'ui.disableCalendars'); + + return { + disableSavedCommands, + disableAttributes, + disableDrivers, + disableMaintenance, + disableGroups, + disableEvents, + disableComputedAttributes, + disableCalendars, + }; + }, +); + +export default () => useSelector(featureSelector); diff --git a/src/common/util/usePersistedState.js b/src/common/util/usePersistedState.js new file mode 100644 index 00000000..70a652ad --- /dev/null +++ b/src/common/util/usePersistedState.js @@ -0,0 +1,22 @@ +import { useEffect, useState } from 'react'; + +export const savePersistedState = (key, value) => { + window.localStorage.setItem(key, JSON.stringify(value)); +}; + +export default (key, defaultValue) => { + const [value, setValue] = useState(() => { + const stickyValue = window.localStorage.getItem(key); + return stickyValue ? JSON.parse(stickyValue) : defaultValue; + }); + + useEffect(() => { + if (value !== defaultValue) { + savePersistedState(key, value); + } else { + window.localStorage.removeItem(key); + } + }, [key, value]); + + return [value, setValue]; +}; diff --git a/src/common/util/useQuery.js b/src/common/util/useQuery.js new file mode 100644 index 00000000..f246df7c --- /dev/null +++ b/src/common/util/useQuery.js @@ -0,0 +1,7 @@ +import { useMemo } from 'react'; +import { useLocation } from 'react-router-dom'; + +export default () => { + const { search } = useLocation(); + return useMemo(() => new URLSearchParams(search), [search]); +}; |