This commit is contained in:
MichaelDvP
2024-10-14 13:13:32 +02:00
69 changed files with 1696 additions and 721 deletions

View File

@@ -16,7 +16,15 @@ export default tseslint.config(
}
},
{
ignores: ['dist/*', 'build/*', '*.js', '**/*.cjs', '**/unpack.ts', 'i18n*.*']
ignores: [
'dist/*',
'*.mjs',
'build/*',
'*.js',
'**/*.cjs',
'**/unpack.ts',
'i18n*.*'
]
},
{
rules: {

View File

@@ -21,13 +21,13 @@
"lint": "eslint . --fix"
},
"dependencies": {
"@alova/adapter-xhr": "2.0.7",
"@alova/adapter-xhr": "2.0.8",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@mui/icons-material": "^6.1.2",
"@mui/material": "^6.1.2",
"@mui/icons-material": "^6.1.3",
"@mui/material": "^6.1.3",
"@table-library/react-table-library": "4.1.7",
"alova": "3.0.17",
"alova": "3.1.0",
"async-validator": "^4.2.5",
"jwt-decode": "^4.0.0",
"mime-types": "^2.1.35",
@@ -35,21 +35,21 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.3.0",
"react-router-dom": "^6.26.2",
"react-toastify": "^10.0.5",
"react-router-dom": "^6.27.0",
"react-toastify": "^10.0.6",
"typesafe-i18n": "^5.26.2",
"typescript": "^5.6.2"
"typescript": "^5.6.3"
},
"devDependencies": {
"@babel/core": "^7.25.7",
"@babel/core": "^7.25.8",
"@eslint/js": "^9.12.0",
"@preact/compat": "^18.3.1",
"@preact/preset-vite": "^2.9.1",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/formidable": "^3",
"@types/node": "^22.7.4",
"@types/node": "^22.7.5",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0",
"@types/react-dom": "^18.3.1",
"@types/react-router-dom": "^5.3.3",
"concurrently": "^9.0.1",
"eslint": "^9.12.0",
@@ -58,7 +58,7 @@
"prettier": "^3.3.3",
"rollup-plugin-visualizer": "^5.12.0",
"terser": "^5.34.1",
"typescript-eslint": "8.8.0",
"typescript-eslint": "8.8.1",
"vite": "^5.4.8",
"vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^5.0.1"

View File

@@ -25,7 +25,7 @@ const App = () => {
<CustomTheme>
<AppRouting />
<ToastContainer
position="bottom-right"
position="bottom-left"
autoClose={3000}
hideProgressBar={false}
newestOnTop={false}

View File

@@ -3,6 +3,7 @@ import { Navigate, Route, Routes } from 'react-router-dom';
import CustomEntities from 'app/main/CustomEntities';
import Customizations from 'app/main/Customizations';
import Dashboard from 'app/main/Dashboard';
import Devices from 'app/main/Devices';
import Help from 'app/main/Help';
import Modules from 'app/main/Modules';
@@ -32,6 +33,7 @@ const AuthenticatedRouting = () => {
return (
<Layout>
<Routes>
<Route path="/dashboard/*" element={<Dashboard />} />
<Route path="/devices/*" element={<Devices />} />
<Route path="/sensors/*" element={<Sensors />} />
<Route path="/status/*" element={<Status />} />

View File

@@ -5,6 +5,7 @@ import type {
Action,
Activity,
CoreData,
DashboardItem,
DeviceData,
DeviceEntity,
Entities,
@@ -19,7 +20,13 @@ import type {
WriteTemperatureSensor
} from '../app/main/types';
// DashboardDevices
// Dashboard
export const readDashboard = () =>
alovaInstance.Get<DashboardItem[]>('/rest/dashboardData', {
responseType: 'arraybuffer' // uses msgpack
});
// Devices
export const readCoreData = () => alovaInstance.Get<CoreData>(`/rest/coreData`);
export const readDeviceData = (id: number) =>
alovaInstance.Get<DeviceData>('/rest/deviceData', {

View File

@@ -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),
@@ -130,15 +125,10 @@ const CustomEntities = () => {
position: relative;
cursor: pointer;
.td {
border-top: 1px solid #565656;
border-bottom: 1px solid #565656;
}
&:hover .td {
border-top: 1px solid #177ac9;
border-bottom: 1px solid #177ac9;
}
&:nth-of-type(odd) .td {
background-color: #303030;
background-color: #177ac9;
}
`
});
@@ -295,7 +285,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()}

View File

@@ -190,10 +190,7 @@ const Customizations = () => {
}
&:hover .td {
border-top: 1px solid #177ac9;
border-bottom: 1px solid #177ac9;
}
&:nth-of-type(odd) .td {
background-color: #303030;
background-color: #177ac9;
}
`,
Cell: `
@@ -427,7 +424,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 ? (

View File

@@ -0,0 +1,363 @@
import { useContext, useEffect, useState } from 'react';
import { IconContext } from 'react-icons/lib';
import { toast } from 'react-toastify';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import EditIcon from '@mui/icons-material/Edit';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import UnfoldLessIcon from '@mui/icons-material/UnfoldLess';
import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
import {
Box,
IconButton,
ToggleButton,
ToggleButtonGroup,
Tooltip,
Typography
} from '@mui/material';
import Grid from '@mui/material/Grid2';
import { Body, Cell, Row, Table } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme';
import { CellTree, useTree } from '@table-library/react-table-library/tree';
import { useRequest } from 'alova/client';
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import { useInterval, usePersistState } from 'utils';
import { readDashboard, writeDeviceValue } from '../../api/app';
import DeviceIcon from './DeviceIcon';
import DevicesDialog from './DevicesDialog';
import { formatValue } from './deviceValue';
import {
type DashboardItem,
DeviceEntityMask,
DeviceType,
type DeviceValue
} from './types';
import { deviceValueItemValidation } from './validators';
const Dashboard = () => {
const { LL } = useI18nContext();
const { me } = useContext(AuthenticatedContext);
useLayoutTitle(LL.DASHBOARD());
const [showAll, setShowAll] = usePersistState(true, 'showAll');
const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState<boolean>(false);
const [parentNodes, setParentNodes] = useState<number>(0);
const [selectedDashboardItem, setSelectedDashboardItem] =
useState<DashboardItem>();
const {
data,
send: fetchDashboard,
error,
loading
} = useRequest(readDashboard, {
initialData: []
}).onSuccess((event) => {
if (event.data.length !== parentNodes) {
setParentNodes(event.data.length); // count number of parents/devices
}
});
const { loading: submitting, send: sendDeviceValue } = useRequest(
(data: { id: number; c: string; v: unknown }) => writeDeviceValue(data),
{
immediate: false
}
);
const deviceValueDialogSave = async (devicevalue: DeviceValue) => {
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());
})
.catch((error: Error) => {
toast.error(error.message);
})
.finally(() => {
setDeviceValueDialogOpen(false);
setSelectedDashboardItem(undefined);
});
};
const dashboard_theme = useTheme({
Table: `
--data-table-library_grid-template-columns: minmax(80px, auto) 120px 32px;
`,
BaseRow: `
font-size: 14px;
.td {
height: 28px;
}
`,
Row: `
cursor: pointer;
background-color: #1e1e1e;
&:hover .td {
background-color: #177ac9;
}
`,
BaseCell: `
&:nth-of-type(2) {
text-align: right;
}
&:nth-of-type(3) {
text-align: right;
}
`
});
const tree = useTree(
{ nodes: data },
{
onChange: undefined // not used but needed
},
{
treeIcon: {
margin: '4px',
iconDefault: null,
iconRight: (
<ChevronRightIcon
sx={{ fontSize: 16, verticalAlign: 'middle' }}
color="info"
/>
),
iconDown: (
<ExpandMoreIcon
sx={{ fontSize: 16, verticalAlign: 'middle' }}
color="info"
/>
)
},
indentation: 45
}
);
useInterval(() => {
if (!deviceValueDialogOpen) {
void fetchDashboard();
}
}, 3000);
useEffect(() => {
showAll
? tree.fns.onAddAll(data.map((item: DashboardItem) => item.id)) // expand tree
: tree.fns.onRemoveAll(); // collapse tree
}, [parentNodes]);
const showType = (n?: string, t?: number) => {
// if we have a name show it
if (n) {
return n;
}
if (t) {
// otherwise pick translation based on type
switch (t) {
case DeviceType.CUSTOM:
return LL.CUSTOM_ENTITIES(0);
case DeviceType.ANALOGSENSOR:
return LL.ANALOG_SENSORS();
case DeviceType.TEMPERATURESENSOR:
return LL.TEMP_SENSORS();
case DeviceType.SCHEDULER:
return LL.SCHEDULER();
default:
break;
}
}
return '';
};
const showName = (di: DashboardItem) => {
if (di.id < 100) {
// if its a device (parent node) and has entities
if (di.nodes?.length) {
return (
<>
<span style="font-size: 14px">
<DeviceIcon type_id={di.t ?? 0} />
&nbsp;&nbsp;{showType(di.n, di.t)}
</span>
<span style={{ color: 'lightblue' }}>&nbsp;({di.nodes?.length})</span>
</>
);
}
}
if (di.dv) {
return <span style="color:lightgrey">{di.dv.id.slice(2)}</span>;
}
};
const hasMask = (id: string, mask: number) =>
(parseInt(id.slice(0, 2), 16) & mask) === mask;
const editDashboardValue = (di: DashboardItem) => {
if (me.admin && di.dv?.c) {
setSelectedDashboardItem(di);
setDeviceValueDialogOpen(true);
}
};
const handleShowAll = (
event: React.MouseEvent<HTMLElement>,
toggle: boolean | null
) => {
if (toggle !== null) {
tree.fns.onToggleAll({});
setShowAll(toggle);
}
};
const renderContent = () => {
if (!data) {
return <FormLoader onRetry={fetchDashboard} errorMessage={error?.message} />;
}
return (
<>
<Box
sx={{
backgroundColor: 'black',
pt: 1,
pl: 2
}}
>
<Grid container spacing={0} justifyContent="flex-start">
<Grid size={11}>
<Typography mb={2} variant="body1" color="warning">
{LL.DASHBOARD_1()}
</Typography>
</Grid>
<Grid size={1} alignItems="end">
<ToggleButtonGroup
color="primary"
size="small"
value={showAll}
exclusive
onChange={handleShowAll}
>
<ToggleButton value={true}>
<UnfoldMoreIcon sx={{ fontSize: 14 }} />
</ToggleButton>
<ToggleButton value={false}>
<UnfoldLessIcon sx={{ fontSize: 14 }} />
</ToggleButton>
</ToggleButtonGroup>
</Grid>
</Grid>
</Box>
<Box
padding={1}
justifyContent="center"
flexDirection="column"
sx={{
borderRadius: 1,
border: '1px solid grey'
}}
>
<IconContext.Provider
value={{
color: 'lightblue',
size: '16',
style: { verticalAlign: 'middle' }
}}
>
{!loading && data.length === 0 ? (
<Typography variant="subtitle2" color="warning">
{LL.NO_DATA()}
</Typography>
) : (
<Table
data={{ nodes: data }}
theme={dashboard_theme}
layout={{ custom: true }}
tree={tree}
>
{(tableList: DashboardItem[]) => (
<Body>
{tableList.map((di: DashboardItem) => (
<Row
key={di.id}
item={di}
onClick={() => editDashboardValue(di)}
>
{di.id > 99 ? (
<>
<Cell>{showName(di)}</Cell>
<Cell>
<Tooltip
placement="left"
title={formatValue(LL, di.dv?.v, di.dv?.u)}
arrow
>
<span style={{ color: 'lightgrey' }}>
{formatValue(LL, di.dv?.v, di.dv?.u)}
</span>
</Tooltip>
</Cell>
<Cell>
{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>
</>
) : (
<>
<CellTree item={di}>{showName(di)}</CellTree>
<Cell />
<Cell />
</>
)}
</Row>
))}
</Body>
)}
</Table>
)}
</IconContext.Provider>
</Box>
</>
);
};
return (
<SectionContent>
{renderContent()}
{selectedDashboardItem && selectedDashboardItem.dv && (
<DevicesDialog
open={deviceValueDialogOpen}
onClose={() => setDeviceValueDialogOpen(false)}
onSave={deviceValueDialogSave}
selectedItem={selectedDashboardItem.dv}
writeable={true}
validator={deviceValueItemValidation(selectedDashboardItem.dv)}
progress={submitting}
/>
)}
</SectionContent>
);
};
export default Dashboard;

View File

@@ -2,59 +2,52 @@ import { AiOutlineAlert, AiOutlineControl, AiOutlineGateway } from 'react-icons/
import { CgSmartHomeBoiler } from 'react-icons/cg';
import { FaSolarPanel } from 'react-icons/fa';
import { GiHeatHaze, GiTap } from 'react-icons/gi';
import { MdPlaylistAdd } from 'react-icons/md';
import { MdMoreTime } from 'react-icons/md';
import {
MdOutlineDevices,
MdOutlinePool,
MdOutlineSensors,
MdThermostatAuto
} from 'react-icons/md';
import { TiFlowSwitch } from 'react-icons/ti';
import { PiFan, PiGauge } from 'react-icons/pi';
import { TiFlowSwitch, TiThermometer } from 'react-icons/ti';
import { VscVmConnect } from 'react-icons/vsc';
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
import type { SvgIconProps } from '@mui/material';
import { DeviceType } from './types';
const deviceIconLookup: {
[key in DeviceType]: React.ComponentType<SvgIconProps> | undefined;
} = {
[DeviceType.TEMPERATURESENSOR]: TiThermometer,
[DeviceType.ANALOGSENSOR]: PiGauge,
[DeviceType.BOILER]: CgSmartHomeBoiler,
[DeviceType.HEATSOURCE]: CgSmartHomeBoiler,
[DeviceType.THERMOSTAT]: MdThermostatAuto,
[DeviceType.MIXER]: AiOutlineControl,
[DeviceType.SOLAR]: FaSolarPanel,
[DeviceType.HEATPUMP]: GiHeatHaze,
[DeviceType.GATEWAY]: AiOutlineGateway,
[DeviceType.SWITCH]: TiFlowSwitch,
[DeviceType.CONTROLLER]: VscVmConnect,
[DeviceType.CONNECT]: VscVmConnect,
[DeviceType.ALERT]: AiOutlineAlert,
[DeviceType.EXTENSION]: MdOutlineDevices,
[DeviceType.WATER]: GiTap,
[DeviceType.POOL]: MdOutlinePool,
[DeviceType.CUSTOM]: MdPlaylistAdd,
[DeviceType.UNKNOWN]: MdOutlineSensors,
[DeviceType.SYSTEM]: undefined,
[DeviceType.SCHEDULER]: MdMoreTime,
[DeviceType.GENERIC]: MdOutlineSensors,
[DeviceType.VENTILATION]: PiFan
};
const DeviceIcon = ({ type_id }: { type_id: DeviceType }) => {
switch (type_id) {
case DeviceType.TEMPERATURESENSOR:
case DeviceType.ANALOGSENSOR:
return <MdOutlineSensors />;
case DeviceType.BOILER:
case DeviceType.HEATSOURCE:
return <CgSmartHomeBoiler />;
case DeviceType.THERMOSTAT:
return <MdThermostatAuto />;
case DeviceType.MIXER:
return <AiOutlineControl />;
case DeviceType.SOLAR:
return <FaSolarPanel />;
case DeviceType.HEATPUMP:
return <GiHeatHaze />;
case DeviceType.GATEWAY:
return <AiOutlineGateway />;
case DeviceType.SWITCH:
return <TiFlowSwitch />;
case DeviceType.CONTROLLER:
case DeviceType.CONNECT:
return <VscVmConnect />;
case DeviceType.ALERT:
return <AiOutlineAlert />;
case DeviceType.EXTENSION:
return <MdOutlineDevices />;
case DeviceType.WATER:
return <GiTap />;
case DeviceType.POOL:
return <MdOutlinePool />;
case DeviceType.CUSTOM:
return (
<PlaylistAddIcon
sx={{ color: 'lightblue', fontSize: 22, verticalAlign: 'middle' }}
/>
);
default:
return null;
}
const Icon = deviceIconLookup[type_id];
return Icon ? <Icon /> : null;
};
export default DeviceIcon;

View File

@@ -60,10 +60,11 @@ 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';
import DashboardDevicesDialog from './DevicesDialog';
import DevicesDialog from './DevicesDialog';
import { formatValue } from './deviceValue';
import { DeviceEntityMask, DeviceType, DeviceValueUOM_s } from './types';
import type { Device, DeviceValue } from './types';
@@ -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();
@@ -95,7 +96,7 @@ const Devices = () => {
(id: number) => readDeviceData(id),
{
initialData: {
data: []
nodes: []
},
immediate: false
}
@@ -147,22 +148,15 @@ const Devices = () => {
}
`,
Row: `
background-color: #1E1E1E;
position: relative;
cursor: pointer;
background-color: #1E1E1E;
.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;
background-color: #177ac9;
font-weight: normal;
}
&:hover .td {
border-top: 1px solid #177ac9;
border-bottom: 1px solid #177ac9;
}
`
});
@@ -170,17 +164,21 @@ const Devices = () => {
common_theme,
{
Table: `
--data-table-library_grid-template-columns: 40px repeat(1, minmax(0, 1fr)) 130px;
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 130px;
`,
BaseRow: `
.td {
height: 42px;
}
// .td {
// height: 42px;
// }
`,
HeaderRow: `
.th {
padding: 8px;
height: 36px;
`,
Row: `
&:hover .td {
background-color: #177ac9;
`
}
]);
@@ -221,7 +219,10 @@ const Devices = () => {
Row: `
&:nth-of-type(odd) .td {
background-color: #303030;
}
},
&:hover .td {
background-color: #177ac9;
}
`
}
]);
@@ -251,7 +252,7 @@ const Devices = () => {
};
const dv_sort = useSort(
{ nodes: deviceData.data },
{ nodes: deviceData.nodes },
{},
{
sortIcon: {
@@ -383,8 +384,8 @@ const Devices = () => {
];
const data = onlyFav
? deviceData.data.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE))
: deviceData.data;
? deviceData.nodes.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE))
: deviceData.nodes;
const csvData = data.reduce(
(csvString: string, rowItem: DeviceValue) =>
@@ -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);
@@ -527,57 +522,57 @@ const Devices = () => {
};
const renderCoreData = () => (
<IconContext.Provider
value={{
color: 'lightblue',
size: '18',
style: { verticalAlign: 'middle' }
}}
>
{!coreData.connected && (
<MessageBox my={2} level="error" message={LL.EMS_BUS_WARNING()} />
)}
<>
<IconContext.Provider
value={{
color: 'lightblue',
size: '18',
style: { verticalAlign: 'middle' }
}}
>
{!coreData.connected && (
<MessageBox my={2} level="error" message={LL.EMS_BUS_WARNING()} />
)}
{coreData.connected && (
<Table
data={{ nodes: coreData.devices }}
select={device_select}
theme={device_theme}
layout={{ custom: true }}
>
{(tableList: Device[]) => (
<>
<Header>
<HeaderRow>
<HeaderCell stiff />
<HeaderCell resize>{LL.DESCRIPTION()}</HeaderCell>
<HeaderCell stiff>{LL.TYPE(0)}</HeaderCell>
</HeaderRow>
</Header>
<Body>
{tableList.length === 0 && (
<CircularProgress sx={{ margin: 1 }} size={18} />
)}
{tableList.map((device: Device) => (
<Row key={device.id} item={device}>
<Cell stiff>
<DeviceIcon type_id={device.t} />
</Cell>
<Cell>
{device.n}
<span style={{ color: 'lightblue' }}>
&nbsp;&nbsp;({device.e})
</span>
</Cell>
<Cell stiff>{device.tn}</Cell>
</Row>
))}
</Body>
</>
)}
</Table>
)}
</IconContext.Provider>
{coreData.connected && (
<Table
data={{ nodes: coreData.devices }}
select={device_select}
theme={device_theme}
layout={{ custom: true }}
>
{(tableList: Device[]) => (
<>
<Header>
<HeaderRow>
<HeaderCell resize>{LL.DESCRIPTION()}</HeaderCell>
<HeaderCell stiff>{LL.TYPE(0)}</HeaderCell>
</HeaderRow>
</Header>
<Body>
{tableList.length === 0 && (
<CircularProgress sx={{ margin: 1 }} size={18} />
)}
{tableList.map((device: Device) => (
<Row key={device.id} item={device}>
<Cell>
<DeviceIcon type_id={device.t} />
&nbsp;&nbsp;
{device.n}
<span style={{ color: 'lightblue' }}>
&nbsp;&nbsp;({device.e})
</span>
</Cell>
<Cell stiff>{device.tn}</Cell>
</Row>
))}
</Body>
</>
)}
</Table>
)}
</IconContext.Provider>
</>
);
const deviceValueDialogClose = () => {
@@ -611,8 +606,8 @@ const Devices = () => {
);
const shown_data = onlyFav
? deviceData.data.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE))
: deviceData.data;
? deviceData.nodes.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE))
: deviceData.nodes;
const deviceIndex = coreData.devices.findIndex(
(d) => d.id === device_select.state.id
@@ -733,7 +728,7 @@ const Devices = () => {
size="small"
onClick={() => showDeviceValue(dv)}
>
{dv.v === '' && dv.c ? (
{dv.v === '' ? (
<PlayArrowIcon color="primary" sx={{ fontSize: 16 }} />
) : (
<EditIcon color="primary" sx={{ fontSize: 16 }} />
@@ -757,7 +752,7 @@ const Devices = () => {
{renderDeviceData()}
{renderDeviceDetails()}
{selectedDeviceValue && (
<DashboardDevicesDialog
<DevicesDialog
open={deviceValueDialogOpen}
onClose={deviceValueDialogClose}
onSave={deviceValueDialogSave}

View File

@@ -29,7 +29,7 @@ import { validate } from 'validators';
import { DeviceValueUOM, DeviceValueUOM_s } from './types';
import type { DeviceValue } from './types';
interface DashboardDevicesDialogProps {
interface DevicesDialogProps {
open: boolean;
onClose: () => void;
onSave: (as: DeviceValue) => void;
@@ -47,7 +47,7 @@ const DevicesDialog = ({
writeable,
validator,
progress
}: DashboardDevicesDialogProps) => {
}: DevicesDialogProps) => {
const { LL } = useI18nContext();
const [editItem, setEditItem] = useState<DeviceValue>(selectedItem);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
@@ -75,7 +75,10 @@ const DevicesDialog = ({
}
};
const setUom = (uom: DeviceValueUOM) => {
const setUom = (uom?: DeviceValueUOM) => {
if (uom === undefined) {
return;
}
switch (uom) {
case DeviceValueUOM.HOURS:
return LL.HOURS();
@@ -195,9 +198,9 @@ const DevicesDialog = ({
</Button>
<Button
startIcon={<WarningIcon color="warning" />}
variant="contained"
variant="outlined"
onClick={save}
color="info"
color="primary"
>
{selectedItem.v === '' && selectedItem.c ? LL.EXECUTE() : LL.UPDATE()}
</Button>

View File

@@ -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

View File

@@ -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 }}

View File

@@ -117,15 +117,10 @@ const Scheduler = () => {
position: relative;
cursor: pointer;
.td {
border-top: 1px solid #565656;
border-bottom: 1px solid #565656;
}
&:hover .td {
border-top: 1px solid #177ac9;
border-bottom: 1px solid #177ac9;
}
&:nth-of-type(odd) .td {
background-color: #303030;
background-color: #177ac9;
}
`
});
@@ -318,7 +313,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()}

View File

@@ -19,10 +19,11 @@ import {
} from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme';
import type { State } from '@table-library/react-table-library/types/common';
import { useAutoRequest, useRequest } from 'alova/client';
import { useRequest } from 'alova/client';
import { SectionContent, useLayoutTitle } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import { useInterval } from 'utils';
import {
readSensorData,
@@ -59,7 +60,7 @@ const Sensors = () => {
const [analogDialogOpen, setAnalogDialogOpen] = useState<boolean>(false);
const [creating, setCreating] = useState<boolean>(false);
const { data: sensorData, send: fetchSensorData } = useAutoRequest(
const { data: sensorData, send: fetchSensorData } = useRequest(
() => readSensorData(),
{
initialData: {
@@ -67,8 +68,7 @@ const Sensors = () => {
as: [],
analog_enabled: false,
platform: 'ESP32'
},
pollingTime: 2000
}
}
);
@@ -86,6 +86,12 @@ const Sensors = () => {
}
);
useInterval(() => {
if (!temperatureDialogOpen && !analogDialogOpen) {
void fetchSensorData();
}
}, 3000);
const common_theme = useTheme({
BaseRow: `
font-size: 14px;
@@ -110,19 +116,10 @@ const Sensors = () => {
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;
background-color: #177ac9;
}
`,
Cell: `

View File

@@ -322,7 +322,7 @@ const SensorsAnalogDialog = ({
<Button
startIcon={<RemoveIcon />}
variant="outlined"
color="error"
color="warning"
onClick={remove}
>
{LL.REMOVE()}
@@ -339,9 +339,9 @@ const SensorsAnalogDialog = ({
</Button>
<Button
startIcon={<WarningIcon color="warning" />}
variant="contained"
variant="outlined"
onClick={save}
color="info"
color="primary"
>
{creating ? LL.ADD(0) : LL.UPDATE()}
</Button>

View File

@@ -122,9 +122,9 @@ const SensorsTemperatureDialog = ({
</Button>
<Button
startIcon={<WarningIcon color="warning" />}
variant="contained"
variant="outlined"
onClick={save}
color="info"
color="primary"
>
{LL.UPDATE()}
</Button>

View File

@@ -27,12 +27,16 @@ const formatDurationMin = (LL: TranslationFunctions, duration_min: number) => {
export function formatValue(
LL: TranslationFunctions,
value: unknown,
uom: DeviceValueUOM
value?: unknown,
uom?: DeviceValueUOM
) {
if (typeof value !== 'number') {
return (value === undefined ? '' : value) as string;
if (typeof value !== 'number' || uom === undefined || value === undefined) {
if (value === undefined || typeof value === 'boolean') {
return '';
}
return value as string;
}
switch (uom) {
case DeviceValueUOM.HOURS:
return value ? formatDurationMin(LL, value * 60) : LL.NUM_HOURS({ num: 0 });

View File

@@ -114,10 +114,18 @@ export interface CoreData {
devices: Device[];
}
export interface DashboardItem {
id: number; // unique index
t?: number; // type from DeviceType
n?: string; // name, optional
dv?: DeviceValue; // device value, optional
nodes?: DashboardItem[]; // children nodes, optional
}
export interface DeviceValue {
id: string; // index, contains mask+name
v: unknown; // value, Number or String
u: number; // uom
v?: unknown; // value, Number, String or Boolean - can be undefined
u?: number; // uom, optional
c?: string; // command, optional
l?: string[]; // list, optional
h?: string; // help text, optional
@@ -125,8 +133,9 @@ export interface DeviceValue {
m?: number; // min, optional
x?: number; // max, optional
}
export interface DeviceData {
data: DeviceValue[];
nodes: DeviceValue[];
}
export interface DeviceEntity {
@@ -299,7 +308,7 @@ export interface ScheduleItem {
time: string; // also used for Condition and On Change
cmd: string;
value: string;
name: string; // is optional
name: string; // can be empty
o_id?: number;
o_active?: boolean;
o_deleted?: boolean;
@@ -382,10 +391,10 @@ export interface Entities {
// matches emsdevice.h DeviceType
export const enum DeviceType {
SYSTEM = 0,
TEMPERATURESENSOR,
ANALOGSENSOR,
SCHEDULER,
CUSTOM,
TEMPERATURESENSOR = 1,
ANALOGSENSOR = 2,
SCHEDULER = 3,
CUSTOM = 4,
BOILER,
THERMOSTAT,
MIXER,

View File

@@ -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} />

View File

@@ -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)));

View File

@@ -21,7 +21,7 @@ const SystemActivity = () => {
data,
send: loadData,
error
} = useAutoRequest(readActivity, { pollingTime: 2000 });
} = useAutoRequest(readActivity, { pollingTime: 3000 });
const { LL } = useI18nContext();

View File

@@ -36,7 +36,7 @@ const HardwareStatus = () => {
data,
send: loadData,
error
} = useAutoRequest(SystemApi.readSystemStatus, { pollingTime: 2000 });
} = useAutoRequest(SystemApi.readSystemStatus, { pollingTime: 3000 });
const content = () => {
if (!data) {

View File

@@ -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'));

View File

@@ -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);

View File

@@ -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)));

View File

@@ -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();
}
}

View File

@@ -65,7 +65,7 @@ const SystemStatus = () => {
error
} = useAutoRequest(readSystemStatus, {
initialData: [],
pollingTime: 5000,
pollingTime: 3000,
async middleware(_, next) {
if (!restarting) {
await next();

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import type { FC } from 'react';
import { useLocation } from 'react-router-dom';
@@ -24,9 +24,11 @@ const Layout: FC<RequiredChildrenProps> = ({ children }) => {
useEffect(() => setMobileOpen(false), [pathname]);
// cache the object to prevent unnecessary re-renders
const obj = useMemo(() => ({ title, setTitle }), [title]);
return (
// TODO wrap title/setTitle in a useMemo()
<LayoutContext.Provider value={{ title, setTitle }}>
<LayoutContext.Provider value={obj}>
<LayoutAppBar title={title} onToggleDrawer={handleDrawerToggle} />
<LayoutDrawer mobileOpen={mobileOpen} onClose={handleDrawerToggle} />
<Box component="main" sx={{ marginLeft: { md: `${DRAWER_WIDTH}px` } }}>

View File

@@ -11,6 +11,7 @@ import PersonIcon from '@mui/icons-material/Person';
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
import SensorsIcon from '@mui/icons-material/Sensors';
import SettingsIcon from '@mui/icons-material/Settings';
import StarIcon from '@mui/icons-material/Star';
import {
Avatar,
Box,
@@ -51,8 +52,8 @@ const LayoutMenu = () => {
return (
<>
<List component="nav">
<LayoutMenuItem icon={StarIcon} label="Dashboard" to={`/dashboard`} />
<LayoutMenuItem icon={CategoryIcon} label={LL.DEVICES()} to={`/devices`} />
<LayoutMenuItem icon={SensorsIcon} label={LL.SENSORS()} to={`/sensors`} />
<Divider />
<Box
@@ -77,19 +78,19 @@ const LayoutMenu = () => {
mb: '2px',
color: 'lightblue'
}}
secondary={
LL.CUSTOMIZATIONS() +
', ' +
LL.SCHEDULER() +
', ' +
LL.CUSTOM_ENTITIES(0) +
'...'
}
secondaryTypographyProps={{
noWrap: true,
fontSize: 12,
color: menuOpen ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0.5)'
}}
// secondary={
// LL.CUSTOMIZATIONS() +
// ', ' +
// LL.SCHEDULER() +
// ', ' +
// LL.CUSTOM_ENTITIES(0) +
// '...'
// }
// secondaryTypographyProps={{
// noWrap: true,
// fontSize: 12,
// color: menuOpen ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0.5)'
// }}
sx={{ my: 0 }}
/>
<KeyboardArrowDown
@@ -103,6 +104,12 @@ const LayoutMenu = () => {
</ListItemButton>
{menuOpen && (
<>
<LayoutMenuItem
icon={SensorsIcon}
label={LL.SENSORS()}
to={`/sensors`}
/>
<LayoutMenuItem
icon={ConstructionIcon}
label={LL.CUSTOMIZATIONS()}
@@ -142,8 +149,8 @@ const LayoutMenu = () => {
</List>
<Divider />
<List>
<ListItem disablePadding onClick={handleClick}>
<ListItemButton>
<ListItem disablePadding>
<ListItemButton component="button" onClick={handleClick}>
<ListItemIcon sx={{ color: '#9e9e9e' }}>
<AccountCircleIcon />
</ListItemIcon>

View File

@@ -35,7 +35,7 @@ export function fetchLoginRedirect(): Partial<Path> {
const signInSearch = getStorage().getItem(SIGN_IN_SEARCH);
clearLoginRedirect();
return {
pathname: signInPathname || `/devices`,
pathname: signInPathname || `/dashboard`,
search: (signInPathname && signInSearch) || undefined
};
}

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import type { FC } from 'react';
import { redirect } from 'react-router-dom';
import { toast } from 'react-toastify';
@@ -67,17 +67,15 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
void refresh();
}, [refresh]);
// cache object to prevent re-renders
const obj = useMemo(
() => ({ signIn, signOut, me, refresh }),
[signIn, signOut, me, refresh]
);
if (initialized) {
return (
// TODO useMemo?
<AuthenticationContext.Provider
value={{
signIn,
signOut,
me,
refresh
}}
>
<AuthenticationContext.Provider value={obj}>
{children}
</AuthenticationContext.Provider>
);

View File

@@ -340,7 +340,10 @@ const de: Translation = {
PLEASE_WAIT: 'Bitte warten',
RESTARTING_PRE: 'Initialisierung',
RESTARTING_POST: 'Vorbereitung',
AUTO_SCROLL: 'Automatisches Scrollen'
AUTO_SCROLL: 'Automatisches Scrollen',
DASHBOARD: 'Dashboard',
NO_DATA: 'Keine Daten verfügbar',
DASHBOARD_1: 'Passen Sie Ihr Dashboard an, indem Sie EMS-Entitäten mithilfe des Moduls „Anpassungen“ als Favorit markieren.'
};
export default de;

View File

@@ -340,7 +340,10 @@ const en: Translation = {
PLEASE_WAIT: 'Please wait',
RESTARTING_PRE: 'Initializing',
RESTARTING_POST: 'Preparing',
AUTO_SCROLL: 'Auto Scroll'
AUTO_SCROLL: 'Auto Scroll',
DASHBOARD: 'Dashboard',
NO_DATA: 'No data available',
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.',
};
export default en;

View File

@@ -340,7 +340,10 @@ const fr: Translation = {
PLEASE_WAIT: 'Please wait', // TODO translate
RESTARTING_PRE: 'Initializing', // TODO translate
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll' // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
NO_DATA: 'No data available', // TODO translate
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.' // TODO translate
};
export default fr;

View File

@@ -340,7 +340,10 @@ const it: Translation = {
PLEASE_WAIT: 'Please wait', // TODO translate
RESTARTING_PRE: 'Initializing', // TODO translate
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll' // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
NO_DATA: 'No data available', // TODO translate
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.' // TODO translate
};
export default it;

View File

@@ -340,7 +340,10 @@ const nl: Translation = {
PLEASE_WAIT: 'Please wait', // TODO translate
RESTARTING_PRE: 'Initializing', // TODO translate
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll' // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
NO_DATA: 'No data available', // TODO translate
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.' // TODO translate
};
export default nl;

View File

@@ -340,7 +340,10 @@ const no: Translation = {
PLEASE_WAIT: 'Please wait', // TODO translate
RESTARTING_PRE: 'Initializing', // TODO translate
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll' // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
NO_DATA: 'No data available', // TODO translate
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.' // TODO translate
};
export default no;

View File

@@ -340,7 +340,10 @@ const pl: BaseTranslation = {
PLEASE_WAIT: 'Please wait', // TODO translate
RESTARTING_PRE: 'Initializing', // TODO translate
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll' // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
NO_DATA: 'No data available', // TODO translate
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.' // TODO translate
};
export default pl;

View File

@@ -340,7 +340,10 @@ const sk: Translation = {
PLEASE_WAIT: 'Čakajte prosím',
RESTARTING_PRE: 'Prebieha inicializácia',
RESTARTING_POST: 'Príprava',
AUTO_SCROLL: 'Auto Scroll' // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
NO_DATA: 'No data available', // TODO translate
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.' // TODO translate
};
export default sk;

View File

@@ -340,7 +340,10 @@ const sv: Translation = {
PLEASE_WAIT: 'Please wait', // TODO translate
RESTARTING_PRE: 'Initializing', // TODO translate
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll' // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
NO_DATA: 'No data available', // TODO translate
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.' // TODO translate
};
export default sv;

View File

@@ -340,7 +340,10 @@ const tr: Translation = {
PLEASE_WAIT: 'Please wait', // TODO translate
RESTARTING_PRE: 'Initializing', // TODO translate
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll' // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
NO_DATA: 'No data available', // TODO translate
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module.' // TODO translate
};
export default tr;

View File

@@ -3,4 +3,7 @@ export * from './route';
export * from './submit';
export * from './time';
export * from './useRest';
export * from './useInterval';
export * from './props';
export * from './file';
export * from './usePersistState';

View File

@@ -0,0 +1,25 @@
import { useEffect, useRef } from 'react';
// adapted from https://www.joshwcomeau.com/snippets/react-hooks/use-interval/
export const useInterval = (callback: () => void, delay: number) => {
const intervalRef = useRef<number | null>(null);
const savedCallback = useRef<() => void>(callback);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
const tick = () => savedCallback.current();
if (typeof delay === 'number') {
intervalRef.current = window.setInterval(tick, delay);
return () => {
if (intervalRef.current !== null) {
window.clearInterval(intervalRef.current);
}
};
}
}, [delay]);
return intervalRef;
};

View File

@@ -0,0 +1,26 @@
import { useEffect, useMemo, useState } from 'react';
export const usePersistState = <T>(
initial_value: T,
id: string
): [T, (new_state: T) => void] => {
// Set initial value
const _initial_value = useMemo(() => {
const local_storage_value_str = localStorage.getItem('state:' + id);
// If there is a value stored in localStorage, use that
if (local_storage_value_str) {
return JSON.parse(local_storage_value_str);
}
// Otherwise use initial_value that was passed to the function
return initial_value;
}, []);
const [state, setState] = useState(_initial_value);
useEffect(() => {
const state_str = JSON.stringify(state); // Stringified state
localStorage.setItem('state:' + id, state_str); // Set stringified state as item in localStorage
}, [state]);
return [state, setState];
};

View File

@@ -5,21 +5,21 @@ __metadata:
version: 8
cacheKey: 10c0
"@alova/adapter-xhr@npm:2.0.7":
version: 2.0.7
resolution: "@alova/adapter-xhr@npm:2.0.7"
"@alova/adapter-xhr@npm:2.0.8":
version: 2.0.8
resolution: "@alova/adapter-xhr@npm:2.0.8"
dependencies:
"@alova/shared": "npm:^1.0.5"
"@alova/shared": "npm:^1.0.6"
peerDependencies:
alova: ^3.0.16
checksum: 10c0/2c21313ef963df22af08ae652c310381f681948e2edca2f863cc92becd7c6e815771649c9a0b77e4193eb507a4bb5bb9e77f332fe194f86f00a4144845b7757e
alova: ^3.0.20
checksum: 10c0/84b2f8f33733af3ed9418e7eb4d6195115b318717afc588147514d474632eaa703f72f753f18198d1a90da0a688c60a29345aabe0f93941c4ef089b7ac636204
languageName: node
linkType: hard
"@alova/shared@npm:^1.0.5":
version: 1.0.5
resolution: "@alova/shared@npm:1.0.5"
checksum: 10c0/58d99d8b6b026e60c7184c55bdccd43d3b39bec598722135b272fd3c355942facc40f9433dceb6fcabb6631b87766c7227432c2a2bf3ae281d9a26b2eba2a69d
"@alova/shared@npm:^1.0.6":
version: 1.0.6
resolution: "@alova/shared@npm:1.0.6"
checksum: 10c0/7779bef946a6c9cca9a6369a9fa664be9fe929b0132a94fe60836b787844f884aef1eb69a1f8850003b92169d7495b772b59e40d8de138019386f32cdc689aad
languageName: node
linkType: hard
@@ -90,9 +90,9 @@ __metadata:
languageName: node
linkType: hard
"@babel/core@npm:^7.25.7":
version: 7.25.7
resolution: "@babel/core@npm:7.25.7"
"@babel/core@npm:^7.25.8":
version: 7.25.8
resolution: "@babel/core@npm:7.25.8"
dependencies:
"@ampproject/remapping": "npm:^2.2.0"
"@babel/code-frame": "npm:^7.25.7"
@@ -100,16 +100,16 @@ __metadata:
"@babel/helper-compilation-targets": "npm:^7.25.7"
"@babel/helper-module-transforms": "npm:^7.25.7"
"@babel/helpers": "npm:^7.25.7"
"@babel/parser": "npm:^7.25.7"
"@babel/parser": "npm:^7.25.8"
"@babel/template": "npm:^7.25.7"
"@babel/traverse": "npm:^7.25.7"
"@babel/types": "npm:^7.25.7"
"@babel/types": "npm:^7.25.8"
convert-source-map: "npm:^2.0.0"
debug: "npm:^4.1.0"
gensync: "npm:^1.0.0-beta.2"
json5: "npm:^2.2.3"
semver: "npm:^6.3.1"
checksum: 10c0/dad20af39624086afc3a0910bd97ae712c9ad0e9dda09fc5da93876e8ea1802b63ddd81c44f4aa8a9834db46de801eaab1ce9b81ab54b4fe907ae052c24de136
checksum: 10c0/8411ea506e6f7c8a39ab5c1524b00589fa3b087edb47389708f7fe07170929192171734666e3ea10b95a951643a531a6d09eedfe071572c9ea28516646265086
languageName: node
linkType: hard
@@ -403,6 +403,17 @@ __metadata:
languageName: node
linkType: hard
"@babel/parser@npm:^7.25.8":
version: 7.25.8
resolution: "@babel/parser@npm:7.25.8"
dependencies:
"@babel/types": "npm:^7.25.8"
bin:
parser: ./bin/babel-parser.js
checksum: 10c0/a1a13845b7e8dda4c970791814a4bbf60004969882f18f470e260ad822d2e1f8941948f851e9335895563610f240fa6c98481ce8019865e469502bbf21daafa4
languageName: node
linkType: hard
"@babel/plugin-syntax-jsx@npm:^7.24.7":
version: 7.24.7
resolution: "@babel/plugin-syntax-jsx@npm:7.24.7"
@@ -551,6 +562,17 @@ __metadata:
languageName: node
linkType: hard
"@babel/types@npm:^7.25.8":
version: 7.25.8
resolution: "@babel/types@npm:7.25.8"
dependencies:
"@babel/helper-string-parser": "npm:^7.25.7"
"@babel/helper-validator-identifier": "npm:^7.25.7"
to-fast-properties: "npm:^2.0.0"
checksum: 10c0/55ca2d6df6426c98db2769ce884ce5e9de83a512ea2dd7bcf56c811984dc14351cacf42932a723630c5afcff2455809323decd645820762182f10b7b5252b59f
languageName: node
linkType: hard
"@emotion/babel-plugin@npm:^11.12.0":
version: 11.12.0
resolution: "@emotion/babel-plugin@npm:11.12.0"
@@ -627,7 +649,7 @@ __metadata:
languageName: node
linkType: hard
"@emotion/serialize@npm:^1.2.0, @emotion/serialize@npm:^1.3.0, @emotion/serialize@npm:^1.3.1":
"@emotion/serialize@npm:^1.2.0, @emotion/serialize@npm:^1.3.0, @emotion/serialize@npm:^1.3.1, @emotion/serialize@npm:^1.3.2":
version: 1.3.2
resolution: "@emotion/serialize@npm:1.3.2"
dependencies:
@@ -1045,38 +1067,38 @@ __metadata:
languageName: node
linkType: hard
"@mui/core-downloads-tracker@npm:^6.1.2":
version: 6.1.2
resolution: "@mui/core-downloads-tracker@npm:6.1.2"
checksum: 10c0/a685ac90a614be07c07bba752a6772be1caf06329e278c731f2a60b7712381c467adb3f5e0cfe7f2bc51b744ba76138f451853ff767c4739fa6379c8bc49d407
"@mui/core-downloads-tracker@npm:^6.1.3":
version: 6.1.3
resolution: "@mui/core-downloads-tracker@npm:6.1.3"
checksum: 10c0/250df05f7cb497fb950d20680c67ce5c0e33d32fb7c56dfd2edfe28c298542c35fc79039123f64ae51a379cd205d182f310bde613d3a36e0da40079d24998515
languageName: node
linkType: hard
"@mui/icons-material@npm:^6.1.2":
version: 6.1.2
resolution: "@mui/icons-material@npm:6.1.2"
"@mui/icons-material@npm:^6.1.3":
version: 6.1.3
resolution: "@mui/icons-material@npm:6.1.3"
dependencies:
"@babel/runtime": "npm:^7.25.6"
peerDependencies:
"@mui/material": ^6.1.2
"@mui/material": ^6.1.3
"@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10c0/ae5f1134e3ac45914c2bfef76a31848a8dc655237a9e478959580f778c31d77e6e54a588ec0ec552d8c6a904298f02d7cf482a5d6df294389d9ea9da8109ef94
checksum: 10c0/38a47fcb22c80f2beeb1897aad66ac8641aad236169e9b324bae63596c1fab69a55e39f233d0c4976f2cf492cd135e4203a24e8b047baa2918d7ca8382358400
languageName: node
linkType: hard
"@mui/material@npm:^6.1.2":
version: 6.1.2
resolution: "@mui/material@npm:6.1.2"
"@mui/material@npm:^6.1.3":
version: 6.1.3
resolution: "@mui/material@npm:6.1.3"
dependencies:
"@babel/runtime": "npm:^7.25.6"
"@mui/core-downloads-tracker": "npm:^6.1.2"
"@mui/system": "npm:^6.1.2"
"@mui/types": "npm:^7.2.17"
"@mui/utils": "npm:^6.1.2"
"@mui/core-downloads-tracker": "npm:^6.1.3"
"@mui/system": "npm:^6.1.3"
"@mui/types": "npm:^7.2.18"
"@mui/utils": "npm:^6.1.3"
"@popperjs/core": "npm:^2.11.8"
"@types/react-transition-group": "npm:^4.4.11"
clsx: "npm:^2.1.1"
@@ -1087,7 +1109,7 @@ __metadata:
peerDependencies:
"@emotion/react": ^11.5.0
"@emotion/styled": ^11.3.0
"@mui/material-pigment-css": ^6.1.2
"@mui/material-pigment-css": ^6.1.3
"@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
@@ -1100,16 +1122,16 @@ __metadata:
optional: true
"@types/react":
optional: true
checksum: 10c0/7825be6ec47f82b3b3469fd0724b818bcc488f9b42de275fed38de314c216dfcefeec1cd23a0e8f8e6355ca51fbb2554ee4011834d6188a9245a8d60e7be2398
checksum: 10c0/657f8d501b368208f69f02c4058f7d93d68d9b50c01812ea5c16117929448fa0e9ead6f49b9828f927b379075af72a991565632de926976a266b1162b3ea0f89
languageName: node
linkType: hard
"@mui/private-theming@npm:^6.1.2":
version: 6.1.2
resolution: "@mui/private-theming@npm:6.1.2"
"@mui/private-theming@npm:^6.1.3":
version: 6.1.3
resolution: "@mui/private-theming@npm:6.1.3"
dependencies:
"@babel/runtime": "npm:^7.25.6"
"@mui/utils": "npm:^6.1.2"
"@mui/utils": "npm:^6.1.3"
prop-types: "npm:^15.8.1"
peerDependencies:
"@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0
@@ -1117,16 +1139,17 @@ __metadata:
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10c0/b8f8ee447dde7ecd34b9b96c19d68cff6771a7d5a133b85c3ff2dcb19fc8f2375375de6695c439dcfb6cb94e28d0a7c0889e132bfa467700a883e0dd834e8080
checksum: 10c0/b84ca829a9f1fbcc15a8b43410108cc03bb65fff80b8ec908352dde5666c3722df60af8130ded9b6eabf02d202c536393adccb6ba13cad08da7edce76200a271
languageName: node
linkType: hard
"@mui/styled-engine@npm:^6.1.2":
version: 6.1.2
resolution: "@mui/styled-engine@npm:6.1.2"
"@mui/styled-engine@npm:^6.1.3":
version: 6.1.3
resolution: "@mui/styled-engine@npm:6.1.3"
dependencies:
"@babel/runtime": "npm:^7.25.6"
"@emotion/cache": "npm:^11.13.1"
"@emotion/serialize": "npm:^1.3.2"
"@emotion/sheet": "npm:^1.4.0"
csstype: "npm:^3.1.3"
prop-types: "npm:^15.8.1"
@@ -1139,19 +1162,19 @@ __metadata:
optional: true
"@emotion/styled":
optional: true
checksum: 10c0/cad5a768d039a8e6652c5f33371ff68e82afedec75f340a0c4154ca4bf29877f23e89c041b0cbdad7d81c4fe4e356882d0de3d24157a05323a88e9703ed85d30
checksum: 10c0/3788657bfae25dc78bfc82f0077919a29ee18c5659e82f9b73de29afd57447397e0dee1416c3f25360b584b26d8116287512bf3a5563a16f5d8eabb03f990a95
languageName: node
linkType: hard
"@mui/system@npm:^6.1.2":
version: 6.1.2
resolution: "@mui/system@npm:6.1.2"
"@mui/system@npm:^6.1.3":
version: 6.1.3
resolution: "@mui/system@npm:6.1.3"
dependencies:
"@babel/runtime": "npm:^7.25.6"
"@mui/private-theming": "npm:^6.1.2"
"@mui/styled-engine": "npm:^6.1.2"
"@mui/types": "npm:^7.2.17"
"@mui/utils": "npm:^6.1.2"
"@mui/private-theming": "npm:^6.1.3"
"@mui/styled-engine": "npm:^6.1.3"
"@mui/types": "npm:^7.2.18"
"@mui/utils": "npm:^6.1.3"
clsx: "npm:^2.1.1"
csstype: "npm:^3.1.3"
prop-types: "npm:^15.8.1"
@@ -1167,28 +1190,28 @@ __metadata:
optional: true
"@types/react":
optional: true
checksum: 10c0/b9240574f5db5a79d1ad1fc5dead2fc2b0dcaee2bc5515e6b8ea88cddd374f58adb18611fee65ae38ef165434712de425a6a5fc4d8d717d9b4f5a3716e2c3c46
checksum: 10c0/53934c4abcae93e1474cf3457e7fc2a43c6a57b51c0b5aba7561746dbab71192dd2716dde86230be7fd56bbef130260e4b8af61462a0453d5ba9da240dafefed
languageName: node
linkType: hard
"@mui/types@npm:^7.2.17":
version: 7.2.17
resolution: "@mui/types@npm:7.2.17"
"@mui/types@npm:^7.2.18":
version: 7.2.18
resolution: "@mui/types@npm:7.2.18"
peerDependencies:
"@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10c0/2dd77e88490d364f1f8408a5270583d323919461812768b804cae3874143d79714a862f14c7167f5f65109f7f1e90252ffee14473e3470dd1ae12d7b7308c6c4
checksum: 10c0/338404bdef7c7f9ebcd389ebbf429c44d2cc9c25c65d8669dc900a24b2c8718240482273bf6cd953578965e3838ad40a8e7376c71d3d9146be3afb88bff1b67a
languageName: node
linkType: hard
"@mui/utils@npm:^6.1.2":
version: 6.1.2
resolution: "@mui/utils@npm:6.1.2"
"@mui/utils@npm:^6.1.3":
version: 6.1.3
resolution: "@mui/utils@npm:6.1.3"
dependencies:
"@babel/runtime": "npm:^7.25.6"
"@mui/types": "npm:^7.2.17"
"@mui/types": "npm:^7.2.18"
"@types/prop-types": "npm:^15.7.13"
clsx: "npm:^2.1.1"
prop-types: "npm:^15.8.1"
@@ -1199,7 +1222,7 @@ __metadata:
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10c0/ed56688610f3c04ad219066b5bd94b16c2c1318e2fc4d49e6d6d7b3800d10b4751185cbcc88431a3a1070a236f61e41238c06ef2998a3d65724ac6245a8b64e6
checksum: 10c0/2879a47b309565a3aa83177c94a9dddb67fcad5286ec1e5cec498543e07d7ee3c8930586a5ec36dc54a33c12c451ea8eb913d83a85127074c6cf8289b75d1ea9
languageName: node
linkType: hard
@@ -1337,10 +1360,10 @@ __metadata:
languageName: node
linkType: hard
"@remix-run/router@npm:1.19.2":
version: 1.19.2
resolution: "@remix-run/router@npm:1.19.2"
checksum: 10c0/ac7fc813350686705f2c29219e70e1e299d9a8e3b301e9e81f7e84f578c40c6462b590cf0d78863bac40dbc325b68c71ae070f4a1465793d1d1971b619618295
"@remix-run/router@npm:1.20.0":
version: 1.20.0
resolution: "@remix-run/router@npm:1.20.0"
checksum: 10c0/2e017dea530717a6e93a16d478714c4c9165313a1c48e39172ec609bc20324ca6362e8ee2243602df6343644c9268d82a3f50f154d3bb8a17dddde6c37be6e83
languageName: node
linkType: hard
@@ -1644,7 +1667,7 @@ __metadata:
languageName: node
linkType: hard
"@types/node@npm:*, @types/node@npm:^22.7.4":
"@types/node@npm:*":
version: 22.7.4
resolution: "@types/node@npm:22.7.4"
dependencies:
@@ -1653,6 +1676,15 @@ __metadata:
languageName: node
linkType: hard
"@types/node@npm:^22.7.5":
version: 22.7.5
resolution: "@types/node@npm:22.7.5"
dependencies:
undici-types: "npm:~6.19.2"
checksum: 10c0/cf11f74f1a26053ec58066616e3a8685b6bcd7259bc569738b8f752009f9f0f7f85a1b2d24908e5b0f752482d1e8b6babdf1fbb25758711ec7bb9500bfcd6e60
languageName: node
linkType: hard
"@types/parse-json@npm:^4.0.0":
version: 4.0.2
resolution: "@types/parse-json@npm:4.0.2"
@@ -1667,12 +1699,12 @@ __metadata:
languageName: node
linkType: hard
"@types/react-dom@npm:^18.3.0":
version: 18.3.0
resolution: "@types/react-dom@npm:18.3.0"
"@types/react-dom@npm:^18.3.1":
version: 18.3.1
resolution: "@types/react-dom@npm:18.3.1"
dependencies:
"@types/react": "npm:*"
checksum: 10c0/6c90d2ed72c5a0e440d2c75d99287e4b5df3e7b011838cdc03ae5cd518ab52164d86990e73246b9d812eaf02ec351d74e3b4f5bd325bf341e13bf980392fd53b
checksum: 10c0/8b416551c60bb6bd8ec10e198c957910cfb271bc3922463040b0d57cf4739cdcd24b13224f8d68f10318926e1ec3cd69af0af79f0291b599a992f8c80d47f1eb
languageName: node
linkType: hard
@@ -1744,15 +1776,15 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:8.8.0":
version: 8.8.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.8.0"
"@typescript-eslint/eslint-plugin@npm:8.8.1":
version: 8.8.1
resolution: "@typescript-eslint/eslint-plugin@npm:8.8.1"
dependencies:
"@eslint-community/regexpp": "npm:^4.10.0"
"@typescript-eslint/scope-manager": "npm:8.8.0"
"@typescript-eslint/type-utils": "npm:8.8.0"
"@typescript-eslint/utils": "npm:8.8.0"
"@typescript-eslint/visitor-keys": "npm:8.8.0"
"@typescript-eslint/scope-manager": "npm:8.8.1"
"@typescript-eslint/type-utils": "npm:8.8.1"
"@typescript-eslint/utils": "npm:8.8.1"
"@typescript-eslint/visitor-keys": "npm:8.8.1"
graphemer: "npm:^1.4.0"
ignore: "npm:^5.3.1"
natural-compare: "npm:^1.4.0"
@@ -1763,66 +1795,66 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: 10c0/98ac37587eda02a713710f0a62ca979833482024968f1d1735881718abe102a6b49707db4f1dac0d7c731d1cbf8111d829c5125348d4829ab6fad7a7b3b344e4
checksum: 10c0/020a0a482202b34c6665a56ec5902e38ae1870b2600ec1b2092de352b23099dde553781ee8323974f63962ebe164a6304f0019e937afb5cf7854b0e0163ad1ca
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:8.8.0":
version: 8.8.0
resolution: "@typescript-eslint/parser@npm:8.8.0"
"@typescript-eslint/parser@npm:8.8.1":
version: 8.8.1
resolution: "@typescript-eslint/parser@npm:8.8.1"
dependencies:
"@typescript-eslint/scope-manager": "npm:8.8.0"
"@typescript-eslint/types": "npm:8.8.0"
"@typescript-eslint/typescript-estree": "npm:8.8.0"
"@typescript-eslint/visitor-keys": "npm:8.8.0"
"@typescript-eslint/scope-manager": "npm:8.8.1"
"@typescript-eslint/types": "npm:8.8.1"
"@typescript-eslint/typescript-estree": "npm:8.8.1"
"@typescript-eslint/visitor-keys": "npm:8.8.1"
debug: "npm:^4.3.4"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
peerDependenciesMeta:
typescript:
optional: true
checksum: 10c0/cf72a644b89c62cd55b09fa1d22b51a2c726714aac344a797f0c2ad80bfbabcb7567000fadd4ea8188aa1d923675bebdca06acc1d28ac1b8360bf28a36b46f3a
checksum: 10c0/2afd147ccec6754316d6837d6108a5d822eb6071e1a7355073288c232530bc3e49901d3f08755ce02d497110c531f3b3658eb46d0ff875a69d4f360b5f938cb4
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.8.0":
version: 8.8.0
resolution: "@typescript-eslint/scope-manager@npm:8.8.0"
"@typescript-eslint/scope-manager@npm:8.8.1":
version: 8.8.1
resolution: "@typescript-eslint/scope-manager@npm:8.8.1"
dependencies:
"@typescript-eslint/types": "npm:8.8.0"
"@typescript-eslint/visitor-keys": "npm:8.8.0"
checksum: 10c0/29ddf589ff0e465dbbf3eb87b79a29face4ec5a6cb617bbaafbac6ae8340d376b5b405bca762ee1c7a40cbdf7912a32734f9119f6864df048c7a0b2de21bdd3d
"@typescript-eslint/types": "npm:8.8.1"
"@typescript-eslint/visitor-keys": "npm:8.8.1"
checksum: 10c0/6f697baf087aedc3f0f228ff964fd108a9dd33fe4e5cc6c914be6367c324cee55629e099832668042bedfec8cdc72c6ef2ca960ee26966dbcc75753059a1352f
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.8.0":
version: 8.8.0
resolution: "@typescript-eslint/type-utils@npm:8.8.0"
"@typescript-eslint/type-utils@npm:8.8.1":
version: 8.8.1
resolution: "@typescript-eslint/type-utils@npm:8.8.1"
dependencies:
"@typescript-eslint/typescript-estree": "npm:8.8.0"
"@typescript-eslint/utils": "npm:8.8.0"
"@typescript-eslint/typescript-estree": "npm:8.8.1"
"@typescript-eslint/utils": "npm:8.8.1"
debug: "npm:^4.3.4"
ts-api-utils: "npm:^1.3.0"
peerDependenciesMeta:
typescript:
optional: true
checksum: 10c0/d6ee11f4686fb54daea1f436f73b96eb31a95f6e535abc0534abf5794e7597669a92d12300969c8afee0fc1912dbc1591664f7e37f0da5935016cc981b2921a8
checksum: 10c0/6edfc2b9fca5233dd922141f080377b677db1093ec3e702a3ab52d58f77b91c0fb69479d4d42f125536b8fc0ffa85c07c7de2f17cc4c6fa1df1226ec01e5608c
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.8.0":
version: 8.8.0
resolution: "@typescript-eslint/types@npm:8.8.0"
checksum: 10c0/cd168fafcaf77641b023c4405ea3a8c30fbad1737abb5aec9fce67fe2ae20224b624b5a2e3e84900ba81dc7dd33343add3653763703a225326cc81356b182d09
"@typescript-eslint/types@npm:8.8.1":
version: 8.8.1
resolution: "@typescript-eslint/types@npm:8.8.1"
checksum: 10c0/4b44857332a0b1bfafbeccb8be157f8266d9e226ac723f6af1272b9b670b49444423ddac733655163eb3b90e8c88393a68ab2d7f326f5775371eaf4b9ca31d7b
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.8.0":
version: 8.8.0
resolution: "@typescript-eslint/typescript-estree@npm:8.8.0"
"@typescript-eslint/typescript-estree@npm:8.8.1":
version: 8.8.1
resolution: "@typescript-eslint/typescript-estree@npm:8.8.1"
dependencies:
"@typescript-eslint/types": "npm:8.8.0"
"@typescript-eslint/visitor-keys": "npm:8.8.0"
"@typescript-eslint/types": "npm:8.8.1"
"@typescript-eslint/visitor-keys": "npm:8.8.1"
debug: "npm:^4.3.4"
fast-glob: "npm:^3.3.2"
is-glob: "npm:^4.0.3"
@@ -1832,31 +1864,31 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: 10c0/9b9e849f6b2d4e250840ef8e05f55a97d6598adaf48c1e6df83084b94c30feca6a3e7916ee1c235178188d0db6364a877cbf8fe218c36d5f8d5acb50767f3273
checksum: 10c0/e3b9bc1e925c07833237044271cdc9bd8bdba3e2143dcfc5bf3bf481c89731b666a6fad25333a4b1980ac2f4c6f5e6e42c71206f73f3704e319f6b3b67463a6a
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.8.0":
version: 8.8.0
resolution: "@typescript-eslint/utils@npm:8.8.0"
"@typescript-eslint/utils@npm:8.8.1":
version: 8.8.1
resolution: "@typescript-eslint/utils@npm:8.8.1"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.4.0"
"@typescript-eslint/scope-manager": "npm:8.8.0"
"@typescript-eslint/types": "npm:8.8.0"
"@typescript-eslint/typescript-estree": "npm:8.8.0"
"@typescript-eslint/scope-manager": "npm:8.8.1"
"@typescript-eslint/types": "npm:8.8.1"
"@typescript-eslint/typescript-estree": "npm:8.8.1"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
checksum: 10c0/fcf2dfd4a2d9491aa096a29c2c1fdd891ca3c13933d20cfea44e51b3d10a397e7ed9a9cd71ac9a29e8c4706264ae00c25a29394e2a6bda3291be298062901f2c
checksum: 10c0/954a2e85ae56a3ebefb6e41fb33c59ffa886963860536e9729a35ecea55eefdc58858c7aa126048c4a61f4fd9997b4f7601e7884ed2b3e4e7a46c9e4617a9f29
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.8.0":
version: 8.8.0
resolution: "@typescript-eslint/visitor-keys@npm:8.8.0"
"@typescript-eslint/visitor-keys@npm:8.8.1":
version: 8.8.1
resolution: "@typescript-eslint/visitor-keys@npm:8.8.1"
dependencies:
"@typescript-eslint/types": "npm:8.8.0"
"@typescript-eslint/types": "npm:8.8.1"
eslint-visitor-keys: "npm:^3.4.3"
checksum: 10c0/580ce74c9b09b9e6a6f3f0ac2d2f0c6a6b983a78ce3b2544822ee08107c57142858d674897f61ff32a9a5e8fca00c916545c159bb75d134f4380884642542d38
checksum: 10c0/6f917090b61277bd443aa851c532c4a9cc91ad57aedf185c5dff0c530f158cce84ef815833bd8deffa87f0bbf7a9f1abd1e02e30af2463c4e7f27c0c08f59080
languageName: node
linkType: hard
@@ -1864,23 +1896,23 @@ __metadata:
version: 0.0.0-use.local
resolution: "EMS-ESP@workspace:."
dependencies:
"@alova/adapter-xhr": "npm:2.0.7"
"@babel/core": "npm:^7.25.7"
"@alova/adapter-xhr": "npm:2.0.8"
"@babel/core": "npm:^7.25.8"
"@emotion/react": "npm:^11.13.3"
"@emotion/styled": "npm:^11.13.0"
"@eslint/js": "npm:^9.12.0"
"@mui/icons-material": "npm:^6.1.2"
"@mui/material": "npm:^6.1.2"
"@mui/icons-material": "npm:^6.1.3"
"@mui/material": "npm:^6.1.3"
"@preact/compat": "npm:^18.3.1"
"@preact/preset-vite": "npm:^2.9.1"
"@table-library/react-table-library": "npm:4.1.7"
"@trivago/prettier-plugin-sort-imports": "npm:^4.3.0"
"@types/formidable": "npm:^3"
"@types/node": "npm:^22.7.4"
"@types/node": "npm:^22.7.5"
"@types/react": "npm:^18.3.11"
"@types/react-dom": "npm:^18.3.0"
"@types/react-dom": "npm:^18.3.1"
"@types/react-router-dom": "npm:^5.3.3"
alova: "npm:3.0.17"
alova: "npm:3.1.0"
async-validator: "npm:^4.2.5"
concurrently: "npm:^9.0.1"
eslint: "npm:^9.12.0"
@@ -1893,13 +1925,13 @@ __metadata:
react: "npm:^18.3.1"
react-dom: "npm:^18.3.1"
react-icons: "npm:^5.3.0"
react-router-dom: "npm:^6.26.2"
react-toastify: "npm:^10.0.5"
react-router-dom: "npm:^6.27.0"
react-toastify: "npm:^10.0.6"
rollup-plugin-visualizer: "npm:^5.12.0"
terser: "npm:^5.34.1"
typesafe-i18n: "npm:^5.26.2"
typescript: "npm:^5.6.2"
typescript-eslint: "npm:8.8.0"
typescript: "npm:^5.6.3"
typescript-eslint: "npm:8.8.1"
vite: "npm:^5.4.8"
vite-plugin-imagemin: "npm:^0.6.1"
vite-tsconfig-paths: "npm:^5.0.1"
@@ -1962,13 +1994,13 @@ __metadata:
languageName: node
linkType: hard
"alova@npm:3.0.17":
version: 3.0.17
resolution: "alova@npm:3.0.17"
"alova@npm:3.1.0":
version: 3.1.0
resolution: "alova@npm:3.1.0"
dependencies:
"@alova/shared": "npm:^1.0.5"
"@alova/shared": "npm:^1.0.6"
rate-limiter-flexible: "npm:^5.0.3"
checksum: 10c0/e8a2ae885a3ff44dafec230d9388dc22b6445bb0cf8511fc9855b5a98ad9961941b0d33a7da874df23db4af0dba75872a470e3edebbdcc5ead8aecbc7fcc3d6b
checksum: 10c0/4f073e37fae112dff2c8b168d37220fa4c9475305d7689869c1338a91d1368f2b941c1131bc46214ddebfa72489b6c449b246e2b4ff484d1ff1486dd7172f694
languageName: node
linkType: hard
@@ -5971,39 +6003,39 @@ __metadata:
languageName: node
linkType: hard
"react-router-dom@npm:^6.26.2":
version: 6.26.2
resolution: "react-router-dom@npm:6.26.2"
"react-router-dom@npm:^6.27.0":
version: 6.27.0
resolution: "react-router-dom@npm:6.27.0"
dependencies:
"@remix-run/router": "npm:1.19.2"
react-router: "npm:6.26.2"
"@remix-run/router": "npm:1.20.0"
react-router: "npm:6.27.0"
peerDependencies:
react: ">=16.8"
react-dom: ">=16.8"
checksum: 10c0/7515128a98eef0a6b2bf354ef9dfefad03556a06be00fa9220eda6526aaada8a42f294911083473d7ced6d7128c3088bd193218bbb3d62593f9f4f7053781c23
checksum: 10c0/7db48ffd0b387af0eed060ceaf42075d074e63fbd30f4cf60993526b3610883a9ff82615965001165ed69d2bf2f1bce05c594a21c8d0d845e7b9bf203201116e
languageName: node
linkType: hard
"react-router@npm:6.26.2":
version: 6.26.2
resolution: "react-router@npm:6.26.2"
"react-router@npm:6.27.0":
version: 6.27.0
resolution: "react-router@npm:6.27.0"
dependencies:
"@remix-run/router": "npm:1.19.2"
"@remix-run/router": "npm:1.20.0"
peerDependencies:
react: ">=16.8"
checksum: 10c0/0d15a39b419c99fb5ccad76388bfc4ee2b01323b3b1b694595a9f9ea28e1fbeea25486b5398f5d3d93922f5c6a9aa751b6bb27419488d85279f6ca5ff9e0a6bb
checksum: 10c0/440d6ee00890cec92a0c2183164149fbb96363efccf52bb132a964f44e51aec2f4b5a0520c67f6f17faddaa4097090fd76f7efe58263947532fceeb11dd4cdf3
languageName: node
linkType: hard
"react-toastify@npm:^10.0.5":
version: 10.0.5
resolution: "react-toastify@npm:10.0.5"
"react-toastify@npm:^10.0.6":
version: 10.0.6
resolution: "react-toastify@npm:10.0.6"
dependencies:
clsx: "npm:^2.1.0"
peerDependencies:
react: ">=18"
react-dom: ">=18"
checksum: 10c0/66c68ec3d6c017d9f32652d73bb925224921c6a80b629b9d481430d5b4fd504abb7a99995a64b9aef0fc31326c74f3cbe088b3287b978dd0c355079c4bbf4158
checksum: 10c0/4042b716d008295d0feab32488d1e88ec655a1b7a9176fa7d253c70387578a8a0c04aca0ff86d20e1722f3b4baadae8970f50f462940d67a90453c307dd350a9
languageName: node
linkType: hard
@@ -7001,37 +7033,37 @@ __metadata:
languageName: node
linkType: hard
"typescript-eslint@npm:8.8.0":
version: 8.8.0
resolution: "typescript-eslint@npm:8.8.0"
"typescript-eslint@npm:8.8.1":
version: 8.8.1
resolution: "typescript-eslint@npm:8.8.1"
dependencies:
"@typescript-eslint/eslint-plugin": "npm:8.8.0"
"@typescript-eslint/parser": "npm:8.8.0"
"@typescript-eslint/utils": "npm:8.8.0"
"@typescript-eslint/eslint-plugin": "npm:8.8.1"
"@typescript-eslint/parser": "npm:8.8.1"
"@typescript-eslint/utils": "npm:8.8.1"
peerDependenciesMeta:
typescript:
optional: true
checksum: 10c0/545f0ce051282921aff56288baf288cffe6f7bafee5149f1b87af2c67f81f8c2088924a2e0fc0f0dcd12692b6a97eca10149a619c8c85d4aaef2fe763938da8d
checksum: 10c0/d6793697fce239ef8838ced6e1e59940c30579c8f62c49bc605fdeda9f3f7a5c24bfddd997b142f8c411859dc0b9985ecdae569814dd4f8e6775e1899d55e9cc
languageName: node
linkType: hard
"typescript@npm:^5.6.2":
version: 5.6.2
resolution: "typescript@npm:5.6.2"
"typescript@npm:^5.6.3":
version: 5.6.3
resolution: "typescript@npm:5.6.3"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 10c0/3ed8297a8c7c56b7fec282532503d1ac795239d06e7c4966b42d4330c6cf433a170b53bcf93a130a7f14ccc5235de5560df4f1045eb7f3550b46ebed16d3c5e5
checksum: 10c0/44f61d3fb15c35359bc60399cb8127c30bae554cd555b8e2b46d68fa79d680354b83320ad419ff1b81a0bdf324197b29affe6cc28988cd6a74d4ac60c94f9799
languageName: node
linkType: hard
"typescript@patch:typescript@npm%3A^5.6.2#optional!builtin<compat/typescript>":
version: 5.6.2
resolution: "typescript@patch:typescript@npm%3A5.6.2#optional!builtin<compat/typescript>::version=5.6.2&hash=8c6c40"
"typescript@patch:typescript@npm%3A^5.6.3#optional!builtin<compat/typescript>":
version: 5.6.3
resolution: "typescript@patch:typescript@npm%3A5.6.3#optional!builtin<compat/typescript>::version=5.6.3&hash=8c6c40"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 10c0/94eb47e130d3edd964b76da85975601dcb3604b0c848a36f63ac448d0104e93819d94c8bdf6b07c00120f2ce9c05256b8b6092d23cf5cf1c6fa911159e4d572f
checksum: 10c0/7c9d2e07c81226d60435939618c91ec2ff0b75fbfa106eec3430f0fcf93a584bc6c73176676f532d78c3594fe28a54b36eb40b3d75593071a7ec91301533ace7
languageName: node
linkType: hard