Merge pull request #1153 from proddy/dev

minor updates, can't remember what
This commit is contained in:
Proddy
2023-04-06 20:56:53 +02:00
committed by GitHub
28 changed files with 1354 additions and 1433 deletions

View File

@@ -4,6 +4,7 @@
"**/.pnp.*": true "**/.pnp.*": true
}, },
"eslint.nodePath": "interface/.yarn/sdks", "eslint.nodePath": "interface/.yarn/sdks",
"eslint.workingDirectories": ["interface"],
"prettier.prettierPath": "interface/.yarn/sdks/prettier/index.js", "prettier.prettierPath": "interface/.yarn/sdks/prettier/index.js",
"typescript.tsdk": "interface/.yarn/sdks/typescript/lib", "typescript.tsdk": "interface/.yarn/sdks/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true "typescript.enablePromptUseWorkspaceTsdk": true

View File

@@ -5,3 +5,5 @@ dist/
.eslintrc* .eslintrc*
.yarn/ .yarn/
env.d.ts env.d.ts
progmem-generator.js
vite.config.ts

View File

@@ -1,50 +0,0 @@
module.exports = {
plugins: ['@typescript-eslint', 'deprecation'],
extends: [
// By extending from a plugin config, we can get recommended rules without having to add them manually.
'eslint:recommended',
'plugin:react/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
'plugin:jsx-a11y/recommended',
'plugin:@typescript-eslint/recommended',
// This disables the formatting rules in ESLint that Prettier is going to be responsible for handling.
// Make sure it's always the last config, so it gets the chance to override other configs.
'eslint-config-prettier'
],
settings: {
react: {
// Tells eslint-plugin-react to automatically detect the version of React to use.
version: 'detect'
},
// Tells eslint how to resolve imports
'import/resolver': {
node: {
paths: ['src'],
extensions: ['.js', '.jsx', '.ts', '.tsx']
},
typescript: {
alwaysTryTypes: true // always try to resolve types under `<root>@types` directory even it doesn't contain any source code, like `@types/unist`
}
}
},
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
project: ['tsconfig.json'],
createDefaultProgram: true
},
rules: {
// Add your own rules here to override ones from the extended configs.
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'jsx-a11y/no-autofocus': 'off',
'react-refresh/only-export-components': 'off',
'no-console': 'warn',
'react/prop-types': 'off',
'react/self-closing-comp': 'warn',
'@typescript-eslint/consistent-type-definitions': ['off', 'type'],
'@typescript-eslint/explicit-function-return-type': 'off',
'deprecation/deprecation': 'warn'
}
};

64
interface/.eslintrc.json Normal file
View File

@@ -0,0 +1,64 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": [
"eslint:recommended",
"airbnb/hooks",
"airbnb-typescript",
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:prettier/recommended",
"plugin:import/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": "latest",
"sourceType": "module",
"tsconfigRootDir": ".",
"project": ["tsconfig.json"]
},
"plugins": ["react", "@typescript-eslint"],
"settings": {
"import/resolver": {
"typescript": {
"project": "./tsconfig.json"
}
},
"react": {
"version": "18.x"
}
},
"rules": {
"react-hooks/exhaustive-deps": "off",
"object-shorthand": "error",
"no-console": "warn",
"@typescript-eslint/consistent-type-definitions": ["off", "type"],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/restrict-plus-operands": "off",
"@typescript-eslint/no-unused-expressions": "off",
"@typescript-eslint/no-implied-eval": "off",
"@typescript-eslint/ban-types": [
"error",
{
"extendDefaults": true,
"types": {
"{}": false
}
}
]
}
}

View File

@@ -1,4 +1,5 @@
node_modules/ node_modules/
build/ build/
dist/
.prettierrc .prettierrc
.yarn/ .yarn/

View File

