Optimize WebUI rendering when using Dialog Boxes #1116

This commit is contained in:
Proddy
2023-04-28 12:46:59 +02:00
parent b9402d3a01
commit cfe8c410ae
59 changed files with 1446 additions and 1120 deletions

View File

@@ -52,7 +52,7 @@
"@typescript-eslint/no-implied-eval": "off",
"@typescript-eslint/no-misused-promises": "off",
"arrow-body-style": ["error", "as-needed"],
"react-hooks/exhaustive-deps": "error",
"react-hooks/exhaustive-deps": "warn",
"@typescript-eslint/consistent-type-imports": [
"error",
{

View File

@@ -20,20 +20,20 @@
"lint-fixall": "eslint . --cache --fix"
},
"dependencies": {
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@emotion/react": "^11.10.8",
"@emotion/styled": "^11.10.8",
"@msgpack/msgpack": "^3.0.0-beta2",
"@mui/icons-material": "^5.11.16",
"@mui/material": "^5.12.1",
"@mui/material": "^5.12.2",
"@remix-run/router": "^1.5.0",
"@table-library/react-table-library": "4.1.0",
"@table-library/react-table-library": "4.1.2",
"@types/lodash-es": "^4.17.7",
"@types/node": "^18.16.0",
"@types/react": "^18.0.38",
"@types/react-dom": "^18.0.11",
"@types/node": "^18.16.2",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.1",
"@types/react-router-dom": "^5.3.3",
"async-validator": "^4.2.5",
"axios": "^1.3.6",
"axios": "^1.4.0",
"history": "^5.3.0",
"jwt-decode": "^3.1.2",
"lodash-es": "^4.17.21",
@@ -67,7 +67,7 @@
"prettier": "^2.8.8",
"rollup-plugin-visualizer": "^5.9.0",
"terser": "^5.17.1",
"vite": "^4.3.1",
"vite": "^4.3.3",
"vite-plugin-svgr": "^2.4.0",
"vite-tsconfig-paths": "^4.2.0"
},

View File

@@ -1,6 +1,6 @@
import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning';
import { Button, Checkbox, MenuItem, Grid, Typography, InputAdornment } from '@mui/material';
import { Button, Checkbox, MenuItem, Grid, Typography, InputAdornment, TextField } from '@mui/material';
import { useState } from 'react';
import type { ValidateFieldsError } from 'async-validator';
import type { FC } from 'react';
@@ -94,7 +94,7 @@ const MqttSettingsForm: FC = () => {
/>
</Grid>
<Grid item xs={12} sm={6}>
<ValidatedTextField
<TextField
name="client_id"
label={LL.ID_OF(LL.CLIENT()) + ' (' + LL.OPTIONAL() + ')'}
fullWidth
@@ -105,7 +105,7 @@ const MqttSettingsForm: FC = () => {
/>
</Grid>
<Grid item xs={12} sm={6}>
<ValidatedTextField
<TextField
name="username"
label={LL.USERNAME(0)}
fullWidth
@@ -143,7 +143,7 @@ const MqttSettingsForm: FC = () => {
/>
</Grid>
<Grid item xs={12} sm={6}>
<ValidatedTextField
<TextField
name="mqtt_qos"
label="QoS"
value={data.mqtt_qos}
@@ -156,7 +156,7 @@ const MqttSettingsForm: FC = () => {
<MenuItem value={0}>0</MenuItem>
<MenuItem value={1}>1</MenuItem>
<MenuItem value={2}>2</MenuItem>
</ValidatedTextField>
</TextField>
</Grid>
</Grid>
<BlockFormControlLabel
@@ -171,7 +171,7 @@ const MqttSettingsForm: FC = () => {
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
{LL.FORMATTING()}
</Typography>
<ValidatedTextField
<TextField
name="nested_format"
label={LL.MQTT_FORMAT()}
value={data.nested_format}
@@ -183,7 +183,7 @@ const MqttSettingsForm: FC = () => {
>
<MenuItem value={1}>{LL.MQTT_NEST_1()}</MenuItem>
<MenuItem value={2}>{LL.MQTT_NEST_2()}</MenuItem>
</ValidatedTextField>
</TextField>
<BlockFormControlLabel
control={<Checkbox name="send_response" checked={data.send_response} onChange={updateFormValue} />}
label={LL.MQTT_RESPONSE()}
@@ -233,7 +233,7 @@ const MqttSettingsForm: FC = () => {
alignItems="flex-start"
>
<Grid item xs={12} sm={6} md={4}>
<ValidatedTextField
<TextField
name="discovery_type"
label={LL.MQTT_PUBLISH_TEXT_5()}
value={data.discovery_type}
@@ -245,10 +245,10 @@ const MqttSettingsForm: FC = () => {
>
<MenuItem value={0}>Home Assistant</MenuItem>
<MenuItem value={1}>Domoticz</MenuItem>
</ValidatedTextField>
</TextField>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<ValidatedTextField
<TextField
name="discovery_prefix"
label={LL.MQTT_PUBLISH_TEXT_4()}
fullWidth
@@ -259,7 +259,7 @@ const MqttSettingsForm: FC = () => {
/>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<ValidatedTextField
<TextField
name="entity_format"
label={LL.MQTT_ENTITY_FORMAT()}
value={data.entity_format}
@@ -272,7 +272,7 @@ const MqttSettingsForm: FC = () => {
<MenuItem value={0}>{LL.MQTT_ENTITY_FORMAT_0()}</MenuItem>
<MenuItem value={1}>{LL.MQTT_ENTITY_FORMAT_1()}</MenuItem>
<MenuItem value={2}>{LL.MQTT_ENTITY_FORMAT_2()}</MenuItem>
</ValidatedTextField>
</TextField>
</Grid>
</Grid>
)}
@@ -299,8 +299,7 @@ const MqttSettingsForm: FC = () => {
/>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
<TextField
name="publish_time_boiler"
label={LL.MQTT_INT_BOILER()}
InputProps={{
@@ -315,8 +314,7 @@ const MqttSettingsForm: FC = () => {
/>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
<TextField
name="publish_time_thermostat"
label={LL.MQTT_INT_THERMOSTATS()}
InputProps={{
@@ -331,8 +329,7 @@ const MqttSettingsForm: FC = () => {
/>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
<TextField
name="publish_time_solar"
label={LL.MQTT_INT_SOLAR()}
InputProps={{
@@ -347,8 +344,7 @@ const MqttSettingsForm: FC = () => {
/>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
<TextField
name="publish_time_mixer"
label={LL.MQTT_INT_MIXER()}
InputProps={{
@@ -363,8 +359,7 @@ const MqttSettingsForm: FC = () => {
/>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
<TextField
name="publish_time_sensor"
label={LL.TEMP_SENSORS()}
InputProps={{
@@ -379,8 +374,7 @@ const MqttSettingsForm: FC = () => {
/>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
<TextField
name="publish_time_other"
InputProps={{
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>

View File

@@ -15,7 +15,8 @@ import {
ListItemSecondaryAction,
ListItemText,
Typography,
InputAdornment
InputAdornment,
TextField
} from '@mui/material';
import { useContext, useEffect, useState } from 'react';
import { toast } from 'react-toastify';
@@ -165,7 +166,6 @@ const WiFiSettingsForm: FC = () => {
margin="normal"
/>
)}
<ValidatedTextField
fieldErrors={fieldErrors}
name="tx_power"
@@ -180,21 +180,17 @@ const WiFiSettingsForm: FC = () => {
type="number"
margin="normal"
/>
<BlockFormControlLabel
control={<Checkbox name="nosleep" checked={data.nosleep} onChange={updateFormValue} />}
label={LL.NETWORK_DISABLE_SLEEP()}
/>
<BlockFormControlLabel
control={<Checkbox name="bandwidth20" checked={data.bandwidth20} onChange={updateFormValue} />}
label={LL.NETWORK_LOW_BAND()}
/>
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
{LL.GENERAL_OPTIONS()}
</Typography>
<ValidatedTextField
fieldErrors={fieldErrors}
name="hostname"
@@ -205,19 +201,16 @@ const WiFiSettingsForm: FC = () => {
onChange={updateFormValue}
margin="normal"
/>
<BlockFormControlLabel
control={<Checkbox name="enableMDNS" checked={data.enableMDNS} onChange={updateFormValue} />}
label={LL.NETWORK_USE_DNS()}
/>
<BlockFormControlLabel
control={<Checkbox name="enableCORS" checked={data.enableCORS} onChange={updateFormValue} />}
label={LL.NETWORK_ENABLE_CORS()}
/>
{data.enableCORS && (
<ValidatedTextField
fieldErrors={fieldErrors}
<TextField
name="CORSOrigin"
label={LL.NETWORK_CORS_ORIGIN()}
fullWidth
@@ -227,12 +220,10 @@ const WiFiSettingsForm: FC = () => {
margin="normal"
/>
)}
<BlockFormControlLabel
control={<Checkbox name="enableIPv6" checked={data.enableIPv6} onChange={updateFormValue} />}
label={LL.NETWORK_ENABLE_IPV6()}
/>
<BlockFormControlLabel
control={<Checkbox name="static_ip_config" checked={data.static_ip_config} onChange={updateFormValue} />}
label={LL.NETWORK_FIXED_IP()}

View File

@@ -1,6 +1,6 @@
import DownloadIcon from '@mui/icons-material/GetApp';
import WarningIcon from '@mui/icons-material/Warning';
import { Box, styled, Button, Checkbox, MenuItem, Grid } from '@mui/material';
import { Box, styled, Button, Checkbox, MenuItem, Grid, TextField } from '@mui/material';
import { useState, useEffect, useCallback, useLayoutEffect } from 'react';
import type { FC } from 'react';
@@ -138,7 +138,6 @@ const SystemLog: FC = () => {
return () => {
es.close();
};
// eslint-disable-next-line
}, []);
const content = () => {
@@ -150,7 +149,7 @@ const SystemLog: FC = () => {
<>
<Grid container spacing={3} direction="row" justifyContent="flex-start" alignItems="center">
<Grid item xs={2}>
<ValidatedTextField
<TextField
name="level"
label={LL.LOG_LEVEL()}
value={data.level}
@@ -167,10 +166,10 @@ const SystemLog: FC = () => {
<MenuItem value={6}>INFO</MenuItem>
<MenuItem value={7}>DEBUG</MenuItem>
<MenuItem value={9}>ALL</MenuItem>
</ValidatedTextField>
</TextField>
</Grid>
<Grid item xs={2}>
<ValidatedTextField
<TextField
name="max_messages"
label={LL.BUFFER_SIZE()}
value={data.max_messages}
@@ -184,7 +183,7 @@ const SystemLog: FC = () => {
<MenuItem value={50}>50</MenuItem>
<MenuItem value={75}>75</MenuItem>
<MenuItem value={100}>100</MenuItem>
</ValidatedTextField>
</TextField>
</Grid>
<Grid item>
<BlockFormControlLabel

View File

@@ -38,9 +38,10 @@ const de: Translation = {
ENTITY_NAME: 'Entitätsname',
VALUE: '{{Wert|wert}}',
SHOW_FAV: 'nur Favoriten anzeigen',
DEVICE_SENSOR_DATA: 'Geräte- und Sensordaten',
DEVICES_SENSORS: 'Geräte & Sensoren',
ATTACHED_SENSORS: 'Angeschlossene EMS-ESP Sensoren',
DEVICE_DATA: 'Gerätedaten',
SENSOR_DATA: 'Sensordaten',
DEVICES: 'Geräte',
SENSORS: 'Sensoren',
RUN_COMMAND: 'Befehl ausführen',
CHANGE_VALUE: 'Wert ändern',
CANCEL: 'Abbrechen',

View File

@@ -38,9 +38,10 @@ const en: Translation = {
ENTITY_NAME: 'Entity Name',
VALUE: '{{Value|value}}',
SHOW_FAV: 'only show favorites',
DEVICE_SENSOR_DATA: 'Device and Sensor Data',
DEVICES_SENSORS: 'Devices & Sensors',
ATTACHED_SENSORS: 'Attached EMS-ESP Sensors',
DEVICE_DATA: 'Device Data',
SENSOR_DATA: 'Sensor Data',
DEVICES: 'Devices',
SENSORS: 'Sensors',
RUN_COMMAND: 'Call Command',
CHANGE_VALUE: 'Change Value',
CANCEL: 'Cancel',

View File

@@ -38,9 +38,10 @@ const fr: Translation = {
ENTITY_NAME: 'Nom de l\'entité',
VALUE: 'Valeur',
SHOW_FAV: 'ne montrer que les favoris',
DEVICE_SENSOR_DATA: 'Données des appareils et capteurs',
DEVICES_SENSORS: 'Appareils et capteurs',
ATTACHED_SENSORS: 'Capteurs EMS-ESP connectés',
DEVICE_DATA: 'Données des appareils',
SENSOR_DATA: 'Données des capteurs',
DEVICES: 'Appareils',
SENSORS: 'Capteurs',
RUN_COMMAND: 'Lancer une commande',
CHANGE_VALUE: 'Changer la valeur',
CANCEL: 'Annuler',

View File

@@ -38,9 +38,10 @@ const nl: Translation = {
ENTITY_NAME: 'Entiteit',
VALUE: '{{Waarde|waarde}}',
SHOW_FAV: 'alleen favorieten weergeven',
DEVICE_SENSOR_DATA: 'Apparaat en Sensor data',
DEVICES_SENSORS: 'Apparaten & Sensoren',
ATTACHED_SENSORS: 'Aangesloten EMS-ESP sensoren',
SENSOR_DATA: 'Sensor data',
DEVICE_DATA: 'Apparaat data',
DEVICES: 'Apparaten',
SENSORS: 'Sensoren',
RUN_COMMAND: 'Call commando',
CHANGE_VALUE: 'Wijzig waarde',
CANCEL: 'Annuleren',
@@ -136,7 +137,7 @@ const nl: Translation = {
BOOLEAN_FORMAT_API: 'Boolean formaat API/MQTT',
ENUM_FORMAT: 'Enum formaat API/MQTT',
INDEX: 'Index',
ENABLE_PARASITE: 'Activeer Dallas parasitaire modus',
ENABLE_PARASITE: 'Activeer parasitaire modus',
LOGGING: 'Logging',
LOG_HEX: 'Log EMS telegrammen in hexadecimaal',
ENABLE_SYSLOG: 'Activeer Syslog',

View File

@@ -38,9 +38,10 @@ const no: Translation = {
ENTITY_NAME: 'Objektsnavn',
VALUE: '{{Verdi|verdi}}',
SHOW_FAV: ' Vis kun favoritter',
DEVICE_SENSOR_DATA: 'Enheter og Sensordata',
DEVICES_SENSORS: 'Enheter og Sensorer',
ATTACHED_SENSORS: 'Tilkoblede EMS-ESP Sensorer',
DEVICE_DATA: 'Enheterdata',
SENSOR_DATA: 'Sensordata',
DEVICES: 'Enheter',
SENSORS: 'Sensorer',
RUN_COMMAND: 'Kjør kommando',
CHANGE_VALUE: 'Endre Verdi',
CANCEL: 'Avbryt',

View File

@@ -38,9 +38,10 @@ const pl: BaseTranslation = {
ENTITY_NAME: 'Nazwa encji',
VALUE: '{{W|w|}}artość',
SHOW_FAV: 'Pokaż tylko "ulubione"',
DEVICE_SENSOR_DATA: 'Dane z urządzeń i czujników',
DEVICES_SENSORS: 'Urządzenia i czujniki',
ATTACHED_SENSORS: 'Urządzenia podłączone do EMS-ESP (czujniki temperatury/analogowe/cyfrowe, wyjścia cyfrowe)',
DEVICE_DATA: 'Dane z urządzeń',
SENSOR_DATA: 'Dane z czujników',
DEVICES: 'Urządzenia',
SENSORS: 'Czujniki',
RUN_COMMAND: 'Wykonaj komendę',
CHANGE_VALUE: 'Zmień wartość',
CANCEL: 'Anuluj',

View File

@@ -38,9 +38,10 @@ const sv: Translation = {
ENTITY_NAME: 'Entitetsnamn',
VALUE: '{{Värde|värde}}',
SHOW_FAV: 'Visa enbart favoriter',
DEVICE_SENSOR_DATA: 'Enhets och Sensor-data',
DEVICES_SENSORS: 'Enheter & Sensorer',
ATTACHED_SENSORS: 'Anslutna EMS-ESP Sensorer',
DEVICE_DATA: 'Enhets data',
SENSOR_DATA: 'Sensor data',
DEVICES: 'Enheter',
SENSORS: 'Sensorer',
RUN_COMMAND: 'Kör Kommando',
CHANGE_VALUE: 'Ändra Värde',
CANCEL: 'Avbryt',

View File

@@ -38,9 +38,10 @@ const tr: Translation = {
ENTITY_NAME: 'Valık Adı',
VALUE: '{{Değer|değer}}',
SHOW_FAV: 'sadece favorileri göster',
DEVICE_SENSOR_DATA: 'Cihaz ve Sensör Bilgisi',
DEVICES_SENSORS: 'Cihazlar & Sensörler',
ATTACHED_SENSORS: 'Eklenmiş EMS-ESP Sensörler',
DEVICE_DATA: 'Cihaz Bilgisi',
SENSOR_DATA: 'Sensör Bilgisi',
DEVICES: 'Cihazlar',
SENSORS: 'Sensörler',
RUN_COMMAND: 'Çalıştırma Komutu',
CHANGE_VALUE: 'Değeri Değiştir',
CANCEL: 'İptal',

View File

@@ -1,7 +1,10 @@
import { Tab } from '@mui/material';
import { Navigate, Route, Routes } from 'react-router-dom';
import DashboardData from './DashboardData';
import DashboardDevices from './DashboardDevices';
import DashboardSensors from './DashboardSensors';
import DashboardStatus from './DashboardStatus';
import type { FC } from 'react';
import { RouterTabs, useRouterTab, useLayoutTitle } from 'components';
@@ -17,13 +20,15 @@ const Dashboard: FC = () => {
return (
<>
<RouterTabs value={routerTab}>
<Tab value="data" label={LL.DEVICES_SENSORS()} />
<Tab value="devices" label={LL.DEVICES()} />
<Tab value="sensors" label={LL.SENSORS()} />
<Tab value="status" label="Status" />
</RouterTabs>
<Routes>
<Route path="data" element={<DashboardData />} />
<Route path="devices" element={<DashboardDevices />} />
<Route path="sensors" element={<DashboardSensors />} />
<Route path="status" element={<DashboardStatus />} />
<Route path="/*" element={<Navigate replace to="data" />} />
<Route path="/*" element={<Navigate replace to="devices" />} />
</Routes>
</>
);

View File

@@ -45,8 +45,8 @@ import DeviceIcon from './DeviceIcon';
import * as EMSESP from './api';
import { DeviceValueUOM, DeviceValueUOM_s, AnalogType, AnalogTypeNames, DeviceEntityMask } from './types';
import type { SensorData, Device, CoreData, DeviceData, DeviceValue, Sensor, Analog } from './types';
import { DeviceValueUOM, DeviceValueUOM_s, DeviceEntityMask } from './types';
import type { SensorData, Device, CoreData, DeviceData, DeviceValue, TemperatureSensor, AnalogSensor } from './types';
import type { FC } from 'react';
import { ButtonRow, ValidatedTextField, SectionContent, MessageBox } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
@@ -54,26 +54,22 @@ import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue, extractErrorMessage } from 'utils';
const DashboardData: FC = () => {
const DashboardDevices: FC = () => {
const { me } = useContext(AuthenticatedContext);
const { LL } = useI18nContext();
const [coreData, setCoreData] = useState<CoreData>({
connected: true,
devices: [],
s_n: '',
active_sensors: 0,
analog_enabled: false
});
const [deviceData, setDeviceData] = useState<DeviceData>({ label: '', data: [] });
const [sensorData, setSensorData] = useState<SensorData>({ sensors: [], analogs: [] });
const [deviceValue, setDeviceValue] = useState<DeviceValue>();
const [sensor, setSensor] = useState<Sensor>();
const [analog, setAnalog] = useState<Analog>();
const [deviceDialog, setDeviceDialog] = useState<number>(-1);
const [onlyFav, setOnlyFav] = useState(false);
const [coreData, setCoreData] = useState<CoreData>({
connected: true,
devices: []
});
const [selectedDevice, setSelectedDevice] = useState<number>();
const [sensorData, setSensorData] = useState<SensorData>({ ts: [], as: [] });
const [analog, setAnalog] = useState<AnalogSensor>();
const [sensor, setSensor] = useState<TemperatureSensor>();
const common_theme = useTheme({
BaseRow: `
@@ -168,25 +164,6 @@ const DashboardData: FC = () => {
}
]);
const temperature_theme = useTheme([data_theme]);
const analog_theme = useTheme([
data_theme,
{
Table: `
--data-table-library_grid-template-columns: 80px repeat(1, minmax(0, 1fr)) 120px 100px 40px;
`,
BaseCell: `
&:nth-of-type(2) {
text-align: left;
},
&:nth-of-type(4) {
text-align: right;
}
`
}
]);
const getSortIcon = (state: any, sortKey: any) => {
if (state.sortKey === sortKey && state.reverse) {
return <KeyboardArrowDownOutlinedIcon />;
@@ -197,41 +174,6 @@ const DashboardData: FC = () => {
return <UnfoldMoreOutlinedIcon />;
};
const analog_sort = useSort(
{ nodes: sensorData.analogs },
{},
{
sortIcon: {
iconDefault: <UnfoldMoreOutlinedIcon />,
iconUp: <KeyboardArrowUpOutlinedIcon />,
iconDown: <KeyboardArrowDownOutlinedIcon />
},
sortToggleType: SortToggleType.AlternateWithReset,
sortFns: {
GPIO: (array) => array.sort((a, b) => a.g - b.g),
NAME: (array) => array.sort((a, b) => a.n.localeCompare(b.n)),
TYPE: (array) => array.sort((a, b) => a.t - b.t)
}
}
);
const sensor_sort = useSort(
{ nodes: sensorData.sensors },
{},
{
sortIcon: {
iconDefault: <UnfoldMoreOutlinedIcon />,
iconUp: <KeyboardArrowUpOutlinedIcon />,
iconDown: <KeyboardArrowDownOutlinedIcon />
},
sortToggleType: SortToggleType.AlternateWithReset,
sortFns: {
NAME: (array) => array.sort((a, b) => a.n.localeCompare(b.n)),
TEMPERATURE: (array) => array.sort((a, b) => a.t - b.t)
}
}
);
const dv_sort = useSort(
{ nodes: deviceData.data },
{},
@@ -256,18 +198,17 @@ const DashboardData: FC = () => {
}
);
const fetchSensorData = async () => {
try {
setSensorData((await EMSESP.readSensorData()).data);
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
};
// const fetchSensorData = async () => {
// try {
// setSensorData((await EMSESP.readSensorData()).data);
// } catch (error) {
// toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
// }
// };
const fetchDeviceData = async (id: string) => {
const unique_id = parseInt(id);
const fetchDeviceData = async (id: number) => {
try {
setDeviceData((await EMSESP.readDeviceData({ id: unique_id })).data);
setDeviceData((await EMSESP.readDeviceData({ id })).data);
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
@@ -285,29 +226,43 @@ const DashboardData: FC = () => {
void fetchCoreData();
}, [fetchCoreData]);
const refreshDataIndex = (selectedDevice: string) => {
if (selectedDevice === 'sensor') {
void fetchSensorData();
return;
}
// const refreshDataIndex = (selectedDevice: string) => {
// // if (selectedDevice === 'sensor') {
// // void fetchSensorData();
// // return;
// // }
setSensorData({ sensors: [], analogs: [] });
// // setSensorData({ sensors: [], analogs: [] });
// if (selectedDevice) {
// void fetchDeviceData(selectedDevice);
// } else {
// void fetchCoreData();
// }
// };
const refreshData = () => {
// const selectedDevice = device_select.state.id;
// if (selectedDevice === 'sensor') {
// // void fetchSensorData();
// return;
// }
// setSensorData({ sensors: [], analogs: [] });
if (selectedDevice) {
void fetchDeviceData(selectedDevice);
} else {
void fetchCoreData();
}
};
const refreshData = () => {
refreshDataIndex(device_select.state.id);
// refreshDataIndex(device_select.state.id);
};
function onSelectChange(action: any, state: any) {
setSelectedDevice(device_select.state.id);
if (action.type === 'ADD_BY_ID_EXCLUSIVELY') {
refreshData();
} else {
setSensorData({ sensors: [], analogs: [] });
// setSensorData({ sensors: [], analogs: [] });
}
}
@@ -437,7 +392,7 @@ const DashboardData: FC = () => {
const sendDeviceValue = async () => {
if (deviceValue) {
try {
const response = await EMSESP.writeValue({
const response = await EMSESP.writeDeviceValue({
id: Number(device_select.state.id),
devicevalue: deviceValue
});
@@ -523,100 +478,6 @@ const DashboardData: FC = () => {
}
};
const addAnalogSensor = () => {
setAnalog({ id: '0', g: 0, n: '', u: 0, v: 0, o: 0, t: 0, f: 1 });
};
const sendSensor = async () => {
if (sensor) {
try {
const response = await EMSESP.writeSensor({
id: sensor.id,
name: sensor.n,
offset: sensor.o
});
if (response.status === 204) {
toast.error(LL.UPLOAD_OF(LL.SENSOR()) + ' ' + LL.FAILED());
} else if (response.status === 403) {
toast.error(LL.ACCESS_DENIED());
} else {
toast.success(LL.UPDATED_OF(LL.SENSOR()));
}
setSensor(undefined);
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
} finally {
setSensor(undefined);
await fetchSensorData();
}
}
};
const renderSensorDialog = () => {
if (sensor) {
return (
<Dialog open={sensor !== undefined} onClose={() => setSensor(undefined)}>
<DialogTitle>
{LL.EDIT()} {LL.TEMP_SENSOR()}
</DialogTitle>
<DialogContent dividers>
<Box color="warning.main" p={0} pl={0} pr={0} mt={0} mb={2}>
<Typography variant="body2">
{LL.ID_OF(LL.SENSOR())}: {sensor.id}
</Typography>
</Box>
<Grid container spacing={1}>
<Grid item>
<ValidatedTextField
name="n"
label={LL.ENTITY_NAME()}
value={sensor.n}
autoFocus
sx={{ width: '30ch' }}
onChange={updateValue(setSensor)}
/>
</Grid>
<Grid item>
<ValidatedTextField
name="o"
label={LL.OFFSET()}
value={numberValue(sensor.o)}
sx={{ width: '12ch' }}
type="number"
variant="outlined"
onChange={updateValue(setSensor)}
inputProps={{ min: '-5', max: '5', step: '0.1' }}
InputProps={{
startAdornment: <InputAdornment position="start">°C</InputAdornment>
}}
/>
</Grid>
</Grid>
</DialogContent>
<DialogActions>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={() => setSensor(undefined)}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<WarningIcon color="warning" />}
variant="contained"
type="submit"
onClick={() => sendSensor()}
color="info"
>
{LL.UPDATE()}
</Button>
</DialogActions>
</Dialog>
);
}
};
const renderDeviceDialog = () => {
if (coreData && coreData.devices.length > 0 && deviceDialog !== -1) {
return (
@@ -692,21 +553,6 @@ const DashboardData: FC = () => {
</Cell>
</Row>
))}
{(coreData.active_sensors > 0 || coreData.analog_enabled) && (
<Row key="sensor" item={{ id: 'sensor' }}>
<Cell>
<DeviceIcon type_id={1} />
</Cell>
<Cell>{coreData.s_n}</Cell>
<Cell>{LL.ATTACHED_SENSORS()}</Cell>
<Cell>{coreData.active_sensors}</Cell>
<Cell>
<IconButton size="small" onClick={() => addAnalogSensor()}>
<AddCircleOutlineOutlinedIcon sx={{ fontSize: 16, verticalAlign: 'middle' }} />
</IconButton>
</Cell>
</Row>
)}
</Body>
</>
)}
@@ -813,421 +659,13 @@ const DashboardData: FC = () => {
);
};
const updateSensor = (s: Sensor) => {
if (s && me.admin) {
setSensor(s);
}
};
const updateAnalog = (a: Analog) => {
if (me.admin) {
setAnalog(a);
}
};
const renderDallasData = () => (
<>
<Typography sx={{ pt: 2, pb: 1 }} variant="h6" color="secondary">
{LL.TEMP_SENSORS()}
</Typography>
<Table
data={{ nodes: sensorData.sensors }}
theme={temperature_theme}
sort={sensor_sort}
layout={{ custom: true }}
>
{(tableList: any) => (
<>
<Header>
<HeaderRow>
<HeaderCell resize>
<Button
fullWidth
style={{ fontSize: '14px', justifyContent: 'flex-start' }}
endIcon={getSortIcon(sensor_sort.state, 'NAME')}
onClick={() => sensor_sort.fns.onToggleSort({ sortKey: 'NAME' })}
>
{LL.ENTITY_NAME()}
</Button>
</HeaderCell>
<HeaderCell stiff>
<Button
fullWidth
style={{ fontSize: '14px', justifyContent: 'flex-end' }}
endIcon={getSortIcon(sensor_sort.state, 'TEMPERATURE')}
onClick={() => sensor_sort.fns.onToggleSort({ sortKey: 'TEMPERATURE' })}
>
{LL.VALUE(0)}
</Button>
</HeaderCell>
<HeaderCell stiff />
</HeaderRow>
</Header>
<Body>
{tableList.map((s: Sensor) => (
<Row key={s.id} item={s} onClick={() => updateSensor(s)}>
<Cell>{s.n}</Cell>
<Cell>{formatValue(s.t, s.u)}</Cell>
<Cell>
{me.admin && (
<IconButton onClick={() => updateSensor(s)}>
<EditIcon color="primary" sx={{ fontSize: 16 }} />
</IconButton>
)}
</Cell>
</Row>
))}
</Body>
</>
)}
</Table>
</>
);
const renderAnalogData = () => (
<>
<Typography sx={{ pt: 2, pb: 1 }} variant="h6" color="secondary">
{LL.ANALOG_SENSORS()}
</Typography>
<Table data={{ nodes: sensorData.analogs }} theme={analog_theme} sort={analog_sort} layout={{ custom: true }}>
{(tableList: any) => (
<>
<Header>
<HeaderRow>
<HeaderCell stiff>
<Button
fullWidth
style={{ fontSize: '14px', justifyContent: 'flex-start' }}
endIcon={getSortIcon(analog_sort.state, 'GPIO')}
onClick={() => analog_sort.fns.onToggleSort({ sortKey: 'GPIO' })}
>
GPIO
</Button>
</HeaderCell>
<HeaderCell resize>
<Button
fullWidth
style={{ fontSize: '14px', justifyContent: 'flex-start' }}
endIcon={getSortIcon(analog_sort.state, 'NAME')}
onClick={() => analog_sort.fns.onToggleSort({ sortKey: 'NAME' })}
>
{LL.ENTITY_NAME()}
</Button>
</HeaderCell>
<HeaderCell stiff>
<Button
fullWidth
style={{ fontSize: '14px', justifyContent: 'flex-start' }}
endIcon={getSortIcon(analog_sort.state, 'TYPE')}
onClick={() => analog_sort.fns.onToggleSort({ sortKey: 'TYPE' })}
>
{LL.TYPE(0)}
</Button>
</HeaderCell>
<HeaderCell stiff>{LL.VALUE(0)}</HeaderCell>
<HeaderCell stiff />
</HeaderRow>
</Header>
<Body>
{tableList.map((a: Analog) => (
<Row key={a.id} item={a} onClick={() => updateAnalog(a)}>
<Cell stiff>{a.g}</Cell>
<Cell>{a.n}</Cell>
<Cell stiff>{AnalogTypeNames[a.t]} </Cell>
<Cell stiff>{a.t ? formatValue(a.v, a.u) : ''}</Cell>
<Cell stiff>
{me.admin && (
<IconButton onClick={() => updateAnalog(a)}>
<EditIcon color="primary" sx={{ fontSize: 16 }} />
</IconButton>
)}
</Cell>
</Row>
))}
</Body>
</>
)}
</Table>
</>
);
const sendRemoveAnalog = async () => {
if (analog) {
try {
const response = await EMSESP.writeAnalog({
gpio: analog.g,
name: analog.n,
offset: analog.o,
factor: analog.f,
uom: analog.u,
type: -1
});
if (response.status === 204) {
toast.error(LL.DELETION_OF(LL.ANALOG_SENSOR()) + ' ' + LL.FAILED());
} else if (response.status === 403) {
toast.error(LL.ACCESS_DENIED());
} else {
toast.success(LL.REMOVED_OF(LL.ANALOG_SENSOR()));
}
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
} finally {
setAnalog(undefined);
await fetchSensorData();
}
}
};
const sendAnalog = async () => {
if (analog) {
try {
const response = await EMSESP.writeAnalog({
gpio: analog.g,
name: analog.n,
offset: analog.o,
factor: analog.f,
uom: analog.u,
type: analog.t
});
if (response.status === 204) {
toast.error(LL.UPDATE_OF(LL.ANALOG_SENSOR()) + ' ' + LL.FAILED());
} else if (response.status === 403) {
toast.error(LL.ACCESS_DENIED());
} else {
toast.success(LL.UPDATED_OF(LL.ANALOG_SENSOR()));
}
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
} finally {
setAnalog(undefined);
await fetchSensorData();
}
}
};
const renderAnalogDialog = () => {
if (analog) {
return (
<Dialog open={analog !== undefined} onClose={() => setAnalog(undefined)}>
<DialogTitle>
{LL.EDIT()} {LL.ANALOG_SENSOR()}
</DialogTitle>
<DialogContent dividers>
<Grid container spacing={2}>
<Grid item xs={12}>
<ValidatedTextField
name="n"
label={LL.ENTITY_NAME()}
value={analog.n}
fullWidth
variant="outlined"
onChange={updateValue(setAnalog)}
/>
</Grid>
<Grid item xs={4}>
<ValidatedTextField
name="g"
label="GPIO"
value={numberValue(analog.g)}
fullWidth
type="number"
variant="outlined"
autoFocus
onChange={updateValue(setAnalog)}
/>
</Grid>
<Grid item xs={8}>
<ValidatedTextField
name="t"
label={LL.TYPE(0)}
value={analog.t}
fullWidth
select
onChange={updateValue(setAnalog)}
>
{AnalogTypeNames.map((val, i) => (
<MenuItem key={i} value={i}>
{val}
</MenuItem>
))}
</ValidatedTextField>
</Grid>
{analog.t >= AnalogType.COUNTER && analog.t <= AnalogType.RATE && (
<>
<Grid item xs={4}>
<ValidatedTextField
name="u"
label={LL.UNIT()}
value={analog.u}
fullWidth
select
onChange={updateValue(setAnalog)}
>
{DeviceValueUOM_s.map((val, i) => (
<MenuItem key={i} value={i}>
{val}
</MenuItem>
))}
</ValidatedTextField>
</Grid>
{analog.t === AnalogType.ADC && (
<Grid item xs={4}>
<ValidatedTextField
name="o"
label={LL.OFFSET()}
value={numberValue(analog.o)}
fullWidth
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
inputProps={{ min: '0', max: '3300', step: '1' }}
InputProps={{
startAdornment: <InputAdornment position="start">mV</InputAdornment>
}}
/>
</Grid>
)}
{analog.t === AnalogType.COUNTER && (
<Grid item xs={4}>
<ValidatedTextField
name="o"
label={LL.STARTVALUE()}
value={numberValue(analog.o)}
fullWidth
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
inputProps={{ step: '0.001' }}
/>
</Grid>
)}
<Grid item xs={4}>
<ValidatedTextField
name="f"
label={LL.FACTOR()}
value={numberValue(analog.f)}
fullWidth
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
inputProps={{ step: '0.001' }}
/>
</Grid>
</>
)}
{analog.t === AnalogType.DIGITAL_OUT && (analog.g === 25 || analog.g === 26) && (
<>
<Grid item xs={4}>
<ValidatedTextField
name="o"
label={LL.VALUE(0)}
value={numberValue(analog.o)}
fullWidth
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
inputProps={{ min: '0', max: '255', step: '1' }}
/>
</Grid>
</>
)}
{analog.t === AnalogType.DIGITAL_OUT && analog.g !== 25 && analog.g !== 26 && (
<>
<Grid item xs={4}>
<ValidatedTextField
name="o"
label={LL.VALUE(0)}
value={numberValue(analog.o)}
fullWidth
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
inputProps={{ min: '0', max: '1', step: '1' }}
/>
</Grid>
</>
)}
{analog.t >= AnalogType.PWM_0 && (
<>
<Grid item xs={4}>
<ValidatedTextField
name="f"
label={LL.FREQ()}
value={numberValue(analog.f)}
fullWidth
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
inputProps={{ min: '1', max: '5000', step: '1' }}
InputProps={{
startAdornment: <InputAdornment position="start">Hz</InputAdornment>
}}
/>
</Grid>
<Grid item xs={4}>
<ValidatedTextField
name="o"
label={LL.DUTY_CYCLE()}
value={numberValue(analog.o)}
fullWidth
type="number"
variant="outlined"
onChange={updateValue(setAnalog)}
inputProps={{ min: '0', max: '100', step: '0.1' }}
InputProps={{
startAdornment: <InputAdornment position="start">%</InputAdornment>
}}
/>
</Grid>
</>
)}
</Grid>
<Box color="warning.main" mt={2}>
<Typography variant="body2">{LL.WARN_GPIO()}</Typography>
</Box>
</DialogContent>
<DialogActions>
<Box flexGrow={1} sx={{ '& button': { mt: 0 } }}>
<Button startIcon={<RemoveIcon />} variant="outlined" color="error" onClick={() => sendRemoveAnalog()}>
{LL.REMOVE()}
</Button>
</Box>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={() => setAnalog(undefined)}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<WarningIcon color="warning" />}
variant="contained"
type="submit"
onClick={() => sendAnalog()}
color="info"
>
{LL.UPDATE()}
</Button>
</DialogActions>
</Dialog>
);
}
};
return (
<SectionContent title={LL.DEVICE_SENSOR_DATA()} titleGutter>
<SectionContent title={LL.DEVICE_DATA()} titleGutter>
{renderCoreData()}
{renderDeviceData()}
{renderDeviceDialog()}
{sensorData.sensors.length !== 0 && renderDallasData()}
{sensorData.analogs.length !== 0 && renderAnalogData()}
{renderDeviceValueDialog()}
{renderSensorDialog()}
{renderAnalogDialog()}
<ButtonRow>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={refreshData}>
{LL.REFRESH()}
@@ -1242,4 +680,4 @@ const DashboardData: FC = () => {
);
};
export default DashboardData;
export default DashboardDevices;

View File

@@ -0,0 +1,469 @@
import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined';
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined';
import RefreshIcon from '@mui/icons-material/Refresh';
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
import { Button, Typography, Box } from '@mui/material';
import { useSort, SortToggleType } from '@table-library/react-table-library/sort';
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme';
import { useState, useContext, useCallback, useEffect } from 'react';
import { toast } from 'react-toastify';
import DashboardSensorsAnalogDialog from './DashboardSensorsAnalogDialog';
import DashboardSensorsTemperatureDialog from './DashboardSensorsTemperatureDialog';
import * as EMSESP from './api';
import { DeviceValueUOM, DeviceValueUOM_s, AnalogTypeNames } from './types';
import { temperatureSensorItemValidation, analogSensorItemValidation } from './validators';
import type { SensorData, TemperatureSensor, AnalogSensor } from './types';
import type { FC } from 'react';
import { ButtonRow, SectionContent } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import { extractErrorMessage } from 'utils';
const DashboardSensors: FC = () => {
const { LL } = useI18nContext();
const { me } = useContext(AuthenticatedContext);
const [sensorData, setSensorData] = useState<SensorData>({ ts: [], as: [], analog_enabled: false });
const [selectedTemperatureSensor, setSelectedTemperatureSensor] = useState<TemperatureSensor>();
const [selectedAnalogSensor, setSelectedAnalogSensor] = useState<AnalogSensor>();
const [temperatureDialogOpen, setTemperatureDialogOpen] = useState<boolean>(false);
const [analogDialogOpen, setAnalogDialogOpen] = useState<boolean>(false);
const [creating, setCreating] = useState<boolean>(false);
const isAdmin = me.admin;
const common_theme = useTheme({
BaseRow: `
font-size: 14px;
.td {
height: 32px;
}
`,
HeaderRow: `
text-transform: uppercase;
background-color: black;
color: #90CAF9;
.th {
border-bottom: 1px solid #565656;
}
.th {
height: 36px;
}
`,
Row: `
background-color: #1e1e1e;
position: relative;
cursor: pointer;
.td {
padding: 8px;
border-top: 1px solid #565656;
border-bottom: 1px solid #565656;
}
&.tr.tr-body.row-select.row-select-single-selected {
background-color: #3d4752;
font-weight: normal;
}
&:hover .td {
border-top: 1px solid #177ac9;
border-bottom: 1px solid #177ac9;
}
&:nth-of-type(odd) .td {
background-color: #303030;
}
`,
Cell: `
&:last-of-type {
text-align: right;
},
`
});
const temperature_theme = useTheme([
common_theme,
{
Table: `
--data-table-library_grid-template-columns: minmax(0, 1fr) 35%;
`
}
]);
const analog_theme = useTheme([
common_theme,
{
Table: `
--data-table-library_grid-template-columns: 80px repeat(1, minmax(0, 1fr)) 120px 100px;
`
}
]);
const fetchSensorData = useCallback(async () => {
try {
setSensorData((await EMSESP.readSensorData()).data);
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
}, [LL]);
useEffect(() => {
void fetchSensorData();
}, []);
const getSortIcon = (state: any, sortKey: any) => {
if (state.sortKey === sortKey && state.reverse) {
return <KeyboardArrowDownOutlinedIcon />;
}
if (state.sortKey === sortKey && !state.reverse) {
return <KeyboardArrowUpOutlinedIcon />;
}
return <UnfoldMoreOutlinedIcon />;
};
const analog_sort = useSort(
{ nodes: sensorData.as },
{},
{
sortIcon: {
iconDefault: <UnfoldMoreOutlinedIcon />,
iconUp: <KeyboardArrowUpOutlinedIcon />,
iconDown: <KeyboardArrowDownOutlinedIcon />
},
sortToggleType: SortToggleType.AlternateWithReset,
sortFns: {
GPIO: (array) => array.sort((a, b) => a.g - b.g),
NAME: (array) => array.sort((a, b) => a.n.localeCompare(b.n)),
TYPE: (array) => array.sort((a, b) => a.t - b.t),
VALUE: (array) => array.sort((a, b) => a.v - b.v)
}
}
);
const temperature_sort = useSort(
{ nodes: sensorData.ts },
{},
{
sortIcon: {
iconDefault: <UnfoldMoreOutlinedIcon />,
iconUp: <KeyboardArrowUpOutlinedIcon />,
iconDown: <KeyboardArrowDownOutlinedIcon />
},
sortToggleType: SortToggleType.AlternateWithReset,
sortFns: {
NAME: (array) => array.sort((a, b) => a.n.localeCompare(b.n)),
VALUE: (array) => array.sort((a, b) => a.t - b.t)
}
}
);
useEffect(() => {
const timer = setInterval(() => fetchSensorData(), 60000);
return () => {
clearInterval(timer);
};
}, [fetchSensorData]);
const formatDurationMin = (duration_min: number) => {
const days = Math.trunc((duration_min * 60000) / 86400000);
const hours = Math.trunc((duration_min * 60000) / 3600000) % 24;
const minutes = Math.trunc((duration_min * 60000) / 60000) % 60;
let formatted = '';
if (days) {
formatted += LL.NUM_DAYS({ num: days }) + ' ';
}
if (hours) {
formatted += LL.NUM_HOURS({ num: hours }) + ' ';
}
if (minutes) {
formatted += LL.NUM_MINUTES({ num: minutes });
}
return formatted;
};
function formatValue(value: any, uom: number) {
if (value === undefined) {
return '';
}
switch (uom) {
case DeviceValueUOM.HOURS:
return value ? formatDurationMin(value * 60) : LL.NUM_HOURS({ num: 0 });
case DeviceValueUOM.MINUTES:
return value ? formatDurationMin(value) : LL.NUM_MINUTES({ num: 0 });
case DeviceValueUOM.SECONDS:
return LL.NUM_SECONDS({ num: value });
case DeviceValueUOM.NONE:
if (typeof value === 'number') {
return new Intl.NumberFormat().format(value);
}
return value;
case DeviceValueUOM.DEGREES:
case DeviceValueUOM.DEGREES_R:
case DeviceValueUOM.FAHRENHEIT:
return (
new Intl.NumberFormat(undefined, {
minimumFractionDigits: 1
}).format(value) +
' ' +
DeviceValueUOM_s[uom]
);
default:
return new Intl.NumberFormat().format(value) + ' ' + DeviceValueUOM_s[uom];
}
}
const updateTemperatureSensor = (ts: TemperatureSensor) => {
if (isAdmin) {
setSelectedTemperatureSensor(ts);
setTemperatureDialogOpen(true);
}
};
const onTemperatureDialogClose = () => {
setTemperatureDialogOpen(false);
};
const onTemperatureDialogSave = async (ts: TemperatureSensor) => {
try {
const response = await EMSESP.writeTemperatureSensor({
id: ts.id,
name: ts.n,
offset: ts.o
});
if (response.status === 204) {
toast.error(LL.UPLOAD_OF(LL.SENSOR()) + ' ' + LL.FAILED());
} else if (response.status === 403) {
toast.error(LL.ACCESS_DENIED());
} else {
toast.success(LL.UPDATED_OF(LL.SENSOR()));
}
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
} finally {
setTemperatureDialogOpen(false);
setSelectedTemperatureSensor(undefined);
await fetchSensorData();
}
};
const updateAnalogSensor = (as: AnalogSensor) => {
if (isAdmin) {
setCreating(false);
setSelectedAnalogSensor(as);
setAnalogDialogOpen(true);
}
};
const onAnalogDialogClose = () => {
setAnalogDialogOpen(false);
};
const addAnalogSensor = () => {
setCreating(true);
setSelectedAnalogSensor({
id: Math.floor(Math.random() * (Math.floor(200) - 100) + 100),
n: '',
g: 40,
u: 0,
v: 0,
o: 0,
t: 0,
f: 1,
d: false
});
setAnalogDialogOpen(true);
};
const onAnalogDialogSave = async (as: AnalogSensor) => {
try {
const response = await EMSESP.writeAnalogSensor({
id: as.id,
gpio: as.g,
name: as.n,
offset: as.o,
factor: as.f,
uom: as.u,
type: as.t,
deleted: as.d
});
if (response.status === 204) {
toast.error(LL.UPDATE_OF(LL.ANALOG_SENSOR()) + ' ' + LL.FAILED());
} else if (response.status === 403) {
toast.error(LL.ACCESS_DENIED());
} else {
toast.success(LL.UPDATED_OF(LL.ANALOG_SENSOR()));
}
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
} finally {
setAnalogDialogOpen(false);
setSelectedAnalogSensor(undefined);
await fetchSensorData();
}
};
const RenderTemperatureSensors = () => (
<Table data={{ nodes: sensorData.ts }} theme={temperature_theme} sort={temperature_sort} layout={{ custom: true }}>
{(tableList: any) => (
<>
<Header>
<HeaderRow>
<HeaderCell resize>
<Button
fullWidth
style={{ fontSize: '14px', justifyContent: 'flex-start' }}
endIcon={getSortIcon(temperature_sort.state, 'NAME')}
onClick={() => temperature_sort.fns.onToggleSort({ sortKey: 'NAME' })}
>
{LL.NAME(0)}
</Button>
</HeaderCell>
<HeaderCell stiff>
<Button
fullWidth
style={{ fontSize: '14px', justifyContent: 'flex-end' }}
endIcon={getSortIcon(temperature_sort.state, 'VALUE')}
onClick={() => temperature_sort.fns.onToggleSort({ sortKey: 'VALUE' })}
>
{LL.VALUE(0)}
</Button>
</HeaderCell>
</HeaderRow>
</Header>
<Body>
{tableList.map((ts: TemperatureSensor) => (
<Row key={ts.id} item={ts} onClick={() => updateTemperatureSensor(ts)}>
<Cell>{ts.n}</Cell>
<Cell>{formatValue(ts.t, ts.u)}</Cell>
</Row>
))}
</Body>
</>
)}
</Table>
);
const RenderAnalogSensors = () => (
<Table data={{ nodes: sensorData.as }} theme={analog_theme} sort={analog_sort} layout={{ custom: true }}>
{(tableList: any) => (
<>
<Header>
<HeaderRow>
<HeaderCell stiff>
<Button
fullWidth
style={{ fontSize: '14px', justifyContent: 'flex-start' }}
endIcon={getSortIcon(analog_sort.state, 'GPIO')}
onClick={() => analog_sort.fns.onToggleSort({ sortKey: 'GPIO' })}
>
GPIO
</Button>
</HeaderCell>
<HeaderCell resize>
<Button
fullWidth
style={{ fontSize: '14px', justifyContent: 'flex-start' }}
endIcon={getSortIcon(analog_sort.state, 'NAME')}
onClick={() => analog_sort.fns.onToggleSort({ sortKey: 'NAME' })}
>
{LL.NAME(0)}
</Button>
</HeaderCell>
<HeaderCell stiff>
<Button
fullWidth
style={{ fontSize: '14px', justifyContent: 'flex-start' }}
endIcon={getSortIcon(analog_sort.state, 'TYPE')}
onClick={() => analog_sort.fns.onToggleSort({ sortKey: 'TYPE' })}
>
{LL.TYPE(0)}
</Button>
</HeaderCell>
<HeaderCell stiff>
<Button
fullWidth
style={{ fontSize: '14px', justifyContent: 'flex-end' }}
endIcon={getSortIcon(analog_sort.state, 'VALUE')}
onClick={() => analog_sort.fns.onToggleSort({ sortKey: 'VALUE' })}
>
{LL.VALUE(0)}
</Button>
</HeaderCell>
</HeaderRow>
</Header>
<Body>
{tableList.map((a: AnalogSensor) => (
<Row key={a.id} item={a} onClick={() => updateAnalogSensor(a)}>
<Cell stiff>{a.g}</Cell>
<Cell>{a.n}</Cell>
<Cell stiff>{AnalogTypeNames[a.t]} </Cell>
<Cell stiff>{a.t ? formatValue(a.v, a.u) : ''}</Cell>
</Row>
))}
</Body>
</>
)}
</Table>
);
return (
<SectionContent title={LL.SENSOR_DATA()} titleGutter>
<Typography sx={{ pt: 2, pb: 1 }} variant="h6" color="secondary">
{LL.TEMP_SENSORS()}
</Typography>
<RenderTemperatureSensors />
{selectedTemperatureSensor && (
<DashboardSensorsTemperatureDialog
open={temperatureDialogOpen}
onClose={onTemperatureDialogClose}
onSave={onTemperatureDialogSave}
selectedItem={selectedTemperatureSensor}
validator={temperatureSensorItemValidation()}
/>
)}
{sensorData?.analog_enabled === true && (
<>
<Typography sx={{ pt: 4, pb: 1 }} variant="h6" color="secondary">
{LL.ANALOG_SENSORS()}
</Typography>
<RenderAnalogSensors />
{selectedAnalogSensor && (
<DashboardSensorsAnalogDialog
open={analogDialogOpen}
onClose={onAnalogDialogClose}
onSave={onAnalogDialogSave}
creating={creating}
selectedItem={selectedAnalogSensor}
validator={analogSensorItemValidation(sensorData.as, creating)}
/>
)}
</>
)}
<ButtonRow>
<Box display="flex" flexWrap="wrap">
<Box flexGrow={1}>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={fetchSensorData}>
{LL.REFRESH()}
</Button>
</Box>
<Button
variant="outlined"
color="primary"
startIcon={<AddCircleOutlineOutlinedIcon />}
onClick={addAnalogSensor}
>
{LL.ADD(0) + ' ' + LL.ANALOG_SENSOR()}
</Button>
</Box>
</ButtonRow>
</SectionContent>
);
};
export default DashboardSensors;

View File

@@ -0,0 +1,261 @@
import CancelIcon from '@mui/icons-material/Cancel';
import RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
import WarningIcon from '@mui/icons-material/Warning';
import {
Button,
Typography,
Box,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
InputAdornment,
Grid,
MenuItem,
TextField
} from '@mui/material';
import { useState, useEffect } from 'react';
import { AnalogType, AnalogTypeNames, DeviceValueUOM_s } from './types';
import type { AnalogSensor } from './types';
import type Schema from 'async-validator';
import type { ValidateFieldsError } from 'async-validator';
import { ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils';
import { validate } from 'validators';
type DashboardSensorsAnalogDialogProps = {
open: boolean;
onClose: () => void;
onSave: (as: AnalogSensor) => void;
creating: boolean;
selectedItem: AnalogSensor;
validator: Schema;
};
const DashboardSensorsAnalogDialog = ({
open,
onClose,
onSave,
creating,
selectedItem,
validator
}: DashboardSensorsAnalogDialogProps) => {
const { LL } = useI18nContext();
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const [editItem, setEditItem] = useState<AnalogSensor>(selectedItem);
const updateFormValue = updateValue(setEditItem);
useEffect(() => {
if (open) {
setFieldErrors(undefined);
setEditItem(selectedItem);
}
}, [open, selectedItem]);
const close = () => {
onClose();
};
const save = async () => {
try {
setFieldErrors(undefined);
await validate(validator, editItem);
onSave(editItem);
} catch (errors: any) {
setFieldErrors(errors);
}
};
const remove = () => {
editItem.d = true;
onSave(editItem);
};
return (
<Dialog open={open} onClose={close}>
<DialogTitle>
{creating ? LL.ADD(1) + ' ' + LL.NEW(1) : LL.EDIT()}&nbsp;{LL.ANALOG_SENSOR()}
</DialogTitle>
<DialogContent dividers>
<Grid container spacing={2}>
<Grid item xs={12}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="n"
label={LL.NAME(0)}
value={editItem.n}
fullWidth
variant="outlined"
onChange={updateFormValue}
/>
</Grid>
<Grid item xs={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="g"
label="GPIO"
value={numberValue(editItem.g)}
fullWidth
type="number"
variant="outlined"
autoFocus
onChange={updateFormValue}
/>
</Grid>
<Grid item xs={8}>
<TextField name="t" label={LL.TYPE(0)} value={editItem.t} fullWidth select onChange={updateFormValue}>
{AnalogTypeNames.map((val, i) => (
<MenuItem key={i} value={i}>
{val}
</MenuItem>
))}
</TextField>
</Grid>
{editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE && (
<>
<Grid item xs={4}>
<TextField name="u" label={LL.UNIT()} value={editItem.u} fullWidth select onChange={updateFormValue}>
{DeviceValueUOM_s.map((val, i) => (
<MenuItem key={i} value={i}>
{val}
</MenuItem>
))}
</TextField>
</Grid>
{editItem.t === AnalogType.ADC && (
<Grid item xs={4}>
<TextField
name="o"
label={LL.OFFSET()}
value={numberValue(editItem.o)}
fullWidth
type="number"
variant="outlined"
onChange={updateFormValue}
inputProps={{ min: '0', max: '3300', step: '1' }}
InputProps={{
startAdornment: <InputAdornment position="start">mV</InputAdornment>
}}
/>
</Grid>
)}
{editItem.t === AnalogType.COUNTER && (
<Grid item xs={4}>
<TextField
name="o"
label={LL.STARTVALUE()}
value={numberValue(editItem.o)}
fullWidth
type="number"
variant="outlined"
onChange={updateFormValue}
inputProps={{ step: '0.001' }}
/>
</Grid>
)}
<Grid item xs={4}>
<TextField
name="f"
label={LL.FACTOR()}
value={numberValue(editItem.f)}
fullWidth
type="number"
variant="outlined"
onChange={updateFormValue}
inputProps={{ step: '0.001' }}
/>
</Grid>
</>
)}
{editItem.t === AnalogType.DIGITAL_OUT && (editItem.g === 25 || editItem.g === 26) && (
<Grid item xs={4}>
<TextField
name="o"
label={LL.VALUE(0)}
value={numberValue(editItem.o)}
fullWidth
type="number"
variant="outlined"
onChange={updateFormValue}
inputProps={{ min: '0', max: '255', step: '1' }}
/>
</Grid>
)}
{editItem.t === AnalogType.DIGITAL_OUT && editItem.g !== 25 && editItem.g !== 26 && (
<Grid item xs={4}>
<TextField
name="o"
label={LL.VALUE(0)}
value={numberValue(editItem.o)}
fullWidth
type="number"
variant="outlined"
onChange={updateFormValue}
inputProps={{ min: '0', max: '1', step: '1' }}
/>
</Grid>
)}
{editItem.t >= AnalogType.PWM_0 && (
<>
<Grid item xs={4}>
<TextField
name="f"
label={LL.FREQ()}
value={numberValue(editItem.f)}
fullWidth
type="number"
variant="outlined"
onChange={updateFormValue}
inputProps={{ min: '1', max: '5000', step: '1' }}
InputProps={{
startAdornment: <InputAdornment position="start">Hz</InputAdornment>
}}
/>
</Grid>
<Grid item xs={4}>
<TextField
name="o"
label={LL.DUTY_CYCLE()}
value={numberValue(editItem.o)}
fullWidth
type="number"
variant="outlined"
onChange={updateFormValue}
inputProps={{ min: '0', max: '100', step: '0.1' }}
InputProps={{
startAdornment: <InputAdornment position="start">%</InputAdornment>
}}
/>
</Grid>
</>
)}
</Grid>
<Box color="warning.main" mt={2}>
<Typography variant="body2">{LL.WARN_GPIO()}</Typography>
</Box>
</DialogContent>
<DialogActions>
{!creating && (
<Box flexGrow={1} sx={{ '& button': { mt: 0 } }}>
<Button startIcon={<RemoveIcon />} variant="outlined" color="error" onClick={remove}>
{LL.REMOVE()}
</Button>
</Box>
)}
<Button startIcon={<CancelIcon />} variant="outlined" onClick={close} color="secondary">
{LL.CANCEL()}
</Button>
<Button startIcon={<WarningIcon color="warning" />} variant="contained" onClick={save} color="info">
{creating ? LL.ADD(0) : LL.UPDATE()}
</Button>
</DialogActions>
</Dialog>
);
};
export default DashboardSensorsAnalogDialog;

View File

@@ -0,0 +1,121 @@
import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning';
import {
Button,
Typography,
Box,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
InputAdornment,
Grid,
TextField
} from '@mui/material';
import { useState, useEffect } from 'react';
import type { TemperatureSensor } from './types';
import type Schema from 'async-validator';
import type { ValidateFieldsError } from 'async-validator';
import { ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils';
import { validate } from 'validators';
type DashboardSensorsTemperatureDialogProps = {
open: boolean;
onClose: () => void;
onSave: (ts: TemperatureSensor) => void;
selectedItem: TemperatureSensor;
validator: Schema;
};
const DashboardSensorsTemperatureDialog = ({
open,
onClose,
onSave,
selectedItem,
validator
}: DashboardSensorsTemperatureDialogProps) => {
const { LL } = useI18nContext();
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const [editItem, setEditItem] = useState<TemperatureSensor>(selectedItem);
const updateFormValue = updateValue(setEditItem);
useEffect(() => {
if (open) {
setFieldErrors(undefined);
setEditItem(selectedItem);
}
}, [open, selectedItem]);
const close = () => {
onClose();
};
const save = async () => {
try {
setFieldErrors(undefined);
await validate(validator, editItem);
onSave(editItem);
} catch (errors: any) {
setFieldErrors(errors);
}
};
return (
<Dialog open={open} onClose={close}>
<DialogTitle>
{LL.EDIT()} {LL.TEMP_SENSOR()}
</DialogTitle>
<DialogContent dividers>
<Box color="warning.main" p={0} pl={0} pr={0} mt={0} mb={2}>
<Typography variant="body2">
{LL.ID_OF(LL.SENSOR())}: {editItem.id}
</Typography>
</Box>
<Grid container spacing={1}>
<Grid item>
<ValidatedTextField
fieldErrors={fieldErrors}
name="n"
label={LL.NAME(0)}
value={editItem.n}
autoFocus
sx={{ width: '30ch' }}
onChange={updateFormValue}
/>
</Grid>
<Grid item>
<TextField
name="o"
label={LL.OFFSET()}
value={numberValue(editItem.o)}
sx={{ width: '12ch' }}
type="number"
variant="outlined"
onChange={updateFormValue}
inputProps={{ min: '-5', max: '5', step: '0.1' }}
InputProps={{
startAdornment: <InputAdornment position="start">°C</InputAdornment>
}}
/>
</Grid>
</Grid>
</DialogContent>
<DialogActions>
<Button startIcon={<CancelIcon />} variant="outlined" onClick={close} color="secondary">
{LL.CANCEL()}
</Button>
<Button startIcon={<WarningIcon color="warning" />} variant="contained" onClick={save} color="info">
{LL.UPDATE()}
</Button>
</DialogActions>
</Dialog>
);
};
export default DashboardSensorsTemperatureDialog;

View File

@@ -15,7 +15,7 @@ interface DeviceIconProps {
// matches emsdevice.h DeviceType
const enum DeviceType {
SYSTEM = 0,
DALLASSENSOR,
TEMPERATURESENSOR,
ANALOGSENSOR,
SCHEDULER,
BOILER,
@@ -37,7 +37,7 @@ const enum DeviceType {
const DeviceIcon: FC<DeviceIconProps> = ({ type_id }) => {
switch (type_id) {
case DeviceType.DALLASSENSOR:
case DeviceType.TEMPERATURESENSOR:
case DeviceType.ANALOGSENSOR:
return <MdOutlineSensors />;
case DeviceType.BOILER:

View File

@@ -1,7 +1,7 @@
import CancelIcon from '@mui/icons-material/Cancel';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import WarningIcon from '@mui/icons-material/Warning';
import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider, InputAdornment } from '@mui/material';
import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider, InputAdornment, TextField } from '@mui/material';
import { useState } from 'react';
import { toast } from 'react-toastify';
@@ -132,7 +132,7 @@ const SettingsApplication: FC = () => {
<Box color="warning.main">
<Typography variant="body2">{LL.BOARD_PROFILE_TEXT()}</Typography>
</Box>
<ValidatedTextField
<TextField
name="board_profile"
label={LL.BOARD_PROFILE()}
value={data.board_profile}
@@ -148,7 +148,7 @@ const SettingsApplication: FC = () => {
<MenuItem key={'CUSTOM'} value={'CUSTOM'}>
{LL.CUSTOM()}&hellip;
</MenuItem>
</ValidatedTextField>
</TextField>
{data.board_profile === 'CUSTOM' && (
<>
<Grid
@@ -230,7 +230,7 @@ const SettingsApplication: FC = () => {
/>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<ValidatedTextField
<TextField
name="phy_type"
label={LL.PHY_TYPE()}
disabled={saving}
@@ -244,7 +244,7 @@ const SettingsApplication: FC = () => {
<MenuItem value={0}>{LL.DISABLED(1)}</MenuItem>
<MenuItem value={1}>LAN8720</MenuItem>
<MenuItem value={2}>TLK110</MenuItem>
</ValidatedTextField>
</TextField>
</Grid>
</Grid>
{data.phy_type !== 0 && (
@@ -257,7 +257,7 @@ const SettingsApplication: FC = () => {
alignItems="flex-start"
>
<Grid item xs={12} sm={6} md={4}>
<ValidatedTextField
<TextField
name="eth_power"
label={LL.GPIO_OF('PHY Power') + ' (-1=' + LL.DISABLED(1) + ')'}
fullWidth
@@ -270,7 +270,7 @@ const SettingsApplication: FC = () => {
/>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<ValidatedTextField
<TextField
name="eth_phy_addr"
label={LL.ADDRESS_OF('PHY I²C')}
fullWidth
@@ -283,7 +283,7 @@ const SettingsApplication: FC = () => {
/>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<ValidatedTextField
<TextField
name="eth_clock_mode"
label="PHY Clk"
disabled={saving}
@@ -298,7 +298,7 @@ const SettingsApplication: FC = () => {
<MenuItem value={1}>GPIO0_OUT</MenuItem>
<MenuItem value={2}>GPIO16_OUT</MenuItem>
<MenuItem value={3}>GPIO17_OUT</MenuItem>
</ValidatedTextField>
</TextField>
</Grid>
</Grid>
)}
@@ -309,7 +309,7 @@ const SettingsApplication: FC = () => {
</Typography>
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={12} sm={6}>
<ValidatedTextField
<TextField
name="tx_mode"
label={LL.TX_MODE()}
disabled={saving}
@@ -324,10 +324,10 @@ const SettingsApplication: FC = () => {
<MenuItem value={2}>EMS+</MenuItem>
<MenuItem value={3}>HT3</MenuItem>
<MenuItem value={4}>{LL.HARDWARE()}</MenuItem>
</ValidatedTextField>
</TextField>
</Grid>
<Grid item xs={12} sm={6}>
<ValidatedTextField
<TextField
name="ems_bus_id"
label={LL.ID_OF(LL.EMS_BUS(1))}
disabled={saving}
@@ -349,14 +349,14 @@ const SettingsApplication: FC = () => {
<MenuItem value={0x4b}>Gateway 4 (0x4B)</MenuItem>
<MenuItem value={0x4c}>Gateway 5 (0x4C)</MenuItem>
<MenuItem value={0x4d}>Gateway 7 (0x4D)</MenuItem>
</ValidatedTextField>
</TextField>
</Grid>
</Grid>
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
{LL.GENERAL_OPTIONS()}
</Typography>
<Grid item>
<ValidatedTextField
<TextField
name="locale"
label={LL.LANGUAGE_ENTITIES()}
disabled={saving}
@@ -376,7 +376,7 @@ const SettingsApplication: FC = () => {
<MenuItem value="pl">Polski (PL)</MenuItem>
<MenuItem value="sv">Svenska (SV)</MenuItem>
<MenuItem value="tr">Türk (TR)</MenuItem>
</ValidatedTextField>
</TextField>
</Grid>
{data.led_gpio !== 0 && (
<BlockFormControlLabel
@@ -478,7 +478,7 @@ const SettingsApplication: FC = () => {
</Typography>
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={12} sm={6} md={4}>
<ValidatedTextField
<TextField
name="bool_dashboard"
label={LL.BOOLEAN_FORMAT_DASHBOARD()}
value={data.bool_dashboard}
@@ -492,10 +492,10 @@ const SettingsApplication: FC = () => {
<MenuItem value={2}>{LL.ONOFF_CAP()}</MenuItem>
<MenuItem value={3}>true/false</MenuItem>
<MenuItem value={5}>1/0</MenuItem>
</ValidatedTextField>
</TextField>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<ValidatedTextField
<TextField
name="bool_format"
label={LL.BOOLEAN_FORMAT_API()}
value={data.bool_format}
@@ -511,10 +511,10 @@ const SettingsApplication: FC = () => {
<MenuItem value={4}>true/false</MenuItem>
<MenuItem value={5}>&quot;1&quot;/&quot;0&quot;</MenuItem>
<MenuItem value={6}>1/0</MenuItem>
</ValidatedTextField>
</TextField>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<ValidatedTextField
<TextField
name="enum_format"
label={LL.ENUM_FORMAT()}
value={data.enum_format}
@@ -526,7 +526,7 @@ const SettingsApplication: FC = () => {
>
<MenuItem value={1}>{LL.VALUE(1)}</MenuItem>
<MenuItem value={2}>{LL.INDEX()}</MenuItem>
</ValidatedTextField>
</TextField>
</Grid>
</Grid>
{data.dallas_gpio !== 0 && (
@@ -590,7 +590,7 @@ const SettingsApplication: FC = () => {
/>
</Grid>
<Grid item xs={12} sm={6}>
<ValidatedTextField
<TextField
name="syslog_level"
label={LL.LOG_LEVEL()}
value={data.syslog_level}
@@ -607,7 +607,7 @@ const SettingsApplication: FC = () => {
<MenuItem value={6}>INFO</MenuItem>
<MenuItem value={7}>DEBUG</MenuItem>
<MenuItem value={9}>ALL</MenuItem>
</ValidatedTextField>
</TextField>
</Grid>
<Grid item xs={12} sm={6}>
<ValidatedTextField
@@ -635,7 +635,6 @@ const SettingsApplication: FC = () => {
</Button>
</MessageBox>
)}
{!restartNeeded && dirtyFlags && dirtyFlags.length !== 0 && (
<ButtonRow>
<Button

View File

@@ -156,7 +156,6 @@ const SettingsCustomization: FC = () => {
}
}, [LL]);
// on mount
useEffect(() => {
void fetchDevices();
}, [fetchDevices]);
@@ -580,7 +579,7 @@ const SettingsCustomization: FC = () => {
open={dialogOpen}
onClose={onDialogClose}
onSave={onDialogSave}
selectedDeviceEntity={selectedDeviceEntity}
selectedItem={selectedDeviceEntity}
/>
)}
</SectionContent>

View File

@@ -26,17 +26,12 @@ type SettingsCustomizationDialogProps = {
open: boolean;
onClose: () => void;
onSave: (di: DeviceEntity) => void;
selectedDeviceEntity: DeviceEntity;
selectedItem: DeviceEntity;
};
const SettingsCustomizationDialog = ({
open,
onClose,
onSave,
selectedDeviceEntity
}: SettingsCustomizationDialogProps) => {
const SettingsCustomizationDialog = ({ open, onClose, onSave, selectedItem }: SettingsCustomizationDialogProps) => {
const { LL } = useI18nContext();
const [editItem, setEditItem] = useState<DeviceEntity>(selectedDeviceEntity);
const [editItem, setEditItem] = useState<DeviceEntity>(selectedItem);
const [error, setError] = useState<boolean>(false);
const updateFormValue = updateValue(setEditItem);
@@ -47,9 +42,9 @@ const SettingsCustomizationDialog = ({
useEffect(() => {
if (open) {
setError(false);
setEditItem(selectedDeviceEntity);
setEditItem(selectedItem);
}
}, [open, selectedDeviceEntity]);
}, [open, selectedItem]);
const close = () => {
onClose();

View File

@@ -261,7 +261,7 @@ const SettingsEntities: FC = () => {
creating={creating}
onClose={onDialogClose}
onSave={onDialogSave}
selectedEntityItem={selectedEntityItem}
selectedItem={selectedEntityItem}
validator={entityItemValidation()}
/>
)}

View File

@@ -12,7 +12,8 @@ import {
DialogTitle,
Grid,
InputAdornment,
MenuItem
MenuItem,
TextField
} from '@mui/material';
import { useEffect, useState } from 'react';
@@ -33,7 +34,7 @@ type SettingsEntitiesDialogProps = {
creating: boolean;
onClose: () => void;
onSave: (ei: EntityItem) => void;
selectedEntityItem: EntityItem;
selectedItem: EntityItem;
validator: Schema;
};
@@ -42,28 +43,26 @@ const SettingsEntitiesDialog = ({
creating,
onClose,
onSave,
selectedEntityItem,
selectedItem,
validator
}: SettingsEntitiesDialogProps) => {
const { LL } = useI18nContext();
const [editItem, setEditItem] = useState<EntityItem>(selectedEntityItem);
const [editItem, setEditItem] = useState<EntityItem>(selectedItem);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const updateFormValue = updateValue(setEditItem);
// on mount
useEffect(() => {
if (open) {
setFieldErrors(undefined);
setEditItem(selectedEntityItem);
setEditItem(selectedItem);
// convert to hex strings straight away
setEditItem({
...selectedEntityItem,
device_id: selectedEntityItem.device_id.toString(16).toUpperCase().slice(-2),
type_id: selectedEntityItem.type_id.toString(16).toUpperCase().slice(-4)
...selectedItem,
device_id: selectedItem.device_id.toString(16).toUpperCase().slice(-2),
type_id: selectedItem.type_id.toString(16).toUpperCase().slice(-4)
});
}
}, [open, selectedEntityItem]);
}, [open, selectedItem]);
const close = () => {
onClose();
@@ -157,7 +156,7 @@ const SettingsEntitiesDialog = ({
/>
</Grid>
<Grid item xs={4}>
<ValidatedTextField
<TextField
name="value_type"
label="Value Type"
value={editItem.value_type}
@@ -174,13 +173,13 @@ const SettingsEntitiesDialog = ({
<MenuItem value={4}>USHORT</MenuItem>
<MenuItem value={5}>ULONG</MenuItem>
<MenuItem value={6}>TIME</MenuItem>
</ValidatedTextField>
</TextField>
</Grid>
{editItem.value_type !== 0 && (
<>
<Grid item xs={4}>
<ValidatedTextField
<TextField
name="factor"
label={LL.FACTOR()}
value={editItem.factor}
@@ -193,7 +192,7 @@ const SettingsEntitiesDialog = ({
/>
</Grid>
<Grid item xs={4}>
<ValidatedTextField
<TextField
name="uom"
label={LL.UNIT()}
value={editItem.uom}
@@ -207,7 +206,7 @@ const SettingsEntitiesDialog = ({
{val}
</MenuItem>
))}
</ValidatedTextField>
</TextField>
</Grid>
</>
)}

View File

@@ -270,7 +270,7 @@ const SettingsScheduler: FC = () => {
creating={creating}
onClose={onDialogClose}
onSave={onDialogSave}
selectedSchedulerItem={selectedScheduleItem}
selectedItem={selectedScheduleItem}
validator={schedulerItemValidation()}
dow={dow}
/>

View File

@@ -37,7 +37,7 @@ type SettingsSchedulerDialogProps = {
creating: boolean;
onClose: () => void;
onSave: (ei: ScheduleItem) => void;
selectedSchedulerItem: ScheduleItem;
selectedItem: ScheduleItem;
validator: Schema;
dow: string[];
};
@@ -47,12 +47,12 @@ const SettingsSchedulerDialog = ({
creating,
onClose,
onSave,
selectedSchedulerItem,
selectedItem,
validator,
dow
}: SettingsSchedulerDialogProps) => {
const { LL } = useI18nContext();
const [editItem, setEditItem] = useState<ScheduleItem>(selectedSchedulerItem);
const [editItem, setEditItem] = useState<ScheduleItem>(selectedItem);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const updateFormValue = updateValue(setEditItem);
@@ -60,9 +60,9 @@ const SettingsSchedulerDialog = ({
useEffect(() => {
if (open) {
setFieldErrors(undefined);
setEditItem(selectedSchedulerItem);
setEditItem(selectedItem);
}
}, [open, selectedSchedulerItem]);
}, [open, selectedItem]);
const close = () => {
onClose();

View File

@@ -10,9 +10,9 @@ import type {
DeviceEntity,
UniqueID,
CustomEntities,
WriteValue,
WriteSensor,
WriteAnalog,
WriteDeviceValue,
WriteTemperatureSensor,
WriteAnalogSensor,
SensorData,
Schedule,
Entities
@@ -68,16 +68,16 @@ export function writeCustomEntities(customEntities: CustomEntities): AxiosPromis
return AXIOS.post('/customEntities', customEntities);
}
export function writeValue(writevalue: WriteValue): AxiosPromise<void> {
return AXIOS.post('/writeValue', writevalue);
export function writeDeviceValue(dv: WriteDeviceValue): AxiosPromise<void> {
return AXIOS.post('/writeDeviceValue', dv);
}
export function writeSensor(writesensor: WriteSensor): AxiosPromise<void> {
return AXIOS.post('/writeSensor', writesensor);
export function writeTemperatureSensor(ts: WriteTemperatureSensor): AxiosPromise<void> {
return AXIOS.post('/writeTemperatureSensor', ts);
}
export function writeAnalog(writeanalog: WriteAnalog): AxiosPromise<void> {
return AXIOS.post('/writeAnalog', writeanalog);
export function writeAnalogSensor(as: WriteAnalogSensor): AxiosPromise<void> {
return AXIOS.post('/writeAnalogSensor', as);
}
export function resetCustomizations(): AxiosPromise<void> {

View File

@@ -71,7 +71,7 @@ export interface Device {
e: number; // number of entries
}
export interface Sensor {
export interface TemperatureSensor {
id: string; // id string
n: string; // name/alias
t?: number; // temp, optional
@@ -79,34 +79,33 @@ export interface Sensor {
u: number; // uom
}
export interface Analog {
id: string; // id string
export interface AnalogSensor {
id: number;
g: number; // GPIO
n: string;
v: number; // is optional
v: number;
u: number;
o: number;
f: number;
t: number;
d: boolean; // deleted flag
}
export interface WriteSensor {
export interface WriteTemperatureSensor {
id: string;
name: string;
offset: number;
}
export interface SensorData {
sensors: Sensor[];
analogs: Analog[];
ts: TemperatureSensor[];
as: AnalogSensor[];
analog_enabled: boolean;
}
export interface CoreData {
connected: boolean;
devices: Device[];
s_n: string;
active_sensors: number;
analog_enabled: boolean;
}
export interface DeviceShort {
@@ -216,6 +215,7 @@ export const DeviceValueUOM_s = [
];
export enum AnalogType {
REMOVED = -1,
NOTUSED = 0,
DIGITAL_IN,
COUNTER,
@@ -281,18 +281,20 @@ export interface APIcall {
id: any;
}
export interface WriteValue {
export interface WriteDeviceValue {
id: number;
devicevalue: DeviceValue;
}
export interface WriteAnalog {
export interface WriteAnalogSensor {
id: number;
gpio: number;
name: string;
factor: number;
offset: number;
uom: number;
type: number;
deleted: boolean;
}
export enum DeviceEntityMask {

View File

@@ -1,5 +1,5 @@
import Schema from 'async-validator';
import type { Settings } from './types';
import type { AnalogSensor, Settings } from './types';
import type { InternalRuleItem } from 'async-validator';
import { IP_OR_HOSTNAME_VALIDATOR } from 'validators/shared';
@@ -136,3 +136,28 @@ export const entityItemValidation = () =>
{ type: 'number', min: 0, max: 255, message: 'Must be between 0 and 255' }
]
});
export const temperatureSensorItemValidation = () =>
new Schema({
n: [{ required: true, message: 'Name is required' }]
});
export const isGPIOUniqueValidator = (sensors: AnalogSensor[]) => ({
validator(rule: InternalRuleItem, gpio: number, callback: (error?: string) => void) {
if (sensors.find((as) => as.g === gpio)) {
callback('GPIO already in use');
} else {
callback();
}
}
});
export const analogSensorItemValidation = (sensors: AnalogSensor[], creating: boolean) =>
new Schema({
n: [{ required: true, message: 'Name is required' }],
g: [
{ required: true, message: 'GPIO is required' },
GPIO_VALIDATOR,
...(creating ? [isGPIOUniqueValidator(sensors)] : [])
]
});

View File

@@ -251,9 +251,9 @@ __metadata:
languageName: node
linkType: hard
"@emotion/babel-plugin@npm:^11.10.6":
version: 11.10.6
resolution: "@emotion/babel-plugin@npm:11.10.6"
"@emotion/babel-plugin@npm:^11.10.8":
version: 11.10.8
resolution: "@emotion/babel-plugin@npm:11.10.8"
dependencies:
"@babel/helper-module-imports": ^7.16.7
"@babel/runtime": ^7.18.3
@@ -265,12 +265,12 @@ __metadata:
escape-string-regexp: ^4.0.0
find-root: ^1.1.0
source-map: ^0.5.7
stylis: 4.1.3
checksum: 734ab5d59f8a64ec2cb140f71483f74910ada115bf78ce5b645a9e3e379554ffd79045edb74efb8c1d8f856ef4d302bf8ac59b969b1cc28dedcd5000072e63ce
stylis: 4.1.4
checksum: bb9b4c81623d64da91d9e7819c66ddef0d64159df92d74f01aaa79b42e69037d1dafdda3aecca6c14a18311c31852ad1171c5a32cc21146834681298cd1884ab
languageName: node
linkType: hard
"@emotion/cache@npm:^11.10.5, @emotion/cache@npm:^11.10.7":
"@emotion/cache@npm:^11.10.7":
version: 11.10.7
resolution: "@emotion/cache@npm:11.10.7"
dependencies:
@@ -283,6 +283,19 @@ __metadata:
languageName: node
linkType: hard
"@emotion/cache@npm:^11.10.8":
version: 11.10.8
resolution: "@emotion/cache@npm:11.10.8"
dependencies:
"@emotion/memoize": ^0.8.0
"@emotion/sheet": ^1.2.1
"@emotion/utils": ^1.2.0
"@emotion/weak-memoize": ^0.3.0
stylis: 4.1.4
checksum: 6369883921b3c7d4598dfd584001922e8ef80aa7c373db6e5fb430b4ae9ecf3de1a2c2bcdbfef078821ea70a9413572f1263ce11b47082e2f532028d9e50ab9d
languageName: node
linkType: hard
"@emotion/hash@npm:^0.9.0":
version: 0.9.0
resolution: "@emotion/hash@npm:0.9.0"
@@ -306,13 +319,13 @@ __metadata:
languageName: node
linkType: hard
"@emotion/react@npm:^11.10.6":
version: 11.10.6
resolution: "@emotion/react@npm:11.10.6"
"@emotion/react@npm:^11.10.8":
version: 11.10.8
resolution: "@emotion/react@npm:11.10.8"
dependencies:
"@babel/runtime": ^7.18.3
"@emotion/babel-plugin": ^11.10.6
"@emotion/cache": ^11.10.5
"@emotion/babel-plugin": ^11.10.8
"@emotion/cache": ^11.10.8
"@emotion/serialize": ^1.1.1
"@emotion/use-insertion-effect-with-fallbacks": ^1.0.0
"@emotion/utils": ^1.2.0
@@ -323,7 +336,7 @@ __metadata:
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 4c5ce8ef279a8ce0d371414720d9b2b195ce6b30abf82d99a856ef3b7dc8643f8a32b9b01fded35d94ab9159e4982f7eb0ffa3c4b1aabb102180383e56232bcf
checksum: eba3e90ec0ce6b110c358632c63df885385293f4d1303af4f049a1ce2478c433b7db6406bcaf4526bca90b61bf38000d727647637fe5a9b84e625f222b203647
languageName: node
linkType: hard
@@ -347,12 +360,12 @@ __metadata:
languageName: node
linkType: hard
"@emotion/styled@npm:^11.10.6":
version: 11.10.6
resolution: "@emotion/styled@npm:11.10.6"
"@emotion/styled@npm:^11.10.8":
version: 11.10.8
resolution: "@emotion/styled@npm:11.10.8"
dependencies:
"@babel/runtime": ^7.18.3
"@emotion/babel-plugin": ^11.10.6
"@emotion/babel-plugin": ^11.10.8
"@emotion/is-prop-valid": ^1.2.0
"@emotion/serialize": ^1.1.1
"@emotion/use-insertion-effect-with-fallbacks": ^1.0.0
@@ -363,7 +376,7 @@ __metadata:
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 69e968bb359758ec454c547d6f1d5b1a50a8c149c13274119f63d81fbb76db46b2df53c236ecbc9cb15696f9834647156d715e5e6ccc84ff7b1852617e367386
checksum: 092493bd6155a45d10c88373fd38e8cfa3ac5ba8050333b714265f4c2f6d6012ce5e140627f6c8170f749407ac3bc9de7fc85d45c6de92fe6b1b7fb7cc24d8ad
languageName: node
linkType: hard
@@ -694,9 +707,9 @@ __metadata:
languageName: node
linkType: hard
"@mui/base@npm:5.0.0-alpha.126":
version: 5.0.0-alpha.126
resolution: "@mui/base@npm:5.0.0-alpha.126"
"@mui/base@npm:5.0.0-alpha.127":
version: 5.0.0-alpha.127
resolution: "@mui/base@npm:5.0.0-alpha.127"
dependencies:
"@babel/runtime": ^7.21.0
"@emotion/is-prop-valid": ^1.2.0
@@ -713,14 +726,14 @@ __metadata:
peerDependenciesMeta:
"@types/react":
optional: true
checksum: afb6b99cba541cdb40bb098a78d105721cd6c6db3156fe9923bbb0aea184f6949ab5db79ab95177b4edb011acb35ac34556124b1203fd91ea16f52dc5477da7b
checksum: 6b339fe6a7653e55d04442776adb13864916fdd1a16c3c40caafb72e0691f9e86a4e49820b0f2d92d84ce24daeb024a24ceed20275ab3dbf1f87493cc57bd0a3
languageName: node
linkType: hard
"@mui/core-downloads-tracker@npm:^5.12.1":
version: 5.12.1
resolution: "@mui/core-downloads-tracker@npm:5.12.1"
checksum: 0fc90f840e888e0a671ce43dfaa50018c7f2b82379b3adf3128387c3f29d06cdc235493939698339eb2aab49cf9a167473ed95f6a64eb424ee2d95849dd4aa76
"@mui/core-downloads-tracker@npm:^5.12.2":
version: 5.12.2
resolution: "@mui/core-downloads-tracker@npm:5.12.2"
checksum: 6d421c1758b99f416c2d7f61c838809e4bbf017da8ec26fe9198b3d10d41e1d0ea58bbf7ed016dca38d4c76300f94bf900dfd02fb34fb53f57492cffe1b57c65
languageName: node
linkType: hard
@@ -740,13 +753,13 @@ __metadata:
languageName: node
linkType: hard
"@mui/material@npm:^5.12.1":
version: 5.12.1
resolution: "@mui/material@npm:5.12.1"
"@mui/material@npm:^5.12.2":
version: 5.12.2
resolution: "@mui/material@npm:5.12.2"
dependencies:
"@babel/runtime": ^7.21.0
"@mui/base": 5.0.0-alpha.126
"@mui/core-downloads-tracker": ^5.12.1
"@mui/base": 5.0.0-alpha.127
"@mui/core-downloads-tracker": ^5.12.2
"@mui/system": ^5.12.1
"@mui/types": ^7.2.4
"@mui/utils": ^5.12.0
@@ -769,7 +782,7 @@ __metadata:
optional: true
"@types/react":
optional: true
checksum: a4b4becea4da7af787d58b09afe6a3e1c7b1aa1b27f93186f848efd0bcb250e5b39ca51cdbcedc0216db32049665a69a13a2f8480d357f2310ca6efcf8b10a76
checksum: 35ef3e103d2ec1aa9fdbffd0df0097349dcb8eaf41add7c9c4ed023012c256bb07df2266e7c50fef67d6cbac61a86b7ad1baa9a791d0c8fcbfce1d2194becf43
languageName: node
linkType: hard
@@ -1198,9 +1211,9 @@ __metadata:
languageName: node
linkType: hard
"@table-library/react-table-library@npm:4.1.0":
version: 4.1.0
resolution: "@table-library/react-table-library@npm:4.1.0"
"@table-library/react-table-library@npm:4.1.2":
version: 4.1.2
resolution: "@table-library/react-table-library@npm:4.1.2"
dependencies:
clsx: 1.1.1
react-virtualized-auto-sizer: 1.0.7
@@ -1209,7 +1222,7 @@ __metadata:
"@emotion/react": ">= 11"
react: ">=16.8.0"
react-dom: ">=16.8.0"
checksum: e64571d6ad5b755abbef3e30dcb56aaa2362ffb26a7cbade56828cedec2532530ddf463a275d7d264ff6822d1191694153e63d9b3b13fcd3f540fa2b3d8a2b4e
checksum: 2c465d0369349f36d2114b6a397ae12588f64fd114677533f76b80569ff1401233bd26bd47ecda14375a6033fd68c76e686f4f57646b7754a2e6062265f84322
languageName: node
linkType: hard
@@ -1264,10 +1277,10 @@ __metadata:
languageName: node
linkType: hard
"@types/node@npm:^18.16.0":
version: 18.16.0
resolution: "@types/node@npm:18.16.0"
checksum: 6e158e0e8a49f597f590f1d4c94a7b89b7ae707c0134540359559b14427e94dbde289c7557c8a2fe33baaaad478248a819fdccaa2b97a76dfa47bc61ab7f2ab6
"@types/node@npm:^18.16.2":
version: 18.16.2
resolution: "@types/node@npm:18.16.2"
checksum: ddcaecb88ffd85c9d6780d19839c40f00719d8159390225daed84a1c30cdbee50fe6ecef2f9c69010d07b7af3358bb800801f2f312fc985e8f183289eb32c6ad
languageName: node
linkType: hard
@@ -1285,12 +1298,12 @@ __metadata:
languageName: node
linkType: hard
"@types/react-dom@npm:^18.0.11":
version: 18.0.11
resolution: "@types/react-dom@npm:18.0.11"
"@types/react-dom@npm:^18.2.1":
version: 18.2.1
resolution: "@types/react-dom@npm:18.2.1"
dependencies:
"@types/react": "*"
checksum: 8bf1e3f710221a937613df4d192f3b9e5a30e5c3103cac52c5210fb56b79f7a8cc66137d3bc5c9d92d375165a97fae53284724191bc01cb9898564fa02595569
checksum: 3af3c2a85f0f9fd96a03ce717559423cbd0bac3c8e279107008708d457db4b75a4ee7fd64d3af3ddad94cb97ff74dc422241edc56ab54f1905caca7fd717c759
languageName: node
linkType: hard
@@ -1344,14 +1357,14 @@ __metadata:
languageName: node
linkType: hard
"@types/react@npm:^18.0.38":
version: 18.0.38
resolution: "@types/react@npm:18.0.38"
"@types/react@npm:^18.2.0":
version: 18.2.0
resolution: "@types/react@npm:18.2.0"
dependencies:
"@types/prop-types": "*"
"@types/scheduler": "*"
csstype: ^3.0.2
checksum: 39bad7b8ffe2288f7168fb35ce0fe12109771dcf985e673351074ca7ba8b67884686f92696692b90bbe28a68c2cfe3aabd691e717f88fbf19e01284a14abfdb5
checksum: e38f98b7524817459bb1214d39f4cfcb1dd7ffb31992a427b4494f3988aa6195dc349dfb66b299270b399b34568d045bf1cb6230349a6d343e183052ee486eaa
languageName: node
linkType: hard
@@ -1505,23 +1518,23 @@ __metadata:
version: 0.0.0-use.local
resolution: "EMS-ESP@workspace:."
dependencies:
"@emotion/react": ^11.10.6
"@emotion/styled": ^11.10.6
"@emotion/react": ^11.10.8
"@emotion/styled": ^11.10.8
"@msgpack/msgpack": ^3.0.0-beta2
"@mui/icons-material": ^5.11.16
"@mui/material": ^5.12.1
"@mui/material": ^5.12.2
"@remix-run/router": ^1.5.0
"@table-library/react-table-library": 4.1.0
"@table-library/react-table-library": 4.1.2
"@types/lodash-es": ^4.17.7
"@types/node": ^18.16.0
"@types/react": ^18.0.38
"@types/react-dom": ^18.0.11
"@types/node": ^18.16.2
"@types/react": ^18.2.0
"@types/react-dom": ^18.2.1
"@types/react-router-dom": ^5.3.3
"@typescript-eslint/eslint-plugin": ^5.59.1
"@typescript-eslint/parser": ^5.59.1
"@vitejs/plugin-react-swc": ^3.3.0
async-validator: ^4.2.5
axios: ^1.3.6
axios: ^1.4.0
eslint: ^8.39.0
eslint-config-airbnb: ^19.0.4
eslint-config-airbnb-typescript: ^17.0.0
@@ -1550,7 +1563,7 @@ __metadata:
terser: ^5.17.1
typesafe-i18n: ^5.24.3
typescript: ^5.0.4
vite: ^4.3.1
vite: ^4.3.3
vite-plugin-svgr: ^2.4.0
vite-tsconfig-paths: ^4.2.0
languageName: unknown
@@ -1800,14 +1813,14 @@ __metadata:
languageName: node
linkType: hard
"axios@npm:^1.3.6":
version: 1.3.6
resolution: "axios@npm:1.3.6"
"axios@npm:^1.4.0":
version: 1.4.0
resolution: "axios@npm:1.4.0"
dependencies:
follow-redirects: ^1.15.0
form-data: ^4.0.0
proxy-from-env: ^1.1.0
checksum: 451d7ec576a74b2846978e193458466f17ab676f1976630290e717e96aff84c90f755612d268731f722d363e45993e1844ef4fe8cdd022fe5663fc8f6267a7e3
checksum: a925a07590b0ec1d4daf28cd27890f930daab980371558deb3b883af174b881da09e5ba2cb8393a648fda5859e39934982d0b8b092fe89fc84cb6c80a70a1910
languageName: node
linkType: hard
@@ -4190,7 +4203,7 @@ __metadata:
languageName: node
linkType: hard
"nanoid@npm:^3.3.4":
"nanoid@npm:^3.3.6":
version: 3.3.6
resolution: "nanoid@npm:3.3.6"
bin:
@@ -4607,14 +4620,14 @@ __metadata:
languageName: node
linkType: hard
"postcss@npm:^8.4.21":
version: 8.4.21
resolution: "postcss@npm:8.4.21"
"postcss@npm:^8.4.23":
version: 8.4.23
resolution: "postcss@npm:8.4.23"
dependencies:
nanoid: ^3.3.4
nanoid: ^3.3.6
picocolors: ^1.0.0
source-map-js: ^1.0.2
checksum: a26e7cc86a1807d624d9965914c26c20faa3f237184cbd69db537387f6a4f62df97347549144284d47e9e8e27e7c60e797cb3b92ad36cb2f4c3c9cb3b73f9758
checksum: 35c2e26496be286a63706a0b8240fc4d2075a746466df530989208f60ea33cbc80c89420221cffb7d4fdd605afc385993f5f60302447e3047a7c0a8756b6471d
languageName: node
linkType: hard
@@ -4989,9 +5002,9 @@ __metadata:
languageName: node
linkType: hard
"rollup@npm:^3.20.2":
version: 3.20.6
resolution: "rollup@npm:3.20.6"
"rollup@npm:^3.21.0":
version: 3.21.0
resolution: "rollup@npm:3.21.0"
dependencies:
fsevents: ~2.3.2
dependenciesMeta:
@@ -4999,7 +5012,7 @@ __metadata:
optional: true
bin:
rollup: dist/bin/rollup
checksum: 977b4e3177dfca46d9b3f78d661a19a8da17543f11f154b6df0f45494794cf4203cbebeafd89b43bde1e99766c0fad35429cd13e3ee26d75d548166f7780154f
checksum: d88fdc7ea41e1db44b903575782bd7075886e158b31d9869505a06f7afa72ff17f9c6030973736c93142904ad1a9ee730724063a2df227740e587ec62aa4cde3
languageName: node
linkType: hard
@@ -5413,6 +5426,13 @@ __metadata:
languageName: node
linkType: hard
"stylis@npm:4.1.4":
version: 4.1.4
resolution: "stylis@npm:4.1.4"
checksum: 4f7f1ce3a13230315bdecbfbcfbc243c6e1b774f94b147005e396aa5e337fda668da78b0d666a5be51b3fcc096a3af3fc34cbdfd55b1bf92a66be654a5fe3fd8
languageName: node
linkType: hard
"supports-color@npm:^5.3.0, supports-color@npm:^5.5.0":
version: 5.5.0
resolution: "supports-color@npm:5.5.0"
@@ -5748,14 +5768,14 @@ __metadata:
languageName: node
linkType: hard
"vite@npm:^4.3.1":
version: 4.3.1
resolution: "vite@npm:4.3.1"
"vite@npm:^4.3.3":
version: 4.3.3
resolution: "vite@npm:4.3.3"
dependencies:
esbuild: ^0.17.5
fsevents: ~2.3.2
postcss: ^8.4.21
rollup: ^3.20.2
postcss: ^8.4.23
rollup: ^3.21.0
peerDependencies:
"@types/node": ">= 14"
less: "*"
@@ -5781,7 +5801,7 @@ __metadata:
optional: true
bin:
vite: bin/vite.js
checksum: c9a9ccb0405b2d76c228b989d283a03962d00d2f48491b8b8492162fe7b1f130e77f842970badb691e669ea75e3a137e417ffeeeab0e6944179d71cbd74fa166
checksum: 87814f40a1e21fb372014fe43d0faf3b8f166ee0bc458527b6bccd1b6340782f8e6f5a8986492d99bd31d02f7d1b1538cc0006a0ab6f7e311e603fd1782ae04e
languageName: node
linkType: hard