From 8eb7793cd0c896f66242e64fc26313ce4ed41eb2 Mon Sep 17 00:00:00 2001 From: Proddy Date: Tue, 11 Apr 2023 18:30:45 +0200 Subject: [PATCH 01/89] update libs --- interface/.eslintrc.json | 1 + interface/package.json | 18 +- .../framework/security/ManageUsersForm.tsx | 1 - interface/src/i18n/en/index.ts | 4 +- interface/src/i18n/fr/index.ts | 4 +- interface/src/i18n/nl/index.ts | 4 +- interface/src/i18n/no/index.ts | 4 +- interface/src/i18n/pl/index.ts | 4 +- interface/src/i18n/sv/index.ts | 4 +- interface/src/i18n/tr/index.ts | 4 +- interface/src/project/SettingsEntities.tsx | 148 ++++--- interface/src/project/SettingsScheduler.tsx | 2 +- interface/src/project/api.ts | 24 +- interface/src/project/types.ts | 14 +- interface/src/project/validators.ts | 49 ++- interface/vite.config.ts | 14 - interface/yarn.lock | 378 ++++++++++-------- mock-api/server.js | 32 ++ 18 files changed, 385 insertions(+), 324 deletions(-) diff --git a/interface/.eslintrc.json b/interface/.eslintrc.json index 135736d0f..86703c739 100644 --- a/interface/.eslintrc.json +++ b/interface/.eslintrc.json @@ -51,6 +51,7 @@ "@typescript-eslint/restrict-plus-operands": "off", "@typescript-eslint/no-unused-expressions": "off", "@typescript-eslint/no-implied-eval": "off", + "@typescript-eslint/no-misused-promises": "off", "@typescript-eslint/ban-types": [ "error", { diff --git a/interface/package.json b/interface/package.json index de07d3a09..f182db090 100644 --- a/interface/package.json +++ b/interface/package.json @@ -23,12 +23,12 @@ "@emotion/styled": "^11.10.6", "@msgpack/msgpack": "^3.0.0-beta2", "@mui/icons-material": "^5.11.16", - "@mui/material": "^5.11.16", + "@mui/material": "^5.12.0", "@remix-run/router": "^1.5.0", "@table-library/react-table-library": "4.1.0", "@types/lodash-es": "^4.17.7", "@types/node": "^18.15.11", - "@types/react": "^18.0.33", + "@types/react": "^18.0.34", "@types/react-dom": "^18.0.11", "@types/react-router-dom": "^5.3.3", "@yarnpkg/pnpify": "^4.0.0-rc.42", @@ -46,15 +46,15 @@ "react-toastify": "^9.1.2", "sockette": "^2.0.6", "typesafe-i18n": "^5.24.3", - "typescript": "^5.0.3" + "typescript": "^5.0.4" }, "devDependencies": { "@types/mime-types": "^2", "@types/styled-components": "^5", - "@typescript-eslint/eslint-plugin": "^5.57.1", - "@typescript-eslint/parser": "^5.57.1", - "@vitejs/plugin-react-swc": "^3.2.0", - "eslint": "^8.37.0", + "@typescript-eslint/eslint-plugin": "^5.58.0", + "@typescript-eslint/parser": "^5.58.0", + "@vitejs/plugin-react-swc": "^3.3.0", + "eslint": "^8.38.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-prettier": "^8.8.0", @@ -68,11 +68,11 @@ "npm-run-all": "^4.1.5", "prettier": "^2.8.7", "rollup-plugin-visualizer": "^5.9.0", - "terser": "^5.16.8", + "terser": "^5.16.9", "vite": "^4.2.1", "vite-plugin-minify": "^1.5.2", "vite-plugin-svgr": "^2.4.0", - "vite-tsconfig-paths": "^4.0.8" + "vite-tsconfig-paths": "^4.1.0" }, "packageManager": "yarn@3.4.1" } diff --git a/interface/src/framework/security/ManageUsersForm.tsx b/interface/src/framework/security/ManageUsersForm.tsx index 43d7b45a5..57b6ea895 100644 --- a/interface/src/framework/security/ManageUsersForm.tsx +++ b/interface/src/framework/security/ManageUsersForm.tsx @@ -60,7 +60,6 @@ const ManageUsersForm: FC = () => { border-top: 1px solid #565656; border-bottom: 1px solid #565656; } - &:nth-of-type(odd) .td { background-color: #303030; } diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index 3f0b829f9..71139effb 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -319,8 +319,8 @@ const en: Translation = { SCHEDULE_TIMER_1: 'on startup', SCHEDULE_TIMER_2: 'every minute', SCHEDULE_TIMER_3: 'every hour', - CUSTOM_ENTITIES: 'Custom entities', - ENTITIES_HELP_1: 'Fetch custom entities from the EMS-bus' + CUSTOM_ENTITIES: 'Custom Entities', + ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus' }; export default en; diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts index aa91b2ca3..1d9734569 100644 --- a/interface/src/i18n/fr/index.ts +++ b/interface/src/i18n/fr/index.ts @@ -319,8 +319,8 @@ const fr: Translation = { SCHEDULE_TIMER_1: 'on startup', // TODO translate SCHEDULE_TIMER_2: 'every minute', // TODO translate SCHEDULE_TIMER_3: 'every hour', // TODO translate - CUSTOM_ENTITIES: 'Custom entities', - ENTITIES_HELP_1: 'Fetch custom entities from the EMS-bus' + CUSTOM_ENTITIES: 'Custom Entities', // TODO translate + ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus' // TODO translate }; export default fr; diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index ff0257861..c51914b62 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -319,8 +319,8 @@ const nl: Translation = { SCHEDULE_TIMER_1: 'on startup', // TODO translate SCHEDULE_TIMER_2: 'every minute', // TODO translate SCHEDULE_TIMER_3: 'every hour', // TODO translate - CUSTOM_ENTITIES: 'Custom entities', - ENTITIES_HELP_1: 'Fetch custom entities from the EMS-bus' + CUSTOM_ENTITIES: 'Custom Entities', // TODO translate + ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus' // TODO translate }; export default nl; diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts index ac7dfe79b..e8983b8af 100644 --- a/interface/src/i18n/no/index.ts +++ b/interface/src/i18n/no/index.ts @@ -319,8 +319,8 @@ const no: Translation = { SCHEDULE_TIMER_1: 'ved oppstart', SCHEDULE_TIMER_2: 'hvert minutt', SCHEDULE_TIMER_3: 'hver time', - CUSTOM_ENTITIES: 'Custom entities', - ENTITIES_HELP_1: 'Fetch custom entities from the EMS-bus' + CUSTOM_ENTITIES: 'Custom Entities', // TODO translate + ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus' // TODO translate }; export default no; diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index 5f1fff143..b91ecd04b 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -319,8 +319,8 @@ const pl: BaseTranslation = { SCHEDULE_TIMER_1: 'przy starcie', SCHEDULE_TIMER_2: 'co minutę', SCHEDULE_TIMER_3: 'co godzinę', - CUSTOM_ENTITIES: 'Custom entities', - ENTITIES_HELP_1: 'Fetch custom entities from the EMS-bus' + CUSTOM_ENTITIES: 'Custom Entities', // TODO translate + ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus' // TODO translate }; export default pl; diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts index e2f21039b..54a94f257 100644 --- a/interface/src/i18n/sv/index.ts +++ b/interface/src/i18n/sv/index.ts @@ -319,8 +319,8 @@ const sv: Translation = { SCHEDULE_TIMER_1: 'on startup', // TODO translate SCHEDULE_TIMER_2: 'every minute', // TODO translate SCHEDULE_TIMER_3: 'every hour', // TODO translate - CUSTOM_ENTITIES: 'Custom entities', - ENTITIES_HELP_1: 'Fetch custom entities from the EMS-bus' + CUSTOM_ENTITIES: 'Custom Entities', // TODO translate + ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus' // TODO translate }; export default sv; diff --git a/interface/src/i18n/tr/index.ts b/interface/src/i18n/tr/index.ts index ddbb41cb6..11858ba32 100644 --- a/interface/src/i18n/tr/index.ts +++ b/interface/src/i18n/tr/index.ts @@ -319,8 +319,8 @@ const tr: Translation = { SCHEDULE_TIMER_1: 'on startup', // TODO translate SCHEDULE_TIMER_2: 'every minute', // TODO translate SCHEDULE_TIMER_3: 'every hour', // TODO translate - CUSTOM_ENTITIES: 'Custom entities', - ENTITIES_HELP_1: 'Fetch custom entities from the EMS-bus' + CUSTOM_ENTITIES: 'Custom Entities', // TODO translate + ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus' // TODO translate }; export default tr; diff --git a/interface/src/project/SettingsEntities.tsx b/interface/src/project/SettingsEntities.tsx index 588888119..756472743 100644 --- a/interface/src/project/SettingsEntities.tsx +++ b/interface/src/project/SettingsEntities.tsx @@ -10,7 +10,6 @@ import { DialogActions, DialogContent, DialogTitle, - TextField, MenuItem, InputAdornment } from '@mui/material'; @@ -33,36 +32,62 @@ import { extractErrorMessage, updateValue } from 'utils'; import { validate } from 'validators'; import { entityItemValidation } from './validators'; +import { ValidateFieldsError } from 'async-validator'; import { useI18nContext } from 'i18n/i18n-react'; -import { ValidateFieldsError } from 'async-validator'; - import * as EMSESP from './api'; +function makeid() { + return Math.floor(Math.random() * (Math.floor(200) - 100) + 100); +} + const SettingsEntities: FC = () => { - const { LL, locale } = useI18nContext(); + const { LL } = useI18nContext(); const [numChanges, setNumChanges] = useState(0); const blocker = useBlocker(numChanges !== 0); const emptyEntity = { + id: 0, + name: '', device_id: 8, - type_id: 2, + type_id: 0, offset: 0, factor: 1, uom: 0, val_type: 2, - name: 'name', - deleted: false + deleted: false, + o_name: '' }; - const [entity, setEntity] = useState([emptyEntity]); + + const [entities, setEntities] = useState([emptyEntity]); const [entityItem, setEntityItem] = useState(); const [errorMessage, setErrorMessage] = useState(); const [creating, setCreating] = useState(false); - const [fieldErrors, setFieldErrors] = useState(); + function hasEntityChanged(ei: EntityItem) { + return ( + ei.id !== ei.o_id || + (ei?.name || '') !== (ei?.o_name || '') || + ei.device_id !== ei.o_device_id || + ei.type_id !== ei.o_type_id || + ei.offset !== ei.o_offset || + ei.uom !== ei.o_uom || + ei.factor !== ei.o_factor || + ei.val_type !== ei.o_val_type || + ei.deleted !== ei.o_deleted + ); + } + + const getNumChanges = () => { + if (!entities) { + return 0; + } + return entities.filter((ei) => hasEntityChanged(ei)).length; + }; + useEffect(() => { setNumChanges(getNumChanges()); }); @@ -78,6 +103,9 @@ const SettingsEntities: FC = () => { } `, BaseCell: ` + &:nth-of-type(1) { + padding: 8px; + } &:nth-of-type(2) { text-align: center; } @@ -88,7 +116,7 @@ const SettingsEntities: FC = () => { text-align: center; } &:nth-of-type(5) { - text-align: right; + text-align: center; } `, HeaderRow: ` @@ -118,23 +146,11 @@ const SettingsEntities: FC = () => { ` }); - const fetchEntities = useCallback(async () => { - try { - const response = await EMSESP.readEntities(); - setOriginalEntity(response.data.entity); - } catch (error) { - setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } - }, [LL]); - - useEffect(() => { - fetchEntities(); - }, [fetchEntities]); - const setOriginalEntity = (data: EntityItem[]) => { - setEntity( + setEntities( data.map((ei) => ({ ...ei, + o_id: ei.id, o_device_id: ei.device_id, o_type_id: ei.type_id, o_offset: ei.offset, @@ -147,41 +163,35 @@ const SettingsEntities: FC = () => { ); }; - function hasEntityChanged(ei: EntityItem) { - return ( - ei.device_id !== ei.o_device_id || - ei.type_id !== ei.o_type_id || - ei.name !== ei.o_name || - ei.offset !== ei.o_offset || - ei.uom !== ei.o_uom || - ei.factor !== ei.o_factor || - ei.val_type !== ei.o_val_type || - ei.deleted !== ei.o_deleted - ); - } - - const getNumChanges = () => { - if (!entity) { - return 0; + const fetchEntities = useCallback(async () => { + try { + const response = await EMSESP.readEntities(); + setOriginalEntity(response.data.entities); + } catch (error) { + setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING())); } - return entity.filter((ei) => hasEntityChanged(ei)).length; - }; + }, [LL]); - const saveEntity = async () => { - if (entity) { + useEffect(() => { + void fetchEntities(); + }, [fetchEntities]); + + const saveEntities = async () => { + if (entities) { try { const response = await EMSESP.writeEntities({ - entity: entity + entities: entities .filter((ei) => !ei.deleted) .map((condensed_ei) => { return { + id: condensed_ei.id, + name: condensed_ei.name, device_id: condensed_ei.device_id, type_id: condensed_ei.type_id, offset: condensed_ei.offset, factor: condensed_ei.factor, - val_type: condensed_ei.val_type, uom: condensed_ei.uom, - name: condensed_ei.name + val_type: condensed_ei.val_type }; }) }); @@ -193,7 +203,7 @@ const SettingsEntities: FC = () => { } catch (error) { toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); } - setOriginalEntity(entity); + setOriginalEntity(entities); } }; @@ -205,20 +215,22 @@ const SettingsEntities: FC = () => { const addEntityItem = () => { setCreating(true); setEntityItem({ - device_id: 8, - type_id: 2, + id: makeid(), + device_id: 11, + type_id: 0, offset: 0, factor: 1, val_type: 2, uom: 0, - name: 'name', + name: '', deleted: false }); }; const updateEntityItem = () => { + console.log('here'); if (entityItem) { - setEntity([...entity.filter((ei) => creating || ei.o_name !== entityItem.o_name), entityItem]); + setEntities([...entities.filter((ei) => creating || ei.o_id !== entityItem.o_id), entityItem]); } setEntityItem(undefined); }; @@ -233,24 +245,20 @@ const SettingsEntities: FC = () => { return new Intl.NumberFormat().format(value) + ' ' + DeviceValueUOM_s[uom]; } - function showHex(value: string, digit: number) { + function showHex(value: number, digit: number) { if (digit === 4) { - return '0x' + ('000' + value).slice(-4); + return '0x' + ('000' + value.toString(16).toUpperCase()).slice(-4); } - return '0x' + ('0' + value).slice(-2); + return '0x' + ('0' + value.toString(16).toUpperCase()).slice(-2); } const renderEntity = () => { - if (!entity) { + if (!entities) { return ; } return ( - !ei.deleted).sort((a, b) => a.name.localeCompare(b.time)) }} - theme={entity_theme} - layout={{ custom: true }} - > +
!ei.deleted) }} theme={entity_theme} layout={{ custom: true }}> {(tableList: any) => ( <>
@@ -259,7 +267,7 @@ const SettingsEntities: FC = () => { Device ID Type ID Offset - {LL.VALUE()} + {LL.VALUE(0)}
@@ -287,11 +295,16 @@ const SettingsEntities: FC = () => { const validateEntityItem = async () => { if (entityItem) { + console.log(1); try { setFieldErrors(undefined); - await validate(entityItemValidation(entity, entityItem), entityItem); + console.log(2); + await validate(entityItemValidation(entities, entityItem), entityItem); + console.log(3); updateEntityItem(); } catch (errors: any) { + console.log(4); + console.log(errors); setFieldErrors(errors); } } @@ -307,7 +320,7 @@ const SettingsEntities: FC = () => { return ( closeDialog()}> - {creating ? LL.ADD(1) + ' ' + LL.NEW() : LL.EDIT()} {LL.CUSTOM_ENTITIES()} + {creating ? LL.ADD(1) + ' ' + LL.NEW() : LL.EDIT()} {LL.ENTITY()} @@ -323,11 +336,12 @@ const SettingsEntities: FC = () => { value={entityItem.name} margin="normal" fullWidth - onChange={updateValue(setEntityItem)} + // onChange={updateValue(setEntityItem)} /> { { { startIcon={} variant="contained" color="info" - onClick={() => saveEntity()} + onClick={() => saveEntities()} > {LL.APPLY_CHANGES(numChanges)} diff --git a/interface/src/project/SettingsScheduler.tsx b/interface/src/project/SettingsScheduler.tsx index 344a67a63..f607c9dfb 100644 --- a/interface/src/project/SettingsScheduler.tsx +++ b/interface/src/project/SettingsScheduler.tsx @@ -72,12 +72,12 @@ const SettingsScheduler: FC = () => { name: '', o_name: '' }; + const [schedule, setSchedule] = useState([emptySchedule]); const [scheduleItem, setScheduleItem] = useState(); const [dow, setDow] = useState([]); const [errorMessage, setErrorMessage] = useState(); const [creating, setCreating] = useState(false); - const [fieldErrors, setFieldErrors] = useState(); // eslint-disable-next-line diff --git a/interface/src/project/api.ts b/interface/src/project/api.ts index ab394168a..155908975 100644 --- a/interface/src/project/api.ts +++ b/interface/src/project/api.ts @@ -97,18 +97,6 @@ export function getCustomizations(): AxiosPromise { return AXIOS.get('/getCustomizations'); } -export function getEntities(): AxiosPromise { - return AXIOS.get('/getEntities'); -} - -export function readEntities(): AxiosPromise { - return AXIOS.get('/entity'); -} - -export function writeEntities(entities: Entities): AxiosPromise { - return AXIOS.post('/entity', entities); -} - export function getSchedule(): AxiosPromise { return AXIOS.get('/getSchedule'); } @@ -120,3 +108,15 @@ export function readSchedule(): AxiosPromise { export function writeSchedule(schedule: Schedule): AxiosPromise { return AXIOS.post('/schedule', schedule); } + +export function getEntities(): AxiosPromise { + return AXIOS.get('/getEntities'); +} + +export function readEntities(): AxiosPromise { + return AXIOS.get('/entities'); +} + +export function writeEntities(entities: Entities): AxiosPromise { + return AXIOS.post('/entities', entities); +} diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index 2989614eb..afa87ecf8 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -339,25 +339,27 @@ export enum ScheduleFlag { } export interface EntityItem { + id: number; // unique number name: string; - device_id: string; - type_id: string; + device_id: number; + type_id: number; offset: number; factor: number; uom: number; val_type: number; value?: number; + deleted?: boolean; // optional + o_id?: number; o_name?: string; - o_device_id?: string; - o_type_id?: string; + o_device_id?: number; + o_type_id?: number; o_offset?: number; o_factor?: number; o_uom?: number; o_val_type?: number; - deleted?: boolean; // optional o_deleted?: boolean; } export interface Entities { - entity: EntityItem[]; + entities: EntityItem[]; } diff --git a/interface/src/project/validators.ts b/interface/src/project/validators.ts index 257078793..6417b06f4 100644 --- a/interface/src/project/validators.ts +++ b/interface/src/project/validators.ts @@ -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) => new Schema({ name: [ @@ -101,9 +111,9 @@ export const schedulerItemValidation = (schedule: ScheduleItem[], scheduleItem: ] }); -export const uniqueNameValidator = (schedule: ScheduleItem[], o_name?: string) => ({ +export const uniqueEntityNameValidator = (entities: EntityItem[], 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)) { + if (name && o_name && o_name !== name && entities.find((ei) => ei.name === name)) { callback('Name already in use'); } else { callback(); @@ -111,7 +121,7 @@ export const uniqueNameValidator = (schedule: ScheduleItem[], o_name?: string) = } }); -export const entityItemValidation = (entity: EntityItem[], entityItem: EntityItem) => +export const entityItemValidation = (entities: EntityItem[], entityItem: EntityItem) => new Schema({ name: [ { required: true, message: 'Name is required' }, @@ -120,28 +130,15 @@ export const entityItemValidation = (entity: EntityItem[], entityItem: EntityIte pattern: /^[a-zA-Z0-9_\\.]{1,15}$/, message: "Must be <15 characters: alpha numeric, '_' or '.'" }, - ...[uniqueEntityNameValidator(entity, entityItem.o_name)] + ...[uniqueEntityNameValidator(entities, entityItem.o_name)] ], - device_id: [ - { required: true, message: 'Device_id is required' }, - { type: 'string', pattern: /^[A-F0-9]{1,2}$/, message: 'Must be a hex number' } - ], - type_id: [ - { required: true, message: 'Type_id is required' }, - { type: 'string', pattern: /^[A-F0-9]{1,4}$/, message: 'Must be a hex number' } - ], - offset: [ - { required: true, message: 'Offset is required' }, - { type: 'number', min: 0, max: 255, message: 'Must be between 0 and 255' } - ] + device_id: [{ type: 'hex', required: true, message: 'ID must be a hex value' }] + // type_id: [ + // { required: true, message: 'Type_id is required' }, + // { type: 'string', pattern: /^[A-F0-9]{1,4}$/, message: 'Must be a hex number' } + // ], + // offset: [ + // { required: true, message: 'Offset is required' }, + // { type: 'number', min: 0, max: 255, message: 'Must be between 0 and 255' } + // ] }); - -export const uniqueEntityNameValidator = (entity: EntityItem[], o_name?: string) => ({ - validator(rule: InternalRuleItem, name: string, callback: (error?: string) => void) { - if (name && o_name && o_name !== name && entity.find((ei) => ei.name === name)) { - callback('Name already in use'); - } else { - callback(); - } - } -}); diff --git a/interface/vite.config.ts b/interface/vite.config.ts index 83f3cfe81..e2509f7eb 100644 --- a/interface/vite.config.ts +++ b/interface/vite.config.ts @@ -2,7 +2,6 @@ import { defineConfig, type PluginOption } from 'vite'; import react from '@vitejs/plugin-react-swc'; import viteTsconfigPaths from 'vite-tsconfig-paths'; import svgrPlugin from 'vite-plugin-svgr'; -// import { ViteMinifyPlugin } from 'vite-plugin-minify'; import { visualizer } from 'rollup-plugin-visualizer'; import ProgmemGenerator from './progmem-generator'; @@ -19,19 +18,6 @@ export default defineConfig(({ command, mode }) => { react(), viteTsconfigPaths(), svgrPlugin(), - // ViteMinifyPlugin({ - // removeAttributeQuotes: true, - // minifyCSS: true, - // minifyJS: true, - // decodeEntities: true, - // removeComments: true, - // removeEmptyAttributes: true, - // removeOptionalTags: true, - // removeEmptyElements: true, - // removeScriptTypeAttributes: true, - // removeTagWhitespace: true, - // minifyURLs: true - // }), ProgmemGenerator({ outputPath: '../lib/framework/WWWData.h', bytesPerLine: 20 }) ], diff --git a/interface/yarn.lock b/interface/yarn.lock index 813634ee0..be5a1cc4a 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -279,7 +279,7 @@ __metadata: languageName: node linkType: hard -"@emotion/cache@npm:^11.10.5": +"@emotion/cache@npm:^11.10.5, @emotion/cache@npm:^11.10.7": version: 11.10.7 resolution: "@emotion/cache@npm:11.10.7" dependencies: @@ -595,10 +595,10 @@ __metadata: languageName: node linkType: hard -"@eslint/js@npm:8.37.0": - version: 8.37.0 - resolution: "@eslint/js@npm:8.37.0" - checksum: 6abb3d97412ac960c7436ecdaa56eb00ac57c34782dc0901c82b259c32704e45044927b2910d786ec2127e548986d67e7ba29fec46abfb5d8fc9bedf379af2cf +"@eslint/js@npm:8.38.0": + version: 8.38.0 + resolution: "@eslint/js@npm:8.38.0" + checksum: e2f4b565d542758779b98019cfa63e24fc56fabfb8d04caf7f6310753703116b880b6a8061d671f2a40a68dba24a8a199eb01d5c8b140f53c49f05c75b404ff5 languageName: node linkType: hard @@ -703,14 +703,14 @@ __metadata: languageName: node linkType: hard -"@mui/base@npm:5.0.0-alpha.124": - version: 5.0.0-alpha.124 - resolution: "@mui/base@npm:5.0.0-alpha.124" +"@mui/base@npm:5.0.0-alpha.125": + version: 5.0.0-alpha.125 + resolution: "@mui/base@npm:5.0.0-alpha.125" dependencies: "@babel/runtime": ^7.21.0 "@emotion/is-prop-valid": ^1.2.0 - "@mui/types": ^7.2.3 - "@mui/utils": ^5.11.13 + "@mui/types": ^7.2.4 + "@mui/utils": ^5.12.0 "@popperjs/core": ^2.11.7 clsx: ^1.2.1 prop-types: ^15.8.1 @@ -722,14 +722,14 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 103a3af33f7125cf7320a6b5de49cc6223563978066ebd4a9721de385c2b7990ff5e14139e5bd290714bd0e5db58b9b960b08051d5c14219c5835ba1a0e0c587 + checksum: 0a87b5141500885c364382375816d23b48799e38c57993b0bbb2dbfce8052a8bdba588b2cd6cee75dc2fc43a873ce3d27a223ef1395c42a0b2e28d59e559b2bf languageName: node linkType: hard -"@mui/core-downloads-tracker@npm:^5.11.16": - version: 5.11.16 - resolution: "@mui/core-downloads-tracker@npm:5.11.16" - checksum: c9f1ed7a6f78ba9db80cb59c4a17ab4ef9af6837bf85ae87161fb745e64a617c91f7f1b35296c33c9039d42e61e49290eedae2e22ea16ded5a57e2c239c055a0 +"@mui/core-downloads-tracker@npm:^5.12.0": + version: 5.12.0 + resolution: "@mui/core-downloads-tracker@npm:5.12.0" + checksum: b0bc0c67be036fc6b965827ffb2ad2134c317237439dcbbe0b90a1807e92f93894e8d5e6650df4833b5a9b88b28d77cb2cd4435d23bbb9ab751c023684012e5f languageName: node linkType: hard @@ -749,16 +749,16 @@ __metadata: languageName: node linkType: hard -"@mui/material@npm:^5.11.16": - version: 5.11.16 - resolution: "@mui/material@npm:5.11.16" +"@mui/material@npm:^5.12.0": + version: 5.12.0 + resolution: "@mui/material@npm:5.12.0" dependencies: "@babel/runtime": ^7.21.0 - "@mui/base": 5.0.0-alpha.124 - "@mui/core-downloads-tracker": ^5.11.16 - "@mui/system": ^5.11.16 - "@mui/types": ^7.2.3 - "@mui/utils": ^5.11.13 + "@mui/base": 5.0.0-alpha.125 + "@mui/core-downloads-tracker": ^5.12.0 + "@mui/system": ^5.12.0 + "@mui/types": ^7.2.4 + "@mui/utils": ^5.12.0 "@types/react-transition-group": ^4.4.5 clsx: ^1.2.1 csstype: ^3.1.2 @@ -778,16 +778,16 @@ __metadata: optional: true "@types/react": optional: true - checksum: a674b179edd9af749d5a7edee6e2bf031b56b06311d6d0c567ec2a53936a0416443a74dcd02d360748442260f0829ad087ed5c1172d714fec2eb29d22683d234 + checksum: d8f2e875393dd254d70aafea08e1289d4cc4d085af581cd8fd4cc2882d5e265b8c926322ac64c1e0d18c5f441969abef2611c87346d685ad18fcfbc27e2d8ddf languageName: node linkType: hard -"@mui/private-theming@npm:^5.11.13": - version: 5.11.13 - resolution: "@mui/private-theming@npm:5.11.13" +"@mui/private-theming@npm:^5.12.0": + version: 5.12.0 + resolution: "@mui/private-theming@npm:5.12.0" dependencies: "@babel/runtime": ^7.21.0 - "@mui/utils": ^5.11.13 + "@mui/utils": ^5.12.0 prop-types: ^15.8.1 peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 @@ -795,16 +795,16 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 114801958fe0153b2c7c07640d5453dc783bebdc96e35612f18b90370a461bb0d5c24d0d3ea5b48dc369299660e4246f6d739a97aa47821e5d5b955329d38ec4 + checksum: c7ad031d9574a4b217400ce78f2f58a487fae5249ce46a124ae1a2814e8cc52848dd20fdff7ccc4e6342b83f5e77ec1d5b1eabc74777647fb1f7f0a4f8b5be25 languageName: node linkType: hard -"@mui/styled-engine@npm:^5.11.16": - version: 5.11.16 - resolution: "@mui/styled-engine@npm:5.11.16" +"@mui/styled-engine@npm:^5.12.0": + version: 5.12.0 + resolution: "@mui/styled-engine@npm:5.12.0" dependencies: "@babel/runtime": ^7.21.0 - "@emotion/cache": ^11.10.5 + "@emotion/cache": ^11.10.7 csstype: ^3.1.2 prop-types: ^15.8.1 peerDependencies: @@ -816,19 +816,19 @@ __metadata: optional: true "@emotion/styled": optional: true - checksum: 308b7406c7e4ade762c21339644803ad5510c96940e1bd99586cbcea9fa3468216fbace47ed603ea8106fff5d493dadba4ebac07e89175c3f12d50b7571c90d8 + checksum: 278ce278b0622866b4b8d1caab4ed151986c5f967830140783e1f5609bd5f9fe34c470daef8bcf2ba9706bc4fe8caae950227bb3853ddde3e5b2f45c2f90aad5 languageName: node linkType: hard -"@mui/system@npm:^5.11.16": - version: 5.11.16 - resolution: "@mui/system@npm:5.11.16" +"@mui/system@npm:^5.12.0": + version: 5.12.0 + resolution: "@mui/system@npm:5.12.0" dependencies: "@babel/runtime": ^7.21.0 - "@mui/private-theming": ^5.11.13 - "@mui/styled-engine": ^5.11.16 - "@mui/types": ^7.2.3 - "@mui/utils": ^5.11.13 + "@mui/private-theming": ^5.12.0 + "@mui/styled-engine": ^5.12.0 + "@mui/types": ^7.2.4 + "@mui/utils": ^5.12.0 clsx: ^1.2.1 csstype: ^3.1.2 prop-types: ^15.8.1 @@ -844,25 +844,25 @@ __metadata: optional: true "@types/react": optional: true - checksum: 53c699dd20dd0bc312b81844a5e2a55e184d13042300e5027ace490b89a41e6e01f857d7b8a21b95aad6e215b4467a0fa9bbf7b1bb1b877fe8429f986378fd4e + checksum: d53e70f35b8cc19c687ba72fc79a1f4cc20e0dd0335433fc255bc70291f89460b88bdddb7a9f17d11ffa6de3710117cc666c28ea0ac234fd2da13e82ee3c3c34 languageName: node linkType: hard -"@mui/types@npm:^7.2.3": - version: 7.2.3 - resolution: "@mui/types@npm:7.2.3" +"@mui/types@npm:^7.2.4": + version: 7.2.4 + resolution: "@mui/types@npm:7.2.4" peerDependencies: "@types/react": "*" peerDependenciesMeta: "@types/react": optional: true - checksum: d66575b6e5b37462bc4182ee5eae7f9f10792d4cdbe407fdb055136c62c3e05c6fa8969680373a723fbede0d1b1c17941c2cccd5a0409751a09aa22a712eafc0 + checksum: 17411effd184eff34d6a1a55b2249c7e1ef195bb30c48154f0b16fdce428ff55be4ec5dde8b4a556c01eda2d34e3dcc18d925f8fdee606f5bc15f91167f0ecbc languageName: node linkType: hard -"@mui/utils@npm:^5.11.13": - version: 5.11.13 - resolution: "@mui/utils@npm:5.11.13" +"@mui/utils@npm:^5.12.0": + version: 5.12.0 + resolution: "@mui/utils@npm:5.12.0" dependencies: "@babel/runtime": ^7.21.0 "@types/prop-types": ^15.7.5 @@ -871,7 +871,7 @@ __metadata: react-is: ^18.2.0 peerDependencies: react: ^17.0.0 || ^18.0.0 - checksum: ba9812784d90b3bbe7c56cc0daf35cd972b576789b2dedb4d1df73005bc55af58d01c11dd21f2104206a49d93f08b9ba1a19a63c605df7b4760a1a864f675ead + checksum: 81813844674de745c3834e40d224b02aae98bfee0ada2dd5c68256fd5e866eaaf11b59f575e5d14676c61699ad8d44350f9b72da3c49cdc4b23e6efead965944 languageName: node linkType: hard @@ -1100,90 +1100,90 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.3.46": - version: 1.3.46 - resolution: "@swc/core-darwin-arm64@npm:1.3.46" +"@swc/core-darwin-arm64@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-darwin-arm64@npm:1.3.49" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.3.46": - version: 1.3.46 - resolution: "@swc/core-darwin-x64@npm:1.3.46" +"@swc/core-darwin-x64@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-darwin-x64@npm:1.3.49" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.3.46": - version: 1.3.46 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.46" +"@swc/core-linux-arm-gnueabihf@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.49" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.3.46": - version: 1.3.46 - resolution: "@swc/core-linux-arm64-gnu@npm:1.3.46" +"@swc/core-linux-arm64-gnu@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-linux-arm64-gnu@npm:1.3.49" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.3.46": - version: 1.3.46 - resolution: "@swc/core-linux-arm64-musl@npm:1.3.46" +"@swc/core-linux-arm64-musl@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-linux-arm64-musl@npm:1.3.49" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.3.46": - version: 1.3.46 - resolution: "@swc/core-linux-x64-gnu@npm:1.3.46" +"@swc/core-linux-x64-gnu@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-linux-x64-gnu@npm:1.3.49" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.3.46": - version: 1.3.46 - resolution: "@swc/core-linux-x64-musl@npm:1.3.46" +"@swc/core-linux-x64-musl@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-linux-x64-musl@npm:1.3.49" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.3.46": - version: 1.3.46 - resolution: "@swc/core-win32-arm64-msvc@npm:1.3.46" +"@swc/core-win32-arm64-msvc@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-win32-arm64-msvc@npm:1.3.49" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.3.46": - version: 1.3.46 - resolution: "@swc/core-win32-ia32-msvc@npm:1.3.46" +"@swc/core-win32-ia32-msvc@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-win32-ia32-msvc@npm:1.3.49" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.3.46": - version: 1.3.46 - resolution: "@swc/core-win32-x64-msvc@npm:1.3.46" +"@swc/core-win32-x64-msvc@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-win32-x64-msvc@npm:1.3.49" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@swc/core@npm:^1.3.35": - version: 1.3.46 - resolution: "@swc/core@npm:1.3.46" +"@swc/core@npm:^1.3.42": + version: 1.3.49 + resolution: "@swc/core@npm:1.3.49" dependencies: - "@swc/core-darwin-arm64": 1.3.46 - "@swc/core-darwin-x64": 1.3.46 - "@swc/core-linux-arm-gnueabihf": 1.3.46 - "@swc/core-linux-arm64-gnu": 1.3.46 - "@swc/core-linux-arm64-musl": 1.3.46 - "@swc/core-linux-x64-gnu": 1.3.46 - "@swc/core-linux-x64-musl": 1.3.46 - "@swc/core-win32-arm64-msvc": 1.3.46 - "@swc/core-win32-ia32-msvc": 1.3.46 - "@swc/core-win32-x64-msvc": 1.3.46 + "@swc/core-darwin-arm64": 1.3.49 + "@swc/core-darwin-x64": 1.3.49 + "@swc/core-linux-arm-gnueabihf": 1.3.49 + "@swc/core-linux-arm64-gnu": 1.3.49 + "@swc/core-linux-arm64-musl": 1.3.49 + "@swc/core-linux-x64-gnu": 1.3.49 + "@swc/core-linux-x64-musl": 1.3.49 + "@swc/core-win32-arm64-msvc": 1.3.49 + "@swc/core-win32-ia32-msvc": 1.3.49 + "@swc/core-win32-x64-msvc": 1.3.49 peerDependencies: "@swc/helpers": ^0.5.0 dependenciesMeta: @@ -1207,7 +1207,10 @@ __metadata: optional: true "@swc/core-win32-x64-msvc": optional: true - checksum: 245c213425fecc16ae5a078ec0451f3567430609ef40ef4f1dc2fb795de86229d4203956b5b02ae304232cf93b25bbec3aaed23989d41fdb432aecc1cfdabc0e + peerDependenciesMeta: + "@swc/helpers": + optional: true + checksum: 3a8463047fb79a4bc5987cd24f935e19e59dfe967648c6abbc0b61f9197b76109de832fb03e5e6452459f9e2165b565f86dbc9ecd9506f58bd4befff8e0ddc47 languageName: node linkType: hard @@ -1407,7 +1410,7 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*, @types/react@npm:^18.0.33": +"@types/react@npm:*": version: 18.0.33 resolution: "@types/react@npm:18.0.33" dependencies: @@ -1418,6 +1421,17 @@ __metadata: languageName: node linkType: hard +"@types/react@npm:^18.0.34": + version: 18.0.34 + resolution: "@types/react@npm:18.0.34" + dependencies: + "@types/prop-types": "*" + "@types/scheduler": "*" + csstype: ^3.0.2 + checksum: 97e6ea3b5eea0b270c2c36f5cc44699fe21e30ceda4ffc6936ad40ff755bd8b16637f41d8b0bd20d50eb3261b0981e8f4822b2bd5805208532292499f6de340c + languageName: node + linkType: hard + "@types/responselike@npm:^1.0.0": version: 1.0.0 resolution: "@types/responselike@npm:1.0.0" @@ -1459,14 +1473,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^5.57.1": - version: 5.57.1 - resolution: "@typescript-eslint/eslint-plugin@npm:5.57.1" +"@typescript-eslint/eslint-plugin@npm:^5.58.0": + version: 5.58.0 + resolution: "@typescript-eslint/eslint-plugin@npm:5.58.0" dependencies: "@eslint-community/regexpp": ^4.4.0 - "@typescript-eslint/scope-manager": 5.57.1 - "@typescript-eslint/type-utils": 5.57.1 - "@typescript-eslint/utils": 5.57.1 + "@typescript-eslint/scope-manager": 5.58.0 + "@typescript-eslint/type-utils": 5.58.0 + "@typescript-eslint/utils": 5.58.0 debug: ^4.3.4 grapheme-splitter: ^1.0.4 ignore: ^5.2.0 @@ -1479,43 +1493,43 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 3847db76ed4a5df9cbb0f0155afa81951323a93ba37de3dca872b325502d0203da859a67f6d201bcfdf4985188b80227b7fd039206f7b921c96d33befe7ed25d + checksum: cda31ae6ff09c742f921304ea1a2a0d0823f7e20d7969ba6320ab14df2b3269b93a61eded96a59f01cfd24f28efb91e461e42bb09f493ed013936a899697a868 languageName: node linkType: hard -"@typescript-eslint/parser@npm:^5.57.1": - version: 5.57.1 - resolution: "@typescript-eslint/parser@npm:5.57.1" +"@typescript-eslint/parser@npm:^5.58.0": + version: 5.58.0 + resolution: "@typescript-eslint/parser@npm:5.58.0" dependencies: - "@typescript-eslint/scope-manager": 5.57.1 - "@typescript-eslint/types": 5.57.1 - "@typescript-eslint/typescript-estree": 5.57.1 + "@typescript-eslint/scope-manager": 5.58.0 + "@typescript-eslint/types": 5.58.0 + "@typescript-eslint/typescript-estree": 5.58.0 debug: ^4.3.4 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 4e2ea4694b261a25bca452db502666ddb4444cced9518eb2d34bd06d099885858307c9b320fd1aaeb45513811dc1984bbba370e5a8567671bad7fc5a0eb8bcc7 + checksum: fb7a4ce59eb803d29705e0134b6731823d9d7b56dd76a4de4ff07eb09d56cc851ed9988ecacdc2d0cbd929115a02ce564b0bb1b97d8732e05707dbe4332460ae languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:5.57.1": - version: 5.57.1 - resolution: "@typescript-eslint/scope-manager@npm:5.57.1" +"@typescript-eslint/scope-manager@npm:5.58.0": + version: 5.58.0 + resolution: "@typescript-eslint/scope-manager@npm:5.58.0" dependencies: - "@typescript-eslint/types": 5.57.1 - "@typescript-eslint/visitor-keys": 5.57.1 - checksum: f0905f70939980164c0205c6c6d3638bea40fd9afb1acc83632360e66725f185eee9da595721f0bcd57cc5f951224d9d47bd8dc9c9b2373920f9d236081a8344 + "@typescript-eslint/types": 5.58.0 + "@typescript-eslint/visitor-keys": 5.58.0 + checksum: 66c82609ac6c9cf00e163126619e7c487adc938f02e4567a2c26319916a175b9aee792aa80bd319a20848c834c6e599cd302c9f5b68c64b95d02f024f511ac66 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:5.57.1": - version: 5.57.1 - resolution: "@typescript-eslint/type-utils@npm:5.57.1" +"@typescript-eslint/type-utils@npm:5.58.0": + version: 5.58.0 + resolution: "@typescript-eslint/type-utils@npm:5.58.0" dependencies: - "@typescript-eslint/typescript-estree": 5.57.1 - "@typescript-eslint/utils": 5.57.1 + "@typescript-eslint/typescript-estree": 5.58.0 + "@typescript-eslint/utils": 5.58.0 debug: ^4.3.4 tsutils: ^3.21.0 peerDependencies: @@ -1523,23 +1537,23 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 21fb0653398d2d6d32e4bcb8fe4f4d802d63f0cd50f2e4b982f410b075f5441edffe64924dd1ba71f89eccef3b04eaae8c23543e7618723c7344914378ce3796 + checksum: 3ca4443f43b8263745afda3ff05517074da77d1dad25867845d386b29b012548b720d12334aca8bf15323a76557099e4ce3025a5a0fa84e070f6a4c1dc36d44e languageName: node linkType: hard -"@typescript-eslint/types@npm:5.57.1": - version: 5.57.1 - resolution: "@typescript-eslint/types@npm:5.57.1" - checksum: 549214a87a52359013270982ef4e69ce17d8a063717a8cb9d127425e76b9113a8db437b67e6802be2ba35c4c025cb37907b003cc29306a8d7d7800c58838aa38 +"@typescript-eslint/types@npm:5.58.0": + version: 5.58.0 + resolution: "@typescript-eslint/types@npm:5.58.0" + checksum: 3e5973909a5c585f5aebf919eec8ac213e9b5089c7357ea832ffa2bd39df70dce0b806d4bcc39a15e309830dfbf7bdf22d9808ab3c466729b8536e9d7e83eccc languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.57.1": - version: 5.57.1 - resolution: "@typescript-eslint/typescript-estree@npm:5.57.1" +"@typescript-eslint/typescript-estree@npm:5.58.0": + version: 5.58.0 + resolution: "@typescript-eslint/typescript-estree@npm:5.58.0" dependencies: - "@typescript-eslint/types": 5.57.1 - "@typescript-eslint/visitor-keys": 5.57.1 + "@typescript-eslint/types": 5.58.0 + "@typescript-eslint/visitor-keys": 5.58.0 debug: ^4.3.4 globby: ^11.1.0 is-glob: ^4.0.3 @@ -1548,46 +1562,46 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 3c5483a9bb8413ba117be12de334b9e533fe75320b5308e7d21a41661dd627b8a403575e5bce84ff9ca7b0678446dd6e98e73b2f05b9f4bb2d02a0a067b421d7 + checksum: 51c2a92217a1ccc01acf3c5c371b8c4b48b066cb6341441c35b74b6f3e13d21f81e0aed041215f3f4cf73320f5cd6cc3061801c51a3049958ee9a171a6efa196 languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.57.1": - version: 5.57.1 - resolution: "@typescript-eslint/utils@npm:5.57.1" +"@typescript-eslint/utils@npm:5.58.0": + version: 5.58.0 + resolution: "@typescript-eslint/utils@npm:5.58.0" dependencies: "@eslint-community/eslint-utils": ^4.2.0 "@types/json-schema": ^7.0.9 "@types/semver": ^7.3.12 - "@typescript-eslint/scope-manager": 5.57.1 - "@typescript-eslint/types": 5.57.1 - "@typescript-eslint/typescript-estree": 5.57.1 + "@typescript-eslint/scope-manager": 5.58.0 + "@typescript-eslint/types": 5.58.0 + "@typescript-eslint/typescript-estree": 5.58.0 eslint-scope: ^5.1.1 semver: ^7.3.7 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: bab8a94e30fc22fa6b3db5c6eee059697ce5e5e03f355c1fc465de184129b4824186a82235e14111c644d2b337721ae9f4d294a52a6f3dd4377edd35b922e3aa + checksum: 71ea338d9b67b59792e9d9a82b723acbee815534044294b169e3727f5394445d95a6200c919f0c28020bc5954df0f7110e9d0a4586e77ebebcd1662c06b30157 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:5.57.1": - version: 5.57.1 - resolution: "@typescript-eslint/visitor-keys@npm:5.57.1" +"@typescript-eslint/visitor-keys@npm:5.58.0": + version: 5.58.0 + resolution: "@typescript-eslint/visitor-keys@npm:5.58.0" dependencies: - "@typescript-eslint/types": 5.57.1 + "@typescript-eslint/types": 5.58.0 eslint-visitor-keys: ^3.3.0 - checksum: 9a84589334fe675d18ca3a572aa0caf1b4c7baa80857642aee40378f5c79cf9dddd06a0fec31d7ff6a73f577f68f910fb745d51728fac317b388b8de764561db + checksum: e41b0cf8bf766c491fe96e26b4cd20e6af4dbe85ff773a32887b7557ffd199117d8cdc86ceef5ce224d06c5e14d54a8edb679e58185f5a9c6b450615eaac6f30 languageName: node linkType: hard -"@vitejs/plugin-react-swc@npm:^3.2.0": - version: 3.2.0 - resolution: "@vitejs/plugin-react-swc@npm:3.2.0" +"@vitejs/plugin-react-swc@npm:^3.3.0": + version: 3.3.0 + resolution: "@vitejs/plugin-react-swc@npm:3.3.0" dependencies: - "@swc/core": ^1.3.35 + "@swc/core": ^1.3.42 peerDependencies: vite: ^4 - checksum: fa2e707ee67244596a8d8d2738875af904d0e30a108cb5788ada0815efebaf5001a3168b90093c2322502be36e87b131f22fb8eb7c5e351f6eb1893eb64299f5 + checksum: 29e7389540ac448328c3450cde81b4a48815a6a57bf823b3b94b4372084d0a7a7cabb39041e9d6b6c31ebdb7f5739e54d91cfc9ec29f724daa2b9b027ecc9376 languageName: node linkType: hard @@ -1718,23 +1732,23 @@ __metadata: "@emotion/styled": ^11.10.6 "@msgpack/msgpack": ^3.0.0-beta2 "@mui/icons-material": ^5.11.16 - "@mui/material": ^5.11.16 + "@mui/material": ^5.12.0 "@remix-run/router": ^1.5.0 "@table-library/react-table-library": 4.1.0 "@types/lodash-es": ^4.17.7 "@types/mime-types": ^2 "@types/node": ^18.15.11 - "@types/react": ^18.0.33 + "@types/react": ^18.0.34 "@types/react-dom": ^18.0.11 "@types/react-router-dom": ^5.3.3 "@types/styled-components": ^5 - "@typescript-eslint/eslint-plugin": ^5.57.1 - "@typescript-eslint/parser": ^5.57.1 - "@vitejs/plugin-react-swc": ^3.2.0 + "@typescript-eslint/eslint-plugin": ^5.58.0 + "@typescript-eslint/parser": ^5.58.0 + "@vitejs/plugin-react-swc": ^3.3.0 "@yarnpkg/pnpify": ^4.0.0-rc.42 async-validator: ^4.2.5 axios: ^1.3.5 - eslint: ^8.37.0 + eslint: ^8.38.0 eslint-config-airbnb: ^19.0.4 eslint-config-airbnb-typescript: ^17.0.0 eslint-config-prettier: ^8.8.0 @@ -1759,13 +1773,13 @@ __metadata: react-toastify: ^9.1.2 rollup-plugin-visualizer: ^5.9.0 sockette: ^2.0.6 - terser: ^5.16.8 + terser: ^5.16.9 typesafe-i18n: ^5.24.3 - typescript: ^5.0.3 + typescript: ^5.0.4 vite: ^4.2.1 vite-plugin-minify: ^1.5.2 vite-plugin-svgr: ^2.4.0 - vite-tsconfig-paths: ^4.0.8 + vite-tsconfig-paths: ^4.1.0 languageName: unknown linkType: soft @@ -3134,14 +3148,14 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8.37.0": - version: 8.37.0 - resolution: "eslint@npm:8.37.0" +"eslint@npm:^8.38.0": + version: 8.38.0 + resolution: "eslint@npm:8.38.0" dependencies: "@eslint-community/eslint-utils": ^4.2.0 "@eslint-community/regexpp": ^4.4.0 "@eslint/eslintrc": ^2.0.2 - "@eslint/js": 8.37.0 + "@eslint/js": 8.38.0 "@humanwhocodes/config-array": ^0.11.8 "@humanwhocodes/module-importer": ^1.0.1 "@nodelib/fs.walk": ^1.2.8 @@ -3180,7 +3194,7 @@ __metadata: text-table: ^0.2.0 bin: eslint: bin/eslint.js - checksum: 3798781652b1c32d8a4a654c2dd7f2abbff808a806ddd7fd8f86a49586a824854369943d49ee52dcda47b8b03cd49585820f2ab69fc61bb17a0d7677785a2cf7 + checksum: 1aeba0106770bd29834bb01550c72fa0ebea851ceeaef61d2860ecb455391992b316f222600939f11d12db2a7ea6fb9443f4aa137566f98f9f26af9fa40b96b5 languageName: node linkType: hard @@ -6042,7 +6056,7 @@ __metadata: languageName: node linkType: hard -"terser@npm:^5.10.0, terser@npm:^5.16.8": +"terser@npm:^5.10.0": version: 5.16.8 resolution: "terser@npm:5.16.8" dependencies: @@ -6056,6 +6070,20 @@ __metadata: languageName: node linkType: hard +"terser@npm:^5.16.9": + version: 5.16.9 + resolution: "terser@npm:5.16.9" + dependencies: + "@jridgewell/source-map": ^0.3.2 + acorn: ^8.5.0 + commander: ^2.20.0 + source-map-support: ~0.5.20 + bin: + terser: bin/terser + checksum: eb883b606aa698e314957aa2cf6e70c1dc632d0d2dcda13e7a2cc73569a05034721826c0d6f9b31c6bb08bbc4fc633b6591871814dada71da9d34af9e284dc4f + languageName: node + linkType: hard + "text-table@npm:^0.2.0": version: 0.2.0 resolution: "text-table@npm:0.2.0" @@ -6217,23 +6245,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.0.3": - version: 5.0.3 - resolution: "typescript@npm:5.0.3" +"typescript@npm:^5.0.4": + version: 5.0.4 + resolution: "typescript@npm:5.0.4" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 13221c7f7dd2aa9cf8ac2fb1b5ca3825ff3d97a43b4656fd22ca36c0ec30179643b5157d620c3e4af9d3fc9234a571bd3ea175624b3d37c494d7ed159b038df2 + checksum: 2f5bd1cead194905957cb34e220b1d6ff1662399adef8ec1864f74620922d860ee35b6e50eafb3b636ea6fd437195e454e1146cb630a4236b5095ed7617395c2 languageName: node linkType: hard -"typescript@patch:typescript@^5.0.3#~builtin": - version: 5.0.3 - resolution: "typescript@patch:typescript@npm%3A5.0.3#~builtin::version=5.0.3&hash=1f5320" +"typescript@patch:typescript@^5.0.4#~builtin": + version: 5.0.4 + resolution: "typescript@patch:typescript@npm%3A5.0.4#~builtin::version=5.0.4&hash=1f5320" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 7e50cd198d2f7f052ac4e10d9a9b6e88ed91b3434c32b6e82f278a4e33727b42fee5d33929ef8a633b409a6bf62fd4bb95df464809507b7a703a4c73269dce3c + checksum: db16dd188048c172051825c7e6eea3e6bf577020625e5635fb8265d22683897d9ed7579b41a3a3e961fba5728c58e324d52041e9ca21d0dfc4bafa8bcf8e7a4b languageName: node linkType: hard @@ -6337,9 +6365,9 @@ __metadata: languageName: node linkType: hard -"vite-tsconfig-paths@npm:^4.0.8": - version: 4.0.8 - resolution: "vite-tsconfig-paths@npm:4.0.8" +"vite-tsconfig-paths@npm:^4.1.0": + version: 4.1.0 + resolution: "vite-tsconfig-paths@npm:4.1.0" dependencies: debug: ^4.1.1 globrex: ^0.1.2 @@ -6349,7 +6377,7 @@ __metadata: peerDependenciesMeta: vite: optional: true - checksum: 754e404a085668cbd1da08e4ebac7aef09efc3357524273d34bb68ab4dd705b8a0cce9e98c7bd0828f1c525669bf5f165d09c8d81c47011c6a5185fc30bd60de + checksum: 9846dfdd7118067539728f88a3b5f6a109d01392fa83bef6ad0505def2b1ed24579a4955df7db4b3ab60e9a816867a48e8b508f34030ef0d20b773293c91298d languageName: node linkType: hard diff --git a/mock-api/server.js b/mock-api/server.js index e42412c42..23c8047cb 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -599,6 +599,32 @@ const emsesp_devicedata_4 = { ] }; +// CUSTOM ENTITIES +let emsesp_entities = { + entities: [ + { + id: 0, + name: 'test1', + device_id: 8, + type_id: 2, + offset: 0, + factor: 0, + uom: 2, + val_type: 2 + }, + { + id: 1, + name: 'test2', + device_id: 11, + type_id: 222, + offset: 2, + factor: 2, + uom: 4, + val_type: 5 + } + ] +}; + // SCHEDULE let emsesp_schedule = { schedule: [ @@ -1369,6 +1395,12 @@ rest_server.get(GET_SCHEDULE_ENDPOINT, (req, res) => { res.json(emsesp_schedule); }); +const GET_ENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'entities'; +rest_server.get(GET_ENTITIES_ENDPOINT, (req, res) => { + console.log('Sending Entities data'); + res.json(emsesp_entities); +}); + // start server const expressServer = rest_server.listen(port, () => console.log(`EMS-ESP REST API server running on http://localhost:${port}/`) From 6bd744f12e87bdb3710dac5e9a47725e00540a28 Mon Sep 17 00:00:00 2001 From: Proddy Date: Tue, 18 Apr 2023 22:04:00 +0200 Subject: [PATCH 02/89] refactor dialog to prevent multiple parent renders --- .vscode/settings.json | 17 +- interface/.eslintrc.json | 26 +- interface/package.json | 15 +- .../framework/network/WiFiNetworkSelector.tsx | 2 +- interface/src/i18n/de/index.ts | 5 +- interface/src/i18n/en/index.ts | 3 +- interface/src/i18n/fr/index.ts | 3 +- interface/src/i18n/nl/index.ts | 3 +- interface/src/i18n/no/index.ts | 3 +- interface/src/i18n/pl/index.ts | 3 +- interface/src/i18n/sv/index.ts | 4 +- interface/src/i18n/tr/index.ts | 3 +- interface/src/project/SettingsEntities.tsx | 330 ++++-------------- .../src/project/SettingsEntitiesDialog.tsx | 228 ++++++++++++ interface/src/project/SettingsScheduler.tsx | 35 +- interface/src/project/types.ts | 6 +- interface/src/project/validators.ts | 20 +- interface/src/validators/shared.ts | 10 +- interface/tsconfig.json | 3 +- interface/yarn.lock | 214 +++++++----- mock-api/server.js | 7 +- src/web/WebEntityService.cpp | 173 ++++----- src/web/WebEntityService.h | 10 +- 23 files changed, 622 insertions(+), 501 deletions(-) create mode 100644 interface/src/project/SettingsEntitiesDialog.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 4bba87e57..5afd93c63 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,20 @@ "eslint.workingDirectories": ["interface"], "prettier.prettierPath": "interface/.yarn/sdks/prettier/index.js", "typescript.tsdk": "interface/.yarn/sdks/typescript/lib", - "typescript.enablePromptUseWorkspaceTsdk": true + "typescript.enablePromptUseWorkspaceTsdk": true, + "files.associations": { + "*.tsx": "typescriptreact", + "*.tcc": "cpp", + "optional": "cpp", + "istream": "cpp", + "ostream": "cpp", + "ratio": "cpp", + "system_error": "cpp", + "array": "cpp", + "functional": "cpp", + "regex": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp" + } } diff --git a/interface/.eslintrc.json b/interface/.eslintrc.json index 86703c739..c207db972 100644 --- a/interface/.eslintrc.json +++ b/interface/.eslintrc.json @@ -24,7 +24,7 @@ "tsconfigRootDir": ".", "project": ["tsconfig.json"] }, - "plugins": ["react", "@typescript-eslint"], + "plugins": ["react", "@typescript-eslint", "autofix", "react-hooks"], "settings": { "import/resolver": { "typescript": { @@ -36,7 +36,6 @@ } }, "rules": { - "react-hooks/exhaustive-deps": "off", "object-shorthand": "error", "no-console": "warn", "@typescript-eslint/consistent-type-definitions": ["off", "type"], @@ -52,6 +51,29 @@ "@typescript-eslint/no-unused-expressions": "off", "@typescript-eslint/no-implied-eval": "off", "@typescript-eslint/no-misused-promises": "off", + "arrow-body-style": ["error", "as-needed"], + "react-hooks/exhaustive-deps": "error", + "@typescript-eslint/consistent-type-imports": [ + "error", + { + "prefer": "type-imports" + } + ], + // "autofix/no-unused-vars": [ + // "error", + // { + // "argsIgnorePattern": "^_", + // "ignoreRestSiblings": true, + // "destructuredArrayIgnorePattern": "^_" + // } + // ], + "react/self-closing-comp": [ + "error", + { + "component": true, + "html": true + } + ], "@typescript-eslint/ban-types": [ "error", { diff --git a/interface/package.json b/interface/package.json index f182db090..7e746c3a3 100644 --- a/interface/package.json +++ b/interface/package.json @@ -23,12 +23,12 @@ "@emotion/styled": "^11.10.6", "@msgpack/msgpack": "^3.0.0-beta2", "@mui/icons-material": "^5.11.16", - "@mui/material": "^5.12.0", + "@mui/material": "^5.12.1", "@remix-run/router": "^1.5.0", "@table-library/react-table-library": "4.1.0", "@types/lodash-es": "^4.17.7", "@types/node": "^18.15.11", - "@types/react": "^18.0.34", + "@types/react": "^18.0.37", "@types/react-dom": "^18.0.11", "@types/react-router-dom": "^5.3.3", "@yarnpkg/pnpify": "^4.0.0-rc.42", @@ -51,14 +51,15 @@ "devDependencies": { "@types/mime-types": "^2", "@types/styled-components": "^5", - "@typescript-eslint/eslint-plugin": "^5.58.0", - "@typescript-eslint/parser": "^5.58.0", + "@typescript-eslint/eslint-plugin": "^5.59.0", + "@typescript-eslint/parser": "^5.59.0", "@vitejs/plugin-react-swc": "^3.3.0", "eslint": "^8.38.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-prettier": "^8.8.0", "eslint-import-resolver-typescript": "^3.5.5", + "eslint-plugin-autofix": "^1.1.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-prettier": "^4.2.1", @@ -68,11 +69,11 @@ "npm-run-all": "^4.1.5", "prettier": "^2.8.7", "rollup-plugin-visualizer": "^5.9.0", - "terser": "^5.16.9", - "vite": "^4.2.1", + "terser": "^5.17.0", + "vite": "^4.2.2", "vite-plugin-minify": "^1.5.2", "vite-plugin-svgr": "^2.4.0", - "vite-tsconfig-paths": "^4.1.0" + "vite-tsconfig-paths": "^4.2.0" }, "packageManager": "yarn@3.4.1" } diff --git a/interface/src/framework/network/WiFiNetworkSelector.tsx b/interface/src/framework/network/WiFiNetworkSelector.tsx index 5233b7826..9be0d46e1 100644 --- a/interface/src/framework/network/WiFiNetworkSelector.tsx +++ b/interface/src/framework/network/WiFiNetworkSelector.tsx @@ -47,7 +47,7 @@ const WiFiNetworkSelector: FC = ({ networkList }) => { const renderNetwork = (network: WiFiNetwork) => { return ( - wifiConnectionContext.selectNetwork(network)}> + wifiConnectionContext.selectNetwork(network)}> {isNetworkOpen(network) ? : } diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index db5fbcbc4..ac9dcf90e 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -311,7 +311,7 @@ const de: Translation = { LEAVE: 'Verlassen', SCHEDULER: 'Planer', SCHEDULER_HELP_1: 'Fügen Sie eigene, geplante Befehle zur Automatisierung hinzu. Vergeben Sie einen Entitätsnamen um die Aktivierung über API/Mqtt zu steuern', - SCHEDULER_HELP_2: 'Use 00:00 to trigger on boot', // TODO translate + SCHEDULER_HELP_2: 'Use 00:00 to trigger once on start-up', // TODO translate SCHEDULE: 'Zeitplan', TIME: 'Zeit', TIMER: 'Timer', @@ -320,7 +320,8 @@ const de: Translation = { SCHEDULE_TIMER_2: 'jede Minute', SCHEDULE_TIMER_3: 'jede Stunde', CUSTOM_ENTITIES: 'Individuelle Entitäten', - ENTITIES_HELP_1: 'Abfrage von Werten auf dem EMS-Bus' + ENTITIES_HELP_1: 'Abfrage von Werten auf dem EMS-Bus', + WRITEABLE: 'Schreibbar' }; export default de; diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index 71139effb..ba5a5d8d7 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -320,7 +320,8 @@ const en: Translation = { SCHEDULE_TIMER_2: 'every minute', SCHEDULE_TIMER_3: 'every hour', CUSTOM_ENTITIES: 'Custom Entities', - ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus' + ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', + WRITEABLE: 'Writeable' }; export default en; diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts index 1d9734569..bac7958d0 100644 --- a/interface/src/i18n/fr/index.ts +++ b/interface/src/i18n/fr/index.ts @@ -320,7 +320,8 @@ const fr: Translation = { SCHEDULE_TIMER_2: 'every minute', // TODO translate SCHEDULE_TIMER_3: 'every hour', // TODO translate CUSTOM_ENTITIES: 'Custom Entities', // TODO translate - ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus' // TODO translate + ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate + WRITEABLE: 'Writeable' // TODO translate }; export default fr; diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index c51914b62..a468ba134 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -320,7 +320,8 @@ const nl: Translation = { SCHEDULE_TIMER_2: 'every minute', // TODO translate SCHEDULE_TIMER_3: 'every hour', // TODO translate CUSTOM_ENTITIES: 'Custom Entities', // TODO translate - ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus' // TODO translate + ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate + WRITEABLE: 'Writeable' // TODO translate }; export default nl; diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts index e8983b8af..34081696b 100644 --- a/interface/src/i18n/no/index.ts +++ b/interface/src/i18n/no/index.ts @@ -320,7 +320,8 @@ const no: Translation = { SCHEDULE_TIMER_2: 'hvert minutt', SCHEDULE_TIMER_3: 'hver time', CUSTOM_ENTITIES: 'Custom Entities', // TODO translate - ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus' // TODO translate + ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate + WRITEABLE: 'Writeable' // TODO translate }; export default no; diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index b91ecd04b..4a85f60e2 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -320,7 +320,8 @@ const pl: BaseTranslation = { SCHEDULE_TIMER_2: 'co minutę', SCHEDULE_TIMER_3: 'co godzinę', CUSTOM_ENTITIES: 'Custom Entities', // TODO translate - ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus' // TODO translate + ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', + WRITEABLE: 'Writeable' // TODO translate }; export default pl; diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts index 54a94f257..3d6873122 100644 --- a/interface/src/i18n/sv/index.ts +++ b/interface/src/i18n/sv/index.ts @@ -320,7 +320,9 @@ const sv: Translation = { SCHEDULE_TIMER_2: 'every minute', // TODO translate SCHEDULE_TIMER_3: 'every hour', // TODO translate CUSTOM_ENTITIES: 'Custom Entities', // TODO translate - ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus' // TODO translate + ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate + WRITEABLE: 'Writeable' // TODO translate + }; export default sv; diff --git a/interface/src/i18n/tr/index.ts b/interface/src/i18n/tr/index.ts index 11858ba32..61867b860 100644 --- a/interface/src/i18n/tr/index.ts +++ b/interface/src/i18n/tr/index.ts @@ -320,7 +320,8 @@ const tr: Translation = { SCHEDULE_TIMER_2: 'every minute', // TODO translate SCHEDULE_TIMER_3: 'every hour', // TODO translate CUSTOM_ENTITIES: 'Custom Entities', // TODO translate - ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus' // TODO translate + ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate + WRITEABLE: 'Writeable' // TODO translate }; export default tr; diff --git a/interface/src/project/SettingsEntities.tsx b/interface/src/project/SettingsEntities.tsx index 756472743..08163a86e 100644 --- a/interface/src/project/SettingsEntities.tsx +++ b/interface/src/project/SettingsEntities.tsx @@ -1,71 +1,40 @@ -import { FC, useState, useEffect, useCallback } from 'react'; +import type { FC } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { unstable_useBlocker as useBlocker } from 'react-router-dom'; -import { - Button, - Typography, - Box, - Grid, - Dialog, - DialogActions, - DialogContent, - DialogTitle, - MenuItem, - InputAdornment -} from '@mui/material'; +import { Button, Typography, Box } from '@mui/material'; import { useTheme } from '@table-library/react-table-library/theme'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; import { toast } from 'react-toastify'; -import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; import WarningIcon from '@mui/icons-material/Warning'; import CancelIcon from '@mui/icons-material/Cancel'; -import DoneIcon from '@mui/icons-material/Done'; import AddIcon from '@mui/icons-material/Add'; -import { ValidatedTextField, ButtonRow, FormLoader, SectionContent, BlockNavigation } from 'components'; +import { ButtonRow, FormLoader, SectionContent, BlockNavigation } from 'components'; -import { DeviceValueUOM_s, EntityItem } from './types'; -import { extractErrorMessage, updateValue } from 'utils'; +import SettingsEntitiesDialog from './SettingsEntitiesDialog'; -import { validate } from 'validators'; -import { entityItemValidation } from './validators'; -import { ValidateFieldsError } from 'async-validator'; +import type { EntityItem } from './types'; +import { DeviceValueUOM_s } from './types'; +import { extractErrorMessage } from 'utils'; import { useI18nContext } from 'i18n/i18n-react'; import * as EMSESP from './api'; - -function makeid() { - return Math.floor(Math.random() * (Math.floor(200) - 100) + 100); -} +import { entityItemValidation } from './validators'; const SettingsEntities: FC = () => { const { LL } = useI18nContext(); - const [numChanges, setNumChanges] = useState(0); const blocker = useBlocker(numChanges !== 0); - - const emptyEntity = { - id: 0, - name: '', - device_id: 8, - type_id: 0, - offset: 0, - factor: 1, - uom: 0, - val_type: 2, - deleted: false, - o_name: '' - }; - - const [entities, setEntities] = useState([emptyEntity]); - const [entityItem, setEntityItem] = useState(); + const [entities, setEntities] = useState([]); + const [selectedEntityItem, setSelectedEntityItem] = useState(); const [errorMessage, setErrorMessage] = useState(); const [creating, setCreating] = useState(false); - const [fieldErrors, setFieldErrors] = useState(); + const [dialogOpen, setDialogOpen] = useState(false); function hasEntityChanged(ei: EntityItem) { return ( @@ -76,21 +45,15 @@ const SettingsEntities: FC = () => { ei.offset !== ei.o_offset || ei.uom !== ei.o_uom || ei.factor !== ei.o_factor || - ei.val_type !== ei.o_val_type || + ei.value_type !== ei.o_value_type || + ei.writeable !== ei.o_writeable || ei.deleted !== ei.o_deleted ); } - const getNumChanges = () => { - if (!entities) { - return 0; - } - return entities.filter((ei) => hasEntityChanged(ei)).length; - }; - useEffect(() => { - setNumChanges(getNumChanges()); - }); + setNumChanges(entities ? entities.filter((ei) => hasEntityChanged(ei)).length : 0); + }, [entities]); const entity_theme = useTheme({ Table: ` @@ -156,8 +119,9 @@ const SettingsEntities: FC = () => { o_offset: ei.offset, o_factor: ei.factor, o_uom: ei.uom, - o_val_type: ei.val_type, + o_value_type: ei.value_type, o_name: ei.name, + o_writeable: ei.writeable, o_deleted: ei.deleted })) ); @@ -182,19 +146,19 @@ const SettingsEntities: FC = () => { const response = await EMSESP.writeEntities({ entities: entities .filter((ei) => !ei.deleted) - .map((condensed_ei) => { - return { - id: condensed_ei.id, - name: condensed_ei.name, - device_id: condensed_ei.device_id, - type_id: condensed_ei.type_id, - offset: condensed_ei.offset, - factor: condensed_ei.factor, - uom: condensed_ei.uom, - val_type: condensed_ei.val_type - }; - }) + .map((condensed_ei) => ({ + id: condensed_ei.id, + name: condensed_ei.name, + device_id: condensed_ei.device_id, + type_id: condensed_ei.type_id, + offset: condensed_ei.offset, + factor: condensed_ei.factor, + uom: condensed_ei.uom, + writeable: condensed_ei.writeable, + value_type: condensed_ei.value_type + })) }); + if (response.status === 200) { toast.success(LL.SUCCESS()); } else { @@ -207,42 +171,48 @@ const SettingsEntities: FC = () => { } }; - const editEntityItem = (ei: EntityItem) => { + const editEntityItem = useCallback((ei: EntityItem) => { setCreating(false); - setEntityItem(ei); + setSelectedEntityItem(ei); + setDialogOpen(true); + }, []); + + const onDialogClose = () => { + setDialogOpen(false); }; + const onDialogSave = (updatedItem: EntityItem) => { + if (creating) { + setEntities([...entities.filter((ei) => creating || ei.o_id !== updatedItem.o_id), updatedItem]); + } else { + setEntities(entities.map((ei) => (ei.id === updatedItem.id ? { ...ei, ...updatedItem } : ei))); + } + setDialogOpen(false); + }; + + // TODO need callback here too? const addEntityItem = () => { setCreating(true); - setEntityItem({ - id: makeid(), + setSelectedEntityItem({ + id: Math.floor(Math.random() * (Math.floor(200) - 100) + 100), device_id: 11, type_id: 0, offset: 0, factor: 1, - val_type: 2, + value_type: 2, uom: 0, name: '', + writeable: false, deleted: false }); - }; - - const updateEntityItem = () => { - console.log('here'); - if (entityItem) { - setEntities([...entities.filter((ei) => creating || ei.o_id !== entityItem.o_id), entityItem]); - } - setEntityItem(undefined); + setDialogOpen(true); }; function formatValue(value: any, uom: number) { if (value === undefined) { return ''; } - if (uom === 0) { - return new Intl.NumberFormat().format(value); - } - return new Intl.NumberFormat().format(value) + ' ' + DeviceValueUOM_s[uom]; + return new Intl.NumberFormat().format(value) + (uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom]); } function showHex(value: number, digit: number) { @@ -287,185 +257,6 @@ const SettingsEntities: FC = () => { ); }; - const removeEntityItem = (ei: EntityItem) => { - ei.deleted = true; - setEntityItem(ei); - updateEntityItem(); - }; - - const validateEntityItem = async () => { - if (entityItem) { - console.log(1); - try { - setFieldErrors(undefined); - console.log(2); - await validate(entityItemValidation(entities, entityItem), entityItem); - console.log(3); - updateEntityItem(); - } catch (errors: any) { - console.log(4); - console.log(errors); - setFieldErrors(errors); - } - } - }; - - const closeDialog = () => { - setEntityItem(undefined); - setFieldErrors(undefined); - }; - - const renderEditEntity = () => { - if (entityItem) { - return ( - closeDialog()}> - - {creating ? LL.ADD(1) + ' ' + LL.NEW() : LL.EDIT()} {LL.ENTITY()} - - - - - - - - - - - - 0x - }} - /> - - - 0x - }} - /> - - - - - - - BOOL - INT - UINT - SHORT - USHORT - ULONG - TIME - - - {entityItem.val_type !== 0 && ( - <> - - - - - - {DeviceValueUOM_s.map((val, i) => ( - - {val} - - ))} - - - - )} - - - - {!creating && ( - - - - )} - - - - - ); - } - }; - return ( {blocker ? : null} @@ -473,10 +264,21 @@ const SettingsEntities: FC = () => { {LL.ENTITIES_HELP_1()} {renderEntity()} - {renderEditEntity()} + + {selectedEntityItem && ( + + )} + - {numChanges !== 0 && ( + {numChanges > 0 && ( + + )} + + + + + ); +}; + +export default SettingsEntitiesDialog; diff --git a/interface/src/project/SettingsScheduler.tsx b/interface/src/project/SettingsScheduler.tsx index f607c9dfb..cf1f84277 100644 --- a/interface/src/project/SettingsScheduler.tsx +++ b/interface/src/project/SettingsScheduler.tsx @@ -1,4 +1,5 @@ -import { FC, useState, useEffect, useCallback } from 'react'; +import type { FC } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { unstable_useBlocker as useBlocker } from 'react-router-dom'; import { @@ -43,9 +44,10 @@ import { extractErrorMessage, updateValue } from 'utils'; import { validate } from 'validators'; import { schedulerItemValidation } from './validators'; -import { ValidateFieldsError } from 'async-validator'; +import type { ValidateFieldsError } from 'async-validator'; -import { ScheduleItem, ScheduleFlag } from './types'; +import type { ScheduleItem } from './types'; +import { ScheduleFlag } from './types'; import { useI18nContext } from 'i18n/i18n-react'; @@ -105,6 +107,7 @@ const SettingsScheduler: FC = () => { ); } + // TODO fix const getNumChanges = () => { if (!schedule) { return 0; @@ -184,12 +187,12 @@ const SettingsScheduler: FC = () => { } catch (error) { setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING())); } - setDow(getDayNames()); }, [LL]); useEffect(() => { - fetchSchedule(); - }, [fetchSchedule]); + void fetchSchedule(); + setDow(getDayNames()); + }, [getDayNames, fetchSchedule]); const getFlagNumber = (newFlag: string[]) => { let new_flag = 0; @@ -234,17 +237,15 @@ const SettingsScheduler: FC = () => { const response = await EMSESP.writeSchedule({ schedule: schedule .filter((si) => !si.deleted) - .map((condensed_si) => { - return { - id: condensed_si.id, - active: condensed_si.active, - flags: condensed_si.flags, - time: condensed_si.time, - cmd: condensed_si.cmd, - value: condensed_si.value, - name: condensed_si.name - }; - }) + .map((condensed_si) => ({ + id: condensed_si.id, + active: condensed_si.active, + flags: condensed_si.flags, + time: condensed_si.time, + cmd: condensed_si.cmd, + value: condensed_si.value, + name: condensed_si.name + })) }); if (response.status === 200) { toast.success(LL.SCHEDULE_SAVED()); diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index afa87ecf8..2fa83fcab 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -346,8 +346,9 @@ export interface EntityItem { offset: number; factor: number; uom: number; - val_type: number; + value_type: number; value?: number; + writeable: boolean; deleted?: boolean; // optional o_id?: number; o_name?: string; @@ -356,8 +357,9 @@ export interface EntityItem { o_offset?: number; o_factor?: number; o_uom?: number; - o_val_type?: number; + o_value_type?: number; o_deleted?: boolean; + o_writeable?: boolean; } export interface Entities { diff --git a/interface/src/project/validators.ts b/interface/src/project/validators.ts index 6417b06f4..3d7e9b795 100644 --- a/interface/src/project/validators.ts +++ b/interface/src/project/validators.ts @@ -1,6 +1,7 @@ -import Schema, { InternalRuleItem } from 'async-validator'; +import type { InternalRuleItem } from 'async-validator'; +import Schema from 'async-validator'; import { IP_OR_HOSTNAME_VALIDATOR } from 'validators/shared'; -import { Settings, ScheduleItem, EntityItem } from './types'; +import type { Settings, ScheduleItem, EntityItem } from './types'; export const GPIO_VALIDATOR = { validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) { @@ -111,17 +112,7 @@ export const schedulerItemValidation = (schedule: ScheduleItem[], scheduleItem: ] }); -export const uniqueEntityNameValidator = (entities: EntityItem[], o_name?: string) => ({ - validator(rule: InternalRuleItem, name: string, callback: (error?: string) => void) { - if (name && o_name && o_name !== name && entities.find((ei) => ei.name === name)) { - callback('Name already in use'); - } else { - callback(); - } - } -}); - -export const entityItemValidation = (entities: EntityItem[], entityItem: EntityItem) => +export const entityItemValidation = (entities: EntityItem[], creating: boolean) => new Schema({ name: [ { required: true, message: 'Name is required' }, @@ -129,8 +120,7 @@ export const entityItemValidation = (entities: EntityItem[], entityItem: EntityI type: 'string', pattern: /^[a-zA-Z0-9_\\.]{1,15}$/, message: "Must be <15 characters: alpha numeric, '_' or '.'" - }, - ...[uniqueEntityNameValidator(entities, entityItem.o_name)] + } ], device_id: [{ type: 'hex', required: true, message: 'ID must be a hex value' }] // type_id: [ diff --git a/interface/src/validators/shared.ts b/interface/src/validators/shared.ts index 9f0c42e5c..a028b5893 100644 --- a/interface/src/validators/shared.ts +++ b/interface/src/validators/shared.ts @@ -1,12 +1,13 @@ -import Schema, { InternalRuleItem, ValidateOption } from 'async-validator'; +import type { InternalRuleItem, ValidateOption } from 'async-validator'; +import type Schema from 'async-validator'; export const validate = ( validator: Schema, source: Partial, options?: ValidateOption -): Promise => { - return new Promise((resolve, reject) => { - validator.validate(source, options ? options : {}, (errors, fieldErrors) => { +): Promise => + new Promise((resolve, reject) => { + void validator.validate(source, options ? options : {}, (errors, fieldErrors) => { if (errors) { reject(fieldErrors); } else { @@ -14,7 +15,6 @@ export const validate = ( } }); }); -}; // updated to support both IPv4 and IPv6 const IP_ADDRESS_REGEXP = diff --git a/interface/tsconfig.json b/interface/tsconfig.json index d50dac669..6a77480de 100644 --- a/interface/tsconfig.json +++ b/interface/tsconfig.json @@ -13,7 +13,8 @@ "noFallthroughCasesInSwitch": true, "composite": true, "module": "ESNext", - "moduleResolution": "Node", + // "moduleResolution": "Node", + "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, diff --git a/interface/yarn.lock b/interface/yarn.lock index be5a1cc4a..7054d0cc2 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -703,9 +703,9 @@ __metadata: languageName: node linkType: hard -"@mui/base@npm:5.0.0-alpha.125": - version: 5.0.0-alpha.125 - resolution: "@mui/base@npm:5.0.0-alpha.125" +"@mui/base@npm:5.0.0-alpha.126": + version: 5.0.0-alpha.126 + resolution: "@mui/base@npm:5.0.0-alpha.126" dependencies: "@babel/runtime": ^7.21.0 "@emotion/is-prop-valid": ^1.2.0 @@ -722,14 +722,14 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 0a87b5141500885c364382375816d23b48799e38c57993b0bbb2dbfce8052a8bdba588b2cd6cee75dc2fc43a873ce3d27a223ef1395c42a0b2e28d59e559b2bf + checksum: afb6b99cba541cdb40bb098a78d105721cd6c6db3156fe9923bbb0aea184f6949ab5db79ab95177b4edb011acb35ac34556124b1203fd91ea16f52dc5477da7b languageName: node linkType: hard -"@mui/core-downloads-tracker@npm:^5.12.0": - version: 5.12.0 - resolution: "@mui/core-downloads-tracker@npm:5.12.0" - checksum: b0bc0c67be036fc6b965827ffb2ad2134c317237439dcbbe0b90a1807e92f93894e8d5e6650df4833b5a9b88b28d77cb2cd4435d23bbb9ab751c023684012e5f +"@mui/core-downloads-tracker@npm:^5.12.1": + version: 5.12.1 + resolution: "@mui/core-downloads-tracker@npm:5.12.1" + checksum: 0fc90f840e888e0a671ce43dfaa50018c7f2b82379b3adf3128387c3f29d06cdc235493939698339eb2aab49cf9a167473ed95f6a64eb424ee2d95849dd4aa76 languageName: node linkType: hard @@ -749,14 +749,14 @@ __metadata: languageName: node linkType: hard -"@mui/material@npm:^5.12.0": - version: 5.12.0 - resolution: "@mui/material@npm:5.12.0" +"@mui/material@npm:^5.12.1": + version: 5.12.1 + resolution: "@mui/material@npm:5.12.1" dependencies: "@babel/runtime": ^7.21.0 - "@mui/base": 5.0.0-alpha.125 - "@mui/core-downloads-tracker": ^5.12.0 - "@mui/system": ^5.12.0 + "@mui/base": 5.0.0-alpha.126 + "@mui/core-downloads-tracker": ^5.12.1 + "@mui/system": ^5.12.1 "@mui/types": ^7.2.4 "@mui/utils": ^5.12.0 "@types/react-transition-group": ^4.4.5 @@ -778,7 +778,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: d8f2e875393dd254d70aafea08e1289d4cc4d085af581cd8fd4cc2882d5e265b8c926322ac64c1e0d18c5f441969abef2611c87346d685ad18fcfbc27e2d8ddf + checksum: a4b4becea4da7af787d58b09afe6a3e1c7b1aa1b27f93186f848efd0bcb250e5b39ca51cdbcedc0216db32049665a69a13a2f8480d357f2310ca6efcf8b10a76 languageName: node linkType: hard @@ -820,9 +820,9 @@ __metadata: languageName: node linkType: hard -"@mui/system@npm:^5.12.0": - version: 5.12.0 - resolution: "@mui/system@npm:5.12.0" +"@mui/system@npm:^5.12.1": + version: 5.12.1 + resolution: "@mui/system@npm:5.12.1" dependencies: "@babel/runtime": ^7.21.0 "@mui/private-theming": ^5.12.0 @@ -844,7 +844,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: d53e70f35b8cc19c687ba72fc79a1f4cc20e0dd0335433fc255bc70291f89460b88bdddb7a9f17d11ffa6de3710117cc666c28ea0ac234fd2da13e82ee3c3c34 + checksum: a9dc1e3503f8c036663d0bc38b9bed71f67833890148a29efba5501f0da1f62d0c5372648c840779bdc74ea6e997783441934fa1df90f00ffef158cb6e6d5ce7 languageName: node linkType: hard @@ -1421,14 +1421,14 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:^18.0.34": - version: 18.0.34 - resolution: "@types/react@npm:18.0.34" +"@types/react@npm:^18.0.37": + version: 18.0.37 + resolution: "@types/react@npm:18.0.37" dependencies: "@types/prop-types": "*" "@types/scheduler": "*" csstype: ^3.0.2 - checksum: 97e6ea3b5eea0b270c2c36f5cc44699fe21e30ceda4ffc6936ad40ff755bd8b16637f41d8b0bd20d50eb3261b0981e8f4822b2bd5805208532292499f6de340c + checksum: 1919fb9fb48d574fafeb196aced7ea1ee345525597afa6ad01049c7ce090a732bc500c4a392deb0a5fb9165d5ae5fe720b795e3023bb5a9b2b2a0fae059b4407 languageName: node linkType: hard @@ -1473,14 +1473,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^5.58.0": - version: 5.58.0 - resolution: "@typescript-eslint/eslint-plugin@npm:5.58.0" +"@typescript-eslint/eslint-plugin@npm:^5.59.0": + version: 5.59.0 + resolution: "@typescript-eslint/eslint-plugin@npm:5.59.0" dependencies: "@eslint-community/regexpp": ^4.4.0 - "@typescript-eslint/scope-manager": 5.58.0 - "@typescript-eslint/type-utils": 5.58.0 - "@typescript-eslint/utils": 5.58.0 + "@typescript-eslint/scope-manager": 5.59.0 + "@typescript-eslint/type-utils": 5.59.0 + "@typescript-eslint/utils": 5.59.0 debug: ^4.3.4 grapheme-splitter: ^1.0.4 ignore: ^5.2.0 @@ -1493,43 +1493,43 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: cda31ae6ff09c742f921304ea1a2a0d0823f7e20d7969ba6320ab14df2b3269b93a61eded96a59f01cfd24f28efb91e461e42bb09f493ed013936a899697a868 + checksum: f3b557fc875f688073835b6d41af4184c08c00b0f4887e62bb110e2935d50a158b026547cde89708bf6463913322e757a07d2de26fc505a3c15a81120d64ccef languageName: node linkType: hard -"@typescript-eslint/parser@npm:^5.58.0": - version: 5.58.0 - resolution: "@typescript-eslint/parser@npm:5.58.0" +"@typescript-eslint/parser@npm:^5.59.0": + version: 5.59.0 + resolution: "@typescript-eslint/parser@npm:5.59.0" dependencies: - "@typescript-eslint/scope-manager": 5.58.0 - "@typescript-eslint/types": 5.58.0 - "@typescript-eslint/typescript-estree": 5.58.0 + "@typescript-eslint/scope-manager": 5.59.0 + "@typescript-eslint/types": 5.59.0 + "@typescript-eslint/typescript-estree": 5.59.0 debug: ^4.3.4 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: fb7a4ce59eb803d29705e0134b6731823d9d7b56dd76a4de4ff07eb09d56cc851ed9988ecacdc2d0cbd929115a02ce564b0bb1b97d8732e05707dbe4332460ae + checksum: 5e0f8dfe4eb762bf1d2e8186559d39df653005a8f976101cd7b3739f3e0253d1003b4268976e52c7e42b63bf6ea042b1ed9412f85d06ed8fb0407b56dba19db2 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:5.58.0": - version: 5.58.0 - resolution: "@typescript-eslint/scope-manager@npm:5.58.0" +"@typescript-eslint/scope-manager@npm:5.59.0": + version: 5.59.0 + resolution: "@typescript-eslint/scope-manager@npm:5.59.0" dependencies: - "@typescript-eslint/types": 5.58.0 - "@typescript-eslint/visitor-keys": 5.58.0 - checksum: 66c82609ac6c9cf00e163126619e7c487adc938f02e4567a2c26319916a175b9aee792aa80bd319a20848c834c6e599cd302c9f5b68c64b95d02f024f511ac66 + "@typescript-eslint/types": 5.59.0 + "@typescript-eslint/visitor-keys": 5.59.0 + checksum: b53c9581daf3d6ac2ec5bd660d62c56ea77f71d77261a23bf21bc23a8140b5b7738304ace576b6af6e1d4ffc5170b7b6be7375da488e9e2997984011c509ead8 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:5.58.0": - version: 5.58.0 - resolution: "@typescript-eslint/type-utils@npm:5.58.0" +"@typescript-eslint/type-utils@npm:5.59.0": + version: 5.59.0 + resolution: "@typescript-eslint/type-utils@npm:5.59.0" dependencies: - "@typescript-eslint/typescript-estree": 5.58.0 - "@typescript-eslint/utils": 5.58.0 + "@typescript-eslint/typescript-estree": 5.59.0 + "@typescript-eslint/utils": 5.59.0 debug: ^4.3.4 tsutils: ^3.21.0 peerDependencies: @@ -1537,23 +1537,23 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 3ca4443f43b8263745afda3ff05517074da77d1dad25867845d386b29b012548b720d12334aca8bf15323a76557099e4ce3025a5a0fa84e070f6a4c1dc36d44e + checksum: 8675af740e89ab15e10ef1938530dc14595f45ebaa8b57375e931a4e8b42d71cff8ddfacf71f20a333532a0dbe593eff6d59eb5d43b73c9dd19ad7439a30f443 languageName: node linkType: hard -"@typescript-eslint/types@npm:5.58.0": - version: 5.58.0 - resolution: "@typescript-eslint/types@npm:5.58.0" - checksum: 3e5973909a5c585f5aebf919eec8ac213e9b5089c7357ea832ffa2bd39df70dce0b806d4bcc39a15e309830dfbf7bdf22d9808ab3c466729b8536e9d7e83eccc +"@typescript-eslint/types@npm:5.59.0": + version: 5.59.0 + resolution: "@typescript-eslint/types@npm:5.59.0" + checksum: f756843a49b418a23674842d356aaef14e7373e7df80729e64cf23b3fc7c9d9ab4f0a764b41555236af7821cd1d3c0efcc9a3c97b778f0b67b6dbbd9c5e852cc languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.58.0": - version: 5.58.0 - resolution: "@typescript-eslint/typescript-estree@npm:5.58.0" +"@typescript-eslint/typescript-estree@npm:5.59.0": + version: 5.59.0 + resolution: "@typescript-eslint/typescript-estree@npm:5.59.0" dependencies: - "@typescript-eslint/types": 5.58.0 - "@typescript-eslint/visitor-keys": 5.58.0 + "@typescript-eslint/types": 5.59.0 + "@typescript-eslint/visitor-keys": 5.59.0 debug: ^4.3.4 globby: ^11.1.0 is-glob: ^4.0.3 @@ -1562,35 +1562,35 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 51c2a92217a1ccc01acf3c5c371b8c4b48b066cb6341441c35b74b6f3e13d21f81e0aed041215f3f4cf73320f5cd6cc3061801c51a3049958ee9a171a6efa196 + checksum: 2e677677927721d0db286f2f2e0263d5b8ae06072f217fc2fd17c96c347f8cec2201dccaf393c41e6f4b2a7c3e2b7ca6ab8a27283e76c6ec5576f53d1d26a0b6 languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.58.0": - version: 5.58.0 - resolution: "@typescript-eslint/utils@npm:5.58.0" +"@typescript-eslint/utils@npm:5.59.0": + version: 5.59.0 + resolution: "@typescript-eslint/utils@npm:5.59.0" dependencies: "@eslint-community/eslint-utils": ^4.2.0 "@types/json-schema": ^7.0.9 "@types/semver": ^7.3.12 - "@typescript-eslint/scope-manager": 5.58.0 - "@typescript-eslint/types": 5.58.0 - "@typescript-eslint/typescript-estree": 5.58.0 + "@typescript-eslint/scope-manager": 5.59.0 + "@typescript-eslint/types": 5.59.0 + "@typescript-eslint/typescript-estree": 5.59.0 eslint-scope: ^5.1.1 semver: ^7.3.7 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 71ea338d9b67b59792e9d9a82b723acbee815534044294b169e3727f5394445d95a6200c919f0c28020bc5954df0f7110e9d0a4586e77ebebcd1662c06b30157 + checksum: 653ea4032b51c8b3bdc386971cb437f59fc20a8df5ca8d11ef6c917e6376df26c73cfd18cbeee8c8818ba4350a8cbd87d0ea9ec5242e35c5d26059a11476bc13 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:5.58.0": - version: 5.58.0 - resolution: "@typescript-eslint/visitor-keys@npm:5.58.0" +"@typescript-eslint/visitor-keys@npm:5.59.0": + version: 5.59.0 + resolution: "@typescript-eslint/visitor-keys@npm:5.59.0" dependencies: - "@typescript-eslint/types": 5.58.0 + "@typescript-eslint/types": 5.59.0 eslint-visitor-keys: ^3.3.0 - checksum: e41b0cf8bf766c491fe96e26b4cd20e6af4dbe85ff773a32887b7557ffd199117d8cdc86ceef5ce224d06c5e14d54a8edb679e58185f5a9c6b450615eaac6f30 + checksum: 184a23424a6bf7ea48f700a71461d3a89270e8af32db6f1fcc5367834818c9e8bc4b57853a15b6a9d44297c064d04d08583815fd8f2019135c0bc197d60a6c0c languageName: node linkType: hard @@ -1732,18 +1732,18 @@ __metadata: "@emotion/styled": ^11.10.6 "@msgpack/msgpack": ^3.0.0-beta2 "@mui/icons-material": ^5.11.16 - "@mui/material": ^5.12.0 + "@mui/material": ^5.12.1 "@remix-run/router": ^1.5.0 "@table-library/react-table-library": 4.1.0 "@types/lodash-es": ^4.17.7 "@types/mime-types": ^2 "@types/node": ^18.15.11 - "@types/react": ^18.0.34 + "@types/react": ^18.0.37 "@types/react-dom": ^18.0.11 "@types/react-router-dom": ^5.3.3 "@types/styled-components": ^5 - "@typescript-eslint/eslint-plugin": ^5.58.0 - "@typescript-eslint/parser": ^5.58.0 + "@typescript-eslint/eslint-plugin": ^5.59.0 + "@typescript-eslint/parser": ^5.59.0 "@vitejs/plugin-react-swc": ^3.3.0 "@yarnpkg/pnpify": ^4.0.0-rc.42 async-validator: ^4.2.5 @@ -1753,6 +1753,7 @@ __metadata: eslint-config-airbnb-typescript: ^17.0.0 eslint-config-prettier: ^8.8.0 eslint-import-resolver-typescript: ^3.5.5 + eslint-plugin-autofix: ^1.1.0 eslint-plugin-import: ^2.27.5 eslint-plugin-jsx-a11y: ^6.7.1 eslint-plugin-prettier: ^4.2.1 @@ -1773,13 +1774,13 @@ __metadata: react-toastify: ^9.1.2 rollup-plugin-visualizer: ^5.9.0 sockette: ^2.0.6 - terser: ^5.16.9 + terser: ^5.17.0 typesafe-i18n: ^5.24.3 typescript: ^5.0.4 - vite: ^4.2.1 + vite: ^4.2.2 vite-plugin-minify: ^1.5.2 vite-plugin-svgr: ^2.4.0 - vite-tsconfig-paths: ^4.1.0 + vite-tsconfig-paths: ^4.2.0 languageName: unknown linkType: soft @@ -3021,6 +3022,21 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-autofix@npm:^1.1.0": + version: 1.1.0 + resolution: "eslint-plugin-autofix@npm:1.1.0" + dependencies: + eslint-rule-composer: ^0.3.0 + espree: ^9.0.0 + esutils: ^2.0.2 + lodash: ^4.17.20 + string-similarity: ^4.0.3 + peerDependencies: + eslint: ">= 5.12.1" + checksum: f46b2a9a1e2d99fd2dfdbdd2303c108d5fd773d00abe3c92f7a44a604235d2df28faf74ede9b6802925461815784a57a4909d6e4e2a2c71031cadf8903711c43 + languageName: node + linkType: hard + "eslint-plugin-import@npm:^2.27.5": version: 2.27.5 resolution: "eslint-plugin-import@npm:2.27.5" @@ -3121,6 +3137,13 @@ __metadata: languageName: node linkType: hard +"eslint-rule-composer@npm:^0.3.0": + version: 0.3.0 + resolution: "eslint-rule-composer@npm:0.3.0" + checksum: 1f0c40d209e1503a955101a0dbba37e7fc67c8aaa47a5b9ae0b0fcbae7022c86e52b3df2b1b9ffd658e16cd80f31fff92e7222460a44d8251e61d49e0af79a07 + languageName: node + linkType: hard + "eslint-scope@npm:^5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" @@ -3198,7 +3221,7 @@ __metadata: languageName: node linkType: hard -"espree@npm:^9.5.1": +"espree@npm:^9.0.0, espree@npm:^9.5.1": version: 9.5.1 resolution: "espree@npm:9.5.1" dependencies: @@ -4401,7 +4424,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.15": +"lodash@npm:^4.17.15, lodash@npm:^4.17.20": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c @@ -5883,6 +5906,13 @@ __metadata: languageName: node linkType: hard +"string-similarity@npm:^4.0.3": + version: 4.0.4 + resolution: "string-similarity@npm:4.0.4" + checksum: fce331b818efafa701f692ddc2e170bd3ceaf6e7ca56a445b36b139981effe0884d8edc794a65005e54304da55ba054edfcff16a339bd301c9b94983fbc62047 + languageName: node + linkType: hard + "string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" @@ -6070,9 +6100,9 @@ __metadata: languageName: node linkType: hard -"terser@npm:^5.16.9": - version: 5.16.9 - resolution: "terser@npm:5.16.9" +"terser@npm:^5.17.0": + version: 5.17.0 + resolution: "terser@npm:5.17.0" dependencies: "@jridgewell/source-map": ^0.3.2 acorn: ^8.5.0 @@ -6080,7 +6110,7 @@ __metadata: source-map-support: ~0.5.20 bin: terser: bin/terser - checksum: eb883b606aa698e314957aa2cf6e70c1dc632d0d2dcda13e7a2cc73569a05034721826c0d6f9b31c6bb08bbc4fc633b6591871814dada71da9d34af9e284dc4f + checksum: fb5c81a837fc4083c1471b5cd599505666ef9f007381fb934c39f8fc5e0c034914e32d2de247aeb7f8a9723c1dc411baf5153368cfe8ed1927139ff030516eda languageName: node linkType: hard @@ -6365,9 +6395,9 @@ __metadata: languageName: node linkType: hard -"vite-tsconfig-paths@npm:^4.1.0": - version: 4.1.0 - resolution: "vite-tsconfig-paths@npm:4.1.0" +"vite-tsconfig-paths@npm:^4.2.0": + version: 4.2.0 + resolution: "vite-tsconfig-paths@npm:4.2.0" dependencies: debug: ^4.1.1 globrex: ^0.1.2 @@ -6377,13 +6407,13 @@ __metadata: peerDependenciesMeta: vite: optional: true - checksum: 9846dfdd7118067539728f88a3b5f6a109d01392fa83bef6ad0505def2b1ed24579a4955df7db4b3ab60e9a816867a48e8b508f34030ef0d20b773293c91298d + checksum: 04bd792bb4f6b4fb57ec8368cff076abffba8d6923af032affb14be43b6e2dfd8b25085947a3204d702a8c8e9d79d3c361373cf98566df682420728857906289 languageName: node linkType: hard -"vite@npm:^4.2.1": - version: 4.2.1 - resolution: "vite@npm:4.2.1" +"vite@npm:^4.2.2": + version: 4.2.2 + resolution: "vite@npm:4.2.2" dependencies: esbuild: ^0.17.5 fsevents: ~2.3.2 @@ -6415,7 +6445,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: a64e3a1563b9584d1fd1ca45e06ee3c9fa4956320e6d4e9d83bf09fc8e64bb9d3ef62f664b6f740141eee16643690e8a41ffdb3030f4f2e170c57894df1f9a5d + checksum: 60e7298c817f0626bcbfdfc8877431421eabab85131c64d69e58f5ea20a66c14c4e0901ed63286b362627a667560d257dd135615b63f844da6052f6248dd7be6 languageName: node linkType: hard diff --git a/mock-api/server.js b/mock-api/server.js index 23c8047cb..dea914377 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -14,7 +14,6 @@ rest_server.use(express.json()); // endpoints const API_ENDPOINT_ROOT = '/api/'; const REST_ENDPOINT_ROOT = '/rest/'; -const EVENTSOURCE_ENDPOINT_ROOT = '/es/'; // LOG const LOG_SETTINGS_ENDPOINT = REST_ENDPOINT_ROOT + 'logSettings'; @@ -610,7 +609,8 @@ let emsesp_entities = { offset: 0, factor: 0, uom: 2, - val_type: 2 + value: 1, + value_type: 2 }, { id: 1, @@ -620,7 +620,8 @@ let emsesp_entities = { offset: 2, factor: 2, uom: 4, - val_type: 5 + value: 2, + value_type: 5 } ] }; diff --git a/src/web/WebEntityService.cpp b/src/web/WebEntityService.cpp index c5ff61ce4..2572f7ce7 100644 --- a/src/web/WebEntityService.cpp +++ b/src/web/WebEntityService.cpp @@ -33,24 +33,27 @@ void WebEntityService::begin() { EMSESP::logger().info("Starting custom entity service"); } -// this creates the scheduler file, saving it to the FS -// and also calls when the Scheduler web page is refreshed +// this creates the entity file, saving it to the FS +// and also calls when the Entity web page is refreshed void WebEntity::read(WebEntity & webEntity, JsonObject & root) { - JsonArray entity = root.createNestedArray("entity"); + JsonArray entity = root.createNestedArray("entity"); + uint8_t counter = 0; for (const EntityItem & entityItem : webEntity.entityItems) { - JsonObject ei = entity.createNestedObject(); - ei["device_id"] = Helpers::hextoa(entityItem.device_id, false); - ei["type_id"] = Helpers::hextoa(entityItem.type_id, false); - ei["offset"] = entityItem.offset; - ei["factor"] = entityItem.factor; - ei["name"] = entityItem.name; - ei["uom"] = entityItem.uom; - ei["val_type"] = entityItem.valuetype; + JsonObject ei = entity.createNestedObject(); + ei["id"] = counter++; // id is only used to render the table and must be unique + ei["device_id"] = Helpers::hextoa(entityItem.device_id, false); + ei["type_id"] = Helpers::hextoa(entityItem.type_id, false); + ei["offset"] = entityItem.offset; + ei["factor"] = entityItem.factor; + ei["name"] = entityItem.name; + ei["uom"] = entityItem.uom; + ei["value_type"] = entityItem.value_type; + ei["writeable"] = entityItem.writeable; EMSESP::webEntityService.render_value(ei, entityItem, true); } } -// call on initialization and also when the Schedule web page is updated +// call on initialization and also when the Entity web page is updated // this loads the data into the internal class StateUpdateResult WebEntity::update(JsonObject & root, WebEntity & webEntity) { for (EntityItem & entityItem : webEntity.entityItems) { @@ -60,36 +63,42 @@ StateUpdateResult WebEntity::update(JsonObject & root, WebEntity & webEntity) { if (root["entity"].is()) { for (const JsonObject ei : root["entity"].as()) { - auto entityItem = EntityItem(); - entityItem.device_id = Helpers::hextoint(ei["device_id"]); - entityItem.type_id = Helpers::hextoint(ei["type_id"]); - entityItem.offset = ei["offset"]; - entityItem.factor = ei["factor"]; - entityItem.name = ei["name"].as(); - entityItem.uom = ei["uom"]; - entityItem.valuetype = ei["val_type"]; + auto entityItem = EntityItem(); + entityItem.device_id = Helpers::hextoint(ei["device_id"]); // TODO don't need + entityItem.type_id = Helpers::hextoint(ei["type_id"]); + entityItem.offset = ei["offset"]; + entityItem.factor = ei["factor"]; + entityItem.name = ei["name"].as(); + entityItem.uom = ei["uom"]; + entityItem.value_type = ei["value_type"]; + entityItem.writeable = ei["writeable"]; - if (entityItem.valuetype == DeviceValueType::BOOL) { - entityItem.val = EMS_VALUE_DEFAULT_BOOL; - } else if (entityItem.valuetype == DeviceValueType::INT) { - entityItem.val = EMS_VALUE_DEFAULT_INT; - } else if (entityItem.valuetype == DeviceValueType::UINT) { - entityItem.val = EMS_VALUE_DEFAULT_UINT; - } else if (entityItem.valuetype == DeviceValueType::SHORT) { - entityItem.val = EMS_VALUE_DEFAULT_SHORT; - } else if (entityItem.valuetype == DeviceValueType::USHORT) { - entityItem.val = EMS_VALUE_DEFAULT_USHORT; - } else { // if (entityItem.valuetype == DeviceValueType::ULONG || entityItem.valuetype == DeviceValueType::TIME) { - entityItem.val = EMS_VALUE_DEFAULT_ULONG; + if (entityItem.value_type == DeviceValueType::BOOL) { + entityItem.value = EMS_VALUE_DEFAULT_BOOL; + } else if (entityItem.value_type == DeviceValueType::INT) { + entityItem.value = EMS_VALUE_DEFAULT_INT; + } else if (entityItem.value_type == DeviceValueType::UINT) { + entityItem.value = EMS_VALUE_DEFAULT_UINT; + } else if (entityItem.value_type == DeviceValueType::SHORT) { + entityItem.value = EMS_VALUE_DEFAULT_SHORT; + } else if (entityItem.value_type == DeviceValueType::USHORT) { + entityItem.value = EMS_VALUE_DEFAULT_USHORT; + } else { // if (entityItem.value_type == DeviceValueType::ULONG || entityItem.valuetype == DeviceValueType::TIME) { + entityItem.value = EMS_VALUE_DEFAULT_ULONG; } webEntity.entityItems.push_back(entityItem); // add to list - Command::add( - EMSdevice::DeviceType::CUSTOM, - webEntity.entityItems.back().name.c_str(), - [webEntity](const char * value, const int8_t id) { return EMSESP::webEntityService.command_setvalue(value, webEntity.entityItems.back().name); }, - FL_(entity_cmd), - CommandFlag::ADMIN_ONLY); + + if (entityItem.writeable) { + Command::add( + EMSdevice::DeviceType::CUSTOM, + webEntity.entityItems.back().name.c_str(), + [webEntity](const char * value, const int8_t id) { + return EMSESP::webEntityService.command_setvalue(value, webEntity.entityItems.back().name); + }, + FL_(entity_cmd), + CommandFlag::ADMIN_ONLY); + } } } return StateUpdateResult::CHANGED; @@ -100,7 +109,7 @@ bool WebEntityService::command_setvalue(const char * value, const std::string na EMSESP::webEntityService.read([&](WebEntity & webEntity) { entityItems = &webEntity.entityItems; }); for (EntityItem & entityItem : *entityItems) { if (entityItem.name == name) { - if (entityItem.valuetype == DeviceValueType::BOOL) { + if (entityItem.value_type == DeviceValueType::BOOL) { bool v; if (!Helpers::value2bool(value, v)) { return false; @@ -112,9 +121,9 @@ bool WebEntityService::command_setvalue(const char * value, const std::string na return false; } int v = f / entityItem.factor; - if (entityItem.valuetype == DeviceValueType::UINT || entityItem.valuetype == DeviceValueType::INT) { + if (entityItem.value_type == DeviceValueType::UINT || entityItem.value_type == DeviceValueType::INT) { EMSESP::send_write_request(entityItem.type_id, entityItem.device_id, entityItem.offset, v, 0); - } else if (entityItem.valuetype == DeviceValueType::USHORT || entityItem.valuetype == DeviceValueType::SHORT) { + } else if (entityItem.value_type == DeviceValueType::USHORT || entityItem.value_type == DeviceValueType::SHORT) { uint8_t v1[2] = {(uint8_t)(v >> 8), (uint8_t)(v & 0xFF)}; EMSESP::send_write_request(entityItem.type_id, entityItem.device_id, entityItem.offset, v1, 2, 0); } else { @@ -122,6 +131,7 @@ bool WebEntityService::command_setvalue(const char * value, const std::string na EMSESP::send_write_request(entityItem.type_id, entityItem.device_id, entityItem.offset, v1, 3, 0); } } + publish_single(entityItem); if (EMSESP::mqtt_.get_publish_onchange(0)) { publish(); @@ -136,42 +146,42 @@ bool WebEntityService::command_setvalue(const char * value, const std::string na void WebEntityService::render_value(JsonObject & output, EntityItem entity, const bool useVal) { char payload[12]; std::string name = useVal ? "value" : entity.name; - switch (entity.valuetype) { + switch (entity.value_type) { case DeviceValueType::BOOL: - if ((uint8_t)entity.val != EMS_VALUE_BOOL_NOTSET) { + if ((uint8_t)entity.value != EMS_VALUE_BOOL_NOTSET) { if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) { - output[name] = (uint8_t)entity.val ? true : false; + output[name] = (uint8_t)entity.value ? true : false; } else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) { - output[name] = (uint8_t)entity.val ? 1 : 0; + output[name] = (uint8_t)entity.value ? 1 : 0; } else { - output[name] = Helpers::render_boolean(payload, (uint8_t)entity.val); + output[name] = Helpers::render_boolean(payload, (uint8_t)entity.value); } } break; case DeviceValueType::INT: - if ((int8_t)entity.val != EMS_VALUE_INT_NOTSET) { - output[name] = serialized(Helpers::render_value(payload, entity.factor * (int8_t)entity.val, 2)); + if ((int8_t)entity.value != EMS_VALUE_INT_NOTSET) { + output[name] = serialized(Helpers::render_value(payload, entity.factor * (int8_t)entity.value, 2)); } break; case DeviceValueType::UINT: - if ((uint8_t)entity.val != EMS_VALUE_UINT_NOTSET) { - output[name] = serialized(Helpers::render_value(payload, entity.factor * (uint8_t)entity.val, 2)); + if ((uint8_t)entity.value != EMS_VALUE_UINT_NOTSET) { + output[name] = serialized(Helpers::render_value(payload, entity.factor * (uint8_t)entity.value, 2)); } break; case DeviceValueType::SHORT: - if ((int16_t)entity.val != EMS_VALUE_SHORT_NOTSET) { - output[name] = serialized(Helpers::render_value(payload, entity.factor * (int16_t)entity.val, 2)); + if ((int16_t)entity.value != EMS_VALUE_SHORT_NOTSET) { + output[name] = serialized(Helpers::render_value(payload, entity.factor * (int16_t)entity.value, 2)); } break; case DeviceValueType::USHORT: - if ((uint16_t)entity.val != EMS_VALUE_USHORT_NOTSET) { - output[name] = serialized(Helpers::render_value(payload, entity.factor * (uint16_t)entity.val, 2)); + if ((uint16_t)entity.value != EMS_VALUE_USHORT_NOTSET) { + output[name] = serialized(Helpers::render_value(payload, entity.factor * (uint16_t)entity.value, 2)); } break; case DeviceValueType::ULONG: case DeviceValueType::TIME: - if (entity.val != EMS_VALUE_ULONG_NOTSET) { - output[name] = serialized(Helpers::render_value(payload, entity.factor * entity.val, 2)); + if (entity.value != EMS_VALUE_ULONG_NOTSET) { + output[name] = serialized(Helpers::render_value(payload, entity.factor * entity.value, 2)); } break; default: @@ -215,7 +225,7 @@ bool WebEntityService::get_value_info(JsonObject & output, const char * cmd) { output["name"] = entity.name; output["uom"] = EMSdevice::uom_to_string(entity.uom); output["readable"] = true; - output["writeable"] = true; + output["writeable"] = entity.writeable; output["visible"] = true; render_value(output, entity, true); if (attribute_s) { @@ -250,8 +260,8 @@ void WebEntityService::publish_single(const EntityItem & entity) { } else { snprintf(topic, sizeof(topic), "%s/%s", "custom_data", entity.name.c_str()); } - StaticJsonDocument<256> doc; - JsonObject output = doc.to(); + StaticJsonDocument doc; + JsonObject output = doc.to(); render_value(output, entity, true); Mqtt::queue_publish(topic, output["value"].as()); } @@ -326,8 +336,10 @@ uint8_t WebEntityService::count_entities() { if (entityItems->size() == 0) { return 0; } + DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE); JsonObject output = doc.to(); + for (const EntityItem & entity : *entityItems) { render_value(output, entity); } @@ -337,46 +349,51 @@ uint8_t WebEntityService::count_entities() { // send to dashboard, msgpack don't like serialized, use number void WebEntityService::generate_value_web(JsonObject & output) { EMSESP::webEntityService.read([&](WebEntity & webEntity) { entityItems = &webEntity.entityItems; }); + output["label"] = (std::string) "Custom Entities"; JsonArray data = output.createNestedArray("data"); + for (const EntityItem & entity : *entityItems) { JsonObject obj = data.createNestedObject(); // create the object, we know there is a value obj["id"] = "00" + entity.name; obj["u"] = entity.uom; - obj["c"] = entity.name; - switch (entity.valuetype) { + if (entity.writeable) { + obj["c"] = entity.name; + } + + switch (entity.value_type) { case DeviceValueType::BOOL: { char s[12]; - obj["v"] = Helpers::render_boolean(s, (uint8_t)entity.val); + obj["v"] = Helpers::render_boolean(s, (uint8_t)entity.value); JsonArray l = obj.createNestedArray("l"); l.add(Helpers::render_boolean(s, false, true)); l.add(Helpers::render_boolean(s, true, true)); break; } case DeviceValueType::INT: - if ((int8_t)entity.val != EMS_VALUE_INT_NOTSET) { - obj["v"] = Helpers::transformNumFloat(entity.factor * (int8_t)entity.val, 0); + if ((int8_t)entity.value != EMS_VALUE_INT_NOTSET) { + obj["v"] = Helpers::transformNumFloat(entity.factor * (int8_t)entity.value, 0); } break; case DeviceValueType::UINT: - if ((uint8_t)entity.val != EMS_VALUE_UINT_NOTSET) { - obj["v"] = Helpers::transformNumFloat(entity.factor * (uint8_t)entity.val, 0); + if ((uint8_t)entity.value != EMS_VALUE_UINT_NOTSET) { + obj["v"] = Helpers::transformNumFloat(entity.factor * (uint8_t)entity.value, 0); } break; case DeviceValueType::SHORT: - if ((int16_t)entity.val != EMS_VALUE_SHORT_NOTSET) { - obj["v"] = Helpers::transformNumFloat(entity.factor * (int16_t)entity.val, 0); + if ((int16_t)entity.value != EMS_VALUE_SHORT_NOTSET) { + obj["v"] = Helpers::transformNumFloat(entity.factor * (int16_t)entity.value, 0); } break; case DeviceValueType::USHORT: - if ((uint16_t)entity.val != EMS_VALUE_USHORT_NOTSET) { - obj["v"] = Helpers::transformNumFloat(entity.factor * (uint16_t)entity.val, 0); + if ((uint16_t)entity.value != EMS_VALUE_USHORT_NOTSET) { + obj["v"] = Helpers::transformNumFloat(entity.factor * (uint16_t)entity.value, 0); } break; case DeviceValueType::ULONG: case DeviceValueType::TIME: - if (entity.val != EMS_VALUE_ULONG_NOTSET) { - obj["v"] = Helpers::transformNumFloat(entity.factor * entity.val, 0); + if (entity.value != EMS_VALUE_ULONG_NOTSET) { + obj["v"] = Helpers::transformNumFloat(entity.factor * entity.value, 0); } break; default: @@ -402,13 +419,13 @@ bool WebEntityService::get_value(std::shared_ptr telegram) { const uint8_t len[] = {1, 1, 1, 2, 2, 3, 3}; for (auto & entity : *entityItems) { if (telegram->type_id == entity.type_id && telegram->src == entity.device_id && telegram->offset <= entity.offset - && (telegram->offset + telegram->message_length) >= (entity.offset + len[entity.valuetype])) { - uint32_t val = 0; - for (uint8_t i = 0; i < len[entity.valuetype]; i++) { - val = (val << 8) + telegram->message_data[i + entity.offset - telegram->offset]; + && (telegram->offset + telegram->message_length) >= (entity.offset + len[entity.value_type])) { + uint32_t value = 0; + for (uint8_t i = 0; i < len[entity.value_type]; i++) { + value = (value << 8) + telegram->message_data[i + entity.offset - telegram->offset]; } - if (val != entity.val) { - entity.val = val; + if (value != entity.value) { + entity.value = value; if (Mqtt::publish_single()) { publish_single(entity); } else if (EMSESP::mqtt_.get_publish_onchange(0)) { diff --git a/src/web/WebEntityService.h b/src/web/WebEntityService.h index fc58370cb..41de3ed9c 100644 --- a/src/web/WebEntityService.h +++ b/src/web/WebEntityService.h @@ -1,7 +1,7 @@ /* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020-2023 Paul Derbyshire - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -27,14 +27,16 @@ namespace emsesp { class EntityItem { public: + uint8_t id; uint8_t device_id; uint16_t type_id; uint8_t offset; - int8_t valuetype; + int8_t value_type; uint8_t uom; std::string name; double factor; - uint32_t val; + bool writeable; + uint32_t value; }; class WebEntity { @@ -65,7 +67,7 @@ class WebEntityService : public StatefulService { HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; - std::list * entityItems; // pointer to the list of schedule events + std::list * entityItems; // pointer to the list of entity items bool ha_registered_ = false; }; From 04dd9eef09f05f48628c749cf6ae7991692c6096 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 08:46:13 +0200 Subject: [PATCH 03/89] Polish translation update #1156 --- .../framework/system/GeneralFileUpload.tsx | 22 ++-- interface/src/i18n/de/index.ts | 1 + interface/src/i18n/en/index.ts | 1 + interface/src/i18n/formatters.ts | 2 +- interface/src/i18n/fr/index.ts | 1 + interface/src/i18n/nl/index.ts | 1 + interface/src/i18n/no/index.ts | 1 + interface/src/i18n/pl/index.ts | 13 ++- interface/src/i18n/sv/index.ts | 1 + interface/src/i18n/tr/index.ts | 1 + interface/src/project/DashboardData.tsx | 106 ++++++++---------- interface/src/project/Settings.tsx | 15 +-- src/locale_translations.h | 7 +- src/web/WebDataService.cpp | 39 ++++--- 14 files changed, 96 insertions(+), 115 deletions(-) diff --git a/interface/src/framework/system/GeneralFileUpload.tsx b/interface/src/framework/system/GeneralFileUpload.tsx index b3a291ca0..d1ef50e66 100644 --- a/interface/src/framework/system/GeneralFileUpload.tsx +++ b/interface/src/framework/system/GeneralFileUpload.tsx @@ -1,21 +1,15 @@ -import { FC } from 'react'; -import { AxiosPromise } from 'axios'; - +import DownloadIcon from '@mui/icons-material/GetApp'; import { Typography, Button, Box } from '@mui/material'; - -import { FileUploadConfig } from 'api/endpoints'; +import { toast } from 'react-toastify'; +import type { FileUploadConfig } from 'api/endpoints'; +import type { AxiosPromise } from 'axios'; +import type { FC } from 'react'; import { SingleUpload, useFileUpload } from 'components'; -import DownloadIcon from '@mui/icons-material/GetApp'; - -import { toast } from 'react-toastify'; - -import { extractErrorMessage } from 'utils'; - -import * as EMSESP from 'project/api'; - import { useI18nContext } from 'i18n/i18n-react'; +import * as EMSESP from 'project/api'; +import { extractErrorMessage } from 'utils'; interface UploadFileProps { uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise; @@ -145,7 +139,7 @@ const GeneralFileUpload: FC = ({ uploadGeneralFile }) => { color="primary" onClick={() => downloadEntities()} > - {LL.ENTITIES()} + {LL.CUSTOM_ENTITIES(0)} diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index ac9dcf90e..e00ae0f54 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -321,6 +321,7 @@ const de: Translation = { SCHEDULE_TIMER_3: 'jede Stunde', CUSTOM_ENTITIES: 'Individuelle Entitäten', ENTITIES_HELP_1: 'Abfrage von Werten auf dem EMS-Bus', + ENTITIES_SAVED: 'Entities Saved', // TODO translate WRITEABLE: 'Schreibbar' }; diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index ba5a5d8d7..30ec9f2fd 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -321,6 +321,7 @@ const en: Translation = { SCHEDULE_TIMER_3: 'every hour', CUSTOM_ENTITIES: 'Custom Entities', ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', + ENTITIES_SAVED: 'Entities Saved', WRITEABLE: 'Writeable' }; diff --git a/interface/src/i18n/formatters.ts b/interface/src/i18n/formatters.ts index b9d999653..592a53490 100644 --- a/interface/src/i18n/formatters.ts +++ b/interface/src/i18n/formatters.ts @@ -1,5 +1,5 @@ -import type { FormattersInitializer } from 'typesafe-i18n'; import type { Locales, Formatters } from './i18n-types'; +import type { FormattersInitializer } from 'typesafe-i18n'; export const initFormatters: FormattersInitializer = (locale: Locales) => { const formatters: Formatters = { diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts index bac7958d0..86a8f4d8d 100644 --- a/interface/src/i18n/fr/index.ts +++ b/interface/src/i18n/fr/index.ts @@ -321,6 +321,7 @@ const fr: Translation = { SCHEDULE_TIMER_3: 'every hour', // TODO translate CUSTOM_ENTITIES: 'Custom Entities', // TODO translate ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate + ENTITIES_SAVED: 'Entities Saved', // TODO translate WRITEABLE: 'Writeable' // TODO translate }; diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index a468ba134..2e78472ea 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -321,6 +321,7 @@ const nl: Translation = { SCHEDULE_TIMER_3: 'every hour', // TODO translate CUSTOM_ENTITIES: 'Custom Entities', // TODO translate ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate + ENTITIES_SAVED: 'Entities Saved', // TODO translate WRITEABLE: 'Writeable' // TODO translate }; diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts index 34081696b..8d62adf01 100644 --- a/interface/src/i18n/no/index.ts +++ b/interface/src/i18n/no/index.ts @@ -321,6 +321,7 @@ const no: Translation = { SCHEDULE_TIMER_3: 'hver time', CUSTOM_ENTITIES: 'Custom Entities', // TODO translate ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate + ENTITIES_SAVED: 'Entities Saved', // TODO translate WRITEABLE: 'Writeable' // TODO translate }; diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index 4a85f60e2..f770bf952 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -24,7 +24,7 @@ const pl: BaseTranslation = { SECURITY: '{{B|b|}}ezpieczeństw{{o|a|}}', ONOFF_CAP: 'wł./wył.', ONOFF: 'włączono/wyłączono', - TYPE: 'Typ', + TYPE: '{{T|t|}}yp{{|u|}}', DESCRIPTION: 'Opis', ENTITIES: 'Encje', REFRESH: 'Odśwież', @@ -240,7 +240,7 @@ const pl: BaseTranslation = { MQTT_RESPONSE: 'Rezultat wykonania komendy publikuj w temacie "response"', MQTT_PUBLISH_TEXT_1: 'Tematy z pojedynczą wartością publikuj po jej zmianie', MQTT_PUBLISH_TEXT_2: 'Publikuj w tematach "command" (ioBroker)', - MQTT_PUBLISH_TEXT_3: 'Włącz opcję "MQTT discovery', + MQTT_PUBLISH_TEXT_3: 'Włącz opcję "MQTT discovery"', MQTT_PUBLISH_TEXT_4: 'Prefiks dla "MQTT discovery"', MQTT_PUBLISH_TEXT_5: 'Typ "MQTT discovery"', MQTT_PUBLISH_INTERVALS: 'Interwały publikowania', @@ -287,7 +287,7 @@ const pl: BaseTranslation = { NETWORK_BLANK_SSID: 'pozostaw puste aby wyłączyć WiFi', TX_POWER: 'Moc nadawania', HOSTNAME: 'Nazwa w sieci', - NETWORK_DISABLE_SLEEP: 'Wyłącz tryb usypiania WiFi', + NETWORK_DISABLE_SLEEP: 'Wyłącz tryb uśpienia WiFi', NETWORK_LOW_BAND: 'Używaj mniejszej szerokości pasma WiFi (20MHz)', NETWORK_USE_DNS: 'Włącz wsparcie dla mDNS', NETWORK_ENABLE_CORS: 'Włącz wsparcie dla CORS', @@ -300,7 +300,7 @@ const pl: BaseTranslation = { ADDRESS_OF: 'Adres {0}', ADMIN: 'Użytkownik "administrator".', GUEST: 'Użytkownik "gość".', - NEW: 'nowego', + NEW: 'nowe{{go|j|}}', NEW_NAME_OF: 'Nowa nazwa {0}', ENTITY: 'encji', MIN: 'Min.', @@ -319,8 +319,9 @@ const pl: BaseTranslation = { SCHEDULE_TIMER_1: 'przy starcie', SCHEDULE_TIMER_2: 'co minutę', SCHEDULE_TIMER_3: 'co godzinę', - CUSTOM_ENTITIES: 'Custom Entities', // TODO translate - ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', + CUSTOM_ENTITIES: '{{N|n|}}iestandardowe{{|j|}} encj{{e|i|}}', + ENTITIES_HELP_1: 'Zdefiniuj niestandardowe encje pobierane z magistrali EMS.', + ENTITIES_SAVED: 'Niestandardowe encje zostały uaktualnione.', WRITEABLE: 'Writeable' // TODO translate }; diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts index 3d6873122..1cf0e7301 100644 --- a/interface/src/i18n/sv/index.ts +++ b/interface/src/i18n/sv/index.ts @@ -321,6 +321,7 @@ const sv: Translation = { SCHEDULE_TIMER_3: 'every hour', // TODO translate CUSTOM_ENTITIES: 'Custom Entities', // TODO translate ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate + ENTITIES_SAVED: 'Entities Saved', // TODO translate WRITEABLE: 'Writeable' // TODO translate }; diff --git a/interface/src/i18n/tr/index.ts b/interface/src/i18n/tr/index.ts index 61867b860..5ea666083 100644 --- a/interface/src/i18n/tr/index.ts +++ b/interface/src/i18n/tr/index.ts @@ -321,6 +321,7 @@ const tr: Translation = { SCHEDULE_TIMER_3: 'every hour', // TODO translate CUSTOM_ENTITIES: 'Custom Entities', // TODO translate ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate + ENTITIES_SAVED: 'Entities Saved', // TODO translate WRITEABLE: 'Writeable' // TODO translate }; diff --git a/interface/src/project/DashboardData.tsx b/interface/src/project/DashboardData.tsx index 15ffcc6cf..ce415cd55 100644 --- a/interface/src/project/DashboardData.tsx +++ b/interface/src/project/DashboardData.tsx @@ -1,5 +1,19 @@ -import { FC, useState, useContext, useCallback, useEffect } from 'react'; - +import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined'; +import CancelIcon from '@mui/icons-material/Cancel'; +import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined'; +import EditIcon from '@mui/icons-material/Edit'; +import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined'; +import DownloadIcon from '@mui/icons-material/GetApp'; +import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; +import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined'; +import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined'; +import PlayArrowIcon from '@mui/icons-material/PlayArrow'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; +import StarIcon from '@mui/icons-material/Star'; +import SendIcon from '@mui/icons-material/TrendingFlat'; +import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined'; +import WarningIcon from '@mui/icons-material/Warning'; import { Button, Typography, @@ -19,59 +33,26 @@ import { FormControlLabel, Checkbox } from '@mui/material'; - -import { toast } from 'react-toastify'; - -import { useTheme } from '@table-library/react-table-library/theme'; +import { useRowSelect } from '@table-library/react-table-library/select'; import { useSort, SortToggleType } from '@table-library/react-table-library/sort'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; -import { useRowSelect } from '@table-library/react-table-library/select'; - -import DownloadIcon from '@mui/icons-material/GetApp'; -import RefreshIcon from '@mui/icons-material/Refresh'; -import EditIcon from '@mui/icons-material/Edit'; -import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; -import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined'; -import CancelIcon from '@mui/icons-material/Cancel'; -import SendIcon from '@mui/icons-material/TrendingFlat'; -import WarningIcon from '@mui/icons-material/Warning'; -import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; -import PlayArrowIcon from '@mui/icons-material/PlayArrow'; -import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined'; -import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined'; -import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined'; -import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined'; -import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined'; -import StarIcon from '@mui/icons-material/Star'; - -import DeviceIcon from './DeviceIcon'; +import { useTheme } from '@table-library/react-table-library/theme'; +import { useState, useContext, useCallback, useEffect } from 'react'; import { IconContext } from 'react-icons'; - -import { AuthenticatedContext } from 'contexts/authentication'; - -import { ButtonRow, ValidatedTextField, SectionContent, MessageBox } from 'components'; +import { toast } from 'react-toastify'; +import DeviceIcon from './DeviceIcon'; import * as EMSESP from './api'; -import { numberValue, updateValue, extractErrorMessage } from 'utils'; - -import { - SensorData, - Device, - CoreData, - DeviceData, - DeviceValue, - DeviceValueUOM, - DeviceValueUOM_s, - AnalogType, - AnalogTypeNames, - Sensor, - Analog, - DeviceEntityMask -} from './types'; +import { DeviceValueUOM, DeviceValueUOM_s, AnalogType, AnalogTypeNames, DeviceEntityMask } from './types'; +import type { SensorData, Device, CoreData, DeviceData, DeviceValue, Sensor, Analog } from './types'; +import type { FC } from 'react'; +import { ButtonRow, ValidatedTextField, SectionContent, MessageBox } from 'components'; +import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; +import { numberValue, updateValue, extractErrorMessage } from 'utils'; const DashboardData: FC = () => { const { me } = useContext(AuthenticatedContext); @@ -301,20 +282,20 @@ const DashboardData: FC = () => { }, [LL]); useEffect(() => { - fetchCoreData(); + void fetchCoreData(); }, [fetchCoreData]); const refreshDataIndex = (selectedDevice: string) => { if (selectedDevice === 'sensor') { - fetchSensorData(); + void fetchSensorData(); return; } setSensorData({ sensors: [], analogs: [] }); if (selectedDevice) { - fetchDeviceData(selectedDevice); + void fetchDeviceData(selectedDevice); } else { - fetchCoreData(); + void fetchCoreData(); } }; @@ -344,11 +325,12 @@ const DashboardData: FC = () => { return sc; }; - const makeCsvData = (columns: any, data: any) => { - return data.reduce((csvString: any, rowItem: any) => { - return csvString + columns.map(({ accessor }: any) => escapeCsvCell(accessor(rowItem))).join(';') + '\r\n'; - }, columns.map(({ name }: any) => escapeCsvCell(name)).join(';') + '\r\n'); - }; + const makeCsvData = (columns: any, data: any) => + data.reduce( + (csvString: any, rowItem: any) => + csvString + columns.map(({ accessor }: any) => escapeCsvCell(accessor(rowItem))).join(';') + '\r\n', + columns.map(({ name }: any) => escapeCsvCell(name)).join(';') + '\r\n' + ); const downloadAsCsv = (columns: any, data: any, filename: string) => { const csvData = makeCsvData(columns, data); @@ -565,7 +547,7 @@ const DashboardData: FC = () => { toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); } finally { setSensor(undefined); - fetchSensorData(); + void fetchSensorData(); } } }; @@ -643,7 +625,7 @@ const DashboardData: FC = () => { - + @@ -688,7 +670,7 @@ const DashboardData: FC = () => {
- {LL.TYPE()} + {LL.TYPE(0)} {LL.DESCRIPTION()} {LL.ENTITIES()} @@ -940,7 +922,7 @@ const DashboardData: FC = () => { endIcon={getSortIcon(analog_sort.state, 'TYPE')} onClick={() => analog_sort.fns.onToggleSort({ sortKey: 'TYPE' })} > - {LL.TYPE()} + {LL.TYPE(0)} {LL.VALUE(0)} @@ -993,7 +975,7 @@ const DashboardData: FC = () => { toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); } finally { setAnalog(undefined); - fetchSensorData(); + void fetchSensorData(); } } }; @@ -1021,7 +1003,7 @@ const DashboardData: FC = () => { toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); } finally { setAnalog(undefined); - fetchSensorData(); + void fetchSensorData(); } } }; @@ -1060,7 +1042,7 @@ const DashboardData: FC = () => { { const { LL } = useI18nContext(); @@ -24,7 +21,7 @@ const Settings: FC = () => { - + } /> diff --git a/src/locale_translations.h b/src/locale_translations.h index 00f918cc3..4639bf451 100644 --- a/src/locale_translations.h +++ b/src/locale_translations.h @@ -49,7 +49,8 @@ MAKE_WORD_TRANSLATION(pump_device, "Pump Module", "Pumpenmodul", "Pump Module", MAKE_WORD_TRANSLATION(heatsource_device, "Heatsource", "Heizquelle", "Heatsource", "Värmekälla", "Źródło ciepła", "Varmekilde", "", "Isı Kaynağı") // TODO translate MAKE_WORD_TRANSLATION(sensors_device, "Sensors", "Sensoren", "Sensoren", "Sensorer", "Czujniki", "Sensorer", "Capteurs", "Sensör Cihazı") MAKE_WORD_TRANSLATION(unknown_device, "Unknown", "Unbekannt", "Onbekend", "Okänt", "Nieznane urządzenie", "Ukjent", "Inconnu", "") // TODO translate -MAKE_WORD_TRANSLATION(custom_device, "User defined entities", "Nutzer deklarierte Entitäten", "", "", "", "", "", "") // TODO translate +MAKE_WORD_TRANSLATION(custom_device, "Custom", "", "", "", "Niestandardowe", "", "", "") // TODO translate +MAKE_WORD_TRANSLATION(custom_device_name, "User defined entities", "Nutzer deklarierte Entitäten", "", "", "Encje zdefiniowane przez użytkownika", "", "", "") // TODO translate // commands // TODO translate @@ -61,11 +62,11 @@ MAKE_WORD_TRANSLATION(setiovalue_cmd, "set io value", "Setze Wertevorgabe", "", MAKE_WORD_TRANSLATION(changeloglevel_cmd, "change log level", "Ändere Sysloglevel", "", "", "zmień poziom log-u", "endre loggnivå", "", "Kayıt seviyesini değiştir") // TODO translate MAKE_WORD_TRANSLATION(fetch_cmd, "refresh all EMS values", "Lese alle EMS-Werte neu", "", "", "odśwież wszystkie wartości EMS", "oppfrisk alle EMS verdier", "", "Bütün EMS değerlerini yenile") // TODO translate MAKE_WORD_TRANSLATION(restart_cmd, "restart EMS-ESP", "Neustart", "", "", "uruchom ponownie EMS-ESP", "restart EMS-ESP", "", "EMS-ESPyi yeniden başlat") // TODO translate -MAKE_WORD_TRANSLATION(watch_cmd, "watch incoming telegrams", "Watch auf eingehende Telegramme", "", "", "obserwuj przyczodzące telegramy", "se innkommende telegrammer", "", "Gelen telegramları ") // TODO translate +MAKE_WORD_TRANSLATION(watch_cmd, "watch incoming telegrams", "Watch auf eingehende Telegramme", "", "", "obserwuj przychodzące telegramy", "se innkommende telegrammer", "", "Gelen telegramları ") // TODO translate MAKE_WORD_TRANSLATION(publish_cmd, "publish all to MQTT", "Publiziere MQTT", "", "", "opublikuj wszystko na MQTT", "Publiser alt til MQTT", "", "Hepsini MQTTye gönder") // TODO translate MAKE_WORD_TRANSLATION(system_info_cmd, "show system status", "Zeige System-Status", "", "", "pokaż status systemu", "vis system status", "", "Sistem Durumunu Göster") // TODO translate MAKE_WORD_TRANSLATION(schedule_cmd, "enable schedule item", "Aktiviere Zeitplan", "", "", "aktywuj wybrany harmonogram", "", "", "") // TODO translate -MAKE_WORD_TRANSLATION(entity_cmd, "set custom value on ems", "Sende eigene Entitäten zu EMS", "", "", "", "", "", "") // TODO translate +MAKE_WORD_TRANSLATION(entity_cmd, "set custom value on ems", "Sende eigene Entitäten zu EMS", "", "", "wyślij własną wartość na EMS", "", "", "") // TODO translate // tags MAKE_WORD_TRANSLATION(tag_boiler_data_ww, "dhw", "WW", "dhw", "VV", "CWU", "dhw", "ecs", "SKS") diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index 05ecdd447..6364a794f 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -1,7 +1,7 @@ /* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020-2023 Paul Derbyshire - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -75,33 +75,32 @@ void WebDataService::core_data(AsyncWebServerRequest * request) { // list is already sorted by device type JsonArray devices = root.createNestedArray("devices"); - char buffer[3]; for (const auto & emsdevice : EMSESP::emsdevices) { // ignore controller if (emsdevice && (emsdevice->device_type() != EMSdevice::DeviceType::CONTROLLER || emsdevice->count_entities() > 0)) { JsonObject obj = devices.createNestedObject(); - obj["id"] = Helpers::smallitoa(buffer, emsdevice->unique_id()); // a unique id as a string - obj["tn"] = emsdevice->device_type_2_device_name_translated(); // translated device type name - obj["t"] = emsdevice->device_type(); // device type number - obj["b"] = emsdevice->brand_to_char(); // brand - obj["n"] = emsdevice->name(); // name - obj["d"] = emsdevice->device_id(); // deviceid - obj["p"] = emsdevice->product_id(); // productid - obj["v"] = emsdevice->version(); // version - obj["e"] = emsdevice->count_entities(); // number of entities (device values) + obj["id"] = emsdevice->unique_id(); // a unique id + obj["tn"] = emsdevice->device_type_2_device_name_translated(); // translated device type name + obj["t"] = emsdevice->device_type(); // device type number + obj["b"] = emsdevice->brand_to_char(); // brand + obj["n"] = emsdevice->name(); // name + obj["d"] = emsdevice->device_id(); // deviceid + obj["p"] = emsdevice->product_id(); // productid + obj["v"] = emsdevice->version(); // version + obj["e"] = emsdevice->count_entities(); // number of entities (device values) } } if (EMSESP::webEntityService.count_entities()) { JsonObject obj = devices.createNestedObject(); - obj["id"] = "99"; // the last unique id as a string - obj["tn"] = "Custom"; // translated device type name - obj["t"] = EMSdevice::DeviceType::CUSTOM; // device type number - obj["b"] = 0; // brand - obj["n"] = Helpers::translated_word(FL_(custom_device)); // name - obj["d"] = 0; // deviceid - obj["p"] = 0; // productid - obj["v"] = 0; // version - obj["e"] = EMSESP::webEntityService.count_entities(); // number of entities (device values) + obj["id"] = 99; // the last unique id + obj["tn"] = Helpers::translated_word(FL_(custom_device)); // translated device type name + obj["t"] = EMSdevice::DeviceType::CUSTOM; // device type number + obj["b"] = Helpers::translated_word(FL_(na)); // brand + obj["n"] = Helpers::translated_word(FL_(custom_device_name)); // name + obj["d"] = 0; // deviceid + obj["p"] = 0; // productid + obj["v"] = 0; // version + obj["e"] = EMSESP::webEntityService.count_entities(); // number of entities (device values) } // sensors stuff From f80764d72b988fe31579b1e8dd16d435f6a0a541 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 09:44:24 +0200 Subject: [PATCH 04/89] refactor diallogs --- interface/src/project/SettingsEntities.tsx | 100 ++-- .../src/project/SettingsEntitiesDialog.tsx | 66 +-- interface/src/project/SettingsScheduler.tsx | 465 ++++-------------- .../src/project/SettingsSchedulerDialog.tsx | 254 ++++++++++ interface/src/project/types.ts | 12 +- interface/src/project/validators.ts | 56 ++- 6 files changed, 462 insertions(+), 491 deletions(-) create mode 100644 interface/src/project/SettingsSchedulerDialog.tsx diff --git a/interface/src/project/SettingsEntities.tsx b/interface/src/project/SettingsEntities.tsx index 08163a86e..d1d2d6504 100644 --- a/interface/src/project/SettingsEntities.tsx +++ b/interface/src/project/SettingsEntities.tsx @@ -1,36 +1,30 @@ -import type { FC } from 'react'; +import AddIcon from '@mui/icons-material/Add'; +import CancelIcon from '@mui/icons-material/Cancel'; +import WarningIcon from '@mui/icons-material/Warning'; +import { Button, Typography, Box } from '@mui/material'; +import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; +import { useTheme } from '@table-library/react-table-library/theme'; import { useState, useEffect, useCallback } from 'react'; import { unstable_useBlocker as useBlocker } from 'react-router-dom'; -import { Button, Typography, Box } from '@mui/material'; - -import { useTheme } from '@table-library/react-table-library/theme'; -import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; - import { toast } from 'react-toastify'; -import WarningIcon from '@mui/icons-material/Warning'; -import CancelIcon from '@mui/icons-material/Cancel'; -import AddIcon from '@mui/icons-material/Add'; - +import SettingsEntitiesDialog from './SettingsEntitiesDialog'; +import * as EMSESP from './api'; +import { DeviceValueUOM_s } from './types'; +import { entityItemValidation } from './validators'; +import type { EntityItem } from './types'; +import type { FC } from 'react'; import { ButtonRow, FormLoader, SectionContent, BlockNavigation } from 'components'; -import SettingsEntitiesDialog from './SettingsEntitiesDialog'; - -import type { EntityItem } from './types'; -import { DeviceValueUOM_s } from './types'; -import { extractErrorMessage } from 'utils'; - import { useI18nContext } from 'i18n/i18n-react'; - -import * as EMSESP from './api'; -import { entityItemValidation } from './validators'; +import { extractErrorMessage } from 'utils'; const SettingsEntities: FC = () => { const { LL } = useI18nContext(); const [numChanges, setNumChanges] = useState(0); const blocker = useBlocker(numChanges !== 0); - const [entities, setEntities] = useState([]); + const [entities, setEntities] = useState(); const [selectedEntityItem, setSelectedEntityItem] = useState(); const [errorMessage, setErrorMessage] = useState(); const [creating, setCreating] = useState(false); @@ -52,7 +46,9 @@ const SettingsEntities: FC = () => { } useEffect(() => { - setNumChanges(entities ? entities.filter((ei) => hasEntityChanged(ei)).length : 0); + if (entities) { + setNumChanges(entities ? entities.filter((ei) => hasEntityChanged(ei)).length : 0); + } }, [entities]); const entity_theme = useTheme({ @@ -109,28 +105,24 @@ const SettingsEntities: FC = () => { ` }); - const setOriginalEntity = (data: EntityItem[]) => { - setEntities( - data.map((ei) => ({ - ...ei, - o_id: ei.id, - o_device_id: ei.device_id, - o_type_id: ei.type_id, - o_offset: ei.offset, - o_factor: ei.factor, - o_uom: ei.uom, - o_value_type: ei.value_type, - o_name: ei.name, - o_writeable: ei.writeable, - o_deleted: ei.deleted - })) - ); - }; - const fetchEntities = useCallback(async () => { try { const response = await EMSESP.readEntities(); - setOriginalEntity(response.data.entities); + setEntities( + response.data.entities.map((ei) => ({ + ...ei, + o_id: ei.id, + o_device_id: ei.device_id, + o_type_id: ei.type_id, + o_offset: ei.offset, + o_factor: ei.factor, + o_uom: ei.uom, + o_value_type: ei.value_type, + o_name: ei.name, + o_writeable: ei.writeable, + o_deleted: ei.deleted + })) + ); } catch (error) { setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING())); } @@ -160,14 +152,14 @@ const SettingsEntities: FC = () => { }); if (response.status === 200) { - toast.success(LL.SUCCESS()); + toast.success(LL.ENTITIES_UPDATED()); } else { toast.error(LL.PROBLEM_UPDATING()); } + void fetchEntities(); } catch (error) { toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); } - setOriginalEntity(entities); } }; @@ -182,26 +174,25 @@ const SettingsEntities: FC = () => { }; const onDialogSave = (updatedItem: EntityItem) => { - if (creating) { + setDialogOpen(false); + if (entities && creating) { setEntities([...entities.filter((ei) => creating || ei.o_id !== updatedItem.o_id), updatedItem]); } else { - setEntities(entities.map((ei) => (ei.id === updatedItem.id ? { ...ei, ...updatedItem } : ei))); + setEntities(entities?.map((ei) => (ei.id === updatedItem.id ? { ...ei, ...updatedItem } : ei))); } - setDialogOpen(false); }; - // TODO need callback here too? const addEntityItem = () => { setCreating(true); setSelectedEntityItem({ id: Math.floor(Math.random() * (Math.floor(200) - 100) + 100), + name: '', device_id: 11, type_id: 0, offset: 0, factor: 1, - value_type: 2, uom: 0, - name: '', + value_type: 2, writeable: false, deleted: false }); @@ -234,8 +225,8 @@ const SettingsEntities: FC = () => {
{LL.NAME(0)} - Device ID - Type ID + {LL.ID_OF(LL.DEVICE())} + {LL.ID_OF(LL.TYPE(1))} Offset {LL.VALUE(0)} @@ -244,8 +235,8 @@ const SettingsEntities: FC = () => { {tableList.map((ei: EntityItem) => ( editEntityItem(ei)}> {ei.name} - {showHex(ei.device_id, 2)} - {showHex(ei.type_id, 4)} + {showHex(ei.device_id as number, 2)} + {showHex(ei.type_id as number, 4)} {ei.offset} {formatValue(ei.value, ei.uom)} @@ -258,11 +249,12 @@ const SettingsEntities: FC = () => { }; return ( - + {blocker ? : null} {LL.ENTITIES_HELP_1()} + {renderEntity()} {selectedEntityItem && ( @@ -272,7 +264,7 @@ const SettingsEntities: FC = () => { onClose={onDialogClose} onSave={onDialogSave} selectedEntityItem={selectedEntityItem} - validator={entityItemValidation(entities, creating)} + validator={entityItemValidation()} /> )} diff --git a/interface/src/project/SettingsEntitiesDialog.tsx b/interface/src/project/SettingsEntitiesDialog.tsx index 4ac93ff76..8fae8dfc8 100644 --- a/interface/src/project/SettingsEntitiesDialog.tsx +++ b/interface/src/project/SettingsEntitiesDialog.tsx @@ -1,35 +1,32 @@ -import { useState, useEffect } from 'react'; - +import AddIcon from '@mui/icons-material/Add'; +import CancelIcon from '@mui/icons-material/Cancel'; +import DoneIcon from '@mui/icons-material/Done'; +import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; import { - Grid, - Button, Box, + Button, + Checkbox, Dialog, DialogActions, DialogContent, DialogTitle, - Checkbox, + Grid, InputAdornment, MenuItem } from '@mui/material'; +import { useEffect, useState } from 'react'; -import { ValidatedTextField, BlockFormControlLabel } from 'components'; +import { DeviceValueUOM_s } from './types'; +import type { EntityItem } from './types'; +import type Schema from 'async-validator'; +import type { ValidateFieldsError } from 'async-validator'; -import DoneIcon from '@mui/icons-material/Done'; -import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; -import CancelIcon from '@mui/icons-material/Cancel'; -import AddIcon from '@mui/icons-material/Add'; +import { BlockFormControlLabel, ValidatedTextField } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import { validate } from 'validators'; -import type { ValidateFieldsError } from 'async-validator'; -import type Schema from 'async-validator'; - import { updateValue } from 'utils'; - -import type { EntityItem } from './types'; -import { DeviceValueUOM_s } from './types'; +import { validate } from 'validators'; type SettingsEntitiesDialogProps = { open: boolean; @@ -58,6 +55,12 @@ const SettingsEntitiesDialog = ({ if (open) { setFieldErrors(undefined); setEditItem(selectedEntityItem); + // convert to hex strings straight away + setEditItem({ + ...selectedEntityItem, + device_id: ('0' + selectedEntityItem.device_id.toString(16).toUpperCase()).slice(-2), + type_id: ('000' + selectedEntityItem.type_id.toString(16).toUpperCase()).slice(-4) + }); } }, [open, selectedEntityItem]); @@ -69,6 +72,12 @@ const SettingsEntitiesDialog = ({ try { setFieldErrors(undefined); await validate(validator, editItem); + if (typeof editItem.device_id === 'string') { + editItem.device_id = parseInt(editItem.device_id, 16); + } + if (typeof editItem.type_id === 'string') { + editItem.type_id = parseInt(editItem.type_id, 16); + } onSave(editItem); } catch (errors: any) { setFieldErrors(errors); @@ -83,13 +92,12 @@ const SettingsEntitiesDialog = ({ return ( - {creating ? LL.ADD(1) + ' ' + LL.NEW() : LL.EDIT()} {LL.ENTITY()} + {creating ? LL.ADD(1) + ' ' + LL.NEW(1) : LL.EDIT()} {LL.ENTITY()} - - } + control={} label={LL.WRITEABLE()} /> @@ -113,28 +120,26 @@ const SettingsEntitiesDialog = ({ 0x - }} + inputProps={{ style: { textTransform: 'uppercase' } }} + InputProps={{ startAdornment: 0x }} /> 0x - }} + inputProps={{ style: { textTransform: 'uppercase' } }} + InputProps={{ startAdornment: 0x }} /> @@ -169,7 +174,8 @@ const SettingsEntitiesDialog = ({ TIME - {selectedEntityItem.value_type !== 0 && ( + + {editItem.value_type !== 0 && ( <> { const { LL, locale } = useI18nContext(); - const [numChanges, setNumChanges] = useState(0); const blocker = useBlocker(numChanges !== 0); - - const emptySchedule = { - id: 0, - active: false, - deleted: false, - flags: 0, - time: '12:00', - cmd: '', - value: '', - name: '', - o_name: '' - }; - - const [schedule, setSchedule] = useState([emptySchedule]); - const [scheduleItem, setScheduleItem] = useState(); + const [schedule, setSchedule] = useState([]); + const [selectedScheduleItem, setSelectedScheduleItem] = useState(); const [dow, setDow] = useState([]); const [errorMessage, setErrorMessage] = useState(); const [creating, setCreating] = useState(false); - const [fieldErrors, setFieldErrors] = useState(); - - // eslint-disable-next-line - const [flags, setFlags] = useState(() => ['']); - - function getDayNames() { - const formatter = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' }); - const days = [1, 2, 3, 4, 5, 6, 7].map((day) => { - const dd = day < 10 ? `0${day}` : day; - return new Date(`2017-01-${dd}T00:00:00+00:00`); - }); - return days.map((date) => formatter.format(date)); - } + const [dialogOpen, setDialogOpen] = useState(false); function hasScheduleChanged(si: ScheduleItem) { return ( @@ -107,17 +45,11 @@ const SettingsScheduler: FC = () => { ); } - // TODO fix - const getNumChanges = () => { - if (!schedule) { - return 0; - } - return schedule.filter((si) => hasScheduleChanged(si)).length; - }; - useEffect(() => { - setNumChanges(getNumChanges()); - }); + if (schedule) { + setNumChanges(schedule ? schedule.filter((si) => hasScheduleChanged(si)).length : 0); + } + }, [schedule]); const schedule_theme = useTheme({ Table: ` @@ -164,72 +96,37 @@ const SettingsScheduler: FC = () => { ` }); - const setOriginalSchedule = (data: ScheduleItem[]) => { - setSchedule( - data.map((si) => ({ - ...si, - o_id: si.id, - o_active: si.active, - o_deleted: si.deleted, - o_flags: si.flags, - o_time: si.time, - o_cmd: si.cmd, - o_value: si.value, - o_name: si.name - })) - ); - }; - const fetchSchedule = useCallback(async () => { try { const response = await EMSESP.readSchedule(); - setOriginalSchedule(response.data.schedule); + setSchedule( + response.data.schedule.map((si) => ({ + ...si, + o_id: si.id, + o_active: si.active, + o_deleted: si.deleted, + o_flags: si.flags, + o_time: si.time, + o_cmd: si.cmd, + o_value: si.value, + o_name: si.name + })) + ); } catch (error) { setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING())); } }, [LL]); + // on mount useEffect(() => { + const formatter = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' }); + const days = [1, 2, 3, 4, 5, 6, 7].map((day) => { + const dd = day < 10 ? `0${day}` : day; + return new Date(`2017-01-${dd}T00:00:00+00:00`); + }); + setDow(days.map((date) => formatter.format(date))); void fetchSchedule(); - setDow(getDayNames()); - }, [getDayNames, fetchSchedule]); - - const getFlagNumber = (newFlag: string[]) => { - let new_flag = 0; - for (const entry of newFlag) { - new_flag |= Number(entry); - } - return new_flag; - }; - - const getFlagString = (f: number) => { - const new_flags: string[] = []; - if ((f & 1) === 1) { - new_flags.push('1'); - } - if ((f & 2) === 2) { - new_flags.push('2'); - } - if ((f & 4) === 4) { - new_flags.push('4'); - } - if ((f & 8) === 8) { - new_flags.push('8'); - } - if ((f & 16) === 16) { - new_flags.push('16'); - } - if ((f & 32) === 32) { - new_flags.push('32'); - } - if ((f & 64) === 64) { - new_flags.push('64'); - } - if ((f & 128) === 128) { - new_flags.push('128'); - } - return new_flags; - }; + }, [locale, fetchSchedule]); const saveSchedule = async () => { if (schedule) { @@ -248,74 +145,40 @@ const SettingsScheduler: FC = () => { })) }); if (response.status === 200) { - toast.success(LL.SCHEDULE_SAVED()); + toast.success(LL.SCHEDULE_UPDATED()); } else { toast.error(LL.PROBLEM_UPDATING()); } + void fetchSchedule(); } catch (error) { toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); } - setOriginalSchedule(schedule); } }; - function getFlagName(flag: number) { - if ((flag & ScheduleFlag.SCHEDULE_MON) === ScheduleFlag.SCHEDULE_MON) { - return dow[1]; - } - if ((flag & ScheduleFlag.SCHEDULE_TUE) === ScheduleFlag.SCHEDULE_TUE) { - return dow[2]; - } - if ((flag & ScheduleFlag.SCHEDULE_WED) === ScheduleFlag.SCHEDULE_WED) { - return dow[3]; - } - if ((flag & ScheduleFlag.SCHEDULE_THU) === ScheduleFlag.SCHEDULE_THU) { - return dow[4]; - } - if ((flag & ScheduleFlag.SCHEDULE_FRI) === ScheduleFlag.SCHEDULE_FRI) { - return dow[5]; - } - if ((flag & ScheduleFlag.SCHEDULE_SAT) === ScheduleFlag.SCHEDULE_SAT) { - return dow[6]; - } - if ((flag & ScheduleFlag.SCHEDULE_SUN) === ScheduleFlag.SCHEDULE_SUN) { - return dow[0]; - } - if ((flag & ScheduleFlag.SCHEDULE_TIMER) === ScheduleFlag.SCHEDULE_TIMER) { - return LL.TIMER(0); - } - return ''; - } - - const dayBox = (si: ScheduleItem, flag: number) => ( - <> - - - {getFlagName(flag)} - - - - - ); - - const showFlag = (si: ScheduleItem, flag: number) => ( - - {getFlagName(flag)} - - ); - - const editScheduleItem = (si: ScheduleItem) => { - if (si.name === undefined) { - si.name = ''; - } + const editScheduleItem = useCallback((si: ScheduleItem) => { setCreating(false); - setScheduleItem(si); + setSelectedScheduleItem(si); + setDialogOpen(true); + }, []); + + const onDialogClose = () => { + setDialogOpen(false); + }; + + const onDialogSave = (updatedItem: ScheduleItem) => { + setDialogOpen(false); + if (schedule && creating) { + setSchedule([...schedule.filter((si) => creating || si.o_id !== updatedItem.o_id), updatedItem]); + } else { + setSchedule(schedule?.map((si) => (si.id === updatedItem.id ? { ...si, ...updatedItem } : si))); + } }; const addScheduleItem = () => { setCreating(true); - setScheduleItem({ - id: makeid(), + setSelectedScheduleItem({ + id: Math.floor(Math.random() * (Math.floor(200) - 100) + 100), active: false, deleted: false, flags: 0, @@ -324,13 +187,7 @@ const SettingsScheduler: FC = () => { value: '', name: '' }); - }; - - const updateScheduleItem = () => { - if (scheduleItem) { - setSchedule([...schedule.filter((si) => creating || si.o_id !== scheduleItem.o_id), scheduleItem]); - } - setScheduleItem(undefined); + setDialogOpen(true); }; const renderSchedule = () => { @@ -338,6 +195,17 @@ const SettingsScheduler: FC = () => { return ; } + const dayBox = (si: ScheduleItem, flag: number) => ( + <> + + + {flag === ScheduleFlag.SCHEDULE_TIMER ? LL.TIMER(0) : dow[Math.log(flag) / Math.log(2)]} + + + + + ); + return (
!si.deleted).sort((a, b) => a.time.localeCompare(b.time)) }} @@ -388,173 +256,6 @@ const SettingsScheduler: FC = () => { ); }; - const removeScheduleItem = (si: ScheduleItem) => { - si.deleted = true; - setScheduleItem(si); - updateScheduleItem(); - }; - - const validateScheduleItem = async () => { - if (scheduleItem) { - try { - setFieldErrors(undefined); - await validate(schedulerItemValidation(schedule, scheduleItem), scheduleItem); - updateScheduleItem(); - } catch (errors: any) { - setFieldErrors(errors); - } - } - }; - - const closeDialog = () => { - setScheduleItem(undefined); - setFieldErrors(undefined); - }; - - const renderEditSchedule = () => { - if (scheduleItem) { - const isTimer = scheduleItem.flags === ScheduleFlag.SCHEDULE_TIMER; - return ( - closeDialog()}> - - {creating ? LL.ADD(1) + ' ' + LL.NEW() : LL.EDIT()} {LL.SCHEDULE(1)} - - - - - { - scheduleItem.flags = getFlagNumber(flag) & 127; - setFlags(['']); // forces refresh - }} - > - {showFlag(scheduleItem, ScheduleFlag.SCHEDULE_MON)} - {showFlag(scheduleItem, ScheduleFlag.SCHEDULE_TUE)} - {showFlag(scheduleItem, ScheduleFlag.SCHEDULE_WED)} - {showFlag(scheduleItem, ScheduleFlag.SCHEDULE_THU)} - {showFlag(scheduleItem, ScheduleFlag.SCHEDULE_FRI)} - {showFlag(scheduleItem, ScheduleFlag.SCHEDULE_SAT)} - {showFlag(scheduleItem, ScheduleFlag.SCHEDULE_SUN)} - - - - {isTimer ? ( - - ) : ( - - )} - - - - - } - label={LL.ACTIVE()} - /> - {scheduleItem.active && ( - - - - )} - - - - - {isTimer && ( - - {LL.SCHEDULER_HELP_2()} - - )} - - - - - - - {!creating && ( - - - - )} - - - - - ); - } - }; - return ( {blocker ? : null} @@ -562,7 +263,19 @@ const SettingsScheduler: FC = () => { {LL.SCHEDULER_HELP_1()} {renderSchedule()} - {renderEditSchedule()} + + {selectedScheduleItem && ( + + )} + {numChanges !== 0 && ( diff --git a/interface/src/project/SettingsSchedulerDialog.tsx b/interface/src/project/SettingsSchedulerDialog.tsx new file mode 100644 index 000000000..976b7b2d5 --- /dev/null +++ b/interface/src/project/SettingsSchedulerDialog.tsx @@ -0,0 +1,254 @@ +import AddIcon from '@mui/icons-material/Add'; +import CancelIcon from '@mui/icons-material/Cancel'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import DoneIcon from '@mui/icons-material/Done'; +import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; + +import { + Box, + Button, + Checkbox, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Grid, + TextField, + ToggleButton, + ToggleButtonGroup, + Typography +} from '@mui/material'; +import { useEffect, useState } from 'react'; + +import { ScheduleFlag } from './types'; +import type { ScheduleItem } from './types'; +import type Schema from 'async-validator'; +import type { ValidateFieldsError } from 'async-validator'; + +import { BlockFormControlLabel, ValidatedTextField } from 'components'; + +import { useI18nContext } from 'i18n/i18n-react'; + +import { updateValue } from 'utils'; +import { validate } from 'validators'; + +type SettingsSchedulerDialogProps = { + open: boolean; + creating: boolean; + onClose: () => void; + onSave: (ei: ScheduleItem) => void; + selectedSchedulerItem: ScheduleItem; + validator: Schema; + dow: string[]; +}; + +const SettingsSchedulerDialog = ({ + open, + creating, + onClose, + onSave, + selectedSchedulerItem, + validator, + dow +}: SettingsSchedulerDialogProps) => { + const { LL } = useI18nContext(); + const [editItem, setEditItem] = useState(selectedSchedulerItem); + const [fieldErrors, setFieldErrors] = useState(); + + const updateFormValue = updateValue(setEditItem); + + useEffect(() => { + if (open) { + setFieldErrors(undefined); + setEditItem(selectedSchedulerItem); + } + }, [open, selectedSchedulerItem]); + + const close = () => { + onClose(); + }; + + const save = async () => { + try { + setFieldErrors(undefined); + await validate(validator, editItem); + onSave(editItem); + } catch (errors: any) { + setFieldErrors(errors); + } + }; + + const remove = () => { + editItem.deleted = true; + onSave(editItem); + }; + + const getFlagNumber = (newFlag: string[]) => { + let new_flag = 0; + for (const entry of newFlag) { + new_flag |= Number(entry); + } + return new_flag; + }; + + const getFlagString = (f: number) => { + const new_flags: string[] = []; + if ((f & 1) === 1) { + new_flags.push('1'); + } + if ((f & 2) === 2) { + new_flags.push('2'); + } + if ((f & 4) === 4) { + new_flags.push('4'); + } + if ((f & 8) === 8) { + new_flags.push('8'); + } + if ((f & 16) === 16) { + new_flags.push('16'); + } + if ((f & 32) === 32) { + new_flags.push('32'); + } + if ((f & 64) === 64) { + new_flags.push('64'); + } + if ((f & 128) === 128) { + new_flags.push('128'); + } + return new_flags; + }; + + const showFlag = (si: ScheduleItem, flag: number) => ( + + {flag === ScheduleFlag.SCHEDULE_TIMER ? LL.TIMER(0) : dow[Math.log(flag) / Math.log(2)]} + + ); + + const isTimer = editItem.flags === ScheduleFlag.SCHEDULE_TIMER; + + return ( + + + {creating ? LL.ADD(1) + ' ' + LL.NEW(1) : LL.EDIT()} {LL.ENTITY()} + + + + + { + setEditItem({ ...editItem, flags: getFlagNumber(flag) & 127 }); + }} + > + {showFlag(editItem, ScheduleFlag.SCHEDULE_MON)} + {showFlag(editItem, ScheduleFlag.SCHEDULE_TUE)} + {showFlag(editItem, ScheduleFlag.SCHEDULE_WED)} + {showFlag(editItem, ScheduleFlag.SCHEDULE_THU)} + {showFlag(editItem, ScheduleFlag.SCHEDULE_FRI)} + {showFlag(editItem, ScheduleFlag.SCHEDULE_SAT)} + {showFlag(editItem, ScheduleFlag.SCHEDULE_SUN)} + + + + {isTimer ? ( + + ) : ( + + )} + + + + } + label={LL.ACTIVE()} + /> + {editItem.active && ( + + + + )} + + + + {isTimer && ( + + {LL.SCHEDULER_HELP_2()} + + )} + + + + + + + {!creating && ( + + + + )} + + + + + ); +}; + +export default SettingsSchedulerDialog; diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index 2fa83fcab..c0179b680 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -60,7 +60,7 @@ export interface Status { } export interface Device { - id: string; // id index + id: number; // id index tn: string; // device type translated name t: number; // device type id b: string; // brand @@ -341,19 +341,19 @@ export enum ScheduleFlag { export interface EntityItem { id: number; // unique number name: string; - device_id: number; - type_id: number; + device_id: number | string; + type_id: number | string; offset: number; factor: number; uom: number; value_type: number; - value?: number; + value?: number; // optional writeable: boolean; deleted?: boolean; // optional o_id?: number; o_name?: string; - o_device_id?: number; - o_type_id?: number; + o_device_id?: number | string; + o_type_id?: number | string; o_offset?: number; o_factor?: number; o_uom?: number; diff --git a/interface/src/project/validators.ts b/interface/src/project/validators.ts index 3d7e9b795..e394a10f4 100644 --- a/interface/src/project/validators.ts +++ b/interface/src/project/validators.ts @@ -1,7 +1,7 @@ -import type { InternalRuleItem } from 'async-validator'; import Schema from 'async-validator'; +import type { Settings } from './types'; +import type { InternalRuleItem } from 'async-validator'; import { IP_OR_HOSTNAME_VALIDATOR } from 'validators/shared'; -import type { Settings, ScheduleItem, EntityItem } from './types'; export const GPIO_VALIDATOR = { validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) { @@ -86,25 +86,14 @@ 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 = () => new Schema({ name: [ { type: 'string', pattern: /^[a-zA-Z0-9_\\.]{0,15}$/, message: "Must be <15 characters: alpha numeric, '_' or '.'" - }, - ...[uniqueNameValidator(schedule, scheduleItem.o_name)] + } ], cmd: [ { required: true, message: 'Command is required' }, @@ -112,7 +101,7 @@ export const schedulerItemValidation = (schedule: ScheduleItem[], scheduleItem: ] }); -export const entityItemValidation = (entities: EntityItem[], creating: boolean) => +export const entityItemValidation = () => new Schema({ name: [ { required: true, message: 'Name is required' }, @@ -122,13 +111,30 @@ export const entityItemValidation = (entities: EntityItem[], creating: boolean) message: "Must be <15 characters: alpha numeric, '_' or '.'" } ], - device_id: [{ type: 'hex', required: true, message: 'ID must be a hex value' }] - // type_id: [ - // { required: true, message: 'Type_id is required' }, - // { type: 'string', pattern: /^[A-F0-9]{1,4}$/, message: 'Must be a hex number' } - // ], - // offset: [ - // { required: true, message: 'Offset is required' }, - // { type: 'number', min: 0, max: 255, message: 'Must be between 0 and 255' } - // ] + device_id: [ + { required: true, message: 'Device ID is required' }, + { + validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) { + if (isNaN(parseInt(value, 16))) { + callback('Must be a hex number'); + } + callback(); + } + } + ], + type_id: [ + { required: true, message: 'Type ID is required' }, + { + validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) { + if (isNaN(parseInt(value, 16))) { + callback('Must be a hex number'); + } + callback(); + } + } + ], + offset: [ + { required: true, message: 'Offset is required' }, + { type: 'number', min: 0, max: 255, message: 'Must be between 0 and 255' } + ] }); From 84635ac889384a85e04bf4ea241988d17792ab83 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 09:44:39 +0200 Subject: [PATCH 05/89] autoformat --- .vscode/settings.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5afd93c63..6cd3e6dd1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,10 @@ "**/.yarn": true, "**/.pnp.*": true }, + "editor.codeActionsOnSave": { + "source.fixAll": true + // "source.organizeImports": true + }, "eslint.nodePath": "interface/.yarn/sdks", "eslint.workingDirectories": ["interface"], "prettier.prettierPath": "interface/.yarn/sdks/prettier/index.js", From e81b1b927701a606306242ef89ba04c2fe059233 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 09:45:12 +0200 Subject: [PATCH 06/89] rename entities_saved to updated --- interface/src/i18n/de/index.ts | 4 ++-- interface/src/i18n/en/index.ts | 4 ++-- interface/src/i18n/fr/index.ts | 4 ++-- interface/src/i18n/nl/index.ts | 4 ++-- interface/src/i18n/no/index.ts | 4 ++-- interface/src/i18n/pl/index.ts | 4 ++-- interface/src/i18n/sv/index.ts | 4 ++-- interface/src/i18n/tr/index.ts | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index e00ae0f54..43ba48d1d 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -315,13 +315,13 @@ const de: Translation = { SCHEDULE: 'Zeitplan', TIME: 'Zeit', TIMER: 'Timer', - SCHEDULE_SAVED: 'Plan gespeichert', + SCHEDULE_UPDATED: 'Plan gespeichert', SCHEDULE_TIMER_1: 'beim Start', SCHEDULE_TIMER_2: 'jede Minute', SCHEDULE_TIMER_3: 'jede Stunde', CUSTOM_ENTITIES: 'Individuelle Entitäten', ENTITIES_HELP_1: 'Abfrage von Werten auf dem EMS-Bus', - ENTITIES_SAVED: 'Entities Saved', // TODO translate + ENTITIES_UPDATED: 'Entities Updated', // TODO translate WRITEABLE: 'Schreibbar' }; diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index 30ec9f2fd..4750a99ad 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -315,13 +315,13 @@ const en: Translation = { SCHEDULE: 'Schedule', TIME: 'Time', TIMER: 'Timer', - SCHEDULE_SAVED: 'Schedule updated', + SCHEDULE_UPDATED: 'Schedule updated', SCHEDULE_TIMER_1: 'on startup', SCHEDULE_TIMER_2: 'every minute', SCHEDULE_TIMER_3: 'every hour', CUSTOM_ENTITIES: 'Custom Entities', ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', - ENTITIES_SAVED: 'Entities Saved', + ENTITIES_UPDATED: 'Entities Updated', WRITEABLE: 'Writeable' }; diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts index 86a8f4d8d..6059cc95c 100644 --- a/interface/src/i18n/fr/index.ts +++ b/interface/src/i18n/fr/index.ts @@ -315,13 +315,13 @@ const fr: Translation = { SCHEDULE: 'Schedule', // TODO translate TIME: 'Time', // TODO translate TIMER: 'Timer', // TODO translate - SCHEDULE_SAVED: 'Schedule updated', // TODO translate + SCHEDULE_UPDATED: 'Schedule updated', // TODO translate SCHEDULE_TIMER_1: 'on startup', // TODO translate SCHEDULE_TIMER_2: 'every minute', // TODO translate SCHEDULE_TIMER_3: 'every hour', // TODO translate CUSTOM_ENTITIES: 'Custom Entities', // TODO translate ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate - ENTITIES_SAVED: 'Entities Saved', // TODO translate + ENTITIES_UPDATED: 'Entities Updated', // TODO translate WRITEABLE: 'Writeable' // TODO translate }; diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index 2e78472ea..beaf38722 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -315,13 +315,13 @@ const nl: Translation = { SCHEDULE: 'Schedule', // TODO translate TIME: 'Time', // TODO translate TIMER: 'Timer', // TODO translate - SCHEDULE_SAVED: 'Schedule updated', // TODO translate + SCHEDULE_UPDATED: 'Schedule updated', // TODO translate SCHEDULE_TIMER_1: 'on startup', // TODO translate SCHEDULE_TIMER_2: 'every minute', // TODO translate SCHEDULE_TIMER_3: 'every hour', // TODO translate CUSTOM_ENTITIES: 'Custom Entities', // TODO translate ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate - ENTITIES_SAVED: 'Entities Saved', // TODO translate + ENTITIES_UPDATED: 'Entities Updated', // TODO translate WRITEABLE: 'Writeable' // TODO translate }; diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts index 8d62adf01..0ab8ecac0 100644 --- a/interface/src/i18n/no/index.ts +++ b/interface/src/i18n/no/index.ts @@ -315,13 +315,13 @@ const no: Translation = { SCHEDULE: 'Planlegg', TIME: 'Tid', TIMER: 'Timer', - SCHEDULE_SAVED: 'Planlegger er oppdatert', + SCHEDULE_UPDATED: 'Planlegger er oppdatert', SCHEDULE_TIMER_1: 'ved oppstart', SCHEDULE_TIMER_2: 'hvert minutt', SCHEDULE_TIMER_3: 'hver time', CUSTOM_ENTITIES: 'Custom Entities', // TODO translate ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate - ENTITIES_SAVED: 'Entities Saved', // TODO translate + ENTITIES_UPDATED: 'Entities Updated', // TODO translate WRITEABLE: 'Writeable' // TODO translate }; diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index f770bf952..edf8e9488 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -315,13 +315,13 @@ const pl: BaseTranslation = { SCHEDULE: '{{H|h|}}armonogram{{|u|}}', TIME: '{{Zegar|Godzina|}}', TIMER: '{{m|M|}}inutnik', - SCHEDULE_SAVED: 'Harmonogram został uaktualniony.', + SCHEDULE_UPDATED: 'Harmonogram został uaktualniony.', SCHEDULE_TIMER_1: 'przy starcie', SCHEDULE_TIMER_2: 'co minutę', SCHEDULE_TIMER_3: 'co godzinę', CUSTOM_ENTITIES: '{{N|n|}}iestandardowe{{|j|}} encj{{e|i|}}', ENTITIES_HELP_1: 'Zdefiniuj niestandardowe encje pobierane z magistrali EMS.', - ENTITIES_SAVED: 'Niestandardowe encje zostały uaktualnione.', + ENTITIES_UPDATED: 'Niestandardowe encje zostały uaktualnione.', WRITEABLE: 'Writeable' // TODO translate }; diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts index 1cf0e7301..9492425d4 100644 --- a/interface/src/i18n/sv/index.ts +++ b/interface/src/i18n/sv/index.ts @@ -315,13 +315,13 @@ const sv: Translation = { SCHEDULE: 'Schedule', // TODO translate TIME: 'Time', // TODO translate TIMER: 'Timer', // TODO translate - SCHEDULE_SAVED: 'Schedule updated', // TODO translate + SCHEDULE_UPDATED: 'Schedule updated', // TODO translate SCHEDULE_TIMER_1: 'on startup', // TODO translate SCHEDULE_TIMER_2: 'every minute', // TODO translate SCHEDULE_TIMER_3: 'every hour', // TODO translate CUSTOM_ENTITIES: 'Custom Entities', // TODO translate ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate - ENTITIES_SAVED: 'Entities Saved', // TODO translate + ENTITIES_UPDATED: 'Entities Updated', // TODO translate WRITEABLE: 'Writeable' // TODO translate }; diff --git a/interface/src/i18n/tr/index.ts b/interface/src/i18n/tr/index.ts index 5ea666083..400fdaafd 100644 --- a/interface/src/i18n/tr/index.ts +++ b/interface/src/i18n/tr/index.ts @@ -315,13 +315,13 @@ const tr: Translation = { SCHEDULE: 'Schedule', // TODO translate TIME: 'Time', // TODO translate TIMER: 'Timer', // TODO translate - SCHEDULE_SAVED: 'Schedule updated', // TODO translate + SCHEDULE_UPDATED: 'Schedule updated', // TODO translate SCHEDULE_TIMER_1: 'on startup', // TODO translate SCHEDULE_TIMER_2: 'every minute', // TODO translate SCHEDULE_TIMER_3: 'every hour', // TODO translate CUSTOM_ENTITIES: 'Custom Entities', // TODO translate ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate - ENTITIES_SAVED: 'Entities Saved', // TODO translate + ENTITIES_UPDATED: 'Entities Updated', // TODO translate WRITEABLE: 'Writeable' // TODO translate }; From fa263918baaf7c15f1469b5942439f862bc4882b Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 09:45:37 +0200 Subject: [PATCH 07/89] updated packages --- interface/package.json | 15 +++---- interface/yarn.lock | 92 ++++++++++++++++++++++-------------------- 2 files changed, 57 insertions(+), 50 deletions(-) diff --git a/interface/package.json b/interface/package.json index 7e746c3a3..81791870b 100644 --- a/interface/package.json +++ b/interface/package.json @@ -16,7 +16,8 @@ "standalone": "npm-run-all -p dev typesafe-i18n mock-api", "typesafe-i18n": "typesafe-i18n", "format": "prettier --write '**/*.{ts,tsx,js,css,json,md}'", - "lint": "eslint . --cache --max-warnings=0" + "lint": "eslint . --cache --max-warnings=0", + "lint-fixall": "eslint . --cache --fix" }, "dependencies": { "@emotion/react": "^11.10.6", @@ -27,13 +28,13 @@ "@remix-run/router": "^1.5.0", "@table-library/react-table-library": "4.1.0", "@types/lodash-es": "^4.17.7", - "@types/node": "^18.15.11", - "@types/react": "^18.0.37", + "@types/node": "^18.15.13", + "@types/react": "^18.0.38", "@types/react-dom": "^18.0.11", "@types/react-router-dom": "^5.3.3", "@yarnpkg/pnpify": "^4.0.0-rc.42", "async-validator": "^4.2.5", - "axios": "^1.3.5", + "axios": "^1.3.6", "history": "^5.3.0", "jwt-decode": "^3.1.2", "lodash-es": "^4.17.21", @@ -54,7 +55,7 @@ "@typescript-eslint/eslint-plugin": "^5.59.0", "@typescript-eslint/parser": "^5.59.0", "@vitejs/plugin-react-swc": "^3.3.0", - "eslint": "^8.38.0", + "eslint": "^8.39.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-prettier": "^8.8.0", @@ -69,8 +70,8 @@ "npm-run-all": "^4.1.5", "prettier": "^2.8.7", "rollup-plugin-visualizer": "^5.9.0", - "terser": "^5.17.0", - "vite": "^4.2.2", + "terser": "^5.17.1", + "vite": "^4.3.1", "vite-plugin-minify": "^1.5.2", "vite-plugin-svgr": "^2.4.0", "vite-tsconfig-paths": "^4.2.0" diff --git a/interface/yarn.lock b/interface/yarn.lock index 7054d0cc2..ebd7ba443 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -595,10 +595,10 @@ __metadata: languageName: node linkType: hard -"@eslint/js@npm:8.38.0": - version: 8.38.0 - resolution: "@eslint/js@npm:8.38.0" - checksum: e2f4b565d542758779b98019cfa63e24fc56fabfb8d04caf7f6310753703116b880b6a8061d671f2a40a68dba24a8a199eb01d5c8b140f53c49f05c75b404ff5 +"@eslint/js@npm:8.39.0": + version: 8.39.0 + resolution: "@eslint/js@npm:8.39.0" + checksum: bb7ed9c22b998e8c765d87b12225ae046ae4c571c5c88d1012908c3ae1ae28e6248ebc98aed66b08334a8a9e43420bcc31a0e7f80173dafa6cc97f59735512e6 languageName: node linkType: hard @@ -1341,13 +1341,20 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:^18.11.11, @types/node@npm:^18.15.11": +"@types/node@npm:*, @types/node@npm:^18.11.11": version: 18.15.11 resolution: "@types/node@npm:18.15.11" checksum: 670deb1a9daa812dc86b1e8964c0c6b0bef7c32672833c10578c1e5dd2682f2bd99b86d814fde86a5dd4a3da48ea039f41db30a835b245aa7c34c62fa1f23f0d languageName: node linkType: hard +"@types/node@npm:^18.15.13": + version: 18.15.13 + resolution: "@types/node@npm:18.15.13" + checksum: 6e5f61c559e60670a7a8fb88e31226ecc18a21be103297ca4cf9848f0a99049dae77f04b7ae677205f2af494f3701b113ba8734f4b636b355477a6534dbb8ada + languageName: node + linkType: hard + "@types/parse-json@npm:^4.0.0": version: 4.0.0 resolution: "@types/parse-json@npm:4.0.0" @@ -1421,14 +1428,14 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:^18.0.37": - version: 18.0.37 - resolution: "@types/react@npm:18.0.37" +"@types/react@npm:^18.0.38": + version: 18.0.38 + resolution: "@types/react@npm:18.0.38" dependencies: "@types/prop-types": "*" "@types/scheduler": "*" csstype: ^3.0.2 - checksum: 1919fb9fb48d574fafeb196aced7ea1ee345525597afa6ad01049c7ce090a732bc500c4a392deb0a5fb9165d5ae5fe720b795e3023bb5a9b2b2a0fae059b4407 + checksum: 39bad7b8ffe2288f7168fb35ce0fe12109771dcf985e673351074ca7ba8b67884686f92696692b90bbe28a68c2cfe3aabd691e717f88fbf19e01284a14abfdb5 languageName: node linkType: hard @@ -1737,8 +1744,8 @@ __metadata: "@table-library/react-table-library": 4.1.0 "@types/lodash-es": ^4.17.7 "@types/mime-types": ^2 - "@types/node": ^18.15.11 - "@types/react": ^18.0.37 + "@types/node": ^18.15.13 + "@types/react": ^18.0.38 "@types/react-dom": ^18.0.11 "@types/react-router-dom": ^5.3.3 "@types/styled-components": ^5 @@ -1747,8 +1754,8 @@ __metadata: "@vitejs/plugin-react-swc": ^3.3.0 "@yarnpkg/pnpify": ^4.0.0-rc.42 async-validator: ^4.2.5 - axios: ^1.3.5 - eslint: ^8.38.0 + axios: ^1.3.6 + eslint: ^8.39.0 eslint-config-airbnb: ^19.0.4 eslint-config-airbnb-typescript: ^17.0.0 eslint-config-prettier: ^8.8.0 @@ -1774,10 +1781,10 @@ __metadata: react-toastify: ^9.1.2 rollup-plugin-visualizer: ^5.9.0 sockette: ^2.0.6 - terser: ^5.17.0 + terser: ^5.17.1 typesafe-i18n: ^5.24.3 typescript: ^5.0.4 - vite: ^4.2.2 + vite: ^4.3.1 vite-plugin-minify: ^1.5.2 vite-plugin-svgr: ^2.4.0 vite-tsconfig-paths: ^4.2.0 @@ -2037,14 +2044,14 @@ __metadata: languageName: node linkType: hard -"axios@npm:^1.3.5": - version: 1.3.5 - resolution: "axios@npm:1.3.5" +"axios@npm:^1.3.6": + version: 1.3.6 + resolution: "axios@npm:1.3.6" dependencies: follow-redirects: ^1.15.0 form-data: ^4.0.0 proxy-from-env: ^1.1.0 - checksum: 74664fbf617fa6c10a2739afa356a16f4182ab41701315a9e475fa1c6f6c1e1c975361eabd8c113156c2eff263cb0eb6da7c0b395fa2aa181971196aed51d2ee + checksum: 451d7ec576a74b2846978e193458466f17ab676f1976630290e717e96aff84c90f755612d268731f722d363e45993e1844ef4fe8cdd022fe5663fc8f6267a7e3 languageName: node linkType: hard @@ -3154,13 +3161,13 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:^7.1.1": - version: 7.1.1 - resolution: "eslint-scope@npm:7.1.1" +"eslint-scope@npm:^7.2.0": + version: 7.2.0 + resolution: "eslint-scope@npm:7.2.0" dependencies: esrecurse: ^4.3.0 estraverse: ^5.2.0 - checksum: 3ae3280cbea34af3b816e941b83888aca063aaa0169966ff7e4c1bfb0715dbbeac3811596e56315e8ceea84007a7403754459ae4f1d19f25487eb02acd951aa7 + checksum: 5b48a3cc2485a3a58ca0bdecfb557c349009308a9b2afb24d070b1c0c254d445ee86d78bfee2c4ed6d1b8944307604a987c92f6d7e611e29de5d06256747a0ff languageName: node linkType: hard @@ -3171,14 +3178,14 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8.38.0": - version: 8.38.0 - resolution: "eslint@npm:8.38.0" +"eslint@npm:^8.39.0": + version: 8.39.0 + resolution: "eslint@npm:8.39.0" dependencies: "@eslint-community/eslint-utils": ^4.2.0 "@eslint-community/regexpp": ^4.4.0 "@eslint/eslintrc": ^2.0.2 - "@eslint/js": 8.38.0 + "@eslint/js": 8.39.0 "@humanwhocodes/config-array": ^0.11.8 "@humanwhocodes/module-importer": ^1.0.1 "@nodelib/fs.walk": ^1.2.8 @@ -3188,7 +3195,7 @@ __metadata: debug: ^4.3.2 doctrine: ^3.0.0 escape-string-regexp: ^4.0.0 - eslint-scope: ^7.1.1 + eslint-scope: ^7.2.0 eslint-visitor-keys: ^3.4.0 espree: ^9.5.1 esquery: ^1.4.2 @@ -3217,7 +3224,7 @@ __metadata: text-table: ^0.2.0 bin: eslint: bin/eslint.js - checksum: 1aeba0106770bd29834bb01550c72fa0ebea851ceeaef61d2860ecb455391992b316f222600939f11d12db2a7ea6fb9443f4aa137566f98f9f26af9fa40b96b5 + checksum: 34679da06fbc9ee75d13de57864589537e7460408c923510029b87cdf9f52fec2eb7f85cebbbff7ed15de0b37b7b14969efb036804f774aa4455809c9ccea2cb languageName: node linkType: hard @@ -5592,9 +5599,9 @@ __metadata: languageName: node linkType: hard -"rollup@npm:^3.18.0": - version: 3.20.2 - resolution: "rollup@npm:3.20.2" +"rollup@npm:^3.20.2": + version: 3.20.6 + resolution: "rollup@npm:3.20.6" dependencies: fsevents: ~2.3.2 dependenciesMeta: @@ -5602,7 +5609,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 1203da200a6f7dd2e62445fe31c9711134545f32d8a91f8fc3d79d5eff648d5faaf296af1d867c95439b862a39457670f5a32ecad583c11fc0de1ab2d2ca12b1 + checksum: 977b4e3177dfca46d9b3f78d661a19a8da17543f11f154b6df0f45494794cf4203cbebeafd89b43bde1e99766c0fad35429cd13e3ee26d75d548166f7780154f languageName: node linkType: hard @@ -6100,9 +6107,9 @@ __metadata: languageName: node linkType: hard -"terser@npm:^5.17.0": - version: 5.17.0 - resolution: "terser@npm:5.17.0" +"terser@npm:^5.17.1": + version: 5.17.1 + resolution: "terser@npm:5.17.1" dependencies: "@jridgewell/source-map": ^0.3.2 acorn: ^8.5.0 @@ -6110,7 +6117,7 @@ __metadata: source-map-support: ~0.5.20 bin: terser: bin/terser - checksum: fb5c81a837fc4083c1471b5cd599505666ef9f007381fb934c39f8fc5e0c034914e32d2de247aeb7f8a9723c1dc411baf5153368cfe8ed1927139ff030516eda + checksum: 00d4712b954307709b4e14cf17e0ab2fc2d527b480a50f212414aad12a739fb374d7b3391da2b73f82e21c9d469111ad2cd1d054861759624bd67c6dea4bd76a languageName: node linkType: hard @@ -6411,15 +6418,14 @@ __metadata: languageName: node linkType: hard -"vite@npm:^4.2.2": - version: 4.2.2 - resolution: "vite@npm:4.2.2" +"vite@npm:^4.3.1": + version: 4.3.1 + resolution: "vite@npm:4.3.1" dependencies: esbuild: ^0.17.5 fsevents: ~2.3.2 postcss: ^8.4.21 - resolve: ^1.22.1 - rollup: ^3.18.0 + rollup: ^3.20.2 peerDependencies: "@types/node": ">= 14" less: "*" @@ -6445,7 +6451,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 60e7298c817f0626bcbfdfc8877431421eabab85131c64d69e58f5ea20a66c14c4e0901ed63286b362627a667560d257dd135615b63f844da6052f6248dd7be6 + checksum: c9a9ccb0405b2d76c228b989d283a03962d00d2f48491b8b8492162fe7b1f130e77f842970badb691e669ea75e3a137e417ffeeeab0e6944179d71cbd74fa166 languageName: node linkType: hard From e12083e3ba03db2cb5dfbbedfa9da8b574ee51a9 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 09:45:54 +0200 Subject: [PATCH 08/89] added more eslint rules --- interface/.eslintrc.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/interface/.eslintrc.json b/interface/.eslintrc.json index c207db972..32758fa78 100644 --- a/interface/.eslintrc.json +++ b/interface/.eslintrc.json @@ -59,6 +59,20 @@ "prefer": "type-imports" } ], + "import/order": [ + "warn", + { + "groups": ["builtin", "external", "parent", "sibling", "index", "object", "type"], + "pathGroups": [ + { + "pattern": "@/**/**", + "group": "parent", + "position": "before" + } + ], + "alphabetize": { "order": "asc" } + } + ], // "autofix/no-unused-vars": [ // "error", // { From 56e95d1d853f17f6487fa48d96c1ca0fa6c4417f Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 09:46:11 +0200 Subject: [PATCH 09/89] updated for custom entities --- mock-api/server.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/mock-api/server.js b/mock-api/server.js index dea914377..81894fa47 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -320,6 +320,7 @@ const EMSESP_WRITE_ANALOG_ENDPOINT = REST_ENDPOINT_ROOT + 'writeAnalog'; const EMSESP_CUSTOM_ENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'customEntities'; const EMSESP_RESET_CUSTOMIZATIONS_ENDPOINT = REST_ENDPOINT_ROOT + 'resetCustomizations'; const EMSESP_WRITE_SCHEDULE_ENDPOINT = REST_ENDPOINT_ROOT + 'schedule'; +const EMSESP_WRITE_ENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'entities'; settings = { locale: 'en', @@ -386,7 +387,7 @@ const emsesp_coredata = { // devices: [], devices: [ { - id: '2', + id: 2, t: 4, tn: 'Boiler', b: 'Nefit', @@ -397,7 +398,7 @@ const emsesp_coredata = { e: 68 }, { - id: '1', + id: 1, t: 5, tn: 'Thermostat', b: '', @@ -408,7 +409,7 @@ const emsesp_coredata = { e: 5 }, { - id: '4', + id: 4, t: 5, tn: 'Thermostat', b: 'Buderus', @@ -610,7 +611,8 @@ let emsesp_entities = { factor: 0, uom: 2, value: 1, - value_type: 2 + value_type: 2, + writeable: false }, { id: 1, @@ -621,7 +623,8 @@ let emsesp_entities = { factor: 2, uom: 4, value: 2, - value_type: 5 + value_type: 5, + writeable: true } ] }; @@ -1097,6 +1100,13 @@ rest_server.post(EMSESP_WRITE_SCHEDULE_ENDPOINT, (req, res) => { res.sendStatus(200); }); +rest_server.post(EMSESP_WRITE_ENTITIES_ENDPOINT, (req, res) => { + console.log('write entities'); + console.log(req.body.entities); + emsesp_entities = req.body; + res.sendStatus(200); +}); + rest_server.post(EMSESP_WRITE_VALUE_ENDPOINT, (req, res) => { const devicevalue = req.body.devicevalue; const id = req.body.id; From 6f14fcb6e88b89d45f9ccf45a3d39642cc018c04 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 09:46:41 +0200 Subject: [PATCH 10/89] id no longer needed --- src/web/WebSchedulerService.cpp | 4 ++-- src/web/WebSchedulerService.h | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp index 62bf6c9f5..4d29f0834 100644 --- a/src/web/WebSchedulerService.cpp +++ b/src/web/WebSchedulerService.cpp @@ -1,7 +1,7 @@ /* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020-2023 Paul Derbyshire - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -56,7 +56,7 @@ StateUpdateResult WebScheduler::update(JsonObject & root, WebScheduler & webSche #ifdef EMSESP_STANDALONE // invoke some fake data for testing const char * json = - "{[{\"id\":\"01\",\"active\":true,\"flags\":31,\"time\": \"07:30\",\"cmd\": \"hc1/mode\",\"value\": \"day\",\"name\": \"turn on central heating\"}]}"; + "{[{\"id\":1,\"active\":true,\"flags\":31,\"time\": \"07:30\",\"cmd\": \"hc1/mode\",\"value\": \"day\",\"name\": \"turn on central heating\"}]}"; StaticJsonDocument<500> doc; deserializeJson(doc, json); root = doc.as(); diff --git a/src/web/WebSchedulerService.h b/src/web/WebSchedulerService.h index 5b919fe3b..43501d66b 100644 --- a/src/web/WebSchedulerService.h +++ b/src/web/WebSchedulerService.h @@ -29,7 +29,6 @@ namespace emsesp { class ScheduleItem { public: - std::string id; // unqiue id boolean active; uint8_t flags; uint16_t elapsed_min; // total mins from 00:00 From d8ff08823170ff7bc31c5efdf1be1f61fffbaaab Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 09:47:03 +0200 Subject: [PATCH 11/89] formatting --- src/web/WebEntityService.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/WebEntityService.cpp b/src/web/WebEntityService.cpp index 2572f7ce7..8687f20be 100644 --- a/src/web/WebEntityService.cpp +++ b/src/web/WebEntityService.cpp @@ -1,7 +1,7 @@ /* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020-2023 Paul Derbyshire - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or From 19e8e4a7a10dbf10c777cb5fefa53c5309cff4dc Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 09:47:29 +0200 Subject: [PATCH 12/89] initial lint cleanup --- interface/src/App.tsx | 7 +- interface/src/AppRouting.tsx | 12 +- interface/src/AuthenticatedRouting.tsx | 18 +-- interface/src/CustomTheme.tsx | 7 +- interface/src/SignIn.tsx | 33 ++-- interface/src/api/ap.ts | 6 +- interface/src/api/authentication.ts | 13 +- interface/src/api/endpoints.ts | 10 +- interface/src/api/features.ts | 7 +- interface/src/api/mqtt.ts | 5 +- interface/src/api/network.ts | 7 +- interface/src/api/ntp.ts | 5 +- interface/src/api/security.ts | 7 +- interface/src/api/system.ts | 8 +- interface/src/components/ButtonRow.tsx | 43 +++-- interface/src/components/MessageBox.tsx | 9 +- interface/src/components/SectionContent.tsx | 5 +- .../inputs/BlockFormControlLabel.tsx | 5 +- .../inputs/ValidatedPasswordField.tsx | 9 +- .../components/inputs/ValidatedTextField.tsx | 8 +- interface/src/components/layout/Layout.tsx | 15 +- .../src/components/layout/LayoutAppBar.tsx | 64 ++++---- .../src/components/layout/LayoutAuthMenu.tsx | 28 ++-- .../src/components/layout/LayoutDrawer.tsx | 8 +- .../src/components/layout/LayoutMenu.tsx | 19 ++- .../src/components/layout/LayoutMenuItem.tsx | 9 +- .../components/loading/ApplicationError.tsx | 5 +- .../src/components/loading/FormLoader.tsx | 5 +- .../src/components/loading/LoadingSpinner.tsx | 6 +- .../components/routing/BlockNavigation.tsx | 4 +- .../src/components/routing/RequireAdmin.tsx | 5 +- .../routing/RequireAuthenticated.tsx | 14 +- .../routing/RequireUnauthenticated.tsx | 5 +- .../src/components/routing/RouterTabs.tsx | 7 +- .../src/components/upload/SingleUpload.tsx | 16 +- .../src/components/upload/useFileUpload.ts | 16 +- .../authentication/Authentication.tsx | 14 +- .../src/contexts/authentication/context.ts | 2 +- .../src/contexts/features/FeaturesLoader.tsx | 15 +- interface/src/contexts/features/context.ts | 2 +- interface/src/framework/ap/APSettingsForm.tsx | 28 ++-- interface/src/framework/ap/APStatusForm.tsx | 15 +- interface/src/framework/ap/AccessPoint.tsx | 10 +- interface/src/framework/mqtt/Mqtt.tsx | 11 +- .../src/framework/mqtt/MqttSettingsForm.tsx | 21 ++- .../src/framework/mqtt/MqttStatusForm.tsx | 102 ++++++------ .../framework/network/NetworkConnection.tsx | 19 ++- .../framework/network/NetworkSettingsForm.tsx | 29 ++-- .../framework/network/NetworkStatusForm.tsx | 19 +-- .../network/WiFiConnectionContext.tsx | 2 +- .../framework/network/WiFiNetworkScanner.tsx | 15 +- .../framework/network/WiFiNetworkSelector.tsx | 50 +++--- .../src/framework/ntp/NTPSettingsForm.tsx | 23 ++- interface/src/framework/ntp/NTPStatusForm.tsx | 25 +-- interface/src/framework/ntp/NetworkTime.tsx | 11 +- .../src/framework/security/GenerateToken.tsx | 12 +- .../framework/security/ManageUsersForm.tsx | 32 ++-- interface/src/framework/security/Security.tsx | 10 +- .../security/SecuritySettingsForm.tsx | 16 +- interface/src/framework/security/UserForm.tsx | 12 +- .../src/framework/system/OTASettingsForm.tsx | 15 +- .../src/framework/system/RestartMonitor.tsx | 3 +- interface/src/framework/system/System.tsx | 15 +- interface/src/framework/system/SystemLog.tsx | 21 ++- .../src/framework/system/SystemStatusForm.tsx | 150 +++++++++--------- .../src/framework/system/UploadFileForm.tsx | 10 +- interface/src/project/Dashboard.tsx | 10 +- interface/src/project/DashboardStatus.tsx | 44 +++-- interface/src/project/DeviceIcon.tsx | 8 +- interface/src/project/Help.tsx | 8 +- interface/src/project/HelpInformation.tsx | 23 ++- interface/src/project/OptionIcon.tsx | 16 +- interface/src/project/SettingsApplication.tsx | 27 ++-- .../src/project/SettingsCustomization.tsx | 144 ++++++++--------- interface/src/project/api.ts | 7 +- interface/src/utils/time.ts | 9 +- interface/src/utils/useRest.ts | 6 +- interface/src/utils/useWs.ts | 2 +- interface/src/validators/ap.ts | 4 +- interface/src/validators/mqtt.ts | 2 +- interface/src/validators/network.ts | 2 +- interface/src/validators/security.ts | 5 +- 82 files changed, 701 insertions(+), 775 deletions(-) diff --git a/interface/src/App.tsx b/interface/src/App.tsx index 4fe3a46a5..5ed0e01dd 100644 --- a/interface/src/App.tsx +++ b/interface/src/App.tsx @@ -1,10 +1,11 @@ -import { FC, useEffect, useState } from 'react'; - +import { useEffect, useState } from 'react'; import { ToastContainer, Slide } from 'react-toastify'; +import type { FC } from 'react'; + import 'react-toastify/dist/ReactToastify.min.css'; -import CustomTheme from 'CustomTheme'; import AppRouting from 'AppRouting'; +import CustomTheme from 'CustomTheme'; import { localStorageDetector } from 'typesafe-i18n/detectors'; import TypesafeI18n from 'i18n/i18n-react'; diff --git a/interface/src/AppRouting.tsx b/interface/src/AppRouting.tsx index b71c9eba2..7e6507621 100644 --- a/interface/src/AppRouting.tsx +++ b/interface/src/AppRouting.tsx @@ -1,16 +1,16 @@ -import { FC, useContext, useEffect } from 'react'; +import { useContext, useEffect } from 'react'; import { Route, Routes, Navigate, useLocation } from 'react-router-dom'; import { toast } from 'react-toastify'; +import type { FC } from 'react'; -import { useI18nContext } from 'i18n/i18n-react'; - -import { Authentication, AuthenticationContext } from 'contexts/authentication'; +import AuthenticatedRouting from 'AuthenticatedRouting'; +import SignIn from 'SignIn'; import { RequireAuthenticated, RequireUnauthenticated } from 'components'; -import SignIn from 'SignIn'; -import AuthenticatedRouting from 'AuthenticatedRouting'; +import { Authentication, AuthenticationContext } from 'contexts/authentication'; +import { useI18nContext } from 'i18n/i18n-react'; interface SecurityRedirectProps { message: string; diff --git a/interface/src/AuthenticatedRouting.tsx b/interface/src/AuthenticatedRouting.tsx index cf4e7c2e0..eaf6041f2 100644 --- a/interface/src/AuthenticatedRouting.tsx +++ b/interface/src/AuthenticatedRouting.tsx @@ -1,21 +1,21 @@ -import { FC, useCallback, useEffect } from 'react'; +import { useCallback, useEffect } from 'react'; import { Navigate, Routes, Route, useNavigate, useLocation } from 'react-router-dom'; -import { AxiosError } from 'axios'; +import Dashboard from './project/Dashboard'; +import Help from './project/Help'; +import Settings from './project/Settings'; +import type { AxiosError } from 'axios'; +import type { FC } from 'react'; import * as AuthenticationApi from 'api/authentication'; import { AXIOS } from 'api/endpoints'; import { Layout, RequireAdmin } from 'components'; -import Dashboard from './project/Dashboard'; -import Settings from './project/Settings'; -import Help from './project/Help'; - -import NetworkConnection from 'framework/network/NetworkConnection'; import AccessPoint from 'framework/ap/AccessPoint'; -import NetworkTime from 'framework/ntp/NetworkTime'; import Mqtt from 'framework/mqtt/Mqtt'; -import System from 'framework/system/System'; +import NetworkConnection from 'framework/network/NetworkConnection'; +import NetworkTime from 'framework/ntp/NetworkTime'; import Security from 'framework/security/Security'; +import System from 'framework/system/System'; const AuthenticatedRouting: FC = () => { const location = useLocation(); diff --git a/interface/src/CustomTheme.tsx b/interface/src/CustomTheme.tsx index 91e1b5c5d..a482e0b28 100644 --- a/interface/src/CustomTheme.tsx +++ b/interface/src/CustomTheme.tsx @@ -1,10 +1,9 @@ -import { FC } from 'react'; - import { CssBaseline } from '@mui/material'; -import { createTheme, responsiveFontSizes, ThemeProvider } from '@mui/material/styles'; import { blueGrey, blue } from '@mui/material/colors'; +import { createTheme, responsiveFontSizes, ThemeProvider } from '@mui/material/styles'; +import type { FC } from 'react'; -import { RequiredChildrenProps } from 'utils'; +import type { RequiredChildrenProps } from 'utils'; const theme = responsiveFontSizes( createTheme({ diff --git a/interface/src/SignIn.tsx b/interface/src/SignIn.tsx index 85881c2f8..132f19b3b 100644 --- a/interface/src/SignIn.tsx +++ b/interface/src/SignIn.tsx @@ -1,31 +1,30 @@ -import { FC, useContext, useState } from 'react'; -import { ValidateFieldsError } from 'async-validator'; -import { toast } from 'react-toastify'; - -import { Box, Fab, Paper, Typography, Button } from '@mui/material'; import ForwardIcon from '@mui/icons-material/Forward'; +import { Box, Fab, Paper, Typography, Button } from '@mui/material'; +import { useContext, useState } from 'react'; +import { toast } from 'react-toastify'; +import type { ValidateFieldsError } from 'async-validator'; +import type { Locales } from 'i18n/i18n-types'; +import type { FC } from 'react'; +import type { SignInRequest } from 'types'; import * as AuthenticationApi from 'api/authentication'; import { PROJECT_NAME } from 'api/env'; + +import { ValidatedTextField } from 'components'; import { AuthenticationContext } from 'contexts/authentication'; -import { extractErrorMessage, onEnterCallback, updateValue } from 'utils'; -import { SignInRequest } from 'types'; -import { ValidatedTextField } from 'components'; -import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators'; - -import { I18nContext } from 'i18n/i18n-react'; -import type { Locales } from 'i18n/i18n-types'; -import { loadLocaleAsync } from 'i18n/i18n-util.async'; - -import { ReactComponent as NLflag } from 'i18n/NL.svg'; import { ReactComponent as DEflag } from 'i18n/DE.svg'; import { ReactComponent as GBflag } from 'i18n/GB.svg'; -import { ReactComponent as SVflag } from 'i18n/SV.svg'; -import { ReactComponent as PLflag } from 'i18n/PL.svg'; +import { ReactComponent as NLflag } from 'i18n/NL.svg'; import { ReactComponent as NOflag } from 'i18n/NO.svg'; +import { ReactComponent as PLflag } from 'i18n/PL.svg'; +import { ReactComponent as SVflag } from 'i18n/SV.svg'; import { ReactComponent as FRflag } from 'i18n/FR.svg'; import { ReactComponent as TRflag } from 'i18n/TR.svg'; +import { I18nContext } from 'i18n/i18n-react'; +import { loadLocaleAsync } from 'i18n/i18n-util.async'; +import { extractErrorMessage, onEnterCallback, updateValue } from 'utils'; +import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators'; const SignIn: FC = () => { const authenticationContext = useContext(AuthenticationContext); diff --git a/interface/src/api/ap.ts b/interface/src/api/ap.ts index 81d1f81f5..734a40f64 100644 --- a/interface/src/api/ap.ts +++ b/interface/src/api/ap.ts @@ -1,7 +1,7 @@ -import { AxiosPromise } from 'axios'; - -import { APSettings, APStatus } from 'types'; import { AXIOS } from './endpoints'; +import type { AxiosPromise } from 'axios'; + +import type { APSettings, APStatus } from 'types'; export function readAPStatus(): AxiosPromise { return AXIOS.get('/apStatus'); diff --git a/interface/src/api/authentication.ts b/interface/src/api/authentication.ts index 32d0c4f90..9b8b01030 100644 --- a/interface/src/api/authentication.ts +++ b/interface/src/api/authentication.ts @@ -1,11 +1,10 @@ -import { AxiosPromise } from 'axios'; -import * as H from 'history'; import jwtDecode from 'jwt-decode'; -import { Path } from 'react-router-dom'; - -import { Me, SignInRequest, SignInResponse } from 'types'; - import { ACCESS_TOKEN, AXIOS } from './endpoints'; +import type { AxiosPromise } from 'axios'; +import type * as H from 'history'; +import type { Path } from 'react-router-dom'; + +import type { Me, SignInRequest, SignInResponse } from 'types'; export const SIGN_IN_PATHNAME = 'loginPathname'; export const SIGN_IN_SEARCH = 'loginSearch'; @@ -45,7 +44,7 @@ export function fetchLoginRedirect(): Partial { } export const clearAccessToken = () => localStorage.removeItem(ACCESS_TOKEN); -export const decodeMeJWT = (accessToken: string): Me => jwtDecode(accessToken) as Me; +export const decodeMeJWT = (accessToken: string): Me => jwtDecode(accessToken); export function addAccessTokenParameter(url: string) { const accessToken = getStorage().getItem(ACCESS_TOKEN); diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index 18ad2a612..2e4f96f8a 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -1,6 +1,6 @@ -import axios, { AxiosPromise, CancelToken, AxiosProgressEvent } from 'axios'; - import { decode } from '@msgpack/msgpack'; +import axios from 'axios'; +import type { AxiosPromise, CancelToken, AxiosProgressEvent } from 'axios'; export const WS_BASE_URL = '/ws/'; export const API_BASE_URL = '/rest/'; @@ -72,11 +72,7 @@ export const AXIOS_BIN = axios.create({ return JSON.stringify(data); } ], - transformResponse: [ - (data) => { - return decode(data); - } - ] + transformResponse: [(data) => decode(data)] }); export interface FileUploadConfig { diff --git a/interface/src/api/features.ts b/interface/src/api/features.ts index c4e1605f9..6ada2405c 100644 --- a/interface/src/api/features.ts +++ b/interface/src/api/features.ts @@ -1,8 +1,7 @@ -import { AxiosPromise } from 'axios'; - -import { Features } from 'types'; - import { AXIOS } from './endpoints'; +import type { AxiosPromise } from 'axios'; + +import type { Features } from 'types'; export function readFeatures(): AxiosPromise { return AXIOS.get('/features'); diff --git a/interface/src/api/mqtt.ts b/interface/src/api/mqtt.ts index e3603050e..d599a3121 100644 --- a/interface/src/api/mqtt.ts +++ b/interface/src/api/mqtt.ts @@ -1,7 +1,6 @@ -import { AxiosPromise } from 'axios'; -import { MqttSettings, MqttStatus } from 'types'; - import { AXIOS } from './endpoints'; +import type { AxiosPromise } from 'axios'; +import type { MqttSettings, MqttStatus } from 'types'; export function readMqttStatus(): AxiosPromise { return AXIOS.get('/mqttStatus'); diff --git a/interface/src/api/network.ts b/interface/src/api/network.ts index 4993964ab..a9535336c 100644 --- a/interface/src/api/network.ts +++ b/interface/src/api/network.ts @@ -1,8 +1,7 @@ -import { AxiosPromise } from 'axios'; - -import { WiFiNetworkList, NetworkSettings, NetworkStatus } from 'types'; - import { AXIOS } from './endpoints'; +import type { AxiosPromise } from 'axios'; + +import type { WiFiNetworkList, NetworkSettings, NetworkStatus } from 'types'; export function readNetworkStatus(): AxiosPromise { return AXIOS.get('/networkStatus'); diff --git a/interface/src/api/ntp.ts b/interface/src/api/ntp.ts index cdd81862d..dcd3c7ca9 100644 --- a/interface/src/api/ntp.ts +++ b/interface/src/api/ntp.ts @@ -1,7 +1,6 @@ -import { AxiosPromise } from 'axios'; -import { NTPSettings, NTPStatus, Time } from 'types'; - import { AXIOS } from './endpoints'; +import type { AxiosPromise } from 'axios'; +import type { NTPSettings, NTPStatus, Time } from 'types'; export function readNTPStatus(): AxiosPromise { return AXIOS.get('/ntpStatus'); diff --git a/interface/src/api/security.ts b/interface/src/api/security.ts index dab188aaf..f46194432 100644 --- a/interface/src/api/security.ts +++ b/interface/src/api/security.ts @@ -1,8 +1,7 @@ -import { AxiosPromise } from 'axios'; - -import { SecuritySettings, Token } from 'types'; - import { AXIOS } from './endpoints'; +import type { AxiosPromise } from 'axios'; + +import type { SecuritySettings, Token } from 'types'; export function readSecuritySettings(): AxiosPromise { return AXIOS.get('/securitySettings'); diff --git a/interface/src/api/system.ts b/interface/src/api/system.ts index 5c5972c7b..dc9b34db6 100644 --- a/interface/src/api/system.ts +++ b/interface/src/api/system.ts @@ -1,8 +1,8 @@ -import { AxiosPromise } from 'axios'; +import { AXIOS, AXIOS_BIN, startUploadFile } from './endpoints'; +import type { FileUploadConfig } from './endpoints'; +import type { AxiosPromise } from 'axios'; -import { OTASettings, SystemStatus, LogSettings, LogEntries } from 'types'; - -import { AXIOS, AXIOS_BIN, FileUploadConfig, startUploadFile } from './endpoints'; +import type { OTASettings, SystemStatus, LogSettings, LogEntries } from 'types'; export function readSystemStatus(timeout?: number): AxiosPromise { return AXIOS.get('/systemStatus', { timeout }); diff --git a/interface/src/components/ButtonRow.tsx b/interface/src/components/ButtonRow.tsx index 40eb0182e..adf01472c 100644 --- a/interface/src/components/ButtonRow.tsx +++ b/interface/src/components/ButtonRow.tsx @@ -1,26 +1,25 @@ -import { FC } from 'react'; -import { Box, BoxProps } from '@mui/material'; +import { Box } from '@mui/material'; +import type { BoxProps } from '@mui/material'; +import type { FC } from 'react'; -const ButtonRow: FC = ({ children, ...rest }) => { - return ( - = ({ children, ...rest }) => ( + - {children} - - ); -}; + } + }} + {...rest} + > + {children} + +); export default ButtonRow; diff --git a/interface/src/components/MessageBox.tsx b/interface/src/components/MessageBox.tsx index a530d8f26..e7358e73c 100644 --- a/interface/src/components/MessageBox.tsx +++ b/interface/src/components/MessageBox.tsx @@ -1,11 +1,10 @@ -import { FC } from 'react'; - -import { Box, BoxProps, SvgIconProps, Theme, Typography, useTheme } from '@mui/material'; - import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined'; +import ErrorIcon from '@mui/icons-material/Error'; import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; import ReportProblemOutlinedIcon from '@mui/icons-material/ReportProblemOutlined'; -import ErrorIcon from '@mui/icons-material/Error'; +import { Box, Typography, useTheme } from '@mui/material'; +import type { BoxProps, SvgIconProps, Theme } from '@mui/material'; +import type { FC } from 'react'; type MessageBoxLevel = 'warning' | 'success' | 'info' | 'error'; diff --git a/interface/src/components/SectionContent.tsx b/interface/src/components/SectionContent.tsx index dfcb37e9b..95428d982 100644 --- a/interface/src/components/SectionContent.tsx +++ b/interface/src/components/SectionContent.tsx @@ -1,8 +1,7 @@ -import { FC } from 'react'; - import { Paper, Divider } from '@mui/material'; +import type { FC } from 'react'; -import { RequiredChildrenProps } from 'utils'; +import type { RequiredChildrenProps } from 'utils'; interface SectionContentProps extends RequiredChildrenProps { title: string; diff --git a/interface/src/components/inputs/BlockFormControlLabel.tsx b/interface/src/components/inputs/BlockFormControlLabel.tsx index e3e8b7cd3..8da7932a5 100644 --- a/interface/src/components/inputs/BlockFormControlLabel.tsx +++ b/interface/src/components/inputs/BlockFormControlLabel.tsx @@ -1,5 +1,6 @@ -import { FC } from 'react'; -import { FormControlLabel, FormControlLabelProps } from '@mui/material'; +import { FormControlLabel } from '@mui/material'; +import type { FormControlLabelProps } from '@mui/material'; +import type { FC } from 'react'; const BlockFormControlLabel: FC = (props) => (
diff --git a/interface/src/components/inputs/ValidatedPasswordField.tsx b/interface/src/components/inputs/ValidatedPasswordField.tsx index d4a215199..43cc0f426 100644 --- a/interface/src/components/inputs/ValidatedPasswordField.tsx +++ b/interface/src/components/inputs/ValidatedPasswordField.tsx @@ -1,10 +1,11 @@ -import { FC, useState } from 'react'; - -import { IconButton, InputAdornment } from '@mui/material'; import VisibilityIcon from '@mui/icons-material/Visibility'; import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; +import { IconButton, InputAdornment } from '@mui/material'; +import { useState } from 'react'; -import ValidatedTextField, { ValidatedTextFieldProps } from './ValidatedTextField'; +import ValidatedTextField from './ValidatedTextField'; +import type { ValidatedTextFieldProps } from './ValidatedTextField'; +import type { FC } from 'react'; type ValidatedPasswordFieldProps = Omit; diff --git a/interface/src/components/inputs/ValidatedTextField.tsx b/interface/src/components/inputs/ValidatedTextField.tsx index 8d07f8127..11f8d9268 100644 --- a/interface/src/components/inputs/ValidatedTextField.tsx +++ b/interface/src/components/inputs/ValidatedTextField.tsx @@ -1,7 +1,7 @@ -import { FC } from 'react'; -import { ValidateFieldsError } from 'async-validator'; - -import { FormHelperText, TextField, TextFieldProps } from '@mui/material'; +import { FormHelperText, TextField } from '@mui/material'; +import type { TextFieldProps } from '@mui/material'; +import type { ValidateFieldsError } from 'async-validator'; +import type { FC } from 'react'; interface ValidatedFieldProps { fieldErrors?: ValidateFieldsError; diff --git a/interface/src/components/layout/Layout.tsx b/interface/src/components/layout/Layout.tsx index e937b12d3..f998deb23 100644 --- a/interface/src/components/layout/Layout.tsx +++ b/interface/src/components/layout/Layout.tsx @@ -1,14 +1,13 @@ -import { FC, useState, useEffect } from 'react'; -import { useLocation } from 'react-router-dom'; - import { Box, Toolbar } from '@mui/material'; - -import { PROJECT_NAME } from 'api/env'; -import { RequiredChildrenProps } from 'utils'; - -import LayoutDrawer from './LayoutDrawer'; +import { useState, useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; import LayoutAppBar from './LayoutAppBar'; +import LayoutDrawer from './LayoutDrawer'; import { LayoutContext } from './context'; +import type { FC } from 'react'; + +import type { RequiredChildrenProps } from 'utils'; +import { PROJECT_NAME } from 'api/env'; export const DRAWER_WIDTH = 240; diff --git a/interface/src/components/layout/LayoutAppBar.tsx b/interface/src/components/layout/LayoutAppBar.tsx index 225283806..c7d9499f6 100644 --- a/interface/src/components/layout/LayoutAppBar.tsx +++ b/interface/src/components/layout/LayoutAppBar.tsx @@ -1,9 +1,7 @@ -import { FC } from 'react'; - -import { AppBar, Box, IconButton, Toolbar, Typography } from '@mui/material'; import MenuIcon from '@mui/icons-material/Menu'; - +import { AppBar, Box, IconButton, Toolbar, Typography } from '@mui/material'; import LayoutAuthMenu from './LayoutAuthMenu'; +import type { FC } from 'react'; export const DRAWER_WIDTH = 240; @@ -12,35 +10,33 @@ interface LayoutAppBarProps { onToggleDrawer: () => void; } -const LayoutAppBar: FC = ({ title, onToggleDrawer }) => { - return ( - - - - - - - {title} - - - - - - ); -}; +const LayoutAppBar: FC = ({ title, onToggleDrawer }) => ( + + + + + + + {title} + + + + + +); export default LayoutAppBar; diff --git a/interface/src/components/layout/LayoutAuthMenu.tsx b/interface/src/components/layout/LayoutAuthMenu.tsx index 65204307d..c8d96344d 100644 --- a/interface/src/components/layout/LayoutAuthMenu.tsx +++ b/interface/src/components/layout/LayoutAuthMenu.tsx @@ -1,5 +1,5 @@ -import { FC, useState, useContext, ChangeEventHandler } from 'react'; - +import AccountCircleIcon from '@mui/icons-material/AccountCircle'; +import PersonIcon from '@mui/icons-material/Person'; import { Box, Button, @@ -9,28 +9,26 @@ import { Typography, Avatar, styled, - TypographyProps, MenuItem, TextField } from '@mui/material'; +import { useState, useContext } from 'react'; +import type { TypographyProps } from '@mui/material'; -import PersonIcon from '@mui/icons-material/Person'; -import AccountCircleIcon from '@mui/icons-material/AccountCircle'; - +import type { Locales } from 'i18n/i18n-types'; +import type { FC, ChangeEventHandler } from 'react'; import { AuthenticatedContext } from 'contexts/authentication'; -import { I18nContext } from 'i18n/i18n-react'; -import type { Locales } from 'i18n/i18n-types'; -import { loadLocaleAsync } from 'i18n/i18n-util.async'; - -import { ReactComponent as NLflag } from 'i18n/NL.svg'; import { ReactComponent as DEflag } from 'i18n/DE.svg'; -import { ReactComponent as GBflag } from 'i18n/GB.svg'; -import { ReactComponent as SVflag } from 'i18n/SV.svg'; -import { ReactComponent as PLflag } from 'i18n/PL.svg'; -import { ReactComponent as NOflag } from 'i18n/NO.svg'; import { ReactComponent as FRflag } from 'i18n/FR.svg'; +import { ReactComponent as GBflag } from 'i18n/GB.svg'; +import { ReactComponent as NLflag } from 'i18n/NL.svg'; +import { ReactComponent as NOflag } from 'i18n/NO.svg'; +import { ReactComponent as PLflag } from 'i18n/PL.svg'; +import { ReactComponent as SVflag } from 'i18n/SV.svg'; import { ReactComponent as TRflag } from 'i18n/TR.svg'; +import { I18nContext } from 'i18n/i18n-react'; +import { loadLocaleAsync } from 'i18n/i18n-util.async'; const ItemTypography = styled(Typography)({ maxWidth: '250px', diff --git a/interface/src/components/layout/LayoutDrawer.tsx b/interface/src/components/layout/LayoutDrawer.tsx index c4d4868ee..1dbde9305 100644 --- a/interface/src/components/layout/LayoutDrawer.tsx +++ b/interface/src/components/layout/LayoutDrawer.tsx @@ -1,12 +1,10 @@ -import { FC } from 'react'; - import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material'; +import { DRAWER_WIDTH } from './Layout'; +import LayoutMenu from './LayoutMenu'; +import type { FC } from 'react'; import { PROJECT_NAME } from 'api/env'; -import LayoutMenu from './LayoutMenu'; -import { DRAWER_WIDTH } from './Layout'; - const LayoutDrawerLogo = styled('img')(({ theme }) => ({ [theme.breakpoints.down('sm')]: { height: 24, diff --git a/interface/src/components/layout/LayoutMenu.tsx b/interface/src/components/layout/LayoutMenu.tsx index 0f0004591..f7d32788f 100644 --- a/interface/src/components/layout/LayoutMenu.tsx +++ b/interface/src/components/layout/LayoutMenu.tsx @@ -1,17 +1,16 @@ -import { FC, useContext } from 'react'; - -import { Divider, List } from '@mui/material'; - -import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna'; import AccessTimeIcon from '@mui/icons-material/AccessTime'; -import DeviceHubIcon from '@mui/icons-material/DeviceHub'; -import SettingsIcon from '@mui/icons-material/Settings'; -import LockIcon from '@mui/icons-material/Lock'; -import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet'; -import TuneIcon from '@mui/icons-material/Tune'; import DashboardIcon from '@mui/icons-material/Dashboard'; +import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import InfoIcon from '@mui/icons-material/Info'; +import LockIcon from '@mui/icons-material/Lock'; +import SettingsIcon from '@mui/icons-material/Settings'; +import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet'; +import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna'; +import TuneIcon from '@mui/icons-material/Tune'; +import { Divider, List } from '@mui/material'; +import { useContext } from 'react'; +import type { FC } from 'react'; import LayoutMenuItem from 'components/layout/LayoutMenuItem'; diff --git a/interface/src/components/layout/LayoutMenuItem.tsx b/interface/src/components/layout/LayoutMenuItem.tsx index 04da15106..91914169d 100644 --- a/interface/src/components/layout/LayoutMenuItem.tsx +++ b/interface/src/components/layout/LayoutMenuItem.tsx @@ -1,12 +1,11 @@ -import { FC } from 'react'; +import { ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; +import { grey } from '@mui/material/colors'; import { Link, useLocation } from 'react-router-dom'; - -import { ListItem, ListItemButton, ListItemIcon, ListItemText, SvgIconProps } from '@mui/material'; +import type { SvgIconProps } from '@mui/material'; +import type { FC } from 'react'; import { routeMatches } from 'utils'; -import { grey } from '@mui/material/colors'; - interface LayoutMenuItemProps { icon: React.ComponentType; label: string; diff --git a/interface/src/components/loading/ApplicationError.tsx b/interface/src/components/loading/ApplicationError.tsx index 9ab428d58..4a1be5c31 100644 --- a/interface/src/components/loading/ApplicationError.tsx +++ b/interface/src/components/loading/ApplicationError.tsx @@ -1,7 +1,6 @@ -import { FC } from 'react'; - -import { Box, Paper, Typography } from '@mui/material'; import WarningIcon from '@mui/icons-material/Warning'; +import { Box, Paper, Typography } from '@mui/material'; +import type { FC } from 'react'; interface ApplicationErrorProps { message?: string; diff --git a/interface/src/components/loading/FormLoader.tsx b/interface/src/components/loading/FormLoader.tsx index b2d23a7a1..5a555f4d0 100644 --- a/interface/src/components/loading/FormLoader.tsx +++ b/interface/src/components/loading/FormLoader.tsx @@ -1,7 +1,6 @@ -import { FC } from 'react'; - -import { Box, Button, CircularProgress, Typography } from '@mui/material'; import RefreshIcon from '@mui/icons-material/Refresh'; +import { Box, Button, CircularProgress, Typography } from '@mui/material'; +import type { FC } from 'react'; import { MessageBox } from 'components'; diff --git a/interface/src/components/loading/LoadingSpinner.tsx b/interface/src/components/loading/LoadingSpinner.tsx index 243e6a8cb..06d6664ba 100644 --- a/interface/src/components/loading/LoadingSpinner.tsx +++ b/interface/src/components/loading/LoadingSpinner.tsx @@ -1,6 +1,6 @@ -import { FC } from 'react'; - -import { CircularProgress, Box, Typography, Theme } from '@mui/material'; +import { CircularProgress, Box, Typography } from '@mui/material'; +import type { Theme } from '@mui/material'; +import type { FC } from 'react'; import { useI18nContext } from 'i18n/i18n-react'; diff --git a/interface/src/components/routing/BlockNavigation.tsx b/interface/src/components/routing/BlockNavigation.tsx index 0f6bb4ef0..445cc7463 100644 --- a/interface/src/components/routing/BlockNavigation.tsx +++ b/interface/src/components/routing/BlockNavigation.tsx @@ -1,6 +1,6 @@ -import { FC } from 'react'; -import type { Blocker } from '@remix-run/router'; import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material'; +import type { Blocker } from '@remix-run/router'; +import type { FC } from 'react'; import { useI18nContext } from 'i18n/i18n-react'; diff --git a/interface/src/components/routing/RequireAdmin.tsx b/interface/src/components/routing/RequireAdmin.tsx index 1f6be2bf4..a78aeaf01 100644 --- a/interface/src/components/routing/RequireAdmin.tsx +++ b/interface/src/components/routing/RequireAdmin.tsx @@ -1,8 +1,9 @@ -import { FC, useContext } from 'react'; +import { useContext } from 'react'; import { Navigate } from 'react-router-dom'; +import type { FC } from 'react'; +import type { RequiredChildrenProps } from 'utils'; import { AuthenticatedContext } from 'contexts/authentication'; -import { RequiredChildrenProps } from 'utils'; const RequireAdmin: FC = ({ children }) => { const authenticatedContext = useContext(AuthenticatedContext); diff --git a/interface/src/components/routing/RequireAuthenticated.tsx b/interface/src/components/routing/RequireAuthenticated.tsx index 6eea9ed7c..d9f157f56 100644 --- a/interface/src/components/routing/RequireAuthenticated.tsx +++ b/interface/src/components/routing/RequireAuthenticated.tsx @@ -1,14 +1,12 @@ -import { FC, useContext, useEffect } from 'react'; +import { useContext, useEffect } from 'react'; import { Navigate, useLocation } from 'react-router-dom'; -import { - AuthenticatedContext, - AuthenticatedContextValue, - AuthenticationContext -} from 'contexts/authentication/context'; -import { storeLoginRedirect } from 'api/authentication'; +import type { AuthenticatedContextValue } from 'contexts/authentication/context'; +import type { FC } from 'react'; -import { RequiredChildrenProps } from 'utils'; +import type { RequiredChildrenProps } from 'utils'; +import { storeLoginRedirect } from 'api/authentication'; +import { AuthenticatedContext, AuthenticationContext } from 'contexts/authentication/context'; const RequireAuthenticated: FC = ({ children }) => { const authenticationContext = useContext(AuthenticationContext); diff --git a/interface/src/components/routing/RequireUnauthenticated.tsx b/interface/src/components/routing/RequireUnauthenticated.tsx index 33ba900c8..65a334926 100644 --- a/interface/src/components/routing/RequireUnauthenticated.tsx +++ b/interface/src/components/routing/RequireUnauthenticated.tsx @@ -1,9 +1,10 @@ -import { FC, useContext } from 'react'; +import { useContext } from 'react'; import { Navigate } from 'react-router-dom'; +import type { FC } from 'react'; +import type { RequiredChildrenProps } from 'utils'; import * as AuthenticationApi from 'api/authentication'; import { AuthenticationContext } from 'contexts/authentication'; -import { RequiredChildrenProps } from 'utils'; const RequireUnauthenticated: FC = ({ children }) => { const authenticationContext = useContext(AuthenticationContext); diff --git a/interface/src/components/routing/RouterTabs.tsx b/interface/src/components/routing/RouterTabs.tsx index c16b77ba6..485a0d995 100644 --- a/interface/src/components/routing/RouterTabs.tsx +++ b/interface/src/components/routing/RouterTabs.tsx @@ -1,9 +1,8 @@ -import { FC } from 'react'; -import { useNavigate } from 'react-router-dom'; - import { Tabs, useMediaQuery, useTheme } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; +import type { FC } from 'react'; -import { RequiredChildrenProps } from 'utils'; +import type { RequiredChildrenProps } from 'utils'; interface RouterTabsProps extends RequiredChildrenProps { value: string | false; diff --git a/interface/src/components/upload/SingleUpload.tsx b/interface/src/components/upload/SingleUpload.tsx index c7c4ce185..57c17bb8a 100644 --- a/interface/src/components/upload/SingleUpload.tsx +++ b/interface/src/components/upload/SingleUpload.tsx @@ -1,12 +1,12 @@ -import { FC, Fragment } from 'react'; -import { useDropzone, DropzoneState } from 'react-dropzone'; - -import { AxiosProgressEvent } from 'axios'; - -import { Box, Button, LinearProgress, Theme, Typography, useTheme } from '@mui/material'; - -import CloudUploadIcon from '@mui/icons-material/CloudUpload'; import CancelIcon from '@mui/icons-material/Cancel'; +import CloudUploadIcon from '@mui/icons-material/CloudUpload'; +import { Box, Button, LinearProgress, Typography, useTheme } from '@mui/material'; +import { Fragment } from 'react'; +import { useDropzone } from 'react-dropzone'; +import type { Theme } from '@mui/material'; +import type { AxiosProgressEvent } from 'axios'; +import type { FC } from 'react'; +import type { DropzoneState } from 'react-dropzone'; import { useI18nContext } from 'i18n/i18n-react'; diff --git a/interface/src/components/upload/useFileUpload.ts b/interface/src/components/upload/useFileUpload.ts index afa41aaa7..47d286a77 100644 --- a/interface/src/components/upload/useFileUpload.ts +++ b/interface/src/components/upload/useFileUpload.ts @@ -1,11 +1,12 @@ +import axios from 'axios'; import { useCallback, useEffect, useState } from 'react'; -import axios, { AxiosPromise, CancelTokenSource, AxiosProgressEvent } from 'axios'; import { toast } from 'react-toastify'; -import { extractErrorMessage } from 'utils'; -import { FileUploadConfig } from 'api/endpoints'; +import type { FileUploadConfig } from 'api/endpoints'; +import type { AxiosPromise, CancelTokenSource, AxiosProgressEvent } from 'axios'; import { useI18nContext } from 'i18n/i18n-react'; +import { extractErrorMessage } from 'utils'; interface MediaUploadOptions { upload: (file: File, config?: FileUploadConfig) => AxiosPromise; @@ -31,11 +32,12 @@ const useFileUpload = ({ upload }: MediaUploadOptions) => { resetUploadingStates(); }, [uploadCancelToken]); - useEffect(() => { - return () => { + useEffect( + () => () => { uploadCancelToken?.cancel(); - }; - }, [uploadCancelToken]); + }, + [uploadCancelToken] + ); const uploadFile = async (images: File[]) => { try { diff --git a/interface/src/contexts/authentication/Authentication.tsx b/interface/src/contexts/authentication/Authentication.tsx index d997356e4..a818f3458 100644 --- a/interface/src/contexts/authentication/Authentication.tsx +++ b/interface/src/contexts/authentication/Authentication.tsx @@ -1,15 +1,15 @@ -import { FC, useCallback, useEffect, useState } from 'react'; -import { toast } from 'react-toastify'; +import { useCallback, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import { toast } from 'react-toastify'; +import { AuthenticationContext } from './context'; +import type { FC } from 'react'; -import { useI18nContext } from 'i18n/i18n-react'; - +import type { Me } from 'types'; +import type { RequiredChildrenProps } from 'utils'; import * as AuthenticationApi from 'api/authentication'; import { ACCESS_TOKEN } from 'api/endpoints'; -import { RequiredChildrenProps } from 'utils'; import { LoadingSpinner } from 'components'; -import { Me } from 'types'; -import { AuthenticationContext } from './context'; +import { useI18nContext } from 'i18n/i18n-react'; const Authentication: FC = ({ children }) => { const { LL } = useI18nContext(); diff --git a/interface/src/contexts/authentication/context.ts b/interface/src/contexts/authentication/context.ts index 858fba24d..d81ef1a99 100644 --- a/interface/src/contexts/authentication/context.ts +++ b/interface/src/contexts/authentication/context.ts @@ -1,5 +1,5 @@ import { createContext } from 'react'; -import { Me } from 'types'; +import type { Me } from 'types'; export interface AuthenticationContextValue { refresh: () => Promise; diff --git a/interface/src/contexts/features/FeaturesLoader.tsx b/interface/src/contexts/features/FeaturesLoader.tsx index 0235de844..c56751237 100644 --- a/interface/src/contexts/features/FeaturesLoader.tsx +++ b/interface/src/contexts/features/FeaturesLoader.tsx @@ -1,12 +1,13 @@ -import { FC, useCallback, useEffect, useState } from 'react'; - -import * as FeaturesApi from 'api/features'; - -import { extractErrorMessage, RequiredChildrenProps } from 'utils'; -import { Features } from 'types'; -import { ApplicationError, LoadingSpinner } from 'components'; +import { useCallback, useEffect, useState } from 'react'; import { FeaturesContext } from '.'; +import type { FC } from 'react'; + +import type { Features } from 'types'; +import type { RequiredChildrenProps } from 'utils'; +import * as FeaturesApi from 'api/features'; +import { ApplicationError, LoadingSpinner } from 'components'; +import { extractErrorMessage } from 'utils'; const FeaturesLoader: FC = (props) => { const [errorMessage, setErrorMessage] = useState(); diff --git a/interface/src/contexts/features/context.ts b/interface/src/contexts/features/context.ts index 5c64ec113..78a8f1ec2 100644 --- a/interface/src/contexts/features/context.ts +++ b/interface/src/contexts/features/context.ts @@ -1,6 +1,6 @@ import { createContext } from 'react'; -import { Features } from 'types'; +import type { Features } from 'types'; export interface FeaturesContextValue { features: Features; diff --git a/interface/src/framework/ap/APSettingsForm.tsx b/interface/src/framework/ap/APSettingsForm.tsx index 349240d1d..b0408820b 100644 --- a/interface/src/framework/ap/APSettingsForm.tsx +++ b/interface/src/framework/ap/APSettingsForm.tsx @@ -1,12 +1,13 @@ -import { FC, useState } from 'react'; -import { ValidateFieldsError } from 'async-validator'; -import { range } from 'lodash-es'; - -import { Button, Checkbox, MenuItem } from '@mui/material'; -import WarningIcon from '@mui/icons-material/Warning'; import CancelIcon from '@mui/icons-material/Cancel'; +import WarningIcon from '@mui/icons-material/Warning'; +import { Button, Checkbox, MenuItem } from '@mui/material'; +import { range } from 'lodash-es'; +import { useState } from 'react'; +import type { ValidateFieldsError } from 'async-validator'; +import type { FC } from 'react'; -import { createAPSettingsValidator, validate } from 'validators'; +import type { APSettings } from 'types'; +import * as APApi from 'api/ap'; import { BlockFormControlLabel, ButtonRow, @@ -17,15 +18,14 @@ import { BlockNavigation } from 'components'; -import { APProvisionMode, APSettings } from 'types'; -import { numberValue, updateValueDirty, useRest } from 'utils'; -import * as APApi from 'api/ap'; - import { useI18nContext } from 'i18n/i18n-react'; +import { APProvisionMode } from 'types'; +import { numberValue, updateValueDirty, useRest } from 'utils'; -export const isAPEnabled = ({ provision_mode }: APSettings) => { - return provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED; -}; +import { createAPSettingsValidator, validate } from 'validators'; + +export const isAPEnabled = ({ provision_mode }: APSettings) => + provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED; const APSettingsForm: FC = () => { const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } = diff --git a/interface/src/framework/ap/APStatusForm.tsx b/interface/src/framework/ap/APStatusForm.tsx index d8a056ebb..a4cfb6172 100644 --- a/interface/src/framework/ap/APStatusForm.tsx +++ b/interface/src/framework/ap/APStatusForm.tsx @@ -1,17 +1,18 @@ -import { FC } from 'react'; - -import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, Theme, useTheme } from '@mui/material'; -import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna'; -import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import ComputerIcon from '@mui/icons-material/Computer'; +import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import RefreshIcon from '@mui/icons-material/Refresh'; +import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna'; +import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, useTheme } from '@mui/material'; +import type { Theme } from '@mui/material'; +import type { FC } from 'react'; +import type { APStatus } from 'types'; import * as APApi from 'api/ap'; -import { APNetworkStatus, APStatus } from 'types'; import { ButtonRow, FormLoader, SectionContent } from 'components'; -import { useRest } from 'utils'; import { useI18nContext } from 'i18n/i18n-react'; +import { APNetworkStatus } from 'types'; +import { useRest } from 'utils'; export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => { switch (status) { diff --git a/interface/src/framework/ap/AccessPoint.tsx b/interface/src/framework/ap/AccessPoint.tsx index 26dc3812d..749aafea7 100644 --- a/interface/src/framework/ap/AccessPoint.tsx +++ b/interface/src/framework/ap/AccessPoint.tsx @@ -1,12 +1,12 @@ -import { FC, useContext } from 'react'; +import { Tab } from '@mui/material'; +import { useContext } from 'react'; import { Navigate, Routes, Route } from 'react-router-dom'; -import { Tab } from '@mui/material'; - -import { AuthenticatedContext } from 'contexts/authentication'; -import APStatusForm from './APStatusForm'; import APSettingsForm from './APSettingsForm'; +import APStatusForm from './APStatusForm'; +import type { FC } from 'react'; import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from 'components'; +import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; diff --git a/interface/src/framework/mqtt/Mqtt.tsx b/interface/src/framework/mqtt/Mqtt.tsx index 9af749753..7520b0eec 100644 --- a/interface/src/framework/mqtt/Mqtt.tsx +++ b/interface/src/framework/mqtt/Mqtt.tsx @@ -1,14 +1,13 @@ -import { FC, useContext } from 'react'; -import { Navigate, Route, Routes } from 'react-router-dom'; - import { Tab } from '@mui/material'; +import { useContext } from 'react'; +import { Navigate, Route, Routes } from 'react-router-dom'; +import MqttSettingsForm from './MqttSettingsForm'; +import MqttStatusForm from './MqttStatusForm'; +import type { FC } from 'react'; import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; -import MqttStatusForm from './MqttStatusForm'; -import MqttSettingsForm from './MqttSettingsForm'; - import { useI18nContext } from 'i18n/i18n-react'; const Mqtt: FC = () => { diff --git a/interface/src/framework/mqtt/MqttSettingsForm.tsx b/interface/src/framework/mqtt/MqttSettingsForm.tsx index ad559c8a1..e531ffee5 100644 --- a/interface/src/framework/mqtt/MqttSettingsForm.tsx +++ b/interface/src/framework/mqtt/MqttSettingsForm.tsx @@ -1,12 +1,12 @@ -import { FC, useState } from 'react'; -import { ValidateFieldsError } from 'async-validator'; - -import { Button, Checkbox, MenuItem, Grid, Typography, InputAdornment } from '@mui/material'; - -import WarningIcon from '@mui/icons-material/Warning'; import CancelIcon from '@mui/icons-material/Cancel'; +import WarningIcon from '@mui/icons-material/Warning'; +import { Button, Checkbox, MenuItem, Grid, Typography, InputAdornment } from '@mui/material'; +import { useState } from 'react'; +import type { ValidateFieldsError } from 'async-validator'; +import type { FC } from 'react'; -import { createMqttSettingsValidator, validate } from 'validators'; +import type { MqttSettings } from 'types'; +import * as MqttApi from 'api/mqtt'; import { BlockFormControlLabel, ButtonRow, @@ -16,11 +16,10 @@ import { ValidatedTextField, BlockNavigation } from 'components'; -import { MqttSettings } from 'types'; -import { numberValue, updateValueDirty, useRest } from 'utils'; -import * as MqttApi from 'api/mqtt'; - import { useI18nContext } from 'i18n/i18n-react'; +import { numberValue, updateValueDirty, useRest } from 'utils'; + +import { createMqttSettingsValidator, validate } from 'validators'; const MqttSettingsForm: FC = () => { const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } = diff --git a/interface/src/framework/mqtt/MqttStatusForm.tsx b/interface/src/framework/mqtt/MqttStatusForm.tsx index e258ae538..b8a20f915 100644 --- a/interface/src/framework/mqtt/MqttStatusForm.tsx +++ b/interface/src/framework/mqtt/MqttStatusForm.tsx @@ -1,18 +1,18 @@ -import { FC } from 'react'; -import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, Theme, useTheme } from '@mui/material'; - +import AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion'; import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import RefreshIcon from '@mui/icons-material/Refresh'; import ReportIcon from '@mui/icons-material/Report'; import SpeakerNotesOffIcon from '@mui/icons-material/SpeakerNotesOff'; -import AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion'; +import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, useTheme } from '@mui/material'; +import type { Theme } from '@mui/material'; +import type { FC } from 'react'; -import { ButtonRow, FormLoader, SectionContent } from 'components'; -import { MqttStatus, MqttDisconnectReason } from 'types'; +import type { MqttStatus } from 'types'; import * as MqttApi from 'api/mqtt'; -import { useRest } from 'utils'; - +import { ButtonRow, FormLoader, SectionContent } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; +import { MqttDisconnectReason } from 'types'; +import { useRest } from 'utils'; export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: Theme) => { if (!enabled) { @@ -83,50 +83,48 @@ const MqttStatusForm: FC = () => { return ; } - const renderConnectionStatus = () => { - return ( - <> - {!data.connected && ( - <> - - - - - - - - - - - )} - - - # - - - - - - - - - - - - - - - - - - - - - - - - ); - }; + const renderConnectionStatus = () => ( + <> + {!data.connected && ( + <> + + + + + + + + + + + )} + + + # + + + + + + + + + + + + + + + + + + + + + + + + ); return ( <> diff --git a/interface/src/framework/network/NetworkConnection.tsx b/interface/src/framework/network/NetworkConnection.tsx index a7930ef03..18d1842ef 100644 --- a/interface/src/framework/network/NetworkConnection.tsx +++ b/interface/src/framework/network/NetworkConnection.tsx @@ -1,16 +1,15 @@ -import { FC, useCallback, useContext, useState } from 'react'; -import { Navigate, Routes, Route, useNavigate } from 'react-router-dom'; - import { Tab } from '@mui/material'; - -import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from 'components'; -import { WiFiNetwork } from 'types'; -import { AuthenticatedContext } from 'contexts/authentication'; -import { WiFiConnectionContext } from './WiFiConnectionContext'; -import NetworkStatusForm from './NetworkStatusForm'; -import WiFiNetworkScanner from './WiFiNetworkScanner'; +import { useCallback, useContext, useState } from 'react'; +import { Navigate, Routes, Route, useNavigate } from 'react-router-dom'; import NetworkSettingsForm from './NetworkSettingsForm'; +import NetworkStatusForm from './NetworkStatusForm'; +import { WiFiConnectionContext } from './WiFiConnectionContext'; +import WiFiNetworkScanner from './WiFiNetworkScanner'; +import type { FC } from 'react'; +import type { WiFiNetwork } from 'types'; +import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from 'components'; +import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; const NetworkConnection: FC = () => { diff --git a/interface/src/framework/network/NetworkSettingsForm.tsx b/interface/src/framework/network/NetworkSettingsForm.tsx index ad980f34a..5bb8ddd9a 100644 --- a/interface/src/framework/network/NetworkSettingsForm.tsx +++ b/interface/src/framework/network/NetworkSettingsForm.tsx @@ -1,6 +1,9 @@ -import { FC, useContext, useEffect, useState } from 'react'; -import { toast } from 'react-toastify'; - +import CancelIcon from '@mui/icons-material/Cancel'; +import DeleteIcon from '@mui/icons-material/Delete'; +import LockIcon from '@mui/icons-material/Lock'; +import LockOpenIcon from '@mui/icons-material/LockOpen'; +import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; +import WarningIcon from '@mui/icons-material/Warning'; import { Avatar, Button, @@ -14,14 +17,14 @@ import { Typography, InputAdornment } from '@mui/material'; +import { useContext, useEffect, useState } from 'react'; +import { toast } from 'react-toastify'; +import type { FC} from 'react'; -import LockOpenIcon from '@mui/icons-material/LockOpen'; -import DeleteIcon from '@mui/icons-material/Delete'; -import WarningIcon from '@mui/icons-material/Warning'; -import LockIcon from '@mui/icons-material/Lock'; -import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; -import CancelIcon from '@mui/icons-material/Cancel'; + +import type { NetworkSettings } from 'types'; +import * as NetworkApi from 'api/network'; import { BlockFormControlLabel, ButtonRow, @@ -32,18 +35,16 @@ import { MessageBox, BlockNavigation } from 'components'; -import { NetworkSettings } from 'types'; -import * as NetworkApi from 'api/network'; -import { numberValue, updateValueDirty, useRest } from 'utils'; +import { useI18nContext } from 'i18n/i18n-react'; import * as EMSESP from 'project/api'; +import { numberValue, updateValueDirty, useRest } from 'utils'; import { WiFiConnectionContext } from './WiFiConnectionContext'; import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector'; -import { ValidateFieldsError } from 'async-validator'; +import type { ValidateFieldsError } from 'async-validator'; import { validate } from 'validators'; import { createNetworkSettingsValidator } from 'validators/network'; -import { useI18nContext } from 'i18n/i18n-react'; import RestartMonitor from '../system/RestartMonitor'; const WiFiSettingsForm: FC = () => { diff --git a/interface/src/framework/network/NetworkStatusForm.tsx b/interface/src/framework/network/NetworkStatusForm.tsx index 8bc70ed72..325a0c5b0 100644 --- a/interface/src/framework/network/NetworkStatusForm.tsx +++ b/interface/src/framework/network/NetworkStatusForm.tsx @@ -1,20 +1,21 @@ -import { FC } from 'react'; -import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, Theme, useTheme } from '@mui/material'; - -import SettingsInputComponentIcon from '@mui/icons-material/SettingsInputComponent'; -import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna'; import DeviceHubIcon from '@mui/icons-material/DeviceHub'; -import WifiIcon from '@mui/icons-material/Wifi'; import DnsIcon from '@mui/icons-material/Dns'; import RefreshIcon from '@mui/icons-material/Refresh'; import RouterIcon from '@mui/icons-material/Router'; +import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna'; +import SettingsInputComponentIcon from '@mui/icons-material/SettingsInputComponent'; +import WifiIcon from '@mui/icons-material/Wifi'; +import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, useTheme } from '@mui/material'; +import type { Theme } from '@mui/material'; +import type { FC } from 'react'; -import { ButtonRow, FormLoader, SectionContent } from 'components'; -import { NetworkConnectionStatus, NetworkStatus } from 'types'; +import type { NetworkStatus } from 'types'; import * as NetworkApi from 'api/network'; -import { useRest } from 'utils'; +import { ButtonRow, FormLoader, SectionContent } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; +import { NetworkConnectionStatus } from 'types'; +import { useRest } from 'utils'; const isConnected = ({ status }: NetworkStatus) => status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED || diff --git a/interface/src/framework/network/WiFiConnectionContext.tsx b/interface/src/framework/network/WiFiConnectionContext.tsx index f22002455..4965416c8 100644 --- a/interface/src/framework/network/WiFiConnectionContext.tsx +++ b/interface/src/framework/network/WiFiConnectionContext.tsx @@ -1,5 +1,5 @@ import { createContext } from 'react'; -import { WiFiNetwork } from 'types'; +import type { WiFiNetwork } from 'types'; export interface WiFiConnectionContextValue { selectedNetwork?: WiFiNetwork; diff --git a/interface/src/framework/network/WiFiNetworkScanner.tsx b/interface/src/framework/network/WiFiNetworkScanner.tsx index 61bb39aed..2f302c0fa 100644 --- a/interface/src/framework/network/WiFiNetworkScanner.tsx +++ b/interface/src/framework/network/WiFiNetworkScanner.tsx @@ -1,14 +1,13 @@ -import { useEffect, FC, useState, useCallback, useRef } from 'react'; +import PermScanWifiIcon from '@mui/icons-material/PermScanWifi'; +import { Button } from '@mui/material'; +import { useEffect, useState, useCallback, useRef } from 'react'; import { toast } from 'react-toastify'; -import { Button } from '@mui/material'; -import PermScanWifiIcon from '@mui/icons-material/PermScanWifi'; - -import * as NetworkApi from 'api/network'; -import { WiFiNetwork, WiFiNetworkList } from 'types'; -import { ButtonRow, FormLoader, SectionContent } from 'components'; - import WiFiNetworkSelector from './WiFiNetworkSelector'; +import type { FC } from 'react'; +import type { WiFiNetwork, WiFiNetworkList } from 'types'; +import * as NetworkApi from 'api/network'; +import { ButtonRow, FormLoader, SectionContent } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; diff --git a/interface/src/framework/network/WiFiNetworkSelector.tsx b/interface/src/framework/network/WiFiNetworkSelector.tsx index 9be0d46e1..b344ca8e1 100644 --- a/interface/src/framework/network/WiFiNetworkSelector.tsx +++ b/interface/src/framework/network/WiFiNetworkSelector.tsx @@ -1,18 +1,16 @@ -import { FC, useContext } from 'react'; - -import { Avatar, Badge, List, ListItem, ListItemAvatar, ListItemIcon, ListItemText } from '@mui/material'; - -import LockOpenIcon from '@mui/icons-material/LockOpen'; import LockIcon from '@mui/icons-material/Lock'; +import LockOpenIcon from '@mui/icons-material/LockOpen'; import WifiIcon from '@mui/icons-material/Wifi'; - -import { MessageBox } from 'components'; - -import { WiFiEncryptionType, WiFiNetwork, WiFiNetworkList } from 'types'; +import { Avatar, Badge, List, ListItem, ListItemAvatar, ListItemIcon, ListItemText } from '@mui/material'; +import { useContext } from 'react'; import { WiFiConnectionContext } from './WiFiConnectionContext'; +import type { FC } from 'react'; +import type { WiFiNetwork, WiFiNetworkList } from 'types'; +import { MessageBox } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; +import { WiFiEncryptionType } from 'types'; interface WiFiNetworkSelectorProps { networkList: WiFiNetworkList; @@ -45,24 +43,22 @@ const WiFiNetworkSelector: FC = ({ networkList }) => { const wifiConnectionContext = useContext(WiFiConnectionContext); - const renderNetwork = (network: WiFiNetwork) => { - return ( - wifiConnectionContext.selectNetwork(network)}> - - {isNetworkOpen(network) ? : } - - - - - - - - - ); - }; + const renderNetwork = (network: WiFiNetwork) => ( + wifiConnectionContext.selectNetwork(network)}> + + {isNetworkOpen(network) ? : } + + + + + + + + + ); if (networkList.networks.length === 0) { return ; diff --git a/interface/src/framework/ntp/NTPSettingsForm.tsx b/interface/src/framework/ntp/NTPSettingsForm.tsx index 12df18103..b12ce75a9 100644 --- a/interface/src/framework/ntp/NTPSettingsForm.tsx +++ b/interface/src/framework/ntp/NTPSettingsForm.tsx @@ -1,11 +1,13 @@ -import { FC, useState } from 'react'; -import { ValidateFieldsError } from 'async-validator'; - -import { Button, Checkbox, MenuItem } from '@mui/material'; -import WarningIcon from '@mui/icons-material/Warning'; import CancelIcon from '@mui/icons-material/Cancel'; +import WarningIcon from '@mui/icons-material/Warning'; +import { Button, Checkbox, MenuItem } from '@mui/material'; +import { useState } from 'react'; +import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ'; +import type { ValidateFieldsError } from 'async-validator'; +import type { FC } from 'react'; -import { validate } from 'validators'; +import type { NTPSettings } from 'types'; +import * as NTPApi from 'api/ntp'; import { BlockFormControlLabel, ButtonRow, @@ -14,13 +16,10 @@ import { ValidatedTextField, BlockNavigation } from 'components'; -import { NTPSettings } from 'types'; -import { updateValueDirty, useRest } from 'utils'; -import * as NTPApi from 'api/ntp'; -import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ'; -import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp'; - import { useI18nContext } from 'i18n/i18n-react'; +import { updateValueDirty, useRest } from 'utils'; +import { validate } from 'validators'; +import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp'; const NTPSettingsForm: FC = () => { const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } = diff --git a/interface/src/framework/ntp/NTPStatusForm.tsx b/interface/src/framework/ntp/NTPStatusForm.tsx index 9f2c42b0a..ade31d3cf 100644 --- a/interface/src/framework/ntp/NTPStatusForm.tsx +++ b/interface/src/framework/ntp/NTPStatusForm.tsx @@ -1,6 +1,9 @@ -import { FC, useContext, useState } from 'react'; -import { toast } from 'react-toastify'; - +import AccessTimeIcon from '@mui/icons-material/AccessTime'; +import CancelIcon from '@mui/icons-material/Cancel'; +import DnsIcon from '@mui/icons-material/Dns'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import SwapVerticalCircleIcon from '@mui/icons-material/SwapVerticalCircle'; +import UpdateIcon from '@mui/icons-material/Update'; import { Avatar, Box, @@ -15,24 +18,22 @@ import { ListItemAvatar, ListItemText, TextField, - Theme, useTheme, Typography } from '@mui/material'; -import RefreshIcon from '@mui/icons-material/Refresh'; -import AccessTimeIcon from '@mui/icons-material/AccessTime'; -import SwapVerticalCircleIcon from '@mui/icons-material/SwapVerticalCircle'; -import UpdateIcon from '@mui/icons-material/Update'; -import DnsIcon from '@mui/icons-material/Dns'; -import CancelIcon from '@mui/icons-material/Cancel'; +import { useContext, useState } from 'react'; +import { toast } from 'react-toastify'; +import type { Theme } from '@mui/material'; +import type { FC } from 'react'; +import type { NTPStatus } from 'types'; import * as NTPApi from 'api/ntp'; -import { NTPStatus, NTPSyncStatus } from 'types'; import { ButtonRow, FormLoader, SectionContent } from 'components'; -import { extractErrorMessage, formatDateTime, formatLocalDateTime, useRest } from 'utils'; import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; +import { NTPSyncStatus } from 'types'; +import { extractErrorMessage, formatDateTime, formatLocalDateTime, useRest } from 'utils'; export const isNtpActive = ({ status }: NTPStatus) => status === NTPSyncStatus.NTP_ACTIVE; export const isNtpEnabled = ({ status }: NTPStatus) => status !== NTPSyncStatus.NTP_DISABLED; diff --git a/interface/src/framework/ntp/NetworkTime.tsx b/interface/src/framework/ntp/NetworkTime.tsx index 22248a1d8..d41d1fb8c 100644 --- a/interface/src/framework/ntp/NetworkTime.tsx +++ b/interface/src/framework/ntp/NetworkTime.tsx @@ -1,14 +1,13 @@ -import { FC, useContext } from 'react'; -import { Navigate, Route, Routes } from 'react-router-dom'; - import { Tab } from '@mui/material'; +import { useContext } from 'react'; +import { Navigate, Route, Routes } from 'react-router-dom'; +import NTPSettingsForm from './NTPSettingsForm'; +import NTPStatusForm from './NTPStatusForm'; +import type { FC } from 'react'; import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; -import NTPStatusForm from './NTPStatusForm'; -import NTPSettingsForm from './NTPSettingsForm'; - import { useI18nContext } from 'i18n/i18n-react'; const NetworkTime: FC = () => { diff --git a/interface/src/framework/security/GenerateToken.tsx b/interface/src/framework/security/GenerateToken.tsx index 2b0c0e9fd..52da8139c 100644 --- a/interface/src/framework/security/GenerateToken.tsx +++ b/interface/src/framework/security/GenerateToken.tsx @@ -1,4 +1,4 @@ -import { FC, useCallback, useState, useEffect } from 'react'; +import CloseIcon from '@mui/icons-material/Close'; import { Dialog, DialogTitle, @@ -10,16 +10,16 @@ import { TextField, Button } from '@mui/material'; +import { useCallback, useState, useEffect } from 'react'; -import CloseIcon from '@mui/icons-material/Close'; - -import { extractErrorMessage } from 'utils'; import { toast } from 'react-toastify'; -import { MessageBox } from 'components'; +import type { FC } from 'react'; +import type { Token } from 'types'; import * as SecurityApi from 'api/security'; -import { Token } from 'types'; +import { MessageBox } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; +import { extractErrorMessage } from 'utils'; interface GenerateTokenProps { username?: string; diff --git a/interface/src/framework/security/ManageUsersForm.tsx b/interface/src/framework/security/ManageUsersForm.tsx index 57b6ea895..022df9ef2 100644 --- a/interface/src/framework/security/ManageUsersForm.tsx +++ b/interface/src/framework/security/ManageUsersForm.tsx @@ -1,28 +1,26 @@ -import { FC, useContext, useState } from 'react'; - -import { Button, IconButton, Box } from '@mui/material'; -import SaveIcon from '@mui/icons-material/Save'; -import DeleteIcon from '@mui/icons-material/Delete'; -import PersonAddIcon from '@mui/icons-material/PersonAdd'; -import EditIcon from '@mui/icons-material/Edit'; import CheckIcon from '@mui/icons-material/Check'; import CloseIcon from '@mui/icons-material/Close'; +import DeleteIcon from '@mui/icons-material/Delete'; +import EditIcon from '@mui/icons-material/Edit'; +import PersonAddIcon from '@mui/icons-material/PersonAdd'; +import SaveIcon from '@mui/icons-material/Save'; import VpnKeyIcon from '@mui/icons-material/VpnKey'; +import { Button, IconButton, Box } from '@mui/material'; -import { useTheme } from '@table-library/react-table-library/theme'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; - -import * as SecurityApi from 'api/security'; -import { SecuritySettings, User } from 'types'; -import { ButtonRow, FormLoader, MessageBox, SectionContent } from 'components'; -import { createUserValidator } from 'validators'; -import { useRest } from 'utils'; -import { AuthenticatedContext } from 'contexts/authentication'; - -import { useI18nContext } from 'i18n/i18n-react'; +import { useTheme } from '@table-library/react-table-library/theme'; +import { useContext, useState } from 'react'; import GenerateToken from './GenerateToken'; import UserForm from './UserForm'; +import type { FC } from 'react'; +import type { SecuritySettings, User } from 'types'; +import * as SecurityApi from 'api/security'; +import { ButtonRow, FormLoader, MessageBox, SectionContent } from 'components'; +import { AuthenticatedContext } from 'contexts/authentication'; +import { useI18nContext } from 'i18n/i18n-react'; +import { useRest } from 'utils'; +import { createUserValidator } from 'validators'; const ManageUsersForm: FC = () => { const { loadData, saving, data, setData, saveData, errorMessage } = useRest({ diff --git a/interface/src/framework/security/Security.tsx b/interface/src/framework/security/Security.tsx index 4bb68580f..d55a171e1 100644 --- a/interface/src/framework/security/Security.tsx +++ b/interface/src/framework/security/Security.tsx @@ -1,13 +1,11 @@ -import { FC } from 'react'; -import { Navigate, Routes, Route } from 'react-router-dom'; - import { Tab } from '@mui/material'; +import { Navigate, Routes, Route } from 'react-router-dom'; +import ManageUsersForm from './ManageUsersForm'; +import SecuritySettingsForm from './SecuritySettingsForm'; +import type { FC } from 'react'; import { RouterTabs, useRouterTab, useLayoutTitle } from 'components'; -import SecuritySettingsForm from './SecuritySettingsForm'; -import ManageUsersForm from './ManageUsersForm'; - import { useI18nContext } from 'i18n/i18n-react'; const Security: FC = () => { diff --git a/interface/src/framework/security/SecuritySettingsForm.tsx b/interface/src/framework/security/SecuritySettingsForm.tsx index f02a5b6f4..3a8cab96b 100644 --- a/interface/src/framework/security/SecuritySettingsForm.tsx +++ b/interface/src/framework/security/SecuritySettingsForm.tsx @@ -1,18 +1,18 @@ -import { FC, useContext, useState } from 'react'; -import { ValidateFieldsError } from 'async-validator'; - -import { Button } from '@mui/material'; -import WarningIcon from '@mui/icons-material/Warning'; import CancelIcon from '@mui/icons-material/Cancel'; +import WarningIcon from '@mui/icons-material/Warning'; +import { Button } from '@mui/material'; +import { useContext, useState } from 'react'; +import type { ValidateFieldsError } from 'async-validator'; +import type { FC } from 'react'; +import type { SecuritySettings } from 'types'; import * as SecurityApi from 'api/security'; -import { SecuritySettings } from 'types'; import { ButtonRow, FormLoader, MessageBox, SectionContent, ValidatedPasswordField, BlockNavigation } from 'components'; -import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators'; -import { updateValueDirty, useRest } from 'utils'; import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; +import { updateValueDirty, useRest } from 'utils'; +import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators'; const SecuritySettingsForm: FC = () => { const { LL } = useI18nContext(); diff --git a/interface/src/framework/security/UserForm.tsx b/interface/src/framework/security/UserForm.tsx index f17c9dfbd..f11343096 100644 --- a/interface/src/framework/security/UserForm.tsx +++ b/interface/src/framework/security/UserForm.tsx @@ -1,19 +1,19 @@ -import { FC, useState, useEffect } from 'react'; -import Schema, { ValidateFieldsError } from 'async-validator'; - import CancelIcon from '@mui/icons-material/Cancel'; import PersonAddIcon from '@mui/icons-material/PersonAdd'; import SaveIcon from '@mui/icons-material/Save'; import { Button, Checkbox, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material'; +import { useState, useEffect } from 'react'; +import type Schema from 'async-validator'; +import type { ValidateFieldsError } from 'async-validator'; +import type { FC } from 'react'; +import type { User } from 'types'; import { BlockFormControlLabel, ValidatedPasswordField, ValidatedTextField } from 'components'; -import { User } from 'types'; +import { useI18nContext } from 'i18n/i18n-react'; import { updateValue } from 'utils'; import { validate } from 'validators'; -import { useI18nContext } from 'i18n/i18n-react'; - interface UserFormProps { creating: boolean; validator: Schema; diff --git a/interface/src/framework/system/OTASettingsForm.tsx b/interface/src/framework/system/OTASettingsForm.tsx index 3bf5c6817..9db23ee98 100644 --- a/interface/src/framework/system/OTASettingsForm.tsx +++ b/interface/src/framework/system/OTASettingsForm.tsx @@ -1,9 +1,11 @@ -import { FC, useState } from 'react'; - -import { Button, Checkbox } from '@mui/material'; -import WarningIcon from '@mui/icons-material/Warning'; import CancelIcon from '@mui/icons-material/Cancel'; +import WarningIcon from '@mui/icons-material/Warning'; +import { Button, Checkbox } from '@mui/material'; +import { useState } from 'react'; +import type { ValidateFieldsError } from 'async-validator'; +import type { FC } from 'react'; +import type { OTASettings } from 'types'; import * as SystemApi from 'api/system'; import { BlockFormControlLabel, @@ -15,15 +17,12 @@ import { BlockNavigation } from 'components'; -import { OTASettings } from 'types'; +import { useI18nContext } from 'i18n/i18n-react'; import { numberValue, updateValueDirty, useRest } from 'utils'; -import { ValidateFieldsError } from 'async-validator'; import { validate } from 'validators'; import { OTA_SETTINGS_VALIDATOR } from 'validators/system'; -import { useI18nContext } from 'i18n/i18n-react'; - const OTASettingsForm: FC = () => { const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } = useRest({ diff --git a/interface/src/framework/system/RestartMonitor.tsx b/interface/src/framework/system/RestartMonitor.tsx index 028028174..1b7e91026 100644 --- a/interface/src/framework/system/RestartMonitor.tsx +++ b/interface/src/framework/system/RestartMonitor.tsx @@ -1,4 +1,5 @@ -import { FC, useRef, useState, useEffect } from 'react'; +import { useRef, useState, useEffect } from 'react'; +import type { FC } from 'react'; import * as SystemApi from 'api/system'; import { FormLoader } from 'components'; diff --git a/interface/src/framework/system/System.tsx b/interface/src/framework/system/System.tsx index 8cc5cc80d..2caf4b7c6 100644 --- a/interface/src/framework/system/System.tsx +++ b/interface/src/framework/system/System.tsx @@ -1,15 +1,14 @@ -import { FC, useContext } from 'react'; -import { Navigate, Routes, Route } from 'react-router-dom'; - import { Tab } from '@mui/material'; +import { useContext } from 'react'; +import { Navigate, Routes, Route } from 'react-router-dom'; +import OTASettingsForm from './OTASettingsForm'; +import SystemLog from './SystemLog'; +import SystemStatusForm from './SystemStatusForm'; +import UploadFileForm from './UploadFileForm'; +import type { FC } from 'react'; import { useRouterTab, RouterTabs, useLayoutTitle, RequireAdmin } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; -import UploadFileForm from './UploadFileForm'; -import SystemStatusForm from './SystemStatusForm'; -import OTASettingsForm from './OTASettingsForm'; - -import SystemLog from './SystemLog'; import { useI18nContext } from 'i18n/i18n-react'; diff --git a/interface/src/framework/system/SystemLog.tsx b/interface/src/framework/system/SystemLog.tsx index b79b02248..393212cbb 100644 --- a/interface/src/framework/system/SystemLog.tsx +++ b/interface/src/framework/system/SystemLog.tsx @@ -1,22 +1,19 @@ -import { FC, useState, useEffect, useCallback, useLayoutEffect } from 'react'; - +import DownloadIcon from '@mui/icons-material/GetApp'; import { Box, styled, Button, Checkbox, MenuItem, Grid, Slider, FormLabel } from '@mui/material'; +import { useState, useEffect, useCallback, useLayoutEffect } from 'react'; +import { toast } from 'react-toastify'; +import type { FC } from 'react'; -import * as SystemApi from 'api/system'; +import type { LogSettings, LogEntry, LogEntries } from 'types'; import { addAccessTokenParameter } from 'api/authentication'; +import { EVENT_SOURCE_ROOT } from 'api/endpoints'; +import * as SystemApi from 'api/system'; import { SectionContent, FormLoader, BlockFormControlLabel, ValidatedTextField } from 'components'; -import { LogSettings, LogEntry, LogEntries, LogLevel } from 'types'; -import { updateValue, useRest, extractErrorMessage } from 'utils'; - -import DownloadIcon from '@mui/icons-material/GetApp'; - -import { toast } from 'react-toastify'; - -import { EVENT_SOURCE_ROOT } from 'api/endpoints'; - import { useI18nContext } from 'i18n/i18n-react'; +import { LogLevel } from 'types'; +import { updateValue, useRest, extractErrorMessage } from 'utils'; export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log'; diff --git a/interface/src/framework/system/SystemStatusForm.tsx b/interface/src/framework/system/SystemStatusForm.tsx index f0d078da9..b7ef1db17 100644 --- a/interface/src/framework/system/SystemStatusForm.tsx +++ b/interface/src/framework/system/SystemStatusForm.tsx @@ -1,5 +1,16 @@ -import { FC, useContext, useState, useEffect } from 'react'; -import { toast } from 'react-toastify'; +import AppsIcon from '@mui/icons-material/Apps'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; +import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore'; +import BuildIcon from '@mui/icons-material/Build'; +import TimerIcon from '@mui/icons-material/Timer'; +import CancelIcon from '@mui/icons-material/Cancel'; +import DevicesIcon from '@mui/icons-material/Devices'; +import FolderIcon from '@mui/icons-material/Folder'; +import MemoryIcon from '@mui/icons-material/Memory'; +import SdCardAlertIcon from '@mui/icons-material/SdCardAlert'; +import SdStorageIcon from '@mui/icons-material/SdStorage'; +import ShowChartIcon from '@mui/icons-material/ShowChart'; import { Avatar, Box, @@ -17,31 +28,18 @@ import { Typography } from '@mui/material'; -import DevicesIcon from '@mui/icons-material/Devices'; -import ShowChartIcon from '@mui/icons-material/ShowChart'; -import MemoryIcon from '@mui/icons-material/Memory'; -import AppsIcon from '@mui/icons-material/Apps'; -import SdStorageIcon from '@mui/icons-material/SdStorage'; -import SdCardAlertIcon from '@mui/icons-material/SdCardAlert'; -import FolderIcon from '@mui/icons-material/Folder'; -import RefreshIcon from '@mui/icons-material/Refresh'; -import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; -import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore'; -import BuildIcon from '@mui/icons-material/Build'; -import TimerIcon from '@mui/icons-material/Timer'; -import CancelIcon from '@mui/icons-material/Cancel'; - -import { ButtonRow, FormLoader, SectionContent, MessageBox } from 'components'; -import { SystemStatus, Version } from 'types'; -import * as SystemApi from 'api/system'; -import { extractErrorMessage, useRest } from 'utils'; - -import { AuthenticatedContext } from 'contexts/authentication'; - import axios from 'axios'; +import { useContext, useState, useEffect } from 'react'; +import { toast } from 'react-toastify'; import RestartMonitor from './RestartMonitor'; +import type { FC } from 'react'; import { useI18nContext } from 'i18n/i18n-react'; +import type { SystemStatus, Version } from 'types'; +import * as SystemApi from 'api/system'; +import { ButtonRow, FormLoader, SectionContent, MessageBox } from 'components'; +import { AuthenticatedContext } from 'contexts/authentication'; +import { extractErrorMessage, useRest } from 'utils'; export const VERSIONCHECK_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/latest'; export const VERSIONCHECK_DEV_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/tags/latest'; @@ -148,61 +146,59 @@ const SystemStatusForm: FC = () => { ); - const renderVersionDialog = () => { - return ( - setShowingVersion(false)}> - {LL.VERSION_CHECK(1)} - - - {latestVersion && ( - - {LL.THE_LATEST()} {LL.OFFICIAL()} {LL.VERSION_IS()} {latestVersion.version} -  ( - - {LL.RELEASE_NOTES()} - - ) ( - - {LL.DOWNLOAD(1)} - - ) - - )} - - {latestDevVersion && ( - - {LL.THE_LATEST()} {LL.DEVELOPMENT()} {LL.VERSION_IS()}  - {latestDevVersion.version} -  ( - - {LL.RELEASE_NOTES()} - - ) ( - - {LL.DOWNLOAD(1)} - - ) - - )} - - - - {LL.USE()}  - - {LL.UPLOAD()} - -  {LL.SYSTEM_APPLY_FIRMWARE()} - + const renderVersionDialog = () => ( + setShowingVersion(false)}> + {LL.VERSION_CHECK(1)} + + + {latestVersion && ( + + {LL.THE_LATEST()} {LL.OFFICIAL()} {LL.VERSION_IS()} {latestVersion.version} +  ( + + {LL.RELEASE_NOTES()} + + ) ( + + {LL.DOWNLOAD(1)} + + ) - - - - - - ); - }; + )} + + {latestDevVersion && ( + + {LL.THE_LATEST()} {LL.DEVELOPMENT()} {LL.VERSION_IS()}  + {latestDevVersion.version} +  ( + + {LL.RELEASE_NOTES()} + + ) ( + + {LL.DOWNLOAD(1)} + + ) + + )} + + + + {LL.USE()}  + + {LL.UPLOAD()} + +  {LL.SYSTEM_APPLY_FIRMWARE()} + + + + + + + + ); const factoryReset = async () => { setProcessing(true); diff --git a/interface/src/framework/system/UploadFileForm.tsx b/interface/src/framework/system/UploadFileForm.tsx index 5a71f6121..568738124 100644 --- a/interface/src/framework/system/UploadFileForm.tsx +++ b/interface/src/framework/system/UploadFileForm.tsx @@ -1,11 +1,11 @@ -import { FC, useRef, useState } from 'react'; +import { useRef, useState } from 'react'; +import GeneralFileUpload from './GeneralFileUpload'; +import RestartMonitor from './RestartMonitor'; +import type { FileUploadConfig } from 'api/endpoints'; +import type { FC } from 'react'; import * as SystemApi from 'api/system'; import { SectionContent } from 'components'; -import { FileUploadConfig } from 'api/endpoints'; - -import GeneralFileUpload from './GeneralFileUpload'; -import RestartMonitor from './RestartMonitor'; import { useI18nContext } from 'i18n/i18n-react'; diff --git a/interface/src/project/Dashboard.tsx b/interface/src/project/Dashboard.tsx index c2c1abaa5..5f9de1ca1 100644 --- a/interface/src/project/Dashboard.tsx +++ b/interface/src/project/Dashboard.tsx @@ -1,15 +1,13 @@ -import { FC } from 'react'; -import { Navigate, Route, Routes } from 'react-router-dom'; - import { Tab } from '@mui/material'; +import { Navigate, Route, Routes } from 'react-router-dom'; +import DashboardData from './DashboardData'; +import DashboardStatus from './DashboardStatus'; +import type { FC } from 'react'; import { RouterTabs, useRouterTab, useLayoutTitle } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import DashboardStatus from './DashboardStatus'; -import DashboardData from './DashboardData'; - const Dashboard: FC = () => { const { routerTab } = useRouterTab(); diff --git a/interface/src/project/DashboardStatus.tsx b/interface/src/project/DashboardStatus.tsx index 842bd0371..13055245a 100644 --- a/interface/src/project/DashboardStatus.tsx +++ b/interface/src/project/DashboardStatus.tsx @@ -1,42 +1,38 @@ -import { FC, useState, useContext, useEffect } from 'react'; -import { toast } from 'react-toastify'; +import CancelIcon from '@mui/icons-material/Cancel'; +import DeviceHubIcon from '@mui/icons-material/DeviceHub'; +import DirectionsBusIcon from '@mui/icons-material/DirectionsBus'; +import PermScanWifiIcon from '@mui/icons-material/PermScanWifi'; +import RefreshIcon from '@mui/icons-material/Refresh'; import { Avatar, - Button, - List, - ListItem, - ListItemAvatar, - ListItemText, Box, + Button, Dialog, DialogActions, DialogContent, DialogTitle, - Theme, + List, + ListItem, + ListItemAvatar, + ListItemText, useTheme } from '@mui/material'; - +import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table'; import { useTheme as tableTheme } from '@table-library/react-table-library/theme'; -import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; - -import DeviceHubIcon from '@mui/icons-material/DeviceHub'; -import RefreshIcon from '@mui/icons-material/Refresh'; -import PermScanWifiIcon from '@mui/icons-material/PermScanWifi'; -import CancelIcon from '@mui/icons-material/Cancel'; -import DirectionsBusIcon from '@mui/icons-material/DirectionsBus'; - -import { AuthenticatedContext } from 'contexts/authentication'; - -import { ButtonRow, FormLoader, SectionContent } from 'components'; - -import { Status, busConnectionStatus, Stat } from './types'; - -import { extractErrorMessage, useRest } from 'utils'; +import { useContext, useEffect, useState } from 'react'; +import { toast } from 'react-toastify'; import * as EMSESP from './api'; +import { busConnectionStatus } from './types'; +import type { Stat, Status } from './types'; +import type { Theme } from '@mui/material'; import type { Translation } from 'i18n/i18n-types'; +import type { FC } from 'react'; +import { ButtonRow, FormLoader, SectionContent } from 'components'; +import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; +import { extractErrorMessage, useRest } from 'utils'; export const isConnected = ({ status }: Status) => status !== busConnectionStatus.BUS_STATUS_OFFLINE; diff --git a/interface/src/project/DeviceIcon.tsx b/interface/src/project/DeviceIcon.tsx index d199db075..190a659a8 100644 --- a/interface/src/project/DeviceIcon.tsx +++ b/interface/src/project/DeviceIcon.tsx @@ -1,12 +1,12 @@ -import { FC } from 'react'; - +import { AiOutlineControl, AiOutlineGateway, AiOutlineAlert, AiOutlineChrome } from 'react-icons/ai'; import { CgSmartHomeBoiler } from 'react-icons/cg'; + import { FaSolarPanel } from 'react-icons/fa'; -import { MdThermostatAuto, MdOutlineSensors, MdOutlineExtension } from 'react-icons/md'; import { GiHeatHaze } from 'react-icons/gi'; +import { MdThermostatAuto, MdOutlineSensors, MdOutlineExtension } from 'react-icons/md'; import { TiFlowSwitch } from 'react-icons/ti'; import { VscVmConnect } from 'react-icons/vsc'; -import { AiOutlineControl, AiOutlineGateway, AiOutlineAlert, AiOutlineChrome } from 'react-icons/ai'; +import type { FC } from 'react'; interface DeviceIconProps { type_id: number; diff --git a/interface/src/project/Help.tsx b/interface/src/project/Help.tsx index 3d7238239..d5b355823 100644 --- a/interface/src/project/Help.tsx +++ b/interface/src/project/Help.tsx @@ -1,14 +1,12 @@ -import { FC } from 'react'; -import { Navigate, Route, Routes } from 'react-router-dom'; - import { Tab } from '@mui/material'; +import { Navigate, Route, Routes } from 'react-router-dom'; +import HelpInformation from './HelpInformation'; +import type { FC } from 'react'; import { RouterTabs, useRouterTab, useLayoutTitle } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import HelpInformation from './HelpInformation'; - const Help: FC = () => { const { LL } = useI18nContext(); const { routerTab } = useRouterTab(); diff --git a/interface/src/project/HelpInformation.tsx b/interface/src/project/HelpInformation.tsx index 4b0c6288b..e849ac19e 100644 --- a/interface/src/project/HelpInformation.tsx +++ b/interface/src/project/HelpInformation.tsx @@ -1,22 +1,17 @@ -import { FC } from 'react'; - +import CommentIcon from '@mui/icons-material/CommentTwoTone'; +import EastIcon from '@mui/icons-material/East'; +import DownloadIcon from '@mui/icons-material/GetApp'; +import GitHubIcon from '@mui/icons-material/GitHub'; +import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone'; import { Typography, Button, Box, List, ListItem, ListItemText, Link, ListItemAvatar } from '@mui/material'; +import { toast } from 'react-toastify'; +import * as EMSESP from './api'; +import type { FC } from 'react'; import { SectionContent } from 'components'; -import { toast } from 'react-toastify'; - -import CommentIcon from '@mui/icons-material/CommentTwoTone'; -import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone'; -import GitHubIcon from '@mui/icons-material/GitHub'; -import DownloadIcon from '@mui/icons-material/GetApp'; -import EastIcon from '@mui/icons-material/East'; - -import { extractErrorMessage } from 'utils'; - import { useI18nContext } from 'i18n/i18n-react'; - -import * as EMSESP from './api'; +import { extractErrorMessage } from 'utils'; const HelpInformation: FC = () => { const { LL } = useI18nContext(); diff --git a/interface/src/project/OptionIcon.tsx b/interface/src/project/OptionIcon.tsx index 220f331c4..d1cbdd2b7 100644 --- a/interface/src/project/OptionIcon.tsx +++ b/interface/src/project/OptionIcon.tsx @@ -1,20 +1,18 @@ -import { FC } from 'react'; -import { SvgIconProps } from '@mui/material'; - -import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; +import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined'; +import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; +import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined'; +import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; +import InsertCommentOutlinedIcon from '@mui/icons-material/InsertCommentOutlined'; import StarIcon from '@mui/icons-material/Star'; import StarOutlineIcon from '@mui/icons-material/StarOutline'; import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined'; import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined'; -import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined'; -import InsertCommentOutlinedIcon from '@mui/icons-material/InsertCommentOutlined'; - -import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; -import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; +import type { SvgIconProps } from '@mui/material'; +import type { FC } from 'react'; type OptionType = 'deleted' | 'readonly' | 'web_exclude' | 'api_mqtt_exclude' | 'favorite'; diff --git a/interface/src/project/SettingsApplication.tsx b/interface/src/project/SettingsApplication.tsx index 78481bebd..41ec58dc0 100644 --- a/interface/src/project/SettingsApplication.tsx +++ b/interface/src/project/SettingsApplication.tsx @@ -1,17 +1,16 @@ -import { FC, useState } from 'react'; -import { ValidateFieldsError } from 'async-validator'; - -import { toast } from 'react-toastify'; - -import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider, InputAdornment } from '@mui/material'; - -import WarningIcon from '@mui/icons-material/Warning'; import CancelIcon from '@mui/icons-material/Cancel'; import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; +import WarningIcon from '@mui/icons-material/Warning'; +import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider, InputAdornment } from '@mui/material'; +import { useState } from 'react'; +import { toast } from 'react-toastify'; -import { validate } from 'validators'; +import * as EMSESP from './api'; +import { BOARD_PROFILES } from './types'; import { createSettingsValidator } from './validators'; - +import type { Settings } from './types'; +import type { ValidateFieldsError } from 'async-validator'; +import type { FC } from 'react'; import { SectionContent, FormLoader, @@ -21,13 +20,11 @@ import { MessageBox, BlockNavigation } from 'components'; -import { numberValue, extractErrorMessage, updateValueDirty, useRest } from 'utils'; -import * as EMSESP from './api'; -import { Settings, BOARD_PROFILES } from './types'; - -import { useI18nContext } from 'i18n/i18n-react'; import RestartMonitor from 'framework/system/RestartMonitor'; +import { useI18nContext } from 'i18n/i18n-react'; +import { numberValue, extractErrorMessage, updateValueDirty, useRest } from 'utils'; +import { validate } from 'validators'; export function boardProfileSelectItems() { return Object.keys(BOARD_PROFILES).map((code) => ( diff --git a/interface/src/project/SettingsCustomization.tsx b/interface/src/project/SettingsCustomization.tsx index 18e9f1cd0..89211a2c8 100644 --- a/interface/src/project/SettingsCustomization.tsx +++ b/interface/src/project/SettingsCustomization.tsx @@ -1,7 +1,11 @@ -import { FC, useState, useEffect, useCallback } from 'react'; - -import { unstable_useBlocker as useBlocker } from 'react-router-dom'; +import CancelIcon from '@mui/icons-material/Cancel'; +import DoneIcon from '@mui/icons-material/Done'; +import FilterListIcon from '@mui/icons-material/FilterList'; +import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; +import SearchIcon from '@mui/icons-material/Search'; +import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore'; +import WarningIcon from '@mui/icons-material/Warning'; import { Button, Typography, @@ -18,34 +22,24 @@ import { TextField, Link } from '@mui/material'; - -import { useTheme } from '@table-library/react-table-library/theme'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; - +import { useTheme } from '@table-library/react-table-library/theme'; +import { useState, useEffect, useCallback } from 'react'; +import { unstable_useBlocker as useBlocker } from 'react-router-dom'; import { toast } from 'react-toastify'; -import WarningIcon from '@mui/icons-material/Warning'; -import CancelIcon from '@mui/icons-material/Cancel'; -import DoneIcon from '@mui/icons-material/Done'; - -import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore'; -import SearchIcon from '@mui/icons-material/Search'; -import FilterListIcon from '@mui/icons-material/FilterList'; -import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; - import OptionIcon from './OptionIcon'; -import { ButtonRow, FormLoader, ValidatedTextField, SectionContent, MessageBox, BlockNavigation } from 'components'; - import * as EMSESP from './api'; -import { extractErrorMessage, updateValue } from 'utils'; - -import { DeviceShort, Devices, DeviceEntity, DeviceEntityMask } from './types'; - -import { useI18nContext } from 'i18n/i18n-react'; +import { DeviceEntityMask } from './types'; +import type { DeviceShort, Devices, DeviceEntity } from './types'; +import type { FC } from 'react'; +import { ButtonRow, FormLoader, ValidatedTextField, SectionContent, MessageBox, BlockNavigation } from 'components'; import RestartMonitor from 'framework/system/RestartMonitor'; +import { useI18nContext } from 'i18n/i18n-react'; +import { extractErrorMessage, updateValue } from 'utils'; export const APIURL = window.location.origin + '/api/'; @@ -584,61 +578,59 @@ const SettingsCustomization: FC = () => { ); - const renderContent = () => { - return ( - <> - - {LL.DEVICE_ENTITIES()} - - {renderDeviceList()} - {renderDeviceData()} - {restartNeeded && ( - - - - )} - {!restartNeeded && ( - - - {numChanges !== 0 && ( - - - - - )} - - - - + const renderContent = () => ( + <> + + {LL.DEVICE_ENTITIES()} + + {renderDeviceList()} + {renderDeviceData()} + {restartNeeded && ( + + + + )} + {!restartNeeded && ( + + + {numChanges !== 0 && ( + + + + + )} - )} - {renderResetDialog()} - - ); - }; + + + + + )} + {renderResetDialog()} + + ); const renderEditDialog = () => { if (deviceEntity) { diff --git a/interface/src/project/api.ts b/interface/src/project/api.ts index 155908975..3b99724ea 100644 --- a/interface/src/project/api.ts +++ b/interface/src/project/api.ts @@ -1,7 +1,4 @@ -import { AxiosPromise } from 'axios'; -import { AXIOS, AXIOS_API, AXIOS_BIN } from 'api/endpoints'; - -import { +import type { BoardProfile, BoardProfileName, APIcall, @@ -20,6 +17,8 @@ import { Schedule, Entities } from './types'; +import type { AxiosPromise } from 'axios'; +import { AXIOS, AXIOS_API, AXIOS_BIN } from 'api/endpoints'; export function restart(): AxiosPromise { return AXIOS.post('/restart'); diff --git a/interface/src/utils/time.ts b/interface/src/utils/time.ts index 5efc58719..3fefb063c 100644 --- a/interface/src/utils/time.ts +++ b/interface/src/utils/time.ts @@ -8,10 +8,7 @@ const LOCALE_FORMAT = new Intl.DateTimeFormat([...window.navigator.languages], { hour12: false }); -export const formatDateTime = (dateTime: string) => { - return LOCALE_FORMAT.format(new Date(dateTime.substring(0, 19))); -}; +export const formatDateTime = (dateTime: string) => LOCALE_FORMAT.format(new Date(dateTime.substring(0, 19))); -export const formatLocalDateTime = (date: Date) => { - return new Date(date.getTime() - date.getTimezoneOffset() * 60000).toISOString().slice(0, -1).substring(0, 19); -}; +export const formatLocalDateTime = (date: Date) => + new Date(date.getTime() - date.getTimezoneOffset() * 60000).toISOString().slice(0, -1).substring(0, 19); diff --git a/interface/src/utils/useRest.ts b/interface/src/utils/useRest.ts index f57d20ec0..0dadda46b 100644 --- a/interface/src/utils/useRest.ts +++ b/interface/src/utils/useRest.ts @@ -1,14 +1,12 @@ import { useCallback, useEffect, useState } from 'react'; +import { unstable_useBlocker as useBlocker } from 'react-router-dom'; import { toast } from 'react-toastify'; -import { AxiosPromise } from 'axios'; - import { extractErrorMessage } from '.'; +import type { AxiosPromise } from 'axios'; import { useI18nContext } from 'i18n/i18n-react'; -import { unstable_useBlocker as useBlocker } from 'react-router-dom'; - export interface RestRequestOptions { read: () => AxiosPromise; update?: (value: D) => AxiosPromise; diff --git a/interface/src/utils/useWs.ts b/interface/src/utils/useWs.ts index 7839b53de..8e30da18b 100644 --- a/interface/src/utils/useWs.ts +++ b/interface/src/utils/useWs.ts @@ -1,6 +1,6 @@ +import { debounce } from 'lodash-es'; import { useCallback, useEffect, useRef, useState } from 'react'; import Sockette from 'sockette'; -import { debounce } from 'lodash-es'; import { addAccessTokenParameter } from 'api/authentication'; diff --git a/interface/src/validators/ap.ts b/interface/src/validators/ap.ts index e9e72ecd0..cd5812e8b 100644 --- a/interface/src/validators/ap.ts +++ b/interface/src/validators/ap.ts @@ -1,7 +1,7 @@ import Schema from 'async-validator'; -import { isAPEnabled } from 'framework/ap/APSettingsForm'; -import { APSettings } from 'types'; import { IP_ADDRESS_VALIDATOR } from './shared'; +import type { APSettings } from 'types'; +import { isAPEnabled } from 'framework/ap/APSettingsForm'; export const createAPSettingsValidator = (apSettings: APSettings) => new Schema({ diff --git a/interface/src/validators/mqtt.ts b/interface/src/validators/mqtt.ts index ec95a1b65..d8f841baa 100644 --- a/interface/src/validators/mqtt.ts +++ b/interface/src/validators/mqtt.ts @@ -1,6 +1,6 @@ import Schema from 'async-validator'; -import { MqttSettings } from 'types'; import { IP_OR_HOSTNAME_VALIDATOR } from './shared'; +import type { MqttSettings } from 'types'; export const createMqttSettingsValidator = (mqttSettings: MqttSettings) => new Schema({ diff --git a/interface/src/validators/network.ts b/interface/src/validators/network.ts index ba40ac051..761ce0de7 100644 --- a/interface/src/validators/network.ts +++ b/interface/src/validators/network.ts @@ -1,6 +1,6 @@ import Schema from 'async-validator'; -import { NetworkSettings } from 'types'; import { HOSTNAME_VALIDATOR, IP_ADDRESS_VALIDATOR } from './shared'; +import type { NetworkSettings } from 'types'; export const createNetworkSettingsValidator = (networkSettings: NetworkSettings) => new Schema({ diff --git a/interface/src/validators/security.ts b/interface/src/validators/security.ts index feb9498e2..d1843ac90 100644 --- a/interface/src/validators/security.ts +++ b/interface/src/validators/security.ts @@ -1,5 +1,6 @@ -import Schema, { InternalRuleItem } from 'async-validator'; -import { User } from 'types'; +import Schema from 'async-validator'; +import type { InternalRuleItem } from 'async-validator'; +import type { User } from 'types'; export const SECURITY_SETTINGS_VALIDATOR = new Schema({ jwt_secret: [ From 209afc87bbe5b17f0f00af6a53627320aa94609a Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 10:05:13 +0200 Subject: [PATCH 13/89] fix lint warnings --- interface/src/App.tsx | 6 +++--- interface/src/SignIn.tsx | 4 ++-- .../contexts/authentication/Authentication.tsx | 2 +- .../src/contexts/features/FeaturesLoader.tsx | 2 +- interface/src/framework/ap/APSettingsForm.tsx | 2 +- interface/src/framework/mqtt/MqttSettingsForm.tsx | 2 +- .../src/framework/network/NetworkSettingsForm.tsx | 15 ++++++--------- .../src/framework/network/WiFiNetworkScanner.tsx | 2 +- interface/src/framework/ntp/NTPSettingsForm.tsx | 2 +- interface/src/framework/ntp/NTPStatusForm.tsx | 2 +- .../src/framework/security/GenerateToken.tsx | 2 +- .../src/framework/security/ManageUsersForm.tsx | 2 +- .../src/framework/system/OTASettingsForm.tsx | 2 +- interface/src/framework/system/RestartMonitor.tsx | 2 +- interface/src/framework/system/SystemLog.tsx | 4 ++-- .../src/framework/system/SystemStatusForm.tsx | 14 +++++++------- interface/src/project/SettingsApplication.tsx | 6 +++--- interface/src/project/SettingsCustomization.tsx | 4 ++-- interface/src/utils/useRest.ts | 2 +- 19 files changed, 37 insertions(+), 40 deletions(-) diff --git a/interface/src/App.tsx b/interface/src/App.tsx index 5ed0e01dd..77f79e41d 100644 --- a/interface/src/App.tsx +++ b/interface/src/App.tsx @@ -1,13 +1,13 @@ import { useEffect, useState } from 'react'; import { ToastContainer, Slide } from 'react-toastify'; -import type { FC } from 'react'; import 'react-toastify/dist/ReactToastify.min.css'; +import { localStorageDetector } from 'typesafe-i18n/detectors'; +import type { FC } from 'react'; import AppRouting from 'AppRouting'; import CustomTheme from 'CustomTheme'; -import { localStorageDetector } from 'typesafe-i18n/detectors'; import TypesafeI18n from 'i18n/i18n-react'; import { detectLocale } from 'i18n/i18n-util'; import { loadLocaleAsync } from 'i18n/i18n-util.async'; @@ -18,7 +18,7 @@ const App: FC = () => { const [wasLoaded, setWasLoaded] = useState(false); useEffect(() => { - loadLocaleAsync(detectedLocale).then(() => setWasLoaded(true)); + void loadLocaleAsync(detectedLocale).then(() => setWasLoaded(true)); }, []); if (!wasLoaded) return null; diff --git a/interface/src/SignIn.tsx b/interface/src/SignIn.tsx index 132f19b3b..c3bf7134f 100644 --- a/interface/src/SignIn.tsx +++ b/interface/src/SignIn.tsx @@ -14,12 +14,12 @@ import { ValidatedTextField } from 'components'; import { AuthenticationContext } from 'contexts/authentication'; import { ReactComponent as DEflag } from 'i18n/DE.svg'; +import { ReactComponent as FRflag } from 'i18n/FR.svg'; import { ReactComponent as GBflag } from 'i18n/GB.svg'; import { ReactComponent as NLflag } from 'i18n/NL.svg'; import { ReactComponent as NOflag } from 'i18n/NO.svg'; import { ReactComponent as PLflag } from 'i18n/PL.svg'; import { ReactComponent as SVflag } from 'i18n/SV.svg'; -import { ReactComponent as FRflag } from 'i18n/FR.svg'; import { ReactComponent as TRflag } from 'i18n/TR.svg'; import { I18nContext } from 'i18n/i18n-react'; import { loadLocaleAsync } from 'i18n/i18n-util.async'; @@ -63,7 +63,7 @@ const SignIn: FC = () => { }); try { await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest); - signIn(); + void signIn(); } catch (errors: any) { setFieldErrors(errors); setProcessing(false); diff --git a/interface/src/contexts/authentication/Authentication.tsx b/interface/src/contexts/authentication/Authentication.tsx index a818f3458..1ff343b19 100644 --- a/interface/src/contexts/authentication/Authentication.tsx +++ b/interface/src/contexts/authentication/Authentication.tsx @@ -57,7 +57,7 @@ const Authentication: FC = ({ children }) => { }, []); useEffect(() => { - refresh(); + void refresh(); }, [refresh]); if (initialized) { diff --git a/interface/src/contexts/features/FeaturesLoader.tsx b/interface/src/contexts/features/FeaturesLoader.tsx index c56751237..8169d7b82 100644 --- a/interface/src/contexts/features/FeaturesLoader.tsx +++ b/interface/src/contexts/features/FeaturesLoader.tsx @@ -23,7 +23,7 @@ const FeaturesLoader: FC = (props) => { }, []); useEffect(() => { - loadFeatures(); + void loadFeatures(); }, [loadFeatures]); if (features) { diff --git a/interface/src/framework/ap/APSettingsForm.tsx b/interface/src/framework/ap/APSettingsForm.tsx index b0408820b..87906481c 100644 --- a/interface/src/framework/ap/APSettingsForm.tsx +++ b/interface/src/framework/ap/APSettingsForm.tsx @@ -49,7 +49,7 @@ const APSettingsForm: FC = () => { try { setFieldErrors(undefined); await validate(createAPSettingsValidator(data), data); - saveData(); + void saveData(); } catch (errors: any) { setFieldErrors(errors); } diff --git a/interface/src/framework/mqtt/MqttSettingsForm.tsx b/interface/src/framework/mqtt/MqttSettingsForm.tsx index e531ffee5..cd81d76fe 100644 --- a/interface/src/framework/mqtt/MqttSettingsForm.tsx +++ b/interface/src/framework/mqtt/MqttSettingsForm.tsx @@ -43,7 +43,7 @@ const MqttSettingsForm: FC = () => { try { setFieldErrors(undefined); await validate(createMqttSettingsValidator(data), data); - saveData(); + void saveData(); } catch (errors: any) { setFieldErrors(errors); } diff --git a/interface/src/framework/network/NetworkSettingsForm.tsx b/interface/src/framework/network/NetworkSettingsForm.tsx index 5bb8ddd9a..ff221b8cb 100644 --- a/interface/src/framework/network/NetworkSettingsForm.tsx +++ b/interface/src/framework/network/NetworkSettingsForm.tsx @@ -19,9 +19,11 @@ import { } from '@mui/material'; import { useContext, useEffect, useState } from 'react'; import { toast } from 'react-toastify'; -import type { FC} from 'react'; - - +import RestartMonitor from '../system/RestartMonitor'; +import { WiFiConnectionContext } from './WiFiConnectionContext'; +import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector'; +import type { ValidateFieldsError } from 'async-validator'; +import type { FC } from 'react'; import type { NetworkSettings } from 'types'; import * as NetworkApi from 'api/network'; @@ -39,14 +41,9 @@ import { useI18nContext } from 'i18n/i18n-react'; import * as EMSESP from 'project/api'; import { numberValue, updateValueDirty, useRest } from 'utils'; -import { WiFiConnectionContext } from './WiFiConnectionContext'; -import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector'; -import type { ValidateFieldsError } from 'async-validator'; import { validate } from 'validators'; import { createNetworkSettingsValidator } from 'validators/network'; -import RestartMonitor from '../system/RestartMonitor'; - const WiFiSettingsForm: FC = () => { const { LL } = useI18nContext(); @@ -107,7 +104,7 @@ const WiFiSettingsForm: FC = () => { try { setFieldErrors(undefined); await validate(createNetworkSettingsValidator(data), data); - saveData(); + void saveData(); } catch (errors: any) { setFieldErrors(errors); } diff --git a/interface/src/framework/network/WiFiNetworkScanner.tsx b/interface/src/framework/network/WiFiNetworkScanner.tsx index 2f302c0fa..97205d02c 100644 --- a/interface/src/framework/network/WiFiNetworkScanner.tsx +++ b/interface/src/framework/network/WiFiNetworkScanner.tsx @@ -75,7 +75,7 @@ const WiFiNetworkScanner: FC = () => { }, [finishedWithError, pollNetworkList, LL]); useEffect(() => { - startNetworkScan(); + void startNetworkScan(); }, [startNetworkScan]); const renderNetworkScanner = () => { diff --git a/interface/src/framework/ntp/NTPSettingsForm.tsx b/interface/src/framework/ntp/NTPSettingsForm.tsx index b12ce75a9..238bb0daa 100644 --- a/interface/src/framework/ntp/NTPSettingsForm.tsx +++ b/interface/src/framework/ntp/NTPSettingsForm.tsx @@ -43,7 +43,7 @@ const NTPSettingsForm: FC = () => { try { setFieldErrors(undefined); await validate(NTP_SETTINGS_VALIDATOR, data); - saveData(); + void saveData(); } catch (errors: any) { setFieldErrors(errors); } diff --git a/interface/src/framework/ntp/NTPStatusForm.tsx b/interface/src/framework/ntp/NTPStatusForm.tsx index ade31d3cf..4c70be20e 100644 --- a/interface/src/framework/ntp/NTPStatusForm.tsx +++ b/interface/src/framework/ntp/NTPStatusForm.tsx @@ -90,7 +90,7 @@ const NTPStatusForm: FC = () => { }); toast.success(LL.TIME_SET()); setSettingTime(false); - loadData(); + void loadData(); } catch (error) { toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); } finally { diff --git a/interface/src/framework/security/GenerateToken.tsx b/interface/src/framework/security/GenerateToken.tsx index 52da8139c..d3937b109 100644 --- a/interface/src/framework/security/GenerateToken.tsx +++ b/interface/src/framework/security/GenerateToken.tsx @@ -42,7 +42,7 @@ const GenerateToken: FC = ({ username, onClose }) => { useEffect(() => { if (open) { - getToken(); + void getToken(); } }, [open, getToken]); diff --git a/interface/src/framework/security/ManageUsersForm.tsx b/interface/src/framework/security/ManageUsersForm.tsx index 022df9ef2..b8ef54804 100644 --- a/interface/src/framework/security/ManageUsersForm.tsx +++ b/interface/src/framework/security/ManageUsersForm.tsx @@ -123,7 +123,7 @@ const ManageUsersForm: FC = () => { const onSubmit = async () => { await saveData(); - authenticatedContext.refresh(); + void authenticatedContext.refresh(); }; const user_table = data.users.map((u) => ({ ...u, id: u.username })); diff --git a/interface/src/framework/system/OTASettingsForm.tsx b/interface/src/framework/system/OTASettingsForm.tsx index 9db23ee98..3447ffbe7 100644 --- a/interface/src/framework/system/OTASettingsForm.tsx +++ b/interface/src/framework/system/OTASettingsForm.tsx @@ -45,7 +45,7 @@ const OTASettingsForm: FC = () => { try { setFieldErrors(undefined); await validate(OTA_SETTINGS_VALIDATOR, data); - saveData(); + void saveData(); } catch (errors: any) { setFieldErrors(errors); } diff --git a/interface/src/framework/system/RestartMonitor.tsx b/interface/src/framework/system/RestartMonitor.tsx index 1b7e91026..9ba331568 100644 --- a/interface/src/framework/system/RestartMonitor.tsx +++ b/interface/src/framework/system/RestartMonitor.tsx @@ -31,7 +31,7 @@ const RestartMonitor: FC = () => { }); useEffect(() => { - poll.current(); + void poll.current(); }, []); useEffect(() => () => timeoutId && clearTimeout(timeoutId), [timeoutId]); diff --git a/interface/src/framework/system/SystemLog.tsx b/interface/src/framework/system/SystemLog.tsx index 393212cbb..f50cf20d6 100644 --- a/interface/src/framework/system/SystemLog.tsx +++ b/interface/src/framework/system/SystemLog.tsx @@ -117,7 +117,7 @@ const SystemLog: FC = () => { ...data, level: parseInt(event.target.value) }); - sendSettings(data.max_messages, parseInt(event.target.value)); + void sendSettings(data.max_messages, parseInt(event.target.value)); } }; @@ -163,7 +163,7 @@ const SystemLog: FC = () => { }, [LL]); useEffect(() => { - fetchLog(); + void fetchLog(); }, [fetchLog]); useEffect(() => { diff --git a/interface/src/framework/system/SystemStatusForm.tsx b/interface/src/framework/system/SystemStatusForm.tsx index b7ef1db17..8c97b2762 100644 --- a/interface/src/framework/system/SystemStatusForm.tsx +++ b/interface/src/framework/system/SystemStatusForm.tsx @@ -1,16 +1,16 @@ import AppsIcon from '@mui/icons-material/Apps'; -import RefreshIcon from '@mui/icons-material/Refresh'; -import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; -import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore'; import BuildIcon from '@mui/icons-material/Build'; -import TimerIcon from '@mui/icons-material/Timer'; import CancelIcon from '@mui/icons-material/Cancel'; import DevicesIcon from '@mui/icons-material/Devices'; import FolderIcon from '@mui/icons-material/Folder'; import MemoryIcon from '@mui/icons-material/Memory'; +import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; +import RefreshIcon from '@mui/icons-material/Refresh'; import SdCardAlertIcon from '@mui/icons-material/SdCardAlert'; import SdStorageIcon from '@mui/icons-material/SdStorage'; +import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore'; import ShowChartIcon from '@mui/icons-material/ShowChart'; +import TimerIcon from '@mui/icons-material/Timer'; import { Avatar, Box, @@ -34,11 +34,11 @@ import { toast } from 'react-toastify'; import RestartMonitor from './RestartMonitor'; import type { FC } from 'react'; -import { useI18nContext } from 'i18n/i18n-react'; import type { SystemStatus, Version } from 'types'; import * as SystemApi from 'api/system'; import { ButtonRow, FormLoader, SectionContent, MessageBox } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; +import { useI18nContext } from 'i18n/i18n-react'; import { extractErrorMessage, useRest } from 'utils'; export const VERSIONCHECK_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/latest'; @@ -64,14 +64,14 @@ const SystemStatusForm: FC = () => { const [latestDevVersion, setLatestDevVersion] = useState(); useEffect(() => { - axios.get(VERSIONCHECK_ENDPOINT).then((response) => { + void axios.get(VERSIONCHECK_ENDPOINT).then((response) => { setLatestVersion({ version: response.data.name, url: response.data.assets[1].browser_download_url, changelog: response.data.assets[0].browser_download_url }); }); - axios.get(VERSIONCHECK_DEV_ENDPOINT).then((response) => { + void axios.get(VERSIONCHECK_DEV_ENDPOINT).then((response) => { setLatestDevVersion({ version: response.data.name.split(/\s+/).splice(-1), url: response.data.assets[1].browser_download_url, diff --git a/interface/src/project/SettingsApplication.tsx b/interface/src/project/SettingsApplication.tsx index 41ec58dc0..e8a820ea3 100644 --- a/interface/src/project/SettingsApplication.tsx +++ b/interface/src/project/SettingsApplication.tsx @@ -95,7 +95,7 @@ const SettingsApplication: FC = () => { try { setFieldErrors(undefined); await validate(createSettingsValidator(data), data); - saveData(); + void saveData(); } catch (errors: any) { setFieldErrors(errors); } @@ -110,12 +110,12 @@ const SettingsApplication: FC = () => { board_profile: boardProfile }); } else { - updateBoardProfile(boardProfile); + void updateBoardProfile(boardProfile); } }; const restart = async () => { - validateAndSubmit(); + void validateAndSubmit(); try { await EMSESP.restart(); setRestarting(true); diff --git a/interface/src/project/SettingsCustomization.tsx b/interface/src/project/SettingsCustomization.tsx index 89211a2c8..e44832c17 100644 --- a/interface/src/project/SettingsCustomization.tsx +++ b/interface/src/project/SettingsCustomization.tsx @@ -190,7 +190,7 @@ const SettingsCustomization: FC = () => { }; useEffect(() => { - fetchDevices(); + void fetchDevices(); }, [fetchDevices]); function formatValue(value: any) { @@ -266,7 +266,7 @@ const SettingsCustomization: FC = () => { const selected_device = parseInt(event.target.value, 10); setSelectedDevice(selected_device); setNumChanges(0); - fetchDeviceEntities(devices?.devices[selected_device].i); + void fetchDeviceEntities(devices?.devices[selected_device].i); setRestartNeeded(false); } }; diff --git a/interface/src/utils/useRest.ts b/interface/src/utils/useRest.ts index 0dadda46b..0ee478f60 100644 --- a/interface/src/utils/useRest.ts +++ b/interface/src/utils/useRest.ts @@ -72,7 +72,7 @@ export const useRest = ({ read, update }: RestRequestOptions) => { const saveData = () => data && save(data); useEffect(() => { - loadData(); + void loadData(); }, [loadData]); return { From c2823a5ed61a397df7cdd0c2950f81a17f7ccdf8 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 21:00:12 +0200 Subject: [PATCH 14/89] add SHOWING and SEARCH --- interface/src/i18n/de/index.ts | 5 ++++- interface/src/i18n/en/index.ts | 4 +++- interface/src/i18n/fr/index.ts | 5 ++++- interface/src/i18n/nl/index.ts | 5 ++++- interface/src/i18n/no/index.ts | 5 ++++- interface/src/i18n/pl/index.ts | 5 ++++- interface/src/i18n/sv/index.ts | 4 +++- interface/src/i18n/tr/index.ts | 5 ++++- 8 files changed, 30 insertions(+), 8 deletions(-) diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index 43ba48d1d..211f96466 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -322,7 +322,10 @@ const de: Translation = { CUSTOM_ENTITIES: 'Individuelle Entitäten', ENTITIES_HELP_1: 'Abfrage von Werten auf dem EMS-Bus', ENTITIES_UPDATED: 'Entities Updated', // TODO translate - WRITEABLE: 'Schreibbar' + WRITEABLE: 'Schreibbar', + SHOWING: 'Showing', // TODO translate + SEARCH: 'Search' // TODO translate + }; export default de; diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index 4750a99ad..313d4a413 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -322,7 +322,9 @@ const en: Translation = { CUSTOM_ENTITIES: 'Custom Entities', ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', ENTITIES_UPDATED: 'Entities Updated', - WRITEABLE: 'Writeable' + WRITEABLE: 'Writeable', + SHOWING: 'Showing', + SEARCH: 'Search' }; export default en; diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts index 6059cc95c..0a5499ebf 100644 --- a/interface/src/i18n/fr/index.ts +++ b/interface/src/i18n/fr/index.ts @@ -322,7 +322,10 @@ const fr: Translation = { CUSTOM_ENTITIES: 'Custom Entities', // TODO translate ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate ENTITIES_UPDATED: 'Entities Updated', // TODO translate - WRITEABLE: 'Writeable' // TODO translate + WRITEABLE: 'Writeable', // TODO translate + SHOWING: 'Showing', // TODO translate + SEARCH: 'Search' // TODO translate + }; export default fr; diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index beaf38722..3246e4c47 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -322,7 +322,10 @@ const nl: Translation = { CUSTOM_ENTITIES: 'Custom Entities', // TODO translate ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate ENTITIES_UPDATED: 'Entities Updated', // TODO translate - WRITEABLE: 'Writeable' // TODO translate + WRITEABLE: 'Writeable', // TODO translate + SHOWING: 'Showing', // TODO translate + SEARCH: 'Zoek' + }; export default nl; diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts index 0ab8ecac0..66fb0c2a6 100644 --- a/interface/src/i18n/no/index.ts +++ b/interface/src/i18n/no/index.ts @@ -322,7 +322,10 @@ const no: Translation = { CUSTOM_ENTITIES: 'Custom Entities', // TODO translate ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate ENTITIES_UPDATED: 'Entities Updated', // TODO translate - WRITEABLE: 'Writeable' // TODO translate + WRITEABLE: 'Writeable', // TODO translate + SHOWING: 'Showing', // TODO translate + SEARCH: 'Search' // TODO translate + }; export default no; diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index edf8e9488..d2634ef5f 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -322,7 +322,10 @@ const pl: BaseTranslation = { CUSTOM_ENTITIES: '{{N|n|}}iestandardowe{{|j|}} encj{{e|i|}}', ENTITIES_HELP_1: 'Zdefiniuj niestandardowe encje pobierane z magistrali EMS.', ENTITIES_UPDATED: 'Niestandardowe encje zostały uaktualnione.', - WRITEABLE: 'Writeable' // TODO translate + WRITEABLE: 'Writeable', // TODO translate + SHOWING: 'Showing', // TODO translate + SEARCH: 'Search' // TODO translate + }; export default pl; diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts index 9492425d4..6da8a2feb 100644 --- a/interface/src/i18n/sv/index.ts +++ b/interface/src/i18n/sv/index.ts @@ -322,7 +322,9 @@ const sv: Translation = { CUSTOM_ENTITIES: 'Custom Entities', // TODO translate ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate ENTITIES_UPDATED: 'Entities Updated', // TODO translate - WRITEABLE: 'Writeable' // TODO translate + WRITEABLE: 'Writeable', // TODO translate + SHOWING: 'Showing', // TODO translate + SEARCH: 'Search' // TODO translate }; diff --git a/interface/src/i18n/tr/index.ts b/interface/src/i18n/tr/index.ts index 400fdaafd..73014c59a 100644 --- a/interface/src/i18n/tr/index.ts +++ b/interface/src/i18n/tr/index.ts @@ -322,7 +322,10 @@ const tr: Translation = { CUSTOM_ENTITIES: 'Custom Entities', // TODO translate ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate ENTITIES_UPDATED: 'Entities Updated', // TODO translate - WRITEABLE: 'Writeable' // TODO translate + WRITEABLE: 'Writeable', // TODO translate + SHOWING: 'Showing', // TODO translate + SEARCH: 'Search' // TODO translate + }; export default tr; From cecbb25857e716844e2c5149a4e90db9c973fe6e Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 21:00:29 +0200 Subject: [PATCH 15/89] device_id and type_id sent as numbers --- src/web/WebEntityService.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/web/WebEntityService.cpp b/src/web/WebEntityService.cpp index 8687f20be..7ed4c4afd 100644 --- a/src/web/WebEntityService.cpp +++ b/src/web/WebEntityService.cpp @@ -64,8 +64,8 @@ StateUpdateResult WebEntity::update(JsonObject & root, WebEntity & webEntity) { if (root["entity"].is()) { for (const JsonObject ei : root["entity"].as()) { auto entityItem = EntityItem(); - entityItem.device_id = Helpers::hextoint(ei["device_id"]); // TODO don't need - entityItem.type_id = Helpers::hextoint(ei["type_id"]); + entityItem.device_id = ei["device_id"]; // send as numeric, will be converted to string in web + entityItem.type_id = ei["type_id"]; entityItem.offset = ei["offset"]; entityItem.factor = ei["factor"]; entityItem.name = ei["name"].as(); From b035e27bc81d28bb60f4782fcffc30c0b3f87770 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 21:00:43 +0200 Subject: [PATCH 16/89] device entity min/max sent as numbers --- src/emsdevice.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index f7bd9d2e8..9281dd692 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -1028,9 +1028,8 @@ void EMSdevice::generate_values_web_customization(JsonArray & output) { int16_t dv_set_min; uint16_t dv_set_max; if (dv.get_min_max(dv_set_min, dv_set_max)) { - char s[10]; - obj["mi"] = Helpers::render_value(s, dv_set_min, 0, fahrenheit); - obj["ma"] = Helpers::render_value(s, dv_set_max, 0, fahrenheit); + obj["mi"] = dv_set_min; + obj["ma"] = dv_set_max; } } } From 445618d23235e12d31264af0a898edf3fff66e79 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 21:01:04 +0200 Subject: [PATCH 17/89] add min/max for entity customization --- mock-api/server.js | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/mock-api/server.js b/mock-api/server.js index 81894fa47..c0f4c670c 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -1019,13 +1019,14 @@ function updateMask(entity, de, dd) { // strip of any min/max ranges const shortname_with_customname = entity.slice(2).split('>')[0]; - const shortname = shortname_with_customname.split('|')[0]; const new_custom_name = shortname_with_customname.split('|')[1]; + const has_min_max = entity.slice(2).split('>')[1]; // find in de de_objIndex = de.findIndex((obj) => obj.id === shortname); if (de_objIndex !== -1) { + // get current name if (de[de_objIndex].cn) { fullname = de[de_objIndex].cn; } else { @@ -1046,7 +1047,7 @@ function updateMask(entity, de, dd) { // see if the custom name has changed const old_custom_name = dd.data[dd_objIndex].cn; - console.log('comparing old ' + old_custom_name + ' with new ' + new_custom_name); + console.log('comparing names, old (' + old_custom_name + ') with new (' + new_custom_name + ')'); if (old_custom_name !== new_custom_name) { changed = true; new_fullname = new_custom_name; @@ -1055,12 +1056,33 @@ function updateMask(entity, de, dd) { new_fullname = fullname; } - if (changed) { + // see if min or max has changed + // get current min/max values if they exist + const current_min = dd.data[dd_objIndex].min; + const current_max = dd.data[dd_objIndex].max; + new_min = current_min; + new_max = current_max; + if (has_min_max) { + new_min = parseInt(has_min_max.split('<')[0]); + new_max = parseInt(has_min_max.split('<')[1]); + if (current_min !== new_min || current_max !== new_max) { + changed = true; + console.log('min/max has changed to ' + new_min + '/' + new_max); + } + } + + if (changed === true) { console.log( 'Updating ' + dd.data[dd_objIndex].id + ' -> ' + current_mask.toString(16).padStart(2, '0') + new_fullname ); de[de_objIndex].m = current_mask; de[de_objIndex].cn = new_fullname; + if (new_min) { + de[de_objIndex].mi = new_min; + } + if (new_max) { + de[de_objIndex].ma = new_max; + } dd.data[dd_objIndex].id = current_mask.toString(16).padStart(2, '0') + new_fullname; dd.data[dd_objIndex].cn = new_fullname; } From f53921d06832b506181dce892da05c0c7aa0f454 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 21:01:19 +0200 Subject: [PATCH 18/89] device entity min/max as numbers --- interface/src/project/types.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index c0179b680..148bd81d2 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -146,12 +146,12 @@ export interface DeviceEntity { cn?: string; // custom fullname, optional m: number; // mask w: boolean; // writeable + mi?: number; // min value + ma?: number; // max value o_m?: number; // original mask before edits o_cn?: string; // original cn before edits - mi?: string; // min value - ma?: string; // max value - o_mi?: string; // original min value - o_ma?: string; // original max value + o_mi?: number; // original min value + o_ma?: number; // original max value } export interface CustomEntities { From 4b9bddd5653abd9d1542bfe6c75f0d66e42a12a9 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 21:01:39 +0200 Subject: [PATCH 19/89] add comment --- interface/src/project/SettingsEntitiesDialog.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/project/SettingsEntitiesDialog.tsx b/interface/src/project/SettingsEntitiesDialog.tsx index 8fae8dfc8..f25d6ef8a 100644 --- a/interface/src/project/SettingsEntitiesDialog.tsx +++ b/interface/src/project/SettingsEntitiesDialog.tsx @@ -51,6 +51,7 @@ const SettingsEntitiesDialog = ({ const updateFormValue = updateValue(setEditItem); + // on mount useEffect(() => { if (open) { setFieldErrors(undefined); From e1ffd8860dbd716dc4f8c7ec4abec446ecaef1aa Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 21:02:11 +0200 Subject: [PATCH 20/89] optimize Settings Customization for rendering --- interface/src/project/EntityMaskToggle.tsx | 81 +++++ interface/src/project/OptionIcon.tsx | 4 +- .../src/project/SettingsCustomization.tsx | 340 ++++++------------ .../project/SettingsCustomizationDialog.tsx | 141 ++++++++ 4 files changed, 331 insertions(+), 235 deletions(-) create mode 100644 interface/src/project/EntityMaskToggle.tsx create mode 100644 interface/src/project/SettingsCustomizationDialog.tsx diff --git a/interface/src/project/EntityMaskToggle.tsx b/interface/src/project/EntityMaskToggle.tsx new file mode 100644 index 000000000..f02af3cea --- /dev/null +++ b/interface/src/project/EntityMaskToggle.tsx @@ -0,0 +1,81 @@ +import { ToggleButton, ToggleButtonGroup } from '@mui/material'; +import OptionIcon from './OptionIcon'; +import { DeviceEntityMask } from './types'; +import type { DeviceEntity } from './types'; + +type EntityMaskToggleProps = { + onUpdate: (de: DeviceEntity) => void; + de: DeviceEntity; +}; + +const EntityMaskToggle = ({ onUpdate, de }: EntityMaskToggleProps) => { + const getMaskNumber = (newMask: string[]) => { + let new_mask = 0; + for (const entry of newMask) { + new_mask |= Number(entry); + } + return new_mask; + }; + + const getMaskString = (m: number) => { + const new_masks: string[] = []; + if ((m & 1) === 1) { + new_masks.push('1'); + } + if ((m & 2) === 2) { + new_masks.push('2'); + } + if ((m & 4) === 4) { + new_masks.push('4'); + } + if ((m & 8) === 8) { + new_masks.push('8'); + } + if ((m & 128) === 128) { + new_masks.push('128'); + } + return new_masks; + }; + + return ( + { + de.m = getMaskNumber(mask); + if (de.n === '' && de.m & DeviceEntityMask.DV_READONLY) { + de.m = de.m | DeviceEntityMask.DV_WEB_EXCLUDE; + } + if (de.m & DeviceEntityMask.DV_WEB_EXCLUDE) { + de.m = de.m & ~DeviceEntityMask.DV_FAVORITE; + } + onUpdate(de); + }} + > + + + + = 3}> + + + + + + + + + + + + + ); +}; + +export default EntityMaskToggle; diff --git a/interface/src/project/OptionIcon.tsx b/interface/src/project/OptionIcon.tsx index d1cbdd2b7..f05f4c3c9 100644 --- a/interface/src/project/OptionIcon.tsx +++ b/interface/src/project/OptionIcon.tsx @@ -32,9 +32,9 @@ interface OptionIconProps { const OptionIcon: FC = ({ type, isSet }) => { const Icon = OPTION_ICONS[type][isSet ? 0 : 1]; return isSet ? ( - + ) : ( - + ); }; diff --git a/interface/src/project/SettingsCustomization.tsx b/interface/src/project/SettingsCustomization.tsx index e44832c17..8d142a0a0 100644 --- a/interface/src/project/SettingsCustomization.tsx +++ b/interface/src/project/SettingsCustomization.tsx @@ -1,7 +1,4 @@ import CancelIcon from '@mui/icons-material/Cancel'; -import DoneIcon from '@mui/icons-material/Done'; - -import FilterListIcon from '@mui/icons-material/FilterList'; import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; import SearchIcon from '@mui/icons-material/Search'; import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore'; @@ -17,10 +14,10 @@ import { DialogTitle, ToggleButton, ToggleButtonGroup, - Tooltip, Grid, TextField, - Link + Link, + InputAdornment } from '@mui/material'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; @@ -28,71 +25,38 @@ import { useState, useEffect, useCallback } from 'react'; import { unstable_useBlocker as useBlocker } from 'react-router-dom'; import { toast } from 'react-toastify'; +import EntityMaskToggle from './EntityMaskToggle'; import OptionIcon from './OptionIcon'; +import SettingsCustomizationDialog from './SettingsCustomizationDialog'; import * as EMSESP from './api'; import { DeviceEntityMask } from './types'; import type { DeviceShort, Devices, DeviceEntity } from './types'; import type { FC } from 'react'; -import { ButtonRow, FormLoader, ValidatedTextField, SectionContent, MessageBox, BlockNavigation } from 'components'; +import { ButtonRow, FormLoader, SectionContent, MessageBox, BlockNavigation } from 'components'; import RestartMonitor from 'framework/system/RestartMonitor'; import { useI18nContext } from 'i18n/i18n-react'; -import { extractErrorMessage, updateValue } from 'utils'; +import { extractErrorMessage } from 'utils'; export const APIURL = window.location.origin + '/api/'; const SettingsCustomization: FC = () => { const { LL } = useI18nContext(); - - const emptyDeviceEntity = { id: '', v: 0, n: '', cn: '', m: 0, w: false }; - const [numChanges, setNumChanges] = useState(0); const blocker = useBlocker(numChanges !== 0); - const [restarting, setRestarting] = useState(false); const [restartNeeded, setRestartNeeded] = useState(false); - const [deviceEntities, setDeviceEntities] = useState([emptyDeviceEntity]); + const [deviceEntities, setDeviceEntities] = useState(); const [devices, setDevices] = useState(); const [errorMessage, setErrorMessage] = useState(); const [selectedDevice, setSelectedDevice] = useState(-1); const [confirmReset, setConfirmReset] = useState(false); const [selectedFilters, setSelectedFilters] = useState(0); const [search, setSearch] = useState(''); - const [deviceEntity, setDeviceEntity] = useState(); - // eslint-disable-next-line - 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(() => { - countChanges(); - }); + const [selectedDeviceEntity, setSelectedDeviceEntity] = useState(); + const [dialogOpen, setDialogOpen] = useState(false); const entities_theme = useTheme({ Table: ` @@ -119,12 +83,10 @@ const SettingsCustomization: FC = () => { text-transform: uppercase; background-color: black; color: #90CAF9; - .th { border-bottom: 1px solid #565656; height: 36px; } - &:nth-of-type(1) .th { text-align: center; } @@ -133,21 +95,17 @@ const SettingsCustomization: FC = () => { background-color: #1e1e1e; position: relative; cursor: pointer; - .td { border-top: 1px solid #565656; border-bottom: 1px solid #565656; } - &.tr.tr-body.row-select.row-select-single-selected { background-color: #3d4752; } - &:hover .td { border-top: 1px solid #177ac9; border-bottom: 1px solid #177ac9; } - &:nth-of-type(odd) .td { background-color: #303030; } @@ -168,6 +126,28 @@ 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; + } + + useEffect(() => { + if (deviceEntities) { + setNumChanges( + 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 : '') + ).length + ); + } + }, [deviceEntities]); + const fetchDevices = useCallback(async () => { try { setDevices((await EMSESP.readDevices()).data); @@ -176,6 +156,11 @@ const SettingsCustomization: FC = () => { } }, [LL]); + // on mount + useEffect(() => { + void fetchDevices(); + }, [fetchDevices]); + const setOriginalSettings = (data: DeviceEntity[]) => { setDeviceEntities(data.map((de) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma }))); }; @@ -189,10 +174,6 @@ const SettingsCustomization: FC = () => { } }; - useEffect(() => { - void fetchDevices(); - }, [fetchDevices]); - function formatValue(value: any) { if (typeof value === 'number') { return new Intl.NumberFormat().format(value); @@ -246,7 +227,7 @@ const SettingsCustomization: FC = () => { const maskDisabled = (set: boolean) => { setDeviceEntities( - deviceEntities.map(function (de) { + deviceEntities?.map(function (de) { if ((de.m & selectedFilters || !selectedFilters) && de.id.toLowerCase().includes(search.toLowerCase())) { return { ...de, @@ -291,9 +272,45 @@ const SettingsCustomization: FC = () => { } }; + const onDialogClose = () => { + setDialogOpen(false); + }; + + const updateDeviceEntity = (updatedItem: DeviceEntity) => { + setDeviceEntities(deviceEntities?.map((de) => (de.id === updatedItem.id ? { ...de, ...updatedItem } : de))); + }; + + const onDialogSave = (updatedItem: DeviceEntity) => { + setDialogOpen(false); + updateDeviceEntity(updatedItem); + }; + + const editDeviceEntity = useCallback((de: DeviceEntity) => { + if (de.n === undefined || (de.n && de.n[0] === '!')) { + return; + } + + if (de.cn === undefined) { + de.cn = ''; + } + + setSelectedDeviceEntity(de); + setDialogOpen(true); + }, []); + const saveCustomization = async () => { if (devices && deviceEntities && selectedDevice !== -1) { - const masked_entities = getChanges(); + const masked_entities = 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 : '') + ); // check size in bytes to match buffer in CPP, which is 2048 const bytes = new TextEncoder().encode(JSON.stringify(masked_entities)).length; @@ -329,8 +346,8 @@ const SettingsCustomization: FC = () => { return ( <> - {LL.CUSTOMIZATIONS_HELP_1()} - + {LL.CUSTOMIZATIONS_HELP_1()}. + ={LL.CUSTOMIZATIONS_HELP_2()}   ={LL.CUSTOMIZATIONS_HELP_3()}   ={LL.CUSTOMIZATIONS_HELP_4()}   @@ -338,7 +355,7 @@ const SettingsCustomization: FC = () => { ={LL.CUSTOMIZATIONS_HELP_6()} - { {device.s} ))} - + ); }; - const editEntity = (de: DeviceEntity) => { - if (de.n === undefined || (de.n && de.n[0] === '!')) { + const renderDeviceData = () => { + if (!deviceEntities) { return; } - if (de.cn === undefined) { - de.cn = ''; - } - setDeviceEntity(de); - }; - - const updateEntity = () => { - if (deviceEntity) { - setDeviceEntities((prevState) => { - const newState = prevState.map((obj) => { - if (obj.id === deviceEntity.id) { - return { ...obj, cn: deviceEntity.cn, mi: deviceEntity.mi, ma: deviceEntity.ma }; - } - return obj; - }); - return newState; - }); - } - - setDeviceEntity(undefined); - }; - - const renderDeviceData = () => { if (devices?.devices.length === 0 || deviceEntities[0].id === '') { return; } @@ -401,33 +395,23 @@ const SettingsCustomization: FC = () => { return ( <> - - - #: - - - - - {shown_data.length}/{deviceEntities.length} - - - - : - { setSearch(event.target.value); }} + InputProps={{ + startAdornment: ( + + + + ) + }} /> - - - : - - { - + + + {LL.SHOWING()} {shown_data.length}/{deviceEntities.length} + +
{(tableList: any) => ( @@ -496,63 +484,14 @@ const SettingsCustomization: FC = () => { {tableList.map((de: DeviceEntity) => ( - editEntity(de)}> + editDeviceEntity(de)}> - {!deviceEntity && ( - { - de.m = getMaskNumber(mask); - if (de.n === '' && de.m & DeviceEntityMask.DV_READONLY) { - de.m = de.m | DeviceEntityMask.DV_WEB_EXCLUDE; - } - if (de.m & DeviceEntityMask.DV_WEB_EXCLUDE) { - de.m = de.m & ~DeviceEntityMask.DV_FAVORITE; - } - setMasks(['']); // forces a refresh - }} - > - - - - = 3}> - - - - - - - - - - - - - )} + - {!deviceEntity && formatName(de)} - {!deviceEntity && !(de.m & DeviceEntityMask.DV_READONLY) && formatValue(de.mi)} - {!deviceEntity && !(de.m & DeviceEntityMask.DV_READONLY) && formatValue(de.ma)} - {!deviceEntity && formatValue(de.v)} + {formatName(de)} + {!(de.m & DeviceEntityMask.DV_READONLY) && formatValue(de.mi)} + {!(de.m & DeviceEntityMask.DV_READONLY) && formatValue(de.ma)} + {formatValue(de.v)} ))} @@ -632,83 +571,18 @@ const SettingsCustomization: FC = () => { ); - const renderEditDialog = () => { - if (deviceEntity) { - return ( - setDeviceEntity(undefined)}> - {LL.EDIT() + ' ' + LL.ENTITY() + ' "' + deviceEntity.id + '"'} - - - - {LL.DEFAULT(1) + ' ' + LL.NAME(1)}: {deviceEntity.n} - - - - - - - {typeof deviceEntity.v === 'number' && - deviceEntity.w && - !(deviceEntity.m & DeviceEntityMask.DV_READONLY) && ( - <> - - - - - - - - )} - - - - - - - - ); - } - }; - return ( {blocker ? : null} {restarting ? : renderContent()} - {renderEditDialog()} + {selectedDeviceEntity && ( + + )} ); }; diff --git a/interface/src/project/SettingsCustomizationDialog.tsx b/interface/src/project/SettingsCustomizationDialog.tsx new file mode 100644 index 000000000..faa1e10a3 --- /dev/null +++ b/interface/src/project/SettingsCustomizationDialog.tsx @@ -0,0 +1,141 @@ +import CancelIcon from '@mui/icons-material/Cancel'; +import DoneIcon from '@mui/icons-material/Done'; + +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Grid, + TextField, + Typography +} from '@mui/material'; +import { useEffect, useState } from 'react'; + +import EntityMaskToggle from './EntityMaskToggle'; +import { DeviceEntityMask } from './types'; +import type { DeviceEntity } from './types'; + +import { useI18nContext } from 'i18n/i18n-react'; + +import { updateValue } from 'utils'; + +type SettingsCustomizationDialogProps = { + open: boolean; + onClose: () => void; + onSave: (di: DeviceEntity) => void; + selectedDeviceEntity: DeviceEntity; +}; + +const SettingsCustomizationDialog = ({ + open, + onClose, + onSave, + selectedDeviceEntity +}: SettingsCustomizationDialogProps) => { + const { LL } = useI18nContext(); + const [editItem, setEditItem] = useState(selectedDeviceEntity); + const [error, setError] = useState(false); + + const updateFormValue = updateValue(setEditItem); + + const isWriteableNumber = + typeof editItem.v === 'number' && editItem.w && !(editItem.m & DeviceEntityMask.DV_READONLY); + + useEffect(() => { + if (open) { + setError(false); + setEditItem(selectedDeviceEntity); + } + }, [open, selectedDeviceEntity]); + + const close = () => { + onClose(); + }; + + const save = () => { + if (isWriteableNumber && editItem.mi && editItem.ma && editItem.mi > editItem?.ma) { + setError(true); + } else { + onSave(editItem); + } + }; + + const updateDeviceEntity = (updatedItem: DeviceEntity) => { + setEditItem({ ...editItem, m: updatedItem.m }); + }; + + return ( + + {LL.EDIT() + ' ' + LL.ENTITY()} + + + + {LL.ENTITY()}: {editItem.id} + + + + + {LL.DEFAULT(1) + ' ' + LL.NAME(1)}: {editItem.n} + + + + + + + + + + {isWriteableNumber && ( + <> + + + + + + + + )} + + {error && ( + + Error: Check min and max values + + )} + + + + + + + ); +}; + +export default SettingsCustomizationDialog; From 728e15772f6c1f721b87d4a69224bd935fb0d36a Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 21:04:37 +0200 Subject: [PATCH 21/89] bump version --- src/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h b/src/version.h index 1335264e9..8d9c86e46 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.6.0-dev.9" +#define EMSESP_APP_VERSION "3.6.0-dev.10" From 71d086a8db62085759d35edc25d5197ecc190aef Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 21:04:54 +0200 Subject: [PATCH 22/89] device_id and type_id as numbers --- src/web/WebEntityService.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/web/WebEntityService.cpp b/src/web/WebEntityService.cpp index 7ed4c4afd..e89d1b032 100644 --- a/src/web/WebEntityService.cpp +++ b/src/web/WebEntityService.cpp @@ -41,8 +41,8 @@ void WebEntity::read(WebEntity & webEntity, JsonObject & root) { for (const EntityItem & entityItem : webEntity.entityItems) { JsonObject ei = entity.createNestedObject(); ei["id"] = counter++; // id is only used to render the table and must be unique - ei["device_id"] = Helpers::hextoa(entityItem.device_id, false); - ei["type_id"] = Helpers::hextoa(entityItem.type_id, false); + ei["device_id"] = entityItem.device_id; + ei["type_id"] = entityItem.type_id; ei["offset"] = entityItem.offset; ei["factor"] = entityItem.factor; ei["name"] = entityItem.name; From 74c88caa1962972d7f2120a412fa8a5ebde0806d Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 21:19:48 +0200 Subject: [PATCH 23/89] add empty entity test --- mock-api/server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/mock-api/server.js b/mock-api/server.js index c0f4c670c..a2ccd127a 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -601,6 +601,7 @@ const emsesp_devicedata_4 = { // CUSTOM ENTITIES let emsesp_entities = { + // entities: [] entities: [ { id: 0, From 466e9ec6fe2c25e917f9840980c7f3176841a3fd Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 21:20:00 +0200 Subject: [PATCH 24/89] rename endpoint to entities to be consistent --- src/web/WebEntityService.cpp | 6 +++--- src/web/WebEntityService.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/web/WebEntityService.cpp b/src/web/WebEntityService.cpp index e89d1b032..e377a8ace 100644 --- a/src/web/WebEntityService.cpp +++ b/src/web/WebEntityService.cpp @@ -36,7 +36,7 @@ void WebEntityService::begin() { // this creates the entity file, saving it to the FS // and also calls when the Entity web page is refreshed void WebEntity::read(WebEntity & webEntity, JsonObject & root) { - JsonArray entity = root.createNestedArray("entity"); + JsonArray entity = root.createNestedArray("entities"); uint8_t counter = 0; for (const EntityItem & entityItem : webEntity.entityItems) { JsonObject ei = entity.createNestedObject(); @@ -61,8 +61,8 @@ StateUpdateResult WebEntity::update(JsonObject & root, WebEntity & webEntity) { } webEntity.entityItems.clear(); - if (root["entity"].is()) { - for (const JsonObject ei : root["entity"].as()) { + if (root["entities"].is()) { + for (const JsonObject ei : root["entities"].as()) { auto entityItem = EntityItem(); entityItem.device_id = ei["device_id"]; // send as numeric, will be converted to string in web entityItem.type_id = ei["type_id"]; diff --git a/src/web/WebEntityService.h b/src/web/WebEntityService.h index 41de3ed9c..37a94e15f 100644 --- a/src/web/WebEntityService.h +++ b/src/web/WebEntityService.h @@ -21,7 +21,7 @@ #define WebEntityService_h #define EMSESP_ENTITY_FILE "/config/emsespEntity.json" -#define EMSESP_ENTITY_SERVICE_PATH "/rest/entity" // GET and POST +#define EMSESP_ENTITY_SERVICE_PATH "/rest/entities" // GET and POST namespace emsesp { From 047eae2d408cff51d96f5acba3712afc163c24d6 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sun, 23 Apr 2023 09:45:12 +0200 Subject: [PATCH 25/89] package update --- interface/package.json | 4 ++-- interface/yarn.lock | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/interface/package.json b/interface/package.json index 81791870b..8dea853f8 100644 --- a/interface/package.json +++ b/interface/package.json @@ -28,7 +28,7 @@ "@remix-run/router": "^1.5.0", "@table-library/react-table-library": "4.1.0", "@types/lodash-es": "^4.17.7", - "@types/node": "^18.15.13", + "@types/node": "^18.16.0", "@types/react": "^18.0.38", "@types/react-dom": "^18.0.11", "@types/react-router-dom": "^5.3.3", @@ -68,7 +68,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "nodemon": "^2.0.22", "npm-run-all": "^4.1.5", - "prettier": "^2.8.7", + "prettier": "^2.8.8", "rollup-plugin-visualizer": "^5.9.0", "terser": "^5.17.1", "vite": "^4.3.1", diff --git a/interface/yarn.lock b/interface/yarn.lock index ebd7ba443..5616f6250 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -1348,10 +1348,10 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^18.15.13": - version: 18.15.13 - resolution: "@types/node@npm:18.15.13" - checksum: 6e5f61c559e60670a7a8fb88e31226ecc18a21be103297ca4cf9848f0a99049dae77f04b7ae677205f2af494f3701b113ba8734f4b636b355477a6534dbb8ada +"@types/node@npm:^18.16.0": + version: 18.16.0 + resolution: "@types/node@npm:18.16.0" + checksum: 6e158e0e8a49f597f590f1d4c94a7b89b7ae707c0134540359559b14427e94dbde289c7557c8a2fe33baaaad478248a819fdccaa2b97a76dfa47bc61ab7f2ab6 languageName: node linkType: hard @@ -1744,7 +1744,7 @@ __metadata: "@table-library/react-table-library": 4.1.0 "@types/lodash-es": ^4.17.7 "@types/mime-types": ^2 - "@types/node": ^18.15.13 + "@types/node": ^18.16.0 "@types/react": ^18.0.38 "@types/react-dom": ^18.0.11 "@types/react-router-dom": ^5.3.3 @@ -1772,7 +1772,7 @@ __metadata: mime-types: ^2.1.35 nodemon: ^2.0.22 npm-run-all: ^4.1.5 - prettier: ^2.8.7 + prettier: ^2.8.8 react: latest react-dom: latest react-dropzone: ^14.2.3 @@ -5204,12 +5204,12 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^2.8.7": - version: 2.8.7 - resolution: "prettier@npm:2.8.7" +"prettier@npm:^2.8.8": + version: 2.8.8 + resolution: "prettier@npm:2.8.8" bin: prettier: bin-prettier.js - checksum: 84c5b62f7d4909ae5b18b1a4cee67f6a30a548244c8919e67158dee1453f4fa4ff4d291c6f2e41e21d443a0c405f03ec27690502d4ad90c3a7c59bcaf38b51ba + checksum: 463ea8f9a0946cd5b828d8cf27bd8b567345cf02f56562d5ecde198b91f47a76b7ac9eae0facd247ace70e927143af6135e8cf411986b8cb8478784a4d6d724a languageName: node linkType: hard From 300fe721a15ea8b08ef3137013a1cd24c176ceed Mon Sep 17 00:00:00 2001 From: Proddy Date: Sun, 23 Apr 2023 10:14:35 +0200 Subject: [PATCH 26/89] tidy up files --- interface/.eslintignore | 6 ++++-- interface/progmem-generator.js | 14 ++++++-------- interface/src/project/types.ts | 1 - interface/tsconfig.json | 1 - 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/interface/.eslintignore b/interface/.eslintignore index 55ba94de1..6d0c74da6 100644 --- a/interface/.eslintignore +++ b/interface/.eslintignore @@ -1,9 +1,11 @@ node_modules/ build/ dist/ +.yarn/ + .prettierrc .eslintrc* -.yarn/ env.d.ts progmem-generator.js -vite.config.ts \ No newline at end of file +vite.config.ts +package.json \ No newline at end of file diff --git a/interface/progmem-generator.js b/interface/progmem-generator.js index f9d9b3ad7..905173626 100644 --- a/interface/progmem-generator.js +++ b/interface/progmem-generator.js @@ -1,5 +1,5 @@ -const { resolve, relative, sep } = require('path'); const { readdirSync, existsSync, unlinkSync, readFileSync, createWriteStream } = require('fs'); +const { resolve, relative, sep } = require('path'); var zlib = require('zlib'); var mime = require('mime-types'); @@ -18,9 +18,9 @@ function getFilesSync(dir, files = []) { return files; } -function coherseToBuffer(input) { - return Buffer.isBuffer(input) ? input : Buffer.from(input); -} +// function coherseToBuffer(input) { +// return Buffer.isBuffer(input) ? input : Buffer.from(input); +// } function cleanAndOpen(path) { if (existsSync(path)) { @@ -87,9 +87,9 @@ export default function ProgmemGenerator({ outputPath = './WWWData.h', bytesPerL // }); }; - const generateWWWClass = () => { + const generateWWWClass = () => // eslint-disable-next-line max-len - return `typedef std::function RouteRegistrationHandler; + `typedef std::function RouteRegistrationHandler; class WWWData { ${indent}public: @@ -100,8 +100,6 @@ ${fileInfo ${indent.repeat(2)}} }; `; - }; - const writeWWWClass = () => { writeStream.write(generateWWWClass()); }; diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index a0309929f..148bd81d2 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -360,7 +360,6 @@ export interface EntityItem { o_value_type?: number; o_deleted?: boolean; o_writeable?: boolean; - } export interface Entities { diff --git a/interface/tsconfig.json b/interface/tsconfig.json index 6a77480de..45592aca2 100644 --- a/interface/tsconfig.json +++ b/interface/tsconfig.json @@ -13,7 +13,6 @@ "noFallthroughCasesInSwitch": true, "composite": true, "module": "ESNext", - // "moduleResolution": "Node", "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, From 71e7887da327dd317a0936adb5096a0735b238d8 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sun, 23 Apr 2023 11:02:10 +0200 Subject: [PATCH 27/89] replace MUI Slider, saves 5KB --- interface/src/framework/system/SystemLog.tsx | 84 ++++++++------------ 1 file changed, 34 insertions(+), 50 deletions(-) diff --git a/interface/src/framework/system/SystemLog.tsx b/interface/src/framework/system/SystemLog.tsx index f50cf20d6..626e8111f 100644 --- a/interface/src/framework/system/SystemLog.tsx +++ b/interface/src/framework/system/SystemLog.tsx @@ -1,5 +1,6 @@ import DownloadIcon from '@mui/icons-material/GetApp'; -import { Box, styled, Button, Checkbox, MenuItem, Grid, Slider, FormLabel } from '@mui/material'; +import WarningIcon from '@mui/icons-material/Warning'; +import { Box, styled, Button, Checkbox, MenuItem, Grid } from '@mui/material'; import { useState, useEffect, useCallback, useLayoutEffect } from 'react'; import { toast } from 'react-toastify'; import type { FC } from 'react'; @@ -9,7 +10,7 @@ import { addAccessTokenParameter } from 'api/authentication'; import { EVENT_SOURCE_ROOT } from 'api/endpoints'; import * as SystemApi from 'api/system'; -import { SectionContent, FormLoader, BlockFormControlLabel, ValidatedTextField } from 'components'; +import { SectionContent, FormLoader, BlockFormControlLabel, ValidatedTextField, ButtonRow } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; import { LogLevel } from 'types'; @@ -90,16 +91,12 @@ const SystemLog: FC = () => { const updateFormValue = updateValue(setData); - const reloadPage = () => { - window.location.reload(); - }; - - const sendSettings = async (new_max_messages: number, new_level: number) => { + const sendSettings = async () => { if (data) { try { const response = await SystemApi.updateLogSettings({ - level: new_level, - max_messages: new_max_messages, + level: data.level, + max_messages: data.max_messages, compact: data.compact }); if (response.status !== 200) { @@ -111,25 +108,6 @@ const SystemLog: FC = () => { } }; - const changeLevel = (event: React.ChangeEvent) => { - if (data) { - setData({ - ...data, - level: parseInt(event.target.value) - }); - void sendSettings(data.max_messages, parseInt(event.target.value)); - } - }; - - const changeMaxMessages = (event: Event, value: number | number[]) => { - if (data) { - setData({ - ...data, - max_messages: value as number - }); - } - }; - const onDownload = () => { let result = ''; for (const i of logEntries.events) { @@ -171,7 +149,7 @@ const SystemLog: FC = () => { es.onmessage = onMessage; es.onerror = () => { es.close(); - reloadPage(); + window.location.reload(); }; return () => { @@ -188,14 +166,14 @@ const SystemLog: FC = () => { return ( <> - + @@ -208,24 +186,22 @@ const SystemLog: FC = () => { ALL - - {LL.BUFFER_SIZE()} - + sendSettings(data.max_messages, data.level)} - /> + label={LL.BUFFER_SIZE()} + value={data.max_messages} + fullWidth + variant="outlined" + onChange={updateFormValue} + margin="normal" + select + > + 25 + 50 + 75 + 100 + { label={LL.COMPACT()} /> - + - + + Date: Mon, 24 Apr 2023 16:50:05 +0200 Subject: [PATCH 28/89] remove force from check_active() --- src/devices/boiler.cpp | 6 +++--- src/devices/boiler.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/devices/boiler.cpp b/src/devices/boiler.cpp index 2d0cbb4bb..f8b50d0dc 100644 --- a/src/devices/boiler.cpp +++ b/src/devices/boiler.cpp @@ -833,7 +833,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const // Check if hot tap water or heating is active // Values will always be posted first time as heatingActive_ and tapwaterActive_ will have values EMS_VALUE_BOOL_NOTSET -void Boiler::check_active(const bool force) { +void Boiler::check_active() { if (!Helpers::hasValue(boilerState_)) { return; } @@ -844,7 +844,7 @@ void Boiler::check_active(const bool force) { // check if heating is active, bits 2 and 4 must be set b = ((boilerState_ & 0x09) == 0x09); val = b ? EMS_VALUE_BOOL_ON : EMS_VALUE_BOOL_OFF; - if (heatingActive_ != val || force) { + if (heatingActive_ != val) { heatingActive_ = val; char s[12]; Mqtt::queue_publish(F_(heating_active), Helpers::render_boolean(s, b)); @@ -868,7 +868,7 @@ void Boiler::check_active(const bool force) { } val = b ? EMS_VALUE_BOOL_ON : EMS_VALUE_BOOL_OFF; - if (tapwaterActive_ != val || force) { + if (tapwaterActive_ != val) { tapwaterActive_ = val; char s[12]; Mqtt::queue_publish(F_(tapwater_active), Helpers::render_boolean(s, b)); diff --git a/src/devices/boiler.h b/src/devices/boiler.h index c0f81c07a..3e5988d05 100644 --- a/src/devices/boiler.h +++ b/src/devices/boiler.h @@ -35,7 +35,7 @@ class Boiler : public EMSdevice { return (flags() & 0x0F); } - void check_active(const bool force = false); + void check_active(); uint8_t boilerState_ = EMS_VALUE_UINT_NOTSET; // Boiler state flag - FOR INTERNAL USE From 005485188f98c26eaa278bc5425a45bd4ddf5361 Mon Sep 17 00:00:00 2001 From: Proddy Date: Mon, 24 Apr 2023 16:50:28 +0200 Subject: [PATCH 29/89] updates to https://github.com/emsesp/EMS-ESP32/pull/1162 --- interface/src/project/SettingsEntities.tsx | 22 +++++++++---------- .../src/project/SettingsEntitiesDialog.tsx | 9 ++++---- interface/src/project/types.ts | 4 ++-- interface/src/project/validators.ts | 6 ++--- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/interface/src/project/SettingsEntities.tsx b/interface/src/project/SettingsEntities.tsx index d1d2d6504..aafa602ee 100644 --- a/interface/src/project/SettingsEntities.tsx +++ b/interface/src/project/SettingsEntities.tsx @@ -187,30 +187,28 @@ const SettingsEntities: FC = () => { setSelectedEntityItem({ id: Math.floor(Math.random() * (Math.floor(200) - 100) + 100), name: '', - device_id: 11, - type_id: 0, + device_id: '', + type_id: '', offset: 0, factor: 1, uom: 0, - value_type: 2, + value_type: 0, writeable: false, deleted: false }); setDialogOpen(true); }; - function formatValue(value: any, uom: number) { - if (value === undefined) { - return ''; - } - return new Intl.NumberFormat().format(value) + (uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom]); + function formatValue(value: any, uom: any) { + return value === undefined || uom === undefined + ? '' + : new Intl.NumberFormat().format(value) + (uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom]); } function showHex(value: number, digit: number) { - if (digit === 4) { - return '0x' + ('000' + value.toString(16).toUpperCase()).slice(-4); - } - return '0x' + ('0' + value.toString(16).toUpperCase()).slice(-2); + return digit === 4 + ? '0x' + ('000' + value.toString(16).toUpperCase()).slice(-4) + : '0x' + ('0' + value.toString(16).toUpperCase()).slice(-2); } const renderEntity = () => { diff --git a/interface/src/project/SettingsEntitiesDialog.tsx b/interface/src/project/SettingsEntitiesDialog.tsx index f25d6ef8a..f2ef22e21 100644 --- a/interface/src/project/SettingsEntitiesDialog.tsx +++ b/interface/src/project/SettingsEntitiesDialog.tsx @@ -59,8 +59,8 @@ const SettingsEntitiesDialog = ({ // convert to hex strings straight away setEditItem({ ...selectedEntityItem, - device_id: ('0' + selectedEntityItem.device_id.toString(16).toUpperCase()).slice(-2), - type_id: ('000' + selectedEntityItem.type_id.toString(16).toUpperCase()).slice(-4) + device_id: selectedEntityItem.device_id.toString(16).toUpperCase().slice(-2), + type_id: selectedEntityItem.type_id.toString(16).toUpperCase().slice(-4) }); } }, [open, selectedEntityItem]); @@ -123,8 +123,9 @@ const SettingsEntitiesDialog = ({ name="device_id" label={LL.ID_OF(LL.DEVICE())} margin="normal" + type="string" fullWidth - value={editItem.device_id} + value={editItem.device_id as string} onChange={updateFormValue} inputProps={{ style: { textTransform: 'uppercase' } }} InputProps={{ startAdornment: 0x }} @@ -157,7 +158,7 @@ const SettingsEntitiesDialog = ({ } ], device_id: [ - { required: true, message: 'Device ID is required' }, { validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) { if (isNaN(parseInt(value, 16))) { - callback('Must be a hex number'); + callback('Is required and must be in hex format'); } callback(); } } ], type_id: [ - { required: true, message: 'Type ID is required' }, { validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) { if (isNaN(parseInt(value, 16))) { - callback('Must be a hex number'); + callback('Is required and must be in hex format'); } callback(); } From bcd0c5ac52fbdad4c80e88abaf05fa553fa03de3 Mon Sep 17 00:00:00 2001 From: Proddy Date: Mon, 24 Apr 2023 17:42:44 +0200 Subject: [PATCH 30/89] only show Update when changes made - https://github.com/emsesp/EMS-ESP32/pull/1162 --- interface/src/framework/system/SystemLog.tsx | 40 +++++--------------- 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/interface/src/framework/system/SystemLog.tsx b/interface/src/framework/system/SystemLog.tsx index 626e8111f..5019738fc 100644 --- a/interface/src/framework/system/SystemLog.tsx +++ b/interface/src/framework/system/SystemLog.tsx @@ -2,7 +2,6 @@ import DownloadIcon from '@mui/icons-material/GetApp'; import WarningIcon from '@mui/icons-material/Warning'; import { Box, styled, Button, Checkbox, MenuItem, Grid } from '@mui/material'; import { useState, useEffect, useCallback, useLayoutEffect } from 'react'; -import { toast } from 'react-toastify'; import type { FC } from 'react'; import type { LogSettings, LogEntry, LogEntries } from 'types'; @@ -14,7 +13,7 @@ import { SectionContent, FormLoader, BlockFormControlLabel, ValidatedTextField, import { useI18nContext } from 'i18n/i18n-react'; import { LogLevel } from 'types'; -import { updateValue, useRest, extractErrorMessage } from 'utils'; +import { useRest, updateValueDirty, extractErrorMessage } from 'utils'; export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log'; @@ -66,8 +65,9 @@ const SystemLog: FC = () => { const { LL } = useI18nContext(); - const { loadData, data, setData } = useRest({ - read: SystemApi.readLogSettings + const { loadData, data, setData, saveData, origData, dirtyFlags, setDirtyFlags } = useRest({ + read: SystemApi.readLogSettings, + update: SystemApi.updateLogSettings }); const [errorMessage, setErrorMessage] = useState(); @@ -89,24 +89,7 @@ const SystemLog: FC = () => { return data?.compact ? label : label.padEnd(7, '\xa0'); }; - const updateFormValue = updateValue(setData); - - const sendSettings = async () => { - if (data) { - try { - const response = await SystemApi.updateLogSettings({ - level: data.level, - max_messages: data.max_messages, - compact: data.compact - }); - if (response.status !== 200) { - toast.error(LL.PROBLEM_UPDATING()); - } - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - } - } - }; + const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData); const onDownload = () => { let result = ''; @@ -213,14 +196,11 @@ const SystemLog: FC = () => { - + {dirtyFlags && dirtyFlags.length !== 0 && ( + + )} Date: Mon, 24 Apr 2023 21:37:47 +0200 Subject: [PATCH 31/89] rename 'Tx issues - check Tx Mode' --- interface/src/i18n/en/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index fc75c4769..de494b4ad 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -74,7 +74,7 @@ const en: Translation = { EMS_BUS_WARNING: 'EMS bus disconnected. If this warning still persists after a few seconds please check settings and board profile', EMS_BUS_SCANNING: 'Scanning for EMS devices...', CONNECTED: 'Connected', - TX_ISSUES: 'Tx issues - try a different Tx Mode', + TX_ISSUES: 'Tx issues - check Tx Mode', DISCONNECTED: 'Disconnected', EMS_SCAN: 'Are you sure you want to initiate a full device scan of the EMS bus?', EMS_BUS_STATUS: 'EMS Bus Status', From f2636d42a4d9c9f9d83c9c51bb9971e4f7db151e Mon Sep 17 00:00:00 2001 From: Proddy Date: Mon, 24 Apr 2023 21:38:03 +0200 Subject: [PATCH 32/89] updated packages --- interface/package.json | 4 +- interface/yarn.lock | 100 ++++++++++++++++++++--------------------- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/interface/package.json b/interface/package.json index 8dea853f8..4c221f7c1 100644 --- a/interface/package.json +++ b/interface/package.json @@ -52,8 +52,8 @@ "devDependencies": { "@types/mime-types": "^2", "@types/styled-components": "^5", - "@typescript-eslint/eslint-plugin": "^5.59.0", - "@typescript-eslint/parser": "^5.59.0", + "@typescript-eslint/eslint-plugin": "^5.59.1", + "@typescript-eslint/parser": "^5.59.1", "@vitejs/plugin-react-swc": "^3.3.0", "eslint": "^8.39.0", "eslint-config-airbnb": "^19.0.4", diff --git a/interface/yarn.lock b/interface/yarn.lock index 5616f6250..d98855548 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -1480,14 +1480,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^5.59.0": - version: 5.59.0 - resolution: "@typescript-eslint/eslint-plugin@npm:5.59.0" +"@typescript-eslint/eslint-plugin@npm:^5.59.1": + version: 5.59.1 + resolution: "@typescript-eslint/eslint-plugin@npm:5.59.1" dependencies: "@eslint-community/regexpp": ^4.4.0 - "@typescript-eslint/scope-manager": 5.59.0 - "@typescript-eslint/type-utils": 5.59.0 - "@typescript-eslint/utils": 5.59.0 + "@typescript-eslint/scope-manager": 5.59.1 + "@typescript-eslint/type-utils": 5.59.1 + "@typescript-eslint/utils": 5.59.1 debug: ^4.3.4 grapheme-splitter: ^1.0.4 ignore: ^5.2.0 @@ -1500,43 +1500,43 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: f3b557fc875f688073835b6d41af4184c08c00b0f4887e62bb110e2935d50a158b026547cde89708bf6463913322e757a07d2de26fc505a3c15a81120d64ccef + checksum: 39b45b35617b47df6242791f67ee53dafe8d973c0ea452cfb6d8f5883b7ee6c8a5056110c53b91fa941c81294110ea2049f082da53b45fe42deafbeec1f9fbdf languageName: node linkType: hard -"@typescript-eslint/parser@npm:^5.59.0": - version: 5.59.0 - resolution: "@typescript-eslint/parser@npm:5.59.0" +"@typescript-eslint/parser@npm:^5.59.1": + version: 5.59.1 + resolution: "@typescript-eslint/parser@npm:5.59.1" dependencies: - "@typescript-eslint/scope-manager": 5.59.0 - "@typescript-eslint/types": 5.59.0 - "@typescript-eslint/typescript-estree": 5.59.0 + "@typescript-eslint/scope-manager": 5.59.1 + "@typescript-eslint/types": 5.59.1 + "@typescript-eslint/typescript-estree": 5.59.1 debug: ^4.3.4 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 5e0f8dfe4eb762bf1d2e8186559d39df653005a8f976101cd7b3739f3e0253d1003b4268976e52c7e42b63bf6ea042b1ed9412f85d06ed8fb0407b56dba19db2 + checksum: 59a9076ac1b547bbc36517cbb8201489541670023c4647d2f21f5b5172ab097c83e3b7d2652ebafe9a0efba673e09056fa8f4f2c1eae656d8328ec9084e31629 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:5.59.0": - version: 5.59.0 - resolution: "@typescript-eslint/scope-manager@npm:5.59.0" +"@typescript-eslint/scope-manager@npm:5.59.1": + version: 5.59.1 + resolution: "@typescript-eslint/scope-manager@npm:5.59.1" dependencies: - "@typescript-eslint/types": 5.59.0 - "@typescript-eslint/visitor-keys": 5.59.0 - checksum: b53c9581daf3d6ac2ec5bd660d62c56ea77f71d77261a23bf21bc23a8140b5b7738304ace576b6af6e1d4ffc5170b7b6be7375da488e9e2997984011c509ead8 + "@typescript-eslint/types": 5.59.1 + "@typescript-eslint/visitor-keys": 5.59.1 + checksum: 0b661e8d7221b6f6c83029127ddfac811f857dacd4bf1d7c70d9ed3c6d5f862da9596f03947d6e9bce6f18ba26d310a07732f70450e16fbd70b54ac74e5df81f languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:5.59.0": - version: 5.59.0 - resolution: "@typescript-eslint/type-utils@npm:5.59.0" +"@typescript-eslint/type-utils@npm:5.59.1": + version: 5.59.1 + resolution: "@typescript-eslint/type-utils@npm:5.59.1" dependencies: - "@typescript-eslint/typescript-estree": 5.59.0 - "@typescript-eslint/utils": 5.59.0 + "@typescript-eslint/typescript-estree": 5.59.1 + "@typescript-eslint/utils": 5.59.1 debug: ^4.3.4 tsutils: ^3.21.0 peerDependencies: @@ -1544,23 +1544,23 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 8675af740e89ab15e10ef1938530dc14595f45ebaa8b57375e931a4e8b42d71cff8ddfacf71f20a333532a0dbe593eff6d59eb5d43b73c9dd19ad7439a30f443 + checksum: fe4ab0609529d2bc2d1a1a6f0aed667448342194c81bf2766b6f015086c37679da57ac9392489f0bd734e7cb49609353b580de96e88b4968ccf3ab7d203aa8ca languageName: node linkType: hard -"@typescript-eslint/types@npm:5.59.0": - version: 5.59.0 - resolution: "@typescript-eslint/types@npm:5.59.0" - checksum: f756843a49b418a23674842d356aaef14e7373e7df80729e64cf23b3fc7c9d9ab4f0a764b41555236af7821cd1d3c0efcc9a3c97b778f0b67b6dbbd9c5e852cc +"@typescript-eslint/types@npm:5.59.1": + version: 5.59.1 + resolution: "@typescript-eslint/types@npm:5.59.1" + checksum: 28c128906bf7a2aaef48db056f75db494007047e60b1bfb9f2dc663aaf5d70f34f4cef51bf4330194cb83144156131aa825e321253519aea1f08f8405d7a0b78 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.59.0": - version: 5.59.0 - resolution: "@typescript-eslint/typescript-estree@npm:5.59.0" +"@typescript-eslint/typescript-estree@npm:5.59.1": + version: 5.59.1 + resolution: "@typescript-eslint/typescript-estree@npm:5.59.1" dependencies: - "@typescript-eslint/types": 5.59.0 - "@typescript-eslint/visitor-keys": 5.59.0 + "@typescript-eslint/types": 5.59.1 + "@typescript-eslint/visitor-keys": 5.59.1 debug: ^4.3.4 globby: ^11.1.0 is-glob: ^4.0.3 @@ -1569,35 +1569,35 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 2e677677927721d0db286f2f2e0263d5b8ae06072f217fc2fd17c96c347f8cec2201dccaf393c41e6f4b2a7c3e2b7ca6ab8a27283e76c6ec5576f53d1d26a0b6 + checksum: 856bcc61c8ec69c979f139ad1bfff965d1f1fe72bfcedee8a62be2b24c5b8b1a1bcd874e83b7f235cd5cadf88936da203064f64dd99de0aa63697228e8109c6f languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.59.0": - version: 5.59.0 - resolution: "@typescript-eslint/utils@npm:5.59.0" +"@typescript-eslint/utils@npm:5.59.1": + version: 5.59.1 + resolution: "@typescript-eslint/utils@npm:5.59.1" dependencies: "@eslint-community/eslint-utils": ^4.2.0 "@types/json-schema": ^7.0.9 "@types/semver": ^7.3.12 - "@typescript-eslint/scope-manager": 5.59.0 - "@typescript-eslint/types": 5.59.0 - "@typescript-eslint/typescript-estree": 5.59.0 + "@typescript-eslint/scope-manager": 5.59.1 + "@typescript-eslint/types": 5.59.1 + "@typescript-eslint/typescript-estree": 5.59.1 eslint-scope: ^5.1.1 semver: ^7.3.7 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 653ea4032b51c8b3bdc386971cb437f59fc20a8df5ca8d11ef6c917e6376df26c73cfd18cbeee8c8818ba4350a8cbd87d0ea9ec5242e35c5d26059a11476bc13 + checksum: 366fcca9bb39ed74a5fac696fda1e12dc8ef9b0c6bc84afcf2da738052ff0921513fccccae7df219bf8f2fd3a81a0438cba70aedbe0b0545f1d47fbf9d766f30 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:5.59.0": - version: 5.59.0 - resolution: "@typescript-eslint/visitor-keys@npm:5.59.0" +"@typescript-eslint/visitor-keys@npm:5.59.1": + version: 5.59.1 + resolution: "@typescript-eslint/visitor-keys@npm:5.59.1" dependencies: - "@typescript-eslint/types": 5.59.0 + "@typescript-eslint/types": 5.59.1 eslint-visitor-keys: ^3.3.0 - checksum: 184a23424a6bf7ea48f700a71461d3a89270e8af32db6f1fcc5367834818c9e8bc4b57853a15b6a9d44297c064d04d08583815fd8f2019135c0bc197d60a6c0c + checksum: f2d48ba4adf19f6b34306b281886e0dfc8bd7b3a6ecf5f65ff2bd104aa01f3b706904a6fd5b8f97eddec11d503d81275ee946f418ce3ee27c947826b2e7aaccf languageName: node linkType: hard @@ -1749,8 +1749,8 @@ __metadata: "@types/react-dom": ^18.0.11 "@types/react-router-dom": ^5.3.3 "@types/styled-components": ^5 - "@typescript-eslint/eslint-plugin": ^5.59.0 - "@typescript-eslint/parser": ^5.59.0 + "@typescript-eslint/eslint-plugin": ^5.59.1 + "@typescript-eslint/parser": ^5.59.1 "@vitejs/plugin-react-swc": ^3.3.0 "@yarnpkg/pnpify": ^4.0.0-rc.42 async-validator: ^4.2.5 From 41b2a67f09db629790c2e712d48b8b8fd5c31c9c Mon Sep 17 00:00:00 2001 From: Proddy Date: Mon, 24 Apr 2023 21:38:21 +0200 Subject: [PATCH 33/89] remove hidden spaces --- platformio.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platformio.ini b/platformio.ini index 02ab00239..6fabde841 100644 --- a/platformio.ini +++ b/platformio.ini @@ -7,7 +7,7 @@ default_envs = esp32_4M ; default_envs = esp32_16M ; default_envs = standalone -extra_configs = +extra_configs = factory_settings.ini pio_local.ini @@ -19,8 +19,8 @@ core_build_flags = -D ESP32=1 ; -std=gnu++17 -; core_unbuild_flags = -std=gnu++11 -; core_unbuild_flags = -std=gnu++17 +; core_unbuild_flags = -std=gnu++11 +; core_unbuild_flags = -std=gnu++17 core_unbuild_flags = ; my_build_flags is set in pio_local.ini @@ -67,7 +67,7 @@ framework = arduino extra_scripts = scripts/rename_fw.py board = esp32dev board_build.partitions = esp32_partition_4M.csv -board_build.filesystem = littlefs +board_build.filesystem = littlefs build_flags = ${common.build_flags} build_unflags = ${common.unbuild_flags} From 67de1c6a4715d27bb27b73d2e3b4229b5b13c003 Mon Sep 17 00:00:00 2001 From: Proddy Date: Mon, 24 Apr 2023 21:38:35 +0200 Subject: [PATCH 34/89] add one more line to box so it fits 'patch' --- src/console.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/console.cpp b/src/console.cpp index e7e4ff3fc..1326ea14c 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -619,12 +619,12 @@ void EMSESPShell::stopped() { // show welcome banner void EMSESPShell::display_banner() { println(); - printfln("┌──────────────────────────────────────┐"); - printfln("│ %sEMS-ESP version %-12s%s │", COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_BOLD_OFF); - printfln("│ %s%shttps://github.com/emsesp/EMS-ESP32%s │", COLOR_BRIGHT_GREEN, COLOR_UNDERLINE, COLOR_RESET); - printfln("│ │"); - printfln("│ type %shelp%s to show available commands │", COLOR_UNDERLINE, COLOR_RESET); - printfln("└──────────────────────────────────────┘"); + printfln("┌───────────────────────────────────────┐"); + printfln("│ %sEMS-ESP version %-12s%s │", COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_BOLD_OFF); + printfln("│ %s%shttps://github.com/emsesp/EMS-ESP32%s │", COLOR_BRIGHT_GREEN, COLOR_UNDERLINE, COLOR_RESET); + printfln("│ │"); + printfln("│ type %shelp%s to show available commands │", COLOR_UNDERLINE, COLOR_RESET); + printfln("└───────────────────────────────────────┘"); println(); // set console name From f1b3cb9646359236d4b18d10e5a4c97aaa1b7cd0 Mon Sep 17 00:00:00 2001 From: Proddy Date: Mon, 24 Apr 2023 21:38:52 +0200 Subject: [PATCH 35/89] added test for bus disconnect --- mock-api/server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/mock-api/server.js b/mock-api/server.js index a2ccd127a..d2323cd76 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -444,6 +444,7 @@ const emsesp_sensordata = { const status = { status: 0, + // status: 2, tx_mode: 1, uptime: 77186, num_devices: 2, From 7d526196a05701f5286812622a47403df63121b7 Mon Sep 17 00:00:00 2001 From: Proddy Date: Mon, 24 Apr 2023 21:39:16 +0200 Subject: [PATCH 36/89] don't show uptime if disconnected --- interface/src/project/DashboardStatus.tsx | 60 +++++++++++------------ 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/interface/src/project/DashboardStatus.tsx b/interface/src/project/DashboardStatus.tsx index 13055245a..ff3570d5d 100644 --- a/interface/src/project/DashboardStatus.tsx +++ b/interface/src/project/DashboardStatus.tsx @@ -73,24 +73,6 @@ const DashboardStatus: FC = () => { const { me } = useContext(AuthenticatedContext); - const showName = (id: any) => { - const name: keyof Translation['STATUS_NAMES'] = id; - return LL.STATUS_NAMES[name](); - }; - - const busStatus = ({ status }: Status) => { - switch (status) { - case busConnectionStatus.BUS_STATUS_CONNECTED: - return LL.CONNECTED(0); - case busConnectionStatus.BUS_STATUS_TX_ERRORS: - return LL.TX_ISSUES(); - case busConnectionStatus.BUS_STATUS_OFFLINE: - return LL.DISCONNECTED(); - default: - return 'Unknown'; - } - }; - const stats_theme = tableTheme({ Table: ` --data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 90px 90px 80px; @@ -137,15 +119,9 @@ const DashboardStatus: FC = () => { // eslint-disable-next-line }, []); - const scan = async () => { - try { - await EMSESP.scanDevices(); - toast.info(LL.SCANNING() + '...'); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - } finally { - setConfirmScan(false); - } + const showName = (id: any) => { + const name: keyof Translation['STATUS_NAMES'] = id; + return LL.STATUS_NAMES[name](); }; const formatDurationSec = (duration_sec: number) => { @@ -168,6 +144,31 @@ const DashboardStatus: FC = () => { return formatted; }; + const busStatus = () => { + if (data) { + switch (data.status) { + case busConnectionStatus.BUS_STATUS_CONNECTED: + return LL.CONNECTED(0) + ' (' + formatDurationSec(data.uptime) + ')'; + case busConnectionStatus.BUS_STATUS_TX_ERRORS: + return LL.TX_ISSUES(); + case busConnectionStatus.BUS_STATUS_OFFLINE: + return LL.DISCONNECTED(); + } + } + return 'Unknown'; + }; + + const scan = async () => { + try { + await EMSESP.scanDevices(); + toast.info(LL.SCANNING() + '...'); + } catch (error) { + toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); + } finally { + setConfirmScan(false); + } + }; + const renderScanDialog = () => ( setConfirmScan(false)}> {LL.SCAN_DEVICES()} @@ -197,10 +198,7 @@ const DashboardStatus: FC = () => { - + From b59c76df3b8cc9bcf4c3603cb5a46665d90edd9c Mon Sep 17 00:00:00 2001 From: Proddy Date: Mon, 24 Apr 2023 21:39:30 +0200 Subject: [PATCH 37/89] replace void with await --- interface/src/SignIn.tsx | 2 +- interface/src/framework/ap/APSettingsForm.tsx | 2 +- interface/src/framework/mqtt/MqttSettingsForm.tsx | 2 +- interface/src/framework/network/NetworkSettingsForm.tsx | 2 +- interface/src/framework/ntp/NTPSettingsForm.tsx | 2 +- interface/src/framework/ntp/NTPStatusForm.tsx | 2 +- interface/src/framework/security/ManageUsersForm.tsx | 2 +- interface/src/framework/system/OTASettingsForm.tsx | 2 +- interface/src/project/DashboardData.tsx | 6 +++--- interface/src/project/SettingsApplication.tsx | 4 ++-- interface/src/project/SettingsEntities.tsx | 2 +- interface/src/project/SettingsScheduler.tsx | 2 +- 12 files changed, 15 insertions(+), 15 deletions(-) diff --git a/interface/src/SignIn.tsx b/interface/src/SignIn.tsx index c3bf7134f..0cca183c9 100644 --- a/interface/src/SignIn.tsx +++ b/interface/src/SignIn.tsx @@ -63,7 +63,7 @@ const SignIn: FC = () => { }); try { await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest); - void signIn(); + await signIn(); } catch (errors: any) { setFieldErrors(errors); setProcessing(false); diff --git a/interface/src/framework/ap/APSettingsForm.tsx b/interface/src/framework/ap/APSettingsForm.tsx index 87906481c..48da99907 100644 --- a/interface/src/framework/ap/APSettingsForm.tsx +++ b/interface/src/framework/ap/APSettingsForm.tsx @@ -49,7 +49,7 @@ const APSettingsForm: FC = () => { try { setFieldErrors(undefined); await validate(createAPSettingsValidator(data), data); - void saveData(); + await saveData(); } catch (errors: any) { setFieldErrors(errors); } diff --git a/interface/src/framework/mqtt/MqttSettingsForm.tsx b/interface/src/framework/mqtt/MqttSettingsForm.tsx index cd81d76fe..b2deb9d25 100644 --- a/interface/src/framework/mqtt/MqttSettingsForm.tsx +++ b/interface/src/framework/mqtt/MqttSettingsForm.tsx @@ -43,7 +43,7 @@ const MqttSettingsForm: FC = () => { try { setFieldErrors(undefined); await validate(createMqttSettingsValidator(data), data); - void saveData(); + await saveData(); } catch (errors: any) { setFieldErrors(errors); } diff --git a/interface/src/framework/network/NetworkSettingsForm.tsx b/interface/src/framework/network/NetworkSettingsForm.tsx index ff221b8cb..308121075 100644 --- a/interface/src/framework/network/NetworkSettingsForm.tsx +++ b/interface/src/framework/network/NetworkSettingsForm.tsx @@ -104,7 +104,7 @@ const WiFiSettingsForm: FC = () => { try { setFieldErrors(undefined); await validate(createNetworkSettingsValidator(data), data); - void saveData(); + await saveData(); } catch (errors: any) { setFieldErrors(errors); } diff --git a/interface/src/framework/ntp/NTPSettingsForm.tsx b/interface/src/framework/ntp/NTPSettingsForm.tsx index 238bb0daa..cf450ffe0 100644 --- a/interface/src/framework/ntp/NTPSettingsForm.tsx +++ b/interface/src/framework/ntp/NTPSettingsForm.tsx @@ -43,7 +43,7 @@ const NTPSettingsForm: FC = () => { try { setFieldErrors(undefined); await validate(NTP_SETTINGS_VALIDATOR, data); - void saveData(); + await saveData(); } catch (errors: any) { setFieldErrors(errors); } diff --git a/interface/src/framework/ntp/NTPStatusForm.tsx b/interface/src/framework/ntp/NTPStatusForm.tsx index 4c70be20e..51fb5a640 100644 --- a/interface/src/framework/ntp/NTPStatusForm.tsx +++ b/interface/src/framework/ntp/NTPStatusForm.tsx @@ -90,7 +90,7 @@ const NTPStatusForm: FC = () => { }); toast.success(LL.TIME_SET()); setSettingTime(false); - void loadData(); + await loadData(); } catch (error) { toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); } finally { diff --git a/interface/src/framework/security/ManageUsersForm.tsx b/interface/src/framework/security/ManageUsersForm.tsx index b8ef54804..e4aa4862a 100644 --- a/interface/src/framework/security/ManageUsersForm.tsx +++ b/interface/src/framework/security/ManageUsersForm.tsx @@ -123,7 +123,7 @@ const ManageUsersForm: FC = () => { const onSubmit = async () => { await saveData(); - void authenticatedContext.refresh(); + await authenticatedContext.refresh(); }; const user_table = data.users.map((u) => ({ ...u, id: u.username })); diff --git a/interface/src/framework/system/OTASettingsForm.tsx b/interface/src/framework/system/OTASettingsForm.tsx index 3447ffbe7..1b9cd033a 100644 --- a/interface/src/framework/system/OTASettingsForm.tsx +++ b/interface/src/framework/system/OTASettingsForm.tsx @@ -45,7 +45,7 @@ const OTASettingsForm: FC = () => { try { setFieldErrors(undefined); await validate(OTA_SETTINGS_VALIDATOR, data); - void saveData(); + await saveData(); } catch (errors: any) { setFieldErrors(errors); } diff --git a/interface/src/project/DashboardData.tsx b/interface/src/project/DashboardData.tsx index ce415cd55..4af8b8673 100644 --- a/interface/src/project/DashboardData.tsx +++ b/interface/src/project/DashboardData.tsx @@ -547,7 +547,7 @@ const DashboardData: FC = () => { toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); } finally { setSensor(undefined); - void fetchSensorData(); + await fetchSensorData(); } } }; @@ -975,7 +975,7 @@ const DashboardData: FC = () => { toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); } finally { setAnalog(undefined); - void fetchSensorData(); + await fetchSensorData(); } } }; @@ -1003,7 +1003,7 @@ const DashboardData: FC = () => { toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); } finally { setAnalog(undefined); - void fetchSensorData(); + await fetchSensorData(); } } }; diff --git a/interface/src/project/SettingsApplication.tsx b/interface/src/project/SettingsApplication.tsx index e8a820ea3..933167329 100644 --- a/interface/src/project/SettingsApplication.tsx +++ b/interface/src/project/SettingsApplication.tsx @@ -95,7 +95,7 @@ const SettingsApplication: FC = () => { try { setFieldErrors(undefined); await validate(createSettingsValidator(data), data); - void saveData(); + await saveData(); } catch (errors: any) { setFieldErrors(errors); } @@ -115,7 +115,7 @@ const SettingsApplication: FC = () => { }; const restart = async () => { - void validateAndSubmit(); + await validateAndSubmit(); try { await EMSESP.restart(); setRestarting(true); diff --git a/interface/src/project/SettingsEntities.tsx b/interface/src/project/SettingsEntities.tsx index aafa602ee..0ef989c4f 100644 --- a/interface/src/project/SettingsEntities.tsx +++ b/interface/src/project/SettingsEntities.tsx @@ -156,7 +156,7 @@ const SettingsEntities: FC = () => { } else { toast.error(LL.PROBLEM_UPDATING()); } - void fetchEntities(); + await fetchEntities(); } catch (error) { toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); } diff --git a/interface/src/project/SettingsScheduler.tsx b/interface/src/project/SettingsScheduler.tsx index 81aa680f7..9a39231e6 100644 --- a/interface/src/project/SettingsScheduler.tsx +++ b/interface/src/project/SettingsScheduler.tsx @@ -149,7 +149,7 @@ const SettingsScheduler: FC = () => { } else { toast.error(LL.PROBLEM_UPDATING()); } - void fetchSchedule(); + await fetchSchedule(); } catch (error) { toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); } From b9402d3a012162c789b1922f640fcf0cb2fb9ca7 Mon Sep 17 00:00:00 2001 From: Proddy Date: Mon, 24 Apr 2023 21:56:40 +0200 Subject: [PATCH 38/89] remove unused libs --- interface/package.json | 5 - interface/yarn.lock | 718 ++--------------------------------------- 2 files changed, 24 insertions(+), 699 deletions(-) diff --git a/interface/package.json b/interface/package.json index 4c221f7c1..1a2472c86 100644 --- a/interface/package.json +++ b/interface/package.json @@ -32,13 +32,11 @@ "@types/react": "^18.0.38", "@types/react-dom": "^18.0.11", "@types/react-router-dom": "^5.3.3", - "@yarnpkg/pnpify": "^4.0.0-rc.42", "async-validator": "^4.2.5", "axios": "^1.3.6", "history": "^5.3.0", "jwt-decode": "^3.1.2", "lodash-es": "^4.17.21", - "mime-types": "^2.1.35", "react": "latest", "react-dom": "latest", "react-dropzone": "^14.2.3", @@ -50,8 +48,6 @@ "typescript": "^5.0.4" }, "devDependencies": { - "@types/mime-types": "^2", - "@types/styled-components": "^5", "@typescript-eslint/eslint-plugin": "^5.59.1", "@typescript-eslint/parser": "^5.59.1", "@vitejs/plugin-react-swc": "^3.3.0", @@ -72,7 +68,6 @@ "rollup-plugin-visualizer": "^5.9.0", "terser": "^5.17.1", "vite": "^4.3.1", - "vite-plugin-minify": "^1.5.2", "vite-plugin-svgr": "^2.4.0", "vite-tsconfig-paths": "^4.2.0" }, diff --git a/interface/yarn.lock b/interface/yarn.lock index d98855548..be425818b 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -15,15 +15,6 @@ __metadata: languageName: node linkType: hard -"@arcanis/slice-ansi@npm:^1.1.1": - version: 1.1.1 - resolution: "@arcanis/slice-ansi@npm:1.1.1" - dependencies: - grapheme-splitter: ^1.0.4 - checksum: 2f222b121b8aaf67e8495e27d60ebfc34e2472033445c3380e93fb06aba9bfef6ab3096aca190a181b3dd505ed4c07f4dc7243fc9cb5369008b649cd1e39e8d8 - languageName: node - linkType: hard - "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.18.6, @babel/code-frame@npm:^7.21.4": version: 7.21.4 resolution: "@babel/code-frame@npm:7.21.4" @@ -966,13 +957,6 @@ __metadata: languageName: node linkType: hard -"@sindresorhus/is@npm:^4.0.0": - version: 4.6.0 - resolution: "@sindresorhus/is@npm:4.6.0" - checksum: 33b6fb1d0834ec8dd7689ddc0e2781c2bfd8b9c4e4bacbcb14111e0ae00621f2c264b8a7d36541799d74888b5dccdf422a891a5cb5a709ace26325eedc81e22e - languageName: node - linkType: hard - "@svgr/babel-plugin-add-jsx-attribute@npm:^6.5.1": version: 6.5.1 resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:6.5.1" @@ -1214,15 +1198,6 @@ __metadata: languageName: node linkType: hard -"@szmarczak/http-timer@npm:^4.0.5": - version: 4.0.6 - resolution: "@szmarczak/http-timer@npm:4.0.6" - dependencies: - defer-to-connect: ^2.0.0 - checksum: 73946918c025339db68b09abd91fa3001e87fc749c619d2e9c2003a663039d4c3cb89836c98a96598b3d47dec2481284ba85355392644911f5ecd2336536697f - languageName: node - linkType: hard - "@table-library/react-table-library@npm:4.1.0": version: 4.1.0 resolution: "@table-library/react-table-library@npm:4.1.0" @@ -1245,25 +1220,6 @@ __metadata: languageName: node linkType: hard -"@types/cacheable-request@npm:^6.0.1": - version: 6.0.3 - resolution: "@types/cacheable-request@npm:6.0.3" - dependencies: - "@types/http-cache-semantics": "*" - "@types/keyv": ^3.1.4 - "@types/node": "*" - "@types/responselike": ^1.0.0 - checksum: 10816a88e4e5b144d43c1d15a81003f86d649776c7f410c9b5e6579d0ad9d4ca71c541962fb403077388b446e41af7ae38d313e46692144985f006ac5e11fa03 - languageName: node - linkType: hard - -"@types/emscripten@npm:^1.39.6": - version: 1.39.6 - resolution: "@types/emscripten@npm:1.39.6" - checksum: cb1ea8ccddada1d304bdf11a54daa0d1e87f29cea947eceff54c1e0a752d2cc185eeffdcf52042f24420aa8e1fa9bbfdbab1231fb2531eefcfdc788199fee2de - languageName: node - linkType: hard - "@types/estree@npm:^1.0.0": version: 1.0.0 resolution: "@types/estree@npm:1.0.0" @@ -1278,23 +1234,6 @@ __metadata: languageName: node linkType: hard -"@types/hoist-non-react-statics@npm:*": - version: 3.3.1 - resolution: "@types/hoist-non-react-statics@npm:3.3.1" - dependencies: - "@types/react": "*" - hoist-non-react-statics: ^3.3.0 - checksum: 5ed808e5fbf0979fe07acd631147420c30319383f4388a57e0fb811c6ff30abef286e937a84c7b00f4647ca7f1ab390cc42af0bfc7547a87d2e59e0e7072d92b - languageName: node - linkType: hard - -"@types/http-cache-semantics@npm:*": - version: 4.0.1 - resolution: "@types/http-cache-semantics@npm:4.0.1" - checksum: 6d6068110a04cac213bdc0fff9c7bac028b5a2da390492204328987d8ddc500adc10d9cf5747a6333dab261712655dcfe120ea1d5527c205d012a39cdccc2a7b - languageName: node - linkType: hard - "@types/json-schema@npm:^7.0.9": version: 7.0.11 resolution: "@types/json-schema@npm:7.0.11" @@ -1309,15 +1248,6 @@ __metadata: languageName: node linkType: hard -"@types/keyv@npm:^3.1.4": - version: 3.1.4 - resolution: "@types/keyv@npm:3.1.4" - dependencies: - "@types/node": "*" - checksum: ff8f54fc49621210291f815fe5b15d809fd7d032941b3180743440bd507ecdf08b9e844625fa346af568c84bf34114eb378dcdc3e921a08ba1e2a08d7e3c809c - languageName: node - linkType: hard - "@types/lodash-es@npm:^4.17.7": version: 4.17.7 resolution: "@types/lodash-es@npm:4.17.7" @@ -1334,20 +1264,6 @@ __metadata: languageName: node linkType: hard -"@types/mime-types@npm:^2": - version: 2.1.1 - resolution: "@types/mime-types@npm:2.1.1" - checksum: 131b33bfd89481f6a791996db9198c6c5ffccbb310e990d1dd9fab7a2287b5a0fd642bdd959a19281397c86f721498e09956e3892e5db17f93f38e726ca05008 - languageName: node - linkType: hard - -"@types/node@npm:*, @types/node@npm:^18.11.11": - version: 18.15.11 - resolution: "@types/node@npm:18.15.11" - checksum: 670deb1a9daa812dc86b1e8964c0c6b0bef7c32672833c10578c1e5dd2682f2bd99b86d814fde86a5dd4a3da48ea039f41db30a835b245aa7c34c62fa1f23f0d - languageName: node - linkType: hard - "@types/node@npm:^18.16.0": version: 18.16.0 resolution: "@types/node@npm:18.16.0" @@ -1439,15 +1355,6 @@ __metadata: languageName: node linkType: hard -"@types/responselike@npm:^1.0.0": - version: 1.0.0 - resolution: "@types/responselike@npm:1.0.0" - dependencies: - "@types/node": "*" - checksum: 474ac2402e6d43c007eee25f50d01eb1f67255ca83dd8e036877292bbe8dd5d2d1e50b54b408e233b50a8c38e681ff3ebeaf22f18b478056eddb65536abb003a - languageName: node - linkType: hard - "@types/scheduler@npm:*": version: 0.16.3 resolution: "@types/scheduler@npm:0.16.3" @@ -1455,31 +1362,13 @@ __metadata: languageName: node linkType: hard -"@types/semver@npm:^7.1.0, @types/semver@npm:^7.3.12": +"@types/semver@npm:^7.3.12": version: 7.3.13 resolution: "@types/semver@npm:7.3.13" checksum: 73295bb1fee46f8c76c7a759feeae5a3022f5bedfdc17d16982092e4b33af17560234fb94861560c20992a702a1e1b9a173bb623a96f95f80892105f5e7d25e3 languageName: node linkType: hard -"@types/styled-components@npm:^5": - version: 5.1.26 - resolution: "@types/styled-components@npm:5.1.26" - dependencies: - "@types/hoist-non-react-statics": "*" - "@types/react": "*" - csstype: ^3.0.2 - checksum: 61c53b035d82bbf6071d3f15348f2e9a43af8c28c630ab472d153277082a578aa60116ddc67bcfea1340d93577d5758c359f0a4d4d1291a419cebb3f8677b63e - languageName: node - linkType: hard - -"@types/treeify@npm:^1.0.0": - version: 1.0.0 - resolution: "@types/treeify@npm:1.0.0" - checksum: 8a279d0f1897e47cc02b4b5a570141ab70de6bc5d95cafe976aaee78740c13c2e80dae69f7ae9ca1c735c653b65a4ec59a7eed6970683cd04fc0ddf4b98794ff - languageName: node - linkType: hard - "@typescript-eslint/eslint-plugin@npm:^5.59.1": version: 5.59.1 resolution: "@typescript-eslint/eslint-plugin@npm:5.59.1" @@ -1612,125 +1501,6 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/core@npm:^4.0.0-rc.42": - version: 4.0.0-rc.42 - resolution: "@yarnpkg/core@npm:4.0.0-rc.42" - dependencies: - "@arcanis/slice-ansi": ^1.1.1 - "@types/semver": ^7.1.0 - "@types/treeify": ^1.0.0 - "@yarnpkg/fslib": ^3.0.0-rc.42 - "@yarnpkg/libzip": ^3.0.0-rc.42 - "@yarnpkg/parsers": ^3.0.0-rc.42 - "@yarnpkg/shell": ^4.0.0-rc.42 - camelcase: ^5.3.1 - chalk: ^3.0.0 - ci-info: ^3.2.0 - clipanion: ^3.2.0-rc.10 - cross-spawn: 7.0.3 - diff: ^5.1.0 - globby: ^11.0.1 - got: ^11.7.0 - lodash: ^4.17.15 - micromatch: ^4.0.2 - p-limit: ^2.2.0 - semver: ^7.1.2 - strip-ansi: ^6.0.0 - tar: ^6.0.5 - tinylogic: ^2.0.0 - treeify: ^1.1.0 - tslib: ^2.4.0 - tunnel: ^0.0.6 - checksum: ccd1cd5d22c7ab93a9f3b05937295547ea8aa7a86d47a506bf4fa89fde60231147075e5d70577de44765c05b1b4399214243663867c1505f627d460b3c9422c9 - languageName: node - linkType: hard - -"@yarnpkg/fslib@npm:^3.0.0-rc.42": - version: 3.0.0-rc.42 - resolution: "@yarnpkg/fslib@npm:3.0.0-rc.42" - dependencies: - tslib: ^2.4.0 - checksum: de0a5cff0c92e9c0201f8908c98468e4ee7a2144673b7a945a1966fc6cfc8b0680e6863d6403f7cc1a86a036e7c9d2422b9b3a53353333a08ec037df8049f77c - languageName: node - linkType: hard - -"@yarnpkg/libzip@npm:^3.0.0-rc.42": - version: 3.0.0-rc.42 - resolution: "@yarnpkg/libzip@npm:3.0.0-rc.42" - dependencies: - "@types/emscripten": ^1.39.6 - "@yarnpkg/fslib": ^3.0.0-rc.42 - tslib: ^2.4.0 - peerDependencies: - "@yarnpkg/fslib": ^3.0.0-rc.42 - checksum: 9c6fd61b1e9fc51bf71ed96e854ac4ae10def0902a810a091d079b52fca2f20e7318f478178c5c09b39a44d575024eed7b977776ae5b70ed01a0566a66acb7c5 - languageName: node - linkType: hard - -"@yarnpkg/nm@npm:^4.0.0-rc.42": - version: 4.0.0-rc.42 - resolution: "@yarnpkg/nm@npm:4.0.0-rc.42" - dependencies: - "@yarnpkg/core": ^4.0.0-rc.42 - "@yarnpkg/fslib": ^3.0.0-rc.42 - "@yarnpkg/pnp": ^4.0.0-rc.42 - checksum: 976f25ae10528333f0fb096979d674e78778e95d6f75722b19f0872010316f955747a234363f03d80d0d82ffbd69c7549d03f260bc59d05aadcd2bd5529a065b - languageName: node - linkType: hard - -"@yarnpkg/parsers@npm:^3.0.0-rc.42": - version: 3.0.0-rc.42 - resolution: "@yarnpkg/parsers@npm:3.0.0-rc.42" - dependencies: - js-yaml: ^3.10.0 - tslib: ^2.4.0 - checksum: 31ffaecb01c903331063168e58cf9d6358bee4baecbc53a04c75a16d9403e477a9ac02c18924acfed54cb6164cd38a9858434d925baf0f4ef2b3d3e9672b4353 - languageName: node - linkType: hard - -"@yarnpkg/pnp@npm:^4.0.0-rc.42": - version: 4.0.0-rc.42 - resolution: "@yarnpkg/pnp@npm:4.0.0-rc.42" - dependencies: - "@types/node": ^18.11.11 - "@yarnpkg/fslib": ^3.0.0-rc.42 - checksum: 48d3946ae21fba474fad093d82c92699cb30f4457779607223054da049d66446f40c2e8008dcc3d0a5522a29e5c97de821b8258a75b0ca0e93d713892fc93642 - languageName: node - linkType: hard - -"@yarnpkg/pnpify@npm:^4.0.0-rc.42": - version: 4.0.0-rc.42 - resolution: "@yarnpkg/pnpify@npm:4.0.0-rc.42" - dependencies: - "@yarnpkg/core": ^4.0.0-rc.42 - "@yarnpkg/fslib": ^3.0.0-rc.42 - "@yarnpkg/nm": ^4.0.0-rc.42 - clipanion: ^3.2.0-rc.10 - tslib: ^2.4.0 - bin: - pnpify: ./lib/cli.js - checksum: 8f9b6495122eb337fbe602fcc124f0a4560723f831e180e8149658117bcbe6c957fe7169237b72e9caa686e41e3678c34015b51e7643618f1261618dc73409e6 - languageName: node - linkType: hard - -"@yarnpkg/shell@npm:^4.0.0-rc.42": - version: 4.0.0-rc.42 - resolution: "@yarnpkg/shell@npm:4.0.0-rc.42" - dependencies: - "@yarnpkg/fslib": ^3.0.0-rc.42 - "@yarnpkg/parsers": ^3.0.0-rc.42 - chalk: ^3.0.0 - clipanion: ^3.2.0-rc.10 - cross-spawn: 7.0.3 - fast-glob: ^3.2.2 - micromatch: ^4.0.2 - tslib: ^2.4.0 - bin: - shell: ./lib/cli.js - checksum: def47161092a303222398dd85f4f8fc651aa529875d2176349e136e22a30e84b05a97a45a90ed09b0e605b4d0f4980b163a67008571f4f6517f648ebaf1f49ff - languageName: node - linkType: hard - "EMS-ESP@workspace:.": version: 0.0.0-use.local resolution: "EMS-ESP@workspace:." @@ -1743,16 +1513,13 @@ __metadata: "@remix-run/router": ^1.5.0 "@table-library/react-table-library": 4.1.0 "@types/lodash-es": ^4.17.7 - "@types/mime-types": ^2 "@types/node": ^18.16.0 "@types/react": ^18.0.38 "@types/react-dom": ^18.0.11 "@types/react-router-dom": ^5.3.3 - "@types/styled-components": ^5 "@typescript-eslint/eslint-plugin": ^5.59.1 "@typescript-eslint/parser": ^5.59.1 "@vitejs/plugin-react-swc": ^3.3.0 - "@yarnpkg/pnpify": ^4.0.0-rc.42 async-validator: ^4.2.5 axios: ^1.3.6 eslint: ^8.39.0 @@ -1769,7 +1536,6 @@ __metadata: history: ^5.3.0 jwt-decode: ^3.1.2 lodash-es: ^4.17.21 - mime-types: ^2.1.35 nodemon: ^2.0.22 npm-run-all: ^4.1.5 prettier: ^2.8.8 @@ -1785,7 +1551,6 @@ __metadata: typesafe-i18n: ^5.24.3 typescript: ^5.0.4 vite: ^4.3.1 - vite-plugin-minify: ^1.5.2 vite-plugin-svgr: ^2.4.0 vite-tsconfig-paths: ^4.2.0 languageName: unknown @@ -1910,15 +1675,6 @@ __metadata: languageName: node linkType: hard -"argparse@npm:^1.0.7": - version: 1.0.10 - resolution: "argparse@npm:1.0.10" - dependencies: - sprintf-js: ~1.0.2 - checksum: b2972c5c23c63df66bca144dbc65d180efa74f25f8fd9b7d9a0a6c88ae839db32df3d54770dcb6460cf840d232b60695d1a6b1053f599d84e73f7437087712de - languageName: node - linkType: hard - "argparse@npm:^2.0.1": version: 2.0.1 resolution: "argparse@npm:2.0.1" @@ -2164,28 +1920,6 @@ __metadata: languageName: node linkType: hard -"cacheable-lookup@npm:^5.0.3": - version: 5.0.4 - resolution: "cacheable-lookup@npm:5.0.4" - checksum: a6547fb4954b318aa831cbdd2f7b376824bc784fb1fa67610e4147099e3074726072d9af89f12efb69121415a0e1f2918a8ddd4aafcbcf4e91fbeef4a59cd42c - languageName: node - linkType: hard - -"cacheable-request@npm:^7.0.2": - version: 7.0.2 - resolution: "cacheable-request@npm:7.0.2" - dependencies: - clone-response: ^1.0.2 - get-stream: ^5.1.0 - http-cache-semantics: ^4.0.0 - keyv: ^4.0.0 - lowercase-keys: ^2.0.0 - normalize-url: ^6.0.1 - responselike: ^2.0.0 - checksum: 681bad13691d0d5d10652d409374747a2ce8676f854b0d454ee8fc65e0a10a52ea83cd1f6c367ada08572fd4982f2aa2582dc38983d4e958e053e181c433765e - languageName: node - linkType: hard - "call-bind@npm:^1.0.0, call-bind@npm:^1.0.2": version: 1.0.2 resolution: "call-bind@npm:1.0.2" @@ -2203,23 +1937,6 @@ __metadata: languageName: node linkType: hard -"camel-case@npm:^4.1.2": - version: 4.1.2 - resolution: "camel-case@npm:4.1.2" - dependencies: - pascal-case: ^3.1.2 - tslib: ^2.0.3 - checksum: bf9eefaee1f20edbed2e9a442a226793bc72336e2b99e5e48c6b7252b6f70b080fc46d8246ab91939e2af91c36cdd422e0af35161e58dd089590f302f8f64c8a - languageName: node - linkType: hard - -"camelcase@npm:^5.3.1": - version: 5.3.1 - resolution: "camelcase@npm:5.3.1" - checksum: 92ff9b443bfe8abb15f2b1513ca182d16126359ad4f955ebc83dc4ddcc4ef3fdd2c078bc223f2673dc223488e75c99b16cc4d056624374b799e6a1555cf61b23 - languageName: node - linkType: hard - "camelcase@npm:^6.2.0": version: 6.3.0 resolution: "camelcase@npm:6.3.0" @@ -2245,16 +1962,6 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^3.0.0": - version: 3.0.0 - resolution: "chalk@npm:3.0.0" - dependencies: - ansi-styles: ^4.1.0 - supports-color: ^7.1.0 - checksum: ee650b0a065b3d7a6fda258e75d3a86fc8e4effa55871da730a9e42ccb035bf5fd203525e5a1ef45ec2582ecc4f65b47eb11357c526b84dd29a14fb162c414d2 - languageName: node - linkType: hard - "chalk@npm:^4.0.0": version: 4.1.2 resolution: "chalk@npm:4.1.2" @@ -2291,22 +1998,6 @@ __metadata: languageName: node linkType: hard -"ci-info@npm:^3.2.0": - version: 3.8.0 - resolution: "ci-info@npm:3.8.0" - checksum: 0d3052193b58356372b34ab40d2668c3e62f1006d5ca33726d1d3c423853b19a85508eadde7f5908496fb41448f465263bf61c1ee58b7832cb6a924537e3863a - languageName: node - linkType: hard - -"clean-css@npm:^5.2.2": - version: 5.3.2 - resolution: "clean-css@npm:5.3.2" - dependencies: - source-map: ~0.6.0 - checksum: 315e0e81306524bd2c1905fa6823bf7658be40799b78f446e5e6922808718d2b80266fb3e96842a06176fa683bc2c1a0d2827b08d154e2f9cf136d7bda909d33 - languageName: node - linkType: hard - "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -2314,17 +2005,6 @@ __metadata: languageName: node linkType: hard -"clipanion@npm:^3.2.0-rc.10": - version: 3.2.0 - resolution: "clipanion@npm:3.2.0" - dependencies: - typanion: ^3.8.0 - peerDependencies: - typanion: "*" - checksum: fcc3d44145bc0e162ccf856c27437c0770ba1be863d73296d54c352621accd7675bca33f0a1bed1b1cd7dd22c695abd35e4839da85f12fc1edb77a3f8aa100f4 - languageName: node - linkType: hard - "cliui@npm:^8.0.1": version: 8.0.1 resolution: "cliui@npm:8.0.1" @@ -2336,15 +2016,6 @@ __metadata: languageName: node linkType: hard -"clone-response@npm:^1.0.2": - version: 1.0.3 - resolution: "clone-response@npm:1.0.3" - dependencies: - mimic-response: ^1.0.0 - checksum: 06a2b611824efb128810708baee3bd169ec9a1bf5976a5258cd7eb3f7db25f00166c6eee5961f075c7e38e194f373d4fdf86b8166ad5b9c7e82bbd2e333a6087 - languageName: node - linkType: hard - "clsx@npm:1.1.1": version: 1.1.1 resolution: "clsx@npm:1.1.1" @@ -2416,13 +2087,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:^8.3.0": - version: 8.3.0 - resolution: "commander@npm:8.3.0" - checksum: 8b043bb8322ea1c39664a1598a95e0495bfe4ca2fad0d84a92d7d1d8d213e2a155b441d2470c8e08de7c4a28cf2bc6e169211c49e1b21d9f7edc6ae4d9356060 - languageName: node - linkType: hard - "concat-map@npm:0.0.1": version: 0.0.1 resolution: "concat-map@npm:0.0.1" @@ -2464,17 +2128,6 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:7.0.3, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": - version: 7.0.3 - resolution: "cross-spawn@npm:7.0.3" - dependencies: - path-key: ^3.1.0 - shebang-command: ^2.0.0 - which: ^2.0.1 - checksum: 5738c312387081c98d69c98e105b6327b069197f864a60593245d64c8089c8a0a744e16349281210d56835bb9274130d825a78b2ad6853ca13cfbeffc0c31750 - languageName: node - linkType: hard - "cross-spawn@npm:^6.0.5": version: 6.0.5 resolution: "cross-spawn@npm:6.0.5" @@ -2488,6 +2141,17 @@ __metadata: languageName: node linkType: hard +"cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": + version: 7.0.3 + resolution: "cross-spawn@npm:7.0.3" + dependencies: + path-key: ^3.1.0 + shebang-command: ^2.0.0 + which: ^2.0.1 + checksum: 5738c312387081c98d69c98e105b6327b069197f864a60593245d64c8089c8a0a744e16349281210d56835bb9274130d825a78b2ad6853ca13cfbeffc0c31750 + languageName: node + linkType: hard + "csstype@npm:^3.0.2, csstype@npm:^3.1.2": version: 3.1.2 resolution: "csstype@npm:3.1.2" @@ -2523,15 +2187,6 @@ __metadata: languageName: node linkType: hard -"decompress-response@npm:^6.0.0": - version: 6.0.0 - resolution: "decompress-response@npm:6.0.0" - dependencies: - mimic-response: ^3.1.0 - checksum: bd89d23141b96d80577e70c54fb226b2f40e74a6817652b80a116d7befb8758261ad073a8895648a29cc0a5947021ab66705cb542fa9c143c82022b27c5b175e - languageName: node - linkType: hard - "deep-equal@npm:^2.0.5": version: 2.2.0 resolution: "deep-equal@npm:2.2.0" @@ -2564,13 +2219,6 @@ __metadata: languageName: node linkType: hard -"defer-to-connect@npm:^2.0.0": - version: 2.0.1 - resolution: "defer-to-connect@npm:2.0.1" - checksum: 625ce28e1b5ad10cf77057b9a6a727bf84780c17660f6644dab61dd34c23de3001f03cedc401f7d30a4ed9965c2e8a7336e220a329146f2cf85d4eddea429782 - languageName: node - linkType: hard - "define-lazy-prop@npm:^2.0.0": version: 2.0.0 resolution: "define-lazy-prop@npm:2.0.0" @@ -2609,13 +2257,6 @@ __metadata: languageName: node linkType: hard -"diff@npm:^5.1.0": - version: 5.1.0 - resolution: "diff@npm:5.1.0" - checksum: 77a0d9beb9ed54796154ac2511872288432124ac90a1cabb1878783c9b4d81f1847f3b746a0630b1e836181461d2c76e1e6b95559bef86ed16294d114862e364 - languageName: node - linkType: hard - "dir-glob@npm:^3.0.1": version: 3.0.1 resolution: "dir-glob@npm:3.0.1" @@ -2653,16 +2294,6 @@ __metadata: languageName: node linkType: hard -"dot-case@npm:^3.0.4": - version: 3.0.4 - resolution: "dot-case@npm:3.0.4" - dependencies: - no-case: ^3.0.4 - tslib: ^2.0.3 - checksum: 5b859ea65097a7ea870e2c91b5768b72ddf7fa947223fd29e167bcdff58fe731d941c48e47a38ec8aa8e43044c8fbd15cd8fa21689a526bc34b6548197cd5b05 - languageName: node - linkType: hard - "electron-to-chromium@npm:^1.4.284": version: 1.4.354 resolution: "electron-to-chromium@npm:1.4.354" @@ -2693,15 +2324,6 @@ __metadata: languageName: node linkType: hard -"end-of-stream@npm:^1.1.0": - version: 1.4.4 - resolution: "end-of-stream@npm:1.4.4" - dependencies: - once: ^1.4.0 - checksum: 870b423afb2d54bb8d243c63e07c170409d41e20b47eeef0727547aea5740bd6717aca45597a9f2745525667a6b804c1e7bede41f856818faee5806dd9ff3975 - languageName: node - linkType: hard - "enhanced-resolve@npm:^5.12.0": version: 5.12.0 resolution: "enhanced-resolve@npm:5.12.0" @@ -3239,16 +2861,6 @@ __metadata: languageName: node linkType: hard -"esprima@npm:^4.0.0": - version: 4.0.1 - resolution: "esprima@npm:4.0.1" - bin: - esparse: ./bin/esparse.js - esvalidate: ./bin/esvalidate.js - checksum: ad4bab9ead0808cf56501750fd9d3fb276f6b105f987707d059005d57e182d18a7c9ec7f3a01794ebddcca676773e42ca48a32d67a250c9d35e009ca613caba3 - languageName: node - linkType: hard - "esquery@npm:^1.4.2": version: 1.5.0 resolution: "esquery@npm:1.5.0" @@ -3309,7 +2921,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.2, fast-glob@npm:^3.2.9": +"fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.9": version: 3.2.12 resolution: "fast-glob@npm:3.2.12" dependencies: @@ -3538,15 +3150,6 @@ __metadata: languageName: node linkType: hard -"get-stream@npm:^5.1.0": - version: 5.2.0 - resolution: "get-stream@npm:5.2.0" - dependencies: - pump: ^3.0.0 - checksum: 43797ffd815fbb26685bf188c8cfebecb8af87b3925091dd7b9a9c915993293d78e3c9e1bce125928ff92f2d0796f3889b92b5ec6d58d1041b574682132e0a80 - languageName: node - linkType: hard - "get-symbol-description@npm:^1.0.0": version: 1.0.0 resolution: "get-symbol-description@npm:1.0.0" @@ -3641,7 +3244,7 @@ __metadata: languageName: node linkType: hard -"globby@npm:^11.0.1, globby@npm:^11.1.0": +"globby@npm:^11.1.0": version: 11.1.0 resolution: "globby@npm:11.1.0" dependencies: @@ -3684,25 +3287,6 @@ __metadata: languageName: node linkType: hard -"got@npm:^11.7.0": - version: 11.8.6 - resolution: "got@npm:11.8.6" - dependencies: - "@sindresorhus/is": ^4.0.0 - "@szmarczak/http-timer": ^4.0.5 - "@types/cacheable-request": ^6.0.1 - "@types/responselike": ^1.0.0 - cacheable-lookup: ^5.0.3 - cacheable-request: ^7.0.2 - decompress-response: ^6.0.0 - http2-wrapper: ^1.0.0-beta.5.2 - lowercase-keys: ^2.0.0 - p-cancelable: ^2.0.0 - responselike: ^2.0.0 - checksum: 754dd44877e5cf6183f1e989ff01c648d9a4719e357457bd4c78943911168881f1cfb7b2cb15d885e2105b3ad313adb8f017a67265dd7ade771afdb261ee8cb1 - languageName: node - linkType: hard - "graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" @@ -3786,15 +3370,6 @@ __metadata: languageName: node linkType: hard -"he@npm:^1.2.0": - version: 1.2.0 - resolution: "he@npm:1.2.0" - bin: - he: bin/he - checksum: a27d478befe3c8192f006cdd0639a66798979dfa6e2125c6ac582a19a5ebfec62ad83e8382e6036170d873f46e4536a7e795bf8b95bf7c247f4cc0825ccc8c17 - languageName: node - linkType: hard - "history@npm:^5.3.0": version: 5.3.0 resolution: "history@npm:5.3.0" @@ -3804,7 +3379,7 @@ __metadata: languageName: node linkType: hard -"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1": +"hoist-non-react-statics@npm:^3.3.1": version: 3.3.2 resolution: "hoist-non-react-statics@npm:3.3.2" dependencies: @@ -3820,24 +3395,7 @@ __metadata: languageName: node linkType: hard -"html-minifier-terser@npm:^6.1.0": - version: 6.1.0 - resolution: "html-minifier-terser@npm:6.1.0" - dependencies: - camel-case: ^4.1.2 - clean-css: ^5.2.2 - commander: ^8.3.0 - he: ^1.2.0 - param-case: ^3.0.4 - relateurl: ^0.2.7 - terser: ^5.10.0 - bin: - html-minifier-terser: cli.js - checksum: 1aa4e4f01cf7149e3ac5ea84fb7a1adab86da40d38d77a6fff42852b5ee3daccb78b615df97264e3a6a5c33e57f0c77f471d607ca1e1debd1dab9b58286f4b5a - languageName: node - linkType: hard - -"http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.0": +"http-cache-semantics@npm:^4.1.0": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" checksum: ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc @@ -3855,16 +3413,6 @@ __metadata: languageName: node linkType: hard -"http2-wrapper@npm:^1.0.0-beta.5.2": - version: 1.0.3 - resolution: "http2-wrapper@npm:1.0.3" - dependencies: - quick-lru: ^5.1.1 - resolve-alpn: ^1.0.0 - checksum: 6a9b72a033e9812e1476b9d776ce2f387bc94bc46c88aea0d5dab6bd47d0a539b8178830e77054dd26d1142c866d515a28a4dc7c3ff4232c88ff2ebe4f5d12d1 - languageName: node - linkType: hard - "https-proxy-agent@npm:^5.0.0": version: 5.0.1 resolution: "https-proxy-agent@npm:5.0.1" @@ -4250,18 +3798,6 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:^3.10.0": - version: 3.14.1 - resolution: "js-yaml@npm:3.14.1" - dependencies: - argparse: ^1.0.7 - esprima: ^4.0.0 - bin: - js-yaml: bin/js-yaml.js - checksum: 6746baaaeac312c4db8e75fa22331d9a04cccb7792d126ed8ce6a0bbcfef0cedaddd0c5098fade53db067c09fe00aa1c957674b4765610a8b06a5a189e46433b - languageName: node - linkType: hard - "js-yaml@npm:^4.1.0": version: 4.1.0 resolution: "js-yaml@npm:4.1.0" @@ -4282,13 +3818,6 @@ __metadata: languageName: node linkType: hard -"json-buffer@npm:3.0.1": - version: 3.0.1 - resolution: "json-buffer@npm:3.0.1" - checksum: 0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 - languageName: node - linkType: hard - "json-parse-better-errors@npm:^1.0.1": version: 1.0.2 resolution: "json-parse-better-errors@npm:1.0.2" @@ -4354,15 +3883,6 @@ __metadata: languageName: node linkType: hard -"keyv@npm:^4.0.0": - version: 4.5.2 - resolution: "keyv@npm:4.5.2" - dependencies: - json-buffer: 3.0.1 - checksum: b633bf53a5afa5591f383d326746226e110e59f13c7e1e8d3e3c9580d2c2345c5eefc21cce168cd5be7fa34b9163e391927146fbd2b7ee7aa2f3aa02b7f0a7de - languageName: node - linkType: hard - "language-subtag-registry@npm:~0.3.2": version: 0.3.22 resolution: "language-subtag-registry@npm:0.3.22" @@ -4431,7 +3951,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.15, lodash@npm:^4.17.20": +"lodash@npm:^4.17.20": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c @@ -4449,22 +3969,6 @@ __metadata: languageName: node linkType: hard -"lower-case@npm:^2.0.2": - version: 2.0.2 - resolution: "lower-case@npm:2.0.2" - dependencies: - tslib: ^2.0.3 - checksum: 3d925e090315cf7dc1caa358e0477e186ffa23947740e4314a7429b6e62d72742e0bbe7536a5ae56d19d7618ce998aba05caca53c2902bd5742fdca5fc57fd7b - languageName: node - linkType: hard - -"lowercase-keys@npm:^2.0.0": - version: 2.0.0 - resolution: "lowercase-keys@npm:2.0.0" - checksum: f82a2b3568910509da4b7906362efa40f5b54ea14c2584778ddb313226f9cbf21020a5db35f9b9a0e95847a9b781d548601f31793d736b22a2b8ae8eb9ab1082 - languageName: node - linkType: hard - "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -4535,7 +4039,7 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4": +"micromatch@npm:^4.0.4": version: 4.0.5 resolution: "micromatch@npm:4.0.5" dependencies: @@ -4552,7 +4056,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.1.12, mime-types@npm:^2.1.35": +"mime-types@npm:^2.1.12": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -4561,20 +4065,6 @@ __metadata: languageName: node linkType: hard -"mimic-response@npm:^1.0.0": - version: 1.0.1 - resolution: "mimic-response@npm:1.0.1" - checksum: c5381a5eae997f1c3b5e90ca7f209ed58c3615caeee850e85329c598f0c000ae7bec40196580eef1781c60c709f47258131dab237cad8786f8f56750594f27fa - languageName: node - linkType: hard - -"mimic-response@npm:^3.1.0": - version: 3.1.0 - resolution: "mimic-response@npm:3.1.0" - checksum: 0d6f07ce6e03e9e4445bee655202153bdb8a98d67ee8dc965ac140900d7a2688343e6b4c9a72cfc9ef2f7944dfd76eef4ab2482eb7b293a68b84916bac735362 - languageName: node - linkType: hard - "minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -4737,16 +4227,6 @@ __metadata: languageName: node linkType: hard -"no-case@npm:^3.0.4": - version: 3.0.4 - resolution: "no-case@npm:3.0.4" - dependencies: - lower-case: ^2.0.2 - tslib: ^2.0.3 - checksum: 8ef545f0b3f8677c848f86ecbd42ca0ff3cd9dd71c158527b344c69ba14710d816d8489c746b6ca225e7b615108938a0bda0a54706f8c255933703ac1cf8e703 - languageName: node - linkType: hard - "node-gyp@npm:latest": version: 9.3.1 resolution: "node-gyp@npm:9.3.1" @@ -4835,13 +4315,6 @@ __metadata: languageName: node linkType: hard -"normalize-url@npm:^6.0.1": - version: 6.1.0 - resolution: "normalize-url@npm:6.1.0" - checksum: 95d948f9bdd2cfde91aa786d1816ae40f8262946e13700bf6628105994fe0ff361662c20af3961161c38a119dc977adeb41fc0b41b1745eb77edaaf9cb22db23 - languageName: node - linkType: hard - "npm-run-all@npm:^4.1.5": version: 4.1.5 resolution: "npm-run-all@npm:4.1.5" @@ -4961,7 +4434,7 @@ __metadata: languageName: node linkType: hard -"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": +"once@npm:^1.3.0": version: 1.4.0 resolution: "once@npm:1.4.0" dependencies: @@ -4995,22 +4468,6 @@ __metadata: languageName: node linkType: hard -"p-cancelable@npm:^2.0.0": - version: 2.1.1 - resolution: "p-cancelable@npm:2.1.1" - checksum: 8c6dc1f8dd4154fd8b96a10e55a3a832684c4365fb9108056d89e79fbf21a2465027c04a59d0d797b5ffe10b54a61a32043af287d5c4860f1e996cbdbc847f01 - languageName: node - linkType: hard - -"p-limit@npm:^2.2.0": - version: 2.3.0 - resolution: "p-limit@npm:2.3.0" - dependencies: - p-try: ^2.0.0 - checksum: 8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 - languageName: node - linkType: hard - "p-limit@npm:^3.0.2": version: 3.1.0 resolution: "p-limit@npm:3.1.0" @@ -5038,23 +4495,6 @@ __metadata: languageName: node linkType: hard -"p-try@npm:^2.0.0": - version: 2.2.0 - resolution: "p-try@npm:2.2.0" - checksum: c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f - languageName: node - linkType: hard - -"param-case@npm:^3.0.4": - version: 3.0.4 - resolution: "param-case@npm:3.0.4" - dependencies: - dot-case: ^3.0.4 - tslib: ^2.0.3 - checksum: ccc053f3019f878eca10e70ec546d92f51a592f762917dafab11c8b532715dcff58356118a6f350976e4ab109e321756f05739643ed0ca94298e82291e6f9e76 - languageName: node - linkType: hard - "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -5086,16 +4526,6 @@ __metadata: languageName: node linkType: hard -"pascal-case@npm:^3.1.2": - version: 3.1.2 - resolution: "pascal-case@npm:3.1.2" - dependencies: - no-case: ^3.0.4 - tslib: ^2.0.3 - checksum: 05ff7c344809fd272fc5030ae0ee3da8e4e63f36d47a1e0a4855ca59736254192c5a27b5822ed4bae96e54048eec5f6907713cfcfff7cdf7a464eaf7490786d8 - languageName: node - linkType: hard - "path-exists@npm:^4.0.0": version: 4.0.0 resolution: "path-exists@npm:4.0.0" @@ -5255,16 +4685,6 @@ __metadata: languageName: node linkType: hard -"pump@npm:^3.0.0": - version: 3.0.0 - resolution: "pump@npm:3.0.0" - dependencies: - end-of-stream: ^1.1.0 - once: ^1.3.1 - checksum: bbdeda4f747cdf47db97428f3a135728669e56a0ae5f354a9ac5b74556556f5446a46f720a8f14ca2ece5be9b4d5d23c346db02b555f46739934cc6c093a5478 - languageName: node - linkType: hard - "punycode@npm:^2.1.0": version: 2.3.0 resolution: "punycode@npm:2.3.0" @@ -5279,13 +4699,6 @@ __metadata: languageName: node linkType: hard -"quick-lru@npm:^5.1.1": - version: 5.1.1 - resolution: "quick-lru@npm:5.1.1" - checksum: a24cba5da8cec30d70d2484be37622580f64765fb6390a928b17f60cd69e8dbd32a954b3ff9176fa1b86d86ff2ba05252fae55dc4d40d0291c60412b0ad096da - languageName: node - linkType: hard - "react-dom@npm:latest": version: 18.2.0 resolution: "react-dom@npm:18.2.0" @@ -5466,13 +4879,6 @@ __metadata: languageName: node linkType: hard -"relateurl@npm:^0.2.7": - version: 0.2.7 - resolution: "relateurl@npm:0.2.7" - checksum: c248b4e3b32474f116a804b537fa6343d731b80056fb506dffd91e737eef4cac6be47a65aae39b522b0db9d0b1011d1a12e288d82a109ecd94a5299d82f6573a - languageName: node - linkType: hard - "require-directory@npm:^2.1.1": version: 2.1.1 resolution: "require-directory@npm:2.1.1" @@ -5480,13 +4886,6 @@ __metadata: languageName: node linkType: hard -"resolve-alpn@npm:^1.0.0": - version: 1.2.1 - resolution: "resolve-alpn@npm:1.2.1" - checksum: b70b29c1843bc39781ef946c8cd4482e6d425976599c0f9c138cec8209e4e0736161bf39319b01676a847000085dfdaf63583c6fb4427bf751a10635bd2aa0c4 - languageName: node - linkType: hard - "resolve-from@npm:^4.0.0": version: 4.0.0 resolution: "resolve-from@npm:4.0.0" @@ -5546,15 +4945,6 @@ __metadata: languageName: node linkType: hard -"responselike@npm:^2.0.0": - version: 2.0.1 - resolution: "responselike@npm:2.0.1" - dependencies: - lowercase-keys: ^2.0.0 - checksum: 360b6deb5f101a9f8a4174f7837c523c3ec78b7ca8a7c1d45a1062b303659308a23757e318b1e91ed8684ad1205721142dd664d94771cd63499353fd4ee732b5 - languageName: node - linkType: hard - "retry@npm:^0.12.0": version: 0.12.0 resolution: "retry@npm:0.12.0" @@ -5674,7 +5064,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.1.2, semver@npm:^7.3.5, semver@npm:^7.3.7": +"semver@npm:^7.3.5, semver@npm:^7.3.7": version: 7.3.8 resolution: "semver@npm:7.3.8" dependencies: @@ -5840,7 +5230,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.6.0, source-map@npm:~0.6.0": +"source-map@npm:^0.6.0": version: 0.6.1 resolution: "source-map@npm:0.6.1" checksum: ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 @@ -5888,13 +5278,6 @@ __metadata: languageName: node linkType: hard -"sprintf-js@npm:~1.0.2": - version: 1.0.3 - resolution: "sprintf-js@npm:1.0.3" - checksum: ecadcfe4c771890140da5023d43e190b7566d9cf8b2d238600f31bec0fc653f328da4450eb04bd59a431771a8e9cc0e118f0aa3974b683a4981b4e07abc2a5bb - languageName: node - linkType: hard - "ssri@npm:^9.0.0": version: 9.0.1 resolution: "ssri@npm:9.0.1" @@ -6079,7 +5462,7 @@ __metadata: languageName: node linkType: hard -"tar@npm:^6.0.5, tar@npm:^6.1.11, tar@npm:^6.1.2": +"tar@npm:^6.1.11, tar@npm:^6.1.2": version: 6.1.13 resolution: "tar@npm:6.1.13" dependencies: @@ -6093,20 +5476,6 @@ __metadata: languageName: node linkType: hard -"terser@npm:^5.10.0": - version: 5.16.8 - resolution: "terser@npm:5.16.8" - dependencies: - "@jridgewell/source-map": ^0.3.2 - acorn: ^8.5.0 - commander: ^2.20.0 - source-map-support: ~0.5.20 - bin: - terser: bin/terser - checksum: b49772a10ed4fa12bfaf231ff018d818c48b80ae9093fe52ef9753acd25fd40bef20dd4b0a226a5d46e52e7904be6b94e51cf41be0159913e13114d0c3372f82 - languageName: node - linkType: hard - "terser@npm:^5.17.1": version: 5.17.1 resolution: "terser@npm:5.17.1" @@ -6138,13 +5507,6 @@ __metadata: languageName: node linkType: hard -"tinylogic@npm:^2.0.0": - version: 2.0.0 - resolution: "tinylogic@npm:2.0.0" - checksum: c9417c4b65dfc469c71c9eba4d43d44813ab8baceb80ba2c0e6c286de2e93e9c4b8522e4b0a7b91cb4a85353368ee93838a862262ce54bac431b884e694d1c89 - languageName: node - linkType: hard - "to-fast-properties@npm:^2.0.0": version: 2.0.0 resolution: "to-fast-properties@npm:2.0.0" @@ -6172,13 +5534,6 @@ __metadata: languageName: node linkType: hard -"treeify@npm:^1.1.0": - version: 1.1.0 - resolution: "treeify@npm:1.1.0" - checksum: 2f0dea9e89328b8a42296a3963d341ab19897a05b723d6b0bced6b28701a340d2a7b03241aef807844198e46009aaf3755139274eb082cfce6fdc1935cbd69dd - languageName: node - linkType: hard - "tsconfck@npm:^2.1.0": version: 2.1.1 resolution: "tsconfck@npm:2.1.1" @@ -6212,7 +5567,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.3, tslib@npm:^2.4.0, tslib@npm:^2.5.0": +"tslib@npm:^2.4.0, tslib@npm:^2.5.0": version: 2.5.0 resolution: "tslib@npm:2.5.0" checksum: e32fc99cc730dd514e53c44e668d76016e738f0bcc726aad5dbd2d335cf19b87a95a9b1e4f0a9993e370f1d702b5e471cdd4acabcac428a3099d496b9af2021e @@ -6230,20 +5585,6 @@ __metadata: languageName: node linkType: hard -"tunnel@npm:^0.0.6": - version: 0.0.6 - resolution: "tunnel@npm:0.0.6" - checksum: e27e7e896f2426c1c747325b5f54efebc1a004647d853fad892b46d64e37591ccd0b97439470795e5262b5c0748d22beb4489a04a0a448029636670bfd801b75 - languageName: node - linkType: hard - -"typanion@npm:^3.8.0": - version: 3.12.1 - resolution: "typanion@npm:3.12.1" - checksum: 90cf558a34011144d4b741d85615423f594ac769c9850677989e9c42ef6f5e1ea9be5d55c6ff00291038058273a9bc25d7271bec5db0bd05e26af9a0872fb8e6 - languageName: node - linkType: hard - "type-check@npm:^0.4.0, type-check@npm:~0.4.0": version: 0.4.0 resolution: "type-check@npm:0.4.0" @@ -6379,17 +5720,6 @@ __metadata: languageName: node linkType: hard -"vite-plugin-minify@npm:^1.5.2": - version: 1.5.2 - resolution: "vite-plugin-minify@npm:1.5.2" - dependencies: - html-minifier-terser: ^6.1.0 - peerDependencies: - vite: "*" - checksum: e7c3d3e936b93f6ba426d72c4396864bd57e546aa621342dc7add5eddbd4d464663e7022c4792012fa2977f4c8a7c598de29ef5a5d4cf6b5b0b80b51bd3970f2 - languageName: node - linkType: hard - "vite-plugin-svgr@npm:^2.4.0": version: 2.4.0 resolution: "vite-plugin-svgr@npm:2.4.0" From cfe8c410aebcfcea36f538b67649906f11abc7cc Mon Sep 17 00:00:00 2001 From: Proddy Date: Fri, 28 Apr 2023 12:46:59 +0200 Subject: [PATCH 39/89] Optimize WebUI rendering when using Dialog Boxes #1116 --- CHANGELOG.md | 4 +- CHANGELOG_LATEST.md | 4 + interface/.eslintrc.json | 2 +- interface/package.json | 18 +- .../src/framework/mqtt/MqttSettingsForm.tsx | 42 +- .../framework/network/NetworkSettingsForm.tsx | 15 +- interface/src/framework/system/SystemLog.tsx | 11 +- interface/src/i18n/de/index.ts | 7 +- interface/src/i18n/en/index.ts | 7 +- interface/src/i18n/fr/index.ts | 7 +- interface/src/i18n/nl/index.ts | 9 +- interface/src/i18n/no/index.ts | 7 +- interface/src/i18n/pl/index.ts | 7 +- interface/src/i18n/sv/index.ts | 7 +- interface/src/i18n/tr/index.ts | 7 +- interface/src/project/Dashboard.tsx | 13 +- ...DashboardData.tsx => DashboardDevices.tsx} | 660 ++---------------- interface/src/project/DashboardSensors.tsx | 469 +++++++++++++ .../project/DashboardSensorsAnalogDialog.tsx | 261 +++++++ .../DashboardSensorsTemperatureDialog.tsx | 121 ++++ interface/src/project/DeviceIcon.tsx | 4 +- interface/src/project/SettingsApplication.tsx | 47 +- .../src/project/SettingsCustomization.tsx | 3 +- .../project/SettingsCustomizationDialog.tsx | 15 +- interface/src/project/SettingsEntities.tsx | 2 +- .../src/project/SettingsEntitiesDialog.tsx | 31 +- interface/src/project/SettingsScheduler.tsx | 2 +- .../src/project/SettingsSchedulerDialog.tsx | 10 +- interface/src/project/api.ts | 18 +- interface/src/project/types.ts | 26 +- interface/src/project/validators.ts | 27 +- interface/yarn.lock | 172 +++-- mock-api/server.js | 94 +-- pio_local.ini_example | 2 +- src/analogsensor.cpp | 5 +- src/analogsensor.h | 5 +- src/command.cpp | 16 +- src/command.h | 2 +- src/console.cpp | 2 +- src/emsdevice.cpp | 10 +- src/emsdevice.h | 6 +- src/emsesp.cpp | 52 +- src/emsesp.h | 20 +- src/emsesp_stub.hpp | 8 +- src/locale_common.h | 2 +- src/mqtt.cpp | 2 +- src/system.cpp | 18 +- ...dallassensor.cpp => temperaturesensor.cpp} | 90 +-- src/{dallassensor.h => temperaturesensor.h} | 14 +- src/test/test.cpp | 26 +- src/test/test.h | 2 +- src/version.h | 2 +- src/web/WebCustomizationService.cpp | 20 +- src/web/WebCustomizationService.h | 2 +- src/web/WebDataService.cpp | 92 ++- src/web/WebDataService.h | 16 +- src/web/WebSettingsService.cpp | 10 +- src/web/WebSettingsService.h | 2 +- src/web/WebStatusService.cpp | 11 +- 59 files changed, 1446 insertions(+), 1120 deletions(-) rename interface/src/project/{DashboardData.tsx => DashboardDevices.tsx} (50%) create mode 100644 interface/src/project/DashboardSensors.tsx create mode 100644 interface/src/project/DashboardSensorsAnalogDialog.tsx create mode 100644 interface/src/project/DashboardSensorsTemperatureDialog.tsx rename src/{dallassensor.cpp => temperaturesensor.cpp} (86%) rename src/{dallassensor.h => temperaturesensor.h} (95%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51ad02fa5..57a0e916e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -212,7 +212,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added pool data to telegrams 0x494 & 0x495 [#102](https://github.com/emsesp/EMS-ESP32/issues/102) - Add RC300 second summermode telegram [#108](https://github.com/emsesp/EMS-ESP32/issues/108) - Add support for the RC25 thermostat [#106](https://github.com/emsesp/EMS-ESP32/issues/106) -- Add new command 'entities' for a device, e.g. http://ems-esp/api/boiler/entities to show the shortname, description and HA Entity name (if HA enabled) [#116](https://github.com/emsesp/EMS-ESP32/issues/116) +- Add new command 'entities' for a device, e.g. to show the shortname, description and HA Entity name (if HA enabled) [#116](https://github.com/emsesp/EMS-ESP32/issues/116) - Support for Junkers program and remote (fb10/fb110) temperature - Home Assistant `state_class` attribute for Wh, kWh, W and KW [#129](https://github.com/emsesp/EMS-ESP32/issues/129) - Add current room influence for RC300 [#136](https://github.com/emsesp/EMS-ESP32/issues/136) @@ -434,4 +434,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - some names of mqtt-tags like in v2.2.1 - new ESP32 partition side to allow for smoother OTA and fallback - Network Gateway IP is optional (#682)emsesp/EMS-ESP -- moved to a new GitHub repo https://github.com/emsesp/EMS-ESP32 +- moved to a new GitHub repo diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index a45fe6e6e..780d0a2da 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -4,6 +4,8 @@ ## **IMPORTANT! BREAKING CHANGES** +- dallassensor has been renamed to temperaturesensor in MQTT payloads + ## Added - Workaround for better Domoticz MQTT intergration? [#904](https://github.com/emsesp/EMS-ESP32/issues/904) @@ -29,3 +31,5 @@ - Improved HA Discovery so previous configs no longer need to be removed when starting [#1077](https://github.com/emsesp/EMS-ESP32/pull/1077) (thanks @pswid!) - Enlarge UART-Stack to 2,5k - Retry timeout for Mqtt-QOS1/2 10seconds +- Optimize WebUI rendering when using Dialog Boxes [#1116](https://github.com/emsesp/EMS-ESP32/issues/1116) +- Optimize Web libraries to reduce bundle size (3.6.x) [#1112](https://github.com/emsesp/EMS-ESP32/issues/1112) diff --git a/interface/.eslintrc.json b/interface/.eslintrc.json index 32758fa78..4fc97f6f7 100644 --- a/interface/.eslintrc.json +++ b/interface/.eslintrc.json @@ -52,7 +52,7 @@ "@typescript-eslint/no-implied-eval": "off", "@typescript-eslint/no-misused-promises": "off", "arrow-body-style": ["error", "as-needed"], - "react-hooks/exhaustive-deps": "error", + "react-hooks/exhaustive-deps": "warn", "@typescript-eslint/consistent-type-imports": [ "error", { diff --git a/interface/package.json b/interface/package.json index 1a2472c86..517e0bb70 100644 --- a/interface/package.json +++ b/interface/package.json @@ -20,20 +20,20 @@ "lint-fixall": "eslint . --cache --fix" }, "dependencies": { - "@emotion/react": "^11.10.6", - "@emotion/styled": "^11.10.6", + "@emotion/react": "^11.10.8", + "@emotion/styled": "^11.10.8", "@msgpack/msgpack": "^3.0.0-beta2", "@mui/icons-material": "^5.11.16", - "@mui/material": "^5.12.1", + "@mui/material": "^5.12.2", "@remix-run/router": "^1.5.0", - "@table-library/react-table-library": "4.1.0", + "@table-library/react-table-library": "4.1.2", "@types/lodash-es": "^4.17.7", - "@types/node": "^18.16.0", - "@types/react": "^18.0.38", - "@types/react-dom": "^18.0.11", + "@types/node": "^18.16.2", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.1", "@types/react-router-dom": "^5.3.3", "async-validator": "^4.2.5", - "axios": "^1.3.6", + "axios": "^1.4.0", "history": "^5.3.0", "jwt-decode": "^3.1.2", "lodash-es": "^4.17.21", @@ -67,7 +67,7 @@ "prettier": "^2.8.8", "rollup-plugin-visualizer": "^5.9.0", "terser": "^5.17.1", - "vite": "^4.3.1", + "vite": "^4.3.3", "vite-plugin-svgr": "^2.4.0", "vite-tsconfig-paths": "^4.2.0" }, diff --git a/interface/src/framework/mqtt/MqttSettingsForm.tsx b/interface/src/framework/mqtt/MqttSettingsForm.tsx index b2deb9d25..189610cf4 100644 --- a/interface/src/framework/mqtt/MqttSettingsForm.tsx +++ b/interface/src/framework/mqtt/MqttSettingsForm.tsx @@ -1,6 +1,6 @@ import CancelIcon from '@mui/icons-material/Cancel'; import WarningIcon from '@mui/icons-material/Warning'; -import { Button, Checkbox, MenuItem, Grid, Typography, InputAdornment } from '@mui/material'; +import { Button, Checkbox, MenuItem, Grid, Typography, InputAdornment, TextField } from '@mui/material'; import { useState } from 'react'; import type { ValidateFieldsError } from 'async-validator'; import type { FC } from 'react'; @@ -94,7 +94,7 @@ const MqttSettingsForm: FC = () => { /> - { /> - { /> - { 0 1 2 - + { {LL.FORMATTING()} - { > {LL.MQTT_NEST_1()} {LL.MQTT_NEST_2()} - + } label={LL.MQTT_RESPONSE()} @@ -233,7 +233,7 @@ const MqttSettingsForm: FC = () => { alignItems="flex-start" > - { > Home Assistant Domoticz - + - { /> - { {LL.MQTT_ENTITY_FORMAT_0()} {LL.MQTT_ENTITY_FORMAT_1()} {LL.MQTT_ENTITY_FORMAT_2()} - + )} @@ -299,8 +299,7 @@ const MqttSettingsForm: FC = () => { /> - { /> - { /> - { /> - { /> - { /> - {LL.SECONDS()} diff --git a/interface/src/framework/network/NetworkSettingsForm.tsx b/interface/src/framework/network/NetworkSettingsForm.tsx index 308121075..bc2a768bc 100644 --- a/interface/src/framework/network/NetworkSettingsForm.tsx +++ b/interface/src/framework/network/NetworkSettingsForm.tsx @@ -15,7 +15,8 @@ import { ListItemSecondaryAction, ListItemText, Typography, - InputAdornment + InputAdornment, + TextField } from '@mui/material'; import { useContext, useEffect, useState } from 'react'; import { toast } from 'react-toastify'; @@ -165,7 +166,6 @@ const WiFiSettingsForm: FC = () => { margin="normal" /> )} - { type="number" margin="normal" /> - } label={LL.NETWORK_DISABLE_SLEEP()} /> - } label={LL.NETWORK_LOW_BAND()} /> - {LL.GENERAL_OPTIONS()} - { onChange={updateFormValue} margin="normal" /> - } label={LL.NETWORK_USE_DNS()} /> - } label={LL.NETWORK_ENABLE_CORS()} /> {data.enableCORS && ( - { margin="normal" /> )} - } label={LL.NETWORK_ENABLE_IPV6()} /> - } label={LL.NETWORK_FIXED_IP()} diff --git a/interface/src/framework/system/SystemLog.tsx b/interface/src/framework/system/SystemLog.tsx index 5019738fc..ca2251d3a 100644 --- a/interface/src/framework/system/SystemLog.tsx +++ b/interface/src/framework/system/SystemLog.tsx @@ -1,6 +1,6 @@ import DownloadIcon from '@mui/icons-material/GetApp'; import WarningIcon from '@mui/icons-material/Warning'; -import { Box, styled, Button, Checkbox, MenuItem, Grid } from '@mui/material'; +import { Box, styled, Button, Checkbox, MenuItem, Grid, TextField } from '@mui/material'; import { useState, useEffect, useCallback, useLayoutEffect } from 'react'; import type { FC } from 'react'; @@ -138,7 +138,6 @@ const SystemLog: FC = () => { return () => { es.close(); }; - // eslint-disable-next-line }, []); const content = () => { @@ -150,7 +149,7 @@ const SystemLog: FC = () => { <> - { INFO DEBUG ALL - + - { 50 75 100 - + { return ( <> - + + - } /> + } /> + } /> } /> - } /> + } /> ); diff --git a/interface/src/project/DashboardData.tsx b/interface/src/project/DashboardDevices.tsx similarity index 50% rename from interface/src/project/DashboardData.tsx rename to interface/src/project/DashboardDevices.tsx index 4af8b8673..e35dcef99 100644 --- a/interface/src/project/DashboardData.tsx +++ b/interface/src/project/DashboardDevices.tsx @@ -45,8 +45,8 @@ import DeviceIcon from './DeviceIcon'; import * as EMSESP from './api'; -import { DeviceValueUOM, DeviceValueUOM_s, AnalogType, AnalogTypeNames, DeviceEntityMask } from './types'; -import type { SensorData, Device, CoreData, DeviceData, DeviceValue, Sensor, Analog } from './types'; +import { DeviceValueUOM, DeviceValueUOM_s, DeviceEntityMask } from './types'; +import type { SensorData, Device, CoreData, DeviceData, DeviceValue, TemperatureSensor, AnalogSensor } from './types'; import type { FC } from 'react'; import { ButtonRow, ValidatedTextField, SectionContent, MessageBox } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; @@ -54,26 +54,22 @@ import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; import { numberValue, updateValue, extractErrorMessage } from 'utils'; -const DashboardData: FC = () => { +const DashboardDevices: FC = () => { const { me } = useContext(AuthenticatedContext); - const { LL } = useI18nContext(); - - const [coreData, setCoreData] = useState({ - connected: true, - devices: [], - s_n: '', - active_sensors: 0, - analog_enabled: false - }); - const [deviceData, setDeviceData] = useState({ label: '', data: [] }); - const [sensorData, setSensorData] = useState({ sensors: [], analogs: [] }); const [deviceValue, setDeviceValue] = useState(); - const [sensor, setSensor] = useState(); - const [analog, setAnalog] = useState(); const [deviceDialog, setDeviceDialog] = useState(-1); const [onlyFav, setOnlyFav] = useState(false); + const [coreData, setCoreData] = useState({ + connected: true, + devices: [] + }); + const [selectedDevice, setSelectedDevice] = useState(); + + const [sensorData, setSensorData] = useState({ ts: [], as: [] }); + const [analog, setAnalog] = useState(); + const [sensor, setSensor] = useState(); const common_theme = useTheme({ BaseRow: ` @@ -168,25 +164,6 @@ const DashboardData: FC = () => { } ]); - const temperature_theme = useTheme([data_theme]); - - const analog_theme = useTheme([ - data_theme, - { - Table: ` - --data-table-library_grid-template-columns: 80px repeat(1, minmax(0, 1fr)) 120px 100px 40px; - `, - BaseCell: ` - &:nth-of-type(2) { - text-align: left; - }, - &:nth-of-type(4) { - text-align: right; - } - ` - } - ]); - const getSortIcon = (state: any, sortKey: any) => { if (state.sortKey === sortKey && state.reverse) { return ; @@ -197,41 +174,6 @@ const DashboardData: FC = () => { return ; }; - const analog_sort = useSort( - { nodes: sensorData.analogs }, - {}, - { - sortIcon: { - iconDefault: , - iconUp: , - iconDown: - }, - sortToggleType: SortToggleType.AlternateWithReset, - sortFns: { - GPIO: (array) => array.sort((a, b) => a.g - b.g), - NAME: (array) => array.sort((a, b) => a.n.localeCompare(b.n)), - TYPE: (array) => array.sort((a, b) => a.t - b.t) - } - } - ); - - const sensor_sort = useSort( - { nodes: sensorData.sensors }, - {}, - { - sortIcon: { - iconDefault: , - iconUp: , - iconDown: - }, - sortToggleType: SortToggleType.AlternateWithReset, - sortFns: { - NAME: (array) => array.sort((a, b) => a.n.localeCompare(b.n)), - TEMPERATURE: (array) => array.sort((a, b) => a.t - b.t) - } - } - ); - const dv_sort = useSort( { nodes: deviceData.data }, {}, @@ -256,18 +198,17 @@ const DashboardData: FC = () => { } ); - const fetchSensorData = async () => { - try { - setSensorData((await EMSESP.readSensorData()).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 fetchDeviceData = async (id: string) => { - const unique_id = parseInt(id); + const fetchDeviceData = async (id: number) => { try { - setDeviceData((await EMSESP.readDeviceData({ id: unique_id })).data); + setDeviceData((await EMSESP.readDeviceData({ id })).data); } catch (error) { toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); } @@ -285,29 +226,43 @@ const DashboardData: FC = () => { void fetchCoreData(); }, [fetchCoreData]); - const refreshDataIndex = (selectedDevice: string) => { - if (selectedDevice === 'sensor') { - void fetchSensorData(); - return; - } + // const refreshDataIndex = (selectedDevice: string) => { + // // if (selectedDevice === 'sensor') { + // // void fetchSensorData(); + // // return; + // // } - setSensorData({ sensors: [], analogs: [] }); + // // setSensorData({ sensors: [], analogs: [] }); + // if (selectedDevice) { + // void fetchDeviceData(selectedDevice); + // } else { + // void fetchCoreData(); + // } + // }; + + const refreshData = () => { + // const selectedDevice = device_select.state.id; + // if (selectedDevice === 'sensor') { + // // void fetchSensorData(); + // return; + // } + + // setSensorData({ sensors: [], analogs: [] }); if (selectedDevice) { void fetchDeviceData(selectedDevice); } else { void fetchCoreData(); } - }; - const refreshData = () => { - refreshDataIndex(device_select.state.id); + // refreshDataIndex(device_select.state.id); }; function onSelectChange(action: any, state: any) { + setSelectedDevice(device_select.state.id); if (action.type === 'ADD_BY_ID_EXCLUSIVELY') { refreshData(); } else { - setSensorData({ sensors: [], analogs: [] }); + // setSensorData({ sensors: [], analogs: [] }); } } @@ -437,7 +392,7 @@ const DashboardData: FC = () => { const sendDeviceValue = async () => { if (deviceValue) { try { - const response = await EMSESP.writeValue({ + const response = await EMSESP.writeDeviceValue({ id: Number(device_select.state.id), devicevalue: deviceValue }); @@ -523,100 +478,6 @@ const DashboardData: FC = () => { } }; - const addAnalogSensor = () => { - setAnalog({ id: '0', g: 0, n: '', u: 0, v: 0, o: 0, t: 0, f: 1 }); - }; - - const sendSensor = async () => { - if (sensor) { - try { - const response = await EMSESP.writeSensor({ - id: sensor.id, - name: sensor.n, - offset: sensor.o - }); - if (response.status === 204) { - toast.error(LL.UPLOAD_OF(LL.SENSOR()) + ' ' + LL.FAILED()); - } else if (response.status === 403) { - toast.error(LL.ACCESS_DENIED()); - } else { - toast.success(LL.UPDATED_OF(LL.SENSOR())); - } - setSensor(undefined); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - } finally { - setSensor(undefined); - await fetchSensorData(); - } - } - }; - - const renderSensorDialog = () => { - if (sensor) { - return ( - setSensor(undefined)}> - - {LL.EDIT()} {LL.TEMP_SENSOR()} - - - - - {LL.ID_OF(LL.SENSOR())}: {sensor.id} - - - - - - - - °C - }} - /> - - - - - - - - - ); - } - }; - const renderDeviceDialog = () => { if (coreData && coreData.devices.length > 0 && deviceDialog !== -1) { return ( @@ -692,21 +553,6 @@ const DashboardData: FC = () => { ))} - {(coreData.active_sensors > 0 || coreData.analog_enabled) && ( - - - - - {coreData.s_n} - {LL.ATTACHED_SENSORS()} - {coreData.active_sensors} - - addAnalogSensor()}> - - - - - )} )} @@ -813,421 +659,13 @@ const DashboardData: FC = () => { ); }; - const updateSensor = (s: Sensor) => { - if (s && me.admin) { - setSensor(s); - } - }; - - const updateAnalog = (a: Analog) => { - if (me.admin) { - setAnalog(a); - } - }; - - const renderDallasData = () => ( - <> - - {LL.TEMP_SENSORS()} - -
- {(tableList: any) => ( - <> -
- - - - - - - - - -
- - {tableList.map((s: Sensor) => ( - updateSensor(s)}> - {s.n} - {formatValue(s.t, s.u)} - - {me.admin && ( - updateSensor(s)}> - - - )} - - - ))} - - - )} -
- - ); - - const renderAnalogData = () => ( - <> - - {LL.ANALOG_SENSORS()} - - - - {(tableList: any) => ( - <> -
- - - - - - - - - - - {LL.VALUE(0)} - - -
- - {tableList.map((a: Analog) => ( - updateAnalog(a)}> - {a.g} - {a.n} - {AnalogTypeNames[a.t]} - {a.t ? formatValue(a.v, a.u) : ''} - - {me.admin && ( - updateAnalog(a)}> - - - )} - - - ))} - - - )} -
- - ); - - const sendRemoveAnalog = async () => { - if (analog) { - try { - const response = await EMSESP.writeAnalog({ - gpio: analog.g, - name: analog.n, - offset: analog.o, - factor: analog.f, - uom: analog.u, - type: -1 - }); - - if (response.status === 204) { - toast.error(LL.DELETION_OF(LL.ANALOG_SENSOR()) + ' ' + LL.FAILED()); - } else if (response.status === 403) { - toast.error(LL.ACCESS_DENIED()); - } else { - toast.success(LL.REMOVED_OF(LL.ANALOG_SENSOR())); - } - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - } finally { - setAnalog(undefined); - await fetchSensorData(); - } - } - }; - - const sendAnalog = async () => { - if (analog) { - try { - const response = await EMSESP.writeAnalog({ - gpio: analog.g, - name: analog.n, - offset: analog.o, - factor: analog.f, - uom: analog.u, - type: analog.t - }); - - if (response.status === 204) { - toast.error(LL.UPDATE_OF(LL.ANALOG_SENSOR()) + ' ' + LL.FAILED()); - } else if (response.status === 403) { - toast.error(LL.ACCESS_DENIED()); - } else { - toast.success(LL.UPDATED_OF(LL.ANALOG_SENSOR())); - } - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - } finally { - setAnalog(undefined); - await fetchSensorData(); - } - } - }; - - const renderAnalogDialog = () => { - if (analog) { - return ( - setAnalog(undefined)}> - - {LL.EDIT()} {LL.ANALOG_SENSOR()} - - - - - - - - - - - - {AnalogTypeNames.map((val, i) => ( - - {val} - - ))} - - - {analog.t >= AnalogType.COUNTER && analog.t <= AnalogType.RATE && ( - <> - - - {DeviceValueUOM_s.map((val, i) => ( - - {val} - - ))} - - - {analog.t === AnalogType.ADC && ( - - mV - }} - /> - - )} - {analog.t === AnalogType.COUNTER && ( - - - - )} - - - - - )} - {analog.t === AnalogType.DIGITAL_OUT && (analog.g === 25 || analog.g === 26) && ( - <> - - - - - )} - {analog.t === AnalogType.DIGITAL_OUT && analog.g !== 25 && analog.g !== 26 && ( - <> - - - - - )} - {analog.t >= AnalogType.PWM_0 && ( - <> - - Hz - }} - /> - - - % - }} - /> - - - )} - - - {LL.WARN_GPIO()} - - - - - - - - - - - ); - } - }; - return ( - + {renderCoreData()} {renderDeviceData()} {renderDeviceDialog()} - {sensorData.sensors.length !== 0 && renderDallasData()} - {sensorData.analogs.length !== 0 && renderAnalogData()} {renderDeviceValueDialog()} - {renderSensorDialog()} - {renderAnalogDialog()} + + + + + + + ); +}; + +export default DashboardSensors; diff --git a/interface/src/project/DashboardSensorsAnalogDialog.tsx b/interface/src/project/DashboardSensorsAnalogDialog.tsx new file mode 100644 index 000000000..b34489b1e --- /dev/null +++ b/interface/src/project/DashboardSensorsAnalogDialog.tsx @@ -0,0 +1,261 @@ +import CancelIcon from '@mui/icons-material/Cancel'; +import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; +import WarningIcon from '@mui/icons-material/Warning'; + +import { + Button, + Typography, + Box, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + InputAdornment, + Grid, + MenuItem, + TextField +} from '@mui/material'; +import { useState, useEffect } from 'react'; + +import { AnalogType, AnalogTypeNames, DeviceValueUOM_s } from './types'; +import type { AnalogSensor } from './types'; +import type Schema from 'async-validator'; +import type { ValidateFieldsError } from 'async-validator'; +import { ValidatedTextField } from 'components'; + +import { useI18nContext } from 'i18n/i18n-react'; +import { numberValue, updateValue } from 'utils'; + +import { validate } from 'validators'; + +type DashboardSensorsAnalogDialogProps = { + open: boolean; + onClose: () => void; + onSave: (as: AnalogSensor) => void; + creating: boolean; + selectedItem: AnalogSensor; + validator: Schema; +}; + +const DashboardSensorsAnalogDialog = ({ + open, + onClose, + onSave, + creating, + selectedItem, + validator +}: DashboardSensorsAnalogDialogProps) => { + const { LL } = useI18nContext(); + const [fieldErrors, setFieldErrors] = useState(); + const [editItem, setEditItem] = useState(selectedItem); + const updateFormValue = updateValue(setEditItem); + + useEffect(() => { + if (open) { + setFieldErrors(undefined); + setEditItem(selectedItem); + } + }, [open, selectedItem]); + + const close = () => { + onClose(); + }; + + const save = async () => { + try { + setFieldErrors(undefined); + await validate(validator, editItem); + onSave(editItem); + } catch (errors: any) { + setFieldErrors(errors); + } + }; + + const remove = () => { + editItem.d = true; + onSave(editItem); + }; + + return ( + + + {creating ? LL.ADD(1) + ' ' + LL.NEW(1) : LL.EDIT()} {LL.ANALOG_SENSOR()} + + + + + + + + + + + + {AnalogTypeNames.map((val, i) => ( + + {val} + + ))} + + + {editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE && ( + <> + + + {DeviceValueUOM_s.map((val, i) => ( + + {val} + + ))} + + + {editItem.t === AnalogType.ADC && ( + + mV + }} + /> + + )} + {editItem.t === AnalogType.COUNTER && ( + + + + )} + + + + + )} + {editItem.t === AnalogType.DIGITAL_OUT && (editItem.g === 25 || editItem.g === 26) && ( + + + + )} + {editItem.t === AnalogType.DIGITAL_OUT && editItem.g !== 25 && editItem.g !== 26 && ( + + + + )} + {editItem.t >= AnalogType.PWM_0 && ( + <> + + Hz + }} + /> + + + % + }} + /> + + + )} + + + {LL.WARN_GPIO()} + + + + {!creating && ( + + + + )} + + + + + ); +}; + +export default DashboardSensorsAnalogDialog; diff --git a/interface/src/project/DashboardSensorsTemperatureDialog.tsx b/interface/src/project/DashboardSensorsTemperatureDialog.tsx new file mode 100644 index 000000000..ddda88a3c --- /dev/null +++ b/interface/src/project/DashboardSensorsTemperatureDialog.tsx @@ -0,0 +1,121 @@ +import CancelIcon from '@mui/icons-material/Cancel'; +import WarningIcon from '@mui/icons-material/Warning'; + +import { + Button, + Typography, + Box, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + InputAdornment, + Grid, + TextField +} from '@mui/material'; +import { useState, useEffect } from 'react'; + +import type { TemperatureSensor } from './types'; +import type Schema from 'async-validator'; +import type { ValidateFieldsError } from 'async-validator'; +import { ValidatedTextField } from 'components'; + +import { useI18nContext } from 'i18n/i18n-react'; +import { numberValue, updateValue } from 'utils'; + +import { validate } from 'validators'; + +type DashboardSensorsTemperatureDialogProps = { + open: boolean; + onClose: () => void; + onSave: (ts: TemperatureSensor) => void; + selectedItem: TemperatureSensor; + validator: Schema; +}; + +const DashboardSensorsTemperatureDialog = ({ + open, + onClose, + onSave, + selectedItem, + validator +}: DashboardSensorsTemperatureDialogProps) => { + const { LL } = useI18nContext(); + const [fieldErrors, setFieldErrors] = useState(); + const [editItem, setEditItem] = useState(selectedItem); + const updateFormValue = updateValue(setEditItem); + + useEffect(() => { + if (open) { + setFieldErrors(undefined); + setEditItem(selectedItem); + } + }, [open, selectedItem]); + + const close = () => { + onClose(); + }; + + const save = async () => { + try { + setFieldErrors(undefined); + await validate(validator, editItem); + onSave(editItem); + } catch (errors: any) { + setFieldErrors(errors); + } + }; + + return ( + + + {LL.EDIT()} {LL.TEMP_SENSOR()} + + + + + {LL.ID_OF(LL.SENSOR())}: {editItem.id} + + + + + + + + °C + }} + /> + + + + + + + + + ); +}; + +export default DashboardSensorsTemperatureDialog; diff --git a/interface/src/project/DeviceIcon.tsx b/interface/src/project/DeviceIcon.tsx index 190a659a8..9db5dbacd 100644 --- a/interface/src/project/DeviceIcon.tsx +++ b/interface/src/project/DeviceIcon.tsx @@ -15,7 +15,7 @@ interface DeviceIconProps { // matches emsdevice.h DeviceType const enum DeviceType { SYSTEM = 0, - DALLASSENSOR, + TEMPERATURESENSOR, ANALOGSENSOR, SCHEDULER, BOILER, @@ -37,7 +37,7 @@ const enum DeviceType { const DeviceIcon: FC = ({ type_id }) => { switch (type_id) { - case DeviceType.DALLASSENSOR: + case DeviceType.TEMPERATURESENSOR: case DeviceType.ANALOGSENSOR: return ; case DeviceType.BOILER: diff --git a/interface/src/project/SettingsApplication.tsx b/interface/src/project/SettingsApplication.tsx index 933167329..a2a67c02b 100644 --- a/interface/src/project/SettingsApplication.tsx +++ b/interface/src/project/SettingsApplication.tsx @@ -1,7 +1,7 @@ import CancelIcon from '@mui/icons-material/Cancel'; import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; import WarningIcon from '@mui/icons-material/Warning'; -import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider, InputAdornment } from '@mui/material'; +import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider, InputAdornment, TextField } from '@mui/material'; import { useState } from 'react'; import { toast } from 'react-toastify'; @@ -132,7 +132,7 @@ const SettingsApplication: FC = () => { {LL.BOARD_PROFILE_TEXT()} - { {LL.CUSTOM()}… - + {data.board_profile === 'CUSTOM' && ( <> { /> - { {LL.DISABLED(1)} LAN8720 TLK110 - + {data.phy_type !== 0 && ( @@ -257,7 +257,7 @@ const SettingsApplication: FC = () => { alignItems="flex-start" > - { /> - { /> - { GPIO0_OUT GPIO16_OUT GPIO17_OUT - + )} @@ -309,7 +309,7 @@ const SettingsApplication: FC = () => { - { EMS+ HT3 {LL.HARDWARE()} - + - { Gateway 4 (0x4B) Gateway 5 (0x4C) Gateway 7 (0x4D) - + {LL.GENERAL_OPTIONS()} - { Polski (PL) Svenska (SV) Türk (TR) - + {data.led_gpio !== 0 && ( { - { {LL.ONOFF_CAP()} true/false 1/0 - + - { true/false "1"/"0" 1/0 - + - { > {LL.VALUE(1)} {LL.INDEX()} - + {data.dallas_gpio !== 0 && ( @@ -590,7 +590,7 @@ const SettingsApplication: FC = () => { /> - { INFO DEBUG ALL - + { )} - {!restartNeeded && dirtyFlags && dirtyFlags.length !== 0 && ( {dirtyFlags && dirtyFlags.length !== 0 && ( - )} - + { return ( + {blocker ? : null} {content()} ); From 28252b987b1d67df316b6d943128ec077eea04cf Mon Sep 17 00:00:00 2001 From: Proddy Date: Fri, 28 Apr 2023 15:46:24 +0200 Subject: [PATCH 42/89] added missing analog_enabled --- src/web/WebDataService.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index 8351282a8..cbf2ae2e3 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -161,6 +161,8 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) { } } + root["analog_enabled"] = EMSESP::analogsensor_.analog_enabled(); + response->setLength(); request->send(response); } From dd25900af4afdfe348bae1624c9c607852b74f8a Mon Sep 17 00:00:00 2001 From: Proddy Date: Fri, 28 Apr 2023 15:46:37 +0200 Subject: [PATCH 43/89] make icon bigger --- interface/src/components/layout/LayoutDrawer.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/interface/src/components/layout/LayoutDrawer.tsx b/interface/src/components/layout/LayoutDrawer.tsx index 1dbde9305..60b8649a6 100644 --- a/interface/src/components/layout/LayoutDrawer.tsx +++ b/interface/src/components/layout/LayoutDrawer.tsx @@ -6,12 +6,8 @@ import type { FC } from 'react'; import { PROJECT_NAME } from 'api/env'; const LayoutDrawerLogo = styled('img')(({ theme }) => ({ - [theme.breakpoints.down('sm')]: { - height: 24, - marginRight: theme.spacing(2) - }, [theme.breakpoints.up('sm')]: { - height: 36, + height: 38, marginRight: theme.spacing(2) } })); @@ -27,9 +23,7 @@ const LayoutDrawer: FC = ({ mobileOpen, onClose }) => { - - {PROJECT_NAME} - + {PROJECT_NAME} From 321558c583e075cc1f0253d05c3f4d6d2d0f2033 Mon Sep 17 00:00:00 2001 From: Proddy Date: Fri, 28 Apr 2023 15:46:45 +0200 Subject: [PATCH 44/89] minor optimizations --- interface/progmem-generator.js | 1 - interface/src/framework/ap/APSettingsForm.tsx | 2 +- interface/src/framework/mqtt/MqttSettingsForm.tsx | 2 +- .../src/framework/network/NetworkSettingsForm.tsx | 2 +- interface/src/framework/ntp/NTPSettingsForm.tsx | 2 +- .../src/framework/security/SecuritySettingsForm.tsx | 2 +- .../src/framework/system/GeneralFileUpload.tsx | 13 ++++--------- interface/src/framework/system/OTASettingsForm.tsx | 2 +- interface/src/project/DashboardDevices.tsx | 1 - interface/src/project/DashboardSensors.tsx | 2 +- interface/src/project/DashboardStatus.tsx | 1 - interface/src/project/SettingsApplication.tsx | 2 +- interface/src/project/SettingsCustomization.tsx | 2 +- interface/src/project/SettingsEntities.tsx | 8 ++++---- interface/src/project/SettingsScheduler.tsx | 6 +++--- 15 files changed, 20 insertions(+), 28 deletions(-) diff --git a/interface/progmem-generator.js b/interface/progmem-generator.js index 905173626..2a96ee410 100644 --- a/interface/progmem-generator.js +++ b/interface/progmem-generator.js @@ -88,7 +88,6 @@ export default function ProgmemGenerator({ outputPath = './WWWData.h', bytesPerL }; const generateWWWClass = () => - // eslint-disable-next-line max-len `typedef std::function RouteRegistrationHandler; class WWWData { diff --git a/interface/src/framework/ap/APSettingsForm.tsx b/interface/src/framework/ap/APSettingsForm.tsx index 48da99907..f43c704d9 100644 --- a/interface/src/framework/ap/APSettingsForm.tsx +++ b/interface/src/framework/ap/APSettingsForm.tsx @@ -174,7 +174,7 @@ const APSettingsForm: FC = () => { variant="outlined" color="primary" type="submit" - onClick={() => loadData()} + onClick={loadData} > {LL.CANCEL()} diff --git a/interface/src/framework/mqtt/MqttSettingsForm.tsx b/interface/src/framework/mqtt/MqttSettingsForm.tsx index 189610cf4..045e3ea79 100644 --- a/interface/src/framework/mqtt/MqttSettingsForm.tsx +++ b/interface/src/framework/mqtt/MqttSettingsForm.tsx @@ -398,7 +398,7 @@ const MqttSettingsForm: FC = () => { variant="outlined" color="primary" type="submit" - onClick={() => loadData()} + onClick={loadData} > {LL.CANCEL()} diff --git a/interface/src/framework/network/NetworkSettingsForm.tsx b/interface/src/framework/network/NetworkSettingsForm.tsx index bc2a768bc..4e4c40912 100644 --- a/interface/src/framework/network/NetworkSettingsForm.tsx +++ b/interface/src/framework/network/NetworkSettingsForm.tsx @@ -298,7 +298,7 @@ const WiFiSettingsForm: FC = () => { variant="outlined" color="primary" type="submit" - onClick={() => loadData()} + onClick={loadData} > {LL.CANCEL()} diff --git a/interface/src/framework/ntp/NTPSettingsForm.tsx b/interface/src/framework/ntp/NTPSettingsForm.tsx index cf450ffe0..56c3b3e0e 100644 --- a/interface/src/framework/ntp/NTPSettingsForm.tsx +++ b/interface/src/framework/ntp/NTPSettingsForm.tsx @@ -95,7 +95,7 @@ const NTPSettingsForm: FC = () => { variant="outlined" color="primary" type="submit" - onClick={() => loadData()} + onClick={loadData} > {LL.CANCEL()} diff --git a/interface/src/framework/security/SecuritySettingsForm.tsx b/interface/src/framework/security/SecuritySettingsForm.tsx index 3a8cab96b..14930d399 100644 --- a/interface/src/framework/security/SecuritySettingsForm.tsx +++ b/interface/src/framework/security/SecuritySettingsForm.tsx @@ -65,7 +65,7 @@ const SecuritySettingsForm: FC = () => { variant="outlined" color="primary" type="submit" - onClick={() => loadData()} + onClick={loadData} > {LL.CANCEL()} diff --git a/interface/src/framework/system/GeneralFileUpload.tsx b/interface/src/framework/system/GeneralFileUpload.tsx index d1ef50e66..11ade4405 100644 --- a/interface/src/framework/system/GeneralFileUpload.tsx +++ b/interface/src/framework/system/GeneralFileUpload.tsx @@ -116,7 +116,7 @@ const GeneralFileUpload: FC = ({ uploadGeneralFile }) => { {LL.DOWNLOAD_SETTINGS_TEXT()} - @@ -124,12 +124,7 @@ const GeneralFileUpload: FC = ({ uploadGeneralFile }) => { {LL.DOWNLOAD_CUSTOMIZATION_TEXT()}{' '} - @@ -146,7 +141,7 @@ const GeneralFileUpload: FC = ({ uploadGeneralFile }) => { {LL.DOWNLOAD_SCHEDULE_TEXT()}{' '} - diff --git a/interface/src/framework/system/OTASettingsForm.tsx b/interface/src/framework/system/OTASettingsForm.tsx index 1b9cd033a..33543f0c9 100644 --- a/interface/src/framework/system/OTASettingsForm.tsx +++ b/interface/src/framework/system/OTASettingsForm.tsx @@ -86,7 +86,7 @@ const OTASettingsForm: FC = () => { variant="outlined" color="primary" type="submit" - onClick={() => loadData()} + onClick={loadData} > {LL.CANCEL()} diff --git a/interface/src/project/DashboardDevices.tsx b/interface/src/project/DashboardDevices.tsx index e35dcef99..3f0c837be 100644 --- a/interface/src/project/DashboardDevices.tsx +++ b/interface/src/project/DashboardDevices.tsx @@ -322,7 +322,6 @@ const DashboardDevices: FC = () => { return () => { clearInterval(timer); }; - // eslint-disable-next-line }, [analog, sensor, deviceValue, sensorData]); const isCmdOnly = (dv: DeviceValue) => dv.v === '' && dv.c; diff --git a/interface/src/project/DashboardSensors.tsx b/interface/src/project/DashboardSensors.tsx index a18d35fd7..2ac954ec2 100644 --- a/interface/src/project/DashboardSensors.tsx +++ b/interface/src/project/DashboardSensors.tsx @@ -446,7 +446,7 @@ const DashboardSensors: FC = () => { )} - + diff --git a/interface/src/project/SettingsCustomization.tsx b/interface/src/project/SettingsCustomization.tsx index edf6228dc..ec1076b24 100644 --- a/interface/src/project/SettingsCustomization.tsx +++ b/interface/src/project/SettingsCustomization.tsx @@ -547,7 +547,7 @@ const SettingsCustomization: FC = () => { startIcon={} variant="contained" color="info" - onClick={() => saveCustomization()} + onClick={saveCustomization} > {LL.APPLY_CHANGES(numChanges)} diff --git a/interface/src/project/SettingsEntities.tsx b/interface/src/project/SettingsEntities.tsx index cb1c93897..0da5ff8e8 100644 --- a/interface/src/project/SettingsEntities.tsx +++ b/interface/src/project/SettingsEntities.tsx @@ -130,7 +130,7 @@ const SettingsEntities: FC = () => { useEffect(() => { void fetchEntities(); - }, [fetchEntities]); + }, []); const saveEntities = async () => { if (entities) { @@ -270,14 +270,14 @@ const SettingsEntities: FC = () => { {numChanges > 0 && ( - @@ -286,7 +286,7 @@ const SettingsEntities: FC = () => { - diff --git a/interface/src/project/SettingsScheduler.tsx b/interface/src/project/SettingsScheduler.tsx index f9d4782f5..4e3da6116 100644 --- a/interface/src/project/SettingsScheduler.tsx +++ b/interface/src/project/SettingsScheduler.tsx @@ -280,14 +280,14 @@ const SettingsScheduler: FC = () => { {numChanges !== 0 && ( - @@ -296,7 +296,7 @@ const SettingsScheduler: FC = () => { - From 1861365124011421e0853b9670c32901bcc5bdc2 Mon Sep 17 00:00:00 2001 From: Proddy Date: Fri, 28 Apr 2023 21:03:29 +0200 Subject: [PATCH 45/89] removed SAVE --- interface/src/i18n/de/index.ts | 1 - interface/src/i18n/en/index.ts | 1 - interface/src/i18n/fr/index.ts | 1 - interface/src/i18n/nl/index.ts | 1 - interface/src/i18n/no/index.ts | 1 - interface/src/i18n/pl/index.ts | 1 - interface/src/i18n/sv/index.ts | 1 - interface/src/i18n/tr/index.ts | 1 - interface/src/project/DashboardDevices.tsx | 230 +++++------------- .../src/project/DashboardDevicesDialog.tsx | 123 ++++++++++ .../DashboardSensorsTemperatureDialog.tsx | 2 +- .../project/SettingsCustomizationDialog.tsx | 6 +- .../src/project/SettingsSchedulerDialog.tsx | 4 +- 13 files changed, 187 insertions(+), 186 deletions(-) create mode 100644 interface/src/project/DashboardDevicesDialog.tsx diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index 27e58e311..2ad6345b5 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -46,7 +46,6 @@ const de: Translation = { CHANGE_VALUE: 'Wert ändern', CANCEL: 'Abbrechen', RESET: 'Zurücksetzen', - SEND: 'Senden', APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate UPDATE: 'Update', // TODO translate REMOVE: 'Entfernen', diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index 11422d5f6..31f8dc798 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -46,7 +46,6 @@ const en: Translation = { CHANGE_VALUE: 'Change Value', CANCEL: 'Cancel', RESET: 'Reset', - SEND: 'Send', APPLY_CHANGES: 'Apply Changes ({0})', UPDATE: 'Update', REMOVE: 'Remove', diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts index 1569300b9..905418d45 100644 --- a/interface/src/i18n/fr/index.ts +++ b/interface/src/i18n/fr/index.ts @@ -46,7 +46,6 @@ const fr: Translation = { CHANGE_VALUE: 'Changer la valeur', CANCEL: 'Annuler', RESET: 'Réinitialiser', - SEND: 'Envoyer', APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate UPDATE: 'Update', // TODO translate REMOVE: 'Enlever', diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index e7970a2a9..1fde01341 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -46,7 +46,6 @@ const nl: Translation = { CHANGE_VALUE: 'Wijzig waarde', CANCEL: 'Annuleren', RESET: 'Reset', - SEND: 'Verzenden', APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate UPDATE: 'Update', // TODO translate REMOVE: 'Verwijderen', diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts index 61f8511c5..880927925 100644 --- a/interface/src/i18n/no/index.ts +++ b/interface/src/i18n/no/index.ts @@ -46,7 +46,6 @@ const no: Translation = { CHANGE_VALUE: 'Endre Verdi', CANCEL: 'Avbryt', RESET: 'Nullstill', - SEND: 'Send', APPLY_CHANGES: 'Utfør endringer({0})', UPDATE: 'Oppdater', REMOVE: 'Fjern', diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index 14f8e62bb..c420d8669 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -46,7 +46,6 @@ const pl: BaseTranslation = { CHANGE_VALUE: 'Zmień wartość', CANCEL: 'Anuluj', RESET: 'Reset{{uj|owanie|}}', - SEND: 'Wyślij', APPLY_CHANGES: 'Zapisz zmiany ({0})', UPDATE: 'Uaktualnij', REMOVE: 'Usuń', diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts index a48d2f19b..2f752c855 100644 --- a/interface/src/i18n/sv/index.ts +++ b/interface/src/i18n/sv/index.ts @@ -46,7 +46,6 @@ const sv: Translation = { CHANGE_VALUE: 'Ändra Värde', CANCEL: 'Avbryt', RESET: 'Nollställ', - SEND: 'Skicka', APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate UPDATE: 'Update', // TODO translate REMOVE: 'Ta bort', diff --git a/interface/src/i18n/tr/index.ts b/interface/src/i18n/tr/index.ts index 8ac4cc23c..da9d53e80 100644 --- a/interface/src/i18n/tr/index.ts +++ b/interface/src/i18n/tr/index.ts @@ -46,7 +46,6 @@ const tr: Translation = { CHANGE_VALUE: 'Değeri Değiştir', CANCEL: 'İptal', RESET: 'Reset', - SEND: 'Gönder', APPLY_CHANGES: 'Apply Changes ({0})', UPDATE: 'Update', REMOVE: 'Kaldır', diff --git a/interface/src/project/DashboardDevices.tsx b/interface/src/project/DashboardDevices.tsx index 3f0c837be..33088ebbd 100644 --- a/interface/src/project/DashboardDevices.tsx +++ b/interface/src/project/DashboardDevices.tsx @@ -1,5 +1,3 @@ -import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined'; -import CancelIcon from '@mui/icons-material/Cancel'; import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined'; import EditIcon from '@mui/icons-material/Edit'; import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined'; @@ -9,27 +7,19 @@ import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDown import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined'; import PlayArrowIcon from '@mui/icons-material/PlayArrow'; import RefreshIcon from '@mui/icons-material/Refresh'; -import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; import StarIcon from '@mui/icons-material/Star'; -import SendIcon from '@mui/icons-material/TrendingFlat'; import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined'; -import WarningIcon from '@mui/icons-material/Warning'; import { Button, Typography, - Box, Dialog, DialogTitle, DialogContent, DialogActions, - MenuItem, - InputAdornment, - FormHelperText, IconButton, List, ListItem, ListItemText, - Grid, FormControlLabel, Checkbox } from '@mui/material'; @@ -41,35 +31,34 @@ import { useState, useContext, useCallback, useEffect } from 'react'; import { IconContext } from 'react-icons'; import { toast } from 'react-toastify'; +import DashboarDevicesDialog from './DashboardDevicesDialog'; import DeviceIcon from './DeviceIcon'; import * as EMSESP from './api'; import { DeviceValueUOM, DeviceValueUOM_s, DeviceEntityMask } from './types'; -import type { SensorData, Device, CoreData, DeviceData, DeviceValue, TemperatureSensor, AnalogSensor } from './types'; +import type { Device, CoreData, DeviceData, DeviceValue } from './types'; import type { FC } from 'react'; -import { ButtonRow, ValidatedTextField, SectionContent, MessageBox } from 'components'; +import { ButtonRow, SectionContent, MessageBox } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; -import { numberValue, updateValue, extractErrorMessage } from 'utils'; +import { extractErrorMessage } from 'utils'; const DashboardDevices: FC = () => { const { me } = useContext(AuthenticatedContext); const { LL } = useI18nContext(); const [deviceData, setDeviceData] = useState({ label: '', data: [] }); - const [deviceValue, setDeviceValue] = useState(); - const [deviceDialog, setDeviceDialog] = useState(-1); + const [selectedDeviceValue, setSelectedDeviceValue] = useState(); + const [deviceDetails, setDeviceDetails] = useState(-1); const [onlyFav, setOnlyFav] = useState(false); + const [selectedDevice, setSelectedDevice] = useState(); + const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false); + const [coreData, setCoreData] = useState({ connected: true, devices: [] }); - const [selectedDevice, setSelectedDevice] = useState(); - - const [sensorData, setSensorData] = useState({ ts: [], as: [] }); - const [analog, setAnalog] = useState(); - const [sensor, setSensor] = useState(); const common_theme = useTheme({ BaseRow: ` @@ -198,14 +187,6 @@ const DashboardDevices: FC = () => { } ); - // const fetchSensorData = async () => { - // try { - // setSensorData((await EMSESP.readSensorData()).data); - // } catch (error) { - // toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); - // } - // }; - const fetchDeviceData = async (id: number) => { try { setDeviceData((await EMSESP.readDeviceData({ id })).data); @@ -224,46 +205,19 @@ const DashboardDevices: FC = () => { useEffect(() => { void fetchCoreData(); - }, [fetchCoreData]); - - // const refreshDataIndex = (selectedDevice: string) => { - // // if (selectedDevice === 'sensor') { - // // void fetchSensorData(); - // // return; - // // } - - // // setSensorData({ sensors: [], analogs: [] }); - // if (selectedDevice) { - // void fetchDeviceData(selectedDevice); - // } else { - // void fetchCoreData(); - // } - // }; + }, []); const refreshData = () => { - // const selectedDevice = device_select.state.id; - // if (selectedDevice === 'sensor') { - // // void fetchSensorData(); - // return; - // } - - // setSensorData({ sensors: [], analogs: [] }); if (selectedDevice) { void fetchDeviceData(selectedDevice); } else { void fetchCoreData(); } - - // refreshDataIndex(device_select.state.id); }; function onSelectChange(action: any, state: any) { setSelectedDevice(device_select.state.id); - if (action.type === 'ADD_BY_ID_EXCLUSIVELY') { - refreshData(); - } else { - // setSensorData({ sensors: [], analogs: [] }); - } + refreshData(); } const escapeCsvCell = (cell: any) => { @@ -322,7 +276,7 @@ const DashboardDevices: FC = () => { return () => { clearInterval(timer); }; - }, [analog, sensor, deviceValue, sensorData]); + }, []); const isCmdOnly = (dv: DeviceValue) => dv.v === '' && dv.c; @@ -375,140 +329,60 @@ const DashboardDevices: FC = () => { } } - const setUom = (uom: number) => { - switch (uom) { - case DeviceValueUOM.HOURS: - return LL.HOURS(); - case DeviceValueUOM.MINUTES: - return LL.MINUTES(); - case DeviceValueUOM.SECONDS: - return LL.SECONDS(); - default: - return DeviceValueUOM_s[uom]; - } - }; - - const sendDeviceValue = async () => { - if (deviceValue) { - try { - const response = await EMSESP.writeDeviceValue({ - id: Number(device_select.state.id), - devicevalue: deviceValue - }); - if (response.status === 204) { - toast.error(LL.WRITE_CMD_FAILED()); - } else if (response.status === 403) { - toast.error(LL.ACCESS_DENIED()); - } else { - toast.success(LL.WRITE_CMD_SENT()); - } - setDeviceValue(undefined); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - } finally { - refreshData(); - setDeviceValue(undefined); + const deviceValueDialogSave = async (dv: DeviceValue) => { + try { + const response = await EMSESP.writeDeviceValue({ + id: Number(device_select.state.id), + devicevalue: dv + }); + if (response.status === 204) { + toast.error(LL.WRITE_CMD_FAILED()); + } else if (response.status === 403) { + toast.error(LL.ACCESS_DENIED()); + } else { + toast.success(LL.WRITE_CMD_SENT()); } + } catch (error) { + toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); + } finally { + setDeviceValueDialogOpen(false); + setSelectedDeviceValue(undefined); + refreshData(); } }; - const renderDeviceValueDialog = () => { - if (deviceValue) { + const renderDeviceDetails = () => { + if (coreData && coreData.devices.length > 0 && deviceDetails !== -1) { return ( - setDeviceValue(undefined)}> - {isCmdOnly(deviceValue) ? LL.RUN_COMMAND() : LL.CHANGE_VALUE()} - - {deviceValue.l && ( - - {deviceValue.l.map((val) => ( - - {val} - - ))} - - )} - {!deviceValue.l && ( - {setUom(deviceValue.u)} - }} - /> - )} - {deviceValue.h && {deviceValue.h}} - - - - - - - ); - } - }; - - const renderDeviceDialog = () => { - if (coreData && coreData.devices.length > 0 && deviceDialog !== -1) { - return ( - setDeviceDialog(-1)}> + setDeviceDetails(-1)}> {LL.DEVICE_DETAILS()} - + - + - + - + - + - @@ -546,7 +420,7 @@ const DashboardDevices: FC = () => { {device.n} {device.e} - setDeviceDialog(index)}> + setDeviceDetails(index)}> @@ -559,14 +433,19 @@ const DashboardDevices: FC = () => { ); + const deviceValueDialogClose = () => { + setDeviceValueDialogOpen(false); + }; + const renderDeviceData = () => { - if (!device_select.state.id || device_select.state.id === 'sensor') { + if (!device_select.state.id) { return; } const sendCommand = (dv: DeviceValue) => { if (dv.c && me.admin && !hasMask(dv.id, DeviceEntityMask.DV_READONLY)) { - setDeviceValue(dv); + setSelectedDeviceValue(dv); + setDeviceValueDialogOpen(true); } }; @@ -662,8 +541,17 @@ const DashboardDevices: FC = () => { {renderCoreData()} {renderDeviceData()} - {renderDeviceDialog()} - {renderDeviceValueDialog()} + {renderDeviceDetails()} + {console.log('redndering device data')} + + {selectedDeviceValue && ( + + )} + + + + ); +}; + +export default DashboarDevicesDialog; diff --git a/interface/src/project/DashboardSensorsTemperatureDialog.tsx b/interface/src/project/DashboardSensorsTemperatureDialog.tsx index ddda88a3c..3089dc363 100644 --- a/interface/src/project/DashboardSensorsTemperatureDialog.tsx +++ b/interface/src/project/DashboardSensorsTemperatureDialog.tsx @@ -69,7 +69,7 @@ const DashboardSensorsTemperatureDialog = ({ return ( - {LL.EDIT()} {LL.TEMP_SENSOR()} + {LL.EDIT()} {LL.TEMP_SENSOR()} diff --git a/interface/src/project/SettingsCustomizationDialog.tsx b/interface/src/project/SettingsCustomizationDialog.tsx index 3e23d44f3..8d48f57fc 100644 --- a/interface/src/project/SettingsCustomizationDialog.tsx +++ b/interface/src/project/SettingsCustomizationDialog.tsx @@ -67,13 +67,11 @@ const SettingsCustomizationDialog = ({ open, onClose, onSave, selectedItem }: Se {LL.EDIT() + ' ' + LL.ENTITY()} - - {LL.ENTITY()}: {editItem.id} - + {editItem.id} - {LL.DEFAULT(1) + ' ' + LL.NAME(1)}: {editItem.n} + {LL.DEFAULT(1) + ' ' + LL.ENTITY_NAME()}: {editItem.n} diff --git a/interface/src/project/SettingsSchedulerDialog.tsx b/interface/src/project/SettingsSchedulerDialog.tsx index 7c360a874..514b36610 100644 --- a/interface/src/project/SettingsSchedulerDialog.tsx +++ b/interface/src/project/SettingsSchedulerDialog.tsx @@ -131,7 +131,7 @@ const SettingsSchedulerDialog = ({ return ( - {creating ? LL.ADD(1) + ' ' + LL.NEW(1) : LL.EDIT()} {LL.ENTITY()} + {creating ? LL.ADD(1) + ' ' + LL.NEW(1) : LL.EDIT()} {LL.SCHEDULE(0)} @@ -215,7 +215,7 @@ const SettingsSchedulerDialog = ({ /> Date: Sat, 29 Apr 2023 14:34:19 +0200 Subject: [PATCH 46/89] use blocker from main lib --- interface/src/components/routing/BlockNavigation.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/components/routing/BlockNavigation.tsx b/interface/src/components/routing/BlockNavigation.tsx index 445cc7463..c8986c14a 100644 --- a/interface/src/components/routing/BlockNavigation.tsx +++ b/interface/src/components/routing/BlockNavigation.tsx @@ -1,7 +1,8 @@ import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material'; -import type { Blocker } from '@remix-run/router'; import type { FC } from 'react'; +import type { unstable_Blocker as Blocker } from 'react-router-dom'; + import { useI18nContext } from 'i18n/i18n-react'; interface BlockNavigationProps { From 26b0c67d1376b9cfa98476600cd135eee8213c54 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 29 Apr 2023 14:34:34 +0200 Subject: [PATCH 47/89] update packages, but not router as it breaks --- interface/package.json | 3 +-- interface/yarn.lock | 48 +++++++----------------------------------- 2 files changed, 9 insertions(+), 42 deletions(-) diff --git a/interface/package.json b/interface/package.json index 517e0bb70..8b6c24f2a 100644 --- a/interface/package.json +++ b/interface/package.json @@ -25,10 +25,9 @@ "@msgpack/msgpack": "^3.0.0-beta2", "@mui/icons-material": "^5.11.16", "@mui/material": "^5.12.2", - "@remix-run/router": "^1.5.0", "@table-library/react-table-library": "4.1.2", "@types/lodash-es": "^4.17.7", - "@types/node": "^18.16.2", + "@types/node": "^18.16.3", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.1", "@types/react-router-dom": "^5.3.3", diff --git a/interface/yarn.lock b/interface/yarn.lock index 0a45e4697..393712080 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -270,20 +270,7 @@ __metadata: languageName: node linkType: hard -"@emotion/cache@npm:^11.10.7": - version: 11.10.7 - resolution: "@emotion/cache@npm:11.10.7" - dependencies: - "@emotion/memoize": ^0.8.0 - "@emotion/sheet": ^1.2.1 - "@emotion/utils": ^1.2.0 - "@emotion/weak-memoize": ^0.3.0 - stylis: 4.1.3 - checksum: 0175b8be5117342e76e100fca92932a34c3641160c733a34534153eab7f1c1b2cecafee6d9a7a0646acf7be3c52b0654dc34900439316ae473b59a9eb1a1c8f3 - languageName: node - linkType: hard - -"@emotion/cache@npm:^11.10.8": +"@emotion/cache@npm:^11.10.7, @emotion/cache@npm:^11.10.8": version: 11.10.8 resolution: "@emotion/cache@npm:11.10.8" dependencies: @@ -947,7 +934,7 @@ __metadata: languageName: node linkType: hard -"@remix-run/router@npm:1.5.0, @remix-run/router@npm:^1.5.0": +"@remix-run/router@npm:1.5.0": version: 1.5.0 resolution: "@remix-run/router@npm:1.5.0" checksum: 63c3695df0470943213f8144183501bbb6a8176671f2ed4547ffa1852f79fd71054b4b4a715795e9e250198991d9e6e121b3fcab9c27df37871dcdfbc8de83a1 @@ -1277,10 +1264,10 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^18.16.2": - version: 18.16.2 - resolution: "@types/node@npm:18.16.2" - checksum: ddcaecb88ffd85c9d6780d19839c40f00719d8159390225daed84a1c30cdbee50fe6ecef2f9c69010d07b7af3358bb800801f2f312fc985e8f183289eb32c6ad +"@types/node@npm:^18.16.3": + version: 18.16.3 + resolution: "@types/node@npm:18.16.3" + checksum: 8405ceea1306790484e15f696be5e9f7f62b9bb385b2f03fcdefd07fcc2cb20052ebf5c1ffde7b81a0090a09454a48a685f22e1704ea7ead99971233e6f0d80d languageName: node linkType: hard @@ -1346,18 +1333,7 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*": - version: 18.0.33 - resolution: "@types/react@npm:18.0.33" - dependencies: - "@types/prop-types": "*" - "@types/scheduler": "*" - csstype: ^3.0.2 - checksum: 12610df107eeac48d63f23c64b9c2f91acf6413faa9868e374433b1bab7a27ce95b0a0198b0712da34e2a1672ce43e04fa0b484e81e985baae3b056e204e27ac - languageName: node - linkType: hard - -"@types/react@npm:^18.2.0": +"@types/react@npm:*, @types/react@npm:^18.2.0": version: 18.2.0 resolution: "@types/react@npm:18.2.0" dependencies: @@ -1523,10 +1499,9 @@ __metadata: "@msgpack/msgpack": ^3.0.0-beta2 "@mui/icons-material": ^5.11.16 "@mui/material": ^5.12.2 - "@remix-run/router": ^1.5.0 "@table-library/react-table-library": 4.1.2 "@types/lodash-es": ^4.17.7 - "@types/node": ^18.16.2 + "@types/node": ^18.16.3 "@types/react": ^18.2.0 "@types/react-dom": ^18.2.1 "@types/react-router-dom": ^5.3.3 @@ -5419,13 +5394,6 @@ __metadata: languageName: node linkType: hard -"stylis@npm:4.1.3": - version: 4.1.3 - resolution: "stylis@npm:4.1.3" - checksum: 3e4670f26f79bcfba628dcc2756d9d415edfcbf4ec51e40f3b628fd15286222257317cad57390752964eba85cca6163a7621ce90038d68dd630a674479e52334 - languageName: node - linkType: hard - "stylis@npm:4.1.4": version: 4.1.4 resolution: "stylis@npm:4.1.4" From ff058b06a1195160fb85961af6f127b48886f23e Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 29 Apr 2023 14:34:48 +0200 Subject: [PATCH 48/89] changed comment --- src/emsdevice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 86943c6df..7c6f45fe8 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -838,7 +838,7 @@ void EMSdevice::generate_values_web(JsonObject & output) { JsonObject obj = data.createNestedObject(); // create the object, we know there is a value uint8_t fahrenheit = 0; - // handle Booleans (true, false), use strings, no native true/false) + // handle Booleans (true, false), use strings, not native true/false) if (dv.type == DeviceValueType::BOOL) { auto value_b = (bool)*(uint8_t *)(dv.value_p); char s[12]; From d06dc3e2cff552beaf15fd9fbfcb436fe4839a43 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 29 Apr 2023 14:34:56 +0200 Subject: [PATCH 49/89] fic dialog deviceentities --- interface/src/project/DashboardDevices.tsx | 89 +++++-------------- .../src/project/DashboardDevicesDialog.tsx | 53 +++++++---- interface/src/project/deviceValue.ts | 85 ++++++++++++++++++ interface/src/project/validators.ts | 15 ++++ mock-api/server.js | 48 +++++----- 5 files changed, 183 insertions(+), 107 deletions(-) create mode 100644 interface/src/project/deviceValue.ts diff --git a/interface/src/project/DashboardDevices.tsx b/interface/src/project/DashboardDevices.tsx index 33088ebbd..1760f22b1 100644 --- a/interface/src/project/DashboardDevices.tsx +++ b/interface/src/project/DashboardDevices.tsx @@ -27,7 +27,7 @@ import { useRowSelect } from '@table-library/react-table-library/select'; import { useSort, SortToggleType } from '@table-library/react-table-library/sort'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; -import { useState, useContext, useCallback, useEffect } from 'react'; +import { useState, useContext, useEffect } from 'react'; import { IconContext } from 'react-icons'; import { toast } from 'react-toastify'; @@ -35,8 +35,10 @@ import DashboarDevicesDialog from './DashboardDevicesDialog'; import DeviceIcon from './DeviceIcon'; import * as EMSESP from './api'; +import { formatValue, isNumberUOM } from './deviceValue'; -import { DeviceValueUOM, DeviceValueUOM_s, DeviceEntityMask } from './types'; +import { DeviceValueUOM_s, DeviceEntityMask } from './types'; +import { deviceValueItemValidation } from './validators'; import type { Device, CoreData, DeviceData, DeviceValue } from './types'; import type { FC } from 'react'; import { ButtonRow, SectionContent, MessageBox } from 'components'; @@ -174,19 +176,12 @@ const DashboardDevices: FC = () => { }, sortToggleType: SortToggleType.AlternateWithReset, sortFns: { - NAME: (array) => array.sort((a, b) => a.id.slice(2).localeCompare(b.id.slice(2))), + NAME: (array) => array.sort((a, b) => a.id.toString().slice(2).localeCompare(b.id.toString().slice(2))), VALUE: (array) => array.sort((a, b) => a.v.toString().localeCompare(b.v.toString())) } } ); - const device_select = useRowSelect( - { nodes: coreData.devices }, - { - onChange: onSelectChange - } - ); - const fetchDeviceData = async (id: number) => { try { setDeviceData((await EMSESP.readDeviceData({ id })).data); @@ -195,13 +190,13 @@ const DashboardDevices: FC = () => { } }; - const fetchCoreData = useCallback(async () => { + const fetchCoreData = async () => { try { setCoreData((await EMSESP.readCoreData()).data); } catch (error) { toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); } - }, [LL]); + }; useEffect(() => { void fetchCoreData(); @@ -216,10 +211,19 @@ const DashboardDevices: FC = () => { }; function onSelectChange(action: any, state: any) { - setSelectedDevice(device_select.state.id); - refreshData(); + setSelectedDevice(state.id); + if (action.type === 'ADD_BY_ID_EXCLUSIVELY') { + void fetchDeviceData(state.id); + } } + const device_select = useRowSelect( + { nodes: coreData.devices }, + { + onChange: onSelectChange + } + ); + const escapeCsvCell = (cell: any) => { if (cell == null) { return ''; @@ -278,57 +282,6 @@ const DashboardDevices: FC = () => { }; }, []); - const isCmdOnly = (dv: DeviceValue) => dv.v === '' && dv.c; - - const formatDurationMin = (duration_min: number) => { - const days = Math.trunc((duration_min * 60000) / 86400000); - const hours = Math.trunc((duration_min * 60000) / 3600000) % 24; - const minutes = Math.trunc((duration_min * 60000) / 60000) % 60; - - let formatted = ''; - if (days) { - formatted += LL.NUM_DAYS({ num: days }) + ' '; - } - if (hours) { - formatted += LL.NUM_HOURS({ num: hours }) + ' '; - } - if (minutes) { - formatted += LL.NUM_MINUTES({ num: minutes }); - } - return formatted; - }; - - function formatValue(value: any, uom: number) { - if (value === undefined) { - return ''; - } - switch (uom) { - case DeviceValueUOM.HOURS: - return value ? formatDurationMin(value * 60) : LL.NUM_HOURS({ num: 0 }); - case DeviceValueUOM.MINUTES: - return value ? formatDurationMin(value) : LL.NUM_MINUTES({ num: 0 }); - case DeviceValueUOM.SECONDS: - return LL.NUM_SECONDS({ num: value }); - case DeviceValueUOM.NONE: - if (typeof value === 'number') { - return new Intl.NumberFormat().format(value); - } - return value; - case DeviceValueUOM.DEGREES: - case DeviceValueUOM.DEGREES_R: - case DeviceValueUOM.FAHRENHEIT: - return ( - new Intl.NumberFormat(undefined, { - minimumFractionDigits: 1 - }).format(value) + - ' ' + - DeviceValueUOM_s[uom] - ); - default: - return new Intl.NumberFormat().format(value) + ' ' + DeviceValueUOM_s[uom]; - } - } - const deviceValueDialogSave = async (dv: DeviceValue) => { try { const response = await EMSESP.writeDeviceValue({ @@ -515,11 +468,11 @@ const DashboardDevices: FC = () => { {tableList.map((dv: DeviceValue) => ( sendCommand(dv)}> {renderNameCell(dv)} - {formatValue(dv.v, dv.u)} + {formatValue(LL, dv.v, dv.u)} {dv.c && me.admin && !hasMask(dv.id, DeviceEntityMask.DV_READONLY) && ( sendCommand(dv)}> - {isCmdOnly(dv) ? ( + {dv.v === '' && dv.c ? ( ) : ( @@ -542,7 +495,6 @@ const DashboardDevices: FC = () => { {renderCoreData()} {renderDeviceData()} {renderDeviceDetails()} - {console.log('redndering device data')} {selectedDeviceValue && ( { onClose={deviceValueDialogClose} onSave={deviceValueDialogSave} selectedItem={selectedDeviceValue} + validator={deviceValueItemValidation(isNumberUOM(selectedDeviceValue.u))} /> )} diff --git a/interface/src/project/DashboardDevicesDialog.tsx b/interface/src/project/DashboardDevicesDialog.tsx index c9670c1da..9631d0335 100644 --- a/interface/src/project/DashboardDevicesDialog.tsx +++ b/interface/src/project/DashboardDevicesDialog.tsx @@ -11,31 +11,48 @@ import { MenuItem, TextField, FormHelperText, - Grid + Grid, + Box, + Typography } from '@mui/material'; import { useState, useEffect } from 'react'; +import { formatValueNoUOM } from './deviceValue'; import { DeviceValueUOM, DeviceValueUOM_s } from './types'; import type { DeviceValue } from './types'; +import type Schema from 'async-validator'; +import type { ValidateFieldsError } from 'async-validator'; +import { ValidatedTextField } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; import { updateValue } from 'utils'; +import { validate } from 'validators'; + type DashboardDevicesDialogProps = { open: boolean; onClose: () => void; onSave: (as: DeviceValue) => void; selectedItem: DeviceValue; + validator: Schema; }; -const DashboarDevicesDialog = ({ open, onClose, onSave, selectedItem }: DashboardDevicesDialogProps) => { +const DashboarDevicesDialog = ({ open, onClose, onSave, selectedItem, validator }: DashboardDevicesDialogProps) => { const { LL } = useI18nContext(); const [editItem, setEditItem] = useState(selectedItem); + const [fieldErrors, setFieldErrors] = useState(); + const updateFormValue = updateValue(setEditItem); useEffect(() => { if (open) { + setFieldErrors(undefined); setEditItem(selectedItem); + // format value and convert to string + setEditItem({ + ...selectedItem, + v: formatValueNoUOM(selectedItem.v, selectedItem.u) + }); } }, [open, selectedItem]); @@ -43,12 +60,16 @@ const DashboarDevicesDialog = ({ open, onClose, onSave, selectedItem }: Dashboar onClose(); }; - const save = () => { - onSave(editItem); + const save = async () => { + try { + setFieldErrors(undefined); + await validate(validator, editItem); + onSave(editItem); + } catch (errors: any) { + setFieldErrors(errors); + } }; - const isCmdOnly = (dv: DeviceValue) => dv.v === '' && dv.c; - const setUom = (uom: number) => { switch (uom) { case DeviceValueUOM.HOURS: @@ -64,16 +85,17 @@ const DashboarDevicesDialog = ({ open, onClose, onSave, selectedItem }: Dashboar return ( - - {isCmdOnly(editItem) ? LL.RUN_COMMAND() : LL.CHANGE_VALUE()} - + {selectedItem.v === '' && selectedItem.c ? LL.RUN_COMMAND() : LL.CHANGE_VALUE()} + + {editItem.id.slice(2)} + {editItem.l && ( )} {!editItem.l && ( - {setUom(editItem.u)} }} diff --git a/interface/src/project/deviceValue.ts b/interface/src/project/deviceValue.ts new file mode 100644 index 000000000..0c1088349 --- /dev/null +++ b/interface/src/project/deviceValue.ts @@ -0,0 +1,85 @@ +import { DeviceValueUOM, DeviceValueUOM_s } from './types'; +import type { TranslationFunctions } from 'i18n/i18n-types'; + +const formatDurationMin = (LL: TranslationFunctions, duration_min: number) => { + const days = Math.trunc((duration_min * 60000) / 86400000); + const hours = Math.trunc((duration_min * 60000) / 3600000) % 24; + const minutes = Math.trunc((duration_min * 60000) / 60000) % 60; + + let formatted = ''; + if (days) { + formatted += LL.NUM_DAYS({ num: days }); + } + + if (hours) { + if (formatted) formatted += ' '; + formatted += LL.NUM_HOURS({ num: hours }); + } + + if (minutes) { + if (formatted) formatted += ' '; + formatted += LL.NUM_MINUTES({ num: minutes }); + } + + return formatted; +}; + +export function formatValue(LL: TranslationFunctions, value: any, uom: number) { + if (value === undefined) { + return ''; + } + switch (uom) { + case DeviceValueUOM.HOURS: + return value ? formatDurationMin(LL, value * 60) : LL.NUM_HOURS({ num: 0 }); + case DeviceValueUOM.MINUTES: + return value ? formatDurationMin(LL, value) : LL.NUM_MINUTES({ num: 0 }); + case DeviceValueUOM.SECONDS: + return LL.NUM_SECONDS({ num: value }); + case DeviceValueUOM.NONE: + if (typeof value === 'number') { + return new Intl.NumberFormat().format(value); + } + return value; + case DeviceValueUOM.DEGREES: + case DeviceValueUOM.DEGREES_R: + case DeviceValueUOM.FAHRENHEIT: + return ( + new Intl.NumberFormat(undefined, { + minimumFractionDigits: 1 + }).format(value) + + ' ' + + DeviceValueUOM_s[uom] + ); + default: + return new Intl.NumberFormat().format(value) + ' ' + DeviceValueUOM_s[uom]; + } +} + +export const formatValueNoUOM = (value: any, uom: number) => { + if (value === undefined) { + return ''; + } + + switch (uom) { + case DeviceValueUOM.NONE: + if (typeof value === 'number') { + return new Intl.NumberFormat().format(value); + } + return value; + case DeviceValueUOM.DEGREES: + case DeviceValueUOM.DEGREES_R: + case DeviceValueUOM.FAHRENHEIT: + return new Intl.NumberFormat(undefined, { + minimumFractionDigits: 1 + }).format(Number(value)); + default: + return value; + } +}; + +export function isNumberUOM(uom: number) { + if (uom === DeviceValueUOM.NONE) { + return false; + } + return true; +} diff --git a/interface/src/project/validators.ts b/interface/src/project/validators.ts index 33f958da5..d9b65bf8a 100644 --- a/interface/src/project/validators.ts +++ b/interface/src/project/validators.ts @@ -161,3 +161,18 @@ export const analogSensorItemValidation = (sensors: AnalogSensor[], creating: bo ...(creating ? [isGPIOUniqueValidator(sensors)] : []) ] }); + +export const deviceValueItemValidation = (isNumber: boolean) => + new Schema({ + v: [ + { required: true, message: 'Value is required' }, + { + validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) { + if (isNumber && isNaN(+value)) { + callback('Not a valid number'); + } + callback(); + } + } + ] + }); diff --git a/mock-api/server.js b/mock-api/server.js index d91e23327..7016c4757 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -488,7 +488,8 @@ const emsesp_devicedata_1 = { v: 'auto', u: 0, id: '00hc1 mode', - c: 'hc1/mode' + c: 'hc1/mode', + l: ['off', 'on', 'auto'] } ] }; @@ -497,8 +498,8 @@ const emsesp_devicedata_2 = { label: 'Boiler: Nefit GBx72/Trendline/Cerapur/Greenstar Si/27i', data: [ { v: '', u: 0, id: '08reset', c: 'reset', l: ['-', 'maintenance', 'error'] }, - { v: 'false', u: 0, id: '08heating active' }, - { v: 'false', u: 0, id: '04tapwater active' }, + { v: 'off', u: 0, id: '08heating active' }, + { v: 'off', u: 0, id: '04tapwater active' }, { v: 5, u: 1, id: '04selected flow temperature', c: 'selflowtemp' }, { v: 0, u: 3, id: '0Eburner selected max power', c: 'selburnpow' }, { v: 0, u: 3, id: '00heating pump modulation' }, @@ -506,14 +507,14 @@ const emsesp_devicedata_2 = { { v: 52.7, u: 1, id: '00return temperature' }, { v: 1.3, u: 10, id: '00system pressure' }, { v: 54.9, u: 1, id: '00actual boiler temperature' }, - { v: 'false', u: 0, id: '00gas' }, - { v: 'false', u: 0, id: '00gas stage 2' }, + { v: 'off', u: 0, id: '00gas' }, + { v: 'off', u: 0, id: '00gas stage 2' }, { v: 0, u: 9, id: '00flame current' }, - { v: 'false', u: 0, id: '00heating pump' }, - { v: 'false', u: 0, id: '00fan' }, - { v: 'false', u: 0, id: '00ignition' }, - { v: 'false', u: 0, id: '00oil preheating' }, - { v: 'true', u: 0, id: '00heating activated', c: 'heatingactivated', l: ['off', 'on'] }, + { v: 'off', u: 0, id: '00heating pump' }, + { v: 'off', u: 0, id: '00fan' }, + { v: 'off', u: 0, id: '00ignition' }, + { v: 'off', u: 0, id: '00oil preheating' }, + { v: 'on', u: 0, id: '00heating activated', c: 'heatingactivated', l: ['off', 'on'] }, { v: 80, u: 1, id: '00heating temperature', c: 'heatingtemp' }, { v: 70, u: 3, id: '00burner pump max power', c: 'pumpmodmax' }, { v: 30, u: 3, id: '00burner pump min power', c: 'pumpmodmin' }, @@ -537,14 +538,14 @@ const emsesp_devicedata_2 = { { v: 'manual', u: 0, id: '00maintenance scheduled', c: 'maintenance', l: ['off', 'time', 'date', 'manual'] }, { v: 6000, u: 7, id: '00time to next maintenance', c: 'maintenancetime' }, { v: '01.01.2012', u: 0, id: '00next maintenance date', c: 'maintenancedate', o: 'Format: < dd.mm.yyyy >' }, - { v: 'true', u: 0, id: '00dhw turn on/off', c: 'wwtapactivated', l: ['off', 'on'] }, + { v: 'on', u: 0, id: '00dhw turn on/off', c: 'wwtapactivated', l: ['off', 'on'] }, { v: 62, u: 1, id: '00dhw set temperature' }, { v: 60, u: 1, id: '00dhw selected temperature', c: 'wwseltemp' }, { v: 'flow', u: 0, id: '00dhw type' }, { v: 'hot', u: 0, id: '00dhw comfort', c: 'wwcomfort', l: ['hot', 'eco', 'intelligent'] }, { v: 40, u: 2, id: '00dhw flow temperature offset', c: 'wwflowtempoffset' }, { v: 100, u: 3, id: '00dhw max power', c: 'wwmaxpower' }, - { v: 'false', u: 0, id: '00dhw circulation pump available', c: 'wwcircpump', l: ['off', 'on'] }, + { v: 'off', u: 0, id: '00dhw circulation pump available', c: 'wwcircpump', l: ['off', 'on'] }, { v: '3-way valve', u: 0, id: '00dhw charging type' }, { v: -5, u: 2, id: '00dhw hysteresis on temperature', c: 'wwhyston' }, { v: 0, u: 2, id: '00dhw hysteresis off temperature', c: 'wwhystoff' }, @@ -556,18 +557,18 @@ const emsesp_devicedata_2 = { c: 'wwcircmode', l: ['off', '1x3min', '2x3min', '3x3min', '4x3min', '5x3min', '6x3min', 'continuous'] }, - { v: 'false', u: 0, id: '00dhw circulation active', c: 'wwcirc', l: ['off', 'on'] }, + { v: 'off', u: 0, id: '00dhw circulation active', c: 'wwcirc', l: ['off', 'on'] }, { v: 47.3, u: 1, id: '00dhw current intern temperature' }, { v: 0, u: 4, id: '00dhw current tap water flow' }, { v: 47.3, u: 1, id: '00dhw storage intern temperature' }, - { v: 'true', u: 0, id: '00dhw activated', c: 'wwactivated', l: ['off', 'on'] }, - { v: 'false', u: 0, id: '00dhw one time charging', c: 'wwonetime', l: ['off', 'on'] }, - { v: 'false', u: 0, id: '00dhw disinfecting', c: 'wwdisinfecting', l: ['off', 'on'] }, - { v: 'false', u: 0, id: '00dhw charging' }, - { v: 'false', u: 0, id: '00dhw recharging' }, - { v: 'true', u: 0, id: '00dhw temperature ok' }, - { v: 'false', u: 0, id: '00dhw active' }, - { v: 'true', u: 0, id: '00dhw 3way valve active' }, + { v: 'on', u: 0, id: '00dhw activated', c: 'wwactivated', l: ['off', 'on'] }, + { v: 'off', u: 0, id: '00dhw one time charging', c: 'wwonetime', l: ['off', 'on'] }, + { v: 'off', u: 0, id: '00dhw disinfecting', c: 'wwdisinfecting', l: ['off', 'on'] }, + { v: 'off', u: 0, id: '00dhw charging' }, + { v: 'off', u: 0, id: '00dhw recharging' }, + { v: 'on', u: 0, id: '00dhw temperature ok' }, + { v: 'off', u: 0, id: '00dhw active' }, + { v: 'on', u: 0, id: '00dhw 3way valve active' }, { v: 0, u: 3, id: '00dhw set pump power' }, { v: 288768, u: 0, id: '00dhw starts' }, { v: 102151, u: 8, id: '00dhw active time' } @@ -593,7 +594,8 @@ const emsesp_devicedata_4 = { v: 'off', u: 0, id: '02hc2 mode', - c: 'hc2/mode' + c: 'hc2/mode', + l: ['off', 'on', 'auto'] } ] }; @@ -963,7 +965,7 @@ rest_server.get(EMSESP_CORE_DATA_ENDPOINT, (req, res) => { }); rest_server.get(EMSESP_SENSOR_DATA_ENDPOINT, (req, res) => { console.log('send back sensor data...'); - console.log(emsesp_sensordata); + // console.log(emsesp_sensordata); res.json(emsesp_sensordata); }); rest_server.get(EMSESP_DEVICES_ENDPOINT, (req, res) => { From 90a719561b464979c3b0c19e2682470488cd5204 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 29 Apr 2023 15:35:33 +0200 Subject: [PATCH 50/89] remove comment --- interface/src/framework/ap/AccessPoint.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/framework/ap/AccessPoint.tsx b/interface/src/framework/ap/AccessPoint.tsx index 749aafea7..87fecefb3 100644 --- a/interface/src/framework/ap/AccessPoint.tsx +++ b/interface/src/framework/ap/AccessPoint.tsx @@ -36,7 +36,6 @@ const AccessPoint: FC = () => { } /> - {/* } /> */} } /> From 2254bf9c1664cee1a3acdf0d405317d49294200a Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 29 Apr 2023 15:35:54 +0200 Subject: [PATCH 51/89] fix eslint warnings --- interface/src/framework/system/SystemLog.tsx | 6 +++--- interface/src/i18n/formatters.ts | 2 +- interface/src/project/DashboardDevices.tsx | 20 ++++++++++--------- interface/src/project/DashboardSensors.tsx | 18 +++++++++-------- .../project/DashboardSensorsAnalogDialog.tsx | 4 ++-- interface/src/project/DashboardStatus.tsx | 2 +- interface/src/project/SettingsEntities.tsx | 2 +- interface/src/project/SettingsScheduler.tsx | 1 - interface/src/utils/useWs.ts | 1 + mock-api/server.js | 1 + 10 files changed, 31 insertions(+), 26 deletions(-) diff --git a/interface/src/framework/system/SystemLog.tsx b/interface/src/framework/system/SystemLog.tsx index 8390e7d37..8d44f7603 100644 --- a/interface/src/framework/system/SystemLog.tsx +++ b/interface/src/framework/system/SystemLog.tsx @@ -31,7 +31,7 @@ const useWindowSize = () => { return size; }; -const LogEntryLine = styled('div')(({ theme }) => ({ +const LogEntryLine = styled('div')(() => ({ color: '#bbbbbb', fontFamily: 'monospace', fontSize: '14px', @@ -125,7 +125,7 @@ const SystemLog: FC = () => { useEffect(() => { void fetchLog(); - }, []); + }, [fetchLog]); useEffect(() => { const es = new EventSource(addAccessTokenParameter(LOG_EVENTSOURCE_URL)); @@ -138,7 +138,7 @@ const SystemLog: FC = () => { return () => { es.close(); }; - }, []); + }); const saveSettings = async () => { if (data) { diff --git a/interface/src/i18n/formatters.ts b/interface/src/i18n/formatters.ts index 592a53490..6818d3da7 100644 --- a/interface/src/i18n/formatters.ts +++ b/interface/src/i18n/formatters.ts @@ -1,7 +1,7 @@ import type { Locales, Formatters } from './i18n-types'; import type { FormattersInitializer } from 'typesafe-i18n'; -export const initFormatters: FormattersInitializer = (locale: Locales) => { +export const initFormatters: FormattersInitializer = () => { const formatters: Formatters = { // add your formatter functions here }; diff --git a/interface/src/project/DashboardDevices.tsx b/interface/src/project/DashboardDevices.tsx index 1760f22b1..51e55f1e0 100644 --- a/interface/src/project/DashboardDevices.tsx +++ b/interface/src/project/DashboardDevices.tsx @@ -27,7 +27,7 @@ import { useRowSelect } from '@table-library/react-table-library/select'; import { useSort, SortToggleType } from '@table-library/react-table-library/sort'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; -import { useState, useContext, useEffect } from 'react'; +import { useState, useContext, useEffect, useCallback } from 'react'; import { IconContext } from 'react-icons'; import { toast } from 'react-toastify'; @@ -183,24 +183,26 @@ const DashboardDevices: FC = () => { ); const fetchDeviceData = async (id: number) => { - try { - setDeviceData((await EMSESP.readDeviceData({ id })).data); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); + if (!deviceValueDialogOpen) { + try { + setDeviceData((await EMSESP.readDeviceData({ id })).data); + } catch (error) { + toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); + } } }; - const fetchCoreData = async () => { + const fetchCoreData = useCallback(async () => { try { setCoreData((await EMSESP.readCoreData()).data); } catch (error) { toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); } - }; + }, [LL]); useEffect(() => { void fetchCoreData(); - }, []); + }, [fetchCoreData]); const refreshData = () => { if (selectedDevice) { @@ -280,7 +282,7 @@ const DashboardDevices: FC = () => { return () => { clearInterval(timer); }; - }, []); + }); const deviceValueDialogSave = async (dv: DeviceValue) => { try { diff --git a/interface/src/project/DashboardSensors.tsx b/interface/src/project/DashboardSensors.tsx index 2ac954ec2..b43cf91b6 100644 --- a/interface/src/project/DashboardSensors.tsx +++ b/interface/src/project/DashboardSensors.tsx @@ -102,16 +102,18 @@ const DashboardSensors: FC = () => { ]); const fetchSensorData = useCallback(async () => { - try { - setSensorData((await EMSESP.readSensorData()).data); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); + if (!analogDialogOpen && !temperatureDialogOpen) { + try { + setSensorData((await EMSESP.readSensorData()).data); + } catch (error) { + toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); + } } - }, [LL]); + }, [LL, analogDialogOpen, temperatureDialogOpen]); useEffect(() => { void fetchSensorData(); - }, []); + }, [fetchSensorData]); const getSortIcon = (state: any, sortKey: any) => { if (state.sortKey === sortKey && state.reverse) { @@ -160,11 +162,11 @@ const DashboardSensors: FC = () => { ); useEffect(() => { - const timer = setInterval(() => fetchSensorData(), 60000); + const timer = setInterval(() => fetchSensorData(), 30000); return () => { clearInterval(timer); }; - }, [fetchSensorData]); + }); const formatDurationMin = (duration_min: number) => { const days = Math.trunc((duration_min * 60000) / 86400000); diff --git a/interface/src/project/DashboardSensorsAnalogDialog.tsx b/interface/src/project/DashboardSensorsAnalogDialog.tsx index 2964e4f5f..35d4a0b44 100644 --- a/interface/src/project/DashboardSensorsAnalogDialog.tsx +++ b/interface/src/project/DashboardSensorsAnalogDialog.tsx @@ -213,7 +213,7 @@ const DashboardSensorsAnalogDialog = ({ name="f" label={LL.FREQ()} value={numberValue(editItem.f)} - // fullWidth + fullWidth type="number" variant="outlined" onChange={updateFormValue} @@ -228,7 +228,7 @@ const DashboardSensorsAnalogDialog = ({ name="o" label={LL.DUTY_CYCLE()} value={numberValue(editItem.o)} - // fullWidth + fullWidth type="number" variant="outlined" onChange={updateFormValue} diff --git a/interface/src/project/DashboardStatus.tsx b/interface/src/project/DashboardStatus.tsx index 3c23a6adb..887a142d8 100644 --- a/interface/src/project/DashboardStatus.tsx +++ b/interface/src/project/DashboardStatus.tsx @@ -116,7 +116,7 @@ const DashboardStatus: FC = () => { return () => { clearInterval(timer); }; - }, []); + }); const showName = (id: any) => { const name: keyof Translation['STATUS_NAMES'] = id; diff --git a/interface/src/project/SettingsEntities.tsx b/interface/src/project/SettingsEntities.tsx index 0da5ff8e8..8093107ac 100644 --- a/interface/src/project/SettingsEntities.tsx +++ b/interface/src/project/SettingsEntities.tsx @@ -130,7 +130,7 @@ const SettingsEntities: FC = () => { useEffect(() => { void fetchEntities(); - }, []); + }, [fetchEntities]); const saveEntities = async () => { if (entities) { diff --git a/interface/src/project/SettingsScheduler.tsx b/interface/src/project/SettingsScheduler.tsx index 4e3da6116..59fdee05b 100644 --- a/interface/src/project/SettingsScheduler.tsx +++ b/interface/src/project/SettingsScheduler.tsx @@ -117,7 +117,6 @@ const SettingsScheduler: FC = () => { } }, [LL]); - // on mount useEffect(() => { const formatter = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' }); const days = [1, 2, 3, 4, 5, 6, 7].map((day) => { diff --git a/interface/src/utils/useWs.ts b/interface/src/utils/useWs.ts index 8e30da18b..3471c691e 100644 --- a/interface/src/utils/useWs.ts +++ b/interface/src/utils/useWs.ts @@ -83,6 +83,7 @@ export const useWs = (wsUrl: string, wsThrottle = 100) => { } }); ws.current = instance; + // eslint-disable-next-line @typescript-eslint/unbound-method return instance.close; }, [wsUrl, onMessage]); diff --git a/mock-api/server.js b/mock-api/server.js index 7016c4757..fbeef3264 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -826,6 +826,7 @@ const emsesp_deviceentities_4 = [ // LOG rest_server.get(FETCH_LOG_ENDPOINT, (req, res) => { const encoded = msgpack.encode(fetch_log); + console.log('fetchlog'); res.write(encoded, 'binary'); res.end(null, 'binary'); }); From 63d105437f7612ad4cc2d3c6011ce9961d6c9638 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 29 Apr 2023 16:25:55 +0200 Subject: [PATCH 52/89] comment change --- mock-api/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock-api/server.js b/mock-api/server.js index fbeef3264..6a94db470 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -1474,7 +1474,7 @@ rest_server.get(ES_LOG_ENDPOINT, function (req, res) { res.write(sseFormattedResponse); res.flush(); // this is important - // if buffer full start over + // if buffer is full, start over if (log_index > 50) { fetch_log.events = []; log_index = 0; From 6f681aa4519b62b506adb0f8b1d605eb272501f8 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 29 Apr 2023 16:26:07 +0200 Subject: [PATCH 53/89] remove useWindowSize --- interface/src/framework/system/SystemLog.tsx | 28 ++++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/interface/src/framework/system/SystemLog.tsx b/interface/src/framework/system/SystemLog.tsx index 8d44f7603..26b9544cc 100644 --- a/interface/src/framework/system/SystemLog.tsx +++ b/interface/src/framework/system/SystemLog.tsx @@ -18,18 +18,18 @@ import { useRest, updateValueDirty, extractErrorMessage } from 'utils'; export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log'; -const useWindowSize = () => { - const [size, setSize] = useState([0, 0]); - useLayoutEffect(() => { - function updateSize() { - setSize([window.innerWidth, window.innerHeight]); - } - window.addEventListener('resize', updateSize); - updateSize(); - return () => window.removeEventListener('resize', updateSize); - }, []); - return size; -}; +// const useWindowSize = () => { +// const [size, setSize] = useState([0, 0]); +// useLayoutEffect(() => { +// function updateSize() { +// setSize([window.innerWidth, window.innerHeight]); +// } +// window.addEventListener('resize', updateSize); +// updateSize(); +// return () => window.removeEventListener('resize', updateSize); +// }, []); +// return size; +// }; const LogEntryLine = styled('div')(() => ({ color: '#bbbbbb', @@ -62,7 +62,7 @@ const levelLabel = (level: LogLevel) => { }; const SystemLog: FC = () => { - useWindowSize(); + // useWindowSize(); const { LL } = useI18nContext(); @@ -238,7 +238,7 @@ const SystemLog: FC = () => { Date: Sat, 29 Apr 2023 16:26:20 +0200 Subject: [PATCH 54/89] but device data in scrollable body --- interface/src/project/DashboardDevices.tsx | 27 +++++++++++++++------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/interface/src/project/DashboardDevices.tsx b/interface/src/project/DashboardDevices.tsx index 51e55f1e0..8a337d8ac 100644 --- a/interface/src/project/DashboardDevices.tsx +++ b/interface/src/project/DashboardDevices.tsx @@ -11,7 +11,6 @@ import StarIcon from '@mui/icons-material/Star'; import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined'; import { Button, - Typography, Dialog, DialogTitle, DialogContent, @@ -21,7 +20,8 @@ import { ListItem, ListItemText, FormControlLabel, - Checkbox + Checkbox, + Box } from '@mui/material'; import { useRowSelect } from '@table-library/react-table-library/select'; import { useSort, SortToggleType } from '@table-library/react-table-library/sort'; @@ -47,6 +47,9 @@ import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; import { extractErrorMessage } from 'utils'; +const topOffset = () => document.getElementById('devices-window')?.getBoundingClientRect().bottom || 0; +const leftOffset = () => document.getElementById('devices-window')?.getBoundingClientRect().left || 0; + const DashboardDevices: FC = () => { const { me } = useContext(AuthenticatedContext); const { LL } = useI18nContext(); @@ -416,10 +419,18 @@ const DashboardDevices: FC = () => { ); return ( - <> - - {deviceData.label} - + leftOffset(), + top: () => topOffset(), + p: 1 + }} + > setOnlyFav(!onlyFav)} />} label={ @@ -488,12 +499,12 @@ const DashboardDevices: FC = () => { )} - + ); }; return ( - + {renderCoreData()} {renderDeviceData()} {renderDeviceDetails()} From a256d2573c93c74e13c3f44e7f011f590833ea6e Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 30 Apr 2023 09:52:35 +0200 Subject: [PATCH 55/89] remove unused lib --- interface/src/framework/system/SystemLog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/framework/system/SystemLog.tsx b/interface/src/framework/system/SystemLog.tsx index 26b9544cc..6c9643eea 100644 --- a/interface/src/framework/system/SystemLog.tsx +++ b/interface/src/framework/system/SystemLog.tsx @@ -1,7 +1,7 @@ import DownloadIcon from '@mui/icons-material/GetApp'; import WarningIcon from '@mui/icons-material/Warning'; import { Box, styled, Button, Checkbox, MenuItem, Grid, TextField } from '@mui/material'; -import { useState, useEffect, useCallback, useLayoutEffect } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { toast } from 'react-toastify'; import type { FC } from 'react'; From ceb63fa09f619d713a8f9e4f21b7d8f3eec4044e Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 30 Apr 2023 09:52:47 +0200 Subject: [PATCH 56/89] rename lint --- interface/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/package.json b/interface/package.json index 8b6c24f2a..24aaf301d 100644 --- a/interface/package.json +++ b/interface/package.json @@ -16,8 +16,7 @@ "standalone": "npm-run-all -p dev typesafe-i18n mock-api", "typesafe-i18n": "typesafe-i18n", "format": "prettier --write '**/*.{ts,tsx,js,css,json,md}'", - "lint": "eslint . --cache --max-warnings=0", - "lint-fixall": "eslint . --cache --fix" + "lint": "eslint . --cache --fix" }, "dependencies": { "@emotion/react": "^11.10.8", From 8a02f2a27a70e151435dc2d557e2faba6e973c27 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 30 Apr 2023 09:53:10 +0200 Subject: [PATCH 57/89] add check for min/max when sending device values --- interface/src/project/DashboardDevices.tsx | 22 ++++++++++++---------- interface/src/project/deviceValue.ts | 7 ------- interface/src/project/types.ts | 6 +++--- interface/src/project/validators.ts | 15 ++++++++++----- mock-api/server.js | 10 ++++++---- 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/interface/src/project/DashboardDevices.tsx b/interface/src/project/DashboardDevices.tsx index 8a337d8ac..e3a9f8701 100644 --- a/interface/src/project/DashboardDevices.tsx +++ b/interface/src/project/DashboardDevices.tsx @@ -35,7 +35,7 @@ import DashboarDevicesDialog from './DashboardDevicesDialog'; import DeviceIcon from './DeviceIcon'; import * as EMSESP from './api'; -import { formatValue, isNumberUOM } from './deviceValue'; +import { formatValue } from './deviceValue'; import { DeviceValueUOM_s, DeviceEntityMask } from './types'; import { deviceValueItemValidation } from './validators'; @@ -186,12 +186,10 @@ const DashboardDevices: FC = () => { ); const fetchDeviceData = async (id: number) => { - if (!deviceValueDialogOpen) { - try { - setDeviceData((await EMSESP.readDeviceData({ id })).data); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } + try { + setDeviceData((await EMSESP.readDeviceData({ id })).data); + } catch (error) { + toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); } }; @@ -208,6 +206,9 @@ const DashboardDevices: FC = () => { }, [fetchCoreData]); const refreshData = () => { + if (deviceValueDialogOpen) { + return; + } if (selectedDevice) { void fetchDeviceData(selectedDevice); } else { @@ -288,9 +289,10 @@ const DashboardDevices: FC = () => { }); const deviceValueDialogSave = async (dv: DeviceValue) => { + const selectedDeviceID = Number(device_select.state.id); try { const response = await EMSESP.writeDeviceValue({ - id: Number(device_select.state.id), + id: selectedDeviceID, devicevalue: dv }); if (response.status === 204) { @@ -304,8 +306,8 @@ const DashboardDevices: FC = () => { toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); } finally { setDeviceValueDialogOpen(false); + await fetchDeviceData(selectedDeviceID); setSelectedDeviceValue(undefined); - refreshData(); } }; @@ -515,7 +517,7 @@ const DashboardDevices: FC = () => { onClose={deviceValueDialogClose} onSave={deviceValueDialogSave} selectedItem={selectedDeviceValue} - validator={deviceValueItemValidation(isNumberUOM(selectedDeviceValue.u))} + validator={deviceValueItemValidation(selectedDeviceValue)} /> )} diff --git a/interface/src/project/deviceValue.ts b/interface/src/project/deviceValue.ts index 0c1088349..825269c8c 100644 --- a/interface/src/project/deviceValue.ts +++ b/interface/src/project/deviceValue.ts @@ -76,10 +76,3 @@ export const formatValueNoUOM = (value: any, uom: number) => { return value; } }; - -export function isNumberUOM(uom: number) { - if (uom === DeviceValueUOM.NONE) { - return false; - } - return true; -} diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index af7e86bd9..8397e5d1f 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -128,9 +128,9 @@ export interface DeviceValue { c?: string; // command, optional l?: string[]; // list, optional h?: string; // help text, optional - s?: string; // steps for up/down, optional - m?: string; // min, optional - x?: string; // max, optional + s?: number; // steps for up/down, optional + m?: number; // min, optional + x?: number; // max, optional } export interface DeviceData { diff --git a/interface/src/project/validators.ts b/interface/src/project/validators.ts index d9b65bf8a..de3ae8ee3 100644 --- a/interface/src/project/validators.ts +++ b/interface/src/project/validators.ts @@ -1,5 +1,6 @@ import Schema from 'async-validator'; -import type { AnalogSensor, Settings } from './types'; +import { DeviceValueUOM } from './types'; +import type { AnalogSensor, DeviceValue, Settings } from './types'; import type { InternalRuleItem } from 'async-validator'; import { IP_OR_HOSTNAME_VALIDATOR } from 'validators/shared'; @@ -162,16 +163,20 @@ export const analogSensorItemValidation = (sensors: AnalogSensor[], creating: bo ] }); -export const deviceValueItemValidation = (isNumber: boolean) => +export const deviceValueItemValidation = (dv: DeviceValue) => new Schema({ v: [ { required: true, message: 'Value is required' }, { validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) { - if (isNumber && isNaN(+value)) { - callback('Not a valid number'); + if (dv.u !== DeviceValueUOM.NONE && isNaN(+value)) { + // not a number + dv.m && dv.x ? callback('Must be a number between ' + dv.m + ' and ' + dv.x) : callback('Must be a number'); } - callback(); + // is a number + (dv.m && Number(value) < dv.m) || (dv.x && Number(value) > dv.x) + ? callback('Must be between ' + dv.m + ' and ' + dv.x) + : callback(); } } ] diff --git a/mock-api/server.js b/mock-api/server.js index 6a94db470..f18627608 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -474,10 +474,12 @@ const emsesp_devicedata_1 = { id: '00date/time' }, { - v: 18, + v: 18.2, u: 1, - id: '00hc1 selected room temperature', - c: 'hc1/seltemp' + id: '00Chosen Room Temperature', + c: 'hc1/seltemp', + m: 5, + x: 52 }, { v: 22.6, @@ -693,7 +695,7 @@ const emsesp_deviceentities_1 = [ }, { v: 18.2, - n: 'hc1 selected room temperature', + n: 'Chosen Room Temperature', id: 'hc1/seltemp', m: 0, mi: 5, From d618d09bdff97e79945f33290e40d22a149e2223 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 30 Apr 2023 09:57:25 +0200 Subject: [PATCH 58/89] min, max and step are sent as Numbers to the webUI --- src/emsdevice.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 7c6f45fe8..89b78b27c 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -922,20 +922,19 @@ void EMSdevice::generate_values_web(JsonObject & output) { } } // handle INTs - // add steps to numeric values with numeric_operator + // add min and max values and steps, as integer values else { - char s[10]; if (dv.numeric_operator > 0) { - obj["s"] = Helpers::render_value(s, (float)1 / dv.numeric_operator, 1); + obj["s"] = (float)1 / dv.numeric_operator; } else if (dv.numeric_operator < 0) { - obj["s"] = Helpers::render_value(s, (float)(-1) * dv.numeric_operator, 0); + obj["s"] = (float)(-1) * dv.numeric_operator; } int16_t dv_set_min; uint16_t dv_set_max; if (dv.get_min_max(dv_set_min, dv_set_max)) { - obj["m"] = Helpers::render_value(s, dv_set_min, 0); - obj["x"] = Helpers::render_value(s, dv_set_max, 0); + obj["m"] = dv_set_min; + obj["x"] = dv_set_max; } } } From 2d9627373cba97c805793c72d5b8e1240728c1ea Mon Sep 17 00:00:00 2001 From: Proddy Date: Sun, 30 Apr 2023 10:41:01 +0200 Subject: [PATCH 59/89] ignore internationl number settings when changing value --- interface/src/project/deviceValue.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/interface/src/project/deviceValue.ts b/interface/src/project/deviceValue.ts index 825269c8c..9b1b7ffab 100644 --- a/interface/src/project/deviceValue.ts +++ b/interface/src/project/deviceValue.ts @@ -63,15 +63,13 @@ export const formatValueNoUOM = (value: any, uom: number) => { switch (uom) { case DeviceValueUOM.NONE: if (typeof value === 'number') { - return new Intl.NumberFormat().format(value); + return Number(value); } return value; case DeviceValueUOM.DEGREES: case DeviceValueUOM.DEGREES_R: case DeviceValueUOM.FAHRENHEIT: - return new Intl.NumberFormat(undefined, { - minimumFractionDigits: 1 - }).format(Number(value)); + return Number(value).toFixed(1); default: return value; } From 3ef3f561b953d2c85540a2f70c42aceac94c7f94 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sun, 30 Apr 2023 10:41:23 +0200 Subject: [PATCH 60/89] text changes --- interface/src/i18n/en/index.ts | 2 +- interface/src/project/validators.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index 31f8dc798..1651cff37 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -69,7 +69,7 @@ const en: Translation = { SENSOR: 'Sensor', TEMP_SENSOR: 'Temperature Sensor', TEMP_SENSORS: 'Temperature Sensors', - WRITE_CMD_SENT: 'Write command has been sent', + WRITE_CMD_SENT: 'Write command sent', WRITE_CMD_FAILED: 'Write command failed', EMS_BUS_WARNING: 'EMS bus disconnected. If this warning still persists after a few seconds please check settings and board profile', EMS_BUS_SCANNING: 'Scanning for EMS devices...', diff --git a/interface/src/project/validators.ts b/interface/src/project/validators.ts index de3ae8ee3..64e1e21c0 100644 --- a/interface/src/project/validators.ts +++ b/interface/src/project/validators.ts @@ -171,11 +171,13 @@ export const deviceValueItemValidation = (dv: DeviceValue) => validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) { if (dv.u !== DeviceValueUOM.NONE && isNaN(+value)) { // not a number - dv.m && dv.x ? callback('Must be a number between ' + dv.m + ' and ' + dv.x) : callback('Must be a number'); + dv.m && dv.x + ? callback('Value must be a number between ' + dv.m + ' and ' + dv.x) + : callback('Value must be a number'); } // is a number (dv.m && Number(value) < dv.m) || (dv.x && Number(value) > dv.x) - ? callback('Must be between ' + dv.m + ' and ' + dv.x) + ? callback('Value must be between ' + dv.m + ' and ' + dv.x) : callback(); } } From cc10c494c60a697aa391bec1134ceabef960683a Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 30 Apr 2023 15:47:59 +0200 Subject: [PATCH 61/89] version release text changes --- interface/src/framework/system/SystemStatusForm.tsx | 6 +++--- interface/src/i18n/de/index.ts | 6 +++--- interface/src/i18n/en/index.ts | 6 +++--- interface/src/i18n/fr/index.ts | 6 +++--- interface/src/i18n/nl/index.ts | 6 +++--- interface/src/i18n/no/index.ts | 6 +++--- interface/src/i18n/pl/index.ts | 4 ++-- interface/src/i18n/sv/index.ts | 6 +++--- interface/src/i18n/tr/index.ts | 6 +++--- 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/interface/src/framework/system/SystemStatusForm.tsx b/interface/src/framework/system/SystemStatusForm.tsx index 8c97b2762..792e5df79 100644 --- a/interface/src/framework/system/SystemStatusForm.tsx +++ b/interface/src/framework/system/SystemStatusForm.tsx @@ -150,10 +150,10 @@ const SystemStatusForm: FC = () => { setShowingVersion(false)}> {LL.VERSION_CHECK(1)} - + {latestVersion && ( - {LL.THE_LATEST()} {LL.OFFICIAL()} {LL.VERSION_IS()} {latestVersion.version} + {LL.THE_LATEST()} {LL.OFFICIAL()} {LL.RELEASE_IS()} {latestVersion.version}  ( {LL.RELEASE_NOTES()} @@ -168,7 +168,7 @@ const SystemStatusForm: FC = () => { {latestDevVersion && ( - {LL.THE_LATEST()} {LL.DEVELOPMENT()} {LL.VERSION_IS()}  + {LL.THE_LATEST()} {LL.DEVELOPMENT()} {LL.RELEASE_IS()}  {latestDevVersion.version}  ( diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index 2ad6345b5..39fa06348 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -174,7 +174,7 @@ const de: Translation = { SUPPORT_INFO: 'Support Info', UPLOAD_OF: '{0} Hochladen', UPLOAD: 'Hochladen', - DOWNLOAD: 'Herunterladen', + DOWNLOAD: '{{H|h|h}}erunterladen', ABORTED: 'abgebrochen', FAILED: 'gescheitert', SUCCESSFUL: 'erfolgreich', @@ -182,7 +182,7 @@ const de: Translation = { LOG_OF: '{0} Log', STATUS_OF: '{0} Status', UPLOAD_DOWNLOAD: 'Hoch-/Herunterladen', - SYSTEM_VERSION_RUNNING: 'Sie verwenden die Version', + VERSION_ON: 'You are currently on', // TODO translate SYSTEM_APPLY_FIRMWARE: 'um die neue Firmware anzuwenden', CLOSE: 'Schließen', USE: 'Verwenden Sie', @@ -193,7 +193,7 @@ const de: Translation = { THE_LATEST: 'Die neueste', OFFICIAL: 'offizielle', DEVELOPMENT: 'Entwicklungs', - VERSION_IS: 'Version ist', + RELEASE_IS: 'release ist', // TODO translate RELEASE_NOTES: 'Versionshinweise', EMS_ESP_VER: 'EMS-ESP Version', PLATFORM: 'Platform (Platform / SDK)', diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index 1651cff37..ed0fe18cf 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -174,7 +174,7 @@ const en: Translation = { SUPPORT_INFO: 'Support Info', UPLOAD_OF: '{0} Upload', UPLOAD: 'Upload', - DOWNLOAD: 'Download', + DOWNLOAD: '{{D|d|d}}ownload', ABORTED: 'aborted', FAILED: 'failed', SUCCESSFUL: 'successful', @@ -182,7 +182,7 @@ const en: Translation = { LOG_OF: '{0} Log', STATUS_OF: '{0} Status', UPLOAD_DOWNLOAD: 'Upload/Download', - SYSTEM_VERSION_RUNNING: 'You are currently running version', + VERSION_ON: 'You are currently on', SYSTEM_APPLY_FIRMWARE: 'to apply the new firmware', CLOSE: 'Close', USE: 'Use', @@ -193,7 +193,7 @@ const en: Translation = { THE_LATEST: 'The latest', OFFICIAL: 'official', DEVELOPMENT: 'development', - VERSION_IS: 'version is', + RELEASE_IS: 'release is', RELEASE_NOTES: 'release notes', EMS_ESP_VER: 'EMS-ESP Version', PLATFORM: 'Device (Platform / SDK)', diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts index 905418d45..bf269cf24 100644 --- a/interface/src/i18n/fr/index.ts +++ b/interface/src/i18n/fr/index.ts @@ -174,7 +174,7 @@ const fr: Translation = { SUPPORT_INFO: 'Information de support', UPLOAD_OF: 'Upload de {0}', UPLOAD: 'Upload', - DOWNLOAD: 'Download', + DOWNLOAD: '{{D|d|d}}ownload', ABORTED: 'annulé', FAILED: 'échoué', SUCCESSFUL: 'réussi', @@ -182,7 +182,7 @@ const fr: Translation = { LOG_OF: '{0} Log', STATUS_OF: 'Statut {0}', UPLOAD_DOWNLOAD: 'Upload/Download', - SYSTEM_VERSION_RUNNING: 'Vous utilisez actuellement la version', + VERSION_ON: 'You are currently on', // TODO translate SYSTEM_APPLY_FIRMWARE: 'pour appliquer le nouveau firmware', CLOSE: 'Fermer', USE: 'Utiliser', @@ -193,7 +193,7 @@ const fr: Translation = { THE_LATEST: 'La dernière', OFFICIAL: 'officielle', DEVELOPMENT: 'développement', - VERSION_IS: 'version est', + RELEASE_IS: 'release est', // TODO translate RELEASE_NOTES: 'notes de version', EMS_ESP_VER: 'Version EMS-ESP', PLATFORM: 'Appareil (Plateforme / SDK)', diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index 1fde01341..10bfd83d3 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -174,7 +174,7 @@ const nl: Translation = { SUPPORT_INFO: 'Support Info', UPLOAD_OF: '{0} Upload', UPLOAD: 'Upload', - DOWNLOAD: 'Download', + DOWNLOAD: '{{D|d|d}}ownload', ABORTED: 'afgebroken', FAILED: 'mislukt', SUCCESSFUL: 'successvol', @@ -182,7 +182,7 @@ const nl: Translation = { LOG_OF: '{0} Log', STATUS_OF: '{0} Status', UPLOAD_DOWNLOAD: 'Upload/Download', - SYSTEM_VERSION_RUNNING: 'op dit moment draai je versie', + VERSION_ON: 'You are currently on', // TODO translate SYSTEM_APPLY_FIRMWARE: 'om de nieuwe firmware te activeren', CLOSE: 'Sluiten', USE: 'Gebruik', @@ -193,7 +193,7 @@ const nl: Translation = { THE_LATEST: 'De laatste', OFFICIAL: 'official', DEVELOPMENT: 'development', - VERSION_IS: 'versie is', + RELEASE_IS: 'release is', RELEASE_NOTES: 'release notes', EMS_ESP_VER: 'EMS-ESP Version', PLATFORM: 'Apparaat (Platform / SDK)', diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts index 880927925..226a67e87 100644 --- a/interface/src/i18n/no/index.ts +++ b/interface/src/i18n/no/index.ts @@ -174,7 +174,7 @@ const no: Translation = { SUPPORT_INFO: 'Supportinfo', UPLOAD_OF: '{0} Opplasning', UPLOAD: 'Opplasning', - DOWNLOAD: 'Nedlasting', + DOWNLOAD: '{{N|n|n}}edlasting', ABORTED: 'avbrutt', FAILED: 'feilet', SUCCESSFUL: 'vellykket', @@ -182,7 +182,7 @@ const no: Translation = { LOG_OF: '{0} Logg', STATUS_OF: '{0} Status', UPLOAD_DOWNLOAD: 'Opp/Nedlasting', - SYSTEM_VERSION_RUNNING: 'Du benytter versjon', + VERSION_ON: 'You are currently on', // TODO translate SYSTEM_APPLY_FIRMWARE: 'for å aktivere ny firmware', CLOSE: 'Steng', USE: 'Bruk', @@ -193,7 +193,7 @@ const no: Translation = { THE_LATEST: 'Den nyeste', OFFICIAL: 'official', DEVELOPMENT: 'development', - VERSION_IS: 'versjonen er', + RELEASE_IS: 'release er', RELEASE_NOTES: 'release notes', EMS_ESP_VER: 'EMS-ESP Version', PLATFORM: 'Enhet (Platform / SDK)', diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index c420d8669..881591adf 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -182,7 +182,7 @@ const pl: BaseTranslation = { LOG_OF: 'Log {0}', STATUS_OF: 'Status {0}', UPLOAD_DOWNLOAD: 'Przesyłanie plików', - SYSTEM_VERSION_RUNNING: 'Obecnie zainstalowana wersja to:', + VERSION_ON: 'You are currently on', // TODO translate SYSTEM_APPLY_FIRMWARE: '', CLOSE: 'Zamknij', USE: 'Aby zaktualizować firmware skorzystaj z funkcji', @@ -193,7 +193,7 @@ const pl: BaseTranslation = { THE_LATEST: 'Najnowsza', OFFICIAL: 'oficjalna', DEVELOPMENT: 'testowa', - VERSION_IS: 'wersja to', + RELEASE_IS: 'release to', // TODO translate RELEASE_NOTES: 'lista zmian', EMS_ESP_VER: 'Wersja EMS-ESP', PLATFORM: 'Urządzenie (platforma / SDK)', diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts index 2f752c855..fe9a9c8e1 100644 --- a/interface/src/i18n/sv/index.ts +++ b/interface/src/i18n/sv/index.ts @@ -174,7 +174,7 @@ const sv: Translation = { SUPPORT_INFO: 'Supportinfo', UPLOAD_OF: '{0} Uppladdning', UPLOAD: 'Uppladdning', - DOWNLOAD: 'Nedladdning', + DOWNLOAD: '{{N|n|n}}edladdning', ABORTED: 'Avbruten', FAILED: 'Misslyckades', SUCCESSFUL: 'Lyckades', @@ -182,7 +182,7 @@ const sv: Translation = { LOG_OF: '{0} Logg', STATUS_OF: '{0} Status', UPLOAD_DOWNLOAD: 'Upp/Nedladdning', - SYSTEM_VERSION_RUNNING: 'Du använder version', + VERSION_ON: 'You are currently on', // TODO translate SYSTEM_APPLY_FIRMWARE: 'för att aktivera ny firmware', CLOSE: 'Stäng', USE: 'Använd', @@ -193,7 +193,7 @@ const sv: Translation = { THE_LATEST: 'Den senaste', OFFICIAL: 'officiell', DEVELOPMENT: 'utveckling', - VERSION_IS: 'version är', + RELEASE_IS: 'release är', // TODO translate RELEASE_NOTES: 'release-logg', EMS_ESP_VER: 'EMS-ESP Version', PLATFORM: 'Enhet (Plattform / SDK)', diff --git a/interface/src/i18n/tr/index.ts b/interface/src/i18n/tr/index.ts index da9d53e80..c4bbc7665 100644 --- a/interface/src/i18n/tr/index.ts +++ b/interface/src/i18n/tr/index.ts @@ -174,7 +174,7 @@ const tr: Translation = { SUPPORT_INFO: 'Destek Bilgisi', UPLOAD_OF: '{0} Yüklemesi', UPLOAD: 'Yükleme', - DOWNLOAD: 'İndirme', + DOWNLOAD: '{{İ|i|i}}İndirme', ABORTED: 'iptal edildi', FAILED: 'başarısız', SUCCESSFUL: 'başarılı', @@ -182,7 +182,7 @@ const tr: Translation = { LOG_OF: '{0} Kaydı', STATUS_OF: '{0} Durumu', UPLOAD_DOWNLOAD: 'Yükleme/İndirme', - SYSTEM_VERSION_RUNNING: 'Şu anda çalıştırdığınız sürüm', + VERSION_ON: 'You are currently on', // TODO translate SYSTEM_APPLY_FIRMWARE: 'yeni bellenimi uygulamak için', CLOSE: 'Kapat', USE: 'KUllan', @@ -193,7 +193,7 @@ const tr: Translation = { THE_LATEST: 'En son', OFFICIAL: 'resmi', DEVELOPMENT: 'geliştirme', - VERSION_IS: 'sürüm: ', + RELEASE_IS: 'release is', // TODO translate RELEASE_NOTES: 'yayınlanma notları', EMS_ESP_VER: 'EMS-ESP Sürümü', PLATFORM: 'Cihaz (Platform / SDK)', From 0d9cd646190aab3a1a9f48719028ce5c56137e5b Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 30 Apr 2023 15:51:15 +0200 Subject: [PATCH 62/89] rename upload finished to successful --- interface/src/i18n/en/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index ed0fe18cf..601df0120 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -17,8 +17,8 @@ const en: Translation = { HELP_OF: '{0} Help', LOGGED_IN: 'Logged in as {name}', PLEASE_SIGNIN: 'Please sign in to continue', - UPLOAD_SUCCESSFUL: 'Upload finished', - DOWNLOAD_SUCCESSFUL: 'Download finished', + UPLOAD_SUCCESSFUL: 'Upload successful', + DOWNLOAD_SUCCESSFUL: 'Download successful', INVALID_LOGIN: 'Invalid login details', NETWORK: 'Network', SECURITY: 'Security', From dc4bd64affdd535f30df5301d89642f84227b524 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sun, 30 Apr 2023 16:22:51 +0200 Subject: [PATCH 63/89] fixes PlatformIO 6.2.0 breaks compilation #1166 (thanks Michael) --- lib/ESPAsyncWebServer/ESPAsyncWebServer.h | 974 +++++----- lib/ESPAsyncWebServer/WebAuthentication.cpp | 470 ++--- lib/ESPAsyncWebServer/WebAuthentication.h | 2 +- lib/ESPAsyncWebServer/WebRequest.cpp | 1860 +++++++++---------- lib/ESPAsyncWebServer/WebResponses.cpp | 1417 +++++++------- 5 files changed, 2362 insertions(+), 2361 deletions(-) diff --git a/lib/ESPAsyncWebServer/ESPAsyncWebServer.h b/lib/ESPAsyncWebServer/ESPAsyncWebServer.h index 327d177e0..02a96cb2f 100644 --- a/lib/ESPAsyncWebServer/ESPAsyncWebServer.h +++ b/lib/ESPAsyncWebServer/ESPAsyncWebServer.h @@ -1,487 +1,487 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#ifndef _ESPAsyncWebServer_H_ -#define _ESPAsyncWebServer_H_ - -#include "Arduino.h" - -#include -#include "FS.h" - -#include "StringArray.h" - -#ifdef ESP32 -#include -#include -#elif defined(ESP8266) -#include -#include -#else -#error Platform not supported -#endif - -#ifdef ASYNCWEBSERVER_REGEX -#define ASYNCWEBSERVER_REGEX_ATTRIBUTE -#else -#define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined"))) -#endif - -#define DEBUGF(...) //Serial.printf(__VA_ARGS__) - -class AsyncWebServer; -class AsyncWebServerRequest; -class AsyncWebServerResponse; -class AsyncWebHeader; -class AsyncWebParameter; -class AsyncWebRewrite; -class AsyncWebHandler; -class AsyncStaticWebHandler; -class AsyncCallbackWebHandler; -class AsyncResponseStream; - -#ifndef WEBSERVER_H -typedef enum { - HTTP_GET = 0b00000001, - HTTP_POST = 0b00000010, - HTTP_DELETE = 0b00000100, - HTTP_PUT = 0b00001000, - HTTP_PATCH = 0b00010000, - HTTP_HEAD = 0b00100000, - HTTP_OPTIONS = 0b01000000, - HTTP_ANY = 0b01111111, -} WebRequestMethod; -#endif - -#ifndef HAVE_FS_FILE_OPEN_MODE -namespace fs { - class FileOpenMode { - public: - static const char *read; - static const char *write; - static const char *append; - }; -}; -#else -#include "FileOpenMode.h" -#endif - -//if this value is returned when asked for data, packet will not be sent and you will be asked for data again -#define RESPONSE_TRY_AGAIN 0xFFFFFFFF - -typedef uint8_t WebRequestMethodComposite; -typedef std::function ArDisconnectHandler; - -/* - * PARAMETER :: Chainable object to hold GET/POST and FILE parameters - * */ - -class AsyncWebParameter { - private: - String _name; - String _value; - size_t _size; - bool _isForm; - bool _isFile; - - public: - - AsyncWebParameter(const String& name, const String& value, bool form=false, bool file=false, size_t size=0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file){} - const String& name() const { return _name; } - const String& value() const { return _value; } - size_t size() const { return _size; } - bool isPost() const { return _isForm; } - bool isFile() const { return _isFile; } -}; - -/* - * HEADER :: Chainable object to hold the headers - * */ - -class AsyncWebHeader { - private: - String _name; - String _value; - - public: - AsyncWebHeader(const String& name, const String& value): _name(name), _value(value){} - AsyncWebHeader(const String& data): _name(), _value(){ - if(!data) return; - int index = data.indexOf(':'); - if (index < 0) return; - _name = data.substring(0, index); - _value = data.substring(index + 2); - } - ~AsyncWebHeader(){} - const String& name() const { return _name; } - const String& value() const { return _value; } - String toString() const { return String(_name + F(": ") + _value + F("\r\n")); } -}; - -/* - * REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect - * */ - -typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT_EVENT, RCT_MAX } RequestedConnectionType; - -typedef std::function AwsResponseFiller; -typedef std::function AwsTemplateProcessor; - -class AsyncWebServerRequest { - using File = fs::File; - using FS = fs::FS; - friend class AsyncWebServer; - friend class AsyncCallbackWebHandler; - friend class HttpCookieHeader; - private: - AsyncClient* _client; - AsyncWebServer* _server; - AsyncWebHandler* _handler; - AsyncWebServerResponse* _response; - StringArray _interestingHeaders; - ArDisconnectHandler _onDisconnectfn; - - String _temp; - uint8_t _parseState; - - uint8_t _version; - WebRequestMethodComposite _method; - String _url; - String _host; - String _contentType; - String _boundary; - String _authorization; - RequestedConnectionType _reqconntype; - void _removeNotInterestingHeaders(); - bool _isDigest; - bool _isMultipart; - bool _isPlainPost; - bool _expectingContinue; - size_t _contentLength; - size_t _parsedLength; - - LinkedList _headers; - LinkedList _params; - LinkedList _pathParams; - - uint8_t _multiParseState; - uint8_t _boundaryPosition; - size_t _itemStartIndex; - size_t _itemSize; - String _itemName; - String _itemFilename; - String _itemType; - String _itemValue; - uint8_t *_itemBuffer; - size_t _itemBufferIndex; - bool _itemIsFile; - - void _onPoll(); - void _onAck(size_t len, uint32_t time); - void _onError(int8_t error); - void _onTimeout(uint32_t time); - void _onDisconnect(); - void _onData(void *buf, size_t len); - - void _addParam(AsyncWebParameter*); - void _addPathParam(const char *param); - - bool _parseReqHead(); - bool _parseReqHeader(); - void _parseLine(); - void _parsePlainPostChar(uint8_t data); - void _parseMultipartPostByte(uint8_t data, bool last); - void _addGetParams(const String& params); - - void _handleUploadStart(); - void _handleUploadByte(uint8_t data, bool last); - void _handleUploadEnd(); - - public: - File _tempFile; - void *_tempObject; - - AsyncWebServerRequest(AsyncWebServer*, AsyncClient*); - ~AsyncWebServerRequest(); - - AsyncClient* client(){ return _client; } - uint8_t version() const { return _version; } - WebRequestMethodComposite method() const { return _method; } - const String& url() const { return _url; } - const String& host() const { return _host; } - const String& contentType() const { return _contentType; } - size_t contentLength() const { return _contentLength; } - bool multipart() const { return _isMultipart; } - const __FlashStringHelper *methodToString() const; - const __FlashStringHelper *requestedConnTypeToString() const; - RequestedConnectionType requestedConnType() const { return _reqconntype; } - bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED); - void onDisconnect (ArDisconnectHandler fn); - - //hash is the string representation of: - // base64(user:pass) for basic or - // user:realm:md5(user:realm:pass) for digest - bool authenticate(const char * hash); - bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false); - void requestAuthentication(const char * realm = NULL, bool isDigest = true); - - void setHandler(AsyncWebHandler *handler){ _handler = handler; } - void addInterestingHeader(const String& name); - - void redirect(const String& url); - - void send(AsyncWebServerResponse *response); - void send(int code, const String& contentType=String(), const String& content=String()); - void send(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); - void send(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); - void send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); - void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); - void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); - void send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); - void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); - - AsyncWebServerResponse *beginResponse(int code, const String& contentType=String(), const String& content=String()); - AsyncWebServerResponse *beginResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); - AsyncWebServerResponse *beginResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); - AsyncWebServerResponse *beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); - AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); - AsyncWebServerResponse *beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); - AsyncResponseStream *beginResponseStream(const String& contentType, size_t bufferSize=1460); - AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); - AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); - - size_t headers() const; // get header count - bool hasHeader(const String& name) const; // check if header exists - bool hasHeader(const __FlashStringHelper * data) const; // check if header exists - - AsyncWebHeader* getHeader(const String& name) const; - AsyncWebHeader* getHeader(const __FlashStringHelper * data) const; - AsyncWebHeader* getHeader(size_t num) const; - - size_t params() const; // get arguments count - bool hasParam(const String& name, bool post=false, bool file=false) const; - bool hasParam(const __FlashStringHelper * data, bool post=false, bool file=false) const; - - AsyncWebParameter* getParam(const String& name, bool post=false, bool file=false) const; - AsyncWebParameter* getParam(const __FlashStringHelper * data, bool post, bool file) const; - AsyncWebParameter* getParam(size_t num) const; - - size_t args() const { return params(); } // get arguments count - const String& arg(const String& name) const; // get request argument value by name - const String& arg(const __FlashStringHelper * data) const; // get request argument value by F(name) - const String& arg(size_t i) const; // get request argument value by number - const String& argName(size_t i) const; // get request argument name by number - bool hasArg(const char* name) const; // check if argument exists - bool hasArg(const __FlashStringHelper * data) const; // check if F(argument) exists - - const String& ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const; - - const String& header(const char* name) const;// get request header value by name - const String& header(const __FlashStringHelper * data) const;// get request header value by F(name) - const String& header(size_t i) const; // get request header value by number - const String& headerName(size_t i) const; // get request header name by number - String urlDecode(const String& text) const; -}; - -/* - * FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server) - * */ - -typedef std::function ArRequestFilterFunction; - -bool ON_STA_FILTER(AsyncWebServerRequest *request); - -bool ON_AP_FILTER(AsyncWebServerRequest *request); - -/* - * REWRITE :: One instance can be handle any Request (done by the Server) - * */ - -class AsyncWebRewrite { - protected: - String _from; - String _toUrl; - String _params; - ArRequestFilterFunction _filter; - public: - AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL){ - int index = _toUrl.indexOf('?'); - if (index > 0) { - _params = _toUrl.substring(index +1); - _toUrl = _toUrl.substring(0, index); - } - } - virtual ~AsyncWebRewrite(){} - AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } - bool filter(AsyncWebServerRequest *request) const { return _filter == NULL || _filter(request); } - const String& from(void) const { return _from; } - const String& toUrl(void) const { return _toUrl; } - const String& params(void) const { return _params; } - virtual bool match(AsyncWebServerRequest *request) { return from() == request->url() && filter(request); } -}; - -/* - * HANDLER :: One instance can be attached to any Request (done by the Server) - * */ - -class AsyncWebHandler { - protected: - ArRequestFilterFunction _filter; - String _username; - String _password; - public: - AsyncWebHandler():_username(""), _password(""){} - AsyncWebHandler& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } - AsyncWebHandler& setAuthentication(const char *username, const char *password){ _username = String(username);_password = String(password); return *this; }; - bool filter(AsyncWebServerRequest *request){ return _filter == NULL || _filter(request); } - virtual ~AsyncWebHandler(){} - virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))){ - return false; - } - virtual void handleRequest(AsyncWebServerRequest *request __attribute__((unused))){} - virtual void handleUpload(AsyncWebServerRequest *request __attribute__((unused)), const String& filename __attribute__((unused)), size_t index __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), bool final __attribute__((unused))){} - virtual void handleBody(AsyncWebServerRequest *request __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))){} - virtual bool isRequestHandlerTrivial(){return true;} -}; - -/* - * RESPONSE :: One instance is created for each Request (attached by the Handler) - * */ - -typedef enum { - RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED -} WebResponseState; - -class AsyncWebServerResponse { - protected: - int _code; - LinkedList _headers; - String _contentType; - size_t _contentLength; - bool _sendContentLength; - bool _chunked; - size_t _headLength; - size_t _sentLength; - size_t _ackedLength; - size_t _writtenLength; - WebResponseState _state; - const char* _responseCodeToString(int code); -public: - static const __FlashStringHelper *responseCodeToString(int code); - - public: - AsyncWebServerResponse(); - virtual ~AsyncWebServerResponse(); - virtual void setCode(int code); - virtual void setContentLength(size_t len); - virtual void setContentType(const String& type); - virtual void addHeader(const String& name, const String& value); - virtual String _assembleHead(uint8_t version); - virtual bool _started() const; - virtual bool _finished() const; - virtual bool _failed() const; - virtual bool _sourceValid() const; - virtual void _respond(AsyncWebServerRequest *request); - virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); -}; - -/* - * SERVER :: One instance - * */ - -typedef std::function ArRequestHandlerFunction; -typedef std::function ArUploadHandlerFunction; -typedef std::function ArBodyHandlerFunction; - -class AsyncWebServer { - protected: - AsyncServer _server; - LinkedList _rewrites; - LinkedList _handlers; - AsyncCallbackWebHandler* _catchAllHandler; - - public: - AsyncWebServer(uint16_t port); - ~AsyncWebServer(); - - void begin(); - void end(); - -#if ASYNC_TCP_SSL_ENABLED - void onSslFileRequest(AcSSlFileHandler cb, void* arg); - void beginSecure(const char *cert, const char *private_key_file, const char *password); -#endif - - AsyncWebRewrite& addRewrite(AsyncWebRewrite* rewrite); - bool removeRewrite(AsyncWebRewrite* rewrite); - AsyncWebRewrite& rewrite(const char* from, const char* to); - - AsyncWebHandler& addHandler(AsyncWebHandler* handler); - bool removeHandler(AsyncWebHandler* handler); - - AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest); - AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest); - AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload); - AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody); - - AsyncStaticWebHandler& serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL); - - void onNotFound(ArRequestHandlerFunction fn); //called when handler is not assigned - void onFileUpload(ArUploadHandlerFunction fn); //handle file uploads - void onRequestBody(ArBodyHandlerFunction fn); //handle posts with plain body content (JSON often transmitted this way as a request) - - void reset(); //remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody - - void _handleDisconnect(AsyncWebServerRequest *request); - void _attachHandler(AsyncWebServerRequest *request); - void _rewriteRequest(AsyncWebServerRequest *request); -}; - -class DefaultHeaders { - using headers_t = LinkedList; - headers_t _headers; - - DefaultHeaders() - :_headers(headers_t([](AsyncWebHeader *h){ delete h; })) - {} -public: - using ConstIterator = headers_t::ConstIterator; - - void addHeader(const String& name, const String& value){ - _headers.add(new AsyncWebHeader(name, value)); - } - - ConstIterator begin() const { return _headers.begin(); } - ConstIterator end() const { return _headers.end(); } - - DefaultHeaders(DefaultHeaders const &) = delete; - DefaultHeaders &operator=(DefaultHeaders const &) = delete; - static DefaultHeaders &Instance() { - static DefaultHeaders instance; - return instance; - } -}; - -#include "WebResponseImpl.h" -#include "WebHandlerImpl.h" -#include "AsyncWebSocket.h" -#include "AsyncEventSource.h" - -#endif /* _AsyncWebServer_H_ */ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef _ESPAsyncWebServer_H_ +#define _ESPAsyncWebServer_H_ + +#include "Arduino.h" + +#include +#include "FS.h" + +#include "StringArray.h" + +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#else +#error Platform not supported +#endif + +#ifdef ASYNCWEBSERVER_REGEX +#define ASYNCWEBSERVER_REGEX_ATTRIBUTE +#else +#define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined"))) +#endif + +#define DEBUGF(...) //Serial.printf(__VA_ARGS__) + +class AsyncWebServer; +class AsyncWebServerRequest; +class AsyncWebServerResponse; +class AsyncWebHeader; +class AsyncWebParameter; +class AsyncWebRewrite; +class AsyncWebHandler; +class AsyncStaticWebHandler; +class AsyncCallbackWebHandler; +class AsyncResponseStream; + +#ifndef WEBSERVER_H +typedef enum { + HTTP_GET = 0b00000001, + HTTP_POST = 0b00000010, + HTTP_DELETE = 0b00000100, + HTTP_PUT = 0b00001000, + HTTP_PATCH = 0b00010000, + HTTP_HEAD = 0b00100000, + HTTP_OPTIONS = 0b01000000, + HTTP_ANY = 0b01111111, +} WebRequestMethod; +#endif + +#ifndef HAVE_FS_FILE_OPEN_MODE +namespace fs { + class FileOpenMode { + public: + static const char *read; + static const char *write; + static const char *append; + }; +}; +#else +#include "FileOpenMode.h" +#endif + +//if this value is returned when asked for data, packet will not be sent and you will be asked for data again +#define RESPONSE_TRY_AGAIN 0xFFFFFFFF + +typedef uint8_t WebRequestMethodComposite; +typedef std::function ArDisconnectHandler; + +/* + * PARAMETER :: Chainable object to hold GET/POST and FILE parameters + * */ + +class AsyncWebParameter { + private: + String _name; + String _value; + size_t _size; + bool _isForm; + bool _isFile; + + public: + + AsyncWebParameter(const String& name, const String& value, bool form=false, bool file=false, size_t size=0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file){} + const String& name() const { return _name; } + const String& value() const { return _value; } + size_t size() const { return _size; } + bool isPost() const { return _isForm; } + bool isFile() const { return _isFile; } +}; + +/* + * HEADER :: Chainable object to hold the headers + * */ + +class AsyncWebHeader { + private: + String _name; + String _value; + + public: + AsyncWebHeader(const String& name, const String& value): _name(name), _value(value){} + AsyncWebHeader(const String& data): _name(), _value(){ + if(!data) return; + int index = data.indexOf(':'); + if (index < 0) return; + _name = data.substring(0, index); + _value = data.substring(index + 2); + } + ~AsyncWebHeader(){} + const String& name() const { return _name; } + const String& value() const { return _value; } + String toString() const { return String(_name + F(": ") + _value + F("\r\n")); } +}; + +/* + * REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect + * */ + +typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT_EVENT, RCT_MAX } RequestedConnectionType; + +typedef std::function AwsResponseFiller; +typedef std::function AwsTemplateProcessor; + +class AsyncWebServerRequest { + using File = fs::File; + using FS = fs::FS; + friend class AsyncWebServer; + friend class AsyncCallbackWebHandler; + friend class HttpCookieHeader; + private: + AsyncClient* _client; + AsyncWebServer* _server; + AsyncWebHandler* _handler; + AsyncWebServerResponse* _response; + StringArray _interestingHeaders; + ArDisconnectHandler _onDisconnectfn; + + String _temp; + uint8_t _parseState; + + uint8_t _version; + WebRequestMethodComposite _method; + String _url; + String _host; + String _contentType; + String _boundary; + String _authorization; + RequestedConnectionType _reqconntype; + void _removeNotInterestingHeaders(); + bool _isDigest; + bool _isMultipart; + bool _isPlainPost; + bool _expectingContinue; + size_t _contentLength; + size_t _parsedLength; + + LinkedList _headers; + LinkedList _params; + LinkedList _pathParams; + + uint8_t _multiParseState; + uint8_t _boundaryPosition; + size_t _itemStartIndex; + size_t _itemSize; + String _itemName; + String _itemFilename; + String _itemType; + String _itemValue; + uint8_t *_itemBuffer; + size_t _itemBufferIndex; + bool _itemIsFile; + + void _onPoll(); + void _onAck(size_t len, uint32_t time); + void _onError(int8_t error); + void _onTimeout(uint32_t time); + void _onDisconnect(); + void _onData(void *buf, size_t len); + + void _addParam(AsyncWebParameter*); + void _addPathParam(const char *param); + + bool _parseReqHead(); + bool _parseReqHeader(); + void _parseLine(); + void _parsePlainPostChar(uint8_t data); + void _parseMultipartPostByte(uint8_t data, bool last); + void _addGetParams(const String& params); + + void _handleUploadStart(); + void _handleUploadByte(uint8_t data, bool last); + void _handleUploadEnd(); + + public: + File _tempFile; + void *_tempObject; + + AsyncWebServerRequest(AsyncWebServer*, AsyncClient*); + ~AsyncWebServerRequest(); + + AsyncClient* client(){ return _client; } + uint8_t version() const { return _version; } + WebRequestMethodComposite method() const { return _method; } + const String& url() const { return _url; } + const String& host() const { return _host; } + const String& contentType() const { return _contentType; } + size_t contentLength() const { return _contentLength; } + bool multipart() const { return _isMultipart; } + const char *methodToString() const; + const char *requestedConnTypeToString() const; + RequestedConnectionType requestedConnType() const { return _reqconntype; } + bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED); + void onDisconnect (ArDisconnectHandler fn); + + //hash is the string representation of: + // base64(user:pass) for basic or + // user:realm:md5(user:realm:pass) for digest + bool authenticate(const char * hash); + bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false); + void requestAuthentication(const char * realm = NULL, bool isDigest = true); + + void setHandler(AsyncWebHandler *handler){ _handler = handler; } + void addInterestingHeader(const String& name); + + void redirect(const String& url); + + void send(AsyncWebServerResponse *response); + void send(int code, const String& contentType=String(), const String& content=String()); + void send(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + void send(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + void send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); + void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + void send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); + void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); + + AsyncWebServerResponse *beginResponse(int code, const String& contentType=String(), const String& content=String()); + AsyncWebServerResponse *beginResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + AsyncWebServerResponse *beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + AsyncResponseStream *beginResponseStream(const String& contentType, size_t bufferSize=1460); + AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); + + size_t headers() const; // get header count + bool hasHeader(const String& name) const; // check if header exists + bool hasHeader(const __FlashStringHelper * data) const; // check if header exists + + AsyncWebHeader* getHeader(const String& name) const; + AsyncWebHeader* getHeader(const __FlashStringHelper * data) const; + AsyncWebHeader* getHeader(size_t num) const; + + size_t params() const; // get arguments count + bool hasParam(const String& name, bool post=false, bool file=false) const; + bool hasParam(const __FlashStringHelper * data, bool post=false, bool file=false) const; + + AsyncWebParameter* getParam(const String& name, bool post=false, bool file=false) const; + AsyncWebParameter* getParam(const __FlashStringHelper * data, bool post, bool file) const; + AsyncWebParameter* getParam(size_t num) const; + + size_t args() const { return params(); } // get arguments count + const String& arg(const String& name) const; // get request argument value by name + const String& arg(const __FlashStringHelper * data) const; // get request argument value by F(name) + const String& arg(size_t i) const; // get request argument value by number + const String& argName(size_t i) const; // get request argument name by number + bool hasArg(const char* name) const; // check if argument exists + bool hasArg(const __FlashStringHelper * data) const; // check if F(argument) exists + + const String& ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const; + + const String& header(const char* name) const;// get request header value by name + const String& header(const __FlashStringHelper * data) const;// get request header value by F(name) + const String& header(size_t i) const; // get request header value by number + const String& headerName(size_t i) const; // get request header name by number + String urlDecode(const String& text) const; +}; + +/* + * FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server) + * */ + +typedef std::function ArRequestFilterFunction; + +bool ON_STA_FILTER(AsyncWebServerRequest *request); + +bool ON_AP_FILTER(AsyncWebServerRequest *request); + +/* + * REWRITE :: One instance can be handle any Request (done by the Server) + * */ + +class AsyncWebRewrite { + protected: + String _from; + String _toUrl; + String _params; + ArRequestFilterFunction _filter; + public: + AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL){ + int index = _toUrl.indexOf('?'); + if (index > 0) { + _params = _toUrl.substring(index +1); + _toUrl = _toUrl.substring(0, index); + } + } + virtual ~AsyncWebRewrite(){} + AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } + bool filter(AsyncWebServerRequest *request) const { return _filter == NULL || _filter(request); } + const String& from(void) const { return _from; } + const String& toUrl(void) const { return _toUrl; } + const String& params(void) const { return _params; } + virtual bool match(AsyncWebServerRequest *request) { return from() == request->url() && filter(request); } +}; + +/* + * HANDLER :: One instance can be attached to any Request (done by the Server) + * */ + +class AsyncWebHandler { + protected: + ArRequestFilterFunction _filter; + String _username; + String _password; + public: + AsyncWebHandler():_username(""), _password(""){} + AsyncWebHandler& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } + AsyncWebHandler& setAuthentication(const char *username, const char *password){ _username = String(username);_password = String(password); return *this; }; + bool filter(AsyncWebServerRequest *request){ return _filter == NULL || _filter(request); } + virtual ~AsyncWebHandler(){} + virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))){ + return false; + } + virtual void handleRequest(AsyncWebServerRequest *request __attribute__((unused))){} + virtual void handleUpload(AsyncWebServerRequest *request __attribute__((unused)), const String& filename __attribute__((unused)), size_t index __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), bool final __attribute__((unused))){} + virtual void handleBody(AsyncWebServerRequest *request __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))){} + virtual bool isRequestHandlerTrivial(){return true;} +}; + +/* + * RESPONSE :: One instance is created for each Request (attached by the Handler) + * */ + +typedef enum { + RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED +} WebResponseState; + +class AsyncWebServerResponse { + protected: + int _code; + LinkedList _headers; + String _contentType; + size_t _contentLength; + bool _sendContentLength; + bool _chunked; + size_t _headLength; + size_t _sentLength; + size_t _ackedLength; + size_t _writtenLength; + WebResponseState _state; + const char* _responseCodeToString(int code); +public: + static const __FlashStringHelper *responseCodeToString(int code); + + public: + AsyncWebServerResponse(); + virtual ~AsyncWebServerResponse(); + virtual void setCode(int code); + virtual void setContentLength(size_t len); + virtual void setContentType(const String& type); + virtual void addHeader(const String& name, const String& value); + virtual String _assembleHead(uint8_t version); + virtual bool _started() const; + virtual bool _finished() const; + virtual bool _failed() const; + virtual bool _sourceValid() const; + virtual void _respond(AsyncWebServerRequest *request); + virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); +}; + +/* + * SERVER :: One instance + * */ + +typedef std::function ArRequestHandlerFunction; +typedef std::function ArUploadHandlerFunction; +typedef std::function ArBodyHandlerFunction; + +class AsyncWebServer { + protected: + AsyncServer _server; + LinkedList _rewrites; + LinkedList _handlers; + AsyncCallbackWebHandler* _catchAllHandler; + + public: + AsyncWebServer(uint16_t port); + ~AsyncWebServer(); + + void begin(); + void end(); + +#if ASYNC_TCP_SSL_ENABLED + void onSslFileRequest(AcSSlFileHandler cb, void* arg); + void beginSecure(const char *cert, const char *private_key_file, const char *password); +#endif + + AsyncWebRewrite& addRewrite(AsyncWebRewrite* rewrite); + bool removeRewrite(AsyncWebRewrite* rewrite); + AsyncWebRewrite& rewrite(const char* from, const char* to); + + AsyncWebHandler& addHandler(AsyncWebHandler* handler); + bool removeHandler(AsyncWebHandler* handler); + + AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest); + AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest); + AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload); + AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody); + + AsyncStaticWebHandler& serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL); + + void onNotFound(ArRequestHandlerFunction fn); //called when handler is not assigned + void onFileUpload(ArUploadHandlerFunction fn); //handle file uploads + void onRequestBody(ArBodyHandlerFunction fn); //handle posts with plain body content (JSON often transmitted this way as a request) + + void reset(); //remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody + + void _handleDisconnect(AsyncWebServerRequest *request); + void _attachHandler(AsyncWebServerRequest *request); + void _rewriteRequest(AsyncWebServerRequest *request); +}; + +class DefaultHeaders { + using headers_t = LinkedList; + headers_t _headers; + + DefaultHeaders() + :_headers(headers_t([](AsyncWebHeader *h){ delete h; })) + {} +public: + using ConstIterator = headers_t::ConstIterator; + + void addHeader(const String& name, const String& value){ + _headers.add(new AsyncWebHeader(name, value)); + } + + ConstIterator begin() const { return _headers.begin(); } + ConstIterator end() const { return _headers.end(); } + + DefaultHeaders(DefaultHeaders const &) = delete; + DefaultHeaders &operator=(DefaultHeaders const &) = delete; + static DefaultHeaders &Instance() { + static DefaultHeaders instance; + return instance; + } +}; + +#include "WebResponseImpl.h" +#include "WebHandlerImpl.h" +#include "AsyncWebSocket.h" +#include "AsyncEventSource.h" + +#endif /* _AsyncWebServer_H_ */ diff --git a/lib/ESPAsyncWebServer/WebAuthentication.cpp b/lib/ESPAsyncWebServer/WebAuthentication.cpp index 5c2538848..288e15508 100644 --- a/lib/ESPAsyncWebServer/WebAuthentication.cpp +++ b/lib/ESPAsyncWebServer/WebAuthentication.cpp @@ -1,235 +1,235 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "WebAuthentication.h" -#include -#ifdef ESP32 -#include "mbedtls/md5.h" -#else -#include "md5.h" -#endif - - -// Basic Auth hash = base64("username:password") - -bool checkBasicAuthentication(const char * hash, const char * username, const char * password){ - if(username == NULL || password == NULL || hash == NULL) - return false; - - size_t toencodeLen = strlen(username)+strlen(password)+1; - size_t encodedLen = base64_encode_expected_len(toencodeLen); - if(strlen(hash) != encodedLen) - return false; - - char *toencode = new char[toencodeLen+1]; - if(toencode == NULL){ - return false; - } - char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; - if(encoded == NULL){ - delete[] toencode; - return false; - } - sprintf_P(toencode, PSTR("%s:%s"), username, password); - if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0){ - delete[] toencode; - delete[] encoded; - return true; - } - delete[] toencode; - delete[] encoded; - return false; -} - -static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more -#ifdef ESP32 - mbedtls_md5_context _ctx; -#else - md5_context_t _ctx; -#endif - uint8_t i; - uint8_t * _buf = (uint8_t*)malloc(16); - if(_buf == NULL) - return false; - memset(_buf, 0x00, 16); -#ifdef ESP32 - mbedtls_md5_init(&_ctx); - mbedtls_md5_update_ret (&_ctx,data,len); - mbedtls_md5_finish_ret(&_ctx,data); - mbedtls_internal_md5_process( &_ctx ,data); -#else - MD5Init(&_ctx); - MD5Update(&_ctx, data, len); - MD5Final(_buf, &_ctx); -#endif - for(i = 0; i < 16; i++) { - sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]); - } - free(_buf); - return true; -} - -static String genRandomMD5(){ -#ifdef ESP8266 - uint32_t r = RANDOM_REG32; -#else - uint32_t r = rand(); -#endif - char * out = (char*)malloc(33); - if(out == NULL || !getMD5((uint8_t*)(&r), 4, out)) - return emptyString; - String res = String(out); - free(out); - return res; -} - -static String stringMD5(const String& in){ - char * out = (char*)malloc(33); - if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) - return emptyString; - String res = String(out); - free(out); - return res; -} - -String generateDigestHash(const char * username, const char * password, const char * realm){ - if(username == NULL || password == NULL || realm == NULL){ - return emptyString; - } - char * out = (char*)malloc(33); - String res = String(username); - res += ':'; - res.concat(realm); - res += ':'; - String in = res; - in.concat(password); - if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) - return emptyString; - res.concat(out); - free(out); - return res; -} - -String requestDigestAuthentication(const char * realm){ - String header = F("realm=\""); - if(realm == NULL) - header.concat(F("asyncesp")); - else - header.concat(realm); - header.concat(F("\", qop=\"auth\", nonce=\"")); - header.concat(genRandomMD5()); - header.concat(F("\", opaque=\"")); - header.concat(genRandomMD5()); - header += '"'; - return header; -} - -bool checkDigestAuthentication(const char * header, const __FlashStringHelper *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri){ - if(username == NULL || password == NULL || header == NULL || method == NULL){ - //os_printf("AUTH FAIL: missing requred fields\n"); - return false; - } - - String myHeader = String(header); - int nextBreak = myHeader.indexOf(','); - if(nextBreak < 0){ - //os_printf("AUTH FAIL: no variables\n"); - return false; - } - - String myUsername = String(); - String myRealm = String(); - String myNonce = String(); - String myUri = String(); - String myResponse = String(); - String myQop = String(); - String myNc = String(); - String myCnonce = String(); - - myHeader += F(", "); - do { - String avLine = myHeader.substring(0, nextBreak); - avLine.trim(); - myHeader = myHeader.substring(nextBreak+1); - nextBreak = myHeader.indexOf(','); - - int eqSign = avLine.indexOf('='); - if(eqSign < 0){ - //os_printf("AUTH FAIL: no = sign\n"); - return false; - } - String varName = avLine.substring(0, eqSign); - avLine = avLine.substring(eqSign + 1); - if(avLine.startsWith(String('"'))){ - avLine = avLine.substring(1, avLine.length() - 1); - } - - if(varName.equals(F("username"))){ - if(!avLine.equals(username)){ - //os_printf("AUTH FAIL: username\n"); - return false; - } - myUsername = avLine; - } else if(varName.equals(F("realm"))){ - if(realm != NULL && !avLine.equals(realm)){ - //os_printf("AUTH FAIL: realm\n"); - return false; - } - myRealm = avLine; - } else if(varName.equals(F("nonce"))){ - if(nonce != NULL && !avLine.equals(nonce)){ - //os_printf("AUTH FAIL: nonce\n"); - return false; - } - myNonce = avLine; - } else if(varName.equals(F("opaque"))){ - if(opaque != NULL && !avLine.equals(opaque)){ - //os_printf("AUTH FAIL: opaque\n"); - return false; - } - } else if(varName.equals(F("uri"))){ - if(uri != NULL && !avLine.equals(uri)){ - //os_printf("AUTH FAIL: uri\n"); - return false; - } - myUri = avLine; - } else if(varName.equals(F("response"))){ - myResponse = avLine; - } else if(varName.equals(F("qop"))){ - myQop = avLine; - } else if(varName.equals(F("nc"))){ - myNc = avLine; - } else if(varName.equals(F("cnonce"))){ - myCnonce = avLine; - } - } while(nextBreak > 0); - - String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ':' + myRealm + ':' + String(password)); - String ha2 = String(method) + ':' + myUri; - String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + stringMD5(ha2); - - if(myResponse.equals(stringMD5(response))){ - //os_printf("AUTH SUCCESS\n"); - return true; - } - - //os_printf("AUTH FAIL: password\n"); - return false; -} +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "WebAuthentication.h" +#include +#ifdef ESP32 +#include "mbedtls/md5.h" +#else +#include "md5.h" +#endif + + +// Basic Auth hash = base64("username:password") + +bool checkBasicAuthentication(const char * hash, const char * username, const char * password){ + if(username == NULL || password == NULL || hash == NULL) + return false; + + size_t toencodeLen = strlen(username)+strlen(password)+1; + size_t encodedLen = base64_encode_expected_len(toencodeLen); + if(strlen(hash) != encodedLen) + return false; + + char *toencode = new char[toencodeLen+1]; + if(toencode == NULL){ + return false; + } + char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; + if(encoded == NULL){ + delete[] toencode; + return false; + } + sprintf_P(toencode, PSTR("%s:%s"), username, password); + if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0){ + delete[] toencode; + delete[] encoded; + return true; + } + delete[] toencode; + delete[] encoded; + return false; +} + +static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more +#ifdef ESP32 + mbedtls_md5_context _ctx; +#else + md5_context_t _ctx; +#endif + uint8_t i; + uint8_t * _buf = (uint8_t*)malloc(16); + if(_buf == NULL) + return false; + memset(_buf, 0x00, 16); +#ifdef ESP32 + mbedtls_md5_init(&_ctx); + mbedtls_md5_update_ret (&_ctx,data,len); + mbedtls_md5_finish_ret(&_ctx,data); + mbedtls_internal_md5_process( &_ctx ,data); +#else + MD5Init(&_ctx); + MD5Update(&_ctx, data, len); + MD5Final(_buf, &_ctx); +#endif + for(i = 0; i < 16; i++) { + sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]); + } + free(_buf); + return true; +} + +static String genRandomMD5(){ +#ifdef ESP8266 + uint32_t r = RANDOM_REG32; +#else + uint32_t r = rand(); +#endif + char * out = (char*)malloc(33); + if(out == NULL || !getMD5((uint8_t*)(&r), 4, out)) + return emptyString; + String res = String(out); + free(out); + return res; +} + +static String stringMD5(const String& in){ + char * out = (char*)malloc(33); + if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) + return emptyString; + String res = String(out); + free(out); + return res; +} + +String generateDigestHash(const char * username, const char * password, const char * realm){ + if(username == NULL || password == NULL || realm == NULL){ + return emptyString; + } + char * out = (char*)malloc(33); + String res = String(username); + res += ':'; + res.concat(realm); + res += ':'; + String in = res; + in.concat(password); + if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) + return emptyString; + res.concat(out); + free(out); + return res; +} + +String requestDigestAuthentication(const char * realm){ + String header = F("realm=\""); + if(realm == NULL) + header.concat(F("asyncesp")); + else + header.concat(realm); + header.concat(F("\", qop=\"auth\", nonce=\"")); + header.concat(genRandomMD5()); + header.concat(F("\", opaque=\"")); + header.concat(genRandomMD5()); + header += '"'; + return header; +} + +bool checkDigestAuthentication(const char * header, const char *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri){ + if(username == NULL || password == NULL || header == NULL || method == NULL){ + //os_printf("AUTH FAIL: missing requred fields\n"); + return false; + } + + String myHeader = String(header); + int nextBreak = myHeader.indexOf(','); + if(nextBreak < 0){ + //os_printf("AUTH FAIL: no variables\n"); + return false; + } + + String myUsername = String(); + String myRealm = String(); + String myNonce = String(); + String myUri = String(); + String myResponse = String(); + String myQop = String(); + String myNc = String(); + String myCnonce = String(); + + myHeader += F(", "); + do { + String avLine = myHeader.substring(0, nextBreak); + avLine.trim(); + myHeader = myHeader.substring(nextBreak+1); + nextBreak = myHeader.indexOf(','); + + int eqSign = avLine.indexOf('='); + if(eqSign < 0){ + //os_printf("AUTH FAIL: no = sign\n"); + return false; + } + String varName = avLine.substring(0, eqSign); + avLine = avLine.substring(eqSign + 1); + if(avLine.startsWith(String('"'))){ + avLine = avLine.substring(1, avLine.length() - 1); + } + + if(varName.equals(F("username"))){ + if(!avLine.equals(username)){ + //os_printf("AUTH FAIL: username\n"); + return false; + } + myUsername = avLine; + } else if(varName.equals(F("realm"))){ + if(realm != NULL && !avLine.equals(realm)){ + //os_printf("AUTH FAIL: realm\n"); + return false; + } + myRealm = avLine; + } else if(varName.equals(F("nonce"))){ + if(nonce != NULL && !avLine.equals(nonce)){ + //os_printf("AUTH FAIL: nonce\n"); + return false; + } + myNonce = avLine; + } else if(varName.equals(F("opaque"))){ + if(opaque != NULL && !avLine.equals(opaque)){ + //os_printf("AUTH FAIL: opaque\n"); + return false; + } + } else if(varName.equals(F("uri"))){ + if(uri != NULL && !avLine.equals(uri)){ + //os_printf("AUTH FAIL: uri\n"); + return false; + } + myUri = avLine; + } else if(varName.equals(F("response"))){ + myResponse = avLine; + } else if(varName.equals(F("qop"))){ + myQop = avLine; + } else if(varName.equals(F("nc"))){ + myNc = avLine; + } else if(varName.equals(F("cnonce"))){ + myCnonce = avLine; + } + } while(nextBreak > 0); + + String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ':' + myRealm + ':' + String(password)); + String ha2 = String(method) + ':' + myUri; + String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + stringMD5(ha2); + + if(myResponse.equals(stringMD5(response))){ + //os_printf("AUTH SUCCESS\n"); + return true; + } + + //os_printf("AUTH FAIL: password\n"); + return false; +} diff --git a/lib/ESPAsyncWebServer/WebAuthentication.h b/lib/ESPAsyncWebServer/WebAuthentication.h index a6f1966e3..951f36a22 100644 --- a/lib/ESPAsyncWebServer/WebAuthentication.h +++ b/lib/ESPAsyncWebServer/WebAuthentication.h @@ -26,7 +26,7 @@ bool checkBasicAuthentication(const char * header, const char * username, const char * password); String requestDigestAuthentication(const char * realm); -bool checkDigestAuthentication(const char * header, const __FlashStringHelper *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri); +bool checkDigestAuthentication(const char * header, const char *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri); //for storing hashed versions on the device that can be authenticated against String generateDigestHash(const char * username, const char * password, const char * realm); diff --git a/lib/ESPAsyncWebServer/WebRequest.cpp b/lib/ESPAsyncWebServer/WebRequest.cpp index 9acec396a..ffe2c2623 100644 --- a/lib/ESPAsyncWebServer/WebRequest.cpp +++ b/lib/ESPAsyncWebServer/WebRequest.cpp @@ -1,930 +1,930 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "ESPAsyncWebServer.h" -#include "WebResponseImpl.h" -#include "WebAuthentication.h" - -#ifndef ESP8266 -#define os_strlen strlen -#endif - -#define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '=')) - -enum { PARSE_REQ_START, PARSE_REQ_HEADERS, PARSE_REQ_BODY, PARSE_REQ_END, PARSE_REQ_FAIL }; - -AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c) - : _client(c) - , _server(s) - , _handler(NULL) - , _response(NULL) - , _temp() - , _parseState(0) - , _version(0) - , _method(HTTP_ANY) - , _url() - , _host() - , _contentType() - , _boundary() - , _authorization() - , _reqconntype(RCT_HTTP) - , _isDigest(false) - , _isMultipart(false) - , _isPlainPost(false) - , _expectingContinue(false) - , _contentLength(0) - , _parsedLength(0) - , _headers(LinkedList([](AsyncWebHeader *h){ delete h; })) - , _params(LinkedList([](AsyncWebParameter *p){ delete p; })) - , _pathParams(LinkedList([](String *p){ delete p; })) - , _multiParseState(0) - , _boundaryPosition(0) - , _itemStartIndex(0) - , _itemSize(0) - , _itemName() - , _itemFilename() - , _itemType() - , _itemValue() - , _itemBuffer(0) - , _itemBufferIndex(0) - , _itemIsFile(false) - , _tempObject(NULL) -{ - c->onError([](void *r, AsyncClient* c, int8_t error){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this); - c->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onAck(len, time); }, this); - c->onDisconnect([](void *r, AsyncClient* c){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onDisconnect(); delete c; }, this); - c->onTimeout([](void *r, AsyncClient* c, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onTimeout(time); }, this); - c->onData([](void *r, AsyncClient* c, void *buf, size_t len){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onData(buf, len); }, this); - c->onPoll([](void *r, AsyncClient* c){ (void)c; AsyncWebServerRequest *req = ( AsyncWebServerRequest*)r; req->_onPoll(); }, this); -} - -AsyncWebServerRequest::~AsyncWebServerRequest(){ - _headers.free(); - - _params.free(); - _pathParams.free(); - - _interestingHeaders.free(); - - if(_response != NULL){ - delete _response; - } - - if(_tempObject != NULL){ - free(_tempObject); - } - - if(_tempFile){ - _tempFile.close(); - } -} - -void AsyncWebServerRequest::_onData(void *buf, size_t len){ - size_t i = 0; - while (true) { - - if(_parseState < PARSE_REQ_BODY){ - // Find new line in buf - char *str = (char*)buf; - for (i = 0; i < len; i++) { - if (str[i] == '\n') { - break; - } - } - if (i == len) { // No new line, just add the buffer in _temp - char ch = str[len-1]; - str[len-1] = 0; - _temp.reserve(_temp.length()+len); - _temp.concat(str); - _temp.concat(ch); - } else { // Found new line - extract it and parse - str[i] = 0; // Terminate the string at the end of the line. - _temp.concat(str); - _temp.trim(); - _parseLine(); - if (++i < len) { - // Still have more buffer to process - buf = str+i; - len-= i; - continue; - } - } - } else if(_parseState == PARSE_REQ_BODY){ - // A handler should be already attached at this point in _parseLine function. - // If handler does nothing (_onRequest is NULL), we don't need to really parse the body. - const bool needParse = _handler && !_handler->isRequestHandlerTrivial(); - if(_isMultipart){ - if(needParse){ - size_t i; - for(i=0; i end) equal = end; - String name = params.substring(start, equal); - String value = equal + 1 < end ? params.substring(equal + 1, end) : String(); - _addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value))); - start = end + 1; - } -} - -bool AsyncWebServerRequest::_parseReqHead(){ - // Split the head into method, url and version - int index = _temp.indexOf(' '); - String m = _temp.substring(0, index); - index = _temp.indexOf(' ', index+1); - String u = _temp.substring(m.length()+1, index); - _temp = _temp.substring(index+1); - - if(m == F("GET")){ - _method = HTTP_GET; - } else if(m == F("POST")){ - _method = HTTP_POST; - } else if(m == F("DELETE")){ - _method = HTTP_DELETE; - } else if(m == F("PUT")){ - _method = HTTP_PUT; - } else if(m == F("PATCH")){ - _method = HTTP_PATCH; - } else if(m == F("HEAD")){ - _method = HTTP_HEAD; - } else if(m == F("OPTIONS")){ - _method = HTTP_OPTIONS; - } - - String g; - index = u.indexOf('?'); - if(index > 0){ - g = u.substring(index +1); - u = u.substring(0, index); - } - _url = urlDecode(u); - _addGetParams(g); - - if(!_temp.startsWith(F("HTTP/1.0"))) - _version = 1; - - _temp = String(); - return true; -} - -bool strContains(const String &src, const String &find, bool mindcase = true) { - int pos=0, i=0; - const int slen = src.length(); - const int flen = find.length(); - - if (slen < flen) return false; - while (pos <= (slen - flen)) { - for (i=0; i < flen; i++) { - if (mindcase) { - if (src[pos+i] != find[i]) i = flen + 1; // no match - } - else if (tolower(src[pos+i]) != tolower(find[i])) { - i = flen + 1; // no match - } - } - if (i == flen) return true; - pos++; - } - return false; -} - -bool AsyncWebServerRequest::_parseReqHeader(){ - int index = _temp.indexOf(':'); - if(index){ - String name = _temp.substring(0, index); - String value = _temp.substring(index + 2); - if(name.equalsIgnoreCase("Host")){ - _host = value; - } else if(name.equalsIgnoreCase(F("Content-Type"))){ - _contentType = value.substring(0, value.indexOf(';')); - if (value.startsWith(F("multipart/"))){ - _boundary = value.substring(value.indexOf('=')+1); - _boundary.replace(String('"'), String()); - _isMultipart = true; - } - } else if(name.equalsIgnoreCase(F("Content-Length"))){ - _contentLength = atoi(value.c_str()); - } else if(name.equalsIgnoreCase(F("Expect")) && value == F("100-continue")){ - _expectingContinue = true; - } else if(name.equalsIgnoreCase(F("Authorization"))){ - if(value.length() > 5 && value.substring(0,5).equalsIgnoreCase(F("Basic"))){ - _authorization = value.substring(6); - } else if(value.length() > 6 && value.substring(0,6).equalsIgnoreCase(F("Digest"))){ - _isDigest = true; - _authorization = value.substring(7); - } - } else { - if(name.equalsIgnoreCase(F("Upgrade")) && value.equalsIgnoreCase(F("websocket"))){ - // WebSocket request can be uniquely identified by header: [Upgrade: websocket] - _reqconntype = RCT_WS; - } else { - if(name.equalsIgnoreCase(F("Accept")) && strContains(value, F("text/event-stream"), false)){ - // WebEvent request can be uniquely identified by header: [Accept: text/event-stream] - _reqconntype = RCT_EVENT; - } - } - } - _headers.add(new AsyncWebHeader(name, value)); - } - _temp = String(); - return true; -} - -void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data){ - if(data && (char)data != '&') - _temp += (char)data; - if(!data || (char)data == '&' || _parsedLength == _contentLength){ - String name = F("body"); - String value = _temp; - if(!_temp.startsWith(String('{')) && !_temp.startsWith(String('[')) && _temp.indexOf('=') > 0){ - name = _temp.substring(0, _temp.indexOf('=')); - value = _temp.substring(_temp.indexOf('=') + 1); - } - _addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value), true)); - _temp = String(); - } -} - -void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last){ - _itemBuffer[_itemBufferIndex++] = data; - - if(last || _itemBufferIndex == 1460){ - //check if authenticated before calling the upload - if(_handler) - _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, false); - _itemBufferIndex = 0; - } -} - -enum { - EXPECT_BOUNDARY, - PARSE_HEADERS, - WAIT_FOR_RETURN1, - EXPECT_FEED1, - EXPECT_DASH1, - EXPECT_DASH2, - BOUNDARY_OR_DATA, - DASH3_OR_RETURN2, - EXPECT_FEED2, - PARSING_FINISHED, - PARSE_ERROR -}; - -void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last){ -#define itemWriteByte(b) do { _itemSize++; if(_itemIsFile) _handleUploadByte(b, last); else _itemValue+=(char)(b); } while(0) - - if(!_parsedLength){ - _multiParseState = EXPECT_BOUNDARY; - _temp = String(); - _itemName = String(); - _itemFilename = String(); - _itemType = String(); - } - - if(_multiParseState == WAIT_FOR_RETURN1){ - if(data != '\r'){ - itemWriteByte(data); - } else { - _multiParseState = EXPECT_FEED1; - } - } else if(_multiParseState == EXPECT_BOUNDARY){ - if(_parsedLength < 2 && data != '-'){ - _multiParseState = PARSE_ERROR; - return; - } else if(_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data){ - _multiParseState = PARSE_ERROR; - return; - } else if(_parsedLength - 2 == _boundary.length() && data != '\r'){ - _multiParseState = PARSE_ERROR; - return; - } else if(_parsedLength - 3 == _boundary.length()){ - if(data != '\n'){ - _multiParseState = PARSE_ERROR; - return; - } - _multiParseState = PARSE_HEADERS; - _itemIsFile = false; - } - } else if(_multiParseState == PARSE_HEADERS){ - if((char)data != '\r' && (char)data != '\n') - _temp += (char)data; - if((char)data == '\n'){ - if(_temp.length()){ - if(_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase(F("Content-Type"))){ - _itemType = _temp.substring(14); - _itemIsFile = true; - } else if(_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))){ - _temp = _temp.substring(_temp.indexOf(';') + 2); - while(_temp.indexOf(';') > 0){ - String name = _temp.substring(0, _temp.indexOf('=')); - String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1); - if(name == F("name")){ - _itemName = nameVal; - } else if(name == F("filename")){ - _itemFilename = nameVal; - _itemIsFile = true; - } - _temp = _temp.substring(_temp.indexOf(';') + 2); - } - String name = _temp.substring(0, _temp.indexOf('=')); - String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1); - if(name == F("name")){ - _itemName = nameVal; - } else if(name == F("filename")){ - _itemFilename = nameVal; - _itemIsFile = true; - } - } - _temp = String(); - } else { - _multiParseState = WAIT_FOR_RETURN1; - //value starts from here - _itemSize = 0; - _itemStartIndex = _parsedLength; - _itemValue = String(); - if(_itemIsFile){ - if(_itemBuffer) - free(_itemBuffer); - _itemBuffer = (uint8_t*)malloc(1460); - if(_itemBuffer == NULL){ - _multiParseState = PARSE_ERROR; - return; - } - _itemBufferIndex = 0; - } - } - } - } else if(_multiParseState == EXPECT_FEED1){ - if(data != '\n'){ - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); _parseMultipartPostByte(data, last); - } else { - _multiParseState = EXPECT_DASH1; - } - } else if(_multiParseState == EXPECT_DASH1){ - if(data != '-'){ - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); itemWriteByte('\n'); _parseMultipartPostByte(data, last); - } else { - _multiParseState = EXPECT_DASH2; - } - } else if(_multiParseState == EXPECT_DASH2){ - if(data != '-'){ - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); _parseMultipartPostByte(data, last); - } else { - _multiParseState = BOUNDARY_OR_DATA; - _boundaryPosition = 0; - } - } else if(_multiParseState == BOUNDARY_OR_DATA){ - if(_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data){ - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); - uint8_t i; - for(i=0; i<_boundaryPosition; i++) - itemWriteByte(_boundary.c_str()[i]); - _parseMultipartPostByte(data, last); - } else if(_boundaryPosition == _boundary.length() - 1){ - _multiParseState = DASH3_OR_RETURN2; - if(!_itemIsFile){ - _addParam(new AsyncWebParameter(_itemName, _itemValue, true)); - } else { - if(_itemSize){ - //check if authenticated before calling the upload - if(_handler) _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true); - _itemBufferIndex = 0; - _addParam(new AsyncWebParameter(_itemName, _itemFilename, true, true, _itemSize)); - } - free(_itemBuffer); - _itemBuffer = NULL; - } - - } else { - _boundaryPosition++; - } - } else if(_multiParseState == DASH3_OR_RETURN2){ - if(data == '-' && (_contentLength - _parsedLength - 4) != 0){ - //os_printf("ERROR: The parser got to the end of the POST but is expecting %u bytes more!\nDrop an issue so we can have more info on the matter!\n", _contentLength - _parsedLength - 4); - _contentLength = _parsedLength + 4;//lets close the request gracefully - } - if(data == '\r'){ - _multiParseState = EXPECT_FEED2; - } else if(data == '-' && _contentLength == (_parsedLength + 4)){ - _multiParseState = PARSING_FINISHED; - } else { - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); - uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); - _parseMultipartPostByte(data, last); - } - } else if(_multiParseState == EXPECT_FEED2){ - if(data == '\n'){ - _multiParseState = PARSE_HEADERS; - _itemIsFile = false; - } else { - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); - uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); - itemWriteByte('\r'); _parseMultipartPostByte(data, last); - } - } -} - -void AsyncWebServerRequest::_parseLine(){ - if(_parseState == PARSE_REQ_START){ - if(!_temp.length()){ - _parseState = PARSE_REQ_FAIL; - _client->close(); - } else { - _parseReqHead(); - _parseState = PARSE_REQ_HEADERS; - } - return; - } - - if(_parseState == PARSE_REQ_HEADERS){ - if(!_temp.length()){ - //end of headers - _server->_rewriteRequest(this); - _server->_attachHandler(this); - _removeNotInterestingHeaders(); - if(_expectingContinue){ - String response = F("HTTP/1.1 100 Continue\r\n\r\n"); - _client->write(response.c_str(), response.length()); - } - //check handler for authentication - if(_contentLength){ - _parseState = PARSE_REQ_BODY; - } else { - _parseState = PARSE_REQ_END; - if(_handler) _handler->handleRequest(this); - else send(501); - } - } else _parseReqHeader(); - } -} - -size_t AsyncWebServerRequest::headers() const{ - return _headers.length(); -} - -bool AsyncWebServerRequest::hasHeader(const String& name) const { - for(const auto& h: _headers){ - if(h->name().equalsIgnoreCase(name)){ - return true; - } - } - return false; -} - -bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper * data) const { - return hasHeader(String(data)); -} - -AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) const { - for(const auto& h: _headers){ - if(h->name().equalsIgnoreCase(name)){ - return h; - } - } - return nullptr; -} - -AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper * data) const { - return getHeader(String(data)); -} - -AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const { - auto header = _headers.nth(num); - return header ? *header : nullptr; -} - -size_t AsyncWebServerRequest::params() const { - return _params.length(); -} - -bool AsyncWebServerRequest::hasParam(const String& name, bool post, bool file) const { - for(const auto& p: _params){ - if(p->name() == name && p->isPost() == post && p->isFile() == file){ - return true; - } - } - return false; -} - -bool AsyncWebServerRequest::hasParam(const __FlashStringHelper * data, bool post, bool file) const { - return hasParam(String(data).c_str(), post, file); -} - -AsyncWebParameter* AsyncWebServerRequest::getParam(const String& name, bool post, bool file) const { - for(const auto& p: _params){ - if(p->name() == name && p->isPost() == post && p->isFile() == file){ - return p; - } - } - return nullptr; -} - -AsyncWebParameter* AsyncWebServerRequest::getParam(const __FlashStringHelper * data, bool post, bool file) const { - return getParam(String(data).c_str(), post, file); -} - -AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const { - auto param = _params.nth(num); - return param ? *param : nullptr; -} - -void AsyncWebServerRequest::addInterestingHeader(const String& name){ - if(!_interestingHeaders.containsIgnoreCase(name)) - _interestingHeaders.add(name); -} - -void AsyncWebServerRequest::send(AsyncWebServerResponse *response){ - _response = response; - if(_response == NULL){ - _client->close(true); - _onDisconnect(); - return; - } - if(!_response->_sourceValid()){ - delete response; - _response = NULL; - send(500); - } - else { - _client->setRxTimeout(0); - _response->_respond(this); - } -} - -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const String& contentType, const String& content){ - return new AsyncBasicResponse(code, contentType, content); -} - -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ - if(fs.exists(path) || (!download && fs.exists(path+F(".gz")))) - return new AsyncFileResponse(fs, path, contentType, download, callback); - return NULL; -} - -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ - if(content == true) - return new AsyncFileResponse(content, path, contentType, download, callback); - return NULL; -} - -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){ - return new AsyncStreamResponse(stream, contentType, len, callback); -} - -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ - return new AsyncCallbackResponse(contentType, len, callback, templateCallback); -} - -AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ - if(_version) - return new AsyncChunkedResponse(contentType, callback, templateCallback); - return new AsyncCallbackResponse(contentType, 0, callback, templateCallback); -} - -AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(const String& contentType, size_t bufferSize){ - return new AsyncResponseStream(contentType, bufferSize); -} - -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){ - return new AsyncProgmemResponse(code, contentType, content, len, callback); -} - -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){ - return beginResponse_P(code, contentType, (const uint8_t *)content, strlen_P(content), callback); -} - -void AsyncWebServerRequest::send(int code, const String& contentType, const String& content){ - send(beginResponse(code, contentType, content)); -} - -void AsyncWebServerRequest::send(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ - if(fs.exists(path) || (!download && fs.exists(path+F(".gz")))){ - send(beginResponse(fs, path, contentType, download, callback)); - } else send(404); -} - -void AsyncWebServerRequest::send(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ - if(content == true){ - send(beginResponse(content, path, contentType, download, callback)); - } else send(404); -} - -void AsyncWebServerRequest::send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){ - send(beginResponse(stream, contentType, len, callback)); -} - -void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ - send(beginResponse(contentType, len, callback, templateCallback)); -} - -void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ - send(beginChunkedResponse(contentType, callback, templateCallback)); -} - -void AsyncWebServerRequest::send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){ - send(beginResponse_P(code, contentType, content, len, callback)); -} - -void AsyncWebServerRequest::send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){ - send(beginResponse_P(code, contentType, content, callback)); -} - -void AsyncWebServerRequest::redirect(const String& url){ - AsyncWebServerResponse * response = beginResponse(302); - response->addHeader(F("Location"), url); - send(response); -} - -bool AsyncWebServerRequest::authenticate(const char * username, const char * password, const char * realm, bool passwordIsHash){ - if(_authorization.length()){ - if(_isDigest) - return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL); - else if(!passwordIsHash) - return checkBasicAuthentication(_authorization.c_str(), username, password); - else - return _authorization.equals(password); - } - return false; -} - -bool AsyncWebServerRequest::authenticate(const char * hash){ - if(!_authorization.length() || hash == NULL) - return false; - - if(_isDigest){ - String hStr = String(hash); - int separator = hStr.indexOf(':'); - if(separator <= 0) - return false; - String username = hStr.substring(0, separator); - hStr = hStr.substring(separator + 1); - separator = hStr.indexOf(':'); - if(separator <= 0) - return false; - String realm = hStr.substring(0, separator); - hStr = hStr.substring(separator + 1); - return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL); - } - - return (_authorization.equals(hash)); -} - -void AsyncWebServerRequest::requestAuthentication(const char * realm, bool isDigest){ - AsyncWebServerResponse * r = beginResponse(401); - if(!isDigest && realm == NULL){ - r->addHeader(F("WWW-Authenticate"), F("Basic realm=\"Login Required\"")); - } else if(!isDigest){ - String header = F("Basic realm=\""); - header.concat(realm); - header += '"'; - r->addHeader(F("WWW-Authenticate"), header); - } else { - String header = F("Digest "); - header.concat(requestDigestAuthentication(realm)); - r->addHeader(F("WWW-Authenticate"), header); - } - send(r); -} - -bool AsyncWebServerRequest::hasArg(const char* name) const { - for(const auto& arg: _params){ - if(arg->name() == name){ - return true; - } - } - return false; -} - -bool AsyncWebServerRequest::hasArg(const __FlashStringHelper * data) const { - return hasArg(String(data).c_str()); -} - - -const String& AsyncWebServerRequest::arg(const String& name) const { - for(const auto& arg: _params){ - if(arg->name() == name){ - return arg->value(); - } - } - return emptyString; -} - -const String& AsyncWebServerRequest::arg(const __FlashStringHelper * data) const { - return arg(String(data).c_str()); -} - -const String& AsyncWebServerRequest::arg(size_t i) const { - return getParam(i)->value(); -} - -const String& AsyncWebServerRequest::argName(size_t i) const { - return getParam(i)->name(); -} - -const String& AsyncWebServerRequest::pathArg(size_t i) const { - auto param = _pathParams.nth(i); - return param ? **param : emptyString; -} - -const String& AsyncWebServerRequest::header(const char* name) const { - AsyncWebHeader* h = getHeader(String(name)); - return h ? h->value() : emptyString; -} - -const String& AsyncWebServerRequest::header(const __FlashStringHelper * data) const { - return header(String(data).c_str()); -}; - - -const String& AsyncWebServerRequest::header(size_t i) const { - AsyncWebHeader* h = getHeader(i); - return h ? h->value() : emptyString; -} - -const String& AsyncWebServerRequest::headerName(size_t i) const { - AsyncWebHeader* h = getHeader(i); - return h ? h->name() : emptyString; -} - -String AsyncWebServerRequest::urlDecode(const String& text) const { - char temp[] = "0x00"; - unsigned int len = text.length(); - unsigned int i = 0; - String decoded = String(); - decoded.reserve(len); // Allocate the string internal buffer - never longer from source text - while (i < len){ - char decodedChar; - char encodedChar = text.charAt(i++); - if ((encodedChar == '%') && (i + 1 < len)){ - temp[2] = text.charAt(i++); - temp[3] = text.charAt(i++); - decodedChar = strtol(temp, NULL, 16); - } else if (encodedChar == '+') { - decodedChar = ' '; - } else { - decodedChar = encodedChar; // normal ascii char - } - decoded.concat(decodedChar); - } - return decoded; -} - - -const __FlashStringHelper *AsyncWebServerRequest::methodToString() const { - if(_method == HTTP_ANY) return F("ANY"); - else if(_method & HTTP_GET) return F("GET"); - else if(_method & HTTP_POST) return F("POST"); - else if(_method & HTTP_DELETE) return F("DELETE"); - else if(_method & HTTP_PUT) return F("PUT"); - else if(_method & HTTP_PATCH) return F("PATCH"); - else if(_method & HTTP_HEAD) return F("HEAD"); - else if(_method & HTTP_OPTIONS) return F("OPTIONS"); - return F("UNKNOWN"); -} - -const __FlashStringHelper *AsyncWebServerRequest::requestedConnTypeToString() const { - switch (_reqconntype) { - case RCT_NOT_USED: return F("RCT_NOT_USED"); - case RCT_DEFAULT: return F("RCT_DEFAULT"); - case RCT_HTTP: return F("RCT_HTTP"); - case RCT_WS: return F("RCT_WS"); - case RCT_EVENT: return F("RCT_EVENT"); - default: return F("ERROR"); - } -} - -bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) { - bool res = false; - if ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype)) res = true; - if ((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype)) res = true; - if ((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype)) res = true; - return res; -} +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ESPAsyncWebServer.h" +#include "WebResponseImpl.h" +#include "WebAuthentication.h" + +#ifndef ESP8266 +#define os_strlen strlen +#endif + +#define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '=')) + +enum { PARSE_REQ_START, PARSE_REQ_HEADERS, PARSE_REQ_BODY, PARSE_REQ_END, PARSE_REQ_FAIL }; + +AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c) + : _client(c) + , _server(s) + , _handler(NULL) + , _response(NULL) + , _temp() + , _parseState(0) + , _version(0) + , _method(HTTP_ANY) + , _url() + , _host() + , _contentType() + , _boundary() + , _authorization() + , _reqconntype(RCT_HTTP) + , _isDigest(false) + , _isMultipart(false) + , _isPlainPost(false) + , _expectingContinue(false) + , _contentLength(0) + , _parsedLength(0) + , _headers(LinkedList([](AsyncWebHeader *h){ delete h; })) + , _params(LinkedList([](AsyncWebParameter *p){ delete p; })) + , _pathParams(LinkedList([](String *p){ delete p; })) + , _multiParseState(0) + , _boundaryPosition(0) + , _itemStartIndex(0) + , _itemSize(0) + , _itemName() + , _itemFilename() + , _itemType() + , _itemValue() + , _itemBuffer(0) + , _itemBufferIndex(0) + , _itemIsFile(false) + , _tempObject(NULL) +{ + c->onError([](void *r, AsyncClient* c, int8_t error){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this); + c->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onAck(len, time); }, this); + c->onDisconnect([](void *r, AsyncClient* c){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onDisconnect(); delete c; }, this); + c->onTimeout([](void *r, AsyncClient* c, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onTimeout(time); }, this); + c->onData([](void *r, AsyncClient* c, void *buf, size_t len){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onData(buf, len); }, this); + c->onPoll([](void *r, AsyncClient* c){ (void)c; AsyncWebServerRequest *req = ( AsyncWebServerRequest*)r; req->_onPoll(); }, this); +} + +AsyncWebServerRequest::~AsyncWebServerRequest(){ + _headers.free(); + + _params.free(); + _pathParams.free(); + + _interestingHeaders.free(); + + if(_response != NULL){ + delete _response; + } + + if(_tempObject != NULL){ + free(_tempObject); + } + + if(_tempFile){ + _tempFile.close(); + } +} + +void AsyncWebServerRequest::_onData(void *buf, size_t len){ + size_t i = 0; + while (true) { + + if(_parseState < PARSE_REQ_BODY){ + // Find new line in buf + char *str = (char*)buf; + for (i = 0; i < len; i++) { + if (str[i] == '\n') { + break; + } + } + if (i == len) { // No new line, just add the buffer in _temp + char ch = str[len-1]; + str[len-1] = 0; + _temp.reserve(_temp.length()+len); + _temp.concat(str); + _temp.concat(ch); + } else { // Found new line - extract it and parse + str[i] = 0; // Terminate the string at the end of the line. + _temp.concat(str); + _temp.trim(); + _parseLine(); + if (++i < len) { + // Still have more buffer to process + buf = str+i; + len-= i; + continue; + } + } + } else if(_parseState == PARSE_REQ_BODY){ + // A handler should be already attached at this point in _parseLine function. + // If handler does nothing (_onRequest is NULL), we don't need to really parse the body. + const bool needParse = _handler && !_handler->isRequestHandlerTrivial(); + if(_isMultipart){ + if(needParse){ + size_t i; + for(i=0; i end) equal = end; + String name = params.substring(start, equal); + String value = equal + 1 < end ? params.substring(equal + 1, end) : String(); + _addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value))); + start = end + 1; + } +} + +bool AsyncWebServerRequest::_parseReqHead(){ + // Split the head into method, url and version + int index = _temp.indexOf(' '); + String m = _temp.substring(0, index); + index = _temp.indexOf(' ', index+1); + String u = _temp.substring(m.length()+1, index); + _temp = _temp.substring(index+1); + + if(m == F("GET")){ + _method = HTTP_GET; + } else if(m == F("POST")){ + _method = HTTP_POST; + } else if(m == F("DELETE")){ + _method = HTTP_DELETE; + } else if(m == F("PUT")){ + _method = HTTP_PUT; + } else if(m == F("PATCH")){ + _method = HTTP_PATCH; + } else if(m == F("HEAD")){ + _method = HTTP_HEAD; + } else if(m == F("OPTIONS")){ + _method = HTTP_OPTIONS; + } + + String g; + index = u.indexOf('?'); + if(index > 0){ + g = u.substring(index +1); + u = u.substring(0, index); + } + _url = urlDecode(u); + _addGetParams(g); + + if(!_temp.startsWith(F("HTTP/1.0"))) + _version = 1; + + _temp = String(); + return true; +} + +bool strContains(const String &src, const String &find, bool mindcase = true) { + int pos=0, i=0; + const int slen = src.length(); + const int flen = find.length(); + + if (slen < flen) return false; + while (pos <= (slen - flen)) { + for (i=0; i < flen; i++) { + if (mindcase) { + if (src[pos+i] != find[i]) i = flen + 1; // no match + } + else if (tolower(src[pos+i]) != tolower(find[i])) { + i = flen + 1; // no match + } + } + if (i == flen) return true; + pos++; + } + return false; +} + +bool AsyncWebServerRequest::_parseReqHeader(){ + int index = _temp.indexOf(':'); + if(index){ + String name = _temp.substring(0, index); + String value = _temp.substring(index + 2); + if(name.equalsIgnoreCase("Host")){ + _host = value; + } else if(name.equalsIgnoreCase(F("Content-Type"))){ + _contentType = value.substring(0, value.indexOf(';')); + if (value.startsWith(F("multipart/"))){ + _boundary = value.substring(value.indexOf('=')+1); + _boundary.replace(String('"'), String()); + _isMultipart = true; + } + } else if(name.equalsIgnoreCase(F("Content-Length"))){ + _contentLength = atoi(value.c_str()); + } else if(name.equalsIgnoreCase(F("Expect")) && value == F("100-continue")){ + _expectingContinue = true; + } else if(name.equalsIgnoreCase(F("Authorization"))){ + if(value.length() > 5 && value.substring(0,5).equalsIgnoreCase(F("Basic"))){ + _authorization = value.substring(6); + } else if(value.length() > 6 && value.substring(0,6).equalsIgnoreCase(F("Digest"))){ + _isDigest = true; + _authorization = value.substring(7); + } + } else { + if(name.equalsIgnoreCase(F("Upgrade")) && value.equalsIgnoreCase(F("websocket"))){ + // WebSocket request can be uniquely identified by header: [Upgrade: websocket] + _reqconntype = RCT_WS; + } else { + if(name.equalsIgnoreCase(F("Accept")) && strContains(value, F("text/event-stream"), false)){ + // WebEvent request can be uniquely identified by header: [Accept: text/event-stream] + _reqconntype = RCT_EVENT; + } + } + } + _headers.add(new AsyncWebHeader(name, value)); + } + _temp = String(); + return true; +} + +void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data){ + if(data && (char)data != '&') + _temp += (char)data; + if(!data || (char)data == '&' || _parsedLength == _contentLength){ + String name = F("body"); + String value = _temp; + if(!_temp.startsWith(String('{')) && !_temp.startsWith(String('[')) && _temp.indexOf('=') > 0){ + name = _temp.substring(0, _temp.indexOf('=')); + value = _temp.substring(_temp.indexOf('=') + 1); + } + _addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value), true)); + _temp = String(); + } +} + +void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last){ + _itemBuffer[_itemBufferIndex++] = data; + + if(last || _itemBufferIndex == 1460){ + //check if authenticated before calling the upload + if(_handler) + _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, false); + _itemBufferIndex = 0; + } +} + +enum { + EXPECT_BOUNDARY, + PARSE_HEADERS, + WAIT_FOR_RETURN1, + EXPECT_FEED1, + EXPECT_DASH1, + EXPECT_DASH2, + BOUNDARY_OR_DATA, + DASH3_OR_RETURN2, + EXPECT_FEED2, + PARSING_FINISHED, + PARSE_ERROR +}; + +void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last){ +#define itemWriteByte(b) do { _itemSize++; if(_itemIsFile) _handleUploadByte(b, last); else _itemValue+=(char)(b); } while(0) + + if(!_parsedLength){ + _multiParseState = EXPECT_BOUNDARY; + _temp = String(); + _itemName = String(); + _itemFilename = String(); + _itemType = String(); + } + + if(_multiParseState == WAIT_FOR_RETURN1){ + if(data != '\r'){ + itemWriteByte(data); + } else { + _multiParseState = EXPECT_FEED1; + } + } else if(_multiParseState == EXPECT_BOUNDARY){ + if(_parsedLength < 2 && data != '-'){ + _multiParseState = PARSE_ERROR; + return; + } else if(_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data){ + _multiParseState = PARSE_ERROR; + return; + } else if(_parsedLength - 2 == _boundary.length() && data != '\r'){ + _multiParseState = PARSE_ERROR; + return; + } else if(_parsedLength - 3 == _boundary.length()){ + if(data != '\n'){ + _multiParseState = PARSE_ERROR; + return; + } + _multiParseState = PARSE_HEADERS; + _itemIsFile = false; + } + } else if(_multiParseState == PARSE_HEADERS){ + if((char)data != '\r' && (char)data != '\n') + _temp += (char)data; + if((char)data == '\n'){ + if(_temp.length()){ + if(_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase(F("Content-Type"))){ + _itemType = _temp.substring(14); + _itemIsFile = true; + } else if(_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))){ + _temp = _temp.substring(_temp.indexOf(';') + 2); + while(_temp.indexOf(';') > 0){ + String name = _temp.substring(0, _temp.indexOf('=')); + String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1); + if(name == F("name")){ + _itemName = nameVal; + } else if(name == F("filename")){ + _itemFilename = nameVal; + _itemIsFile = true; + } + _temp = _temp.substring(_temp.indexOf(';') + 2); + } + String name = _temp.substring(0, _temp.indexOf('=')); + String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1); + if(name == F("name")){ + _itemName = nameVal; + } else if(name == F("filename")){ + _itemFilename = nameVal; + _itemIsFile = true; + } + } + _temp = String(); + } else { + _multiParseState = WAIT_FOR_RETURN1; + //value starts from here + _itemSize = 0; + _itemStartIndex = _parsedLength; + _itemValue = String(); + if(_itemIsFile){ + if(_itemBuffer) + free(_itemBuffer); + _itemBuffer = (uint8_t*)malloc(1460); + if(_itemBuffer == NULL){ + _multiParseState = PARSE_ERROR; + return; + } + _itemBufferIndex = 0; + } + } + } + } else if(_multiParseState == EXPECT_FEED1){ + if(data != '\n'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = EXPECT_DASH1; + } + } else if(_multiParseState == EXPECT_DASH1){ + if(data != '-'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = EXPECT_DASH2; + } + } else if(_multiParseState == EXPECT_DASH2){ + if(data != '-'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = BOUNDARY_OR_DATA; + _boundaryPosition = 0; + } + } else if(_multiParseState == BOUNDARY_OR_DATA){ + if(_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; + for(i=0; i<_boundaryPosition; i++) + itemWriteByte(_boundary.c_str()[i]); + _parseMultipartPostByte(data, last); + } else if(_boundaryPosition == _boundary.length() - 1){ + _multiParseState = DASH3_OR_RETURN2; + if(!_itemIsFile){ + _addParam(new AsyncWebParameter(_itemName, _itemValue, true)); + } else { + if(_itemSize){ + //check if authenticated before calling the upload + if(_handler) _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true); + _itemBufferIndex = 0; + _addParam(new AsyncWebParameter(_itemName, _itemFilename, true, true, _itemSize)); + } + free(_itemBuffer); + _itemBuffer = NULL; + } + + } else { + _boundaryPosition++; + } + } else if(_multiParseState == DASH3_OR_RETURN2){ + if(data == '-' && (_contentLength - _parsedLength - 4) != 0){ + //os_printf("ERROR: The parser got to the end of the POST but is expecting %u bytes more!\nDrop an issue so we can have more info on the matter!\n", _contentLength - _parsedLength - 4); + _contentLength = _parsedLength + 4;//lets close the request gracefully + } + if(data == '\r'){ + _multiParseState = EXPECT_FEED2; + } else if(data == '-' && _contentLength == (_parsedLength + 4)){ + _multiParseState = PARSING_FINISHED; + } else { + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); + _parseMultipartPostByte(data, last); + } + } else if(_multiParseState == EXPECT_FEED2){ + if(data == '\n'){ + _multiParseState = PARSE_HEADERS; + _itemIsFile = false; + } else { + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); + itemWriteByte('\r'); _parseMultipartPostByte(data, last); + } + } +} + +void AsyncWebServerRequest::_parseLine(){ + if(_parseState == PARSE_REQ_START){ + if(!_temp.length()){ + _parseState = PARSE_REQ_FAIL; + _client->close(); + } else { + _parseReqHead(); + _parseState = PARSE_REQ_HEADERS; + } + return; + } + + if(_parseState == PARSE_REQ_HEADERS){ + if(!_temp.length()){ + //end of headers + _server->_rewriteRequest(this); + _server->_attachHandler(this); + _removeNotInterestingHeaders(); + if(_expectingContinue){ + String response = F("HTTP/1.1 100 Continue\r\n\r\n"); + _client->write(response.c_str(), response.length()); + } + //check handler for authentication + if(_contentLength){ + _parseState = PARSE_REQ_BODY; + } else { + _parseState = PARSE_REQ_END; + if(_handler) _handler->handleRequest(this); + else send(501); + } + } else _parseReqHeader(); + } +} + +size_t AsyncWebServerRequest::headers() const{ + return _headers.length(); +} + +bool AsyncWebServerRequest::hasHeader(const String& name) const { + for(const auto& h: _headers){ + if(h->name().equalsIgnoreCase(name)){ + return true; + } + } + return false; +} + +bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper * data) const { + return hasHeader(String(data)); +} + +AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) const { + for(const auto& h: _headers){ + if(h->name().equalsIgnoreCase(name)){ + return h; + } + } + return nullptr; +} + +AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper * data) const { + return getHeader(String(data)); +} + +AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const { + auto header = _headers.nth(num); + return header ? *header : nullptr; +} + +size_t AsyncWebServerRequest::params() const { + return _params.length(); +} + +bool AsyncWebServerRequest::hasParam(const String& name, bool post, bool file) const { + for(const auto& p: _params){ + if(p->name() == name && p->isPost() == post && p->isFile() == file){ + return true; + } + } + return false; +} + +bool AsyncWebServerRequest::hasParam(const __FlashStringHelper * data, bool post, bool file) const { + return hasParam(String(data).c_str(), post, file); +} + +AsyncWebParameter* AsyncWebServerRequest::getParam(const String& name, bool post, bool file) const { + for(const auto& p: _params){ + if(p->name() == name && p->isPost() == post && p->isFile() == file){ + return p; + } + } + return nullptr; +} + +AsyncWebParameter* AsyncWebServerRequest::getParam(const __FlashStringHelper * data, bool post, bool file) const { + return getParam(String(data).c_str(), post, file); +} + +AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const { + auto param = _params.nth(num); + return param ? *param : nullptr; +} + +void AsyncWebServerRequest::addInterestingHeader(const String& name){ + if(!_interestingHeaders.containsIgnoreCase(name)) + _interestingHeaders.add(name); +} + +void AsyncWebServerRequest::send(AsyncWebServerResponse *response){ + _response = response; + if(_response == NULL){ + _client->close(true); + _onDisconnect(); + return; + } + if(!_response->_sourceValid()){ + delete response; + _response = NULL; + send(500); + } + else { + _client->setRxTimeout(0); + _response->_respond(this); + } +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const String& contentType, const String& content){ + return new AsyncBasicResponse(code, contentType, content); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ + if(fs.exists(path) || (!download && fs.exists(path+F(".gz")))) + return new AsyncFileResponse(fs, path, contentType, download, callback); + return NULL; +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ + if(content == true) + return new AsyncFileResponse(content, path, contentType, download, callback); + return NULL; +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){ + return new AsyncStreamResponse(stream, contentType, len, callback); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ + return new AsyncCallbackResponse(contentType, len, callback, templateCallback); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ + if(_version) + return new AsyncChunkedResponse(contentType, callback, templateCallback); + return new AsyncCallbackResponse(contentType, 0, callback, templateCallback); +} + +AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(const String& contentType, size_t bufferSize){ + return new AsyncResponseStream(contentType, bufferSize); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){ + return new AsyncProgmemResponse(code, contentType, content, len, callback); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){ + return beginResponse_P(code, contentType, (const uint8_t *)content, strlen_P(content), callback); +} + +void AsyncWebServerRequest::send(int code, const String& contentType, const String& content){ + send(beginResponse(code, contentType, content)); +} + +void AsyncWebServerRequest::send(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ + if(fs.exists(path) || (!download && fs.exists(path+F(".gz")))){ + send(beginResponse(fs, path, contentType, download, callback)); + } else send(404); +} + +void AsyncWebServerRequest::send(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ + if(content == true){ + send(beginResponse(content, path, contentType, download, callback)); + } else send(404); +} + +void AsyncWebServerRequest::send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){ + send(beginResponse(stream, contentType, len, callback)); +} + +void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ + send(beginResponse(contentType, len, callback, templateCallback)); +} + +void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ + send(beginChunkedResponse(contentType, callback, templateCallback)); +} + +void AsyncWebServerRequest::send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){ + send(beginResponse_P(code, contentType, content, len, callback)); +} + +void AsyncWebServerRequest::send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){ + send(beginResponse_P(code, contentType, content, callback)); +} + +void AsyncWebServerRequest::redirect(const String& url){ + AsyncWebServerResponse * response = beginResponse(302); + response->addHeader(F("Location"), url); + send(response); +} + +bool AsyncWebServerRequest::authenticate(const char * username, const char * password, const char * realm, bool passwordIsHash){ + if(_authorization.length()){ + if(_isDigest) + return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL); + else if(!passwordIsHash) + return checkBasicAuthentication(_authorization.c_str(), username, password); + else + return _authorization.equals(password); + } + return false; +} + +bool AsyncWebServerRequest::authenticate(const char * hash){ + if(!_authorization.length() || hash == NULL) + return false; + + if(_isDigest){ + String hStr = String(hash); + int separator = hStr.indexOf(':'); + if(separator <= 0) + return false; + String username = hStr.substring(0, separator); + hStr = hStr.substring(separator + 1); + separator = hStr.indexOf(':'); + if(separator <= 0) + return false; + String realm = hStr.substring(0, separator); + hStr = hStr.substring(separator + 1); + return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL); + } + + return (_authorization.equals(hash)); +} + +void AsyncWebServerRequest::requestAuthentication(const char * realm, bool isDigest){ + AsyncWebServerResponse * r = beginResponse(401); + if(!isDigest && realm == NULL){ + r->addHeader(F("WWW-Authenticate"), F("Basic realm=\"Login Required\"")); + } else if(!isDigest){ + String header = F("Basic realm=\""); + header.concat(realm); + header += '"'; + r->addHeader(F("WWW-Authenticate"), header); + } else { + String header = F("Digest "); + header.concat(requestDigestAuthentication(realm)); + r->addHeader(F("WWW-Authenticate"), header); + } + send(r); +} + +bool AsyncWebServerRequest::hasArg(const char* name) const { + for(const auto& arg: _params){ + if(arg->name() == name){ + return true; + } + } + return false; +} + +bool AsyncWebServerRequest::hasArg(const __FlashStringHelper * data) const { + return hasArg(String(data).c_str()); +} + + +const String& AsyncWebServerRequest::arg(const String& name) const { + for(const auto& arg: _params){ + if(arg->name() == name){ + return arg->value(); + } + } + return emptyString; +} + +const String& AsyncWebServerRequest::arg(const __FlashStringHelper * data) const { + return arg(String(data).c_str()); +} + +const String& AsyncWebServerRequest::arg(size_t i) const { + return getParam(i)->value(); +} + +const String& AsyncWebServerRequest::argName(size_t i) const { + return getParam(i)->name(); +} + +const String& AsyncWebServerRequest::pathArg(size_t i) const { + auto param = _pathParams.nth(i); + return param ? **param : emptyString; +} + +const String& AsyncWebServerRequest::header(const char* name) const { + AsyncWebHeader* h = getHeader(String(name)); + return h ? h->value() : emptyString; +} + +const String& AsyncWebServerRequest::header(const __FlashStringHelper * data) const { + return header(String(data).c_str()); +}; + + +const String& AsyncWebServerRequest::header(size_t i) const { + AsyncWebHeader* h = getHeader(i); + return h ? h->value() : emptyString; +} + +const String& AsyncWebServerRequest::headerName(size_t i) const { + AsyncWebHeader* h = getHeader(i); + return h ? h->name() : emptyString; +} + +String AsyncWebServerRequest::urlDecode(const String& text) const { + char temp[] = "0x00"; + unsigned int len = text.length(); + unsigned int i = 0; + String decoded = String(); + decoded.reserve(len); // Allocate the string internal buffer - never longer from source text + while (i < len){ + char decodedChar; + char encodedChar = text.charAt(i++); + if ((encodedChar == '%') && (i + 1 < len)){ + temp[2] = text.charAt(i++); + temp[3] = text.charAt(i++); + decodedChar = strtol(temp, NULL, 16); + } else if (encodedChar == '+') { + decodedChar = ' '; + } else { + decodedChar = encodedChar; // normal ascii char + } + decoded.concat(decodedChar); + } + return decoded; +} + + +const char *AsyncWebServerRequest::methodToString() const { + if(_method == HTTP_ANY) return ("ANY"); + else if(_method & HTTP_GET) return ("GET"); + else if(_method & HTTP_POST) return ("POST"); + else if(_method & HTTP_DELETE) return ("DELETE"); + else if(_method & HTTP_PUT) return ("PUT"); + else if(_method & HTTP_PATCH) return ("PATCH"); + else if(_method & HTTP_HEAD) return ("HEAD"); + else if(_method & HTTP_OPTIONS) return ("OPTIONS"); + return ("UNKNOWN"); +} + +const char *AsyncWebServerRequest::requestedConnTypeToString() const { + switch (_reqconntype) { + case RCT_NOT_USED: return ("RCT_NOT_USED"); + case RCT_DEFAULT: return ("RCT_DEFAULT"); + case RCT_HTTP: return ("RCT_HTTP"); + case RCT_WS: return ("RCT_WS"); + case RCT_EVENT: return ("RCT_EVENT"); + default: return ("ERROR"); + } +} + +bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) { + bool res = false; + if ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype)) res = true; + if ((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype)) res = true; + if ((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype)) res = true; + return res; +} diff --git a/lib/ESPAsyncWebServer/WebResponses.cpp b/lib/ESPAsyncWebServer/WebResponses.cpp index 6e24444b7..d2c2f78a9 100644 --- a/lib/ESPAsyncWebServer/WebResponses.cpp +++ b/lib/ESPAsyncWebServer/WebResponses.cpp @@ -1,708 +1,709 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "ESPAsyncWebServer.h" -#include "WebResponseImpl.h" -#include "cbuf.h" - -// Since ESP8266 does not link memchr by default, here's its implementation. -void* memchr(void* ptr, int ch, size_t count) -{ - unsigned char* p = static_cast(ptr); - while(count--) - if(*p++ == static_cast(ch)) - return --p; - return nullptr; -} - - -/* - * Abstract Response - * */ -const char* AsyncWebServerResponse::_responseCodeToString(int code) { - return reinterpret_cast(responseCodeToString(code)); -} - -const __FlashStringHelper *AsyncWebServerResponse::responseCodeToString(int code) { - switch (code) { - case 100: return F("Continue"); - case 101: return F("Switching Protocols"); - case 200: return F("OK"); - case 201: return F("Created"); - case 202: return F("Accepted"); - case 203: return F("Non-Authoritative Information"); - case 204: return F("No Content"); - case 205: return F("Reset Content"); - case 206: return F("Partial Content"); - case 300: return F("Multiple Choices"); - case 301: return F("Moved Permanently"); - case 302: return F("Found"); - case 303: return F("See Other"); - case 304: return F("Not Modified"); - case 305: return F("Use Proxy"); - case 307: return F("Temporary Redirect"); - case 400: return F("Bad Request"); - case 401: return F("Unauthorized"); - case 402: return F("Payment Required"); - case 403: return F("Forbidden"); - case 404: return F("Not Found"); - case 405: return F("Method Not Allowed"); - case 406: return F("Not Acceptable"); - case 407: return F("Proxy Authentication Required"); - case 408: return F("Request Time-out"); - case 409: return F("Conflict"); - case 410: return F("Gone"); - case 411: return F("Length Required"); - case 412: return F("Precondition Failed"); - case 413: return F("Request Entity Too Large"); - case 414: return F("Request-URI Too Large"); - case 415: return F("Unsupported Media Type"); - case 416: return F("Requested range not satisfiable"); - case 417: return F("Expectation Failed"); - case 500: return F("Internal Server Error"); - case 501: return F("Not Implemented"); - case 502: return F("Bad Gateway"); - case 503: return F("Service Unavailable"); - case 504: return F("Gateway Time-out"); - case 505: return F("HTTP Version not supported"); - default: return F(""); - } -} - -AsyncWebServerResponse::AsyncWebServerResponse() - : _code(0) - , _headers(LinkedList([](AsyncWebHeader *h){ delete h; })) - , _contentType() - , _contentLength(0) - , _sendContentLength(true) - , _chunked(false) - , _headLength(0) - , _sentLength(0) - , _ackedLength(0) - , _writtenLength(0) - , _state(RESPONSE_SETUP) -{ - for(auto header: DefaultHeaders::Instance()) { - _headers.add(new AsyncWebHeader(header->name(), header->value())); - } -} - -AsyncWebServerResponse::~AsyncWebServerResponse(){ - _headers.free(); -} - -void AsyncWebServerResponse::setCode(int code){ - if(_state == RESPONSE_SETUP) - _code = code; -} - -void AsyncWebServerResponse::setContentLength(size_t len){ - if(_state == RESPONSE_SETUP) - _contentLength = len; -} - -void AsyncWebServerResponse::setContentType(const String& type){ - if(_state == RESPONSE_SETUP) - _contentType = type; -} - -void AsyncWebServerResponse::addHeader(const String& name, const String& value){ - _headers.add(new AsyncWebHeader(name, value)); -} - -String AsyncWebServerResponse::_assembleHead(uint8_t version){ - if(version){ - addHeader(F("Accept-Ranges"), F("none")); - if(_chunked) - addHeader(F("Transfer-Encoding"), F("chunked")); - } - String out = String(); - int bufSize = 300; - char buf[bufSize]; - - snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code)); - out.concat(buf); - - if(_sendContentLength) { - snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength); - out.concat(buf); - } - if(_contentType.length()) { - snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str()); - out.concat(buf); - } - - for(const auto& header: _headers){ - snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header->name().c_str(), header->value().c_str()); - out.concat(buf); - } - _headers.free(); - - out.concat(F("\r\n")); - _headLength = out.length(); - return out; -} - -bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; } -bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; } -bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; } -bool AsyncWebServerResponse::_sourceValid() const { return false; } -void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request){ _state = RESPONSE_END; request->client()->close(); } -size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ (void)request; (void)len; (void)time; return 0; } - -/* - * String/Code Response - * */ -AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content){ - _code = code; - _content = content; - _contentType = contentType; - if(_content.length()){ - _contentLength = _content.length(); - if(!_contentType.length()) - _contentType = F("text/plain"); - } - addHeader(F("Connection"), F("close")); -} - -void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){ - _state = RESPONSE_HEADERS; - String out = _assembleHead(request->version()); - size_t outLen = out.length(); - size_t space = request->client()->space(); - if(!_contentLength && space >= outLen){ - _writtenLength += request->client()->write(out.c_str(), outLen); - _state = RESPONSE_WAIT_ACK; - } else if(_contentLength && space >= outLen + _contentLength){ - out += _content; - outLen += _contentLength; - _writtenLength += request->client()->write(out.c_str(), outLen); - _state = RESPONSE_WAIT_ACK; - } else if(space && space < outLen){ - String partial = out.substring(0, space); - _content = out.substring(space) + _content; - _contentLength += outLen - space; - _writtenLength += request->client()->write(partial.c_str(), partial.length()); - _state = RESPONSE_CONTENT; - } else if(space > outLen && space < (outLen + _contentLength)){ - size_t shift = space - outLen; - outLen += shift; - _sentLength += shift; - out += _content.substring(0, shift); - _content = _content.substring(shift); - _writtenLength += request->client()->write(out.c_str(), outLen); - _state = RESPONSE_CONTENT; - } else { - _content = out + _content; - _contentLength += outLen; - _state = RESPONSE_CONTENT; - } -} - -size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ - (void)time; - _ackedLength += len; - if(_state == RESPONSE_CONTENT){ - size_t available = _contentLength - _sentLength; - size_t space = request->client()->space(); - //we can fit in this packet - if(space > available){ - _writtenLength += request->client()->write(_content.c_str(), available); - _content = String(); - _state = RESPONSE_WAIT_ACK; - return available; - } - //send some data, the rest on ack - String out = _content.substring(0, space); - _content = _content.substring(space); - _sentLength += space; - _writtenLength += request->client()->write(out.c_str(), space); - return space; - } else if(_state == RESPONSE_WAIT_ACK){ - if(_ackedLength >= _writtenLength){ - _state = RESPONSE_END; - } - } - return 0; -} - - -/* - * Abstract Response - * */ - -AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _callback(callback) -{ - // In case of template processing, we're unable to determine real response size - if(callback) { - _contentLength = 0; - _sendContentLength = false; - _chunked = true; - } -} - -void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){ - addHeader(F("Connection"), F("close")); - _head = _assembleHead(request->version()); - _state = RESPONSE_HEADERS; - _ack(request, 0, 0); -} - -size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ - (void)time; - if(!_sourceValid()){ - _state = RESPONSE_FAILED; - request->client()->close(); - return 0; - } - _ackedLength += len; - size_t space = request->client()->space(); - - size_t headLen = _head.length(); - if(_state == RESPONSE_HEADERS){ - if(space >= headLen){ - _state = RESPONSE_CONTENT; - space -= headLen; - } else { - String out = _head.substring(0, space); - _head = _head.substring(space); - _writtenLength += request->client()->write(out.c_str(), out.length()); - return out.length(); - } - } - - if(_state == RESPONSE_CONTENT){ - size_t outLen; - if(_chunked){ - if(space <= 8){ - return 0; - } - outLen = space; - } else if(!_sendContentLength){ - outLen = space; - } else { - outLen = ((_contentLength - _sentLength) > space)?space:(_contentLength - _sentLength); - } - - uint8_t *buf = (uint8_t *)malloc(outLen+headLen); - if (!buf) { - // os_printf("_ack malloc %d failed\n", outLen+headLen); - return 0; - } - - if(headLen){ - memcpy(buf, _head.c_str(), _head.length()); - } - - size_t readLen = 0; - - if(_chunked){ - // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added. - // See RFC2616 sections 2, 3.6.1. - readLen = _fillBufferAndProcessTemplates(buf+headLen+6, outLen - 8); - if(readLen == RESPONSE_TRY_AGAIN){ - free(buf); - return 0; - } - outLen = snprintf_P((char*)buf+headLen, sizeof(buf)-headLen-2, PSTR("%x"), readLen) + headLen; - while(outLen < headLen + 4) buf[outLen++] = ' '; - buf[outLen++] = '\r'; - buf[outLen++] = '\n'; - outLen += readLen; - buf[outLen++] = '\r'; - buf[outLen++] = '\n'; - } else { - readLen = _fillBufferAndProcessTemplates(buf+headLen, outLen); - if(readLen == RESPONSE_TRY_AGAIN){ - free(buf); - return 0; - } - outLen = readLen + headLen; - } - - if(headLen){ - _head = String(); - } - - if(outLen){ - _writtenLength += request->client()->write((const char*)buf, outLen); - } - - if(_chunked){ - _sentLength += readLen; - } else { - _sentLength += outLen - headLen; - } - - free(buf); - - if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)){ - _state = RESPONSE_WAIT_ACK; - } - return outLen; - - } else if(_state == RESPONSE_WAIT_ACK){ - if(!_sendContentLength || _ackedLength >= _writtenLength){ - _state = RESPONSE_END; - if(!_chunked && !_sendContentLength) - request->client()->close(true); - } - } - return 0; -} - -size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len) -{ - // If we have something in cache, copy it to buffer - const size_t readFromCache = std::min(len, _cache.size()); - if(readFromCache) { - memcpy(data, _cache.data(), readFromCache); - _cache.erase(_cache.begin(), _cache.begin() + readFromCache); - } - // If we need to read more... - const size_t needFromFile = len - readFromCache; - const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile); - return readFromCache + readFromContent; -} - -size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) -{ - if(!_callback) - return _fillBuffer(data, len); - - const size_t originalLen = len; - len = _readDataFromCacheOrContent(data, len); - // Now we've read 'len' bytes, either from cache or from file - // Search for template placeholders - uint8_t* pTemplateStart = data; - while((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1] - uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr; - // temporary buffer to hold parameter name - uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; - String paramName; - // If closing placeholder is found: - if(pTemplateEnd) { - // prepare argument to callback - const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1)); - if(paramNameLength) { - memcpy(buf, pTemplateStart + 1, paramNameLength); - buf[paramNameLength] = 0; - paramName = String(reinterpret_cast(buf)); - } else { // double percent sign encountered, this is single percent sign escaped. - // remove the 2nd percent sign - memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); - len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1; - ++pTemplateStart; - } - } else if(&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data - memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart); - const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1)); - if(readFromCacheOrContent) { - pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent); - if(pTemplateEnd) { - // prepare argument to callback - *pTemplateEnd = 0; - paramName = String(reinterpret_cast(buf)); - // Copy remaining read-ahead data into cache - _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); - pTemplateEnd = &data[len - 1]; - } - else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position - { - // but first, store read file data in cache - _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); - ++pTemplateStart; - } - } - else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position - ++pTemplateStart; - } - else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position - ++pTemplateStart; - if(paramName.length()) { - // call callback and replace with result. - // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value. - // Data after pTemplateEnd may need to be moved. - // The first byte of data after placeholder is located at pTemplateEnd + 1. - // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value). - const String paramValue(_callback(paramName)); - const char* pvstr = paramValue.c_str(); - const unsigned int pvlen = paramValue.length(); - const size_t numBytesCopied = std::min(pvlen, static_cast(&data[originalLen - 1] - pTemplateStart + 1)); - // make room for param value - // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store - if((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) { - _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]); - //2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end - memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied); - len = originalLen; // fix issue with truncated data, not sure if it has any side effects - } else if(pTemplateEnd + 1 != pTemplateStart + numBytesCopied) - //2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit. - // Move the entire data after the placeholder - memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); - // 3. replace placeholder with actual value - memcpy(pTemplateStart, pvstr, numBytesCopied); - // If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer) - if(numBytesCopied < pvlen) { - _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen); - } else if(pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text... - // there is some free room, fill it from cache - const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied; - const size_t totalFreeRoom = originalLen - len + roomFreed; - len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed; - } else { // result is copied fully; it is longer than placeholder text - const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1; - len = std::min(len + roomTaken, originalLen); - } - } - } // while(pTemplateStart) - return len; -} - - -/* - * File Response - * */ - -AsyncFileResponse::~AsyncFileResponse(){ - if(_content) - _content.close(); -} - -void AsyncFileResponse::_setContentType(const String& path){ -#if HAVE_EXTERN_GET_CONTENT_TYPE_FUNCTION - extern const __FlashStringHelper *getContentType(const String &path); - _contentType = getContentType(path); -#else - if (path.endsWith(F(".html"))) _contentType = F("text/html"); - else if (path.endsWith(F(".htm"))) _contentType = F("text/html"); - else if (path.endsWith(F(".css"))) _contentType = F("text/css"); - else if (path.endsWith(F(".json"))) _contentType = F("application/json"); - else if (path.endsWith(F(".js"))) _contentType = F("application/javascript"); - else if (path.endsWith(F(".png"))) _contentType = F("image/png"); - else if (path.endsWith(F(".gif"))) _contentType = F("image/gif"); - else if (path.endsWith(F(".jpg"))) _contentType = F("image/jpeg"); - else if (path.endsWith(F(".ico"))) _contentType = F("image/x-icon"); - else if (path.endsWith(F(".svg"))) _contentType = F("image/svg+xml"); - else if (path.endsWith(F(".eot"))) _contentType = F("font/eot"); - else if (path.endsWith(F(".woff"))) _contentType = F("font/woff"); - else if (path.endsWith(F(".woff2"))) _contentType = F("font/woff2"); - else if (path.endsWith(F(".ttf"))) _contentType = F("font/ttf"); - else if (path.endsWith(F(".xml"))) _contentType = F("text/xml"); - else if (path.endsWith(F(".pdf"))) _contentType = F("application/pdf"); - else if (path.endsWith(F(".zip"))) _contentType = F("application/zip"); - else if(path.endsWith(F(".gz"))) _contentType = F("application/x-gzip"); - else _contentType = F("text/plain"); -#endif -} - -AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ - _code = 200; - _path = path; - - if(!download && !fs.exists(_path) && fs.exists(_path + F(".gz"))){ - _path = _path + F(".gz"); - addHeader(F("Content-Encoding"), F("gzip")); - _callback = nullptr; // Unable to process zipped templates - _sendContentLength = true; - _chunked = false; - } - - _content = fs.open(_path, fs::FileOpenMode::read); - _contentLength = _content.size(); - - if(contentType.length() == 0) - _setContentType(path); - else - _contentType = contentType; - - int filenameStart = path.lastIndexOf('/') + 1; - char buf[26+path.length()-filenameStart]; - char* filename = (char*)path.c_str() + filenameStart; - - if(download) { - // set filename and force download - snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename); - } else { - // set filename and force rendering - snprintf_P(buf, sizeof (buf), PSTR("inline; filename=\"%s\""), filename); - } - addHeader(F("Content-Disposition"), buf); -} - -AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ - _code = 200; - _path = path; - - if(!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))){ - addHeader(F("Content-Encoding"), F("gzip")); - _callback = nullptr; // Unable to process gzipped templates - _sendContentLength = true; - _chunked = false; - } - - _content = content; - _contentLength = _content.size(); - - if(contentType.length() == 0) - _setContentType(path); - else - _contentType = contentType; - - int filenameStart = path.lastIndexOf('/') + 1; - char buf[26+path.length()-filenameStart]; - char* filename = (char*)path.c_str() + filenameStart; - - if(download) { - snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename); - } else { - snprintf_P(buf, sizeof (buf), PSTR("inline; filename=\"%s\""), filename); - } - addHeader(F("Content-Disposition"), buf); -} - -size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){ - return _content.read(data, len); -} - -/* - * Stream Response - * */ - -AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { - _code = 200; - _content = &stream; - _contentLength = len; - _contentType = contentType; -} - -size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){ - size_t available = _content->available(); - size_t outLen = (available > len)?len:available; - size_t i; - for(i=0;iread(); - return outLen; -} - -/* - * Callback Response - * */ - -AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback): AsyncAbstractResponse(templateCallback) { - _code = 200; - _content = callback; - _contentLength = len; - if(!len) - _sendContentLength = false; - _contentType = contentType; - _filledLength = 0; -} - -size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){ - size_t ret = _content(data, len, _filledLength); - if(ret != RESPONSE_TRY_AGAIN){ - _filledLength += ret; - } - return ret; -} - -/* - * Chunked Response - * */ - -AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) { - _code = 200; - _content = callback; - _contentLength = 0; - _contentType = contentType; - _sendContentLength = false; - _chunked = true; - _filledLength = 0; -} - -size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){ - size_t ret = _content(data, len, _filledLength); - if(ret != RESPONSE_TRY_AGAIN){ - _filledLength += ret; - } - return ret; -} - -/* - * Progmem Response - * */ - -AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { - _code = code; - _content = content; - _contentType = contentType; - _contentLength = len; - _readLength = 0; -} - -size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){ - size_t left = _contentLength - _readLength; - if (left > len) { - memcpy_P(data, _content + _readLength, len); - _readLength += len; - return len; - } - memcpy_P(data, _content + _readLength, left); - _readLength += left; - return left; -} - - -/* - * Response Stream (You can print/write/printf to it, up to the contentLen bytes) - * */ - -AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize){ - _code = 200; - _contentLength = 0; - _contentType = contentType; - _content = new cbuf(bufferSize); -} - -AsyncResponseStream::~AsyncResponseStream(){ - delete _content; -} - -size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen){ - return _content->read((char*)buf, maxLen); -} - -size_t AsyncResponseStream::write(const uint8_t *data, size_t len){ - if(_started()) - return 0; - - if(len > _content->room()){ - size_t needed = len - _content->room(); - _content->resizeAdd(needed); - } - size_t written = _content->write((const char*)data, len); - _contentLength += written; - return written; -} - -size_t AsyncResponseStream::write(uint8_t data){ - return write(&data, 1); -} +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ESPAsyncWebServer.h" +#include "WebResponseImpl.h" +#include "cbuf.h" + +// Since ESP8266 does not link memchr by default, here's its implementation. +void* memchr(void* ptr, int ch, size_t count) +{ + unsigned char* p = static_cast(ptr); + while(count--) + if(*p++ == static_cast(ch)) + return --p; + return nullptr; +} + + +/* + * Abstract Response + * */ +const char* AsyncWebServerResponse::_responseCodeToString(int code) { + switch (code) { + case 100: return ("Continue"); + case 101: return ("Switching Protocols"); + case 200: return ("OK"); + case 201: return ("Created"); + case 202: return ("Accepted"); + case 203: return ("Non-Authoritative Information"); + case 204: return ("No Content"); + case 205: return ("Reset Content"); + case 206: return ("Partial Content"); + case 300: return ("Multiple Choices"); + case 301: return ("Moved Permanently"); + case 302: return ("Found"); + case 303: return ("See Other"); + case 304: return ("Not Modified"); + case 305: return ("Use Proxy"); + case 307: return ("Temporary Redirect"); + case 400: return ("Bad Request"); + case 401: return ("Unauthorized"); + case 402: return ("Payment Required"); + case 403: return ("Forbidden"); + case 404: return ("Not Found"); + case 405: return ("Method Not Allowed"); + case 406: return ("Not Acceptable"); + case 407: return ("Proxy Authentication Required"); + case 408: return ("Request Time-out"); + case 409: return ("Conflict"); + case 410: return ("Gone"); + case 411: return ("Length Required"); + case 412: return ("Precondition Failed"); + case 413: return ("Request Entity Too Large"); + case 414: return ("Request-URI Too Large"); + case 415: return ("Unsupported Media Type"); + case 416: return ("Requested range not satisfiable"); + case 417: return ("Expectation Failed"); + case 500: return ("Internal Server Error"); + case 501: return ("Not Implemented"); + case 502: return ("Bad Gateway"); + case 503: return ("Service Unavailable"); + case 504: return ("Gateway Time-out"); + case 505: return ("HTTP Version not supported"); + case 507: return ("Insufficient Storage"); + default: return (""); + } +} + +const __FlashStringHelper *AsyncWebServerResponse::responseCodeToString(int code) { + return reinterpret_cast(responseCodeToString(code)); +} + +AsyncWebServerResponse::AsyncWebServerResponse() + : _code(0) + , _headers(LinkedList([](AsyncWebHeader *h){ delete h; })) + , _contentType() + , _contentLength(0) + , _sendContentLength(true) + , _chunked(false) + , _headLength(0) + , _sentLength(0) + , _ackedLength(0) + , _writtenLength(0) + , _state(RESPONSE_SETUP) +{ + for(auto header: DefaultHeaders::Instance()) { + _headers.add(new AsyncWebHeader(header->name(), header->value())); + } +} + +AsyncWebServerResponse::~AsyncWebServerResponse(){ + _headers.free(); +} + +void AsyncWebServerResponse::setCode(int code){ + if(_state == RESPONSE_SETUP) + _code = code; +} + +void AsyncWebServerResponse::setContentLength(size_t len){ + if(_state == RESPONSE_SETUP) + _contentLength = len; +} + +void AsyncWebServerResponse::setContentType(const String& type){ + if(_state == RESPONSE_SETUP) + _contentType = type; +} + +void AsyncWebServerResponse::addHeader(const String& name, const String& value){ + _headers.add(new AsyncWebHeader(name, value)); +} + +String AsyncWebServerResponse::_assembleHead(uint8_t version){ + if(version){ + addHeader(F("Accept-Ranges"), F("none")); + if(_chunked) + addHeader(F("Transfer-Encoding"), F("chunked")); + } + String out = String(); + int bufSize = 300; + char buf[bufSize]; + + snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code)); + out.concat(buf); + + if(_sendContentLength) { + snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength); + out.concat(buf); + } + if(_contentType.length()) { + snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str()); + out.concat(buf); + } + + for(const auto& header: _headers){ + snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header->name().c_str(), header->value().c_str()); + out.concat(buf); + } + _headers.free(); + + out.concat(F("\r\n")); + _headLength = out.length(); + return out; +} + +bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; } +bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; } +bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; } +bool AsyncWebServerResponse::_sourceValid() const { return false; } +void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request){ _state = RESPONSE_END; request->client()->close(); } +size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ (void)request; (void)len; (void)time; return 0; } + +/* + * String/Code Response + * */ +AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content){ + _code = code; + _content = content; + _contentType = contentType; + if(_content.length()){ + _contentLength = _content.length(); + if(!_contentType.length()) + _contentType = F("text/plain"); + } + addHeader(F("Connection"), F("close")); +} + +void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){ + _state = RESPONSE_HEADERS; + String out = _assembleHead(request->version()); + size_t outLen = out.length(); + size_t space = request->client()->space(); + if(!_contentLength && space >= outLen){ + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_WAIT_ACK; + } else if(_contentLength && space >= outLen + _contentLength){ + out += _content; + outLen += _contentLength; + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_WAIT_ACK; + } else if(space && space < outLen){ + String partial = out.substring(0, space); + _content = out.substring(space) + _content; + _contentLength += outLen - space; + _writtenLength += request->client()->write(partial.c_str(), partial.length()); + _state = RESPONSE_CONTENT; + } else if(space > outLen && space < (outLen + _contentLength)){ + size_t shift = space - outLen; + outLen += shift; + _sentLength += shift; + out += _content.substring(0, shift); + _content = _content.substring(shift); + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_CONTENT; + } else { + _content = out + _content; + _contentLength += outLen; + _state = RESPONSE_CONTENT; + } +} + +size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ + (void)time; + _ackedLength += len; + if(_state == RESPONSE_CONTENT){ + size_t available = _contentLength - _sentLength; + size_t space = request->client()->space(); + //we can fit in this packet + if(space > available){ + _writtenLength += request->client()->write(_content.c_str(), available); + _content = String(); + _state = RESPONSE_WAIT_ACK; + return available; + } + //send some data, the rest on ack + String out = _content.substring(0, space); + _content = _content.substring(space); + _sentLength += space; + _writtenLength += request->client()->write(out.c_str(), space); + return space; + } else if(_state == RESPONSE_WAIT_ACK){ + if(_ackedLength >= _writtenLength){ + _state = RESPONSE_END; + } + } + return 0; +} + + +/* + * Abstract Response + * */ + +AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _callback(callback) +{ + // In case of template processing, we're unable to determine real response size + if(callback) { + _contentLength = 0; + _sendContentLength = false; + _chunked = true; + } +} + +void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){ + addHeader(F("Connection"), F("close")); + _head = _assembleHead(request->version()); + _state = RESPONSE_HEADERS; + _ack(request, 0, 0); +} + +size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ + (void)time; + if(!_sourceValid()){ + _state = RESPONSE_FAILED; + request->client()->close(); + return 0; + } + _ackedLength += len; + size_t space = request->client()->space(); + + size_t headLen = _head.length(); + if(_state == RESPONSE_HEADERS){ + if(space >= headLen){ + _state = RESPONSE_CONTENT; + space -= headLen; + } else { + String out = _head.substring(0, space); + _head = _head.substring(space); + _writtenLength += request->client()->write(out.c_str(), out.length()); + return out.length(); + } + } + + if(_state == RESPONSE_CONTENT){ + size_t outLen; + if(_chunked){ + if(space <= 8){ + return 0; + } + outLen = space; + } else if(!_sendContentLength){ + outLen = space; + } else { + outLen = ((_contentLength - _sentLength) > space)?space:(_contentLength - _sentLength); + } + + uint8_t *buf = (uint8_t *)malloc(outLen+headLen); + if (!buf) { + // os_printf("_ack malloc %d failed\n", outLen+headLen); + return 0; + } + + if(headLen){ + memcpy(buf, _head.c_str(), _head.length()); + } + + size_t readLen = 0; + + if(_chunked){ + // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added. + // See RFC2616 sections 2, 3.6.1. + readLen = _fillBufferAndProcessTemplates(buf+headLen+6, outLen - 8); + if(readLen == RESPONSE_TRY_AGAIN){ + free(buf); + return 0; + } + outLen = snprintf_P((char*)buf+headLen, sizeof(buf)-headLen-2, PSTR("%x"), readLen) + headLen; + while(outLen < headLen + 4) buf[outLen++] = ' '; + buf[outLen++] = '\r'; + buf[outLen++] = '\n'; + outLen += readLen; + buf[outLen++] = '\r'; + buf[outLen++] = '\n'; + } else { + readLen = _fillBufferAndProcessTemplates(buf+headLen, outLen); + if(readLen == RESPONSE_TRY_AGAIN){ + free(buf); + return 0; + } + outLen = readLen + headLen; + } + + if(headLen){ + _head = String(); + } + + if(outLen){ + _writtenLength += request->client()->write((const char*)buf, outLen); + } + + if(_chunked){ + _sentLength += readLen; + } else { + _sentLength += outLen - headLen; + } + + free(buf); + + if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)){ + _state = RESPONSE_WAIT_ACK; + } + return outLen; + + } else if(_state == RESPONSE_WAIT_ACK){ + if(!_sendContentLength || _ackedLength >= _writtenLength){ + _state = RESPONSE_END; + if(!_chunked && !_sendContentLength) + request->client()->close(true); + } + } + return 0; +} + +size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len) +{ + // If we have something in cache, copy it to buffer + const size_t readFromCache = std::min(len, _cache.size()); + if(readFromCache) { + memcpy(data, _cache.data(), readFromCache); + _cache.erase(_cache.begin(), _cache.begin() + readFromCache); + } + // If we need to read more... + const size_t needFromFile = len - readFromCache; + const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile); + return readFromCache + readFromContent; +} + +size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) +{ + if(!_callback) + return _fillBuffer(data, len); + + const size_t originalLen = len; + len = _readDataFromCacheOrContent(data, len); + // Now we've read 'len' bytes, either from cache or from file + // Search for template placeholders + uint8_t* pTemplateStart = data; + while((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1] + uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr; + // temporary buffer to hold parameter name + uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; + String paramName; + // If closing placeholder is found: + if(pTemplateEnd) { + // prepare argument to callback + const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1)); + if(paramNameLength) { + memcpy(buf, pTemplateStart + 1, paramNameLength); + buf[paramNameLength] = 0; + paramName = String(reinterpret_cast(buf)); + } else { // double percent sign encountered, this is single percent sign escaped. + // remove the 2nd percent sign + memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); + len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1; + ++pTemplateStart; + } + } else if(&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data + memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart); + const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1)); + if(readFromCacheOrContent) { + pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent); + if(pTemplateEnd) { + // prepare argument to callback + *pTemplateEnd = 0; + paramName = String(reinterpret_cast(buf)); + // Copy remaining read-ahead data into cache + _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); + pTemplateEnd = &data[len - 1]; + } + else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position + { + // but first, store read file data in cache + _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); + ++pTemplateStart; + } + } + else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position + ++pTemplateStart; + } + else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position + ++pTemplateStart; + if(paramName.length()) { + // call callback and replace with result. + // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value. + // Data after pTemplateEnd may need to be moved. + // The first byte of data after placeholder is located at pTemplateEnd + 1. + // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value). + const String paramValue(_callback(paramName)); + const char* pvstr = paramValue.c_str(); + const unsigned int pvlen = paramValue.length(); + const size_t numBytesCopied = std::min(pvlen, static_cast(&data[originalLen - 1] - pTemplateStart + 1)); + // make room for param value + // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store + if((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) { + _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]); + //2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end + memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied); + len = originalLen; // fix issue with truncated data, not sure if it has any side effects + } else if(pTemplateEnd + 1 != pTemplateStart + numBytesCopied) + //2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit. + // Move the entire data after the placeholder + memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); + // 3. replace placeholder with actual value + memcpy(pTemplateStart, pvstr, numBytesCopied); + // If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer) + if(numBytesCopied < pvlen) { + _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen); + } else if(pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text... + // there is some free room, fill it from cache + const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied; + const size_t totalFreeRoom = originalLen - len + roomFreed; + len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed; + } else { // result is copied fully; it is longer than placeholder text + const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1; + len = std::min(len + roomTaken, originalLen); + } + } + } // while(pTemplateStart) + return len; +} + + +/* + * File Response + * */ + +AsyncFileResponse::~AsyncFileResponse(){ + if(_content) + _content.close(); +} + +void AsyncFileResponse::_setContentType(const String& path){ +#if HAVE_EXTERN_GET_CONTENT_TYPE_FUNCTION + extern const __FlashStringHelper *getContentType(const String &path); + _contentType = getContentType(path); +#else + if (path.endsWith(F(".html"))) _contentType = F("text/html"); + else if (path.endsWith(F(".htm"))) _contentType = F("text/html"); + else if (path.endsWith(F(".css"))) _contentType = F("text/css"); + else if (path.endsWith(F(".json"))) _contentType = F("application/json"); + else if (path.endsWith(F(".js"))) _contentType = F("application/javascript"); + else if (path.endsWith(F(".png"))) _contentType = F("image/png"); + else if (path.endsWith(F(".gif"))) _contentType = F("image/gif"); + else if (path.endsWith(F(".jpg"))) _contentType = F("image/jpeg"); + else if (path.endsWith(F(".ico"))) _contentType = F("image/x-icon"); + else if (path.endsWith(F(".svg"))) _contentType = F("image/svg+xml"); + else if (path.endsWith(F(".eot"))) _contentType = F("font/eot"); + else if (path.endsWith(F(".woff"))) _contentType = F("font/woff"); + else if (path.endsWith(F(".woff2"))) _contentType = F("font/woff2"); + else if (path.endsWith(F(".ttf"))) _contentType = F("font/ttf"); + else if (path.endsWith(F(".xml"))) _contentType = F("text/xml"); + else if (path.endsWith(F(".pdf"))) _contentType = F("application/pdf"); + else if (path.endsWith(F(".zip"))) _contentType = F("application/zip"); + else if(path.endsWith(F(".gz"))) _contentType = F("application/x-gzip"); + else _contentType = F("text/plain"); +#endif +} + +AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ + _code = 200; + _path = path; + + if(!download && !fs.exists(_path) && fs.exists(_path + F(".gz"))){ + _path = _path + F(".gz"); + addHeader(F("Content-Encoding"), F("gzip")); + _callback = nullptr; // Unable to process zipped templates + _sendContentLength = true; + _chunked = false; + } + + _content = fs.open(_path, fs::FileOpenMode::read); + _contentLength = _content.size(); + + if(contentType.length() == 0) + _setContentType(path); + else + _contentType = contentType; + + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26+path.length()-filenameStart]; + char* filename = (char*)path.c_str() + filenameStart; + + if(download) { + // set filename and force download + snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename); + } else { + // set filename and force rendering + snprintf_P(buf, sizeof (buf), PSTR("inline; filename=\"%s\""), filename); + } + addHeader(F("Content-Disposition"), buf); +} + +AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ + _code = 200; + _path = path; + + if(!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))){ + addHeader(F("Content-Encoding"), F("gzip")); + _callback = nullptr; // Unable to process gzipped templates + _sendContentLength = true; + _chunked = false; + } + + _content = content; + _contentLength = _content.size(); + + if(contentType.length() == 0) + _setContentType(path); + else + _contentType = contentType; + + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26+path.length()-filenameStart]; + char* filename = (char*)path.c_str() + filenameStart; + + if(download) { + snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename); + } else { + snprintf_P(buf, sizeof (buf), PSTR("inline; filename=\"%s\""), filename); + } + addHeader(F("Content-Disposition"), buf); +} + +size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){ + return _content.read(data, len); +} + +/* + * Stream Response + * */ + +AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { + _code = 200; + _content = &stream; + _contentLength = len; + _contentType = contentType; +} + +size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){ + size_t available = _content->available(); + size_t outLen = (available > len)?len:available; + size_t i; + for(i=0;iread(); + return outLen; +} + +/* + * Callback Response + * */ + +AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback): AsyncAbstractResponse(templateCallback) { + _code = 200; + _content = callback; + _contentLength = len; + if(!len) + _sendContentLength = false; + _contentType = contentType; + _filledLength = 0; +} + +size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){ + size_t ret = _content(data, len, _filledLength); + if(ret != RESPONSE_TRY_AGAIN){ + _filledLength += ret; + } + return ret; +} + +/* + * Chunked Response + * */ + +AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) { + _code = 200; + _content = callback; + _contentLength = 0; + _contentType = contentType; + _sendContentLength = false; + _chunked = true; + _filledLength = 0; +} + +size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){ + size_t ret = _content(data, len, _filledLength); + if(ret != RESPONSE_TRY_AGAIN){ + _filledLength += ret; + } + return ret; +} + +/* + * Progmem Response + * */ + +AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { + _code = code; + _content = content; + _contentType = contentType; + _contentLength = len; + _readLength = 0; +} + +size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){ + size_t left = _contentLength - _readLength; + if (left > len) { + memcpy_P(data, _content + _readLength, len); + _readLength += len; + return len; + } + memcpy_P(data, _content + _readLength, left); + _readLength += left; + return left; +} + + +/* + * Response Stream (You can print/write/printf to it, up to the contentLen bytes) + * */ + +AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize){ + _code = 200; + _contentLength = 0; + _contentType = contentType; + _content = new cbuf(bufferSize); +} + +AsyncResponseStream::~AsyncResponseStream(){ + delete _content; +} + +size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen){ + return _content->read((char*)buf, maxLen); +} + +size_t AsyncResponseStream::write(const uint8_t *data, size_t len){ + if(_started()) + return 0; + + if(len > _content->room()){ + size_t needed = len - _content->room(); + _content->resizeAdd(needed); + } + size_t written = _content->write((const char*)data, len); + _contentLength += written; + return written; +} + +size_t AsyncResponseStream::write(uint8_t data){ + return write(&data, 1); +} From 6e3b36c070e36deb27d9f0dd1446c4f7e472b8e7 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sun, 30 Apr 2023 16:36:50 +0200 Subject: [PATCH 64/89] update for 3.6.0-dev-11 --- CHANGELOG_LATEST.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 780d0a2da..6bf09662c 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -4,7 +4,10 @@ ## **IMPORTANT! BREAKING CHANGES** -- dallassensor has been renamed to temperaturesensor in MQTT payloads +There are breaking changes in 3.6.0. Please read carefully before applying the update. + +- The sensors have been renamed. `dallassensor` is now `temperaturesensor` in MQTT and `ts` in the Customizations file. Also `analogs` is now `analogsensor` in MQTT and `as` in the Customizations file. If you have customizations, make backup first using the Download option and rename the JSON arrays to `as` and `ts` respectively. Also removed any MQTT topics that start with `dallassensor` using something like MQTTExplorer. +- The format of the Custom Entities has changed, so you will need to manually re-create them. ## Added From bf5c11156a0ec2718cce9a3f9a55b9b916cab59e Mon Sep 17 00:00:00 2001 From: Proddy Date: Sun, 30 Apr 2023 20:24:50 +0200 Subject: [PATCH 65/89] reduce margins so data fills plane --- interface/src/project/DashboardDevices.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/src/project/DashboardDevices.tsx b/interface/src/project/DashboardDevices.tsx index e3a9f8701..ced1d801f 100644 --- a/interface/src/project/DashboardDevices.tsx +++ b/interface/src/project/DashboardDevices.tsx @@ -133,7 +133,7 @@ const DashboardDevices: FC = () => { common_theme, { Table: ` - --data-table-library_grid-template-columns: minmax(0, 1fr) 35% 40px; + --data-table-library_grid-template-columns: minmax(0, 1fr) 30% 40px; `, BaseRow: ` .td { @@ -429,18 +429,18 @@ const DashboardDevices: FC = () => { right: 18, bottom: 18, left: () => leftOffset(), - top: () => topOffset(), - p: 1 + top: () => topOffset() }} > setOnlyFav(!onlyFav)} />} label={ - {LL.SHOW_FAV()}  - + {LL.SHOW_FAV()} ( + ) } + labelPlacement="start" /> Date: Sun, 30 Apr 2023 20:25:04 +0200 Subject: [PATCH 66/89] fix logo on mobile devices --- interface/src/components/layout/LayoutDrawer.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/src/components/layout/LayoutDrawer.tsx b/interface/src/components/layout/LayoutDrawer.tsx index 60b8649a6..b63955c54 100644 --- a/interface/src/components/layout/LayoutDrawer.tsx +++ b/interface/src/components/layout/LayoutDrawer.tsx @@ -6,6 +6,10 @@ import type { FC } from 'react'; import { PROJECT_NAME } from 'api/env'; const LayoutDrawerLogo = styled('img')(({ theme }) => ({ + [theme.breakpoints.down('sm')]: { + height: 24, + marginRight: theme.spacing(2) + }, [theme.breakpoints.up('sm')]: { height: 38, marginRight: theme.spacing(2) From 8a01a1e4716421a9e1ece8c25bfdb1ae937ea0b6 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sun, 30 Apr 2023 20:35:29 +0200 Subject: [PATCH 67/89] merge changes from Michael for #600 --- interface/src/project/SettingsEntities.tsx | 6 ++++-- interface/src/project/types.ts | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/interface/src/project/SettingsEntities.tsx b/interface/src/project/SettingsEntities.tsx index 8093107ac..9d38e570a 100644 --- a/interface/src/project/SettingsEntities.tsx +++ b/interface/src/project/SettingsEntities.tsx @@ -199,10 +199,12 @@ const SettingsEntities: FC = () => { setDialogOpen(true); }; - function formatValue(value: any, uom: any) { + function formatValue(value: any, uom: number) { return value === undefined || uom === undefined ? '' - : new Intl.NumberFormat().format(value) + (uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom]); + : typeof value === 'number' + ? new Intl.NumberFormat().format(value) + (uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom]) + : value; } function showHex(value: number, digit: number) { diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index 8397e5d1f..954014dac 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -346,12 +346,12 @@ export interface EntityItem { device_id: number | string; type_id: number | string; offset: number; - factor?: number; // optional - uom?: number; // optional + factor: number; + uom: number; value_type: number; - value?: number; // optional + value?: any; writeable: boolean; - deleted?: boolean; // optional + deleted?: boolean; o_id?: number; o_name?: string; o_device_id?: number | string; From 148e35ea5370a74413bd38889f35cac5e27dac07 Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 1 May 2023 13:39:12 +0200 Subject: [PATCH 68/89] improve device value helper text --- .../src/project/DashboardDevicesDialog.tsx | 22 +++++++++++++++++-- interface/src/project/validators.ts | 8 ++----- mock-api/server.js | 7 ++++++ src/locale_common.h | 14 ++++++------ 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/interface/src/project/DashboardDevicesDialog.tsx b/interface/src/project/DashboardDevicesDialog.tsx index 9631d0335..91bd958cb 100644 --- a/interface/src/project/DashboardDevicesDialog.tsx +++ b/interface/src/project/DashboardDevicesDialog.tsx @@ -83,6 +83,22 @@ const DashboarDevicesDialog = ({ open, onClose, onSave, selectedItem, validator } }; + const showHelperText = (dv: DeviceValue) => { + if (dv.h) { + return dv.h; + } + if (dv.l) { + return '[ ' + dv.l.join(' | ') + ' ]'; + } + if (dv.u !== DeviceValueUOM.NONE) { + if (dv.m && dv.x) { + return ''; + } + return ''; + } + return ''; + }; + return ( {selectedItem.v === '' && selectedItem.c ? LL.RUN_COMMAND() : LL.CHANGE_VALUE()} @@ -90,7 +106,7 @@ const DashboarDevicesDialog = ({ open, onClose, onSave, selectedItem, validator {editItem.id.slice(2)} - + {editItem.l && ( )} - {editItem.h && {editItem.h}} + + format: {showHelperText(editItem)} + diff --git a/interface/src/project/validators.ts b/interface/src/project/validators.ts index 64e1e21c0..1bae952ca 100644 --- a/interface/src/project/validators.ts +++ b/interface/src/project/validators.ts @@ -170,14 +170,10 @@ export const deviceValueItemValidation = (dv: DeviceValue) => { validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) { if (dv.u !== DeviceValueUOM.NONE && isNaN(+value)) { - // not a number - dv.m && dv.x - ? callback('Value must be a number between ' + dv.m + ' and ' + dv.x) - : callback('Value must be a number'); + callback('Not a valid number'); } - // is a number (dv.m && Number(value) < dv.m) || (dv.x && Number(value) > dv.x) - ? callback('Value must be between ' + dv.m + ' and ' + dv.x) + ? callback('Value out of range') : callback(); } } diff --git a/mock-api/server.js b/mock-api/server.js index f18627608..d53fbfdd4 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -492,6 +492,13 @@ const emsesp_devicedata_1 = { id: '00hc1 mode', c: 'hc1/mode', l: ['off', 'on', 'auto'] + }, + { + v: '00 mo 00:00 T1', + u: 0, + id: '00hc1 program switchtime', + c: 'hc1/switchtime', + h: ' [ not_set | day hh:mm Tn ]' } ] }; diff --git a/src/locale_common.h b/src/locale_common.h index 24f2a038a..edc2a4d3e 100644 --- a/src/locale_common.h +++ b/src/locale_common.h @@ -194,13 +194,13 @@ MAKE_NOTRANSLATION(6kW, "6 kW") MAKE_NOTRANSLATION(9kW, "9 kW") // templates - this are not translated and will be saved under options_single -MAKE_NOTRANSLATION(tpl_datetime, "Format: < NTP | dd.mm.yyyy-hh:mm:ss-day(0-6)-dst(0/1) >") -MAKE_NOTRANSLATION(tpl_switchtime, "Format: [ not_set | day hh:mm on|off ]") -MAKE_NOTRANSLATION(tpl_switchtime1, "Format: [ not_set | day hh:mm Tn ]") -MAKE_NOTRANSLATION(tpl_holidays, "Format: < dd.mm.yyyy-dd.mm.yyyy >") -MAKE_NOTRANSLATION(tpl_date, "Format: < dd.mm.yyyy >") -MAKE_NOTRANSLATION(tpl_input, "Format: []") -MAKE_NOTRANSLATION(tpl_input4, "Format: []") +MAKE_NOTRANSLATION(tpl_datetime, "< NTP | dd.mm.yyyy-hh:mm:ss-day(0-6)-dst(0/1) >") +MAKE_NOTRANSLATION(tpl_switchtime, " [ not_set | day hh:mm on|off ]") +MAKE_NOTRANSLATION(tpl_switchtime1, " [ not_set | day hh:mm Tn ]") +MAKE_NOTRANSLATION(tpl_holidays, "< dd.mm.yyyy-dd.mm.yyyy >") +MAKE_NOTRANSLATION(tpl_date, "< dd.mm.yyyy >") +MAKE_NOTRANSLATION(tpl_input, "[]") +MAKE_NOTRANSLATION(tpl_input4, "[]") #if defined(EMSESP_TEST) MAKE_NOTRANSLATION(test_cmd, "run a test") From ff5247eaf597d19b357650fe91b37dfdaf9e7798 Mon Sep 17 00:00:00 2001 From: Proddy Date: Mon, 1 May 2023 22:13:00 +0200 Subject: [PATCH 69/89] update eslint --- interface/package.json | 4 +- interface/yarn.lock | 100 ++++++++++++++++++++--------------------- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/interface/package.json b/interface/package.json index 24aaf301d..52ff5146b 100644 --- a/interface/package.json +++ b/interface/package.json @@ -46,8 +46,8 @@ "typescript": "^5.0.4" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^5.59.1", - "@typescript-eslint/parser": "^5.59.1", + "@typescript-eslint/eslint-plugin": "^5.59.2", + "@typescript-eslint/parser": "^5.59.2", "@vitejs/plugin-react-swc": "^3.3.0", "eslint": "^8.39.0", "eslint-config-airbnb": "^19.0.4", diff --git a/interface/yarn.lock b/interface/yarn.lock index 393712080..ddd72c329 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -1358,14 +1358,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^5.59.1": - version: 5.59.1 - resolution: "@typescript-eslint/eslint-plugin@npm:5.59.1" +"@typescript-eslint/eslint-plugin@npm:^5.59.2": + version: 5.59.2 + resolution: "@typescript-eslint/eslint-plugin@npm:5.59.2" dependencies: "@eslint-community/regexpp": ^4.4.0 - "@typescript-eslint/scope-manager": 5.59.1 - "@typescript-eslint/type-utils": 5.59.1 - "@typescript-eslint/utils": 5.59.1 + "@typescript-eslint/scope-manager": 5.59.2 + "@typescript-eslint/type-utils": 5.59.2 + "@typescript-eslint/utils": 5.59.2 debug: ^4.3.4 grapheme-splitter: ^1.0.4 ignore: ^5.2.0 @@ -1378,43 +1378,43 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 39b45b35617b47df6242791f67ee53dafe8d973c0ea452cfb6d8f5883b7ee6c8a5056110c53b91fa941c81294110ea2049f082da53b45fe42deafbeec1f9fbdf + checksum: bb9eb6fdd7d3b7438d4807c7af6bc037e017343c7d86a4a7483029e995f388c406b085bbdd329a672fa314f483182c7c5622c9d243f2f1d67e3b3f70c00d4e00 languageName: node linkType: hard -"@typescript-eslint/parser@npm:^5.59.1": - version: 5.59.1 - resolution: "@typescript-eslint/parser@npm:5.59.1" +"@typescript-eslint/parser@npm:^5.59.2": + version: 5.59.2 + resolution: "@typescript-eslint/parser@npm:5.59.2" dependencies: - "@typescript-eslint/scope-manager": 5.59.1 - "@typescript-eslint/types": 5.59.1 - "@typescript-eslint/typescript-estree": 5.59.1 + "@typescript-eslint/scope-manager": 5.59.2 + "@typescript-eslint/types": 5.59.2 + "@typescript-eslint/typescript-estree": 5.59.2 debug: ^4.3.4 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 59a9076ac1b547bbc36517cbb8201489541670023c4647d2f21f5b5172ab097c83e3b7d2652ebafe9a0efba673e09056fa8f4f2c1eae656d8328ec9084e31629 + checksum: 9c142ff97ec171240f48896b4b2b16159b031fe3d12eaab53071b39071846a66251f5fb51094c019e7fc439793a4a0a9ee8e1139cff39cd3fa9333baffe554c6 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:5.59.1": - version: 5.59.1 - resolution: "@typescript-eslint/scope-manager@npm:5.59.1" +"@typescript-eslint/scope-manager@npm:5.59.2": + version: 5.59.2 + resolution: "@typescript-eslint/scope-manager@npm:5.59.2" dependencies: - "@typescript-eslint/types": 5.59.1 - "@typescript-eslint/visitor-keys": 5.59.1 - checksum: 0b661e8d7221b6f6c83029127ddfac811f857dacd4bf1d7c70d9ed3c6d5f862da9596f03947d6e9bce6f18ba26d310a07732f70450e16fbd70b54ac74e5df81f + "@typescript-eslint/types": 5.59.2 + "@typescript-eslint/visitor-keys": 5.59.2 + checksum: 92e2927640cd9a155c3ebc971f64ecc4b0dd76449b803b189fdfcf5cd6f9dae16fc0d459a889d517f1bf1b8f17a77a4852f49a773ca2e3f0e63ae517123c044c languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:5.59.1": - version: 5.59.1 - resolution: "@typescript-eslint/type-utils@npm:5.59.1" +"@typescript-eslint/type-utils@npm:5.59.2": + version: 5.59.2 + resolution: "@typescript-eslint/type-utils@npm:5.59.2" dependencies: - "@typescript-eslint/typescript-estree": 5.59.1 - "@typescript-eslint/utils": 5.59.1 + "@typescript-eslint/typescript-estree": 5.59.2 + "@typescript-eslint/utils": 5.59.2 debug: ^4.3.4 tsutils: ^3.21.0 peerDependencies: @@ -1422,23 +1422,23 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: fe4ab0609529d2bc2d1a1a6f0aed667448342194c81bf2766b6f015086c37679da57ac9392489f0bd734e7cb49609353b580de96e88b4968ccf3ab7d203aa8ca + checksum: a8c3371a031b4760d2c40ff30acf894055a45bc3f10ca36fe46e893dd1f9ab46c5cea8e0e159a6ea944687536c7dcb8aa677a88bd33cd88b9c955f4bb971366b languageName: node linkType: hard -"@typescript-eslint/types@npm:5.59.1": - version: 5.59.1 - resolution: "@typescript-eslint/types@npm:5.59.1" - checksum: 28c128906bf7a2aaef48db056f75db494007047e60b1bfb9f2dc663aaf5d70f34f4cef51bf4330194cb83144156131aa825e321253519aea1f08f8405d7a0b78 +"@typescript-eslint/types@npm:5.59.2": + version: 5.59.2 + resolution: "@typescript-eslint/types@npm:5.59.2" + checksum: 6e213fbd54b69d03f4db387931329d1274ff23b1d22514dc05087b35883c52f9050957c662fdc4fc232dc5691c7efab16961894a01bdbaf39412222df8bbd910 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.59.1": - version: 5.59.1 - resolution: "@typescript-eslint/typescript-estree@npm:5.59.1" +"@typescript-eslint/typescript-estree@npm:5.59.2": + version: 5.59.2 + resolution: "@typescript-eslint/typescript-estree@npm:5.59.2" dependencies: - "@typescript-eslint/types": 5.59.1 - "@typescript-eslint/visitor-keys": 5.59.1 + "@typescript-eslint/types": 5.59.2 + "@typescript-eslint/visitor-keys": 5.59.2 debug: ^4.3.4 globby: ^11.1.0 is-glob: ^4.0.3 @@ -1447,35 +1447,35 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 856bcc61c8ec69c979f139ad1bfff965d1f1fe72bfcedee8a62be2b24c5b8b1a1bcd874e83b7f235cd5cadf88936da203064f64dd99de0aa63697228e8109c6f + checksum: f2a168ba62074ab4375176bc4ab28a9e61442d99b9aa62f468f8e8af41915d86520f9f81b1f6a39de2b68438c17c1ba88ed32d39eab757a5552ef5b33193d0c9 languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.59.1": - version: 5.59.1 - resolution: "@typescript-eslint/utils@npm:5.59.1" +"@typescript-eslint/utils@npm:5.59.2": + version: 5.59.2 + resolution: "@typescript-eslint/utils@npm:5.59.2" dependencies: "@eslint-community/eslint-utils": ^4.2.0 "@types/json-schema": ^7.0.9 "@types/semver": ^7.3.12 - "@typescript-eslint/scope-manager": 5.59.1 - "@typescript-eslint/types": 5.59.1 - "@typescript-eslint/typescript-estree": 5.59.1 + "@typescript-eslint/scope-manager": 5.59.2 + "@typescript-eslint/types": 5.59.2 + "@typescript-eslint/typescript-estree": 5.59.2 eslint-scope: ^5.1.1 semver: ^7.3.7 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 366fcca9bb39ed74a5fac696fda1e12dc8ef9b0c6bc84afcf2da738052ff0921513fccccae7df219bf8f2fd3a81a0438cba70aedbe0b0545f1d47fbf9d766f30 + checksum: a1c7cf17c2a3e6458ba689b7ba2cf9a3f6f8673effa66dd3f1fbfb88373a9fd1710422bd2209628def6397846cd8d45c4bb06834a5a01eaa48580cbfe717dbf9 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:5.59.1": - version: 5.59.1 - resolution: "@typescript-eslint/visitor-keys@npm:5.59.1" +"@typescript-eslint/visitor-keys@npm:5.59.2": + version: 5.59.2 + resolution: "@typescript-eslint/visitor-keys@npm:5.59.2" dependencies: - "@typescript-eslint/types": 5.59.1 + "@typescript-eslint/types": 5.59.2 eslint-visitor-keys: ^3.3.0 - checksum: f2d48ba4adf19f6b34306b281886e0dfc8bd7b3a6ecf5f65ff2bd104aa01f3b706904a6fd5b8f97eddec11d503d81275ee946f418ce3ee27c947826b2e7aaccf + checksum: a3d2a498b92b57048dbab93ca72acf960d4aa6e6cf4f277ab24b9d004c99dba5ef46f22959b7c37a0e12f555a8a88286ff4732205e0f1a40ad937ce26bb8dbd3 languageName: node linkType: hard @@ -1505,8 +1505,8 @@ __metadata: "@types/react": ^18.2.0 "@types/react-dom": ^18.2.1 "@types/react-router-dom": ^5.3.3 - "@typescript-eslint/eslint-plugin": ^5.59.1 - "@typescript-eslint/parser": ^5.59.1 + "@typescript-eslint/eslint-plugin": ^5.59.2 + "@typescript-eslint/parser": ^5.59.2 "@vitejs/plugin-react-swc": ^3.3.0 async-validator: ^4.2.5 axios: ^1.4.0 From b027ce0d91528285634cd8dbeee69ad591913c8f Mon Sep 17 00:00:00 2001 From: Proddy Date: Mon, 1 May 2023 22:13:17 +0200 Subject: [PATCH 70/89] remember old settings --- interface/src/framework/system/SystemLog.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/framework/system/SystemLog.tsx b/interface/src/framework/system/SystemLog.tsx index 6c9643eea..b011b1232 100644 --- a/interface/src/framework/system/SystemLog.tsx +++ b/interface/src/framework/system/SystemLog.tsx @@ -66,7 +66,7 @@ const SystemLog: FC = () => { const { LL } = useI18nContext(); - const { loadData, data, setData, origData, dirtyFlags, blocker, setDirtyFlags } = useRest({ + const { loadData, data, setData, origData, dirtyFlags, blocker, setDirtyFlags, setOrigData } = useRest({ read: SystemApi.readLogSettings }); @@ -152,6 +152,7 @@ const SystemLog: FC = () => { if (response.status !== 200) { toast.error(LL.PROBLEM_UPDATING()); } else { + setOrigData(response.data); setDirtyFlags([]); toast.success(LL.UPDATED_OF(LL.SETTINGS_OF(''))); } From 72f530b9695a1112331e76dc9b15ec7b97d3c376 Mon Sep 17 00:00:00 2001 From: Proddy Date: Mon, 1 May 2023 22:13:39 +0200 Subject: [PATCH 71/89] export setOrigData needed for systemlog --- interface/src/utils/useRest.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/utils/useRest.ts b/interface/src/utils/useRest.ts index 0ee478f60..7dbc5cd56 100644 --- a/interface/src/utils/useRest.ts +++ b/interface/src/utils/useRest.ts @@ -84,6 +84,7 @@ export const useRest = ({ read, update }: RestRequestOptions) => { origData, dirtyFlags, setDirtyFlags, + setOrigData, blocker, errorMessage, restartNeeded From 8aa7cd166b74d78fd2ce7891fc1cf19b97732c11 Mon Sep 17 00:00:00 2001 From: Proddy Date: Mon, 1 May 2023 22:13:53 +0200 Subject: [PATCH 72/89] experiment showing only 1 device --- interface/src/project/DashboardDevices.tsx | 75 ++++++++++++++-------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/interface/src/project/DashboardDevices.tsx b/interface/src/project/DashboardDevices.tsx index ced1d801f..182f5560e 100644 --- a/interface/src/project/DashboardDevices.tsx +++ b/interface/src/project/DashboardDevices.tsx @@ -7,6 +7,7 @@ import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDown import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined'; import PlayArrowIcon from '@mui/icons-material/PlayArrow'; import RefreshIcon from '@mui/icons-material/Refresh'; +import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore'; import StarIcon from '@mui/icons-material/Star'; import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined'; import { @@ -193,8 +194,23 @@ const DashboardDevices: FC = () => { } }; + function onSelectChange(action: any, state: any) { + setSelectedDevice(state.id); + if (action.type === 'ADD_BY_ID_EXCLUSIVELY') { + void fetchDeviceData(state.id); + } + } + + const device_select = useRowSelect( + { nodes: coreData.devices }, + { + onChange: onSelectChange + } + ); + const fetchCoreData = useCallback(async () => { try { + setSelectedDevice(undefined); setCoreData((await EMSESP.readCoreData()).data); } catch (error) { toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); @@ -216,20 +232,6 @@ const DashboardDevices: FC = () => { } }; - function onSelectChange(action: any, state: any) { - setSelectedDevice(state.id); - if (action.type === 'ADD_BY_ID_EXCLUSIVELY') { - void fetchDeviceData(state.id); - } - } - - const device_select = useRowSelect( - { nodes: coreData.devices }, - { - onChange: onSelectChange - } - ); - const escapeCsvCell = (cell: any) => { if (cell == null) { return ''; @@ -358,7 +360,12 @@ const DashboardDevices: FC = () => { )} -
+
dv.id === selectedDevice) : coreData.devices }} + select={device_select} + theme={device_theme} + layout={{ custom: true }} + > {(tableList: any) => ( <>
@@ -398,7 +405,7 @@ const DashboardDevices: FC = () => { }; const renderDeviceData = () => { - if (!device_select.state.id) { + if (!selectedDevice) { return; } @@ -521,16 +528,32 @@ const DashboardDevices: FC = () => { /> )} - - - {device_select.state.id && device_select.state.id !== 'sensor' && ( - - )} - + + + + + {device_select.state.id && device_select.state.id !== 'sensor' && ( + + )} + + + + + + + + ); }; From 5b87e74be819a1509f4d26b3e43deaa833888c2f Mon Sep 17 00:00:00 2001 From: Proddy Date: Wed, 3 May 2023 08:54:27 +0200 Subject: [PATCH 73/89] update comment --- mock-api/server.js | 5 +++-- src/emsdevice.cpp | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/mock-api/server.js b/mock-api/server.js index d53fbfdd4..ee510a735 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -479,7 +479,8 @@ const emsesp_devicedata_1 = { id: '00Chosen Room Temperature', c: 'hc1/seltemp', m: 5, - x: 52 + x: 52, + s: 0.5 }, { v: 22.6, @@ -508,7 +509,7 @@ const emsesp_devicedata_2 = { data: [ { v: '', u: 0, id: '08reset', c: 'reset', l: ['-', 'maintenance', 'error'] }, { v: 'off', u: 0, id: '08heating active' }, - { v: 'off', u: 0, id: '04tapwater active' }, + { v: 'off2', u: 0, id: '04tapwater active' }, { v: 5, u: 1, id: '04selected flow temperature', c: 'selflowtemp' }, { v: 0, u: 3, id: '0Eburner selected max power', c: 'selburnpow' }, { v: 0, u: 3, id: '00heating pump modulation' }, diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 89b78b27c..ff26bc870 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -513,7 +513,7 @@ void EMSdevice::add_device_value(uint8_t tag, // to b // get fullname, getting translation if it exists const char * const * fullname; if (Helpers::count_items(name) == 1) { - fullname = nullptr; // no translations available, use empty + fullname = nullptr; // no translations available, use empty } else { fullname = &name[1]; // translations start at index 1 } @@ -838,7 +838,7 @@ void EMSdevice::generate_values_web(JsonObject & output) { JsonObject obj = data.createNestedObject(); // create the object, we know there is a value uint8_t fahrenheit = 0; - // handle Booleans (true, false), use strings, not native true/false) + // handle Booleans (true, false), output as strings according to the user settings if (dv.type == DeviceValueType::BOOL) { auto value_b = (bool)*(uint8_t *)(dv.value_p); char s[12]; @@ -1303,7 +1303,7 @@ void EMSdevice::dump_value_info() { if (dv.type == DeviceValueType::BOOL) { snprintf(entityid, sizeof(entityid), "binary_sensor.%s", entity_with_tag); // binary sensor (for booleans) } else { - snprintf(entityid, sizeof(entityid), "sensor.%s", entity_with_tag); // normal HA sensor + snprintf(entityid, sizeof(entityid), "sensor.%s", entity_with_tag); // normal HA sensor } } From 310bb53985401b071432a63995ef114ae6034f6f Mon Sep 17 00:00:00 2001 From: Proddy Date: Wed, 3 May 2023 08:54:36 +0200 Subject: [PATCH 74/89] package update --- interface/package.json | 4 +- interface/yarn.lock | 90 +++++++++++++++++++++--------------------- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/interface/package.json b/interface/package.json index 52ff5146b..c44d6683c 100644 --- a/interface/package.json +++ b/interface/package.json @@ -23,7 +23,7 @@ "@emotion/styled": "^11.10.8", "@msgpack/msgpack": "^3.0.0-beta2", "@mui/icons-material": "^5.11.16", - "@mui/material": "^5.12.2", + "@mui/material": "^5.12.3", "@table-library/react-table-library": "4.1.2", "@types/lodash-es": "^4.17.7", "@types/node": "^18.16.3", @@ -65,7 +65,7 @@ "prettier": "^2.8.8", "rollup-plugin-visualizer": "^5.9.0", "terser": "^5.17.1", - "vite": "^4.3.3", + "vite": "^4.3.4", "vite-plugin-svgr": "^2.4.0", "vite-tsconfig-paths": "^4.2.0" }, diff --git a/interface/yarn.lock b/interface/yarn.lock index ddd72c329..ee4479b1d 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -270,7 +270,7 @@ __metadata: languageName: node linkType: hard -"@emotion/cache@npm:^11.10.7, @emotion/cache@npm:^11.10.8": +"@emotion/cache@npm:^11.10.8": version: 11.10.8 resolution: "@emotion/cache@npm:11.10.8" dependencies: @@ -694,14 +694,14 @@ __metadata: languageName: node linkType: hard -"@mui/base@npm:5.0.0-alpha.127": - version: 5.0.0-alpha.127 - resolution: "@mui/base@npm:5.0.0-alpha.127" +"@mui/base@npm:5.0.0-alpha.128": + version: 5.0.0-alpha.128 + resolution: "@mui/base@npm:5.0.0-alpha.128" dependencies: "@babel/runtime": ^7.21.0 "@emotion/is-prop-valid": ^1.2.0 "@mui/types": ^7.2.4 - "@mui/utils": ^5.12.0 + "@mui/utils": ^5.12.3 "@popperjs/core": ^2.11.7 clsx: ^1.2.1 prop-types: ^15.8.1 @@ -713,14 +713,14 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 6b339fe6a7653e55d04442776adb13864916fdd1a16c3c40caafb72e0691f9e86a4e49820b0f2d92d84ce24daeb024a24ceed20275ab3dbf1f87493cc57bd0a3 + checksum: 10177bfd2e17df542fd5a011b080ce3881ce197a4273988cd244919fda6b8091b0b230f3d4675991dac7913e0785f064aab1bf27c61c2b21510bd7868af23393 languageName: node linkType: hard -"@mui/core-downloads-tracker@npm:^5.12.2": - version: 5.12.2 - resolution: "@mui/core-downloads-tracker@npm:5.12.2" - checksum: 6d421c1758b99f416c2d7f61c838809e4bbf017da8ec26fe9198b3d10d41e1d0ea58bbf7ed016dca38d4c76300f94bf900dfd02fb34fb53f57492cffe1b57c65 +"@mui/core-downloads-tracker@npm:^5.12.3": + version: 5.12.3 + resolution: "@mui/core-downloads-tracker@npm:5.12.3" + checksum: 0172d83758dfa85e90687ffeaa8d95af9651f4a3e1ad20292b4b7f3985a228ae5041a584963607a6e0a197b29432aeae9f7511b938fcddd2336b90a280395afd languageName: node linkType: hard @@ -740,16 +740,16 @@ __metadata: languageName: node linkType: hard -"@mui/material@npm:^5.12.2": - version: 5.12.2 - resolution: "@mui/material@npm:5.12.2" +"@mui/material@npm:^5.12.3": + version: 5.12.3 + resolution: "@mui/material@npm:5.12.3" dependencies: "@babel/runtime": ^7.21.0 - "@mui/base": 5.0.0-alpha.127 - "@mui/core-downloads-tracker": ^5.12.2 - "@mui/system": ^5.12.1 + "@mui/base": 5.0.0-alpha.128 + "@mui/core-downloads-tracker": ^5.12.3 + "@mui/system": ^5.12.3 "@mui/types": ^7.2.4 - "@mui/utils": ^5.12.0 + "@mui/utils": ^5.12.3 "@types/react-transition-group": ^4.4.5 clsx: ^1.2.1 csstype: ^3.1.2 @@ -769,16 +769,16 @@ __metadata: optional: true "@types/react": optional: true - checksum: 35ef3e103d2ec1aa9fdbffd0df0097349dcb8eaf41add7c9c4ed023012c256bb07df2266e7c50fef67d6cbac61a86b7ad1baa9a791d0c8fcbfce1d2194becf43 + checksum: 3456e6a5541add3a5cde92f49b5b0fc9ca5d8a66f75a42cd2c9e09dcc413ce820e6d1db5d917ea57ea335c5f72304bb247ad47f12d5b7215ad0e48db82ccef4d languageName: node linkType: hard -"@mui/private-theming@npm:^5.12.0": - version: 5.12.0 - resolution: "@mui/private-theming@npm:5.12.0" +"@mui/private-theming@npm:^5.12.3": + version: 5.12.3 + resolution: "@mui/private-theming@npm:5.12.3" dependencies: "@babel/runtime": ^7.21.0 - "@mui/utils": ^5.12.0 + "@mui/utils": ^5.12.3 prop-types: ^15.8.1 peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 @@ -786,16 +786,16 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: c7ad031d9574a4b217400ce78f2f58a487fae5249ce46a124ae1a2814e8cc52848dd20fdff7ccc4e6342b83f5e77ec1d5b1eabc74777647fb1f7f0a4f8b5be25 + checksum: 7a1cfda9fe9e7b5dc4b2bd48521ec5087db3a7d13986a7a5fc51f6ab62ec14f64db19cb7a2f6b2d13184fbcbb626228be3008def0236c191e77b29a9b1842a1c languageName: node linkType: hard -"@mui/styled-engine@npm:^5.12.0": - version: 5.12.0 - resolution: "@mui/styled-engine@npm:5.12.0" +"@mui/styled-engine@npm:^5.12.3": + version: 5.12.3 + resolution: "@mui/styled-engine@npm:5.12.3" dependencies: "@babel/runtime": ^7.21.0 - "@emotion/cache": ^11.10.7 + "@emotion/cache": ^11.10.8 csstype: ^3.1.2 prop-types: ^15.8.1 peerDependencies: @@ -807,19 +807,19 @@ __metadata: optional: true "@emotion/styled": optional: true - checksum: 278ce278b0622866b4b8d1caab4ed151986c5f967830140783e1f5609bd5f9fe34c470daef8bcf2ba9706bc4fe8caae950227bb3853ddde3e5b2f45c2f90aad5 + checksum: 65d31cbb7f27a3340de12737820fd266cab21eb369bf0d8fa9eed2f28a7314f6ed43d6a5684c56397dbb2acddbc32970b964755fd7c9c349cdd19657cb742b0a languageName: node linkType: hard -"@mui/system@npm:^5.12.1": - version: 5.12.1 - resolution: "@mui/system@npm:5.12.1" +"@mui/system@npm:^5.12.3": + version: 5.12.3 + resolution: "@mui/system@npm:5.12.3" dependencies: "@babel/runtime": ^7.21.0 - "@mui/private-theming": ^5.12.0 - "@mui/styled-engine": ^5.12.0 + "@mui/private-theming": ^5.12.3 + "@mui/styled-engine": ^5.12.3 "@mui/types": ^7.2.4 - "@mui/utils": ^5.12.0 + "@mui/utils": ^5.12.3 clsx: ^1.2.1 csstype: ^3.1.2 prop-types: ^15.8.1 @@ -835,7 +835,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: a9dc1e3503f8c036663d0bc38b9bed71f67833890148a29efba5501f0da1f62d0c5372648c840779bdc74ea6e997783441934fa1df90f00ffef158cb6e6d5ce7 + checksum: 72c312b56ad142d9d0df0ab834d496e4107df33f148a26e065178566fd4678a17f8d1310aa33dced6e5c4660b7de7beca8b7d4205cd26b957a89b9e198c2db0f languageName: node linkType: hard @@ -851,9 +851,9 @@ __metadata: languageName: node linkType: hard -"@mui/utils@npm:^5.12.0": - version: 5.12.0 - resolution: "@mui/utils@npm:5.12.0" +"@mui/utils@npm:^5.12.3": + version: 5.12.3 + resolution: "@mui/utils@npm:5.12.3" dependencies: "@babel/runtime": ^7.21.0 "@types/prop-types": ^15.7.5 @@ -862,7 +862,7 @@ __metadata: react-is: ^18.2.0 peerDependencies: react: ^17.0.0 || ^18.0.0 - checksum: 81813844674de745c3834e40d224b02aae98bfee0ada2dd5c68256fd5e866eaaf11b59f575e5d14676c61699ad8d44350f9b72da3c49cdc4b23e6efead965944 + checksum: b2bfae7116f031c16e8a22a9187de979c06081308ddb75f5450eaaea2738fe11650a7e377e9ac4933cc6f90d4f8d55ac94b4f90e45251dea082a96091c129d47 languageName: node linkType: hard @@ -1498,7 +1498,7 @@ __metadata: "@emotion/styled": ^11.10.8 "@msgpack/msgpack": ^3.0.0-beta2 "@mui/icons-material": ^5.11.16 - "@mui/material": ^5.12.2 + "@mui/material": ^5.12.3 "@table-library/react-table-library": 4.1.2 "@types/lodash-es": ^4.17.7 "@types/node": ^18.16.3 @@ -1538,7 +1538,7 @@ __metadata: terser: ^5.17.1 typesafe-i18n: ^5.24.3 typescript: ^5.0.4 - vite: ^4.3.3 + vite: ^4.3.4 vite-plugin-svgr: ^2.4.0 vite-tsconfig-paths: ^4.2.0 languageName: unknown @@ -5736,9 +5736,9 @@ __metadata: languageName: node linkType: hard -"vite@npm:^4.3.3": - version: 4.3.3 - resolution: "vite@npm:4.3.3" +"vite@npm:^4.3.4": + version: 4.3.4 + resolution: "vite@npm:4.3.4" dependencies: esbuild: ^0.17.5 fsevents: ~2.3.2 @@ -5769,7 +5769,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 87814f40a1e21fb372014fe43d0faf3b8f166ee0bc458527b6bccd1b6340782f8e6f5a8986492d99bd31d02f7d1b1538cc0006a0ab6f7e311e603fd1782ae04e + checksum: 05cbff29d9379a4441845a2b6c6c6770a35a858408f3e17f56c86e01d88dc7eda60f41aff7b15b99382b64c198b892a76f9a16505a62a2b0d53ec38e454d5948 languageName: node linkType: hard From e84a3bc99a584d600971629e8d2d2cf05bdfa4fb Mon Sep 17 00:00:00 2001 From: Proddy Date: Wed, 3 May 2023 08:54:57 +0200 Subject: [PATCH 75/89] improve dv validation, add back ugly step --- .../src/project/DashboardDevicesDialog.tsx | 42 ++++++++++++------- interface/src/project/deviceValue.ts | 20 --------- interface/src/project/types.ts | 2 +- interface/src/project/validators.ts | 10 ++--- 4 files changed, 32 insertions(+), 42 deletions(-) diff --git a/interface/src/project/DashboardDevicesDialog.tsx b/interface/src/project/DashboardDevicesDialog.tsx index 91bd958cb..64226c880 100644 --- a/interface/src/project/DashboardDevicesDialog.tsx +++ b/interface/src/project/DashboardDevicesDialog.tsx @@ -17,7 +17,6 @@ import { } from '@mui/material'; import { useState, useEffect } from 'react'; -import { formatValueNoUOM } from './deviceValue'; import { DeviceValueUOM, DeviceValueUOM_s } from './types'; import type { DeviceValue } from './types'; import type Schema from 'async-validator'; @@ -48,11 +47,6 @@ const DashboarDevicesDialog = ({ open, onClose, onSave, selectedItem, validator if (open) { setFieldErrors(undefined); setEditItem(selectedItem); - // format value and convert to string - setEditItem({ - ...selectedItem, - v: formatValueNoUOM(selectedItem.v, selectedItem.u) - }); } }, [open, selectedItem]); @@ -90,13 +84,20 @@ const DashboarDevicesDialog = ({ open, onClose, onSave, selectedItem, validator if (dv.l) { return '[ ' + dv.l.join(' | ') + ' ]'; } + + let helperText = '<'; if (dv.u !== DeviceValueUOM.NONE) { + helperText += 'n'; if (dv.m && dv.x) { - return ''; + helperText += ' between ' + dv.m + ' and ' + dv.x; } - return ''; + if (dv.s) { + helperText += ' , step ' + dv.s; + } + } else { + helperText += 'text'; } - return ''; + return helperText + '>'; }; return ( @@ -108,7 +109,7 @@ const DashboarDevicesDialog = ({ open, onClose, onSave, selectedItem, validator - {editItem.l && ( + {editItem.l ? ( ))} - )} - {!editItem.l && ( + ) : editItem.u !== DeviceValueUOM.NONE ? ( + {setUom(editItem.u)} + }} + /> + ) : ( {setUom(editItem.u)} - }} /> )} diff --git a/interface/src/project/deviceValue.ts b/interface/src/project/deviceValue.ts index 9b1b7ffab..ff7900340 100644 --- a/interface/src/project/deviceValue.ts +++ b/interface/src/project/deviceValue.ts @@ -54,23 +54,3 @@ export function formatValue(LL: TranslationFunctions, value: any, uom: number) { return new Intl.NumberFormat().format(value) + ' ' + DeviceValueUOM_s[uom]; } } - -export const formatValueNoUOM = (value: any, uom: number) => { - if (value === undefined) { - return ''; - } - - switch (uom) { - case DeviceValueUOM.NONE: - if (typeof value === 'number') { - return Number(value); - } - return value; - case DeviceValueUOM.DEGREES: - case DeviceValueUOM.DEGREES_R: - case DeviceValueUOM.FAHRENHEIT: - return Number(value).toFixed(1); - default: - return value; - } -}; diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index 954014dac..ba668a4e5 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -123,7 +123,7 @@ export interface Devices { export interface DeviceValue { id: string; // index, contains mask+name - v: any; // value, in any format + v: any; // value, Number or String u: number; // uom c?: string; // command, optional l?: string[]; // list, optional diff --git a/interface/src/project/validators.ts b/interface/src/project/validators.ts index 1bae952ca..5cc019ecb 100644 --- a/interface/src/project/validators.ts +++ b/interface/src/project/validators.ts @@ -168,13 +168,11 @@ export const deviceValueItemValidation = (dv: DeviceValue) => v: [ { required: true, message: 'Value is required' }, { - validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) { - if (dv.u !== DeviceValueUOM.NONE && isNaN(+value)) { - callback('Not a valid number'); + validator(rule: InternalRuleItem, value: any, callback: (error?: string) => void) { + if (typeof value === 'number' && dv.m && dv.x && (value < dv.m || value > dv.x)) { + callback('Value out of range'); } - (dv.m && Number(value) < dv.m) || (dv.x && Number(value) > dv.x) - ? callback('Value out of range') - : callback(); + callback(); } } ] From 979503256a6c7d5cfb3651a9651bc7c61d23f74f Mon Sep 17 00:00:00 2001 From: Proddy Date: Fri, 5 May 2023 20:25:59 +0200 Subject: [PATCH 76/89] package update --- interface/package.json | 15 +- interface/yarn.lock | 397 ++++++++++++++++++++++++----------------- 2 files changed, 237 insertions(+), 175 deletions(-) diff --git a/interface/package.json b/interface/package.json index c44d6683c..e3cccc503 100644 --- a/interface/package.json +++ b/interface/package.json @@ -24,11 +24,11 @@ "@msgpack/msgpack": "^3.0.0-beta2", "@mui/icons-material": "^5.11.16", "@mui/material": "^5.12.3", - "@table-library/react-table-library": "4.1.2", + "@table-library/react-table-library": "4.1.4", "@types/lodash-es": "^4.17.7", - "@types/node": "^18.16.3", - "@types/react": "^18.2.0", - "@types/react-dom": "^18.2.1", + "@types/node": "^20.0.0", + "@types/react": "^18.2.5", + "@types/react-dom": "^18.2.4", "@types/react-router-dom": "^5.3.3", "async-validator": "^4.2.5", "axios": "^1.4.0", @@ -39,7 +39,8 @@ "react-dom": "latest", "react-dropzone": "^14.2.3", "react-icons": "^4.8.0", - "react-router-dom": "^6.10.0", + "react-number-format": "^5.1.4", + "react-router-dom": "^6.11.1", "react-toastify": "^9.1.2", "sockette": "^2.0.6", "typesafe-i18n": "^5.24.3", @@ -65,8 +66,8 @@ "prettier": "^2.8.8", "rollup-plugin-visualizer": "^5.9.0", "terser": "^5.17.1", - "vite": "^4.3.4", - "vite-plugin-svgr": "^2.4.0", + "vite": "^4.3.5", + "vite-plugin-svgr": "^3.2.0", "vite-tsconfig-paths": "^4.2.0" }, "packageManager": "yarn@3.4.1" diff --git a/interface/yarn.lock b/interface/yarn.lock index ee4479b1d..0d7696ecc 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -24,67 +24,67 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/compat-data@npm:7.21.4" - checksum: 8752c19f78f6b91188b8c4867ae357fe79206ed3ea2fbc9357ac66639b1bd4aa1ba44cedba238369070704605caf9a4a742bf1cfa2b9414845a8995e0c9ac40a +"@babel/compat-data@npm:^7.21.5": + version: 7.21.7 + resolution: "@babel/compat-data@npm:7.21.7" + checksum: cd6bc85364a569cc74bcf0bfdc27161a1cb423c60c624e06f44b53c9e6fe7708bd0af3e389d376aec8ed9b2795907c43d01e4163dbc2a3a3142a2de55464a51d languageName: node linkType: hard -"@babel/core@npm:^7.19.6": - version: 7.21.4 - resolution: "@babel/core@npm:7.21.4" +"@babel/core@npm:^7.21.3": + version: 7.21.8 + resolution: "@babel/core@npm:7.21.8" dependencies: "@ampproject/remapping": ^2.2.0 "@babel/code-frame": ^7.21.4 - "@babel/generator": ^7.21.4 - "@babel/helper-compilation-targets": ^7.21.4 - "@babel/helper-module-transforms": ^7.21.2 - "@babel/helpers": ^7.21.0 - "@babel/parser": ^7.21.4 + "@babel/generator": ^7.21.5 + "@babel/helper-compilation-targets": ^7.21.5 + "@babel/helper-module-transforms": ^7.21.5 + "@babel/helpers": ^7.21.5 + "@babel/parser": ^7.21.8 "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.4 - "@babel/types": ^7.21.4 + "@babel/traverse": ^7.21.5 + "@babel/types": ^7.21.5 convert-source-map: ^1.7.0 debug: ^4.1.0 gensync: ^1.0.0-beta.2 json5: ^2.2.2 semver: ^6.3.0 - checksum: 0987cf87f277eb19c410ef3a03f9377efec40005a5dd2a67ddd0a5f6f429c9d88fefba25206ccf3378c93814b4c9c06a236bf8fcd3ed6ef1c8089fefaa76af24 + checksum: bf6bb92bd78fb8b6628bb0612ac0915407b996b179e1404108f92ed32972978340b4457b08f2abf86390a58fb51815cab419edb2dbbc8846efc39eaa61b8cde3 languageName: node linkType: hard -"@babel/generator@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/generator@npm:7.21.4" +"@babel/generator@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/generator@npm:7.21.5" dependencies: - "@babel/types": ^7.21.4 + "@babel/types": ^7.21.5 "@jridgewell/gen-mapping": ^0.3.2 "@jridgewell/trace-mapping": ^0.3.17 jsesc: ^2.5.1 - checksum: 0eb142a5ca8a978981c11de9e0ab033659f7110bc21cd14eaeb80977835b895c3a97e5a1807a2f6e79003682141057f00b4bd5f69fe998b4cf99bf989c361277 + checksum: e98b51440cbbcee68e66c66684b5334f5929dba512067a6c3c1aecc77131b308bf61eca74a5ae1fb73028089d22a188ca2219c364596117f27695102afc18e95 languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/helper-compilation-targets@npm:7.21.4" +"@babel/helper-compilation-targets@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helper-compilation-targets@npm:7.21.5" dependencies: - "@babel/compat-data": ^7.21.4 + "@babel/compat-data": ^7.21.5 "@babel/helper-validator-option": ^7.21.0 browserslist: ^4.21.3 lru-cache: ^5.1.1 semver: ^6.3.0 peerDependencies: "@babel/core": ^7.0.0 - checksum: ad553d5a473beeedaf7be4e450d3d6f36920f34005bc45bc62d94a16ae553dcb7d9fc5b2bc721ffa203e542bc8a1fb241e1c97fba1fae5f7ef5ba87a7730a1b9 + checksum: 36752452eb70d6a6f52f68846344a739089374a97619e5a4857e31e7d067bdad8270efd9dd0dd5dfc483dd2d98bf0c1c6f08e3315fe949e7bfffef67eaf669ad languageName: node linkType: hard -"@babel/helper-environment-visitor@npm:^7.18.9": - version: 7.18.9 - resolution: "@babel/helper-environment-visitor@npm:7.18.9" - checksum: a69dd50ea91d8143b899a40ca7a387fa84dbaa02e606d8692188c7c59bd4007bcd632c189f7b7dab72cb7a016e159557a6fccf7093ab9b584d87cf2ea8cf36b7 +"@babel/helper-environment-visitor@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helper-environment-visitor@npm:7.21.5" + checksum: d3f965d9691e3e2e11036d23ba9993a42d18f9be3d4589d3bb3d09d02e9d4d204026965633e36fb43b35fde905c2dfe753fb59b72ae0c3841f5a627fb1738d8a languageName: node linkType: hard @@ -107,7 +107,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.18.6": +"@babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.21.4": version: 7.21.4 resolution: "@babel/helper-module-imports@npm:7.21.4" dependencies: @@ -116,28 +116,28 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.21.2": - version: 7.21.2 - resolution: "@babel/helper-module-transforms@npm:7.21.2" +"@babel/helper-module-transforms@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helper-module-transforms@npm:7.21.5" dependencies: - "@babel/helper-environment-visitor": ^7.18.9 - "@babel/helper-module-imports": ^7.18.6 - "@babel/helper-simple-access": ^7.20.2 + "@babel/helper-environment-visitor": ^7.21.5 + "@babel/helper-module-imports": ^7.21.4 + "@babel/helper-simple-access": ^7.21.5 "@babel/helper-split-export-declaration": ^7.18.6 "@babel/helper-validator-identifier": ^7.19.1 "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.2 - "@babel/types": ^7.21.2 - checksum: 35d4508826bae2db69ab6966db1810b5e7b9157e471525ad1f2119e16742bd293da02587bddb2843368dcd411ddd5ae0f212d6381bcf32e1b338a84b5b27ae30 + "@babel/traverse": ^7.21.5 + "@babel/types": ^7.21.5 + checksum: a3b6ceaa995bf35e7a072066c3c9ba9ee6983cf36605f0c6a0ffcaab94d6dc13eba21b00434a023bf99d66c080fec335cf464619b97f7af39e1a5269cf0d7169 languageName: node linkType: hard -"@babel/helper-simple-access@npm:^7.20.2": - version: 7.20.2 - resolution: "@babel/helper-simple-access@npm:7.20.2" +"@babel/helper-simple-access@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helper-simple-access@npm:7.21.5" dependencies: - "@babel/types": ^7.20.2 - checksum: 79cea28155536c74b37839748caea534bc413fac8c512e6101e9eecfe83f670db77bc782bdb41114caecbb1e2a73007ff6015d6a5ce58cae5363b8c5bd2dcee9 + "@babel/types": ^7.21.5 + checksum: 682cd80b47c2424c31afe70bcc8ad3e401c612f6923c432e4b8245c5b6bc5ccddf3e405ea41ba890ccab79c0b5b95da3db125944ac0decc8d31d48469e593a0e languageName: node linkType: hard @@ -157,6 +157,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helper-string-parser@npm:7.21.5" + checksum: 4d0834c4a67c283e9277f5e565551fede00b7d68007e368c95c776e13d05002e8f9861716e11613880889d6f3463329d2af687ceea5fc5263f8b3d25a53d31da + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.18.6, @babel/helper-validator-identifier@npm:^7.19.1": version: 7.19.1 resolution: "@babel/helper-validator-identifier@npm:7.19.1" @@ -171,14 +178,14 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.21.0": - version: 7.21.0 - resolution: "@babel/helpers@npm:7.21.0" +"@babel/helpers@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helpers@npm:7.21.5" dependencies: "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.0 - "@babel/types": ^7.21.0 - checksum: a7415373f1c9b84fe32839d5219c3d695e84b910f49a20786caf3b5a37f5079d26af6a5b36b4f2e3eb450b2413c309785483a8d59246d1326c44184c51c24255 + "@babel/traverse": ^7.21.5 + "@babel/types": ^7.21.5 + checksum: 5e58854afa1d0896185dcb12a1b6feacefb7d913d52bafa84792274651af2d3172923bdc26d1320fd6b04a2e208dc0d6730951043f17d10c08ca87231e5b84ec languageName: node linkType: hard @@ -193,7 +200,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.20.7, @babel/parser@npm:^7.21.4": +"@babel/parser@npm:^7.20.7": version: 7.21.4 resolution: "@babel/parser@npm:7.21.4" bin: @@ -202,6 +209,15 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.21.5, @babel/parser@npm:^7.21.8": + version: 7.21.8 + resolution: "@babel/parser@npm:7.21.8" + bin: + parser: ./bin/babel-parser.js + checksum: 58789e972e5acce3abbd9dd4c8d4be7e15e071818d2038d195bc56664722f238abb8842d91da5c8894ab0b8f8c0841eabc675f681925c2fba12675bf3ec5c5fc + languageName: node + linkType: hard + "@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.7": version: 7.21.0 resolution: "@babel/runtime@npm:7.21.0" @@ -222,25 +238,25 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.21.0, @babel/traverse@npm:^7.21.2, @babel/traverse@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/traverse@npm:7.21.4" +"@babel/traverse@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/traverse@npm:7.21.5" dependencies: "@babel/code-frame": ^7.21.4 - "@babel/generator": ^7.21.4 - "@babel/helper-environment-visitor": ^7.18.9 + "@babel/generator": ^7.21.5 + "@babel/helper-environment-visitor": ^7.21.5 "@babel/helper-function-name": ^7.21.0 "@babel/helper-hoist-variables": ^7.18.6 "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/parser": ^7.21.4 - "@babel/types": ^7.21.4 + "@babel/parser": ^7.21.5 + "@babel/types": ^7.21.5 debug: ^4.1.0 globals: ^11.1.0 - checksum: 3b2e7e80ef088881ad1f30a032f71ba63d734c270cd240dc229f26bfdeabcd661cf40d2c083f250812b08bb04985f77fb038b7b1ee629b3378ee867dff163878 + checksum: 1b126b71b98aaff01ec1f0f0389d08beb6eda3d0b71878af4c6cf386686933a076d969240f270c6a01910d8036a1fb9013d53bd5c136b9b24025204a4dc48d03 languageName: node linkType: hard -"@babel/types@npm:^7.18.6, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.2, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.0, @babel/types@npm:^7.21.2, @babel/types@npm:^7.21.4, @babel/types@npm:^7.8.3": +"@babel/types@npm:^7.18.6, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.0, @babel/types@npm:^7.21.4, @babel/types@npm:^7.8.3": version: 7.21.4 resolution: "@babel/types@npm:7.21.4" dependencies: @@ -251,6 +267,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.21.3, @babel/types@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/types@npm:7.21.5" + dependencies: + "@babel/helper-string-parser": ^7.21.5 + "@babel/helper-validator-identifier": ^7.19.1 + to-fast-properties: ^2.0.0 + checksum: 23c943aa2c0d11b798e9298b55b1993da8b386504aac2f781a49b4bbf2cf2ad5e1003409241578574e421c999ff7a3aab2cf30ad3581d33eb9053d82b9e20408 + languageName: node + linkType: hard + "@emotion/babel-plugin@npm:^11.10.8": version: 11.10.8 resolution: "@emotion/babel-plugin@npm:11.10.8" @@ -934,10 +961,10 @@ __metadata: languageName: node linkType: hard -"@remix-run/router@npm:1.5.0": - version: 1.5.0 - resolution: "@remix-run/router@npm:1.5.0" - checksum: 63c3695df0470943213f8144183501bbb6a8176671f2ed4547ffa1852f79fd71054b4b4a715795e9e250198991d9e6e121b3fcab9c27df37871dcdfbc8de83a1 +"@remix-run/router@npm:1.6.1": + version: 1.6.1 + resolution: "@remix-run/router@npm:1.6.1" + checksum: 6d6dad1b6a06171d5d8549eca7cd7ef332a40987b732829e48993d55b3c7a7ca661c7d891be938055a42a4b18f07b5f2b66295c89cb221464eda2678ac41621e languageName: node linkType: hard @@ -957,16 +984,16 @@ __metadata: languageName: node linkType: hard -"@svgr/babel-plugin-add-jsx-attribute@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:6.5.1" +"@svgr/babel-plugin-add-jsx-attribute@npm:^7.0.0": + version: 7.0.0 + resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:7.0.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: a13ed0797189d5497890530449029bec388310e260a96459e304e2729e7a2cf4d20d34f882d9a77ccce73dd3d36065afbb6987258fdff618d7d57955065a8ad4 + checksum: 66714c2961f21409b0d33f0f65cf52f2496838b4ed056e98c872faa9f60754fae491ca4397717991eaa9884a0a44ae8920fd550101c9877759bd73f361a49800 languageName: node linkType: hard -"@svgr/babel-plugin-remove-jsx-attribute@npm:*": +"@svgr/babel-plugin-remove-jsx-attribute@npm:^7.0.0": version: 7.0.0 resolution: "@svgr/babel-plugin-remove-jsx-attribute@npm:7.0.0" peerDependencies: @@ -975,7 +1002,7 @@ __metadata: languageName: node linkType: hard -"@svgr/babel-plugin-remove-jsx-empty-expression@npm:*": +"@svgr/babel-plugin-remove-jsx-empty-expression@npm:^7.0.0": version: 7.0.0 resolution: "@svgr/babel-plugin-remove-jsx-empty-expression@npm:7.0.0" peerDependencies: @@ -984,103 +1011,100 @@ __metadata: languageName: node linkType: hard -"@svgr/babel-plugin-replace-jsx-attribute-value@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-plugin-replace-jsx-attribute-value@npm:6.5.1" +"@svgr/babel-plugin-replace-jsx-attribute-value@npm:^7.0.0": + version: 7.0.0 + resolution: "@svgr/babel-plugin-replace-jsx-attribute-value@npm:7.0.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 318786787c9a217c33a7340c8856436858e1fffa5a6df635fedc6b9a371f3afea080ea074b9e3cfbbd9dd962ead924fde8bc9855a394c38dd60e391883a58c81 + checksum: 9a39807bd09fb00c121e2b6952e24b90b6d9cd2318105176b93ccc4e1ec5b87b9999b96bce6f9f5e7769033583565908b440951de89ac9c3cb82ea0e0a3db686 languageName: node linkType: hard -"@svgr/babel-plugin-svg-dynamic-title@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-plugin-svg-dynamic-title@npm:6.5.1" +"@svgr/babel-plugin-svg-dynamic-title@npm:^7.0.0": + version: 7.0.0 + resolution: "@svgr/babel-plugin-svg-dynamic-title@npm:7.0.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 16ef228c793b909fec47dd7dc05c1c3c2d77a824f42055df37e141e0534081b1bc4aec6dcc51be50c221df9f262f59270fc1c379923bfd4f5db302abafabfd8d + checksum: 49dd7907a63bd7643e6081d0bc4daee23e3fc095b6eafc58760f5d67314eee1ea60a6788ccbe68e2457f083ea31522c847119fe48eb6e2dc20956b9bb3316cbb languageName: node linkType: hard -"@svgr/babel-plugin-svg-em-dimensions@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-plugin-svg-em-dimensions@npm:6.5.1" +"@svgr/babel-plugin-svg-em-dimensions@npm:^7.0.0": + version: 7.0.0 + resolution: "@svgr/babel-plugin-svg-em-dimensions@npm:7.0.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: dfdd5cbe6ae543505eaa0da69df0735b7407294c4b0504b3e74c0e7e371f1acb914eb99fd21ff39ef5bd626b3474f064a4cccc50f41b7c556ee834f9a6d6610a + checksum: 9d5b569b75a612074b03aab20837dd1858f97d002b05fc9a2ec939aebbc8053e893960e264a1f2261bf0c426e4f8fa93c72313bcf7dface89fc09bc643147ebd languageName: node linkType: hard -"@svgr/babel-plugin-transform-react-native-svg@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:6.5.1" +"@svgr/babel-plugin-transform-react-native-svg@npm:^7.0.0": + version: 7.0.0 + resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:7.0.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 332fbf3bbc19d938b744440dbab9c8acd8f7a2ed6bf9c4e23f40e3f2c25615a60b3bf00902a4f1f6c20b5f382a1547b3acc6f2b2d70d80e532b5d45945f1b979 + checksum: 9091bd61d787e8506965f10a946dec463881b337aa435eedb0d5423ece1d0589fa643c2e01003cbb3447d3dbdf5d937ff7bae487a3098abbbe94ac04c84022d8 languageName: node linkType: hard -"@svgr/babel-plugin-transform-svg-component@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-plugin-transform-svg-component@npm:6.5.1" +"@svgr/babel-plugin-transform-svg-component@npm:^7.0.0": + version: 7.0.0 + resolution: "@svgr/babel-plugin-transform-svg-component@npm:7.0.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 8d9e1c7c62abce23837e53cdacc6d09bc1f1f2b0ad7322105001c097995e9aa8dca4fa41acf39148af69f342e40081c438106949fb083e997ca497cb0448f27d + checksum: 715c371bdae660fa9452083f2be6c1736d9ad516dc7134656c6e70374799de94eacda596504394aa6934aacb6da9099acd99569089220d66aaf91b34aa934c7b languageName: node linkType: hard -"@svgr/babel-preset@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-preset@npm:6.5.1" +"@svgr/babel-preset@npm:^7.0.0": + version: 7.0.0 + resolution: "@svgr/babel-preset@npm:7.0.0" dependencies: - "@svgr/babel-plugin-add-jsx-attribute": ^6.5.1 - "@svgr/babel-plugin-remove-jsx-attribute": "*" - "@svgr/babel-plugin-remove-jsx-empty-expression": "*" - "@svgr/babel-plugin-replace-jsx-attribute-value": ^6.5.1 - "@svgr/babel-plugin-svg-dynamic-title": ^6.5.1 - "@svgr/babel-plugin-svg-em-dimensions": ^6.5.1 - "@svgr/babel-plugin-transform-react-native-svg": ^6.5.1 - "@svgr/babel-plugin-transform-svg-component": ^6.5.1 + "@svgr/babel-plugin-add-jsx-attribute": ^7.0.0 + "@svgr/babel-plugin-remove-jsx-attribute": ^7.0.0 + "@svgr/babel-plugin-remove-jsx-empty-expression": ^7.0.0 + "@svgr/babel-plugin-replace-jsx-attribute-value": ^7.0.0 + "@svgr/babel-plugin-svg-dynamic-title": ^7.0.0 + "@svgr/babel-plugin-svg-em-dimensions": ^7.0.0 + "@svgr/babel-plugin-transform-react-native-svg": ^7.0.0 + "@svgr/babel-plugin-transform-svg-component": ^7.0.0 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 8e8d7a0049279152f9ac308fbfd4ce74063d8a376154718cba6309bae4316318804a32201c75c5839c629f8e1e5d641a87822764000998161d0fc1de24b0374a + checksum: 7d0755e2f007d4108b9ccbd7ccb2de2787ed3aa54cf873426bb211666996fe7a4fde73710a76bbdc169e1e72d7eca1dec5a6b26f14ab3124ff154ecbe387b69a languageName: node linkType: hard -"@svgr/core@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/core@npm:6.5.1" +"@svgr/core@npm:^7.0.0": + version: 7.0.0 + resolution: "@svgr/core@npm:7.0.0" dependencies: - "@babel/core": ^7.19.6 - "@svgr/babel-preset": ^6.5.1 - "@svgr/plugin-jsx": ^6.5.1 + "@babel/core": ^7.21.3 + "@svgr/babel-preset": ^7.0.0 camelcase: ^6.2.0 - cosmiconfig: ^7.0.1 - checksum: 60cce11e13391171132115dcc8da592d23e51f155ebadf9b819bd1836b8c13d40aa5c30a03a7d429f65e70a71c50669b2e10c94e4922de4e58bc898275f46c05 + cosmiconfig: ^8.1.3 + checksum: 347617081188fc0ed5de53a8643b70949c8737a1b5baf6e4a2dd23ecb8311de111d4e76f8f005959ec66e7d53a5f8155249f6b947c8111042b978fc798f53c4c languageName: node linkType: hard -"@svgr/hast-util-to-babel-ast@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/hast-util-to-babel-ast@npm:6.5.1" +"@svgr/hast-util-to-babel-ast@npm:^7.0.0": + version: 7.0.0 + resolution: "@svgr/hast-util-to-babel-ast@npm:7.0.0" dependencies: - "@babel/types": ^7.20.0 + "@babel/types": ^7.21.3 entities: ^4.4.0 - checksum: 18fa37b36581ba1678f5cc5a05ce0411e08df4db267f3cd900af7ffdf5bd90522f3a46465f315cd5d7345264949479133930aafdd27ce05c474e63756196256f + checksum: 2d6880fac9445559cc2e29f87782a52c37d2db7b99a4892f65def1e79a8239d7961c483934ff9ce2d37cb087f5b34c80ca5a51f7bc9eaceacfe0bd66e4e64373 languageName: node linkType: hard -"@svgr/plugin-jsx@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/plugin-jsx@npm:6.5.1" +"@svgr/plugin-jsx@npm:^7.0.0": + version: 7.0.0 + resolution: "@svgr/plugin-jsx@npm:7.0.0" dependencies: - "@babel/core": ^7.19.6 - "@svgr/babel-preset": ^6.5.1 - "@svgr/hast-util-to-babel-ast": ^6.5.1 + "@babel/core": ^7.21.3 + "@svgr/babel-preset": ^7.0.0 + "@svgr/hast-util-to-babel-ast": ^7.0.0 svg-parser: ^2.0.4 - peerDependencies: - "@svgr/core": ^6.0.0 - checksum: 365da6e43ceeff6b49258fa2fbb3c880210300e4a85ba74831e92d2dc9c53e6ab8dda422dc33fb6a339803227cf8d9a0024ce769401c46fd87209abe36d5ae43 + checksum: bd649a306b83bc355315265046461cfa089c81604785b081fe0ccffd0112dc8bfad1e19d8e042d85339792458ab2e9022f8bf29fdd64bfea90718a40553ce00e languageName: node linkType: hard @@ -1198,9 +1222,9 @@ __metadata: languageName: node linkType: hard -"@table-library/react-table-library@npm:4.1.2": - version: 4.1.2 - resolution: "@table-library/react-table-library@npm:4.1.2" +"@table-library/react-table-library@npm:4.1.4": + version: 4.1.4 + resolution: "@table-library/react-table-library@npm:4.1.4" dependencies: clsx: 1.1.1 react-virtualized-auto-sizer: 1.0.7 @@ -1209,7 +1233,7 @@ __metadata: "@emotion/react": ">= 11" react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 2c465d0369349f36d2114b6a397ae12588f64fd114677533f76b80569ff1401233bd26bd47ecda14375a6033fd68c76e686f4f57646b7754a2e6062265f84322 + checksum: ace2711b777e596ed5fd1623108ac8fb7403566b108725f97e6710019a11f28f1f86b916932a662ad53ad18c994ba626f0b2fd7e6be91c44dff16d54e0364f0d languageName: node linkType: hard @@ -1264,10 +1288,10 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^18.16.3": - version: 18.16.3 - resolution: "@types/node@npm:18.16.3" - checksum: 8405ceea1306790484e15f696be5e9f7f62b9bb385b2f03fcdefd07fcc2cb20052ebf5c1ffde7b81a0090a09454a48a685f22e1704ea7ead99971233e6f0d80d +"@types/node@npm:^20.0.0": + version: 20.0.0 + resolution: "@types/node@npm:20.0.0" + checksum: 34d67327d4145b406c0eff4f4b76a1d38a5ace393918381db0991e24ed0cc084450eb3341a80fca074e3eed1c4ae445e92749fd475026a16f14d2305871c280d languageName: node linkType: hard @@ -1285,12 +1309,12 @@ __metadata: languageName: node linkType: hard -"@types/react-dom@npm:^18.2.1": - version: 18.2.1 - resolution: "@types/react-dom@npm:18.2.1" +"@types/react-dom@npm:^18.2.4": + version: 18.2.4 + resolution: "@types/react-dom@npm:18.2.4" dependencies: "@types/react": "*" - checksum: 3af3c2a85f0f9fd96a03ce717559423cbd0bac3c8e279107008708d457db4b75a4ee7fd64d3af3ddad94cb97ff74dc422241edc56ab54f1905caca7fd717c759 + checksum: dfeaabb4268d39bdd5addc6c0b7099d5c57a364e70f1087b7c3ee189374312dc65201abfd3d87fee0de11d27c225678ce39c22d14b3035cde5792678704c27b5 languageName: node linkType: hard @@ -1333,7 +1357,7 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*, @types/react@npm:^18.2.0": +"@types/react@npm:*": version: 18.2.0 resolution: "@types/react@npm:18.2.0" dependencies: @@ -1344,6 +1368,17 @@ __metadata: languageName: node linkType: hard +"@types/react@npm:^18.2.5": + version: 18.2.5 + resolution: "@types/react@npm:18.2.5" + dependencies: + "@types/prop-types": "*" + "@types/scheduler": "*" + csstype: ^3.0.2 + checksum: 94aa73f74b45efdb2c790ec49ade0feb1871e8c83f3b3f81775fe8dd415ea85364f569003d48a87fe2889a610dba95f3c599a6696f1dc543115ef437314f7652 + languageName: node + linkType: hard + "@types/scheduler@npm:*": version: 0.16.3 resolution: "@types/scheduler@npm:0.16.3" @@ -1499,11 +1534,11 @@ __metadata: "@msgpack/msgpack": ^3.0.0-beta2 "@mui/icons-material": ^5.11.16 "@mui/material": ^5.12.3 - "@table-library/react-table-library": 4.1.2 + "@table-library/react-table-library": 4.1.4 "@types/lodash-es": ^4.17.7 - "@types/node": ^18.16.3 - "@types/react": ^18.2.0 - "@types/react-dom": ^18.2.1 + "@types/node": ^20.0.0 + "@types/react": ^18.2.5 + "@types/react-dom": ^18.2.4 "@types/react-router-dom": ^5.3.3 "@typescript-eslint/eslint-plugin": ^5.59.2 "@typescript-eslint/parser": ^5.59.2 @@ -1531,15 +1566,16 @@ __metadata: react-dom: latest react-dropzone: ^14.2.3 react-icons: ^4.8.0 - react-router-dom: ^6.10.0 + react-number-format: ^5.1.4 + react-router-dom: ^6.11.1 react-toastify: ^9.1.2 rollup-plugin-visualizer: ^5.9.0 sockette: ^2.0.6 terser: ^5.17.1 typesafe-i18n: ^5.24.3 typescript: ^5.0.4 - vite: ^4.3.4 - vite-plugin-svgr: ^2.4.0 + vite: ^4.3.5 + vite-plugin-svgr: ^3.2.0 vite-tsconfig-paths: ^4.2.0 languageName: unknown linkType: soft @@ -2103,7 +2139,7 @@ __metadata: languageName: node linkType: hard -"cosmiconfig@npm:^7.0.0, cosmiconfig@npm:^7.0.1": +"cosmiconfig@npm:^7.0.0": version: 7.1.0 resolution: "cosmiconfig@npm:7.1.0" dependencies: @@ -2116,6 +2152,18 @@ __metadata: languageName: node linkType: hard +"cosmiconfig@npm:^8.1.3": + version: 8.1.3 + resolution: "cosmiconfig@npm:8.1.3" + dependencies: + import-fresh: ^3.2.1 + js-yaml: ^4.1.0 + parse-json: ^5.0.0 + path-type: ^4.0.0 + checksum: 80144be230b89857e7c4cafd59ba8feb3f5f7e6dae90faa324629fdecf9a6fc3f5b4106c3623f69a1a3d77cb11ef90e5ab65a67f21d73ffda3d76b18f8e4e6c2 + languageName: node + linkType: hard + "cross-spawn@npm:^6.0.5": version: 6.0.5 resolution: "cross-spawn@npm:6.0.5" @@ -4648,7 +4696,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.6.2, prop-types@npm:^15.8.1": +"prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -4735,27 +4783,39 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:^6.10.0": - version: 6.10.0 - resolution: "react-router-dom@npm:6.10.0" +"react-number-format@npm:^5.1.4": + version: 5.1.4 + resolution: "react-number-format@npm:5.1.4" dependencies: - "@remix-run/router": 1.5.0 - react-router: 6.10.0 + prop-types: ^15.7.2 peerDependencies: - react: ">=16.8" - react-dom: ">=16.8" - checksum: c62e4b4aac2067e64af632e113b8db0626b846ec88fb668e84492ceb04179a0789120844263e35dead1e13f70f6554c1a463c7c33d8feed579bf5a2a02f916f4 + react: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 121bae7eda40e3b81fa47f29974d6af2b1809c29ee287a9722497d139ba6ffdf3dab006c317961e31c7635991a3a20277b13f8d7df7ce53e229c5243b0507350 languageName: node linkType: hard -"react-router@npm:6.10.0": - version: 6.10.0 - resolution: "react-router@npm:6.10.0" +"react-router-dom@npm:^6.11.1": + version: 6.11.1 + resolution: "react-router-dom@npm:6.11.1" dependencies: - "@remix-run/router": 1.5.0 + "@remix-run/router": 1.6.1 + react-router: 6.11.1 peerDependencies: react: ">=16.8" - checksum: 83b8d6a7d1572403b9227045148b34253d3e705dd196d5beff9be2e3c044ba1136100a6e45c219c109de7c764d548302242ab029b0df4c5536b8aaddb3b1aa57 + react-dom: ">=16.8" + checksum: 17f1f9c3d71604fb9e270b672a97accbee0bedf5b8cb03518fd20068aa4d653fcbb46c2dfd5cd04a41864bed21cc23c275d7af39e4be2224435529779bf7e7e4 + languageName: node + linkType: hard + +"react-router@npm:6.11.1": + version: 6.11.1 + resolution: "react-router@npm:6.11.1" + dependencies: + "@remix-run/router": 1.6.1 + peerDependencies: + react: ">=16.8" + checksum: 3b49692947ef2ddae134a15462e4fa47022d82c358cc90085b471989e4bc3c4e1637aa3a81389166b69db557ac420c289f64d64309f4d0bd87a6099e49aee6e1 languageName: node linkType: hard @@ -5708,15 +5768,16 @@ __metadata: languageName: node linkType: hard -"vite-plugin-svgr@npm:^2.4.0": - version: 2.4.0 - resolution: "vite-plugin-svgr@npm:2.4.0" +"vite-plugin-svgr@npm:^3.2.0": + version: 3.2.0 + resolution: "vite-plugin-svgr@npm:3.2.0" dependencies: "@rollup/pluginutils": ^5.0.2 - "@svgr/core": ^6.5.1 + "@svgr/core": ^7.0.0 + "@svgr/plugin-jsx": ^7.0.0 peerDependencies: vite: ^2.6.0 || 3 || 4 - checksum: df5cff70a7e9fff10a789575ebecf05d2da9fa0e7352b38477fa616e2d91f254ba5331ea185fe47ed3f0549b822dcb1f7cbec32a9f3cf5d8dfd430dcf76358a2 + checksum: f801759810be82e997acb26b6b0f8c6dc012d7bcb4d430e1e75ef210f6f05580c589b7f65c9729fe4993fa919433903b71a74ddfc490e41af69720cf857de9d9 languageName: node linkType: hard @@ -5736,9 +5797,9 @@ __metadata: languageName: node linkType: hard -"vite@npm:^4.3.4": - version: 4.3.4 - resolution: "vite@npm:4.3.4" +"vite@npm:^4.3.5": + version: 4.3.5 + resolution: "vite@npm:4.3.5" dependencies: esbuild: ^0.17.5 fsevents: ~2.3.2 @@ -5769,7 +5830,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 05cbff29d9379a4441845a2b6c6c6770a35a858408f3e17f56c86e01d88dc7eda60f41aff7b15b99382b64c198b892a76f9a16505a62a2b0d53ec38e454d5948 + checksum: 56c5de4c6a4cb383492302173372be71c4e55a9eabc7268b6036152880800b7a85ee19be3d4c9f2cdd7af33df53c0153c58e6db402b08e8f6299a7b15fd53b88 languageName: node linkType: hard From ca964c13a7ba3412868896db6fb078278cea5427 Mon Sep 17 00:00:00 2001 From: Proddy Date: Fri, 5 May 2023 20:37:48 +0200 Subject: [PATCH 77/89] fix row unselect --- interface/src/project/DashboardDevices.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/project/DashboardDevices.tsx b/interface/src/project/DashboardDevices.tsx index 182f5560e..daa1a727a 100644 --- a/interface/src/project/DashboardDevices.tsx +++ b/interface/src/project/DashboardDevices.tsx @@ -546,7 +546,7 @@ const DashboardDevices: FC = () => {