@@ -1,5 +1,5 @@
{ {
"adapter": "react", "adapter": "react",
"baseLocale": "pl", "baseLocale": "pl",
"$schema": "https://unpkg.com/typesafe-i18n@5.24.2/schema/typesafe-i18n.json" "$schema": "https://unpkg.com/typesafe-i18n@5.24.3/schema/typesafe-i18n.json"
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "eslint", "name": "eslint",
"version": "8.35.0-sdk", "version": "8.36.0-sdk",
"main": "./lib/api.js", "main": "./lib/api.js",
"type": "commonjs" "type": "commonjs"
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "prettier", "name": "prettier",
"version": "2.8.4-sdk", "version": "2.8.7-sdk",
"main": "./index.js", "main": "./index.js",
"type": "commonjs" "type": "commonjs"
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "typescript", "name": "typescript",
"version": "4.9.5-sdk", "version": "5.0.2-sdk",
"main": "./lib/typescript.js", "main": "./lib/typescript.js",
"type": "commonjs" "type": "commonjs"
} }

View File

@@ -3,7 +3,12 @@ plugins:
spec: '@yarnpkg/plugin-typescript' spec: '@yarnpkg/plugin-typescript'
yarnPath: .yarn/releases/yarn-3.4.1.cjs yarnPath: .yarn/releases/yarn-3.4.1.cjs
# uing pnp
# nodeLinker: pnp
# use these if not using PnP and have node_modules # use these if not using PnP and have node_modules
# compressionLevel: 0 nodeLinker: node-modules
# nmMode: hardlinks-local compressionLevel: 0
# enableGlobalCache: true nmMode: hardlinks-local
enableGlobalCache: true

View File

@@ -16,25 +16,24 @@
"standalone": "npm-run-all -p dev typesafe-i18n mock-api", "standalone": "npm-run-all -p dev typesafe-i18n mock-api",
"typesafe-i18n": "typesafe-i18n", "typesafe-i18n": "typesafe-i18n",
"format": "prettier --write '**/*.{ts,tsx,js,css,json,md}'", "format": "prettier --write '**/*.{ts,tsx,js,css,json,md}'",
"lint": "tsc --noEmit && eslint src/**/*.ts{,x} --cache --max-warnings=0", "lint": "eslint . --cache --max-warnings=0"
"lint:fix": "eslint src/**/*.ts{,x} --fix"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "^11.10.6", "@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6", "@emotion/styled": "^11.10.6",
"@msgpack/msgpack": "^2.8.0", "@msgpack/msgpack": "^2.8.0",
"@mui/icons-material": "^5.11.11", "@mui/icons-material": "^5.11.16",
"@mui/material": "^5.11.14", "@mui/material": "^5.11.16",
"@remix-run/router": "^1.4.0", "@remix-run/router": "^1.5.0",
"@table-library/react-table-library": "4.1.0", "@table-library/react-table-library": "4.1.0",
"@types/lodash-es": "^4.17.7", "@types/lodash-es": "^4.17.7",
"@types/node": "^18.15.7", "@types/node": "^18.15.11",
"@types/react": "^18.0.28", "@types/react": "^18.0.33",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"@yarnpkg/pnpify": "^4.0.0-rc.40", "@yarnpkg/pnpify": "^4.0.0-rc.42",
"async-validator": "^4.2.5", "async-validator": "^4.2.5",
"axios": "^1.3.4", "axios": "^1.3.5",
"history": "^5.3.0", "history": "^5.3.0",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
@@ -43,24 +42,23 @@
"react-dom": "latest", "react-dom": "latest",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",
"react-icons": "^4.8.0", "react-icons": "^4.8.0",
"react-router-dom": "^6.9.0", "react-router-dom": "^6.10.0",
"react-toastify": "^9.1.2", "react-toastify": "^9.1.2",
"sockette": "^2.0.6", "sockette": "^2.0.6",
"typesafe-i18n": "^5.24.2", "typesafe-i18n": "^5.24.3",
"typescript": "^5.0.2" "typescript": "^5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@types/mime-types": "^2", "@types/mime-types": "^2",
"@types/styled-components": "^5", "@types/styled-components": "^5",
"@typescript-eslint/eslint-plugin": "^5.56.0", "@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.56.0", "@typescript-eslint/parser": "^5.57.1",
"@vitejs/plugin-react-swc": "^3.2.0", "@vitejs/plugin-react-swc": "^3.2.0",
"eslint": "^8.36.0", "eslint": "^8.37.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
"eslint-import-resolver-typescript": "^3.5.3", "eslint-import-resolver-typescript": "^3.5.5",
"eslint-plugin-deprecation": "^1.3.3",
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
@@ -70,11 +68,11 @@
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "^2.8.7", "prettier": "^2.8.7",
"rollup-plugin-visualizer": "^5.9.0", "rollup-plugin-visualizer": "^5.9.0",
"terser": "^5.16.6", "terser": "^5.16.8",
"vite": "^4.2.1", "vite": "^4.2.1",
"vite-plugin-minify": "^1.5.2", "vite-plugin-minify": "^1.5.2",
"vite-plugin-svgr": "^2.4.0", "vite-plugin-svgr": "^2.4.0",
"vite-tsconfig-paths": "^4.0.7" "vite-tsconfig-paths": "^4.0.8"
}, },
"packageManager": "yarn@3.4.1" "packageManager": "yarn@3.4.1"
} }

