mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 16:29:51 +03:00
translations and finish writeDevice
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useBlocker } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { useInterval } from 'utils';
|
||||
|
||||
import { readCustomEntities, writeCustomEntities } from '../../api/app';
|
||||
import SettingsCustomEntitiesDialog from './CustomEntitiesDialog';
|
||||
@@ -52,17 +53,11 @@ const CustomEntities = () => {
|
||||
initialData: []
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(async () => {
|
||||
if (dialogOpen || numChanges > 0) {
|
||||
return;
|
||||
}
|
||||
await fetchEntities();
|
||||
}, 2000);
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
};
|
||||
}, []);
|
||||
useInterval(() => {
|
||||
if (!dialogOpen && !numChanges) {
|
||||
void fetchEntities();
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
const { send: writeEntities } = useRequest(
|
||||
(data: Entities) => writeCustomEntities(data),
|
||||
@@ -295,7 +290,7 @@ const CustomEntities = () => {
|
||||
<SectionContent>
|
||||
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
||||
<Box mb={2} color="warning.main">
|
||||
<Typography variant="body2">{LL.ENTITIES_HELP_1()}</Typography>
|
||||
<Typography variant="body1">{LL.ENTITIES_HELP_1()}</Typography>
|
||||
</Box>
|
||||
|
||||
{renderEntity()}
|
||||
|
||||
@@ -427,7 +427,7 @@ const Customizations = () => {
|
||||
const renderDeviceList = () => (
|
||||
<>
|
||||
<Box mb={1} color="warning.main">
|
||||
<Typography variant="body2">{LL.CUSTOMIZATIONS_HELP_1()}.</Typography>
|
||||
<Typography variant="body1">{LL.CUSTOMIZATIONS_HELP_1()}.</Typography>
|
||||
</Box>
|
||||
<Box display="flex" flexWrap="wrap" alignItems="center" gap={2}>
|
||||
{rename ? (
|
||||
|
||||
@@ -22,23 +22,29 @@ import { useRequest } from 'alova/client';
|
||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { useInterval } from 'utils';
|
||||
|
||||
import { readDashboard, writeDeviceValue } from '../../api/app';
|
||||
import DeviceIcon from './DeviceIcon';
|
||||
import DashboardDevicesDialog from './DevicesDialog';
|
||||
import { formatValue } from './deviceValue';
|
||||
import { type DashboardItem, type DeviceValue } from './types';
|
||||
import { type DashboardItem, DeviceEntityMask, type DeviceValue } from './types';
|
||||
import { deviceValueItemValidation } from './validators';
|
||||
|
||||
const Dashboard = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
|
||||
useLayoutTitle('Dashboard'); // TODO translate
|
||||
useLayoutTitle(LL.DASHBOARD());
|
||||
|
||||
const [firstLoad, setFirstLoad] = useState<boolean>(true);
|
||||
const [showAll, setShowAll] = useState<boolean>(true);
|
||||
|
||||
const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState<boolean>(false);
|
||||
|
||||
const [selectedDashboardItem, setSelectedDashboardItem] =
|
||||
useState<DashboardItem>();
|
||||
|
||||
const {
|
||||
data,
|
||||
send: fetchDashboard,
|
||||
@@ -55,17 +61,11 @@ const Dashboard = () => {
|
||||
}
|
||||
);
|
||||
|
||||
const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false);
|
||||
const [selectedDeviceValue, setSelectedDeviceValue] = useState<DeviceValue>();
|
||||
|
||||
const deviceValueDialogClose = () => {
|
||||
setDeviceValueDialogOpen(false);
|
||||
void sendDeviceData(selectedDevice);
|
||||
};
|
||||
|
||||
// TODO get this working next
|
||||
const deviceValueDialogSave = async (devicevalue: DeviceValue) => {
|
||||
const id = Number(device_select.state.id);
|
||||
if (!selectedDashboardItem) {
|
||||
return;
|
||||
}
|
||||
const id = selectedDashboardItem.parentNode.id; // this is the parent ID
|
||||
await sendDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v })
|
||||
.then(() => {
|
||||
toast.success(LL.WRITE_CMD_SENT());
|
||||
@@ -73,10 +73,9 @@ const Dashboard = () => {
|
||||
.catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
})
|
||||
.finally(async () => {
|
||||
.finally(() => {
|
||||
setDeviceValueDialogOpen(false);
|
||||
await sendDeviceData(id);
|
||||
setSelectedDeviceValue(undefined);
|
||||
setSelectedDashboardItem(undefined);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -105,7 +104,7 @@ const Dashboard = () => {
|
||||
const tree = useTree(
|
||||
{ nodes: data },
|
||||
{
|
||||
onChange: null
|
||||
onChange: null // not used but needed
|
||||
},
|
||||
{
|
||||
treeIcon: {
|
||||
@@ -118,25 +117,19 @@ const Dashboard = () => {
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
if (deviceValueDialogOpen) {
|
||||
return;
|
||||
}
|
||||
fetchDashboard();
|
||||
}, 2000);
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
};
|
||||
}, []);
|
||||
useInterval(() => {
|
||||
if (!deviceValueDialogOpen) {
|
||||
void fetchDashboard();
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
// auto expand on first load
|
||||
useEffect(() => {
|
||||
if (firstLoad && data.length && !tree.state.ids.length) {
|
||||
if (firstLoad && Array.isArray(data) && data.length && !tree.state.ids.length) {
|
||||
tree.fns.onToggleAll({});
|
||||
setFirstLoad(false);
|
||||
}
|
||||
}, [data]);
|
||||
}, [loading]);
|
||||
|
||||
const showName = (di: DashboardItem) => {
|
||||
if (di.id < 100) {
|
||||
@@ -145,7 +138,7 @@ const Dashboard = () => {
|
||||
return (
|
||||
<>
|
||||
<span style="font-size: 14px">
|
||||
<DeviceIcon type_id={di.t} />
|
||||
<DeviceIcon type_id={di.t ?? 0} />
|
||||
{di.n}
|
||||
</span>
|
||||
<span style={{ color: 'lightblue' }}> ({di.nodes?.length})</span>
|
||||
@@ -153,20 +146,14 @@ const Dashboard = () => {
|
||||
);
|
||||
}
|
||||
}
|
||||
return <span style="color:lightgrey">{di.n}</span>;
|
||||
return <span style="color:lightgrey">{di.dv ? di.dv.id.slice(2) : ''}</span>;
|
||||
};
|
||||
|
||||
const showDeviceValue = (di: DashboardItem) => {
|
||||
// convert di to dv
|
||||
// TODO should we not just use dv?
|
||||
const dv: DeviceValue = {
|
||||
id: ' ' + di.n,
|
||||
v: di.v,
|
||||
u: di.u,
|
||||
c: di.c,
|
||||
l: di.l
|
||||
};
|
||||
setSelectedDeviceValue(dv);
|
||||
const hasMask = (id: string, mask: number) =>
|
||||
(parseInt(id.slice(0, 2), 16) & mask) === mask;
|
||||
|
||||
const editDashboardValue = (di: DashboardItem) => {
|
||||
setSelectedDashboardItem(di);
|
||||
setDeviceValueDialogOpen(true);
|
||||
};
|
||||
|
||||
@@ -185,21 +172,10 @@ const Dashboard = () => {
|
||||
return <FormLoader onRetry={fetchDashboard} errorMessage={error?.message} />;
|
||||
}
|
||||
|
||||
// if (data.length === 0) {
|
||||
// return (
|
||||
// <Typography variant="body2" color="warning">
|
||||
// {/* TODO translate */}
|
||||
// No entities found.
|
||||
// </Typography>
|
||||
// );
|
||||
// }
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography mb={2} variant="body2" color="warning">
|
||||
{/* TODO translate */}
|
||||
The dashboard shows all EMS entities that are marked as favorite, and the
|
||||
sensors.
|
||||
<Typography mb={2} variant="body1" color="warning">
|
||||
{LL.DASHBOARD_1()}
|
||||
</Typography>
|
||||
|
||||
<ToggleButtonGroup
|
||||
@@ -235,9 +211,8 @@ const Dashboard = () => {
|
||||
}}
|
||||
>
|
||||
{!loading && data.length === 0 ? (
|
||||
<Typography variant="body2" color="warning">
|
||||
{/* TODO translate */}
|
||||
No entities found.
|
||||
<Typography variant="subtitle2" color="warning">
|
||||
{LL.NO_DATA()}
|
||||
</Typography>
|
||||
) : (
|
||||
<Table
|
||||
@@ -257,19 +232,21 @@ const Dashboard = () => {
|
||||
)}
|
||||
<Cell pinRight>
|
||||
<span style={{ color: 'lightgrey' }}>
|
||||
{formatValue(LL, di.v, di.u)}
|
||||
{di.dv && formatValue(LL, di.dv.v, di.dv.u)}
|
||||
</span>
|
||||
</Cell>
|
||||
|
||||
<Cell stiff>
|
||||
{me.admin && di.c && (
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => showDeviceValue(di)}
|
||||
>
|
||||
<EditIcon color="primary" sx={{ fontSize: 16 }} />
|
||||
</IconButton>
|
||||
)}
|
||||
{me.admin &&
|
||||
di.dv?.c &&
|
||||
!hasMask(di.dv.id, DeviceEntityMask.DV_READONLY) && (
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => editDashboardValue(di)}
|
||||
>
|
||||
<EditIcon color="primary" sx={{ fontSize: 16 }} />
|
||||
</IconButton>
|
||||
)}
|
||||
</Cell>
|
||||
</Row>
|
||||
))}
|
||||
@@ -286,14 +263,14 @@ const Dashboard = () => {
|
||||
return (
|
||||
<SectionContent>
|
||||
{renderContent()}
|
||||
{selectedDeviceValue && (
|
||||
{selectedDashboardItem && selectedDashboardItem.dv && (
|
||||
<DashboardDevicesDialog
|
||||
open={deviceValueDialogOpen}
|
||||
onClose={deviceValueDialogClose}
|
||||
onClose={() => setDeviceValueDialogOpen(false)}
|
||||
onSave={deviceValueDialogSave}
|
||||
selectedItem={selectedDeviceValue}
|
||||
selectedItem={selectedDashboardItem.dv}
|
||||
writeable={true}
|
||||
validator={deviceValueItemValidation(selectedDeviceValue)}
|
||||
validator={deviceValueItemValidation(selectedDashboardItem.dv)}
|
||||
progress={submitting}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -60,6 +60,7 @@ import { useRequest } from 'alova/client';
|
||||
import { MessageBox, SectionContent, useLayoutTitle } from 'components';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { useInterval } from 'utils';
|
||||
|
||||
import { readCoreData, readDeviceData, writeDeviceValue } from '../../api/app';
|
||||
import DeviceIcon from './DeviceIcon';
|
||||
@@ -77,7 +78,7 @@ const Devices = () => {
|
||||
const [selectedDeviceValue, setSelectedDeviceValue] = useState<DeviceValue>();
|
||||
const [onlyFav, setOnlyFav] = useState(false);
|
||||
const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false);
|
||||
const [showDeviceInfo, setShowDeviceInfo] = useState<boolean>(false);
|
||||
const [showDeviceInfo, setShowDeviceInfo] = useState(false);
|
||||
const [selectedDevice, setSelectedDevice] = useState<number>();
|
||||
|
||||
const navigate = useNavigate();
|
||||
@@ -418,17 +419,11 @@ const Devices = () => {
|
||||
downloadBlob(new Blob([csvData], { type: 'text/csv;charset:utf-8' }));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
if (deviceValueDialogOpen) {
|
||||
return;
|
||||
}
|
||||
useInterval(() => {
|
||||
if (!deviceValueDialogOpen) {
|
||||
selectedDevice ? void sendDeviceData(selectedDevice) : void sendCoreData();
|
||||
}, 2000);
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
const deviceValueDialogSave = async (devicevalue: DeviceValue) => {
|
||||
const id = Number(device_select.state.id);
|
||||
|
||||
@@ -24,7 +24,7 @@ import { useRequest } from 'alova/client';
|
||||
import { SectionContent, useLayoutTitle } from 'components';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { saveFile } from 'utils/file';
|
||||
import { saveFile } from 'utils';
|
||||
|
||||
import { API, callAction } from '../../api/app';
|
||||
import type { APIcall } from './types';
|
||||
@@ -147,7 +147,7 @@ const Help = () => {
|
||||
)}
|
||||
|
||||
<Box p={2} color="warning.main">
|
||||
<Typography mb={1} variant="body2">
|
||||
<Typography mb={1} variant="body1">
|
||||
{LL.HELP_INFORMATION_4()}
|
||||
</Typography>
|
||||
<Button
|
||||
|
||||
@@ -175,7 +175,7 @@ const Modules = () => {
|
||||
return (
|
||||
<>
|
||||
<Box mb={2} color="warning.main">
|
||||
<Typography variant="body2">{LL.MODULES_DESCRIPTION()}</Typography>
|
||||
<Typography variant="body1">{LL.MODULES_DESCRIPTION()}</Typography>
|
||||
</Box>
|
||||
<Table
|
||||
data={{ nodes: modules }}
|
||||
|
||||
@@ -318,7 +318,7 @@ const Scheduler = () => {
|
||||
<SectionContent>
|
||||
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
||||
<Box mb={2} color="warning.main">
|
||||
<Typography variant="body2">{LL.SCHEDULER_HELP_1()}</Typography>
|
||||
<Typography variant="body1">{LL.SCHEDULER_HELP_1()}</Typography>
|
||||
</Box>
|
||||
{renderSchedule()}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ const Sensors = () => {
|
||||
analog_enabled: false,
|
||||
platform: 'ESP32'
|
||||
},
|
||||
pollingTime: 2000
|
||||
pollingTime: 3000
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -116,14 +116,10 @@ export interface CoreData {
|
||||
|
||||
export interface DashboardItem {
|
||||
id: number; // unique index
|
||||
n: string; // name
|
||||
v?: unknown; // value, optional
|
||||
u: number; // uom
|
||||
t: number; // type from DeviceType
|
||||
c?: string; // command, optional
|
||||
l?: string[]; // list, optional
|
||||
h?: string; // help text, optional
|
||||
nodes?: DashboardItem[]; // nodes, optional
|
||||
t?: number; // type from DeviceType
|
||||
n?: string; // name
|
||||
dv?: DeviceValue;
|
||||
nodes?: DashboardItem[]; // children nodes, optional
|
||||
}
|
||||
|
||||
export interface DashboardData {
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { saveFile } from 'utils/file';
|
||||
import { saveFile } from 'utils';
|
||||
|
||||
const DownloadUpload = () => {
|
||||
const { LL } = useI18nContext();
|
||||
@@ -221,7 +221,7 @@ const DownloadUpload = () => {
|
||||
{LL.DOWNLOAD(0)}
|
||||
</Typography>
|
||||
|
||||
<Typography mb={1} variant="body2" color="warning">
|
||||
<Typography mb={1} variant="body1" color="warning">
|
||||
{LL.DOWNLOAD_SETTINGS_TEXT()}
|
||||
</Typography>
|
||||
<Grid container spacing={1}>
|
||||
@@ -269,7 +269,7 @@ const DownloadUpload = () => {
|
||||
</Typography>
|
||||
|
||||
<Box color="warning.main" sx={{ pb: 2 }}>
|
||||
<Typography variant="body2">{LL.UPLOAD_TEXT()}</Typography>
|
||||
<Typography variant="body1">{LL.UPLOAD_TEXT()}</Typography>
|
||||
</Box>
|
||||
|
||||
<SingleUpload doRestart={doRestart} />
|
||||
|
||||
@@ -38,7 +38,7 @@ const APStatus = () => {
|
||||
data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useAutoRequest(APApi.readAPStatus, { pollingTime: 5000 });
|
||||
} = useAutoRequest(APApi.readAPStatus, { pollingTime: 3000 });
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.STATUS_OF(LL.ACCESS_POINT(0)));
|
||||
|
||||
@@ -21,7 +21,7 @@ const SystemActivity = () => {
|
||||
data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useAutoRequest(readActivity, { pollingTime: 2000 });
|
||||
} = useAutoRequest(readActivity, { pollingTime: 3000 });
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ const HardwareStatus = () => {
|
||||
data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useAutoRequest(SystemApi.readSystemStatus, { pollingTime: 2000 });
|
||||
} = useAutoRequest(SystemApi.readSystemStatus, { pollingTime: 3000 });
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
|
||||
@@ -58,7 +58,7 @@ const MqttStatus = () => {
|
||||
data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useAutoRequest(MqttApi.readMqttStatus, { pollingTime: 5000 });
|
||||
} = useAutoRequest(MqttApi.readMqttStatus, { pollingTime: 3000 });
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.STATUS_OF('MQTT'));
|
||||
|
||||
@@ -40,7 +40,7 @@ const NTPStatus = () => {
|
||||
data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useAutoRequest(NTPApi.readNTPStatus, { pollingTime: 5000 });
|
||||
} = useAutoRequest(NTPApi.readNTPStatus, { pollingTime: 3000 });
|
||||
|
||||
const [localTime, setLocalTime] = useState<string>('');
|
||||
const [settingTime, setSettingTime] = useState<boolean>(false);
|
||||
|
||||
@@ -85,7 +85,7 @@ const NetworkStatus = () => {
|
||||
data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useAutoRequest(NetworkApi.readNetworkStatus, { pollingTime: 5000 });
|
||||
} = useAutoRequest(NetworkApi.readNetworkStatus, { pollingTime: 3000 });
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.STATUS_OF(LL.NETWORK(1)));
|
||||
|
||||
@@ -28,7 +28,7 @@ const RestartMonitor = () => {
|
||||
initialData: { status: 'Getting ready...' },
|
||||
async middleware(_, next) {
|
||||
if (count++ >= 1) {
|
||||
// skip first request (1 seconds) to allow AsyncWS to send its response
|
||||
// skip first request (1 second) to allow AsyncWS to send its response
|
||||
await next();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ const SystemStatus = () => {
|
||||
error
|
||||
} = useAutoRequest(readSystemStatus, {
|
||||
initialData: [],
|
||||
pollingTime: 5000,
|
||||
pollingTime: 3000,
|
||||
async middleware(_, next) {
|
||||
if (!restarting) {
|
||||
await next();
|
||||
|
||||
Reference in New Issue
Block a user