This commit is contained in:
proddy
2024-08-08 12:39:48 +02:00
parent dc53ff42f6
commit 3481a879c2
59 changed files with 259 additions and 453 deletions

View File

@@ -1,5 +1,4 @@
import { useCallback, useState } from 'react';
import type { FC } from 'react';
import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify';
@@ -30,13 +29,13 @@ import {
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api';
import SettingsCustomEntitiesDialog from './CustomEntitiesDialog';
import { readCustomEntities, writeCustomEntities } from './api';
import { DeviceValueTypeNames, DeviceValueUOM_s } from './types';
import type { Entities, EntityItem } from './types';
import { entityItemValidation } from './validators';
const CustomEntities: FC = () => {
const CustomEntities = () => {
const { LL } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0);
@@ -50,13 +49,12 @@ const CustomEntities: FC = () => {
data: entities,
send: fetchEntities,
error
} = useRequest(EMSESP.readCustomEntities, {
initialData: [],
force: true
} = useRequest(readCustomEntities, {
initialData: []
});
const { send: writeEntities } = useRequest(
(data: Entities) => EMSESP.writeCustomEntities(data),
(data: Entities) => writeCustomEntities(data),
{ immediate: false }
);
@@ -182,7 +180,7 @@ const CustomEntities: FC = () => {
const onDialogSave = (updatedItem: EntityItem) => {
setDialogOpen(false);
updateState('entities', (data: EntityItem[]) => {
void updateState(readCustomEntities(), (data: EntityItem[]) => {
const new_data = creating
? [
...data.filter((ei) => creating || ei.o_id !== updatedItem.o_id),

View File

@@ -291,7 +291,11 @@ const CustomEntitiesDialog = ({
fullWidth
margin="normal"
type="number"
inputProps={{ min: '1', max: String(256 - editItem.offset), step: '1' }}
inputProps={{
min: '1',
max: String(256 - editItem.offset),
step: '1'
}}
/>
</Grid>
)}

View File

@@ -1,5 +1,4 @@
import { useCallback, useEffect, useState } from 'react';
import type { FC } from 'react';
import { useBlocker, useLocation } from 'react-router-dom';
import { toast } from 'react-toastify';
@@ -52,7 +51,7 @@ import {
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api';
import SettingsCustomizationDialog from './CustomizationDialog';
import SettingsCustomizationsDialog from './CustomizationsDialog';
import EntityMaskToggle from './EntityMaskToggle';
import OptionIcon from './OptionIcon';
import { DeviceEntityMask } from './types';
@@ -60,7 +59,7 @@ import type { DeviceEntity, DeviceShort } from './types';
export const APIURL = window.location.origin + '/api/';
const Customization: FC = () => {
const Customizations = () => {
const { LL } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0);
@@ -106,13 +105,15 @@ const Customization: FC = () => {
}
);
const { send: readDeviceEntities, onSuccess: onSuccess } = useRequest(
const { send: readDeviceEntities } = useRequest(
(data: number) => EMSESP.readDeviceEntities(data),
{
initialData: [],
immediate: false
}
);
).onSuccess((event) => {
setOriginalSettings(event.data);
});
const setOriginalSettings = (data: DeviceEntity[]) => {
setDeviceEntities(
@@ -126,10 +127,6 @@ const Customization: FC = () => {
);
};
onSuccess((event) => {
setOriginalSettings(event.data);
});
const { send: restartCommand } = useRequest(SystemApi.restart(), {
immediate: false
});
@@ -727,7 +724,7 @@ const Customization: FC = () => {
{blocker ? <BlockNavigation blocker={blocker} /> : null}
{restarting ? <RestartMonitor /> : renderContent()}
{selectedDeviceEntity && (
<SettingsCustomizationDialog
<SettingsCustomizationsDialog
open={dialogOpen}
onClose={onDialogClose}
onSave={onDialogSave}
@@ -738,4 +735,4 @@ const Customization: FC = () => {
);
};
export default Customization;
export default Customizations;

View File

@@ -23,19 +23,19 @@ import EntityMaskToggle from './EntityMaskToggle';
import { DeviceEntityMask } from './types';
import type { DeviceEntity } from './types';
interface SettingsCustomizationDialogProps {
interface SettingsCustomizationsDialogProps {
open: boolean;
onClose: () => void;
onSave: (di: DeviceEntity) => void;
selectedItem: DeviceEntity;
}
const CustomizationDialog = ({
const CustomizationsDialog = ({
open,
onClose,
onSave,
selectedItem
}: SettingsCustomizationDialogProps) => {
}: SettingsCustomizationsDialogProps) => {
const { LL } = useI18nContext();
const [editItem, setEditItem] = useState<DeviceEntity>(selectedItem);
const [error, setError] = useState<boolean>(false);
@@ -175,4 +175,4 @@ const CustomizationDialog = ({
);
};
export default CustomizationDialog;
export default CustomizationsDialog;

View File

@@ -1,4 +1,3 @@
import type { FC } from 'react';
import { AiOutlineAlert, AiOutlineControl, AiOutlineGateway } from 'react-icons/ai';
import { CgSmartHomeBoiler } from 'react-icons/cg';
import { FaSolarPanel } from 'react-icons/fa';
@@ -16,11 +15,7 @@ import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
import { DeviceType } from './types';
interface DeviceIconProps {
type_id: number;
}
const DeviceIcon: FC<DeviceIconProps> = ({ type_id }) => {
export default function DeviceIcon({ type_id }) {
switch (type_id as DeviceType) {
case DeviceType.TEMPERATURESENSOR:
case DeviceType.ANALOGSENSOR:
@@ -60,6 +55,4 @@ const DeviceIcon: FC<DeviceIconProps> = ({ type_id }) => {
default:
return null;
}
};
export default DeviceIcon;
}

View File

@@ -5,7 +5,6 @@ import {
useLayoutEffect,
useState
} from 'react';
import type { FC } from 'react';
import { IconContext } from 'react-icons';
import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify';
@@ -70,7 +69,7 @@ import { DeviceEntityMask, DeviceType, DeviceValueUOM_s } from './types';
import type { Device, DeviceValue } from './types';
import { deviceValueItemValidation } from './validators';
const Devices: FC = () => {
const Devices = () => {
const { LL } = useI18nContext();
const { me } = useContext(AuthenticatedContext);

View File

@@ -1,4 +1,3 @@
import type { FC } from 'react';
import { toast } from 'react-toastify';
import CommentIcon from '@mui/icons-material/CommentTwoTone';
@@ -21,27 +20,19 @@ import {
import * as SystemApi from 'api/system';
import * as EMSESP from 'app/main/api';
import { useAutoRequest, useRequest } from 'alova/client';
import { useRequest } from 'alova/client';
import { SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { APIcall } from './types';
const Help: FC = () => {
const Help = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.HELP_OF(''));
const { send: getAPI, onSuccess: onGetAPI } = useRequest(
(data: APIcall) => EMSESP.API(data),
{
immediate: false
}
);
// TODO check useAutoRequest - https://alova.js.org/tutorial/client/strategy/use-auto-request/#basic-usage
const { data, loading } = useAutoRequest(SystemApi.readSystemStatus);
onGetAPI((event) => {
const { send: getAPI } = useRequest((data: APIcall) => EMSESP.API(data), {
immediate: false
}).onSuccess((event) => {
const anchor = document.createElement('a');
anchor.href = URL.createObjectURL(
new Blob([JSON.stringify(event.data, null, 2)], {
@@ -56,14 +47,16 @@ const Help: FC = () => {
toast.info(LL.DOWNLOAD_SUCCESSFUL());
});
const { data, loading } = useRequest(SystemApi.readSystemStatus);
const callAPI = async (device: string, entity: string) => {
await getAPI({ device, entity, id: 0 }).catch((error: Error) => {
toast.error(error.message);
});
};
// TODO remove debug testing useRequest preact hook
console.log('loading: ' + loading + ' data2: ' + data);
if (loading) {
return <div>Loading...</div>;
}

View File

@@ -1,5 +1,4 @@
import { useCallback, useState } from 'react';
import type { FC } from 'react';
import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify';
@@ -8,8 +7,6 @@ import CircleIcon from '@mui/icons-material/Circle';
import WarningIcon from '@mui/icons-material/Warning';
import { Box, Button, Typography } from '@mui/material';
import { alovaInstance } from 'api/endpoints';
import {
Body,
Cell,
@@ -30,11 +27,11 @@ import {
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api';
import ModulesDialog from './ModulesDialog';
import { readModules, writeModules } from './api';
import type { ModuleItem, Modules } from './types';
const Modules: FC = () => {
const Modules = () => {
const { LL } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0);
@@ -46,13 +43,12 @@ const Modules: FC = () => {
data: modules,
send: fetchModules,
error
} = useRequest(EMSESP.readModules, {
} = useRequest(readModules, {
initialData: []
});
const { send: writeModules } = useRequest(
(data: { key: string; enabled: boolean; license: string }) =>
EMSESP.writeModules(data),
const { send: updateModules } = useRequest(
(data: { key: string; enabled: boolean; license: string }) => writeModules(data),
{
immediate: false
}
@@ -124,23 +120,18 @@ const Modules: FC = () => {
return mi.enabled !== mi.o_enabled || mi.license !== mi.o_license;
}
// TODO example of how to use updateState
// TODO see https://alova.js.org/api/states/#updatestate
const updateModuleItem = (updatedItem: ModuleItem) => {
updateState<ModuleItem[]>(
[alovaInstance.snapshots.match('modules', true)] as any,
(data: ModuleItem[]) => {
const new_data = data.map((mi) =>
mi.id === updatedItem.id ? { ...mi, ...updatedItem } : mi
);
setNumChanges(new_data.filter((mi) => hasModulesChanged(mi)).length);
return new_data;
}
);
void updateState(readModules(), (data: ModuleItem[]) => {
const new_data = data.map((mi) =>
mi.id === updatedItem.id ? { ...mi, ...updatedItem } : mi
);
setNumChanges(new_data.filter((mi) => hasModulesChanged(mi)).length);
return new_data;
});
};
const saveModules = async () => {
await writeModules({
await updateModules({
modules: modules.map((condensed_mi) => ({
key: condensed_mi.key,
enabled: condensed_mi.enabled,

View File

@@ -1,5 +1,3 @@
import type { FC } from 'react';
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
@@ -32,18 +30,11 @@ const OPTION_ICONS: {
favorite: [StarIcon, StarOutlineIcon]
};
interface OptionIconProps {
type: OptionType;
isSet: boolean;
}
const OptionIcon: FC<OptionIconProps> = ({ type, isSet }) => {
export default function OptionIcon({ type, isSet }) {
const Icon = OPTION_ICONS[type][isSet ? 0 : 1];
return isSet ? (
<Icon color="primary" sx={{ fontSize: 16, verticalAlign: 'middle' }} />
) : (
<Icon sx={{ fontSize: 16, verticalAlign: 'middle' }} />
);
};
export default OptionIcon;
}

View File

@@ -1,5 +1,4 @@
import { useCallback, useEffect, useState } from 'react';
import type { FC } from 'react';
import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify';
@@ -29,13 +28,13 @@ import {
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api';
import SettingsSchedulerDialog from './SchedulerDialog';
import { readSchedule, writeSchedule } from './api';
import { ScheduleFlag } from './types';
import type { Schedule, ScheduleItem } from './types';
import { schedulerItemValidation } from './validators';
const Scheduler: FC = () => {
const Scheduler = () => {
const { LL, locale } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0);
@@ -48,13 +47,12 @@ const Scheduler: FC = () => {
data: schedule,
send: fetchSchedule,
error
} = useRequest(EMSESP.readSchedule, {
initialData: [],
force: true
} = useRequest(readSchedule, {
initialData: []
});
const { send: writeSchedule } = useRequest(
(data: Schedule) => EMSESP.writeSchedule(data),
const { send: updateSchedule } = useRequest(
(data: Schedule) => writeSchedule(data),
{
immediate: false
}
@@ -131,7 +129,7 @@ const Scheduler: FC = () => {
});
const saveSchedule = async () => {
await writeSchedule({
await updateSchedule({
schedule: schedule
.filter((si) => !si.deleted)
.map((condensed_si) => ({
@@ -177,7 +175,7 @@ const Scheduler: FC = () => {
const onDialogSave = (updatedItem: ScheduleItem) => {
setDialogOpen(false);
updateState('schedule', (data: ScheduleItem[]) => {
void updateState(readSchedule(), (data: ScheduleItem[]) => {
const new_data = creating
? [
...data.filter((si) => creating || si.o_id !== updatedItem.o_id),

View File

@@ -208,7 +208,7 @@ const SchedulerDialog = ({
scheduleType === ScheduleFlag.SCHEDULE_ONCHANGE ? 'primary' : 'grey'
}
>
{LL.ONCHANGE(0)}
{LL.ONCHANGE()}
</Typography>
</ToggleButton>
<ToggleButton value={ScheduleFlag.SCHEDULE_CONDITION}>
@@ -218,7 +218,7 @@ const SchedulerDialog = ({
scheduleType === ScheduleFlag.SCHEDULE_CONDITION ? 'primary' : 'grey'
}
>
{LL.CONDITION(0)}
{LL.CONDITION()}
</Typography>
</ToggleButton>
<ToggleButton value={ScheduleFlag.SCHEDULE_IMMEDIATE}>
@@ -228,7 +228,7 @@ const SchedulerDialog = ({
scheduleType === ScheduleFlag.SCHEDULE_IMMEDIATE ? 'primary' : 'grey'
}
>
{LL.IMMEDIATE(0)}
{LL.IMMEDIATE()}
</Typography>
</ToggleButton>
</ToggleButtonGroup>
@@ -282,7 +282,7 @@ const SchedulerDialog = ({
</Grid>
<Grid container>
{scheduleType === ScheduleFlag.SCHEDULE_DAY ||
scheduleType === ScheduleFlag.SCHEDULE_TIMER ? (
scheduleType === ScheduleFlag.SCHEDULE_TIMER ? (
<>
<TextField
name="time"
@@ -311,10 +311,10 @@ const SchedulerDialog = ({
name="time"
label={
scheduleType === ScheduleFlag.SCHEDULE_CONDITION
? LL.CONDITION(1)
? LL.CONDITION()
: scheduleType === ScheduleFlag.SCHEDULE_ONCHANGE
? LL.ONCHANGE(1)
: LL.IMMEDIATE(1)
? LL.ONCHANGE()
: LL.IMMEDIATE()
}
multiline
fullWidth

View File

@@ -1,5 +1,4 @@
import { useContext, useEffect, useState } from 'react';
import type { FC } from 'react';
import { toast } from 'react-toastify';
import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined';
@@ -46,7 +45,7 @@ import {
temperatureSensorItemValidation
} from './validators';
const Sensors: FC = () => {
const Sensors = () => {
const { LL } = useI18nContext();
const { me } = useContext(AuthenticatedContext);

View File

@@ -88,7 +88,6 @@ export const writeDeviceName = (data: { id: number; name: string }) =>
// SettingsScheduler
export const readSchedule = () =>
alovaInstance.Get<ScheduleItem[]>('/rest/schedule', {
name: 'schedule',
transform(data) {
return (data as Schedule).schedule.map((si: ScheduleItem) => ({
...si,
@@ -109,7 +108,6 @@ export const writeSchedule = (data: Schedule) =>
// Modules
export const readModules = () =>
alovaInstance.Get<ModuleItem[]>('/rest/modules', {
name: 'modules',
transform(data) {
return (data as Modules).modules.map((mi: ModuleItem) => ({
...mi,
@@ -127,7 +125,6 @@ export const writeModules = (data: {
// SettingsEntities
export const readCustomEntities = () =>
alovaInstance.Get<EntityItem[]>('/rest/customEntities', {
name: 'entities',
transform(data) {
return (data as Entities).entities.map((ei: EntityItem) => ({
...ei,