mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-06-14 11:56:32 +03:00
Remove useMemo/useCallback across the web UI
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
@@ -63,22 +63,16 @@ const APSettings = () => {
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
|
||||
const updateFormValue = useMemo(
|
||||
() =>
|
||||
updateValueDirty(
|
||||
origData as unknown as Record<string, unknown>,
|
||||
dirtyFlags,
|
||||
setDirtyFlags,
|
||||
updateDataValue as (value: unknown) => void
|
||||
),
|
||||
[origData, dirtyFlags, setDirtyFlags, updateDataValue]
|
||||
const updateFormValue = updateValueDirty(
|
||||
origData as unknown as Record<string, unknown>,
|
||||
dirtyFlags,
|
||||
setDirtyFlags,
|
||||
updateDataValue as (value: unknown) => void
|
||||
);
|
||||
|
||||
// Memoize AP enabled state
|
||||
const apEnabled = useMemo(() => (data ? isAPEnabled(data) : false), [data]);
|
||||
const apEnabled = data ? isAPEnabled(data) : false;
|
||||
|
||||
// Memoize validation and submit handler
|
||||
const validateAndSubmit = useCallback(async () => {
|
||||
const validateAndSubmit = async () => {
|
||||
if (!data) return;
|
||||
|
||||
try {
|
||||
@@ -88,7 +82,7 @@ const APSettings = () => {
|
||||
} catch (error) {
|
||||
setFieldErrors((error as ValidationError).fieldErrors);
|
||||
}
|
||||
}, [data, saveData]);
|
||||
};
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
@@ -106,49 +106,36 @@ const ApplicationSettings = () => {
|
||||
});
|
||||
});
|
||||
|
||||
// Memoized input props to prevent recreation on every render
|
||||
const SecondsInputProps = useMemo(
|
||||
() => ({
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}),
|
||||
[LL]
|
||||
);
|
||||
const SecondsInputProps = {
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
};
|
||||
|
||||
const MinutesInputProps = useMemo(
|
||||
() => ({
|
||||
endAdornment: <InputAdornment position="end">{LL.MINUTES()}</InputAdornment>
|
||||
}),
|
||||
[LL]
|
||||
);
|
||||
const MinutesInputProps = {
|
||||
endAdornment: <InputAdornment position="end">{LL.MINUTES()}</InputAdornment>
|
||||
};
|
||||
|
||||
const HoursInputProps = useMemo(
|
||||
() => ({
|
||||
endAdornment: <InputAdornment position="end">{LL.HOURS()}</InputAdornment>
|
||||
}),
|
||||
[LL]
|
||||
);
|
||||
const HoursInputProps = {
|
||||
endAdornment: <InputAdornment position="end">{LL.HOURS()}</InputAdornment>
|
||||
};
|
||||
|
||||
const doRestart = useCallback(async () => {
|
||||
const doRestart = async () => {
|
||||
setRestarting(true);
|
||||
await sendAPI({ device: 'system', cmd: 'restart', id: 0 }).catch(
|
||||
(error: Error) => {
|
||||
toast.error(error.message);
|
||||
}
|
||||
);
|
||||
}, [sendAPI]);
|
||||
};
|
||||
|
||||
const updateBoardProfile = useCallback(
|
||||
async (board_profile: string) => {
|
||||
await readBoardProfile(board_profile).catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
},
|
||||
[readBoardProfile]
|
||||
);
|
||||
const updateBoardProfile = async (board_profile: string) => {
|
||||
await readBoardProfile(board_profile).catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
};
|
||||
|
||||
useLayoutTitle(LL.APPLICATION());
|
||||
|
||||
const validateAndSubmit = useCallback(async () => {
|
||||
const validateAndSubmit = async () => {
|
||||
try {
|
||||
setFieldErrors(undefined);
|
||||
await validate(createSettingsValidator(data), data);
|
||||
@@ -157,31 +144,27 @@ const ApplicationSettings = () => {
|
||||
} finally {
|
||||
await saveData();
|
||||
}
|
||||
}, [data, saveData]);
|
||||
};
|
||||
|
||||
const changeBoardProfile = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const boardProfile = event.target.value;
|
||||
updateFormValue(event);
|
||||
if (boardProfile === 'CUSTOM') {
|
||||
updateDataValue({
|
||||
...data,
|
||||
board_profile: boardProfile
|
||||
});
|
||||
} else {
|
||||
void updateBoardProfile(boardProfile);
|
||||
}
|
||||
},
|
||||
[data, updateBoardProfile, updateFormValue, updateDataValue]
|
||||
);
|
||||
const changeBoardProfile = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const boardProfile = event.target.value;
|
||||
updateFormValue(event);
|
||||
if (boardProfile === 'CUSTOM') {
|
||||
updateDataValue({
|
||||
...data,
|
||||
board_profile: boardProfile
|
||||
});
|
||||
} else {
|
||||
void updateBoardProfile(boardProfile);
|
||||
}
|
||||
};
|
||||
|
||||
const restart = useCallback(async () => {
|
||||
const restart = async () => {
|
||||
await validateAndSubmit();
|
||||
await doRestart();
|
||||
}, [validateAndSubmit, doRestart]);
|
||||
};
|
||||
|
||||
// Memoize board profile select items to prevent recreation
|
||||
const boardProfileItems = useMemo(() => boardProfileSelectItems(), []);
|
||||
const boardProfileItems = boardProfileSelectItems();
|
||||
|
||||
const content = () => {
|
||||
if (!data || !hardwareData) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
@@ -57,7 +57,7 @@ const DownloadUpload = () => {
|
||||
|
||||
const { data, send: loadData, error } = useRequest(SystemApi.readSystemStatus);
|
||||
|
||||
const doRestart = useCallback(async () => {
|
||||
const doRestart = async () => {
|
||||
setRestarting(true);
|
||||
try {
|
||||
await sendAPI({ device: 'system', cmd: 'restart', id: 0 });
|
||||
@@ -65,16 +65,33 @@ const DownloadUpload = () => {
|
||||
toast.error((error as Error).message);
|
||||
setRestarting(false);
|
||||
}
|
||||
}, [sendAPI]);
|
||||
};
|
||||
|
||||
useLayoutTitle(LL.DOWNLOAD_UPLOAD());
|
||||
|
||||
const handleCloseBackupDialog = useCallback(() => {
|
||||
const handleCloseBackupDialog = () => {
|
||||
setConfirmBackup(false);
|
||||
}, []);
|
||||
};
|
||||
|
||||
const renderBackupDialog = useMemo(
|
||||
() => (
|
||||
const handleDownload = (type: string) => () => {
|
||||
void sendExportData(type);
|
||||
setConfirmBackup(false);
|
||||
};
|
||||
|
||||
if (restarting) {
|
||||
return <SystemMonitor />;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
<SectionContent>
|
||||
<FormLoader onRetry={loadData} errorMessage={error?.message || ''} />
|
||||
</SectionContent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SectionContent>
|
||||
<Dialog
|
||||
sx={dialogStyle}
|
||||
open={confirmBackup}
|
||||
@@ -98,40 +115,13 @@ const DownloadUpload = () => {
|
||||
<Button
|
||||
startIcon={<DownloadIcon />}
|
||||
variant="outlined"
|
||||
onClick={() => handleDownload('systembackup')()}
|
||||
onClick={handleDownload('systembackup')}
|
||||
color="primary"
|
||||
>
|
||||
{LL.DOWNLOAD(0)}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
),
|
||||
[confirmBackup, handleCloseBackupDialog, LL]
|
||||
);
|
||||
|
||||
const handleDownload = useCallback(
|
||||
(type: string) => () => {
|
||||
void sendExportData(type);
|
||||
setConfirmBackup(false);
|
||||
},
|
||||
[sendExportData]
|
||||
);
|
||||
|
||||
if (restarting) {
|
||||
return <SystemMonitor />;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
<SectionContent>
|
||||
<FormLoader onRetry={loadData} errorMessage={error?.message || ''} />
|
||||
</SectionContent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SectionContent>
|
||||
{renderBackupDialog}
|
||||
|
||||
<Typography sx={{ pb: 2 }} variant="h6" color="primary">
|
||||
{LL.DOWNLOAD(0)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
@@ -57,7 +57,7 @@ const MqttSettings = () => {
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
|
||||
const sendResetMQTT = useCallback(() => {
|
||||
const sendResetMQTT = () => {
|
||||
void callAction({ action: 'resetMQTT' })
|
||||
.then(() => {
|
||||
toast.success('MQTT ' + LL.REFRESH() + ' successful');
|
||||
@@ -65,29 +65,20 @@ const MqttSettings = () => {
|
||||
.catch((error) => {
|
||||
toast.error(String(error.error?.message || 'An error occurred'));
|
||||
});
|
||||
}, []);
|
||||
};
|
||||
|
||||
const updateFormValue = useMemo(
|
||||
() =>
|
||||
updateValueDirty(
|
||||
origData as unknown as Record<string, unknown>,
|
||||
dirtyFlags,
|
||||
setDirtyFlags,
|
||||
updateDataValue as (value: unknown) => void
|
||||
),
|
||||
[origData, dirtyFlags, setDirtyFlags, updateDataValue]
|
||||
const updateFormValue = updateValueDirty(
|
||||
origData as unknown as Record<string, unknown>,
|
||||
dirtyFlags,
|
||||
setDirtyFlags,
|
||||
updateDataValue as (value: unknown) => void
|
||||
);
|
||||
|
||||
const SecondsInputProps = useMemo(
|
||||
() => ({
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}),
|
||||
[LL]
|
||||
);
|
||||
const SecondsInputProps = {
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
};
|
||||
|
||||
const emptyFieldErrors = useMemo(() => ({}), []);
|
||||
|
||||
const validateAndSubmit = useCallback(async () => {
|
||||
const validateAndSubmit = async () => {
|
||||
if (!data) return;
|
||||
try {
|
||||
setFieldErrors(undefined);
|
||||
@@ -96,25 +87,22 @@ const MqttSettings = () => {
|
||||
} catch (error) {
|
||||
setFieldErrors((error as ValidationError).fieldErrors);
|
||||
}
|
||||
}, [data, saveData]);
|
||||
};
|
||||
|
||||
const publishIntervalFields = useMemo(
|
||||
() => [
|
||||
{ name: 'publish_time_heartbeat', label: 'Heartbeat', validated: true },
|
||||
{ name: 'publish_time_boiler', label: LL.MQTT_INT_BOILER(), validated: false },
|
||||
{
|
||||
name: 'publish_time_thermostat',
|
||||
label: LL.MQTT_INT_THERMOSTATS(),
|
||||
validated: false
|
||||
},
|
||||
{ name: 'publish_time_solar', label: LL.MQTT_INT_SOLAR(), validated: false },
|
||||
{ name: 'publish_time_mixer', label: LL.MQTT_INT_MIXER(), validated: false },
|
||||
{ name: 'publish_time_water', label: LL.MQTT_INT_WATER(), validated: false },
|
||||
{ name: 'publish_time_sensor', label: LL.SENSORS(), validated: false },
|
||||
{ name: 'publish_time_other', label: LL.DEFAULT(0), validated: false }
|
||||
],
|
||||
[LL]
|
||||
);
|
||||
const publishIntervalFields = [
|
||||
{ name: 'publish_time_heartbeat', label: 'Heartbeat', validated: true },
|
||||
{ name: 'publish_time_boiler', label: LL.MQTT_INT_BOILER(), validated: false },
|
||||
{
|
||||
name: 'publish_time_thermostat',
|
||||
label: LL.MQTT_INT_THERMOSTATS(),
|
||||
validated: false
|
||||
},
|
||||
{ name: 'publish_time_solar', label: LL.MQTT_INT_SOLAR(), validated: false },
|
||||
{ name: 'publish_time_mixer', label: LL.MQTT_INT_MIXER(), validated: false },
|
||||
{ name: 'publish_time_water', label: LL.MQTT_INT_WATER(), validated: false },
|
||||
{ name: 'publish_time_sensor', label: LL.SENSORS(), validated: false },
|
||||
{ name: 'publish_time_other', label: LL.DEFAULT(0), validated: false }
|
||||
];
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
@@ -154,7 +142,7 @@ const MqttSettings = () => {
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors ?? emptyFieldErrors}
|
||||
fieldErrors={fieldErrors ?? {}}
|
||||
name="host"
|
||||
label={LL.ADDRESS_OF(LL.BROKER())}
|
||||
multiline
|
||||
@@ -166,7 +154,7 @@ const MqttSettings = () => {
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors ?? emptyFieldErrors}
|
||||
fieldErrors={fieldErrors ?? {}}
|
||||
name="port"
|
||||
label="Port"
|
||||
variant="outlined"
|
||||
@@ -178,7 +166,7 @@ const MqttSettings = () => {
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors ?? emptyFieldErrors}
|
||||
fieldErrors={fieldErrors ?? {}}
|
||||
name="base"
|
||||
label={LL.BASE_TOPIC()}
|
||||
variant="outlined"
|
||||
@@ -219,7 +207,7 @@ const MqttSettings = () => {
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors ?? emptyFieldErrors}
|
||||
fieldErrors={fieldErrors ?? {}}
|
||||
name="keep_alive"
|
||||
label="Keep Alive"
|
||||
slotProps={{
|
||||
@@ -438,7 +426,7 @@ const MqttSettings = () => {
|
||||
<Grid key={field.name}>
|
||||
{field.validated ? (
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors ?? emptyFieldErrors}
|
||||
fieldErrors={fieldErrors ?? {}}
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
slotProps={{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||
@@ -61,14 +61,11 @@ const NTPSettings = () => {
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle('NTP');
|
||||
|
||||
// Memoized timezone select items for better performance
|
||||
const timeZoneItems = useTimeZoneSelectItems();
|
||||
|
||||
// Memoized selected timezone value
|
||||
const selectedTzValue = useMemo(
|
||||
() => (data ? selectedTimeZone(data.tz_label, data.tz_format) : undefined),
|
||||
[data?.tz_label, data?.tz_format]
|
||||
);
|
||||
const selectedTzValue = data
|
||||
? selectedTimeZone(data.tz_label, data.tz_format)
|
||||
: undefined;
|
||||
|
||||
const [localTime, setLocalTime] = useState<string>('');
|
||||
const [settingTime, setSettingTime] = useState<boolean>(false);
|
||||
@@ -82,32 +79,22 @@ const NTPSettings = () => {
|
||||
}
|
||||
);
|
||||
|
||||
// Memoize updateFormValue to prevent recreation on every render
|
||||
const updateFormValue = useMemo(
|
||||
() =>
|
||||
updateValueDirty(
|
||||
origData as unknown as Record<string, unknown>,
|
||||
dirtyFlags,
|
||||
setDirtyFlags,
|
||||
updateDataValue as (value: unknown) => void
|
||||
),
|
||||
[origData, dirtyFlags, setDirtyFlags, updateDataValue]
|
||||
const updateFormValue = updateValueDirty(
|
||||
origData as unknown as Record<string, unknown>,
|
||||
dirtyFlags,
|
||||
setDirtyFlags,
|
||||
updateDataValue as (value: unknown) => void
|
||||
);
|
||||
|
||||
// Memoize updateLocalTime handler
|
||||
const updateLocalTime = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => setLocalTime(event.target.value),
|
||||
[]
|
||||
);
|
||||
const updateLocalTime = (event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setLocalTime(event.target.value);
|
||||
|
||||
// Memoize openSetTime handler
|
||||
const openSetTime = useCallback(() => {
|
||||
const openSetTime = () => {
|
||||
setLocalTime(formatLocalDateTime(new Date()));
|
||||
setSettingTime(true);
|
||||
}, []);
|
||||
};
|
||||
|
||||
// Memoize configureTime handler
|
||||
const configureTime = useCallback(async () => {
|
||||
const configureTime = async () => {
|
||||
setProcessing(true);
|
||||
|
||||
try {
|
||||
@@ -120,13 +107,11 @@ const NTPSettings = () => {
|
||||
} finally {
|
||||
setProcessing(false);
|
||||
}
|
||||
}, [localTime, updateTime, LL, loadData]);
|
||||
};
|
||||
|
||||
// Memoize close dialog handler
|
||||
const handleCloseSetTime = useCallback(() => setSettingTime(false), []);
|
||||
const handleCloseSetTime = () => setSettingTime(false);
|
||||
|
||||
// Memoize validate and submit handler
|
||||
const validateAndSubmit = useCallback(async () => {
|
||||
const validateAndSubmit = async () => {
|
||||
if (!data) return;
|
||||
try {
|
||||
setFieldErrors(undefined);
|
||||
@@ -135,23 +120,18 @@ const NTPSettings = () => {
|
||||
} catch (error) {
|
||||
setFieldErrors((error as ValidationError).fieldErrors);
|
||||
}
|
||||
}, [data, saveData]);
|
||||
};
|
||||
|
||||
// Memoize timezone change handler
|
||||
const changeTimeZone = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
void updateState(readNTPSettings(), (settings: NTPSettingsType) => ({
|
||||
...settings,
|
||||
tz_label: event.target.value,
|
||||
tz_format: TIME_ZONES[event.target.value]
|
||||
}));
|
||||
updateFormValue(event);
|
||||
},
|
||||
[updateFormValue]
|
||||
);
|
||||
const changeTimeZone = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
void updateState(readNTPSettings(), (settings: NTPSettingsType) => ({
|
||||
...settings,
|
||||
tz_label: event.target.value,
|
||||
tz_format: TIME_ZONES[event.target.value]
|
||||
}));
|
||||
updateFormValue(event);
|
||||
};
|
||||
|
||||
// Memoize render content to prevent unnecessary re-renders
|
||||
const renderContent = useMemo(() => {
|
||||
const renderContent = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||
}
|
||||
@@ -236,26 +216,12 @@ const NTPSettings = () => {
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}, [
|
||||
data,
|
||||
errorMessage,
|
||||
loadData,
|
||||
updateFormValue,
|
||||
fieldErrors,
|
||||
selectedTzValue,
|
||||
changeTimeZone,
|
||||
timeZoneItems,
|
||||
dirtyFlags,
|
||||
openSetTime,
|
||||
saving,
|
||||
validateAndSubmit,
|
||||
LL
|
||||
]);
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent>
|
||||
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
||||
{renderContent}
|
||||
{renderContent()}
|
||||
<Dialog sx={dialogStyle} open={settingTime} onClose={handleCloseSetTime}>
|
||||
<DialogTitle>{LL.SET_TIME(1)}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
@@ -43,148 +43,141 @@ const Settings = () => {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const doFormat = useCallback(async () => {
|
||||
const doFormat = async () => {
|
||||
await sendAPI({ device: 'system', cmd: 'format', id: 0 }).then(() => {
|
||||
setRestarting(true);
|
||||
setConfirmFactoryReset(false);
|
||||
});
|
||||
}, [sendAPI]);
|
||||
};
|
||||
|
||||
const handleFactoryResetClose = useCallback(() => {
|
||||
const handleFactoryResetClose = () => {
|
||||
setConfirmFactoryReset(false);
|
||||
}, []);
|
||||
};
|
||||
|
||||
const handleFactoryResetClick = useCallback(() => {
|
||||
const handleFactoryResetClick = () => {
|
||||
setConfirmFactoryReset(true);
|
||||
}, []);
|
||||
};
|
||||
|
||||
const content = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<List>
|
||||
<ListMenuItem
|
||||
icon={TuneIcon}
|
||||
bgcolor="#134ba2"
|
||||
label={LL.APPLICATION()}
|
||||
text={LL.APPLICATION_SETTINGS_1()}
|
||||
to="application"
|
||||
/>
|
||||
if (restarting) {
|
||||
return <SystemMonitor />;
|
||||
}
|
||||
|
||||
<ListMenuItem
|
||||
icon={SettingsEthernetIcon}
|
||||
bgcolor="#40828f"
|
||||
label={LL.NETWORK(0)}
|
||||
text={LL.CONFIGURE(LL.SETTINGS_OF(LL.NETWORK(1)))}
|
||||
to="network"
|
||||
/>
|
||||
return (
|
||||
<SectionContent>
|
||||
<List>
|
||||
<ListMenuItem
|
||||
icon={TuneIcon}
|
||||
bgcolor="#134ba2"
|
||||
label={LL.APPLICATION()}
|
||||
text={LL.APPLICATION_SETTINGS_1()}
|
||||
to="application"
|
||||
/>
|
||||
|
||||
<ListMenuItem
|
||||
icon={SettingsInputAntennaIcon}
|
||||
bgcolor="#5f9a5f"
|
||||
label={LL.ACCESS_POINT(0)}
|
||||
text={LL.CONFIGURE(LL.ACCESS_POINT(1))}
|
||||
to="ap"
|
||||
/>
|
||||
<ListMenuItem
|
||||
icon={SettingsEthernetIcon}
|
||||
bgcolor="#40828f"
|
||||
label={LL.NETWORK(0)}
|
||||
text={LL.CONFIGURE(LL.SETTINGS_OF(LL.NETWORK(1)))}
|
||||
to="network"
|
||||
/>
|
||||
|
||||
<ListMenuItem
|
||||
icon={AccessTimeIcon}
|
||||
bgcolor="#c5572c"
|
||||
label="NTP"
|
||||
text={LL.CONFIGURE(LL.LOCAL_TIME(1))}
|
||||
to="ntp"
|
||||
/>
|
||||
<ListMenuItem
|
||||
icon={SettingsInputAntennaIcon}
|
||||
bgcolor="#5f9a5f"
|
||||
label={LL.ACCESS_POINT(0)}
|
||||
text={LL.CONFIGURE(LL.ACCESS_POINT(1))}
|
||||
to="ap"
|
||||
/>
|
||||
|
||||
<ListMenuItem
|
||||
icon={DeviceHubIcon}
|
||||
bgcolor="#68374d"
|
||||
label="MQTT"
|
||||
text={LL.CONFIGURE('MQTT')}
|
||||
to="mqtt"
|
||||
/>
|
||||
<ListMenuItem
|
||||
icon={AccessTimeIcon}
|
||||
bgcolor="#c5572c"
|
||||
label="NTP"
|
||||
text={LL.CONFIGURE(LL.LOCAL_TIME(1))}
|
||||
to="ntp"
|
||||
/>
|
||||
|
||||
<ListMenuItem
|
||||
icon={LockIcon}
|
||||
label={LL.SECURITY(0)}
|
||||
text={LL.SECURITY_1()}
|
||||
to="security"
|
||||
/>
|
||||
<ListMenuItem
|
||||
icon={DeviceHubIcon}
|
||||
bgcolor="#68374d"
|
||||
label="MQTT"
|
||||
text={LL.CONFIGURE('MQTT')}
|
||||
to="mqtt"
|
||||
/>
|
||||
|
||||
<ListMenuItem
|
||||
icon={ViewModuleIcon}
|
||||
bgcolor="#efc34b"
|
||||
label={LL.MODULES()}
|
||||
text={LL.MODULES_1()}
|
||||
to="modules"
|
||||
/>
|
||||
<ListMenuItem
|
||||
icon={LockIcon}
|
||||
label={LL.SECURITY(0)}
|
||||
text={LL.SECURITY_1()}
|
||||
to="security"
|
||||
/>
|
||||
|
||||
<ListMenuItem
|
||||
icon={ImportExportIcon}
|
||||
bgcolor="#5d89f7"
|
||||
label={LL.DOWNLOAD_UPLOAD()}
|
||||
text={LL.DOWNLOAD_UPLOAD_1()}
|
||||
to="downloadUpload"
|
||||
/>
|
||||
</List>
|
||||
<ListMenuItem
|
||||
icon={ViewModuleIcon}
|
||||
bgcolor="#efc34b"
|
||||
label={LL.MODULES()}
|
||||
text={LL.MODULES_1()}
|
||||
to="modules"
|
||||
/>
|
||||
|
||||
<Dialog
|
||||
sx={dialogStyle}
|
||||
open={confirmFactoryReset}
|
||||
onClose={handleFactoryResetClose}
|
||||
>
|
||||
<DialogTitle>{LL.FACTORY_RESET()}</DialogTitle>
|
||||
<DialogContent dividers>{LL.SYSTEM_FACTORY_TEXT_DIALOG()}</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
startIcon={<CancelIcon />}
|
||||
variant="outlined"
|
||||
onClick={handleFactoryResetClose}
|
||||
color="secondary"
|
||||
>
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<SettingsBackupRestoreIcon />}
|
||||
variant="outlined"
|
||||
onClick={doFormat}
|
||||
color="error"
|
||||
>
|
||||
{LL.FACTORY_RESET()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<ListMenuItem
|
||||
icon={ImportExportIcon}
|
||||
bgcolor="#5d89f7"
|
||||
label={LL.DOWNLOAD_UPLOAD()}
|
||||
text={LL.DOWNLOAD_UPLOAD_1()}
|
||||
to="downloadUpload"
|
||||
/>
|
||||
</List>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
mt: 2,
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
flexWrap: 'nowrap',
|
||||
whiteSpace: 'nowrap'
|
||||
}}
|
||||
>
|
||||
<Dialog
|
||||
sx={dialogStyle}
|
||||
open={confirmFactoryReset}
|
||||
onClose={handleFactoryResetClose}
|
||||
>
|
||||
<DialogTitle>{LL.FACTORY_RESET()}</DialogTitle>
|
||||
<DialogContent dividers>{LL.SYSTEM_FACTORY_TEXT_DIALOG()}</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
startIcon={<CancelIcon />}
|
||||
variant="outlined"
|
||||
onClick={handleFactoryResetClose}
|
||||
color="secondary"
|
||||
>
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<SettingsBackupRestoreIcon />}
|
||||
variant="outlined"
|
||||
onClick={handleFactoryResetClick}
|
||||
onClick={doFormat}
|
||||
color="error"
|
||||
>
|
||||
{LL.FACTORY_RESET()}
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}, [
|
||||
LL,
|
||||
handleFactoryResetClick,
|
||||
handleFactoryResetClose,
|
||||
doFormat,
|
||||
confirmFactoryReset,
|
||||
restarting
|
||||
]);
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
return restarting ? <SystemMonitor /> : <SectionContent>{content}</SectionContent>;
|
||||
<Divider />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
mt: 2,
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
flexWrap: 'nowrap',
|
||||
whiteSpace: 'nowrap'
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
startIcon={<SettingsBackupRestoreIcon />}
|
||||
variant="outlined"
|
||||
onClick={handleFactoryResetClick}
|
||||
color="error"
|
||||
>
|
||||
{LL.FACTORY_RESET()}
|
||||
</Button>
|
||||
</Box>
|
||||
</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { MenuItem } from '@mui/material';
|
||||
|
||||
export const TIME_ZONES: Record<string, string> = {
|
||||
@@ -472,26 +470,16 @@ export function selectedTimeZone(label: string, format: string) {
|
||||
return TIME_ZONES[label] === format ? label : undefined;
|
||||
}
|
||||
|
||||
// Memoized version for use in components
|
||||
export function useTimeZoneSelectItems() {
|
||||
return useMemo(
|
||||
() =>
|
||||
TIME_ZONE_LABELS.map((label) => (
|
||||
<MenuItem key={label} value={label}>
|
||||
{label}
|
||||
</MenuItem>
|
||||
)),
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
// Fallback export for backward compatibility - now memoized
|
||||
const precomputedTimeZoneItems = TIME_ZONE_LABELS.map((label) => (
|
||||
<MenuItem key={label} value={label}>
|
||||
{label}
|
||||
</MenuItem>
|
||||
));
|
||||
|
||||
export function useTimeZoneSelectItems() {
|
||||
return precomputedTimeZoneItems;
|
||||
}
|
||||
|
||||
export function timeZoneSelectItems() {
|
||||
return precomputedTimeZoneItems;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { memo, useState } from 'react';
|
||||
import {
|
||||
Navigate,
|
||||
Route,
|
||||
@@ -40,26 +40,20 @@ const Network = () => {
|
||||
|
||||
const [selectedNetwork, setSelectedNetwork] = useState<WiFiNetwork>();
|
||||
|
||||
const selectNetwork = useCallback(
|
||||
(network: WiFiNetwork) => {
|
||||
setSelectedNetwork(network);
|
||||
void navigate('/settings/network/settings');
|
||||
},
|
||||
[navigate]
|
||||
);
|
||||
const selectNetwork = (network: WiFiNetwork) => {
|
||||
setSelectedNetwork(network);
|
||||
void navigate('/settings/network/settings');
|
||||
};
|
||||
|
||||
const deselectNetwork = useCallback(() => {
|
||||
const deselectNetwork = () => {
|
||||
setSelectedNetwork(undefined);
|
||||
}, []);
|
||||
};
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
...(selectedNetwork && { selectedNetwork }),
|
||||
selectNetwork,
|
||||
deselectNetwork
|
||||
}),
|
||||
[selectedNetwork, selectNetwork, deselectNetwork]
|
||||
);
|
||||
const contextValue = {
|
||||
...(selectedNetwork && { selectedNetwork }),
|
||||
selectNetwork,
|
||||
deselectNetwork
|
||||
};
|
||||
|
||||
return (
|
||||
<WiFiConnectionContext.Provider value={contextValue}>
|
||||
|
||||
@@ -121,19 +121,19 @@ const NetworkSettings = () => {
|
||||
deselectNetwork();
|
||||
}, [data, saveData, deselectNetwork]);
|
||||
|
||||
const setCancel = useCallback(async () => {
|
||||
const setCancel = async () => {
|
||||
deselectNetwork();
|
||||
await loadData();
|
||||
}, [deselectNetwork, loadData]);
|
||||
};
|
||||
|
||||
const doRestart = useCallback(async () => {
|
||||
const doRestart = async () => {
|
||||
setRestarting(true);
|
||||
await sendAPI({ device: 'system', cmd: 'restart', id: 0 }).catch(
|
||||
(error: Error) => {
|
||||
toast.error(error.message);
|
||||
}
|
||||
);
|
||||
}, [sendAPI]);
|
||||
};
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useCallback, useRef, useState } from 'react';
|
||||
import { memo, useRef, useState } from 'react';
|
||||
|
||||
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
|
||||
import { Button } from '@mui/material';
|
||||
@@ -48,12 +48,12 @@ const WiFiNetworkScanner = () => {
|
||||
}
|
||||
});
|
||||
|
||||
const renderNetworkScanner = useCallback(() => {
|
||||
const renderNetworkScanner = () => {
|
||||
if (!networkList) {
|
||||
return <FormLoader errorMessage={errorMessage || ''} />;
|
||||
}
|
||||
return <WiFiNetworkSelector networkList={networkList} />;
|
||||
}, [networkList, errorMessage]);
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useCallback, useContext } from 'react';
|
||||
import { memo, useContext } from 'react';
|
||||
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import LockOpenIcon from '@mui/icons-material/LockOpen';
|
||||
@@ -63,34 +63,31 @@ const WiFiNetworkSelector = ({ networkList }: { networkList: WiFiNetworkList })
|
||||
|
||||
const wifiConnectionContext = useContext(WiFiConnectionContext);
|
||||
|
||||
const renderNetwork = useCallback(
|
||||
(network: WiFiNetwork) => (
|
||||
<ListItem
|
||||
key={network.bssid}
|
||||
onClick={() => wifiConnectionContext.selectNetwork(network)}
|
||||
>
|
||||
<ListItemAvatar>
|
||||
<Avatar>{isNetworkOpen(network) ? <LockOpenIcon /> : <LockIcon />}</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={network.ssid}
|
||||
secondary={
|
||||
'Security: ' +
|
||||
networkSecurityMode(network) +
|
||||
', Ch: ' +
|
||||
network.channel +
|
||||
', bssid: ' +
|
||||
network.bssid
|
||||
}
|
||||
/>
|
||||
<ListItemIcon>
|
||||
<Badge badgeContent={network.rssi + 'dBm'}>
|
||||
<WifiIcon sx={{ color: networkQualityHighlight(network, theme) }} />
|
||||
</Badge>
|
||||
</ListItemIcon>
|
||||
</ListItem>
|
||||
),
|
||||
[wifiConnectionContext, theme]
|
||||
const renderNetwork = (network: WiFiNetwork) => (
|
||||
<ListItem
|
||||
key={network.bssid}
|
||||
onClick={() => wifiConnectionContext.selectNetwork(network)}
|
||||
>
|
||||
<ListItemAvatar>
|
||||
<Avatar>{isNetworkOpen(network) ? <LockOpenIcon /> : <LockIcon />}</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={network.ssid}
|
||||
secondary={
|
||||
'Security: ' +
|
||||
networkSecurityMode(network) +
|
||||
', Ch: ' +
|
||||
network.channel +
|
||||
', bssid: ' +
|
||||
network.bssid
|
||||
}
|
||||
/>
|
||||
<ListItemIcon>
|
||||
<Badge badgeContent={network.rssi + 'dBm'}>
|
||||
<WifiIcon sx={{ color: networkQualityHighlight(network, theme) }} />
|
||||
</Badge>
|
||||
</ListItemIcon>
|
||||
</ListItem>
|
||||
);
|
||||
|
||||
if (networkList.networks.length === 0) {
|
||||
|
||||
@@ -99,34 +99,28 @@ const ManageUsers = () => {
|
||||
[]
|
||||
);
|
||||
|
||||
const noAdminConfigured = useCallback(
|
||||
() => !data?.users.find((u) => u.admin),
|
||||
[data]
|
||||
);
|
||||
const noAdminConfigured = () => !data?.users.find((u) => u.admin);
|
||||
|
||||
const removeUser = useCallback(
|
||||
(toRemove: UserType) => {
|
||||
if (!data) return;
|
||||
const users = data.users.filter((u) => u.username !== toRemove.username);
|
||||
updateDataValue({ ...data, users });
|
||||
setChanged(changed + 1);
|
||||
},
|
||||
[data, updateDataValue, changed]
|
||||
);
|
||||
const removeUser = (toRemove: UserType) => {
|
||||
if (!data) return;
|
||||
const users = data.users.filter((u) => u.username !== toRemove.username);
|
||||
updateDataValue({ ...data, users });
|
||||
setChanged(changed + 1);
|
||||
};
|
||||
|
||||
const createUser = useCallback(() => {
|
||||
const createUser = () => {
|
||||
setCreating(true);
|
||||
setUser({
|
||||
username: '',
|
||||
password: '',
|
||||
admin: true
|
||||
});
|
||||
}, []);
|
||||
};
|
||||
|
||||
const editUser = useCallback((toEdit: UserType) => {
|
||||
const editUser = (toEdit: UserType) => {
|
||||
setCreating(false);
|
||||
setUser({ ...toEdit });
|
||||
}, []);
|
||||
};
|
||||
|
||||
const cancelEditingUser = useCallback(() => {
|
||||
setUser(undefined);
|
||||
@@ -150,20 +144,20 @@ const ManageUsers = () => {
|
||||
setGeneratingToken(undefined);
|
||||
}, []);
|
||||
|
||||
const generateTokenForUser = useCallback((username: string) => {
|
||||
const generateTokenForUser = (username: string) => {
|
||||
setGeneratingToken(username);
|
||||
}, []);
|
||||
};
|
||||
|
||||
const onSubmit = useCallback(async () => {
|
||||
const onSubmit = async () => {
|
||||
await saveData();
|
||||
await authenticatedContext.refresh();
|
||||
setChanged(0);
|
||||
}, [saveData, authenticatedContext]);
|
||||
};
|
||||
|
||||
const onCancelSubmit = useCallback(async () => {
|
||||
const onCancelSubmit = async () => {
|
||||
await loadData();
|
||||
setChanged(0);
|
||||
}, [loadData]);
|
||||
};
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
@@ -177,15 +171,10 @@ const ManageUsers = () => {
|
||||
admin: boolean;
|
||||
}
|
||||
|
||||
// add id to the type, needed for the table
|
||||
const user_table = useMemo(
|
||||
() =>
|
||||
data.users.map((u) => ({
|
||||
...u,
|
||||
id: u.username
|
||||
})) as UserType2[],
|
||||
[data.users]
|
||||
);
|
||||
const user_table = data.users.map((u) => ({
|
||||
...u,
|
||||
id: u.username
|
||||
})) as UserType2[];
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useMemo } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { Navigate, Route, Routes, matchRoutes, useLocation } from 'react-router';
|
||||
|
||||
import { Tab } from '@mui/material';
|
||||
@@ -15,19 +15,15 @@ const Security = () => {
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
const matchedRoutes = useMemo(
|
||||
() =>
|
||||
matchRoutes(
|
||||
[
|
||||
{
|
||||
path: '/settings/security/settings',
|
||||
element: <ManageUsers />
|
||||
},
|
||||
{ path: '/settings/security/users', element: <SecuritySettings /> }
|
||||
],
|
||||
location
|
||||
),
|
||||
[location]
|
||||
const matchedRoutes = matchRoutes(
|
||||
[
|
||||
{
|
||||
path: '/settings/security/settings',
|
||||
element: <ManageUsers />
|
||||
},
|
||||
{ path: '/settings/security/users', element: <SecuritySettings /> }
|
||||
],
|
||||
location
|
||||
);
|
||||
const routerTab = matchedRoutes?.[0]?.route.path || false;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useCallback, useEffect, useState } from 'react';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
@@ -62,7 +62,7 @@ const User: FC<UserFormProps> = ({
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const validateAndDone = useCallback(async () => {
|
||||
const validateAndDone = async () => {
|
||||
if (user) {
|
||||
try {
|
||||
setFieldErrors(undefined);
|
||||
@@ -72,7 +72,7 @@ const User: FC<UserFormProps> = ({
|
||||
setFieldErrors((error as ValidationError).fieldErrors);
|
||||
}
|
||||
}
|
||||
}, [user, validator, onDoneEditing]);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
|
||||
Reference in New Issue
Block a user