aboutsummaryrefslogtreecommitdiff
path: root/modern/src/common
diff options
context:
space:
mode:
authorAnton Tananaev <anton@traccar.org>2024-04-06 09:22:10 -0700
committerAnton Tananaev <anton@traccar.org>2024-04-06 09:22:10 -0700
commitf418231b6b2f5e030a0d2dcc390c314602b1f740 (patch)
tree10326adf3792bc2697e06bb5f2b8ef2a8f7e55fe /modern/src/common
parentb392a4af78e01c8e0f50aad5468e9583675b24be (diff)
downloadtrackermap-web-f418231b6b2f5e030a0d2dcc390c314602b1f740.tar.gz
trackermap-web-f418231b6b2f5e030a0d2dcc390c314602b1f740.tar.bz2
trackermap-web-f418231b6b2f5e030a0d2dcc390c314602b1f740.zip
Move modern to the top
Diffstat (limited to 'modern/src/common')
-rw-r--r--modern/src/common/attributes/useCommandAttributes.js213
-rw-r--r--modern/src/common/attributes/useCommonDeviceAttributes.js21
-rw-r--r--modern/src/common/attributes/useCommonUserAttributes.js136
-rw-r--r--modern/src/common/attributes/useDeviceAttributes.js33
-rw-r--r--modern/src/common/attributes/useGeofenceAttributes.js18
-rw-r--r--modern/src/common/attributes/useGroupAttributes.js12
-rw-r--r--modern/src/common/attributes/usePositionAttributes.js380
-rw-r--r--modern/src/common/attributes/useServerAttributes.js62
-rw-r--r--modern/src/common/attributes/useUserAttributes.js60
-rw-r--r--modern/src/common/components/AddressValue.jsx37
-rw-r--r--modern/src/common/components/BottomMenu.jsx135
-rw-r--r--modern/src/common/components/DriverValue.js9
-rw-r--r--modern/src/common/components/ErrorHandler.jsx27
-rw-r--r--modern/src/common/components/GeofencesValue.js9
-rw-r--r--modern/src/common/components/LinkField.jsx93
-rw-r--r--modern/src/common/components/LocalizationProvider.jsx187
-rw-r--r--modern/src/common/components/NativeInterface.js72
-rw-r--r--modern/src/common/components/NavBar.jsx25
-rw-r--r--modern/src/common/components/PageLayout.jsx118
-rw-r--r--modern/src/common/components/PositionValue.jsx133
-rw-r--r--modern/src/common/components/RemoveDialog.jsx54
-rw-r--r--modern/src/common/components/SelectField.jsx77
-rw-r--r--modern/src/common/components/SideNav.jsx33
-rw-r--r--modern/src/common/components/SplitButton.jsx48
-rw-r--r--modern/src/common/components/StatusCard.jsx288
-rw-r--r--modern/src/common/components/TableShimmer.jsx17
-rw-r--r--modern/src/common/theme/components.js40
-rw-r--r--modern/src/common/theme/dimensions.js14
-rw-r--r--modern/src/common/theme/index.js11
-rw-r--r--modern/src/common/theme/palette.js22
-rw-r--r--modern/src/common/util/converter.js107
-rw-r--r--modern/src/common/util/deviceCategories.js24
-rw-r--r--modern/src/common/util/duration.js2
-rw-r--r--modern/src/common/util/formatter.js143
-rw-r--r--modern/src/common/util/permissions.js28
-rw-r--r--modern/src/common/util/preferences.js41
-rw-r--r--modern/src/common/util/stringUtils.js3
-rw-r--r--modern/src/common/util/useFeatures.js44
-rw-r--r--modern/src/common/util/usePersistedState.js22
-rw-r--r--modern/src/common/util/useQuery.js7
40 files changed, 0 insertions, 2805 deletions
diff --git a/modern/src/common/attributes/useCommandAttributes.js b/modern/src/common/attributes/useCommandAttributes.js
deleted file mode 100644
index 189a0e2e..00000000
--- a/modern/src/common/attributes/useCommandAttributes.js
+++ /dev/null
@@ -1,213 +0,0 @@
-import { useMemo } from 'react';
-
-export default (t) => useMemo(() => ({
- custom: [
- {
- key: 'data',
- name: t('commandData'),
- type: 'string',
- },
- ],
- positionPeriodic: [
- {
- key: 'frequency',
- name: t('commandFrequency'),
- type: 'number',
- },
- ],
- setTimezone: [
- {
- key: 'timezone',
- name: t('commandTimezone'),
- type: 'string',
- },
- ],
- sendSms: [
- {
- key: 'phone',
- name: t('commandPhone'),
- type: 'string',
- },
- {
- key: 'message',
- name: t('commandMessage'),
- type: 'string',
- },
- ],
- message: [
- {
- key: 'message',
- name: t('commandMessage'),
- type: 'string',
- },
- ],
- sendUssd: [
- {
- key: 'phone',
- name: t('commandPhone'),
- type: 'string',
- },
- ],
- sosNumber: [
- {
- key: 'index',
- name: t('commandIndex'),
- type: 'number',
- },
- {
- key: 'phone',
- name: t('commandPhone'),
- type: 'string',
- },
- ],
- silenceTime: [
- {
- key: 'data',
- name: t('commandData'),
- type: 'string',
- },
- ],
- setPhonebook: [
- {
- key: 'data',
- name: t('commandData'),
- type: 'string',
- },
- ],
- voiceMessage: [
- {
- key: 'data',
- name: t('commandData'),
- type: 'string',
- },
- ],
- outputControl: [
- {
- key: 'index',
- name: t('commandIndex'),
- type: 'number',
- },
- {
- key: 'data',
- name: t('commandData'),
- type: 'string',
- },
- ],
- voiceMonitoring: [
- {
- key: 'enable',
- name: t('commandEnable'),
- type: 'boolean',
- },
- ],
- setAgps: [
- {
- key: 'enable',
- name: t('commandEnable'),
- type: 'boolean',
- },
- ],
- setIndicator: [
- {
- key: 'data',
- name: t('commandData'),
- type: 'string',
- },
- ],
- configuration: [
- {
- key: 'data',
- name: t('commandData'),
- type: 'string',
- },
- ],
- setConnection: [
- {
- key: 'server',
- name: t('commandServer'),
- type: 'string',
- },
- {
- key: 'port',
- name: t('commandPort'),
- type: 'number',
- },
- ],
- setOdometer: [
- {
- key: 'data',
- name: t('commandData'),
- type: 'string',
- },
- ],
- modePowerSaving: [
- {
- key: 'enable',
- name: t('commandEnable'),
- type: 'boolean',
- },
- ],
- modeDeepSleep: [
- {
- key: 'enable',
- name: t('commandEnable'),
- type: 'boolean',
- },
- ],
- alarmGeofence: [
- {
- key: 'radius',
- name: t('commandRadius'),
- type: 'number',
- },
- ],
- alarmBattery: [
- {
- key: 'enable',
- name: t('commandEnable'),
- type: 'boolean',
- },
- ],
- alarmSos: [
- {
- key: 'enable',
- name: t('commandEnable'),
- type: 'boolean',
- },
- ],
- alarmRemove: [
- {
- key: 'enable',
- name: t('commandEnable'),
- type: 'boolean',
- },
- ],
- alarmClock: [
- {
- key: 'data',
- name: t('commandData'),
- type: 'string',
- },
- ],
- alarmSpeed: [
- {
- key: 'data',
- name: t('commandData'),
- type: 'string',
- },
- ],
- alarmFall: [
- {
- key: 'enable',
- name: t('commandEnable'),
- type: 'boolean',
- },
- ],
- alarmVibration: [
- {
- key: 'data',
- name: t('commandData'),
- type: 'string',
- },
- ],
-}), [t]);
diff --git a/modern/src/common/attributes/useCommonDeviceAttributes.js b/modern/src/common/attributes/useCommonDeviceAttributes.js
deleted file mode 100644
index f9214818..00000000
--- a/modern/src/common/attributes/useCommonDeviceAttributes.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { useMemo } from 'react';
-
-export default (t) => useMemo(() => ({
- speedLimit: {
- name: t('attributeSpeedLimit'),
- type: 'number',
- subtype: 'speed',
- },
- fuelDropThreshold: {
- name: t('attributeFuelDropThreshold'),
- type: 'number',
- },
- fuelIncreaseThreshold: {
- name: t('attributeFuelIncreaseThreshold'),
- type: 'number',
- },
- 'report.ignoreOdometer': {
- name: t('attributeReportIgnoreOdometer'),
- type: 'boolean',
- },
-}), [t]);
diff --git a/modern/src/common/attributes/useCommonUserAttributes.js b/modern/src/common/attributes/useCommonUserAttributes.js
deleted file mode 100644
index 294ddea8..00000000
--- a/modern/src/common/attributes/useCommonUserAttributes.js
+++ /dev/null
@@ -1,136 +0,0 @@
-import { useMemo } from 'react';
-
-export default (t) => useMemo(() => ({
- mapGeofences: {
- name: t('attributeShowGeofences'),
- type: 'boolean',
- },
- mapLiveRoutes: {
- name: t('mapLiveRoutes'),
- type: 'string',
- },
- mapDirection: {
- name: t('mapDirection'),
- type: 'string',
- },
- mapFollow: {
- name: t('deviceFollow'),
- type: 'boolean',
- },
- mapCluster: {
- name: t('mapClustering'),
- type: 'boolean',
- },
- mapOnSelect: {
- name: t('mapOnSelect'),
- type: 'boolean',
- },
- activeMapStyles: {
- name: t('mapActive'),
- type: 'string',
- },
- devicePrimary: {
- name: t('devicePrimaryInfo'),
- type: 'string',
- },
- deviceSecondary: {
- name: t('deviceSecondaryInfo'),
- type: 'string',
- },
- soundEvents: {
- name: t('eventsSoundEvents'),
- type: 'string',
- },
- soundAlarms: {
- name: t('eventsSoundAlarms'),
- type: 'string',
- },
- positionItems: {
- name: t('attributePopupInfo'),
- type: 'string',
- },
- locationIqKey: {
- name: t('mapLocationIqKey'),
- type: 'string',
- },
- mapboxAccessToken: {
- name: t('mapMapboxKey'),
- type: 'string',
- },
- mapTilerKey: {
- name: t('mapMapTilerKey'),
- type: 'string',
- },
- bingMapsKey: {
- name: t('mapBingKey'),
- type: 'string',
- },
- openWeatherKey: {
- name: t('mapOpenWeatherKey'),
- type: 'string',
- },
- tomTomKey: {
- name: t('mapTomTomKey'),
- type: 'string',
- },
- hereKey: {
- name: t('mapHereKey'),
- type: 'string',
- },
- notificationTokens: {
- name: t('attributeNotificationTokens'),
- type: 'string',
- },
- 'ui.disableSavedCommands': {
- name: t('attributeUiDisableSavedCommands'),
- type: 'boolean',
- },
- 'ui.disableGroups': {
- name: t('attributeUiDisableGroups'),
- type: 'boolean',
- },
- 'ui.disableAttributes': {
- name: t('attributeUiDisableAttributes'),
- type: 'boolean',
- },
- 'ui.disableEvents': {
- name: t('attributeUiDisableEvents'),
- type: 'boolean',
- },
- 'ui.disableVehicleFeatures': {
- name: t('attributeUiDisableVehicleFeatures'),
- 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',
- },
- 'web.liveRouteLength': {
- name: t('attributeWebLiveRouteLength'),
- type: 'number',
- },
- 'web.selectZoom': {
- name: t('attributeWebSelectZoom'),
- type: 'number',
- },
- 'web.maxZoom': {
- name: t('attributeWebMaxZoom'),
- type: 'number',
- },
- iconScale: {
- name: t('sharedIconScale'),
- type: 'number',
- },
-}), [t]);
diff --git a/modern/src/common/attributes/useDeviceAttributes.js b/modern/src/common/attributes/useDeviceAttributes.js
deleted file mode 100644
index eab9b8f6..00000000
--- a/modern/src/common/attributes/useDeviceAttributes.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { useMemo } from 'react';
-
-export default (t) => useMemo(() => ({
- 'web.reportColor': {
- name: t('attributeWebReportColor'),
- type: 'string',
- subtype: 'color',
- },
- devicePassword: {
- name: t('attributeDevicePassword'),
- type: 'string',
- },
- deviceImage: {
- name: t('attributeDeviceImage'),
- type: 'string',
- },
- 'processing.copyAttributes': {
- name: t('attributeProcessingCopyAttributes'),
- type: 'string',
- },
- 'decoder.timezone': {
- name: t('sharedTimezone'),
- type: 'string',
- },
- deviceInactivityStart: {
- name: t('attributeDeviceInactivityStart'),
- type: 'number',
- },
- deviceInactivityPeriod: {
- name: t('attributeDeviceInactivityPeriod'),
- type: 'number',
- },
-}), [t]);
diff --git a/modern/src/common/attributes/useGeofenceAttributes.js b/modern/src/common/attributes/useGeofenceAttributes.js
deleted file mode 100644
index a5cd068b..00000000
--- a/modern/src/common/attributes/useGeofenceAttributes.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { useMemo } from 'react';
-
-export default (t) => useMemo(() => ({
- color: {
- name: t('attributeColor'),
- type: 'string',
- },
- speedLimit: {
- name: t('attributeSpeedLimit'),
- type: 'number',
- subtype: 'speed',
- },
- polylineDistance: {
- name: t('attributePolylineDistance'),
- type: 'number',
- subtype: 'distance',
- },
-}), [t]);
diff --git a/modern/src/common/attributes/useGroupAttributes.js b/modern/src/common/attributes/useGroupAttributes.js
deleted file mode 100644
index 53a299e1..00000000
--- a/modern/src/common/attributes/useGroupAttributes.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { useMemo } from 'react';
-
-export default (t) => useMemo(() => ({
- 'processing.copyAttributes': {
- name: t('attributeProcessingCopyAttributes'),
- type: 'string',
- },
- 'decoder.timezone': {
- name: t('sharedTimezone'),
- type: 'string',
- },
-}), [t]);
diff --git a/modern/src/common/attributes/usePositionAttributes.js b/modern/src/common/attributes/usePositionAttributes.js
deleted file mode 100644
index 0b191ebc..00000000
--- a/modern/src/common/attributes/usePositionAttributes.js
+++ /dev/null
@@ -1,380 +0,0 @@
-import { useMemo } from 'react';
-
-export default (t) => useMemo(() => ({
- id: {
- name: t('deviceIdentifier'),
- type: 'number',
- property: true,
- },
- latitude: {
- name: t('positionLatitude'),
- type: 'number',
- property: true,
- },
- longitude: {
- name: t('positionLongitude'),
- type: 'number',
- property: true,
- },
- speed: {
- name: t('positionSpeed'),
- type: 'number',
- dataType: 'speed',
- property: true,
- },
- course: {
- name: t('positionCourse'),
- type: 'number',
- property: true,
- },
- altitude: {
- name: t('positionAltitude'),
- type: 'number',
- property: true,
- },
- accuracy: {
- name: t('positionAccuracy'),
- type: 'number',
- dataType: 'distance',
- property: true,
- },
- valid: {
- name: t('positionValid'),
- type: 'boolean',
- property: true,
- },
- protocol: {
- name: t('positionProtocol'),
- type: 'string',
- property: true,
- },
- address: {
- name: t('positionAddress'),
- type: 'string',
- property: true,
- },
- deviceTime: {
- name: t('positionDeviceTime'),
- type: 'string',
- property: true,
- },
- fixTime: {
- name: t('positionFixTime'),
- type: 'string',
- property: true,
- },
- serverTime: {
- name: t('positionServerTime'),
- type: 'string',
- property: true,
- },
- geofenceIds: {
- name: t('sharedGeofences'),
- property: true,
- },
- raw: {
- name: t('positionRaw'),
- type: 'string',
- },
- index: {
- name: t('positionIndex'),
- type: 'number',
- },
- hdop: {
- name: t('positionHdop'),
- type: 'number',
- },
- vdop: {
- name: t('positionVdop'),
- type: 'number',
- },
- pdop: {
- name: t('positionPdop'),
- type: 'number',
- },
- sat: {
- name: t('positionSat'),
- type: 'number',
- },
- satVisible: {
- name: t('positionSatVisible'),
- type: 'number',
- },
- rssi: {
- name: t('positionRssi'),
- type: 'number',
- },
- coolantTemp: {
- name: t('positionCoolantTemp'),
- type: 'number',
- },
- gps: {
- name: t('positionGps'),
- type: 'number',
- },
- roaming: {
- name: t('positionRoaming'),
- type: 'boolean',
- },
- event: {
- name: t('positionEvent'),
- type: 'string',
- },
- alarm: {
- name: t('positionAlarm'),
- type: 'string',
- },
- status: {
- name: t('positionStatus'),
- type: 'string',
- },
- odometer: {
- name: t('positionOdometer'),
- type: 'number',
- dataType: 'distance',
- },
- serviceOdometer: {
- name: t('positionServiceOdometer'),
- type: 'number',
- dataType: 'distance',
- },
- tripOdometer: {
- name: t('positionTripOdometer'),
- type: 'number',
- dataType: 'distance',
- },
- hours: {
- name: t('positionHours'),
- type: 'number',
- dataType: 'hours',
- },
- steps: {
- name: t('positionSteps'),
- type: 'number',
- },
- heartRate: {
- name: t('positionHeartRate'),
- type: 'number',
- },
- input: {
- name: t('positionInput'),
- type: 'number',
- },
- in1: {
- name: `${t('positionInput')} 1`,
- type: 'boolean',
- },
- in2: {
- name: `${t('positionInput')} 2`,
- type: 'boolean',
- },
- in3: {
- name: `${t('positionInput')} 3`,
- type: 'boolean',
- },
- in4: {
- name: `${t('positionInput')} 4`,
- type: 'boolean',
- },
- output: {
- name: t('positionOutput'),
- type: 'number',
- },
- out1: {
- name: `${t('positionOutput')} 1`,
- type: 'boolean',
- },
- out2: {
- name: `${t('positionOutput')} 2`,
- type: 'boolean',
- },
- out3: {
- name: `${t('positionOutput')} 3`,
- type: 'boolean',
- },
- out4: {
- name: `${t('positionOutput')} 4`,
- type: 'boolean',
- },
- power: {
- name: t('positionPower'),
- type: 'number',
- dataType: 'voltage',
- },
- battery: {
- name: t('positionBattery'),
- type: 'number',
- dataType: 'voltage',
- },
- batteryLevel: {
- name: t('positionBatteryLevel'),
- type: 'number',
- dataType: 'percentage',
- },
- fuel: {
- name: t('positionFuel'),
- type: 'number',
- dataType: 'volume',
- },
- fuelConsumption: {
- name: t('positionFuelConsumption'),
- type: 'number',
- },
- versionFw: {
- name: t('positionVersionFw'),
- type: 'string',
- },
- versionHw: {
- name: t('positionVersionHw'),
- type: 'string',
- },
- type: {
- name: t('sharedType'),
- type: 'string',
- },
- ignition: {
- name: t('positionIgnition'),
- type: 'boolean',
- },
- flags: {
- name: t('positionFlags'),
- type: 'string',
- },
- charge: {
- name: t('positionCharge'),
- type: 'boolean',
- },
- ip: {
- name: t('positionIp'),
- type: 'string',
- },
- archive: {
- name: t('positionArchive'),
- type: 'boolean',
- },
- distance: {
- name: t('positionDistance'),
- type: 'number',
- dataType: 'distance',
- },
- totalDistance: {
- name: t('deviceTotalDistance'),
- type: 'number',
- dataType: 'distance',
- },
- rpm: {
- name: t('positionRpm'),
- type: 'number',
- },
- vin: {
- name: t('positionVin'),
- type: 'string',
- },
- approximate: {
- name: t('positionApproximate'),
- type: 'boolean',
- },
- throttle: {
- name: t('positionThrottle'),
- type: 'number',
- },
- motion: {
- name: t('positionMotion'),
- type: 'boolean',
- },
- armed: {
- name: t('positionArmed'),
- type: 'boolean',
- },
- geofence: {
- name: t('sharedGeofence'),
- type: 'string',
- },
- acceleration: {
- name: t('positionAcceleration'),
- type: 'number',
- },
- deviceTemp: {
- name: t('positionDeviceTemp'),
- type: 'number',
- },
- temp1: {
- name: `${t('positionTemp')} 1`,
- type: 'number',
- },
- temp2: {
- name: `${t('positionTemp')} 2`,
- type: 'number',
- },
- temp3: {
- name: `${t('positionTemp')} 3`,
- type: 'number',
- },
- temp4: {
- name: `${t('positionTemp')} 4`,
- type: 'number',
- },
- operator: {
- name: t('positionOperator'),
- type: 'string',
- },
- command: {
- name: t('deviceCommand'),
- type: 'string',
- },
- blocked: {
- name: t('positionBlocked'),
- type: 'boolean',
- },
- lock: {
- name: t('alarmLock'),
- type: 'boolean',
- },
- dtcs: {
- name: t('positionDtcs'),
- type: 'string',
- },
- obdSpeed: {
- name: t('positionObdSpeed'),
- type: 'number',
- dataType: 'speed',
- },
- obdOdometer: {
- name: t('positionObdOdometer'),
- type: 'number',
- dataType: 'distance',
- },
- result: {
- name: t('eventCommandResult'),
- type: 'string',
- },
- driverUniqueId: {
- name: t('sharedDriver'),
- type: 'string',
- },
- card: {
- name: t('positionCard'),
- type: 'string',
- },
- drivingTime: {
- name: t('positionDrivingTime'),
- type: 'number',
- dataType: 'hours',
- },
- color: {
- name: t('attributeColor'),
- type: 'string',
- },
- image: {
- name: t('positionImage'),
- type: 'string',
- },
- video: {
- name: t('positionVideo'),
- type: 'string',
- },
- audio: {
- name: t('positionAudio'),
- type: 'string',
- },
-}), [t]);
diff --git a/modern/src/common/attributes/useServerAttributes.js b/modern/src/common/attributes/useServerAttributes.js
deleted file mode 100644
index 80ac3c7d..00000000
--- a/modern/src/common/attributes/useServerAttributes.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import { useMemo } from 'react';
-
-export default (t) => useMemo(() => ({
- support: {
- name: t('settingsSupport'),
- type: 'string',
- },
- title: {
- name: t('serverName'),
- type: 'string',
- },
- description: {
- name: t('serverDescription'),
- type: 'string',
- },
- logo: {
- name: t('serverLogo'),
- type: 'string',
- },
- logoInverted: {
- name: t('serverLogoInverted'),
- type: 'string',
- },
- colorPrimary: {
- name: t('serverColorPrimary'),
- type: 'string',
- subtype: 'color',
- },
- colorSecondary: {
- name: t('serverColorSecondary'),
- type: 'string',
- subtype: 'color',
- },
- disableChange: {
- name: t('serverChangeDisable'),
- type: 'boolean',
- },
- darkMode: {
- name: t('settingsDarkMode'),
- type: 'boolean',
- },
- totpEnable: {
- name: t('settingsTotpEnable'),
- type: 'boolean',
- },
- totpForce: {
- name: t('settingsTotpForce'),
- type: 'boolean',
- },
- serviceWorkerUpdateInterval: {
- name: t('settingsServiceWorkerUpdateInterval'),
- type: 'number',
- },
- 'ui.disableLoginLanguage': {
- name: t('attributeUiDisableLoginLanguage'),
- type: 'boolean',
- },
- disableShare: {
- name: t('serverDisableShare'),
- type: 'boolean',
- },
-}), [t]);
diff --git a/modern/src/common/attributes/useUserAttributes.js b/modern/src/common/attributes/useUserAttributes.js
deleted file mode 100644
index 81230884..00000000
--- a/modern/src/common/attributes/useUserAttributes.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import { useMemo } from 'react';
-
-export default (t) => useMemo(() => ({
- telegramChatId: {
- name: t('attributeTelegramChatId'),
- type: 'string',
- },
- pushoverUserKey: {
- name: t('attributePushoverUserKey'),
- type: 'string',
- },
- pushoverDeviceNames: {
- name: t('attributePushoverDeviceNames'),
- type: 'string',
- },
- 'mail.smtp.host': {
- name: t('attributeMailSmtpHost'),
- type: 'string',
- },
- 'mail.smtp.port': {
- name: t('attributeMailSmtpPort'),
- type: 'number',
- },
- 'mail.smtp.starttls.enable': {
- name: t('attributeMailSmtpStarttlsEnable'),
- type: 'boolean',
- },
- 'mail.smtp.starttls.required': {
- name: t('attributeMailSmtpStarttlsRequired'),
- type: 'boolean',
- },
- 'mail.smtp.ssl.enable': {
- name: t('attributeMailSmtpSslEnable'),
- type: 'boolean',
- },
- 'mail.smtp.ssl.trust': {
- name: t('attributeMailSmtpSslTrust'),
- type: 'string',
- },
- 'mail.smtp.ssl.protocols': {
- name: t('attributeMailSmtpSslProtocols'),
- type: 'string',
- },
- 'mail.smtp.from': {
- name: t('attributeMailSmtpFrom'),
- type: 'string',
- },
- 'mail.smtp.auth': {
- name: t('attributeMailSmtpAuth'),
- type: 'boolean',
- },
- 'mail.smtp.username': {
- name: t('attributeMailSmtpUsername'),
- type: 'string',
- },
- 'mail.smtp.password': {
- name: t('attributeMailSmtpPassword'),
- type: 'string',
- },
-}), [t]);
diff --git a/modern/src/common/components/AddressValue.jsx b/modern/src/common/components/AddressValue.jsx
deleted file mode 100644
index 827a71de..00000000
--- a/modern/src/common/components/AddressValue.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { useSelector } from 'react-redux';
-import { Link } from '@mui/material';
-import { useTranslation } from './LocalizationProvider';
-import { useCatch } from '../../reactHelper';
-
-const AddressValue = ({ latitude, longitude, originalAddress }) => {
- const t = useTranslation();
-
- const addressEnabled = useSelector((state) => state.session.server.geocoderEnabled);
-
- const [address, setAddress] = useState();
-
- useEffect(() => {
- setAddress(originalAddress);
- }, [latitude, longitude, originalAddress]);
-
- const showAddress = useCatch(async () => {
- const query = new URLSearchParams({ latitude, longitude });
- const response = await fetch(`/api/server/geocode?${query.toString()}`);
- if (response.ok) {
- setAddress(await response.text());
- } else {
- throw Error(await response.text());
- }
- });
-
- if (address) {
- return address;
- }
- if (addressEnabled) {
- return (<Link href="#" onClick={showAddress}>{t('sharedShowAddress')}</Link>);
- }
- return '';
-};
-
-export default AddressValue;
diff --git a/modern/src/common/components/BottomMenu.jsx b/modern/src/common/components/BottomMenu.jsx
deleted file mode 100644
index 07fa2e11..00000000
--- a/modern/src/common/components/BottomMenu.jsx
+++ /dev/null
@@ -1,135 +0,0 @@
-import React, { useState } from 'react';
-import { useDispatch, useSelector } from 'react-redux';
-import { useNavigate, useLocation } from 'react-router-dom';
-import {
- Paper, BottomNavigation, BottomNavigationAction, Menu, MenuItem, Typography, Badge,
-} from '@mui/material';
-
-import DescriptionIcon from '@mui/icons-material/Description';
-import SettingsIcon from '@mui/icons-material/Settings';
-import MapIcon from '@mui/icons-material/Map';
-import PersonIcon from '@mui/icons-material/Person';
-import ExitToAppIcon from '@mui/icons-material/ExitToApp';
-
-import { sessionActions } from '../../store';
-import { useTranslation } from './LocalizationProvider';
-import { useRestriction } from '../util/permissions';
-import { nativePostMessage } from './NativeInterface';
-
-const BottomMenu = () => {
- const navigate = useNavigate();
- const location = useLocation();
- const dispatch = useDispatch();
- const t = useTranslation();
-
- const readonly = useRestriction('readonly');
- const disableReports = useRestriction('disableReports');
- const user = useSelector((state) => state.session.user);
- const socket = useSelector((state) => state.session.socket);
-
- const [anchorEl, setAnchorEl] = useState(null);
-
- const currentSelection = () => {
- if (location.pathname === `/settings/user/${user.id}`) {
- return 'account';
- } if (location.pathname.startsWith('/settings')) {
- return 'settings';
- } if (location.pathname.startsWith('/reports')) {
- return 'reports';
- } if (location.pathname === '/') {
- return 'map';
- }
- return null;
- };
-
- const handleAccount = () => {
- setAnchorEl(null);
- navigate(`/settings/user/${user.id}`);
- };
-
- const handleLogout = async () => {
- setAnchorEl(null);
-
- const notificationToken = window.localStorage.getItem('notificationToken');
- if (notificationToken && !user.readonly) {
- window.localStorage.removeItem('notificationToken');
- const tokens = user.attributes.notificationTokens?.split(',') || [];
- if (tokens.includes(notificationToken)) {
- const updatedUser = {
- ...user,
- attributes: {
- ...user.attributes,
- notificationTokens: tokens.length > 1 ? tokens.filter((it) => it !== notificationToken).join(',') : undefined,
- },
- };
- await fetch(`/api/users/${user.id}`, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(updatedUser),
- });
- }
- }
-
- await fetch('/api/session', { method: 'DELETE' });
- nativePostMessage('logout');
- navigate('/login');
- dispatch(sessionActions.updateUser(null));
- };
-
- const handleSelection = (event, value) => {
- switch (value) {
- case 'map':
- navigate('/');
- break;
- case 'reports':
- navigate('/reports/combined');
- break;
- case 'settings':
- navigate('/settings/preferences');
- break;
- case 'account':
- setAnchorEl(event.currentTarget);
- break;
- case 'logout':
- handleLogout();
- break;
- default:
- break;
- }
- };
-
- return (
- <Paper square elevation={3}>
- <BottomNavigation value={currentSelection()} onChange={handleSelection} showLabels>
- <BottomNavigationAction
- label={t('mapTitle')}
- icon={(
- <Badge color="error" variant="dot" overlap="circular" invisible={socket !== false}>
- <MapIcon />
- </Badge>
- )}
- value="map"
- />
- {!disableReports && (
- <BottomNavigationAction label={t('reportTitle')} icon={<DescriptionIcon />} value="reports" />
- )}
- <BottomNavigationAction label={t('settingsTitle')} icon={<SettingsIcon />} value="settings" />
- {readonly ? (
- <BottomNavigationAction label={t('loginLogout')} icon={<ExitToAppIcon />} value="logout" />
- ) : (
- <BottomNavigationAction label={t('settingsUser')} icon={<PersonIcon />} value="account" />
- )}
- </BottomNavigation>
- <Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={() => setAnchorEl(null)}>
- <MenuItem onClick={handleAccount}>
- <Typography color="textPrimary">{t('settingsUser')}</Typography>
- </MenuItem>
- <MenuItem onClick={handleLogout}>
- <Typography color="error">{t('loginLogout')}</Typography>
- </MenuItem>
- </Menu>
- </Paper>
- );
-};
-
-export default BottomMenu;
diff --git a/modern/src/common/components/DriverValue.js b/modern/src/common/components/DriverValue.js
deleted file mode 100644
index 6148b418..00000000
--- a/modern/src/common/components/DriverValue.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import { useSelector } from 'react-redux';
-
-const DriverValue = ({ driverUniqueId }) => {
- const driver = useSelector((state) => state.drivers.items[driverUniqueId]);
-
- return driver?.name || driverUniqueId;
-};
-
-export default DriverValue;
diff --git a/modern/src/common/components/ErrorHandler.jsx b/modern/src/common/components/ErrorHandler.jsx
deleted file mode 100644
index 5c9c26d9..00000000
--- a/modern/src/common/components/ErrorHandler.jsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Snackbar, Alert } from '@mui/material';
-import React from 'react';
-import { useDispatch, useSelector } from 'react-redux';
-import { usePrevious } from '../../reactHelper';
-import { errorsActions } from '../../store';
-
-const ErrorHandler = () => {
- const dispatch = useDispatch();
-
- const error = useSelector((state) => state.errors.errors.find(() => true));
- const previousError = usePrevious(error);
-
- return (
- <Snackbar open={!!error}>
- <Alert
- elevation={6}
- onClose={() => dispatch(errorsActions.pop())}
- severity="error"
- variant="filled"
- >
- {error || previousError}
- </Alert>
- </Snackbar>
- );
-};
-
-export default ErrorHandler;
diff --git a/modern/src/common/components/GeofencesValue.js b/modern/src/common/components/GeofencesValue.js
deleted file mode 100644
index 4808a8a2..00000000
--- a/modern/src/common/components/GeofencesValue.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import { useSelector } from 'react-redux';
-
-const GeofencesValue = ({ geofenceIds }) => {
- const geofences = useSelector((state) => state.geofences.items);
-
- return geofenceIds.map((id) => geofences[id]?.name).join(', ');
-};
-
-export default GeofencesValue;
diff --git a/modern/src/common/components/LinkField.jsx b/modern/src/common/components/LinkField.jsx
deleted file mode 100644
index 08c6213a..00000000
--- a/modern/src/common/components/LinkField.jsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import { Autocomplete, TextField } from '@mui/material';
-import React, { useState } from 'react';
-import { useEffectAsync } from '../../reactHelper';
-
-const LinkField = ({
- label,
- endpointAll,
- endpointLinked,
- baseId,
- keyBase,
- keyLink,
- keyGetter = (item) => item.id,
- titleGetter = (item) => item.name,
-}) => {
- const [active, setActive] = useState(false);
- const [open, setOpen] = useState(false);
- const [items, setItems] = useState();
- const [linked, setLinked] = useState();
-
- useEffectAsync(async () => {
- if (active) {
- const response = await fetch(endpointAll);
- if (response.ok) {
- setItems(await response.json());
- } else {
- throw Error(await response.text());
- }
- }
- }, [active]);
-
- useEffectAsync(async () => {
- if (active) {
- const response = await fetch(endpointLinked);
- if (response.ok) {
- setLinked(await response.json());
- } else {
- throw Error(await response.text());
- }
- }
- }, [active]);
-
- const createBody = (linkId) => {
- const body = {};
- body[keyBase] = baseId;
- body[keyLink] = linkId;
- return body;
- };
-
- const onChange = async (value) => {
- const oldValue = linked.map((it) => keyGetter(it));
- const newValue = value.map((it) => keyGetter(it));
- if (!newValue.find((it) => it < 0)) {
- const results = [];
- newValue.filter((it) => !oldValue.includes(it)).forEach((added) => {
- results.push(fetch('/api/permissions', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(createBody(added)),
- }));
- });
- oldValue.filter((it) => !newValue.includes(it)).forEach((removed) => {
- results.push(fetch('/api/permissions', {
- method: 'DELETE',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(createBody(removed)),
- }));
- });
- await Promise.all(results);
- setLinked(value);
- }
- };
-
- return (
- <Autocomplete
- loading={active && !items}
- isOptionEqualToValue={(i1, i2) => keyGetter(i1) === keyGetter(i2)}
- options={items || []}
- getOptionLabel={(item) => titleGetter(item)}
- renderInput={(params) => <TextField {...params} label={label} />}
- value={(items && linked) || []}
- onChange={(_, value) => onChange(value)}
- open={open}
- onOpen={() => {
- setOpen(true);
- setActive(true);
- }}
- onClose={() => setOpen(false)}
- multiple
- />
- );
-};
-
-export default LinkField;
diff --git a/modern/src/common/components/LocalizationProvider.jsx b/modern/src/common/components/LocalizationProvider.jsx
deleted file mode 100644
index 4104c773..00000000
--- a/modern/src/common/components/LocalizationProvider.jsx
+++ /dev/null
@@ -1,187 +0,0 @@
-/* eslint-disable import/no-relative-packages */
-import React, {
- createContext, useContext, useEffect, useMemo,
-} from 'react';
-import dayjs from 'dayjs';
-import usePersistedState from '../util/usePersistedState';
-
-import af from '../../resources/l10n/af.json'; import 'dayjs/locale/af';
-import ar from '../../resources/l10n/ar.json'; import 'dayjs/locale/ar';
-import az from '../../resources/l10n/az.json'; import 'dayjs/locale/az';
-import bg from '../../resources/l10n/bg.json'; import 'dayjs/locale/bg';
-import bn from '../../resources/l10n/bn.json'; import 'dayjs/locale/bn';
-import ca from '../../resources/l10n/ca.json'; import 'dayjs/locale/ca';
-import cs from '../../resources/l10n/cs.json'; import 'dayjs/locale/cs';
-import da from '../../resources/l10n/da.json'; import 'dayjs/locale/da';
-import de from '../../resources/l10n/de.json'; import 'dayjs/locale/de';
-import el from '../../resources/l10n/el.json'; import 'dayjs/locale/el';
-import en from '../../resources/l10n/en.json'; import 'dayjs/locale/en';
-import es from '../../resources/l10n/es.json'; import 'dayjs/locale/es';
-import fa from '../../resources/l10n/fa.json'; import 'dayjs/locale/fa';
-import fi from '../../resources/l10n/fi.json'; import 'dayjs/locale/fi';
-import fr from '../../resources/l10n/fr.json'; import 'dayjs/locale/fr';
-import gl from '../../resources/l10n/gl.json'; import 'dayjs/locale/gl';
-import he from '../../resources/l10n/he.json'; import 'dayjs/locale/he';
-import hi from '../../resources/l10n/hi.json'; import 'dayjs/locale/hi';
-import hr from '../../resources/l10n/hr.json'; import 'dayjs/locale/hr';
-import hu from '../../resources/l10n/hu.json'; import 'dayjs/locale/hu';
-import id from '../../resources/l10n/id.json'; import 'dayjs/locale/id';
-import it from '../../resources/l10n/it.json'; import 'dayjs/locale/it';
-import ja from '../../resources/l10n/ja.json'; import 'dayjs/locale/ja';
-import ka from '../../resources/l10n/ka.json'; import 'dayjs/locale/ka';
-import kk from '../../resources/l10n/kk.json'; import 'dayjs/locale/kk';
-import km from '../../resources/l10n/km.json'; import 'dayjs/locale/km';
-import ko from '../../resources/l10n/ko.json'; import 'dayjs/locale/ko';
-import lo from '../../resources/l10n/lo.json'; import 'dayjs/locale/lo';
-import lt from '../../resources/l10n/lt.json'; import 'dayjs/locale/lt';
-import lv from '../../resources/l10n/lv.json'; import 'dayjs/locale/lv';
-import mk from '../../resources/l10n/mk.json'; import 'dayjs/locale/mk';
-import ml from '../../resources/l10n/ml.json'; import 'dayjs/locale/ml';
-import mn from '../../resources/l10n/mn.json'; import 'dayjs/locale/mn';
-import ms from '../../resources/l10n/ms.json'; import 'dayjs/locale/ms';
-import nb from '../../resources/l10n/nb.json'; import 'dayjs/locale/nb';
-import ne from '../../resources/l10n/ne.json'; import 'dayjs/locale/ne';
-import nl from '../../resources/l10n/nl.json'; import 'dayjs/locale/nl';
-import nn from '../../resources/l10n/nn.json'; import 'dayjs/locale/nn';
-import pl from '../../resources/l10n/pl.json'; import 'dayjs/locale/pl';
-import pt from '../../resources/l10n/pt.json'; import 'dayjs/locale/pt';
-import ptBR from '../../resources/l10n/pt_BR.json'; import 'dayjs/locale/pt-br';
-import ro from '../../resources/l10n/ro.json'; import 'dayjs/locale/ro';
-import ru from '../../resources/l10n/ru.json'; import 'dayjs/locale/ru';
-import si from '../../resources/l10n/si.json'; import 'dayjs/locale/si';
-import sk from '../../resources/l10n/sk.json'; import 'dayjs/locale/sk';
-import sl from '../../resources/l10n/sl.json'; import 'dayjs/locale/sl';
-import sq from '../../resources/l10n/sq.json'; import 'dayjs/locale/sq';
-import sr from '../../resources/l10n/sr.json'; import 'dayjs/locale/sr';
-import sv from '../../resources/l10n/sv.json'; import 'dayjs/locale/sv';
-import ta from '../../resources/l10n/ta.json'; import 'dayjs/locale/ta';
-import th from '../../resources/l10n/th.json'; import 'dayjs/locale/th';
-import tr from '../../resources/l10n/tr.json'; import 'dayjs/locale/tr';
-import uk from '../../resources/l10n/uk.json'; import 'dayjs/locale/uk';
-import uz from '../../resources/l10n/uz.json'; import 'dayjs/locale/uz';
-import vi from '../../resources/l10n/vi.json'; import 'dayjs/locale/vi';
-import zh from '../../resources/l10n/zh.json'; import 'dayjs/locale/zh';
-import zhTW from '../../resources/l10n/zh_TW.json'; import 'dayjs/locale/zh-tw';
-
-const languages = {
- af: { data: af, country: 'ZA', name: 'Afrikaans' },
- ar: { data: ar, country: 'AE', name: 'العربية' },
- az: { data: az, country: 'AZ', name: 'Azərbaycanca' },
- bg: { data: bg, country: 'BG', name: 'Български' },
- bn: { data: bn, country: 'IN', name: 'বাংলা' },
- ca: { data: ca, country: 'ES', name: 'Català' },
- cs: { data: cs, country: 'CZ', name: 'Čeština' },
- de: { data: de, country: 'DE', name: 'Deutsch' },
- da: { data: da, country: 'DK', name: 'Dansk' },
- el: { data: el, country: 'GR', name: 'Ελληνικά' },
- en: { data: en, country: 'US', name: 'English' },
- es: { data: es, country: 'ES', name: 'Español' },
- fa: { data: fa, country: 'IR', name: 'فارسی' },
- fi: { data: fi, country: 'FI', name: 'Suomi' },
- fr: { data: fr, country: 'FR', name: 'Français' },
- gl: { data: gl, country: 'ES', name: 'Galego' },
- he: { data: he, country: 'IL', name: 'עברית' },
- hi: { data: hi, country: 'IN', name: 'हिन्दी' },
- hr: { data: hr, country: 'HR', name: 'Hrvatski' },
- hu: { data: hu, country: 'HU', name: 'Magyar' },
- id: { data: id, country: 'ID', name: 'Bahasa Indonesia' },
- it: { data: it, country: 'IT', name: 'Italiano' },
- ja: { data: ja, country: 'JP', name: '日本語' },
- ka: { data: ka, country: 'GE', name: 'ქართული' },
- kk: { data: kk, country: 'KZ', name: 'Қазақша' },
- ko: { data: ko, country: 'KR', name: '한국어' },
- km: { data: km, country: 'KH', name: 'ភាសាខ្មែរ' },
- lo: { data: lo, country: 'LA', name: 'ລາວ' },
- lt: { data: lt, country: 'LT', name: 'Lietuvių' },
- lv: { data: lv, country: 'LV', name: 'Latviešu' },
- mk: { data: mk, country: 'MK', name: 'Mакедонски' },
- ml: { data: ml, country: 'IN', name: 'മലയാളം' },
- mn: { data: mn, country: 'MN', name: 'Монгол хэл' },
- ms: { data: ms, country: 'MY', name: 'بهاس ملايو' },
- nb: { data: nb, country: 'NO', name: 'Norsk bokmål' },
- ne: { data: ne, country: 'NP', name: 'नेपाली' },
- nl: { data: nl, country: 'NL', name: 'Nederlands' },
- nn: { data: nn, country: 'NO', name: 'Norsk nynorsk' },
- pl: { data: pl, country: 'PL', name: 'Polski' },
- pt: { data: pt, country: 'PT', name: 'Português' },
- ptBR: { data: ptBR, country: 'BR', name: 'Português (Brasil)' },
- ro: { data: ro, country: 'RO', name: 'Română' },
- ru: { data: ru, country: 'RU', name: 'Русский' },
- si: { data: si, country: 'LK', name: 'සිංහල' },
- sk: { data: sk, country: 'SK', name: 'Slovenčina' },
- sl: { data: sl, country: 'SI', name: 'Slovenščina' },
- sq: { data: sq, country: 'AL', name: 'Shqipëria' },
- sr: { data: sr, country: 'RS', name: 'Srpski' },
- sv: { data: sv, country: 'SE', name: 'Svenska' },
- ta: { data: ta, country: 'IN', name: 'தமிழ்' },
- th: { data: th, country: 'TH', name: 'ไทย' },
- tr: { data: tr, country: 'TR', name: 'Türkçe' },
- uk: { data: uk, country: 'UA', name: 'Українська' },
- uz: { data: uz, country: 'UZ', name: 'Oʻzbekcha' },
- vi: { data: vi, country: 'VN', name: 'Tiếng Việt' },
- zh: { data: zh, country: 'CN', name: '中文' },
- zhTW: { data: zhTW, country: 'TW', name: '中文 (Taiwan)' },
-};
-
-const getDefaultLanguage = () => {
- const browserLanguages = window.navigator.languages ? window.navigator.languages.slice() : [];
- const browserLanguage = window.navigator.userLanguage || window.navigator.language;
- browserLanguages.push(browserLanguage);
- browserLanguages.push(browserLanguage.substring(0, 2));
-
- for (let i = 0; i < browserLanguages.length; i += 1) {
- let language = browserLanguages[i].replace('-', '');
- if (language in languages) {
- return language;
- }
- if (language.length > 2) {
- language = language.substring(0, 2);
- if (language in languages) {
- return language;
- }
- }
- }
- return 'en';
-};
-
-const LocalizationContext = createContext({
- languages,
- language: 'en',
- setLanguage: () => {},
-});
-
-export const LocalizationProvider = ({ children }) => {
- const [language, setLanguage] = usePersistedState('language', getDefaultLanguage());
-
- const value = useMemo(() => ({ languages, language, setLanguage }), [languages, language, setLanguage]);
-
- useEffect(() => {
- let selected;
- if (language.length > 2) {
- selected = `${language.slice(0, 2)}-${language.slice(-2).toLowerCase()}`;
- } else {
- selected = language;
- }
- dayjs.locale(selected);
- }, [language]);
-
- return (
- <LocalizationContext.Provider value={value}>
- {children}
- </LocalizationContext.Provider>
- );
-};
-
-export const useLocalization = () => useContext(LocalizationContext);
-
-export const useTranslation = () => {
- const context = useContext(LocalizationContext);
- const { data } = context.languages[context.language];
- return useMemo(() => (key) => data[key], [data]);
-};
-
-export const useTranslationKeys = (predicate) => {
- const context = useContext(LocalizationContext);
- const { data } = context.languages[context.language];
- return Object.keys(data).filter(predicate);
-};
diff --git a/modern/src/common/components/NativeInterface.js b/modern/src/common/components/NativeInterface.js
deleted file mode 100644
index b088de0e..00000000
--- a/modern/src/common/components/NativeInterface.js
+++ /dev/null
@@ -1,72 +0,0 @@
-import { useEffect, useState } from 'react';
-import { useDispatch, useSelector } from 'react-redux';
-import { useEffectAsync } from '../../reactHelper';
-import { sessionActions } from '../../store';
-
-export const nativeEnvironment = window.appInterface || (window.webkit && window.webkit.messageHandlers.appInterface);
-
-export const nativePostMessage = (message) => {
- if (window.webkit && window.webkit.messageHandlers.appInterface) {
- window.webkit.messageHandlers.appInterface.postMessage(message);
- }
- if (window.appInterface) {
- window.appInterface.postMessage(message);
- }
-};
-
-export const handleLoginTokenListeners = new Set();
-window.handleLoginToken = (token) => {
- handleLoginTokenListeners.forEach((listener) => listener(token));
-};
-
-const updateNotificationTokenListeners = new Set();
-window.updateNotificationToken = (token) => {
- updateNotificationTokenListeners.forEach((listener) => listener(token));
-};
-
-const NativeInterface = () => {
- const dispatch = useDispatch();
-
- const user = useSelector((state) => state.session.user);
- const [notificationToken, setNotificationToken] = useState(null);
-
- useEffect(() => {
- const listener = (token) => setNotificationToken(token);
- updateNotificationTokenListeners.add(listener);
- return () => updateNotificationTokenListeners.delete(listener);
- }, [setNotificationToken]);
-
- useEffectAsync(async () => {
- if (user && !user.readonly && notificationToken) {
- window.localStorage.setItem('notificationToken', notificationToken);
- setNotificationToken(null);
-
- const tokens = user.attributes.notificationTokens?.split(',') || [];
- if (!tokens.includes(notificationToken)) {
- const updatedUser = {
- ...user,
- attributes: {
- ...user.attributes,
- notificationTokens: [...tokens.slice(-2), notificationToken].join(','),
- },
- };
-
- const response = await fetch(`/api/users/${user.id}`, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(updatedUser),
- });
-
- if (response.ok) {
- dispatch(sessionActions.updateUser(await response.json()));
- } else {
- throw Error(await response.text());
- }
- }
- }
- }, [user, notificationToken, setNotificationToken]);
-
- return null;
-};
-
-export default NativeInterface;
diff --git a/modern/src/common/components/NavBar.jsx b/modern/src/common/components/NavBar.jsx
deleted file mode 100644
index a53960fd..00000000
--- a/modern/src/common/components/NavBar.jsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import React from 'react';
-import {
- AppBar, Toolbar, Typography, IconButton,
-} from '@mui/material';
-import MenuIcon from '@mui/icons-material/Menu';
-
-const Navbar = ({ setOpenDrawer, title }) => (
- <AppBar position="sticky" color="inherit">
- <Toolbar>
- <IconButton
- color="inherit"
- edge="start"
- sx={{ mr: 2 }}
- onClick={() => setOpenDrawer(true)}
- >
- <MenuIcon />
- </IconButton>
- <Typography variant="h6" noWrap>
- {title}
- </Typography>
- </Toolbar>
- </AppBar>
-);
-
-export default Navbar;
diff --git a/modern/src/common/components/PageLayout.jsx b/modern/src/common/components/PageLayout.jsx
deleted file mode 100644
index e81c9754..00000000
--- a/modern/src/common/components/PageLayout.jsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import React, { useState } from 'react';
-import {
- AppBar,
- Breadcrumbs,
- Divider,
- Drawer,
- IconButton,
- Toolbar,
- Typography,
- useMediaQuery,
- useTheme,
-} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
-import ArrowBackIcon from '@mui/icons-material/ArrowBack';
-import MenuIcon from '@mui/icons-material/Menu';
-import { useNavigate } from 'react-router-dom';
-import { useTranslation } from './LocalizationProvider';
-
-const useStyles = makeStyles((theme) => ({
- desktopRoot: {
- height: '100%',
- display: 'flex',
- },
- mobileRoot: {
- height: '100%',
- display: 'flex',
- flexDirection: 'column',
- },
- desktopDrawer: {
- width: theme.dimensions.drawerWidthDesktop,
- },
- mobileDrawer: {
- width: theme.dimensions.drawerWidthTablet,
- },
- mobileToolbar: {
- zIndex: 1,
- },
- content: {
- flexGrow: 1,
- alignItems: 'stretch',
- display: 'flex',
- flexDirection: 'column',
- overflowY: 'auto',
- },
-}));
-
-const PageTitle = ({ breadcrumbs }) => {
- const theme = useTheme();
- const t = useTranslation();
-
- const desktop = useMediaQuery(theme.breakpoints.up('md'));
-
- if (desktop) {
- return (
- <Typography variant="h6" noWrap>{t(breadcrumbs[0])}</Typography>
- );
- }
- return (
- <Breadcrumbs>
- {breadcrumbs.slice(0, -1).map((breadcrumb) => (
- <Typography variant="h6" color="inherit" key={breadcrumb}>{t(breadcrumb)}</Typography>
- ))}
- <Typography variant="h6" color="textPrimary">{t(breadcrumbs[breadcrumbs.length - 1])}</Typography>
- </Breadcrumbs>
- );
-};
-
-const PageLayout = ({ menu, breadcrumbs, children }) => {
- const classes = useStyles();
- const theme = useTheme();
- const navigate = useNavigate();
-
- const desktop = useMediaQuery(theme.breakpoints.up('md'));
-
- const [openDrawer, setOpenDrawer] = useState(false);
-
- return desktop ? (
- <div className={classes.desktopRoot}>
- <Drawer
- variant="permanent"
- className={classes.desktopDrawer}
- classes={{ paper: classes.desktopDrawer }}
- >
- <Toolbar>
- <IconButton color="inherit" edge="start" sx={{ mr: 2 }} onClick={() => navigate('/')}>
- <ArrowBackIcon />
- </IconButton>
- <PageTitle breadcrumbs={breadcrumbs} />
- </Toolbar>
- <Divider />
- {menu}
- </Drawer>
- <div className={classes.content}>{children}</div>
- </div>
- ) : (
- <div className={classes.mobileRoot}>
- <Drawer
- variant="temporary"
- open={openDrawer}
- onClose={() => setOpenDrawer(false)}
- classes={{ paper: classes.mobileDrawer }}
- >
- {menu}
- </Drawer>
- <AppBar className={classes.mobileToolbar} position="static" color="inherit">
- <Toolbar>
- <IconButton color="inherit" edge="start" sx={{ mr: 2 }} onClick={() => setOpenDrawer(true)}>
- <MenuIcon />
- </IconButton>
- <PageTitle breadcrumbs={breadcrumbs} />
- </Toolbar>
- </AppBar>
- <div className={classes.content}>{children}</div>
- </div>
- );
-};
-
-export default PageLayout;
diff --git a/modern/src/common/components/PositionValue.jsx b/modern/src/common/components/PositionValue.jsx
deleted file mode 100644
index b1f8f656..00000000
--- a/modern/src/common/components/PositionValue.jsx
+++ /dev/null
@@ -1,133 +0,0 @@
-import React from 'react';
-import { useSelector } from 'react-redux';
-import { Link } from '@mui/material';
-import { Link as RouterLink } from 'react-router-dom';
-import {
- formatAlarm,
- formatAltitude,
- formatBoolean,
- formatCoordinate,
- formatCourse,
- formatDistance,
- formatNumber,
- formatNumericHours,
- formatPercentage,
- formatSpeed,
- formatTime,
- formatTemperature,
- formatVoltage,
- formatVolume,
- formatConsumption,
-} from '../util/formatter';
-import { speedToKnots } from '../util/converter';
-import { useAttributePreference, usePreference } from '../util/preferences';
-import { useTranslation } from './LocalizationProvider';
-import { useAdministrator } from '../util/permissions';
-import AddressValue from './AddressValue';
-import GeofencesValue from './GeofencesValue';
-import DriverValue from './DriverValue';
-
-const PositionValue = ({ position, property, attribute }) => {
- const t = useTranslation();
-
- const admin = useAdministrator();
-
- const device = useSelector((state) => state.devices.items[position.deviceId]);
-
- const key = property || attribute;
- const value = property ? position[property] : position.attributes[attribute];
-
- const distanceUnit = useAttributePreference('distanceUnit');
- const altitudeUnit = useAttributePreference('altitudeUnit');
- 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);
- case 'latitude':
- return formatCoordinate('latitude', value, coordinateFormat);
- case 'longitude':
- return formatCoordinate('longitude', value, coordinateFormat);
- case 'speed':
- return value != null ? formatSpeed(value, speedUnit, t) : '';
- case 'obdSpeed':
- return value != null ? formatSpeed(speedToKnots(value, 'kmh'), speedUnit, t) : '';
- case 'course':
- return formatCourse(value);
- case 'altitude':
- return formatAltitude(value, altitudeUnit, t);
- case 'power':
- case 'battery':
- return formatVoltage(value, t);
- case 'batteryLevel':
- return value != null ? formatPercentage(value, t) : '';
- case 'volume':
- return value != null ? formatVolume(value, volumeUnit, t) : '';
- case 'fuelConsumption':
- return value != null ? formatConsumption(value, t) : '';
- case 'coolantTemp':
- return formatTemperature(value);
- case 'alarm':
- return formatAlarm(value, t);
- case 'odometer':
- case 'serviceOdometer':
- case 'tripOdometer':
- case 'obdOdometer':
- case 'distance':
- case 'totalDistance':
- return value != null ? formatDistance(value, distanceUnit, t) : '';
- case 'hours':
- return value != null ? formatNumericHours(value, t) : '';
- default:
- if (typeof value === 'number') {
- return formatNumber(value);
- } if (typeof value === 'boolean') {
- return formatBoolean(value, t);
- }
- return value || '';
- }
- };
-
- switch (key) {
- case 'image':
- case 'video':
- case 'audio':
- return <Link href={`/api/media/${device.uniqueId}/${value}`} target="_blank">{value}</Link>;
- case 'totalDistance':
- case 'hours':
- return (
- <>
- {formatValue(value)}
- &nbsp;&nbsp;
- {admin && <Link component={RouterLink} underline="none" to={`/settings/accumulators/${position.deviceId}`}>&#9881;</Link>}
- </>
- );
- case 'address':
- return <AddressValue latitude={position.latitude} longitude={position.longitude} originalAddress={value} />;
- case 'network':
- if (value) {
- return <Link component={RouterLink} underline="none" to={`/network/${position.id}`}>{t('sharedInfoTitle')}</Link>;
- }
- return '';
- case 'geofenceIds':
- if (value) {
- return <GeofencesValue geofenceIds={value} />;
- }
- return '';
- case 'driverUniqueId':
- if (value) {
- return <DriverValue driverUniqueId={value} />;
- }
- return '';
- default:
- return formatValue(value);
- }
-};
-
-export default PositionValue;
diff --git a/modern/src/common/components/RemoveDialog.jsx b/modern/src/common/components/RemoveDialog.jsx
deleted file mode 100644
index 0f4254a8..00000000
--- a/modern/src/common/components/RemoveDialog.jsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import React from 'react';
-import Button from '@mui/material/Button';
-import { Snackbar } from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
-import { useTranslation } from './LocalizationProvider';
-import { useCatch } from '../../reactHelper';
-import { snackBarDurationLongMs } from '../util/duration';
-
-const useStyles = makeStyles((theme) => ({
- root: {
- [theme.breakpoints.down('md')]: {
- bottom: `calc(${theme.dimensions.bottomBarHeight}px + ${theme.spacing(1)})`,
- },
- },
- button: {
- height: 'auto',
- marginTop: 0,
- marginBottom: 0,
- color: theme.palette.error.main,
- },
-}));
-
-const RemoveDialog = ({
- open, endpoint, itemId, onResult,
-}) => {
- const classes = useStyles();
- const t = useTranslation();
-
- const handleRemove = useCatch(async () => {
- const response = await fetch(`/api/${endpoint}/${itemId}`, { method: 'DELETE' });
- if (response.ok) {
- onResult(true);
- } else {
- throw Error(await response.text());
- }
- });
-
- return (
- <Snackbar
- className={classes.root}
- open={open}
- autoHideDuration={snackBarDurationLongMs}
- onClose={() => onResult(false)}
- message={t('sharedRemoveConfirm')}
- action={(
- <Button size="small" className={classes.button} onClick={handleRemove}>
- {t('sharedRemove')}
- </Button>
- )}
- />
- );
-};
-
-export default RemoveDialog;
diff --git a/modern/src/common/components/SelectField.jsx b/modern/src/common/components/SelectField.jsx
deleted file mode 100644
index db8c30b0..00000000
--- a/modern/src/common/components/SelectField.jsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import {
- FormControl, InputLabel, MenuItem, Select, Autocomplete, TextField,
-} from '@mui/material';
-import React, { useState } from 'react';
-import { useEffectAsync } from '../../reactHelper';
-
-const SelectField = ({
- label,
- fullWidth,
- multiple,
- value = null,
- emptyValue = null,
- emptyTitle = '',
- onChange,
- endpoint,
- data,
- keyGetter = (item) => item.id,
- titleGetter = (item) => item.name,
-}) => {
- const [items, setItems] = useState(data);
-
- const getOptionLabel = (option) => {
- if (typeof option !== 'object') {
- option = items.find((obj) => keyGetter(obj) === option);
- }
- return option ? titleGetter(option) : emptyTitle;
- };
-
- useEffectAsync(async () => {
- if (endpoint) {
- const response = await fetch(endpoint);
- if (response.ok) {
- setItems(await response.json());
- } else {
- throw Error(await response.text());
- }
- }
- }, []);
-
- if (items) {
- return (
- <FormControl fullWidth={fullWidth}>
- {multiple ? (
- <>
- <InputLabel>{label}</InputLabel>
- <Select
- label={label}
- multiple
- value={value}
- onChange={onChange}
- >
- {items.map((item) => (
- <MenuItem key={keyGetter(item)} value={keyGetter(item)}>{titleGetter(item)}</MenuItem>
- ))}
- </Select>
- </>
- ) : (
- <Autocomplete
- size="small"
- options={items}
- getOptionLabel={getOptionLabel}
- renderOption={(props, option) => (
- <MenuItem {...props} key={keyGetter(option)} value={keyGetter(option)}>{titleGetter(option)}</MenuItem>
- )}
- isOptionEqualToValue={(option, value) => keyGetter(option) === value}
- value={value}
- onChange={(_, value) => onChange({ target: { value: value ? keyGetter(value) : emptyValue } })}
- renderInput={(params) => <TextField {...params} label={label} />}
- />
- )}
- </FormControl>
- );
- }
- return null;
-};
-
-export default SelectField;
diff --git a/modern/src/common/components/SideNav.jsx b/modern/src/common/components/SideNav.jsx
deleted file mode 100644
index 97968bd1..00000000
--- a/modern/src/common/components/SideNav.jsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import React, { Fragment } from 'react';
-import {
- List, ListItemText, ListItemIcon, Divider, ListSubheader, ListItemButton,
-} from '@mui/material';
-import { Link, useLocation } from 'react-router-dom';
-
-const SideNav = ({ routes }) => {
- const location = useLocation();
-
- return (
- <List disablePadding style={{ paddingTop: '16px' }}>
- {routes.map((route) => (route.subheader ? (
- <Fragment key={route.subheader}>
- <Divider />
- <ListSubheader>{route.subheader}</ListSubheader>
- </Fragment>
- ) : (
- <ListItemButton
- disableRipple
- component={Link}
- key={route.href}
- to={route.href}
- selected={location.pathname.match(route.match || route.href) !== null}
- >
- <ListItemIcon>{route.icon}</ListItemIcon>
- <ListItemText primary={route.name} />
- </ListItemButton>
- )))}
- </List>
- );
-};
-
-export default SideNav;
diff --git a/modern/src/common/components/SplitButton.jsx b/modern/src/common/components/SplitButton.jsx
deleted file mode 100644
index 84876f15..00000000
--- a/modern/src/common/components/SplitButton.jsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import React, { useRef, useState } from 'react';
-import {
- Button, ButtonGroup, Menu, MenuItem, Typography,
-} from '@mui/material';
-import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
-
-const SplitButton = ({
- fullWidth, variant, color, disabled, onClick, options, selected, setSelected,
-}) => {
- const anchorRef = useRef();
- const [menuAnchorEl, setMenuAnchorEl] = useState(null);
-
- return (
- <>
- <ButtonGroup fullWidth={fullWidth} variant={variant} color={color} ref={anchorRef}>
- <Button disabled={disabled} onClick={() => onClick(selected)}>
- <Typography variant="button" noWrap>{options[selected]}</Typography>
- </Button>
- <Button fullWidth={false} size="small" onClick={() => setMenuAnchorEl(anchorRef.current)}>
- <ArrowDropDownIcon />
- </Button>
- </ButtonGroup>
- <Menu
- open={!!menuAnchorEl}
- anchorEl={menuAnchorEl}
- onClose={() => setMenuAnchorEl(null)}
- anchorOrigin={{
- vertical: 'bottom',
- horizontal: 'right',
- }}
- >
- {Object.entries(options).map(([key, value]) => (
- <MenuItem
- key={key}
- onClick={() => {
- setSelected(key);
- setMenuAnchorEl(null);
- }}
- >
- {value}
- </MenuItem>
- ))}
- </Menu>
- </>
- );
-};
-
-export default SplitButton;
diff --git a/modern/src/common/components/StatusCard.jsx b/modern/src/common/components/StatusCard.jsx
deleted file mode 100644
index a63d0f80..00000000
--- a/modern/src/common/components/StatusCard.jsx
+++ /dev/null
@@ -1,288 +0,0 @@
-import React, { useState } from 'react';
-import { useDispatch, useSelector } from 'react-redux';
-import { useNavigate } from 'react-router-dom';
-import Draggable from 'react-draggable';
-import {
- Card,
- CardContent,
- Typography,
- CardActions,
- IconButton,
- Table,
- TableBody,
- TableRow,
- TableCell,
- Menu,
- MenuItem,
- CardMedia,
-} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
-import CloseIcon from '@mui/icons-material/Close';
-import ReplayIcon from '@mui/icons-material/Replay';
-import PublishIcon from '@mui/icons-material/Publish';
-import EditIcon from '@mui/icons-material/Edit';
-import DeleteIcon from '@mui/icons-material/Delete';
-import PendingIcon from '@mui/icons-material/Pending';
-
-import { useTranslation } from './LocalizationProvider';
-import RemoveDialog from './RemoveDialog';
-import PositionValue from './PositionValue';
-import { useDeviceReadonly } from '../util/permissions';
-import usePositionAttributes from '../attributes/usePositionAttributes';
-import { devicesActions } from '../../store';
-import { useCatch, useCatchCallback } from '../../reactHelper';
-import { useAttributePreference } from '../util/preferences';
-
-const useStyles = makeStyles((theme) => ({
- card: {
- pointerEvents: 'auto',
- width: theme.dimensions.popupMaxWidth,
- },
- media: {
- height: theme.dimensions.popupImageHeight,
- display: 'flex',
- justifyContent: 'flex-end',
- alignItems: 'flex-start',
- },
- mediaButton: {
- color: theme.palette.primary.contrastText,
- mixBlendMode: 'difference',
- },
- header: {
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
- padding: theme.spacing(1, 1, 0, 2),
- },
- content: {
- paddingTop: theme.spacing(1),
- paddingBottom: theme.spacing(1),
- maxHeight: theme.dimensions.cardContentMaxHeight,
- overflow: 'auto',
- },
- delete: {
- color: theme.palette.error.main,
- },
- icon: {
- width: '25px',
- height: '25px',
- filter: 'brightness(0) invert(1)',
- },
- table: {
- '& .MuiTableCell-sizeSmall': {
- paddingLeft: 0,
- paddingRight: 0,
- },
- },
- cell: {
- borderBottom: 'none',
- },
- actions: {
- justifyContent: 'space-between',
- },
- root: ({ desktopPadding }) => ({
- pointerEvents: 'none',
- position: 'fixed',
- zIndex: 5,
- left: '50%',
- [theme.breakpoints.up('md')]: {
- left: `calc(50% + ${desktopPadding} / 2)`,
- bottom: theme.spacing(3),
- },
- [theme.breakpoints.down('md')]: {
- left: '50%',
- bottom: `calc(${theme.spacing(3)} + ${theme.dimensions.bottomBarHeight}px)`,
- },
- transform: 'translateX(-50%)',
- }),
-}));
-
-const StatusRow = ({ name, content }) => {
- const classes = useStyles();
-
- return (
- <TableRow>
- <TableCell className={classes.cell}>
- <Typography variant="body2">{name}</Typography>
- </TableCell>
- <TableCell className={classes.cell}>
- <Typography variant="body2" color="textSecondary">{content}</Typography>
- </TableCell>
- </TableRow>
- );
-};
-
-const StatusCard = ({ deviceId, position, onClose, disableActions, desktopPadding = 0 }) => {
- const classes = useStyles({ desktopPadding });
- const navigate = useNavigate();
- const dispatch = useDispatch();
- const t = useTranslation();
-
- const deviceReadonly = useDeviceReadonly();
-
- const shareDisabled = useSelector((state) => state.session.server.attributes.disableShare);
- const user = useSelector((state) => state.session.user);
- const device = useSelector((state) => state.devices.items[deviceId]);
-
- const deviceImage = device?.attributes?.deviceImage;
-
- const positionAttributes = usePositionAttributes(t);
- const positionItems = useAttributePreference('positionItems', 'speed,address,totalDistance,course');
-
- const [anchorEl, setAnchorEl] = useState(null);
-
- const [removing, setRemoving] = useState(false);
-
- const handleRemove = useCatch(async (removed) => {
- if (removed) {
- const response = await fetch('/api/devices');
- if (response.ok) {
- dispatch(devicesActions.refresh(await response.json()));
- } else {
- throw Error(await response.text());
- }
- }
- setRemoving(false);
- });
-
- const handleGeofence = useCatchCallback(async () => {
- const newItem = {
- name: t('sharedGeofence'),
- area: `CIRCLE (${position.latitude} ${position.longitude}, 50)`,
- };
- const response = await fetch('/api/geofences', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(newItem),
- });
- if (response.ok) {
- const item = await response.json();
- const permissionResponse = await fetch('/api/permissions', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ deviceId: position.deviceId, geofenceId: item.id }),
- });
- if (!permissionResponse.ok) {
- throw Error(await permissionResponse.text());
- }
- navigate(`/settings/geofence/${item.id}`);
- } else {
- throw Error(await response.text());
- }
- }, [navigate, position]);
-
- return (
- <>
- <div className={classes.root}>
- {device && (
- <Draggable
- handle={`.${classes.media}, .${classes.header}`}
- >
- <Card elevation={3} className={classes.card}>
- {deviceImage ? (
- <CardMedia
- className={classes.media}
- image={`/api/media/${device.uniqueId}/${deviceImage}`}
- >
- <IconButton
- size="small"
- onClick={onClose}
- onTouchStart={onClose}
- >
- <CloseIcon fontSize="small" className={classes.mediaButton} />
- </IconButton>
- </CardMedia>
- ) : (
- <div className={classes.header}>
- <Typography variant="body2" color="textSecondary">
- {device.name}
- </Typography>
- <IconButton
- size="small"
- onClick={onClose}
- onTouchStart={onClose}
- >
- <CloseIcon fontSize="small" />
- </IconButton>
- </div>
- )}
- {position && (
- <CardContent className={classes.content}>
- <Table size="small" classes={{ root: classes.table }}>
- <TableBody>
- {positionItems.split(',').filter((key) => position.hasOwnProperty(key) || position.attributes.hasOwnProperty(key)).map((key) => (
- <StatusRow
- key={key}
- name={positionAttributes[key]?.name || key}
- content={(
- <PositionValue
- position={position}
- property={position.hasOwnProperty(key) ? key : null}
- attribute={position.hasOwnProperty(key) ? null : key}
- />
- )}
- />
- ))}
- </TableBody>
- </Table>
- </CardContent>
- )}
- <CardActions classes={{ root: classes.actions }} disableSpacing>
- <IconButton
- color="secondary"
- onClick={(e) => setAnchorEl(e.currentTarget)}
- disabled={!position}
- >
- <PendingIcon />
- </IconButton>
- <IconButton
- onClick={() => navigate('/replay')}
- disabled={disableActions || !position}
- >
- <ReplayIcon />
- </IconButton>
- <IconButton
- onClick={() => navigate(`/settings/device/${deviceId}/command`)}
- disabled={disableActions}
- >
- <PublishIcon />
- </IconButton>
- <IconButton
- onClick={() => navigate(`/settings/device/${deviceId}`)}
- disabled={disableActions || deviceReadonly}
- >
- <EditIcon />
- </IconButton>
- <IconButton
- onClick={() => setRemoving(true)}
- disabled={disableActions || deviceReadonly}
- className={classes.delete}
- >
- <DeleteIcon />
- </IconButton>
- </CardActions>
- </Card>
- </Draggable>
- )}
- </div>
- {position && (
- <Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={() => setAnchorEl(null)}>
- <MenuItem onClick={() => navigate(`/position/${position.id}`)}><Typography color="secondary">{t('sharedShowDetails')}</Typography></MenuItem>
- <MenuItem onClick={handleGeofence}>{t('sharedCreateGeofence')}</MenuItem>
- <MenuItem component="a" target="_blank" href={`https://www.google.com/maps/search/?api=1&query=${position.latitude}%2C${position.longitude}`}>{t('linkGoogleMaps')}</MenuItem>
- <MenuItem component="a" target="_blank" href={`http://maps.apple.com/?ll=${position.latitude},${position.longitude}`}>{t('linkAppleMaps')}</MenuItem>
- <MenuItem component="a" target="_blank" href={`https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=${position.latitude}%2C${position.longitude}&heading=${position.course}`}>{t('linkStreetView')}</MenuItem>
- {!shareDisabled && !user.temporary && <MenuItem onClick={() => navigate(`/settings/device/${deviceId}/share`)}>{t('deviceShare')}</MenuItem>}
- </Menu>
- )}
- <RemoveDialog
- open={removing}
- endpoint="devices"
- itemId={deviceId}
- onResult={(removed) => handleRemove(removed)}
- />
- </>
- );
-};
-
-export default StatusCard;
diff --git a/modern/src/common/components/TableShimmer.jsx b/modern/src/common/components/TableShimmer.jsx
deleted file mode 100644
index 08a984a4..00000000
--- a/modern/src/common/components/TableShimmer.jsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-import { Skeleton, TableCell, TableRow } from '@mui/material';
-
-const TableShimmer = ({ columns, startAction, endAction }) => [...Array(3)].map((_, i) => (
- <TableRow key={-i}>
- {[...Array(columns)].map((_, j) => {
- const action = (startAction && j === 0) || (endAction && j === columns - 1);
- return (
- <TableCell key={-j} padding={action ? 'none' : 'normal'}>
- {!action && <Skeleton />}
- </TableCell>
- );
- })}
- </TableRow>
-));
-
-export default TableShimmer;
diff --git a/modern/src/common/theme/components.js b/modern/src/common/theme/components.js
deleted file mode 100644
index 56a2ac75..00000000
--- a/modern/src/common/theme/components.js
+++ /dev/null
@@ -1,40 +0,0 @@
-export default {
- MuiUseMediaQuery: {
- defaultProps: {
- noSsr: true,
- },
- },
- MuiOutlinedInput: {
- styleOverrides: {
- root: ({ theme }) => ({
- backgroundColor: theme.palette.background.default,
- }),
- },
- },
- MuiButton: {
- styleOverrides: {
- sizeMedium: {
- height: '40px',
- },
- },
- },
- MuiFormControl: {
- defaultProps: {
- size: 'small',
- },
- },
- MuiSnackbar: {
- defaultProps: {
- anchorOrigin: {
- vertical: 'bottom',
- horizontal: 'center',
- },
- },
- },
- MuiTooltip: {
- defaultProps: {
- enterDelay: 500,
- enterNextDelay: 500,
- },
- },
-};
diff --git a/modern/src/common/theme/dimensions.js b/modern/src/common/theme/dimensions.js
deleted file mode 100644
index 4930803a..00000000
--- a/modern/src/common/theme/dimensions.js
+++ /dev/null
@@ -1,14 +0,0 @@
-export default {
- sidebarWidth: '28%',
- sidebarWidthTablet: '52px',
- drawerWidthDesktop: '360px',
- drawerWidthTablet: '320px',
- drawerHeightPhone: '250px',
- filterFormWidth: '160px',
- eventsDrawerWidth: '320px',
- bottomBarHeight: 56,
- popupMapOffset: 300,
- popupMaxWidth: 288,
- popupImageHeight: 144,
- cardContentMaxHeight: '40vh',
-};
diff --git a/modern/src/common/theme/index.js b/modern/src/common/theme/index.js
deleted file mode 100644
index e8ce698b..00000000
--- a/modern/src/common/theme/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import { useMemo } from 'react';
-import { createTheme } from '@mui/material/styles';
-import palette from './palette';
-import dimensions from './dimensions';
-import components from './components';
-
-export default (server, darkMode) => useMemo(() => createTheme({
- palette: palette(server, darkMode),
- dimensions,
- components,
-}), [server, darkMode]);
diff --git a/modern/src/common/theme/palette.js b/modern/src/common/theme/palette.js
deleted file mode 100644
index f32ed93e..00000000
--- a/modern/src/common/theme/palette.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { grey, green, indigo } from '@mui/material/colors';
-
-const validatedColor = (color) => (/^#([0-9A-Fa-f]{3}){1,2}$/.test(color) ? color : null);
-
-export default (server, darkMode) => ({
- mode: darkMode ? 'dark' : 'light',
- background: {
- default: darkMode ? grey[900] : grey[50],
- },
- primary: {
- main: validatedColor(server?.attributes?.colorPrimary) || (darkMode ? indigo[200] : indigo[900]),
- },
- secondary: {
- main: validatedColor(server?.attributes?.colorSecondary) || (darkMode ? green[200] : green[800]),
- },
- neutral: {
- main: grey[500],
- },
- geometry: {
- main: '#3bb2d0',
- },
-});
diff --git a/modern/src/common/util/converter.js b/modern/src/common/util/converter.js
deleted file mode 100644
index cb21566b..00000000
--- a/modern/src/common/util/converter.js
+++ /dev/null
@@ -1,107 +0,0 @@
-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/modern/src/common/util/deviceCategories.js b/modern/src/common/util/deviceCategories.js
deleted file mode 100644
index a991e505..00000000
--- a/modern/src/common/util/deviceCategories.js
+++ /dev/null
@@ -1,24 +0,0 @@
-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/modern/src/common/util/duration.js b/modern/src/common/util/duration.js
deleted file mode 100644
index aae74868..00000000
--- a/modern/src/common/util/duration.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export const snackBarDurationShortMs = 1500;
-export const snackBarDurationLongMs = 2750;
diff --git a/modern/src/common/util/formatter.js b/modern/src/common/util/formatter.js
deleted file mode 100644
index 7b7fc96d..00000000
--- a/modern/src/common/util/formatter.js
+++ /dev/null
@@ -1,143 +0,0 @@
-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/modern/src/common/util/permissions.js b/modern/src/common/util/permissions.js
deleted file mode 100644
index 8a63b5a1..00000000
--- a/modern/src/common/util/permissions.js
+++ /dev/null
@@ -1,28 +0,0 @@
-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/modern/src/common/util/preferences.js b/modern/src/common/util/preferences.js
deleted file mode 100644
index 229b6f17..00000000
--- a/modern/src/common/util/preferences.js
+++ /dev/null
@@ -1,41 +0,0 @@
-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/modern/src/common/util/stringUtils.js b/modern/src/common/util/stringUtils.js
deleted file mode 100644
index fc997fe0..00000000
--- a/modern/src/common/util/stringUtils.js
+++ /dev/null
@@ -1,3 +0,0 @@
-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/modern/src/common/util/useFeatures.js b/modern/src/common/util/useFeatures.js
deleted file mode 100644
index 30361589..00000000
--- a/modern/src/common/util/useFeatures.js
+++ /dev/null
@@ -1,44 +0,0 @@
-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/modern/src/common/util/usePersistedState.js b/modern/src/common/util/usePersistedState.js
deleted file mode 100644
index 70a652ad..00000000
--- a/modern/src/common/util/usePersistedState.js
+++ /dev/null
@@ -1,22 +0,0 @@
-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/modern/src/common/util/useQuery.js b/modern/src/common/util/useQuery.js
deleted file mode 100644
index f246df7c..00000000
--- a/modern/src/common/util/useQuery.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import { useMemo } from 'react';
-import { useLocation } from 'react-router-dom';
-
-export default () => {
- const { search } = useLocation();
- return useMemo(() => new URLSearchParams(search), [search]);
-};