View File

@@ -28,7 +28,7 @@ const App: FC = () => {
<AppRouting /> <AppRouting />
<ToastContainer <ToastContainer
position="bottom-left" position="bottom-left"
autoClose={2000} autoClose={3000}
hideProgressBar={false} hideProgressBar={false}
newestOnTop={false} newestOnTop={false}
closeOnClick={true} closeOnClick={true}

View File

@@ -30,6 +30,8 @@ import { ReactComponent as TRflag } from 'i18n/TR.svg';
const SignIn: FC = () => { const SignIn: FC = () => {
const authenticationContext = useContext(AuthenticationContext); const authenticationContext = useContext(AuthenticationContext);
const { LL, setLocale, locale } = useContext(I18nContext);
const [signInRequest, setSignInRequest] = useState<SignInRequest>({ const [signInRequest, setSignInRequest] = useState<SignInRequest>({
username: '', username: '',
password: '' password: ''
@@ -39,20 +41,6 @@ const SignIn: FC = () => {
const updateLoginRequestValue = updateValue(setSignInRequest); const updateLoginRequestValue = updateValue(setSignInRequest);
const validateAndSignIn = async () => {
setProcessing(true);
SIGN_IN_REQUEST_VALIDATOR.messages({
required: LL.IS_REQUIRED('%s')
});
try {
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
signIn();
} catch (errors: any) {
setFieldErrors(errors);
setProcessing(false);
}
};
const signIn = async () => { const signIn = async () => {
try { try {
const { data: loginResponse } = await AuthenticationApi.signIn(signInRequest); const { data: loginResponse } = await AuthenticationApi.signIn(signInRequest);
@@ -69,9 +57,21 @@ const SignIn: FC = () => {
} }
}; };
const submitOnEnter = onEnterCallback(signIn); const validateAndSignIn = async () => {
setProcessing(true);
SIGN_IN_REQUEST_VALIDATOR.messages({
required: LL.IS_REQUIRED('%s')
});
try {
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
signIn();
} catch (errors: any) {
setFieldErrors(errors);
setProcessing(false);
}
};
const { LL, setLocale, locale } = useContext(I18nContext); const submitOnEnter = onEnterCallback(signIn);
const selectLocale = async (loc: Locales) => { const selectLocale = async (loc: Locales) => {
localStorage.setItem('lang', loc); localStorage.setItem('lang', loc);

View File

@@ -7,8 +7,11 @@ export const API_BASE_URL = '/rest/';
export const ES_BASE_URL = '/es/'; export const ES_BASE_URL = '/es/';
export const EMSESP_API_BASE_URL = '/api/'; export const EMSESP_API_BASE_URL = '/api/';
export const ACCESS_TOKEN = 'access_token'; export const ACCESS_TOKEN = 'access_token';
export const WEB_SOCKET_ROOT = calculateWebSocketRoot(WS_BASE_URL);
export const EVENT_SOURCE_ROOT = calculateEventSourceRoot(ES_BASE_URL); const location = window.location;
const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
export const WEB_SOCKET_ROOT = webProtocol + '//' + location.host + WS_BASE_URL;
export const EVENT_SOURCE_ROOT = location.protocol + '//' + location.host + ES_BASE_URL;
export const AXIOS = axios.create({ export const AXIOS = axios.create({
baseURL: API_BASE_URL, baseURL: API_BASE_URL,
@@ -76,17 +79,6 @@ export const AXIOS_BIN = axios.create({
] ]
}); });
function calculateWebSocketRoot(webSocketPath: string) {
const location = window.location;
const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
return webProtocol + '//' + location.host + webSocketPath;
}
function calculateEventSourceRoot(endpointPath: string) {
const location = window.location;
return location.protocol + '//' + location.host + endpointPath;
}
export interface FileUploadConfig { export interface FileUploadConfig {
cancelToken?: CancelToken; cancelToken?: CancelToken;
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void; onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;

View File

@@ -1,4 +1,4 @@
import React, { FC } from 'react'; import { FC } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Tabs, useMediaQuery, useTheme } from '@mui/material'; import { Tabs, useMediaQuery, useTheme } from '@mui/material';
@@ -15,7 +15,7 @@ const RouterTabs: FC<RouterTabsProps> = ({ value, children }) => {
const theme = useTheme(); const theme = useTheme();
const smallDown = useMediaQuery(theme.breakpoints.down('sm')); const smallDown = useMediaQuery(theme.breakpoints.down('sm'));
const handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { const handleTabChange = (event: React.ChangeEvent<HTMLInputElement>, path: string) => {
navigate(path); navigate(path);
}; };

View File

@@ -1,4 +1,4 @@
import React, { FC, useContext } from 'react'; import { FC, useContext } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom'; import { Navigate, Route, Routes } from 'react-router-dom';
import { Tab } from '@mui/material'; import { Tab } from '@mui/material';

View File

@@ -1,4 +1,4 @@
import React, { FC, useState, useEffect } from 'react'; import { FC, useState, useEffect } from 'react';
import Schema, { ValidateFieldsError } from 'async-validator'; import Schema, { ValidateFieldsError } from 'async-validator';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';

View File

@@ -1,13 +1,13 @@
import React from 'react'; import { StrictMode } from 'react';
import ReactDOM from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import { createBrowserRouter, createRoutesFromElements, Route, RouterProvider } from 'react-router-dom'; import { createBrowserRouter, createRoutesFromElements, Route, RouterProvider } from 'react-router-dom';
import App from 'App'; import App from 'App';
const router = createBrowserRouter(createRoutesFromElements(<Route path="/*" element={<App />} />)); const router = createBrowserRouter(createRoutesFromElements(<Route path="/*" element={<App />} />));
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode> <StrictMode>
<RouterProvider router={router} /> <RouterProvider router={router} />
</React.StrictMode> </StrictMode>
); );

View File

@@ -102,7 +102,6 @@ const DashboardData: FC = () => {
text-transform: uppercase; text-transform: uppercase;
background-color: black; background-color: black;
color: #90CAF9; color: #90CAF9;
.th { .th {
border-bottom: 1px solid #565656; border-bottom: 1px solid #565656;
} }
@@ -111,18 +110,15 @@ const DashboardData: FC = () => {
background-color: #1e1e1e; background-color: #1e1e1e;
position: relative; position: relative;
cursor: pointer; cursor: pointer;
.td { .td {
padding: 8px; padding: 8px;
border-top: 1px solid #565656; border-top: 1px solid #565656;
border-bottom: 1px solid #565656; border-bottom: 1px solid #565656;
} }
&.tr.tr-body.row-select.row-select-single-selected { &.tr.tr-body.row-select.row-select-single-selected {
background-color: #3d4752; background-color: #3d4752;
font-weight: normal; font-weight: normal;
} }
&:hover .td { &:hover .td {
border-top: 1px solid #177ac9; border-top: 1px solid #177ac9;
border-bottom: 1px solid #177ac9; border-bottom: 1px solid #177ac9;
@@ -279,6 +275,53 @@ const DashboardData: FC = () => {
} }
); );
const fetchSensorData = async () => {
try {
setSensorData((await EMSESP.readSensorData()).data);
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
};
const fetchDeviceData = async (id: string) => {
const unique_id = parseInt(id);
try {
setDeviceData((await EMSESP.readDeviceData({ id: unique_id })).data);
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
};
const fetchCoreData = useCallback(async () => {
try {
setCoreData((await EMSESP.readCoreData()).data);
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
}, [LL]);
useEffect(() => {
fetchCoreData();
}, [fetchCoreData]);
const refreshDataIndex = (selectedDevice: string) => {
if (selectedDevice === 'sensor') {
fetchSensorData();
return;
}
setSensorData({ sensors: [], analogs: [] });
if (selectedDevice) {
fetchDeviceData(selectedDevice);
} else {
fetchCoreData();
}
};
const refreshData = () => {
refreshDataIndex(device_select.state.id);
};
function onSelectChange(action: any, state: any) { function onSelectChange(action: any, state: any) {
if (action.type === 'ADD_BY_ID_EXCLUSIVELY') { if (action.type === 'ADD_BY_ID_EXCLUSIVELY') {
refreshData(); refreshData();
@@ -337,36 +380,6 @@ const DashboardData: FC = () => {
); );
}; };
const refreshDataIndex = (selectedDevice: string) => {
if (selectedDevice === 'sensor') {
fetchSensorData();
return;
}
setSensorData({ sensors: [], analogs: [] });
if (selectedDevice) {
fetchDeviceData(selectedDevice);
} else {
fetchCoreData();
}
};
const refreshData = () => {
refreshDataIndex(device_select.state.id);
};
const fetchCoreData = useCallback(async () => {
try {
setCoreData((await EMSESP.readCoreData()).data);
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
}, [LL]);
useEffect(() => {
fetchCoreData();
}, [fetchCoreData]);
useEffect(() => { useEffect(() => {
const timer = setInterval(() => refreshData(), 60000); const timer = setInterval(() => refreshData(), 60000);
return () => { return () => {
@@ -375,23 +388,6 @@ const DashboardData: FC = () => {
// eslint-disable-next-line // eslint-disable-next-line
}, [analog, sensor, deviceValue, sensorData]); }, [analog, sensor, deviceValue, sensorData]);
const fetchDeviceData = async (id: string) => {
const unique_id = parseInt(id);
try {
setDeviceData((await EMSESP.readDeviceData({ id: unique_id })).data);
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
};
const fetchSensorData = async () => {
try {
setSensorData((await EMSESP.readSensorData()).data);
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
};
const isCmdOnly = (dv: DeviceValue) => dv.v === '' && dv.c; const isCmdOnly = (dv: DeviceValue) => dv.v === '' && dv.c;
const formatDurationMin = (duration_min: number) => { const formatDurationMin = (duration_min: number) => {

View File

@@ -63,14 +63,14 @@ const SettingsApplication: FC = () => {
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const [processingBoard, setProcessingBoard] = useState<boolean>(false); const [processingBoard, setProcessingBoard] = useState<boolean>(false);
const updateBoardProfile = async (board_profile: string) => { const updateBoardProfile = async (boardProfile: string) => {
setProcessingBoard(true); setProcessingBoard(true);
try { try {
const response = await EMSESP.getBoardProfile({ board_profile: board_profile }); const response = await EMSESP.getBoardProfile({ board_profile: boardProfile });
if (data) { if (data) {
setData({ setData({
...data, ...data,
board_profile: board_profile, board_profile: boardProfile,
led_gpio: response.data.led_gpio, led_gpio: response.data.led_gpio,
dallas_gpio: response.data.dallas_gpio, dallas_gpio: response.data.dallas_gpio,
rx_gpio: response.data.rx_gpio, rx_gpio: response.data.rx_gpio,
@@ -105,15 +105,15 @@ const SettingsApplication: FC = () => {
}; };
const changeBoardProfile = (event: React.ChangeEvent<HTMLInputElement>) => { const changeBoardProfile = (event: React.ChangeEvent<HTMLInputElement>) => {
const board_profile = event.target.value; const boardProfile = event.target.value;
updateFormValue(event); updateFormValue(event);
if (board_profile === 'CUSTOM') { if (boardProfile === 'CUSTOM') {
setData({ setData({
...data, ...data,
board_profile: board_profile board_profile: boardProfile
}); });
} else { } else {
updateBoardProfile(board_profile); updateBoardProfile(boardProfile);
} }
}; };

View File

@@ -70,6 +70,32 @@ const SettingsCustomization: FC = () => {
// eslint-disable-next-line // eslint-disable-next-line
const [masks, setMasks] = useState(() => ['']); const [masks, setMasks] = useState(() => ['']);
function hasEntityChanged(de: DeviceEntity) {
return (de?.cn || '') !== (de?.o_cn || '') || de.m !== de.o_m || de.ma !== de.o_ma || de.mi !== de.o_mi;
}
const getChanges = () => {
if (!deviceEntities || selectedDevice === -1) {
return [];
}
return deviceEntities
.filter((de) => hasEntityChanged(de))
.map(
(new_de) =>
new_de.m.toString(16).padStart(2, '0') +
new_de.id +
(new_de.cn || new_de.mi || new_de.ma ? '|' : '') +
(new_de.cn ? new_de.cn : '') +
(new_de.mi ? '>' + new_de.mi : '') +
(new_de.ma ? '<' + new_de.ma : '')
);
};
const countChanges = () => {
setNumChanges(getChanges().length);
};
useEffect(() => { useEffect(() => {
countChanges(); countChanges();
}); });
@@ -261,32 +287,6 @@ const SettingsCustomization: FC = () => {
} }
}; };
function hasEntityChanged(de: DeviceEntity) {
return (de?.cn || '') !== (de?.o_cn || '') || de.m !== de.o_m || de.ma !== de.o_ma || de.mi !== de.o_mi;
}
const getChanges = () => {
if (!deviceEntities || selectedDevice === -1) {
return [];
}
return deviceEntities
.filter((de) => hasEntityChanged(de))
.map(
(new_de) =>
new_de.m.toString(16).padStart(2, '0') +
new_de.id +
(new_de.cn || new_de.mi || new_de.ma ? '|' : '') +
(new_de.cn ? new_de.cn : '') +
(new_de.mi ? '>' + new_de.mi : '') +
(new_de.ma ? '<' + new_de.ma : '')
);
};
const countChanges = () => {
setNumChanges(getChanges().length);
};
const restart = async () => { const restart = async () => {
try { try {
await EMSESP.restart(); await EMSESP.restart();

View File

@@ -92,6 +92,26 @@ const SettingsScheduler: FC = () => {
return days.map((date) => formatter.format(date)); return days.map((date) => formatter.format(date));
} }
function hasScheduleChanged(si: ScheduleItem) {
return (
si.id !== si.o_id ||
(si?.name || '') !== (si?.o_name || '') ||
si.active !== si.o_active ||
si.deleted !== si.o_deleted ||
si.flags !== si.o_flags ||
si.time !== si.o_time ||
si.cmd !== si.o_cmd ||
si.value !== si.o_value
);
}
const getNumChanges = () => {
if (!schedule) {
return 0;
}
return schedule.filter((si) => hasScheduleChanged(si)).length;
};
useEffect(() => { useEffect(() => {
setNumChanges(getNumChanges()); setNumChanges(getNumChanges());
}); });
@@ -141,20 +161,6 @@ const SettingsScheduler: FC = () => {
` `
}); });
const fetchSchedule = useCallback(async () => {
try {
const response = await EMSESP.readSchedule();
setOriginalSchedule(response.data.schedule);
} catch (error) {
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
setDow(getDayNames());
}, [LL]);
useEffect(() => {
fetchSchedule();
}, [fetchSchedule]);
const setOriginalSchedule = (data: ScheduleItem[]) => { const setOriginalSchedule = (data: ScheduleItem[]) => {
setSchedule( setSchedule(
data.map((si) => ({ data.map((si) => ({
@@ -171,6 +177,20 @@ const SettingsScheduler: FC = () => {
); );
}; };
const fetchSchedule = useCallback(async () => {
try {
const response = await EMSESP.readSchedule();
setOriginalSchedule(response.data.schedule);
} catch (error) {
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
setDow(getDayNames());
}, [LL]);
useEffect(() => {
fetchSchedule();
}, [fetchSchedule]);
const getFlagNumber = (newFlag: string[]) => { const getFlagNumber = (newFlag: string[]) => {
let new_flag = 0; let new_flag = 0;
for (const entry of newFlag) { for (const entry of newFlag) {
@@ -208,26 +228,6 @@ const SettingsScheduler: FC = () => {
return new_flags; return new_flags;
}; };
function hasScheduleChanged(si: ScheduleItem) {
return (
si.id !== si.o_id ||
(si?.name || '') !== (si?.o_name || '') ||
si.active !== si.o_active ||
si.deleted !== si.o_deleted ||
si.flags !== si.o_flags ||
si.time !== si.o_time ||
si.cmd !== si.o_cmd ||
si.value !== si.o_value
);
}
const getNumChanges = () => {
if (!schedule) {
return 0;
}
return schedule.filter((si) => hasScheduleChanged(si)).length;
};
const saveSchedule = async () => { const saveSchedule = async () => {
if (schedule) { if (schedule) {
try { try {

View File

@@ -85,6 +85,16 @@ export const createSettingsValidator = (settings: Settings) =>
}) })
}); });
export const uniqueNameValidator = (schedule: ScheduleItem[], o_name?: string) => ({
validator(rule: InternalRuleItem, name: string, callback: (error?: string) => void) {
if (name && o_name && o_name !== name && schedule.find((si) => si.name === name)) {
callback('Name already in use');
} else {
callback();
}
}
});
export const schedulerItemValidation = (schedule: ScheduleItem[], scheduleItem: ScheduleItem) => export const schedulerItemValidation = (schedule: ScheduleItem[], scheduleItem: ScheduleItem) =>
new Schema({ new Schema({
name: [ name: [
@@ -100,13 +110,3 @@ export const schedulerItemValidation = (schedule: ScheduleItem[], scheduleItem:
{ type: 'string', min: 1, max: 64, message: 'Command must be 1-64 characters' } { type: 'string', min: 1, max: 64, message: 'Command must be 1-64 characters' }
] ]
}); });
export const uniqueNameValidator = (schedule: ScheduleItem[], o_name?: string) => ({
validator(rule: InternalRuleItem, name: string, callback: (error?: string) => void) {
if (name && o_name && o_name !== name && schedule.find((si) => si.name === name)) {
callback('Name already in use');
} else {
callback();
}
}
});

View File

@@ -8,6 +8,16 @@ export const SECURITY_SETTINGS_VALIDATOR = new Schema({
] ]
}); });
export const createUniqueUsernameValidator = (users: User[]) => ({
validator(rule: InternalRuleItem, username: string, callback: (error?: string) => void) {
if (username && users.find((u) => u.username === username)) {
callback('Username already in use');
} else {
callback();
}
}
});
export const createUserValidator = (users: User[], creating: boolean) => export const createUserValidator = (users: User[], creating: boolean) =>
new Schema({ new Schema({
username: [ username: [
@@ -24,13 +34,3 @@ export const createUserValidator = (users: User[], creating: boolean) =>
{ type: 'string', min: 1, max: 64, message: 'Password must be 1-64 characters' } { type: 'string', min: 1, max: 64, message: 'Password must be 1-64 characters' }
] ]
}); });
export const createUniqueUsernameValidator = (users: User[]) => ({
validator(rule: InternalRuleItem, username: string, callback: (error?: string) => void) {
if (username && users.find((u) => u.username === username)) {
callback('Username already in use');
} else {
callback();
}
}
});

View File

@@ -11,6 +11,7 @@
"strict": true, "strict": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"composite": true,
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Node", "moduleResolution": "Node",
"resolveJsonModule": true, "resolveJsonModule": true,
@@ -25,5 +26,6 @@
"@/*": ["src/*"] "@/*": ["src/*"]
} }
}, },
"include": ["src", "vite.config.ts", "progmem-generator.js"] "include": ["src/**/*", "vite.config.ts", "progmem-generator.js"],
"exclude": ["node_modules", "dist", "src/**/*.test.tsx", "src/**/*.test.ts"]
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
nodeLinker: pnp #nodeLinker: pnp
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.4.1.cjs yarnPath: .yarn/releases/yarn-3.4.1.cjs

View File

@@ -364,19 +364,19 @@ const emsesp_devices = {
{ {
i: 1, i: 1,
s: 'Thermostat (RC20/Moduline 300)', s: 'Thermostat (RC20/Moduline 300)',
t: 4, t: 5,
tn: 'thermostat' tn: 'thermostat'
}, },
{ {
i: 2, i: 2,
s: 'Boiler (Nefit GBx72/Trendline/Cerapur/Greenstar Si/27i)', s: 'Boiler (Nefit GBx72/Trendline/Cerapur/Greenstar Si/27i)',
t: 3, t: 4,
tn: 'boiler' tn: 'boiler'
}, },
{ {
i: 4, i: 4,
s: 'Thermostat (RC100/Moduline 1000/1010)', s: 'Thermostat (RC100/Moduline 1000/1010)',
t: 4, t: 5,
tn: 'thermostat' tn: 'thermostat'
} }
] ]
@@ -388,7 +388,7 @@ const emsesp_coredata = {
devices: [ devices: [
{ {
id: '2', id: '2',
t: 3, t: 4,
tn: 'Boiler', tn: 'Boiler',
b: 'Nefit', b: 'Nefit',
n: 'GBx72/Trendline/Cerapur/Greenstar Si/27i', n: 'GBx72/Trendline/Cerapur/Greenstar Si/27i',
@@ -399,7 +399,7 @@ const emsesp_coredata = {
}, },
{ {
id: '1', id: '1',
t: 4, t: 5,
tn: 'Thermostat', tn: 'Thermostat',
b: '', b: '',
n: 'RC20/Moduline 300', n: 'RC20/Moduline 300',
@@ -410,7 +410,7 @@ const emsesp_coredata = {
}, },
{ {
id: '4', id: '4',
t: 4, t: 5,
tn: 'Thermostat', tn: 'Thermostat',
b: 'Buderus', b: 'Buderus',
n: 'RC100/Moduline 1000/1010', n: 'RC100/Moduline 1000/1010',