diff --git a/.github/workflows/test-and-release.yml b/.github/workflows/test-and-release.yml index e8bb987da..66b6b52b8 100644 --- a/.github/workflows/test-and-release.yml +++ b/.github/workflows/test-and-release.yml @@ -120,7 +120,7 @@ jobs: # if: failure() uses: actions/upload-artifact@v4 with: - path: tmp/screenshots/**/*.png + path: packages/admin/tmp/screenshots/**/*.png retention-days: 3 # Deploys the final package to NPM diff --git a/package.json b/package.json index e8e6a77cf..2a80938c1 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,9 @@ "@esbuild-plugins/node-globals-polyfill": "^0.2.3", "@fnando/sparkline": "^0.3.10", "@honkhonk/vite-plugin-svgr": "^1.1.0", - "@iobroker/adapter-react-v5": "^4.13.23", + "@iobroker/adapter-react-v5": "^5.0.1", "@iobroker/admin-component-easy-access": "^0.3.2", - "@iobroker/dm-utils": "^0.1.9", + "@iobroker/dm-utils": "^0.2.0", "@iobroker/legacy-testing": "^1.0.12", "@iobroker/socket-client": "^2.4.13", "@iobroker/testing": "^4.1.3", @@ -73,7 +73,7 @@ "date-fns": "^2.30.0", "echarts": "^5.5.0", "echarts-for-react": "^3.0.2", - "eslint": "^9.3.0", + "eslint": "^8.56.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-prettier": "^9.1.0", "eslint-config-react-app": "^7.0.1", diff --git a/packages/admin/src-backend/main.js b/packages/admin/src-backend/main.js index c85eb7108..ebbc93005 100644 --- a/packages/admin/src-backend/main.js +++ b/packages/admin/src-backend/main.js @@ -1270,7 +1270,7 @@ class Admin extends utils.Adapter { for (const _adapter of adapters) { if (repository[_adapter].blockedVersions) { - // read current version + // read a current version if (Array.isArray(repository[_adapter].blockedVersions)) { const instance = instances.rows.find( item => item.value?.common.name === _adapter && item.value.common.enabled @@ -1344,34 +1344,46 @@ class Admin extends utils.Adapter { } } + restartRepoUpdate() { + // start the next cycle + if (this.config.autoUpdate) { + this.timerRepo && clearTimeout(this.timerRepo); + this.log.debug( + `Next repo update on ${new Date( + Date.now() + this.config.autoUpdate * ONE_HOUR_MS + 1 + ).toLocaleString()}` + ); + this.timerRepo = setTimeout( + () => { + this.timerRepo = null; + this.updateRegister(); + }, + this.config.autoUpdate * ONE_HOUR_MS + 1 + ); + } + } + /** * Read repository information from active repository */ updateRegister() { if (lastRepoUpdate && Date.now() - lastRepoUpdate < 3600000) { this.log.error('Automatic repository update is not allowed more than once a hour'); - if (this.config.autoUpdate) { - this.timerRepo && clearTimeout(this.timerRepo); - this.timerRepo = setTimeout( - () => { - this.timerRepo = null; - this.updateRegister(); - }, - this.config.autoUpdate * ONE_HOUR_MS + 1 - ); - } + this.restartRepoUpdate(); return; } lastRepoUpdate = Date.now(); - this.getForeignObject('system.config', (err, systemConfig) => { + this.getForeignObject('system.config', async (err, systemConfig) => { err && this.log.error('May not read "system.config"'); - if (systemConfig && systemConfig.common) { - this.getForeignObject('system.repositories', (err, repos) => { - err && this.log.error('May not read "system.repositories"'); + if (systemConfig?.common) { + try { + const repos = await this.getForeignObjectAsync('system.repositories'); if (!repos || repos.ts === undefined) { + // start the next cycle + this.restartRepoUpdate(); return; } @@ -1380,16 +1392,32 @@ class Admin extends utils.Adapter { const active = systemConfig.common.activeRepo; // if repo is valid and actual - if ( - !err && - repos?.native?.repositories?.[active] && - Date.now() < repos.ts + this.config.autoUpdate * ONE_HOUR_MS - ) { - exists = true; + if (Array.isArray(active)) { + if (!err && + Date.now() < repos.ts + this.config.autoUpdate * ONE_HOUR_MS && + !active.find(repo => !repos?.native?.repositories?.[repo]?.json) + ) { + exists = true; + } + } else { + if (!err && + repos?.native?.repositories?.[active]?.json && + Date.now() < repos.ts + this.config.autoUpdate * ONE_HOUR_MS + ) { + exists = true; + } } - if (!exists) { this.log.info('Request actual repository...'); + // first check if the host is running + const aliveState = await this.getForeignStateAsync(`system.host.${this.host}.alive`); + if (!aliveState || !aliveState.val) { + this.log.error('Host is not alive'); + // start the next cycle + this.restartRepoUpdate(); + return; + } + // request repo from host this.sendToHost( this.host, @@ -1406,25 +1434,12 @@ class Admin extends utils.Adapter { this.log.info('Repository received successfully.'); socket && socket.repoUpdated(); - this.checkRevokedVersions(_repository).then(() => {}); + this.checkRevokedVersions(_repository) + .catch(e => this.log.error(`Cannot check revoked versions: ${e}`)); } // start the next cycle - if (this.config.autoUpdate) { - this.timerRepo && clearTimeout(this.timerRepo); - this.log.debug( - `Next repo update on ${new Date( - Date.now() + this.config.autoUpdate * ONE_HOUR_MS + 1 - ).toLocaleString()}` - ); - this.timerRepo = setTimeout( - () => { - this.timerRepo = null; - this.updateRegister(); - }, - this.config.autoUpdate * ONE_HOUR_MS + 1 - ); - } + this.restartRepoUpdate(); } ); } else if (this.config.autoUpdate) { @@ -1439,7 +1454,9 @@ class Admin extends utils.Adapter { this.updateRegister(); }, interval); } - }); + } catch (err) { + err && this.log.error(`May not read "system.repositories": ${err}`); + } } }); } @@ -1482,15 +1499,16 @@ class Admin extends utils.Adapter { } // check info.connected - this.getObjectAsync('info.connected').then(obj => { - if (!obj) { - const packageJson = JSON.parse(fs.readFileSync(`${__dirname}/../io-package.json`).toString('utf8')); - const obj = packageJson.instanceObjects.find(o => o._id === 'info.connected'); - if (obj) { - return this.setObjectAsync(obj._id, obj); + this.getObjectAsync('info.connected') + .then(obj => { + if (!obj) { + const packageJson = JSON.parse(fs.readFileSync(`${__dirname}/../io-package.json`).toString('utf8')); + const obj = packageJson.instanceObjects.find(o => o._id === 'info.connected'); + if (obj) { + return this.setObjectAsync(obj._id, obj); + } } - } - }); + }); this.config.autoUpdate && this.updateRegister(); diff --git a/packages/admin/src/.eslintrc.js b/packages/admin/src/.eslintrc.js index a1b8a0269..85d5bd749 100644 --- a/packages/admin/src/.eslintrc.js +++ b/packages/admin/src/.eslintrc.js @@ -94,5 +94,7 @@ module.exports = { requireLast: false, }, }], + '@typescript-eslint/type-annotation-spacing': 'error', + '@typescript-eslint/consistent-type-imports': 'error', }, }; diff --git a/packages/admin/src/package.json b/packages/admin/src/package.json index 8acf0012a..13e58a654 100644 --- a/packages/admin/src/package.json +++ b/packages/admin/src/package.json @@ -21,7 +21,8 @@ "not op_mini all" ], "dependencies": { - "@iobroker/json-config": "file:../../jsonConfig" + "@iobroker/json-config": "file:../../jsonConfig", + "@iobroker/dm-gui-components": "file:../../dm-gui-components" }, "proxy": "http://127.0.0.1:8081", "plugins": [ diff --git a/packages/admin/src/src/App.jsx b/packages/admin/src/src/App.jsx index a9a8eeac3..0962668b0 100644 --- a/packages/admin/src/src/App.jsx +++ b/packages/admin/src/src/App.jsx @@ -1564,7 +1564,7 @@ class App extends Router { /** * Get a theme * @param {string} name Theme name - * @returns {Theme} + * @returns {IobTheme} */ static createTheme(name) { return Theme(Utils.getThemeName(name)); diff --git a/packages/admin/src/src/Utils.tsx b/packages/admin/src/src/Utils.tsx index 051ae4c6f..0bda1310b 100644 --- a/packages/admin/src/src/Utils.tsx +++ b/packages/admin/src/src/Utils.tsx @@ -1,5 +1,5 @@ -import { I18n } from '@iobroker/adapter-react-v5'; import semver from 'semver'; +import { type Translate } from '@iobroker/adapter-react-v5'; const ANSI_RESET = 0; const ANSI_RESET_COLOR = 39; @@ -147,7 +147,7 @@ class Utils { * @param seconds * @param t i18n.t function */ - static formatSeconds(seconds: number, t: typeof I18n.t): string { + static formatSeconds(seconds: number, t: Translate): string { const days = Math.floor(seconds / (3600 * 24)); let minutesRes: string; let secondsRes: string; @@ -667,4 +667,11 @@ class Utils { } } +declare module '@mui/material/Button' { + interface ButtonPropsColorOverrides { + grey: true; + gray: true; + } +} + export default Utils; diff --git a/packages/admin/src/src/components/Adapters/AdapterRow.tsx b/packages/admin/src/src/components/Adapters/AdapterRow.tsx index a5f4acf18..40d616fa7 100644 --- a/packages/admin/src/src/components/Adapters/AdapterRow.tsx +++ b/packages/admin/src/src/components/Adapters/AdapterRow.tsx @@ -2,8 +2,6 @@ import React, { Component } from 'react'; import { type Styles, withStyles } from '@mui/styles'; -import { i18n, Utils } from '@iobroker/adapter-react-v5'; - import { Avatar, CardMedia, @@ -14,7 +12,6 @@ import { Tooltip, Typography, Rating, - type Theme, Link, } from '@mui/material'; @@ -44,11 +41,13 @@ import { MonetizationOn, } from '@mui/icons-material'; +import { type Translate, type IobTheme, Utils } from '@iobroker/adapter-react-v5'; + import IsVisible from '../IsVisible'; import MaterialDynamicIcon from '../../helpers/MaterialDynamicIcon'; import sentryIcon from '../../assets/sentry.svg'; -const styles = (theme: Theme) => ({ +const styles = (theme: IobTheme) => ({ smallAvatar: { width: theme.spacing(4), height: theme.spacing(4), @@ -188,7 +187,7 @@ interface AdapterRowProps { connectionType: string; openInstallVersionDialog: () => void; dataSource: string; - t: typeof i18n.t; + t: Translate; installedFrom: string; sentry: boolean; allowAdapterInstall: boolean; diff --git a/packages/admin/src/src/components/Adapters/AdapterTile.tsx b/packages/admin/src/src/components/Adapters/AdapterTile.tsx index 41427ea7c..2049e5f70 100644 --- a/packages/admin/src/src/components/Adapters/AdapterTile.tsx +++ b/packages/admin/src/src/components/Adapters/AdapterTile.tsx @@ -24,7 +24,7 @@ import { } from '@mui/icons-material'; import { amber } from '@mui/material/colors'; -import { i18n, Utils } from '@iobroker/adapter-react-v5'; +import { type Translate, Utils } from '@iobroker/adapter-react-v5'; import Link from '@mui/material/Link'; import sentryIcon from '../../assets/sentry.svg'; @@ -282,7 +282,7 @@ interface AdapterTileProps { connectionType: string; openInstallVersionDialog: () => void; dataSource: string; - t: typeof i18n.t; + t: Translate; installedFrom: string; sentry: boolean; allowAdapterInstall: boolean; diff --git a/packages/admin/src/src/components/Adapters/AdaptersUpdater.tsx b/packages/admin/src/src/components/Adapters/AdaptersUpdater.tsx index fda514afc..a8b2f7199 100644 --- a/packages/admin/src/src/components/Adapters/AdaptersUpdater.tsx +++ b/packages/admin/src/src/components/Adapters/AdaptersUpdater.tsx @@ -388,7 +388,6 @@ class AdaptersUpdater extends Component this.setState({ showNews: null })} - // @ts-expect-error this is fine color="grey" startIcon={} > diff --git a/packages/admin/src/src/components/BaseSettings/BaseSettingsLog.tsx b/packages/admin/src/src/components/BaseSettings/BaseSettingsLog.tsx index 98e032e6a..b50e113d4 100644 --- a/packages/admin/src/src/components/BaseSettings/BaseSettingsLog.tsx +++ b/packages/admin/src/src/components/BaseSettings/BaseSettingsLog.tsx @@ -19,7 +19,6 @@ import { AccordionSummary, Typography, Fab, - type Theme, } from '@mui/material'; // Icons @@ -32,10 +31,10 @@ import { Computer as IconSyslog, Send as IconStream, } from '@mui/icons-material'; -import { withWidth } from '@iobroker/adapter-react-v5'; +import { withWidth, type IobTheme } from '@iobroker/adapter-react-v5'; import IconSeq from '../../assets/seq.png'; -const styles: Styles = (theme: Theme) => ({ +const styles: Styles = (theme: IobTheme) => ({ paper: { height: '100%', maxHeight: '100%', @@ -96,7 +95,7 @@ interface TransportSettings { ssl?: boolean; } -interface SettingsLog { +export interface SettingsLog { transport?: Record; level?: string; maxDays?: number; @@ -919,7 +918,6 @@ class BaseSettingsLog extends Component } : null} {this.state.editing ? diff --git a/packages/admin/src/src/components/Instances/InstanceRow.tsx b/packages/admin/src/src/components/Instances/InstanceRow.tsx index 8da05b5c9..c9ffc13fe 100644 --- a/packages/admin/src/src/components/Instances/InstanceRow.tsx +++ b/packages/admin/src/src/components/Instances/InstanceRow.tsx @@ -5,7 +5,6 @@ import { Accordion, AccordionDetails, AccordionSummary, Avatar, Grid, Hidden, Tooltip, Typography, - type Theme, } from '@mui/material'; import { @@ -14,6 +13,7 @@ import { import { Utils, + type IobTheme, } from '@iobroker/adapter-react-v5'; import BasicUtils from '@/Utils'; @@ -25,7 +25,7 @@ import InstanceGeneric, { style as genericStyles, } from './InstanceGeneric'; -const styles: Record = (theme: Theme) => ({ +const styles: Record = (theme: IobTheme) => ({ ...genericStyles(theme), row: { paddingLeft: 8, diff --git a/packages/admin/src/src/components/Instances/LinksDialog.tsx b/packages/admin/src/src/components/Instances/LinksDialog.tsx index 12404aca5..fb0341761 100644 --- a/packages/admin/src/src/components/Instances/LinksDialog.tsx +++ b/packages/admin/src/src/components/Instances/LinksDialog.tsx @@ -11,8 +11,7 @@ import { Avatar, } from '@mui/material'; -import { I18n } from '@iobroker/adapter-react-v5'; -import type { ThemeType } from '@iobroker/adapter-react-v5/types'; +import { I18n, type ThemeType } from '@iobroker/adapter-react-v5'; import Utils from '../Utils'; import BasicUtils from '../../Utils'; diff --git a/packages/admin/src/src/components/IsVisible.tsx b/packages/admin/src/src/components/IsVisible.tsx index b75ebdc45..866213982 100644 --- a/packages/admin/src/src/components/IsVisible.tsx +++ b/packages/admin/src/src/components/IsVisible.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import type React from 'react'; function getAttr(obj: Record, attr: string | string[]): boolean { if (!obj) { diff --git a/packages/admin/src/src/components/MDUtils.jsx b/packages/admin/src/src/components/MDUtils.jsx index 6bdb3d1d8..4f21e664f 100644 --- a/packages/admin/src/src/components/MDUtils.jsx +++ b/packages/admin/src/src/components/MDUtils.jsx @@ -116,77 +116,77 @@ class MDUtils { // ignore it } else //

ioBroker.linkeddevices

- if (line.match(/^

.+<\/h1>/)) { + if (line.match(/^

.+<\/h1>/)) { // skip - } else - if (line.match(/^# /)) { - const cont = MDUtils.findTitle(line, -1, path); - title = cont.title; - } else - if (line.trim().startsWith('|')) { - if (!parts[last] || parts[last].type !== 'table') { - parts.push({ type: 'table', lines: [line] }); - } else { - parts[last].lines.push(line); - } - } else - if (line.match(/^##+ /)) { - parts.push({ lines: [line], type: 'chapter' }); - last++; - let level = line.split('#').length - 3; - const cont = MDUtils.findTitle(line, level, path); - content[cont.href] = cont; - current[level] = cont; - level++; - while (current[level] !== undefined) level = null; - } else - if (line.startsWith('@@@')) { - line = line.substring(3).trim(); - parts.push({ lines: [line], type: '@@@' }); - last++; - if (line.trim().endsWith('@@@')) { - parts[last].lines[0] = line.substring(0, line.length - 3); - } else { - while (i + 1 < lines.length && !lines[i + 1].trim().endsWith('@@@')) { - parts[last].lines.push(lines[i + 1].trim()); - i++; - } - } - } else if (line.trim().startsWith('```')) { - parts.push({ lines: [line], type: 'code' }); - last++; - if (!line.substring(3).trim().endsWith('```')) { - while (i + 1 < lines.length && !lines[i + 1].trim().endsWith('```')) { - parts[last].lines.push(lines[i + 1]); - i++; - } - parts[last].lines.push(lines[i + 1]); - i++; - } - } else if (line.startsWith('?> ') || line.startsWith('!> ')) { - parts.push({ lines: [line.substring(3)], type: line.startsWith('?>') ? 'warn' : 'alarm' }); - last++; - while (i + 1 < lines.length && lines[i + 1].trim()) { - parts[last].lines.push(lines[i + 1]); - i++; - } - } else if (line.startsWith('> ')) { - parts.push({ lines: [line.substring(2)], type: 'notice' }); - last++; - while (i + 1 < lines.length && lines[i + 1].trim()) { - parts[last].lines.push(lines[i + 1]); - i++; - } - } else if (line.trim()) { - parts.push({ lines: [line], type: 'p' }); - last++; - while (i + 1 < lines.length && // lines[i + 1].trim() && + } else + if (line.match(/^# /)) { + const cont = MDUtils.findTitle(line, -1, path); + title = cont.title; + } else + if (line.trim().startsWith('|')) { + if (!parts[last] || parts[last].type !== 'table') { + parts.push({ type: 'table', lines: [line] }); + } else { + parts[last].lines.push(line); + } + } else + if (line.match(/^##+ /)) { + parts.push({ lines: [line], type: 'chapter' }); + last++; + let level = line.split('#').length - 3; + const cont = MDUtils.findTitle(line, level, path); + content[cont.href] = cont; + current[level] = cont; + level++; + while (current[level] !== undefined) level = null; + } else + if (line.startsWith('@@@')) { + line = line.substring(3).trim(); + parts.push({ lines: [line], type: '@@@' }); + last++; + if (line.trim().endsWith('@@@')) { + parts[last].lines[0] = line.substring(0, line.length - 3); + } else { + while (i + 1 < lines.length && !lines[i + 1].trim().endsWith('@@@')) { + parts[last].lines.push(lines[i + 1].trim()); + i++; + } + } + } else if (line.trim().startsWith('```')) { + parts.push({ lines: [line], type: 'code' }); + last++; + if (!line.substring(3).trim().endsWith('```')) { + while (i + 1 < lines.length && !lines[i + 1].trim().endsWith('```')) { + parts[last].lines.push(lines[i + 1]); + i++; + } + parts[last].lines.push(lines[i + 1]); + i++; + } + } else if (line.startsWith('?> ') || line.startsWith('!> ')) { + parts.push({ lines: [line.substring(3)], type: line.startsWith('?>') ? 'warn' : 'alarm' }); + last++; + while (i + 1 < lines.length && lines[i + 1].trim()) { + parts[last].lines.push(lines[i + 1]); + i++; + } + } else if (line.startsWith('> ')) { + parts.push({ lines: [line.substring(2)], type: 'notice' }); + last++; + while (i + 1 < lines.length && lines[i + 1].trim()) { + parts[last].lines.push(lines[i + 1]); + i++; + } + } else if (line.trim()) { + parts.push({ lines: [line], type: 'p' }); + last++; + while (i + 1 < lines.length && // lines[i + 1].trim() && //! lines[i + 1].trim().match(/^>\s|^\?>\s|^!>\s|^@@@|^#+|^====|^\|/)) { !lines[i + 1].trim().match(/^```|^>\s|^\?>\s|^!>\s|^@@@|^#+|^====|^\|/)) { - parts[last].lines.push(lines[i + 1].trimRight()); - i++; - } - } + parts[last].lines.push(lines[i + 1].trimRight()); + i++; + } + } } return { diff --git a/packages/admin/src/src/components/Markdown.jsx b/packages/admin/src/src/components/Markdown.jsx index d0092f2a3..91268990c 100644 --- a/packages/admin/src/src/components/Markdown.jsx +++ b/packages/admin/src/src/components/Markdown.jsx @@ -521,10 +521,10 @@ class Markdown extends Component { this.parseText()); } } else - if (this.props.language !== nextProps.language) { - this.mounted && this.setState({ notFound: false, parts: [] }); - this.load(null, nextProps.language); - } + if (this.props.language !== nextProps.language) { + this.mounted && this.setState({ notFound: false, parts: [] }); + this.load(null, nextProps.language); + } } /* onHashChange(location) { diff --git a/packages/admin/src/src/components/Object/ObjectBrowserEditObject.tsx b/packages/admin/src/src/components/Object/ObjectBrowserEditObject.tsx index 9c48bb782..bbd407f3a 100644 --- a/packages/admin/src/src/components/Object/ObjectBrowserEditObject.tsx +++ b/packages/admin/src/src/components/Object/ObjectBrowserEditObject.tsx @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import { type Styles, withStyles } from '@mui/styles'; +import { withStyles } from '@mui/styles'; import { Dialog, @@ -33,12 +33,14 @@ import { Utils, I18n, SelectID as DialogSelectID, IconFx, - UploadImage, AdminConnection, i18n, + UploadImage, + type AdminConnection, + type Translate, type IobTheme, } from '@iobroker/adapter-react-v5'; import Editor from '../Editor'; -const styles = (theme: Record) => ({ +const styles: Record = (theme: IobTheme) => ({ divWithoutTitle: { width: '100%', height: '100%', @@ -173,7 +175,7 @@ const styles = (theme: Record) => ({ fontStyle: 'italic', fontSize: 'smaller', }, -}) satisfies Styles; +}); const DEFAULT_ROLES = [ 'button', @@ -432,7 +434,7 @@ const DEFAULT_ROLES = [ ] as const; interface ObjectBrowserEditObjectProps { - classes: Record; + classes: Record; socket: AdminConnection; obj: ioBroker.AnyObject; roleArray: string[]; @@ -441,11 +443,11 @@ interface ObjectBrowserEditObjectProps { aliasTab: boolean; onClose: (obj?: ioBroker.AnyObject) => void; dialogName: string; - objects: Record; + objects: Record; dateFormat: string; isFloatComma: boolean; onNewObject: (obj: ioBroker.AnyObject) => void; - t: typeof i18n.t; + t: Translate; } interface ObjectBrowserEditObjectState { @@ -552,9 +554,9 @@ class ObjectBrowserEditObject extends Component) { + onCopy(e: React.MouseEvent) { // @ts-expect-error types in adapter-react-v5 not optimal Utils.copyToClipboard(this.state.text, e); window.alert(this.props.t('ra_Copied')); @@ -1325,7 +1327,6 @@ class ObjectBrowserEditObject extends Component - {/* - // @ts-expect-error this color works */} ; diff --git a/packages/admin/src/src/components/ObjectBrowser.tsx b/packages/admin/src/src/components/ObjectBrowser.tsx index cac7f1323..975dc5e43 100644 --- a/packages/admin/src/src/components/ObjectBrowser.tsx +++ b/packages/admin/src/src/components/ObjectBrowser.tsx @@ -38,7 +38,6 @@ import { Snackbar, Switch, TextField, - type Theme, Tooltip, } from '@mui/material'; @@ -97,12 +96,11 @@ import { IconState, withWidth, Connection, - Router, + type Router, + type IobTheme, + type ThemeType, + type ThemeName, } from '@iobroker/adapter-react-v5'; -import type { - ThemeType, - ThemeName, -} from '@iobroker/adapter-react-v5/types'; // own import Utils from './Utils'; // @iobroker/adapter-react-v5/Components/Utils import TabContainer from './TabContainer'; @@ -277,7 +275,7 @@ interface GetValueStyleOptions { isButton?: boolean; } -const styles: Record = (theme: Theme) => ({ +const styles: Record = (theme: IobTheme) => ({ toolbar: { minHeight: 38, // Theme.toolbar.height, // boxShadow: '0px 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12)' @@ -2219,7 +2217,7 @@ interface ObjectBrowserProps { themeType: ThemeType; /** will be filled by withWidth */ width?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'; - theme: Theme; + theme: IobTheme; t: (word: string, ...args: any[]) => string; lang: ioBroker.Languages; multiSelect?: boolean; @@ -4193,7 +4191,6 @@ class ObjectBrowser extends Component { {this.state.filter.expertMode || this.state.showAllExportOptions ? : : null} + + + ; + } + + renderInputDialog() { + if (!this.state.showInput || !this.state.showInput.inputBefore) { + return null; + } + let okDisabled = false; + if (!this.state.showInput.inputBefore.allowEmptyValue && this.state.showInput.inputBefore.type !== 'checkbox') { + if (this.state.showInput.inputBefore.type === 'number' || this.state.showInput.inputBefore.type === 'slider') { + okDisabled = this.state.inputValue === '' || this.state.inputValue === null || !window.isFinite(this.state.inputValue as number); + } else { + okDisabled = !this.state.inputValue; + } + } + + return this.setState({ showInput: null })} + > + {getTranslation('pleaseEnterValueText')} + + {this.state.showInput.inputBefore.type === 'text' || this.state.showInput.inputBefore.type === 'number' || !this.state.showInput.inputBefore.type ? this.setState({ inputValue: e.target.value })} + InputProps={{ + endAdornment: this.state.inputValue ? + this.setState({ inputValue: '' })} + > + + + : null, + }} + /> : null} + {this.state.showInput.inputBefore.type === 'checkbox' ? this.setState({ inputValue: e.target.checked })} + />} + label={getTranslation(this.state.showInput.inputBefore.label)} + /> : null} + {this.state.showInput.inputBefore.type === 'select' ? + {getTranslation(this.state.showInput.inputBefore.label)} + + : null} + {this.state.showInput.inputBefore.type === 'slider' ? + + {getTranslation(this.state.showInput.inputBefore.label)} + + + + this.setState({ inputValue: newValue })} + /> + + + this.setState({ inputValue: e.target.value === '' ? 0 : Number(e.target.value) })} + onBlur={() => { + const min = this.state.showInput.inputBefore.min === undefined ? 0 : this.state.showInput.inputBefore.min; + const max = this.state.showInput.inputBefore.max === undefined ? 100 : this.state.showInput.inputBefore.max; + + if ((this.state.inputValue as number) < min) { + this.setState({ inputValue: min }); + } else if ((this.state.inputValue as number) > max) { + this.setState({ inputValue: max }); + } + }} + inputProps={{ + step: this.state.showInput.inputBefore.step, + min: this.state.showInput.inputBefore.min === undefined ? 0 : this.state.showInput.inputBefore.min, + max: this.state.showInput.inputBefore.max === undefined ? 100 : this.state.showInput.inputBefore.max, + type: 'number', + }} + /> + + + : null} + + + + + + + } + render() { return <> {this.renderSnackbar()} @@ -436,6 +623,8 @@ class Communication

{this.renderMessageDialog()} {this.renderFormDialog()} {this.renderProgressDialog()} + {this.renderConfirmationDialog()} + {this.renderInputDialog()} ; } } diff --git a/packages/dm-gui-components/src/DeviceActionButton.tsx b/packages/dm-gui-components/src/DeviceActionButton.tsx index cb5668b6d..ab1ff76e0 100644 --- a/packages/dm-gui-components/src/DeviceActionButton.tsx +++ b/packages/dm-gui-components/src/DeviceActionButton.tsx @@ -1,28 +1,32 @@ import React from 'react'; +import type { ActionBase, DeviceAction } from '@iobroker/dm-utils/build/types/api'; import TooltipButton from './TooltipButton'; -import { renderIcon, getTranslation } from './Utils'; -import type { ActionBase } from '@iobroker/dm-utils/build/types/base'; +import { renderActionIcon, getTranslation } from './Utils'; interface DeviceActionButtonProps { deviceId: string; - action: any; + action: DeviceAction; refresh: () => void; - deviceHandler: (deviceId: string, action: ActionBase<'api'>, refresh: () => void) => () => void; + deviceHandler: (deviceId: string, action: ActionBase, refresh: () => void) => () => void; disabled?: boolean; } export default function DeviceActionButton(props: DeviceActionButtonProps): React.JSX.Element { const { - deviceId, action, refresh, deviceHandler, disabled, + deviceId, + action, + refresh, + deviceHandler, + disabled, } = props; - const tooltip = getTranslation(action.description); + const icon = renderActionIcon(action); - const icon = renderIcon(action); + const tooltip = getTranslation(action.description) || (icon ? null : action.id); return +const NoImageIcon = (props: { style?: React.CSSProperties, className?: string }) => , refresh: () => void) => () => void; + deviceHandler: (deviceId: string, action: ActionBase, refresh: () => void) => () => void; controlHandler: (deviceId: string, control: ControlBase, state: ControlState) => () => Promise; controlStateHandler: (deviceId: string, control: ControlBase) => () => Promise; smallCards?: boolean; @@ -136,9 +146,8 @@ class DeviceCard extends Component { /** * Copy the device ID to the clipboard - * @returns {void} */ - copyToClipboard = async () => { + copyToClipboard = (): void => { const textToCopy = this.props.device.id; Utils.copyToClipboard(textToCopy); alert(`${getTranslation('copied')} ${textToCopy} ${getTranslation('toClipboard')}!`); @@ -223,7 +232,10 @@ class DeviceCard extends Component { renderControls() { const colors = { primary: '#111', secondary: '#888' }; const firstControl = this.props.device.controls?.[0]; - if (this.props.device.controls?.length === 1 && firstControl && ((firstControl.type === 'icon' || firstControl.type === 'switch') && !firstControl.label)) { + if (this.props.device.controls?.length === 1 && + firstControl && + ((firstControl.type === 'icon' || firstControl.type === 'switch') && !firstControl.label) + ) { // control can be placed in button icon return { width: 'calc(100% - 46px)', }} > - {status.map((s, i) => )} + {status.map((s, i) => )} : null}

@@ -454,7 +466,7 @@ class DeviceCard extends Component { : null}
- {status.map((s, i) => )} + {status.map((s, i) => )}
diff --git a/packages/dm-gui-components/src/DeviceControl.tsx b/packages/dm-gui-components/src/DeviceControl.tsx index 45482e9fa..4606a8fd0 100644 --- a/packages/dm-gui-components/src/DeviceControl.tsx +++ b/packages/dm-gui-components/src/DeviceControl.tsx @@ -3,35 +3,35 @@ import { Button, Fab, Switch, } from '@mui/material'; -import { renderIcon, getTranslation } from './Utils'; + +import { Connection } from '@iobroker/adapter-react-v5'; import type { ControlBase, ControlState } from '@iobroker/dm-utils/build/types/base'; +import type { DeviceControl } from '@iobroker/dm-utils/build/types/api'; + +import { renderControlIcon, getTranslation } from './Utils'; interface DeviceControlProps { deviceId: string; - control: any; - socket: any; + /** Control object */ + control: DeviceControl; + socket: Connection; + /** Control handler to set the state */ controlHandler: (deviceId: string, control: ControlBase, state: ControlState) => () => Promise; + /** Control handler to read the state */ controlStateHandler: (deviceId: string, control: ControlBase) => () => Promise; - colors: any; + colors: { primary: string; secondary: string }; disabled?: boolean; } interface DeviceControlState { - value: any; + value: ControlState; ts: number; } /** * Device Control component - * @param {object} props - Parameters - * @param {object} props.control - Control object - * @param {object} props.socket - Socket object - * @param {object} props.controlHandler - Control handler to set the state - * @param {object} props.controlStateHandler - Control handler to read the state - * @returns {React.JSX.Element|null} - * @constructor */ -export default class DeviceControl extends Component { +export default class DeviceControlComponent extends Component { constructor(props: DeviceControlProps) { super(props); this.state = { @@ -88,10 +88,11 @@ export default class DeviceControl extends Component this.sendControl(this.props.deviceId, this.props.control, true)} @@ -105,7 +106,7 @@ export default class DeviceControl extends Component this.sendControl(this.props.deviceId, this.props.control, true)} startIcon={icon} > - {this.props.control.label} + {getTranslation(this.props.control.label)} ; } @@ -116,7 +117,7 @@ export default class DeviceControl extends Component this.sendControl(this.props.deviceId, this.props.control, e.target.checked)} />; } @@ -151,16 +152,22 @@ export default class DeviceControl extends Component this.sendControl(this.props.deviceId, this.props.control, !this.state.value)} > {icon} @@ -169,12 +176,12 @@ export default class DeviceControl extends Component this.sendControl(this.props.deviceId, this.props.control, !this.state.value)} startIcon={icon} > - {this.props.control.label} + {getTranslation(this.props.control.label)} ; } @@ -191,6 +198,8 @@ export default class DeviceControl extends Component{this.props.control.type}
; + return
+ {this.props.control.type} +
; } } diff --git a/packages/dm-gui-components/src/DeviceList.tsx b/packages/dm-gui-components/src/DeviceList.tsx index ed143af01..6fa7ff333 100644 --- a/packages/dm-gui-components/src/DeviceList.tsx +++ b/packages/dm-gui-components/src/DeviceList.tsx @@ -7,7 +7,7 @@ import { import { Clear, Refresh } from '@mui/icons-material'; import { I18n } from '@iobroker/adapter-react-v5'; -import type { DeviceInfo, InstanceDetails } from '@iobroker/dm-utils'; +import type { DeviceInfo, InstanceDetails } from '@iobroker/dm-utils/build/types/api'; import DeviceCard from './DeviceCard'; import { getTranslation } from './Utils'; @@ -27,17 +27,17 @@ import uk from './i18n/uk.json'; import zhCn from './i18n/zh-cn.json'; interface DeviceListProps extends CommunicationProps { - /* Instance to upload images to, like `adapterName.X` */ + /** Instance to upload images to, like `adapterName.X` */ uploadImagesToInstance?: string; - /* Filter devices with this string */ + /** Filter devices with this string */ filter?: string; - /* If this component is used in GUI with own toolbar. `false` if this list is used with multiple instances and true if only with one (in this case, it will monitor alive itself */ + /** If this component is used in GUI with own toolbar. `false` if this list is used with multiple instances and true if only with one (in this case, it will monitor alive itself */ embedded?: boolean; - /* If embedded, this text is shown in the toolbar */ + /** If embedded, this text is shown in the toolbar */ title?: string; - /* Style of a component that displays all devices */ + /** Style of a component that displays all devices */ style?: React.CSSProperties; - /* Use small cards for devices */ + /** Use small cards for devices */ smallCards?: boolean; } @@ -52,15 +52,6 @@ interface DeviceListState extends CommunicationState { /** * Device List Component - * @param {object} params - Component parameters - * @param {object} params.socket - socket object - * @param {string} params.selectedInstance - Selected instance - * @param {string} params.uploadImagesToInstance - Instance to upload images to - * @param {string} params.filter - Filter - * @param {string} params.empbedded - true if this list used with multiple instances and false if only with one - * @param {string} params.title - Title in appbar (only in non-embedded mode) - * @param {string} params.style - Style of devices list - * @returns {*[]} - Array of device cards */ export default class DeviceList extends Communication { static i18nInitialized = false; diff --git a/packages/dm-gui-components/src/DeviceStatus.tsx b/packages/dm-gui-components/src/DeviceStatus.tsx index c1f235b88..5eaf5a370 100644 --- a/packages/dm-gui-components/src/DeviceStatus.tsx +++ b/packages/dm-gui-components/src/DeviceStatus.tsx @@ -17,11 +17,12 @@ import { BatteryCharging50 as BatteryCharging50Icon, } from '@mui/icons-material'; -import { getTranslation } from './Utils'; +import type { DeviceStatus } from '@iobroker/dm-utils'; +import { getTranslation } from './Utils'; interface DeviceStatusProps { - status: any; + status: DeviceStatus | null; } /** * Device Status component @@ -35,31 +36,27 @@ export default function DeviceStatus(params: DeviceStatusProps): React.JSX.Eleme return null; } - let status; + let status: DeviceStatus; if (typeof params.status === 'string') { status = { - connection: params.status, + connection: params.status as 'connected' | 'disconnected', }; } else { status = params.status; } - /** @type {object} */ const iconStyleOK = { fill: '#00ac00', }; - /** @type {object} */ const iconStyleNotOK = { fill: '#ff0000', }; - /** @type {object} */ const iconStyleWarning = { fill: '#ff9900', }; - /** @type {object} */ - let batteryIconTooltip; + let batteryIconTooltip: React.JSX.Element; if (typeof status.battery === 'number') { if (status.battery >= 96 && status.battery <= 100) { batteryIconTooltip = ; diff --git a/packages/dm-gui-components/src/InstanceActionButton.tsx b/packages/dm-gui-components/src/InstanceActionButton.tsx index ec1ef5533..3d59f9fba 100644 --- a/packages/dm-gui-components/src/InstanceActionButton.tsx +++ b/packages/dm-gui-components/src/InstanceActionButton.tsx @@ -1,10 +1,13 @@ import React from 'react'; + +import type { ActionBase, InstanceAction } from '@iobroker/dm-utils/build/types/api'; + import TooltipButton from './TooltipButton'; -import { renderIcon, getTranslation } from './Utils'; +import { getTranslation, renderActionIcon } from './Utils'; interface InstanceActionButtonProps { - action: any; - instanceHandler: (action: any) => () => void; + action: InstanceAction; + instanceHandler: (action: ActionBase) => () => void; } export default function InstanceActionButton(params: InstanceActionButtonProps): React.JSX.Element | null { @@ -13,7 +16,7 @@ export default function InstanceActionButton(params: InstanceActionButtonProps): const tooltip = getTranslation(action?.description ? action.description : ''); const title = getTranslation(action?.title ? action.title : ''); - const icon = renderIcon(action); + const icon = renderActionIcon(action); return ; data: Record; onChange: (data: Record) => void; @@ -18,7 +23,6 @@ export default function JsonConfig(props: JsonConfigProps): React.JSX.Element | const { instanceId, socket, schema, data, onChange, } = props; - console.log('JsonConfig', props); const [error, setError] = useState(false); if (schema === undefined) { @@ -30,9 +34,9 @@ export default function JsonConfig(props: JsonConfigProps): React.JSX.Element | return <> {error &&
{error}
} s.trim()).filter(s => s !== 'fa-solid'); - let color = (value && action.colorOn) || action.color || (action.state ? 'primary' : 'inherit'); - - if (colors) { - if (color === 'primary') { - color = colors.primary; - } else if (color === 'secondary') { - color = colors.secondary; - } + if (iconStyle.includes('fa-trash-can') || iconStyle.includes('fa-trash')) { + return ; } - - if (action.icon?.startsWith('fa-') || action.icon?.startsWith('fas')) { - const iconStyle = action.icon.split(' ').map(s => s.trim()).filter(s => s !== 'fa-solid'); - - if (iconStyle.includes('fa-trash-can') || iconStyle.includes('fa-trash')) { - return ; - } - if (iconStyle.includes('fa-pen')) { - return ; - } - if (iconStyle.includes('fa-redo-alt')) { - return ; - } - if (iconStyle.includes('fa-plus')) { - return ; - } - if (iconStyle.includes('fa-wifi')) { - return ; - } - if (iconStyle.includes('fa-wifi-slash')) { - return ; - } - if (iconStyle.includes('fa-bluetooth')) { - return ; - } - if (iconStyle.includes('fa-bluetooth-slash')) { - return ; - } - if (iconStyle.includes('fa-eye')) { - return ; - } - if (iconStyle.includes('fa-search')) { - return ; - } - if (iconStyle.includes('fa-unlink')) { - return ; - } - if (iconStyle.includes('fa-link')) { - return ; - } - if (iconStyle.includes('fa-search-location')) { - return ; - } - if (iconStyle.includes('fa-play')) { - return ; - } - if (iconStyle.includes('fa-stop')) { - return ; - } - if (iconStyle.includes('fa-pause')) { - return ; - } - if (iconStyle.includes('forward') || iconStyle.includes('fa-forward')) { - return ; - } - if (iconStyle.includes('rewind') || iconStyle.includes('fa-rewind')) { - return ; - } - return null; + if (iconStyle.includes('fa-pen')) { + return ; } - if (value && action.iconOn?.startsWith('data:image')) { - return ; + if (iconStyle.includes('fa-redo-alt')) { + return ; } - if (action.icon?.startsWith('data:image')) { - return ; + if (iconStyle.includes('fa-plus')) { + return ; + } + if (iconStyle.includes('fa-wifi')) { + return ; + } + if (iconStyle.includes('fa-wifi-slash')) { + return ; + } + if (iconStyle.includes('fa-bluetooth')) { + return ; + } + if (iconStyle.includes('fa-bluetooth-slash')) { + return ; + } + if (iconStyle.includes('fa-eye')) { + return ; + } + if (iconStyle.includes('fa-search')) { + return ; + } + if (iconStyle.includes('fa-unlink')) { + return ; + } + if (iconStyle.includes('fa-link')) { + return ; + } + if (iconStyle.includes('fa-search-location')) { + return ; + } + if (iconStyle.includes('fa-play')) { + return ; + } + if (iconStyle.includes('fa-stop')) { + return ; } - if (action.id === 'edit' || action.id === 'rename') { + if (iconStyle.includes('fa-pause')) { + return ; + } + if (iconStyle.includes('forward') || iconStyle.includes('fa-forward')) { + return ; + } + if (iconStyle.includes('rewind') || iconStyle.includes('fa-rewind')) { + return ; + } + return null; +} + +function getIconByName(name: string, color: string): React.JSX.Element | null { + if (name === 'edit' || name === 'rename') { return ; } - if (action.id === 'delete') { + if (name === 'delete') { return ; } - if (action.id === 'refresh') { + if (name === 'refresh') { return ; } - if (action.id === 'newDevice' || action.id === 'new' || action.id === 'add') { + if (name === 'newDevice' || name === 'new' || name === 'add') { return ; } - if (action.id === 'discover' || action.id === 'search') { + if (name === 'discover' || name === 'search') { return ; } - if (action.id === 'unpairDevice') { + if (name === 'unpairDevice') { return ; } - if (action.id === 'pairDevice') { + if (name === 'pairDevice') { return ; } - if (action.id === 'identify') { + if (name === 'identify') { return ; } - if (action.id === 'play') { + if (name === 'play') { return ; } - if (action.id === 'stop') { + if (name === 'stop') { return ; } - if (action.id === 'pause') { + if (name === 'pause') { return ; } - if (action.id === 'forward' || action.id === 'next') { + if (name === 'forward' || name === 'next') { return ; } - if (action.id === 'rewind' || action.id === 'previous') { + if (name === 'rewind' || name === 'previous') { return ; } - if (action.id === 'lamp' || action.id === 'light') { + if (name === 'lamp' || name === 'light') { return ; } - if (action.id === 'backlight') { + if (name === 'backlight') { return ; } - if (action.id === 'dimmer') { + if (name === 'dimmer') { return ; } - if (action.id === 'socket') { + if (name === 'socket') { return ; } - if (action.id === 'settings') { + if (name === 'settings') { return ; } return null; } +export function renderControlIcon( + action: ControlBase, + colors?: { primary: string, secondary: string }, + value?: string | number | boolean | null, +): React.JSX.Element | null { + if (!action) { + return null; + } + + let color = (value && action.colorOn) || action.color || (action.state ? 'primary' : 'inherit'); + + if (colors) { + if (color === 'primary') { + color = colors.primary; + } else if (color === 'secondary') { + color = colors.secondary; + } + } + + if (action.icon?.startsWith('fa-') || action.icon?.startsWith('fas')) { + return getFaIcon(action.icon, color); + } + if (value && action.iconOn?.startsWith('data:image')) { + return ; + } + if (action.icon?.startsWith('data:image')) { + return ; + } + return getIconByName(action.id, color); +} + +export function renderActionIcon( + action: ActionBase, +): React.JSX.Element | null { + if (!action) { + return null; + } + + if (action.icon?.startsWith('fa-') || action.icon?.startsWith('fas')) { + return getFaIcon(action.icon, action.color); + } + if (action.icon?.startsWith('data:image')) { + return ; + } + return getIconByName(action.id, action.color); +} + let language: ioBroker.Languages; /** * Get Translation - * @param {string | object} text - Text to translate - * @returns {string} */ -export function getTranslation(text: ioBroker.StringOrTranslated): string { +export function getTranslation( + /** Text to translate */ + text: ioBroker.StringOrTranslated, +): string { language = language || I18n.getLanguage(); if (typeof text === 'object') { diff --git a/packages/dm-gui-components/src/i18n/de.json b/packages/dm-gui-components/src/i18n/de.json index 50e79bd44..027fa1027 100644 --- a/packages/dm-gui-components/src/i18n/de.json +++ b/packages/dm-gui-components/src/i18n/de.json @@ -1,21 +1,22 @@ { - "manufacturer": "Hersteller", - "model": "Modell", - "batteryTooltip": "Batterie", - "connectedIconTooltip": "Verbunden", - "disconnectedIconTooltip": "Getrennt", - "cancelButtonText": "Abbrechen", - "okButtonText": "OK", - "yesButtonText": "Ja", - "noButtonText": "Nein", - "noInstanceSelectedText": "Bitte Instanz auswählen", - "filterLabelText": "Nach Name filtern", - "instanceLabelText": "Instanz", - "refreshInstanceList": "Instanzliste aktualisieren", - "copied": "Kopiert", - "toClipboard": "in die Zwischenablage kopiert", - "allDevicesFilteredOut": "Alle Geräte herausgefiltert", - "closeButtonText": "Schließen", - "instanceNotAlive": "Instanz ist nicht aktiv", - "noDevicesFoundText": "Keine Geräte gefunden" -} + "manufacturer": "Hersteller", + "model": "Modell", + "batteryTooltip": "Batterie", + "connectedIconTooltip": "Verbunden", + "disconnectedIconTooltip": "Getrennt", + "cancelButtonText": "Abbrechen", + "okButtonText": "OK", + "yesButtonText": "Ja", + "noButtonText": "Nein", + "noInstanceSelectedText": "Bitte Instanz auswählen", + "filterLabelText": "Nach Name filtern", + "instanceLabelText": "Instanz", + "refreshInstanceList": "Instanzliste aktualisieren", + "copied": "Kopiert", + "toClipboard": "in die Zwischenablage kopiert", + "allDevicesFilteredOut": "Alle Geräte herausgefiltert", + "closeButtonText": "Schließen", + "instanceNotAlive": "Instanz ist nicht aktiv", + "noDevicesFoundText": "Keine Geräte gefunden", + "pleaseEnterValueText": "Bitte eingeben" +} \ No newline at end of file diff --git a/packages/dm-gui-components/src/i18n/en.json b/packages/dm-gui-components/src/i18n/en.json index 617424151..006651a2c 100644 --- a/packages/dm-gui-components/src/i18n/en.json +++ b/packages/dm-gui-components/src/i18n/en.json @@ -17,5 +17,6 @@ "copied": "Copied", "toClipboard": "to clipboard", "instanceNotAlive": "Instance is not alive", - "allDevicesFilteredOut": "All devices filtered out" + "allDevicesFilteredOut": "All devices filtered out", + "pleaseEnterValueText": "Please enter" } \ No newline at end of file diff --git a/packages/dm-gui-components/src/i18n/es.json b/packages/dm-gui-components/src/i18n/es.json index 6023b8e0e..95cec8844 100644 --- a/packages/dm-gui-components/src/i18n/es.json +++ b/packages/dm-gui-components/src/i18n/es.json @@ -1,21 +1,22 @@ { - "manufacturer": "Fabricante", - "model": "Modelo", - "batteryTooltip": "Batería", - "connectedIconTooltip": "Conectado", - "allDevicesFilteredOut": "Todos los dispositivos filtrados", - "cancelButtonText": "Cancelar", - "closeButtonText": "Cerca", - "copied": "copiado", - "disconnectedIconTooltip": "Desconectado", - "filterLabelText": "Filtrar por nombre", - "instanceLabelText": "Instancia", - "instanceNotAlive": "La instancia no está viva.", - "noButtonText": "No", - "noDevicesFoundText": "No se encontraron dispositivos", - "noInstanceSelectedText": "Por favor seleccione instancia", - "okButtonText": "DE ACUERDO", - "refreshInstanceList": "Actualizar lista de instancias", - "toClipboard": "al portapapeles", - "yesButtonText": "Sí" -} + "manufacturer": "Fabricante", + "model": "Modelo", + "batteryTooltip": "Batería", + "connectedIconTooltip": "Conectado", + "allDevicesFilteredOut": "Todos los dispositivos filtrados", + "cancelButtonText": "Cancelar", + "closeButtonText": "Cerca", + "copied": "copiado", + "disconnectedIconTooltip": "Desconectado", + "filterLabelText": "Filtrar por nombre", + "instanceLabelText": "Instancia", + "instanceNotAlive": "La instancia no está viva.", + "noButtonText": "No", + "noDevicesFoundText": "No se encontraron dispositivos", + "noInstanceSelectedText": "Por favor seleccione instancia", + "okButtonText": "DE ACUERDO", + "refreshInstanceList": "Actualizar lista de instancias", + "toClipboard": "al portapapeles", + "yesButtonText": "Sí", + "pleaseEnterValueText": "Por favor escribe" +} \ No newline at end of file diff --git a/packages/dm-gui-components/src/i18n/fr.json b/packages/dm-gui-components/src/i18n/fr.json index 865df129d..d8f4ec704 100644 --- a/packages/dm-gui-components/src/i18n/fr.json +++ b/packages/dm-gui-components/src/i18n/fr.json @@ -1,21 +1,22 @@ { - "manufacturer": "Fabricant", - "model": "Modèle", - "batteryTooltip": "Batterie", - "connectedIconTooltip": "Connecté", - "allDevicesFilteredOut": "Tous les appareils filtrés", - "cancelButtonText": "Annuler", - "closeButtonText": "Fermer", - "copied": "Copié", - "disconnectedIconTooltip": "Débranché", - "filterLabelText": "Filtrer par nom", - "instanceLabelText": "Exemple", - "instanceNotAlive": "L'instance n'est pas vivante", - "noButtonText": "Non", - "noDevicesFoundText": "Aucun périphérique trouvé", - "noInstanceSelectedText": "Veuillez sélectionner une instance", - "okButtonText": "D'ACCORD", - "refreshInstanceList": "Actualiser la liste des instances", - "toClipboard": "au presse-papiers", - "yesButtonText": "Oui" -} + "manufacturer": "Fabricant", + "model": "Modèle", + "batteryTooltip": "Batterie", + "connectedIconTooltip": "Connecté", + "allDevicesFilteredOut": "Tous les appareils filtrés", + "cancelButtonText": "Annuler", + "closeButtonText": "Fermer", + "copied": "Copié", + "disconnectedIconTooltip": "Débranché", + "filterLabelText": "Filtrer par nom", + "instanceLabelText": "Exemple", + "instanceNotAlive": "L'instance n'est pas vivante", + "noButtonText": "Non", + "noDevicesFoundText": "Aucun périphérique trouvé", + "noInstanceSelectedText": "Veuillez sélectionner une instance", + "okButtonText": "D'ACCORD", + "refreshInstanceList": "Actualiser la liste des instances", + "toClipboard": "au presse-papiers", + "yesButtonText": "Oui", + "pleaseEnterValueText": "Entrez s'il vous plait" +} \ No newline at end of file diff --git a/packages/dm-gui-components/src/i18n/it.json b/packages/dm-gui-components/src/i18n/it.json index ce568b53b..5cd8661e5 100644 --- a/packages/dm-gui-components/src/i18n/it.json +++ b/packages/dm-gui-components/src/i18n/it.json @@ -1,21 +1,22 @@ { - "manufacturer": "Produttore", - "model": "Modello", - "batteryTooltip": "Batteria", - "connectedIconTooltip": "Connesso", - "allDevicesFilteredOut": "Tutti i dispositivi sono stati filtrati", - "cancelButtonText": "Annulla", - "closeButtonText": "Vicino", - "copied": "Copiato", - "disconnectedIconTooltip": "Disconnesso", - "filterLabelText": "Filtra per nome", - "instanceLabelText": "Esempio", - "instanceNotAlive": "L'istanza non è viva", - "noButtonText": "NO", - "noDevicesFoundText": "Nessun dispositivo trovato", - "noInstanceSelectedText": "Seleziona l'istanza", - "okButtonText": "OK", - "refreshInstanceList": "Aggiorna l'elenco delle istanze", - "toClipboard": "negli appunti", - "yesButtonText": "SÌ" -} + "manufacturer": "Produttore", + "model": "Modello", + "batteryTooltip": "Batteria", + "connectedIconTooltip": "Connesso", + "allDevicesFilteredOut": "Tutti i dispositivi sono stati filtrati", + "cancelButtonText": "Annulla", + "closeButtonText": "Vicino", + "copied": "Copiato", + "disconnectedIconTooltip": "Disconnesso", + "filterLabelText": "Filtra per nome", + "instanceLabelText": "Esempio", + "instanceNotAlive": "L'istanza non è viva", + "noButtonText": "NO", + "noDevicesFoundText": "Nessun dispositivo trovato", + "noInstanceSelectedText": "Seleziona l'istanza", + "okButtonText": "OK", + "refreshInstanceList": "Aggiorna l'elenco delle istanze", + "toClipboard": "negli appunti", + "yesButtonText": "SÌ", + "pleaseEnterValueText": "Prego entra" +} \ No newline at end of file diff --git a/packages/dm-gui-components/src/i18n/nl.json b/packages/dm-gui-components/src/i18n/nl.json index 8fc92b0fc..5ce2d1a0c 100644 --- a/packages/dm-gui-components/src/i18n/nl.json +++ b/packages/dm-gui-components/src/i18n/nl.json @@ -1,21 +1,22 @@ { - "manufacturer": "Fabrikant", - "model": "Model", - "batteryTooltip": "Batterij", - "connectedIconTooltip": "Verbonden", - "allDevicesFilteredOut": "Alle apparaten zijn eruit gefilterd", - "cancelButtonText": "Annuleren", - "closeButtonText": "Dichtbij", - "copied": "Gekopieerd", - "disconnectedIconTooltip": "Losgekoppeld", - "filterLabelText": "Filter op naam", - "instanceLabelText": "Voorbeeld", - "instanceNotAlive": "Instantie leeft niet", - "noButtonText": "Nee", - "noDevicesFoundText": "Geen apparaten gevonden", - "noInstanceSelectedText": "Selecteer een exemplaar", - "okButtonText": "OK", - "refreshInstanceList": "Ververs de exemplaarlijst", - "toClipboard": "naar klembord", - "yesButtonText": "Ja" -} + "manufacturer": "Fabrikant", + "model": "Model", + "batteryTooltip": "Batterij", + "connectedIconTooltip": "Verbonden", + "allDevicesFilteredOut": "Alle apparaten zijn eruit gefilterd", + "cancelButtonText": "Annuleren", + "closeButtonText": "Dichtbij", + "copied": "Gekopieerd", + "disconnectedIconTooltip": "Losgekoppeld", + "filterLabelText": "Filter op naam", + "instanceLabelText": "Voorbeeld", + "instanceNotAlive": "Instantie leeft niet", + "noButtonText": "Nee", + "noDevicesFoundText": "Geen apparaten gevonden", + "noInstanceSelectedText": "Selecteer een exemplaar", + "okButtonText": "OK", + "refreshInstanceList": "Ververs de exemplaarlijst", + "toClipboard": "naar klembord", + "yesButtonText": "Ja", + "pleaseEnterValueText": "Kom binnen alstublieft" +} \ No newline at end of file diff --git a/packages/dm-gui-components/src/i18n/pl.json b/packages/dm-gui-components/src/i18n/pl.json index 8b54feb06..55f1a4196 100644 --- a/packages/dm-gui-components/src/i18n/pl.json +++ b/packages/dm-gui-components/src/i18n/pl.json @@ -1,21 +1,22 @@ { - "manufacturer": "Producent", - "model": "Model", - "batteryTooltip": "Bateria", - "connectedIconTooltip": "Połączono", - "allDevicesFilteredOut": "Wszystkie urządzenia zostały odfiltrowane", - "cancelButtonText": "Anulować", - "closeButtonText": "Zamknąć", - "copied": "Skopiowano", - "disconnectedIconTooltip": "Bezładny", - "filterLabelText": "Filtruj według nazwy", - "instanceLabelText": "Instancja", - "instanceNotAlive": "Instancja nie żyje", - "noButtonText": "NIE", - "noDevicesFoundText": "Nie znaleziono urządzeń", - "noInstanceSelectedText": "Wybierz instancję", - "okButtonText": "OK", - "refreshInstanceList": "Odśwież listę instancji", - "toClipboard": "do schowka", - "yesButtonText": "Tak" -} + "manufacturer": "Producent", + "model": "Model", + "batteryTooltip": "Bateria", + "connectedIconTooltip": "Połączono", + "allDevicesFilteredOut": "Wszystkie urządzenia zostały odfiltrowane", + "cancelButtonText": "Anulować", + "closeButtonText": "Zamknąć", + "copied": "Skopiowano", + "disconnectedIconTooltip": "Bezładny", + "filterLabelText": "Filtruj według nazwy", + "instanceLabelText": "Instancja", + "instanceNotAlive": "Instancja nie żyje", + "noButtonText": "NIE", + "noDevicesFoundText": "Nie znaleziono urządzeń", + "noInstanceSelectedText": "Wybierz instancję", + "okButtonText": "OK", + "refreshInstanceList": "Odśwież listę instancji", + "toClipboard": "do schowka", + "yesButtonText": "Tak", + "pleaseEnterValueText": "Podaj" +} \ No newline at end of file diff --git a/packages/dm-gui-components/src/i18n/pt.json b/packages/dm-gui-components/src/i18n/pt.json index ea409fe84..be3241847 100644 --- a/packages/dm-gui-components/src/i18n/pt.json +++ b/packages/dm-gui-components/src/i18n/pt.json @@ -1,21 +1,22 @@ { - "manufacturer": "Fabricante", - "model": "Modelo", - "batteryTooltip": "Bateria", - "connectedIconTooltip": "Conectado", - "allDevicesFilteredOut": "Todos os dispositivos foram filtrados", - "cancelButtonText": "Cancelar", - "closeButtonText": "Fechar", - "copied": "Copiado", - "disconnectedIconTooltip": "Desconectado", - "filterLabelText": "Filtrar por nome", - "instanceLabelText": "Instância", - "instanceNotAlive": "A instância não está ativa", - "noButtonText": "Não", - "noDevicesFoundText": "Nenhum dispositivo encontrado", - "noInstanceSelectedText": "Selecione a instância", - "okButtonText": "OK", - "refreshInstanceList": "Atualizar lista de instâncias", - "toClipboard": "para a área de transferência", - "yesButtonText": "Sim" -} + "manufacturer": "Fabricante", + "model": "Modelo", + "batteryTooltip": "Bateria", + "connectedIconTooltip": "Conectado", + "allDevicesFilteredOut": "Todos os dispositivos foram filtrados", + "cancelButtonText": "Cancelar", + "closeButtonText": "Fechar", + "copied": "Copiado", + "disconnectedIconTooltip": "Desconectado", + "filterLabelText": "Filtrar por nome", + "instanceLabelText": "Instância", + "instanceNotAlive": "A instância não está ativa", + "noButtonText": "Não", + "noDevicesFoundText": "Nenhum dispositivo encontrado", + "noInstanceSelectedText": "Selecione a instância", + "okButtonText": "OK", + "refreshInstanceList": "Atualizar lista de instâncias", + "toClipboard": "para a área de transferência", + "yesButtonText": "Sim", + "pleaseEnterValueText": "Por favor, insira" +} \ No newline at end of file diff --git a/packages/dm-gui-components/src/i18n/ru.json b/packages/dm-gui-components/src/i18n/ru.json index b6f7d71c1..d17e2599d 100644 --- a/packages/dm-gui-components/src/i18n/ru.json +++ b/packages/dm-gui-components/src/i18n/ru.json @@ -1,21 +1,22 @@ { - "manufacturer": "Производитель", - "model": "Модель", - "batteryTooltip": "Батарея", - "connectedIconTooltip": "Подключено", - "allDevicesFilteredOut": "Все устройства отфильтрованы", - "cancelButtonText": "Отмена", - "closeButtonText": "Закрыть", - "copied": "Скопировано", - "disconnectedIconTooltip": "Отключено", - "filterLabelText": "Фильтровать по имени", - "instanceLabelText": "Екземпляр", - "instanceNotAlive": "Экземпляр не жив", - "noButtonText": "Нет", - "noDevicesFoundText": "Устройства не найдены", - "noInstanceSelectedText": "Пожалуйста, выберите экземпляр", - "okButtonText": "ХОРОШО", - "refreshInstanceList": "Обновить список экземпляров", - "toClipboard": "в буфер обмена", - "yesButtonText": "Да" -} + "manufacturer": "Производитель", + "model": "Модель", + "batteryTooltip": "Батарея", + "connectedIconTooltip": "Подключено", + "allDevicesFilteredOut": "Все устройства отфильтрованы", + "cancelButtonText": "Отмена", + "closeButtonText": "Закрыть", + "copied": "Скопировано", + "disconnectedIconTooltip": "Отключено", + "filterLabelText": "Фильтровать по имени", + "instanceLabelText": "Екземпляр", + "instanceNotAlive": "Экземпляр не жив", + "noButtonText": "Нет", + "noDevicesFoundText": "Устройства не найдены", + "noInstanceSelectedText": "Пожалуйста, выберите экземпляр", + "okButtonText": "ХОРОШО", + "refreshInstanceList": "Обновить список экземпляров", + "toClipboard": "в буфер обмена", + "yesButtonText": "Да", + "pleaseEnterValueText": "Пожалуйста входите" +} \ No newline at end of file diff --git a/packages/dm-gui-components/src/i18n/uk.json b/packages/dm-gui-components/src/i18n/uk.json index 857038c66..07b8e280d 100644 --- a/packages/dm-gui-components/src/i18n/uk.json +++ b/packages/dm-gui-components/src/i18n/uk.json @@ -1,21 +1,22 @@ { - "manufacturer": "Виробник", - "model": "Модель", - "batteryTooltip": "Батарея", - "allDevicesFilteredOut": "Усі пристрої відфільтровано", - "cancelButtonText": "Скасувати", - "closeButtonText": "Закрити", - "connectedIconTooltip": "Підключено", - "copied": "Скопійовано", - "disconnectedIconTooltip": "Відключено", - "filterLabelText": "Фільтрувати за назвою", - "instanceLabelText": "Екземпляр", - "instanceNotAlive": "Примірник не живий", - "noButtonText": "Немає", - "noDevicesFoundText": "Пристроїв не знайдено", - "noInstanceSelectedText": "Виберіть екземпляр", - "okButtonText": "в порядку", - "refreshInstanceList": "Оновити список екземплярів", - "toClipboard": "в буфер обміну", - "yesButtonText": "Так" -} + "manufacturer": "Виробник", + "model": "Модель", + "batteryTooltip": "Батарея", + "allDevicesFilteredOut": "Усі пристрої відфільтровано", + "cancelButtonText": "Скасувати", + "closeButtonText": "Закрити", + "connectedIconTooltip": "Підключено", + "copied": "Скопійовано", + "disconnectedIconTooltip": "Відключено", + "filterLabelText": "Фільтрувати за назвою", + "instanceLabelText": "Екземпляр", + "instanceNotAlive": "Примірник не живий", + "noButtonText": "Немає", + "noDevicesFoundText": "Пристроїв не знайдено", + "noInstanceSelectedText": "Виберіть екземпляр", + "okButtonText": "в порядку", + "refreshInstanceList": "Оновити список екземплярів", + "toClipboard": "в буфер обміну", + "yesButtonText": "Так", + "pleaseEnterValueText": "Будь ласка введіть" +} \ No newline at end of file diff --git a/packages/dm-gui-components/src/i18n/zh-cn.json b/packages/dm-gui-components/src/i18n/zh-cn.json index c8a4225ca..4204670cd 100644 --- a/packages/dm-gui-components/src/i18n/zh-cn.json +++ b/packages/dm-gui-components/src/i18n/zh-cn.json @@ -1,21 +1,22 @@ { - "manufacturer": "制造商", - "model": "型号", - "batteryTooltip": "电池", - "connectedIconTooltip": "已连接", - "allDevicesFilteredOut": "所有设备均被过滤掉", - "cancelButtonText": "取消", - "closeButtonText": "关闭", - "copied": "已复制", - "disconnectedIconTooltip": "已断开连接", - "filterLabelText": "按名称过滤", - "instanceLabelText": "实例", - "instanceNotAlive": "实例不存在", - "noButtonText": "不", - "noDevicesFoundText": "未找到设备", - "noInstanceSelectedText": "请选择实例", - "okButtonText": "好的", - "refreshInstanceList": "刷新实例列表", - "toClipboard": "到剪贴板", - "yesButtonText": "是的" -} + "manufacturer": "制造商", + "model": "型号", + "batteryTooltip": "电池", + "connectedIconTooltip": "已连接", + "allDevicesFilteredOut": "所有设备均被过滤掉", + "cancelButtonText": "取消", + "closeButtonText": "关闭", + "copied": "已复制", + "disconnectedIconTooltip": "已断开连接", + "filterLabelText": "按名称过滤", + "instanceLabelText": "实例", + "instanceNotAlive": "实例不存在", + "noButtonText": "不", + "noDevicesFoundText": "未找到设备", + "noInstanceSelectedText": "请选择实例", + "okButtonText": "好的", + "refreshInstanceList": "刷新实例列表", + "toClipboard": "到剪贴板", + "yesButtonText": "是的", + "pleaseEnterValueText": "请输入" +} \ No newline at end of file diff --git a/packages/jsonConfig/after_build.js b/packages/jsonConfig/after_build.js new file mode 100644 index 000000000..95a9593a8 --- /dev/null +++ b/packages/jsonConfig/after_build.js @@ -0,0 +1,3 @@ +const fs = require('node:fs'); + +fs.copyFileSync('./src/types.d.ts', './build/types.d.ts'); diff --git a/packages/jsonConfig/package.json b/packages/jsonConfig/package.json index e05a5a182..68c3ce4cf 100644 --- a/packages/jsonConfig/package.json +++ b/packages/jsonConfig/package.json @@ -5,7 +5,7 @@ "main": "./build/index.js", "types": "./build/index.d.ts", "scripts": { - "build": "tsc && tsc-alias", + "build": "tsc && tsc-alias && node after_build", "clean": "rimraf build", "prepublishOnly": "npm run build", "build:ts": "tsc -p tsconfig.json" @@ -14,8 +14,7 @@ "access": "public" }, "dependencies": { - "@iobroker/adapter-react-v5": "^4.13.22", - "@iobroker/dm-gui-components": "file:../dm-gui-components", + "@iobroker/adapter-react-v5": "^5.0.1", "crypto-js": "^4.2.0", "react-ace": "^11.0.1" }, diff --git a/packages/jsonConfig/src/JsonConfig.tsx b/packages/jsonConfig/src/JsonConfig.tsx index f910b639f..8d00947b4 100644 --- a/packages/jsonConfig/src/JsonConfig.tsx +++ b/packages/jsonConfig/src/JsonConfig.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { type Styles, withStyles } from '@mui/styles'; +import { withStyles } from '@mui/styles'; import JSON5 from 'json5'; import MD5 from 'crypto-js/md5'; @@ -14,18 +14,20 @@ import { I18n, Router, SaveCloseButtons, - Theme as theme, + Theme, Confirm as ConfirmDialog, - AdminConnection, + type AdminConnection, + type IobTheme, + type ThemeName, + type ThemeType, } from '@iobroker/adapter-react-v5'; -import type { Theme, ThemeName, ThemeType } from '@iobroker/adapter-react-v5/types'; import type { ConfigItemAny, ConfigItemPanel, ConfigItemTabs } from '#JC/types'; import Utils from '#JC/Utils'; -import ConfigGeneric from './JsonConfigComponent/ConfigGeneric'; +import ConfigGeneric, { type DeviceManagerPropsProps } from './JsonConfigComponent/ConfigGeneric'; import JsonConfigComponent from './JsonConfigComponent'; -const styles = { +const styles: Record = { root: { width: '100%', height: '100%', @@ -45,7 +47,7 @@ const styles = { button: { marginRight: 5, }, -} satisfies Styles; +}; /** * Decrypt the password/value with given key @@ -181,7 +183,7 @@ interface JsonConfigProps { dateFormat: string; secret: string; socket: AdminConnection; - theme: Record; + theme: IobTheme; themeName: ThemeName; themeType: ThemeType; /** CSS classes */ @@ -190,6 +192,7 @@ interface JsonConfigProps { t: typeof I18n.t; configStored: (notChanged: boolean) => void; width: 'xs' | 'sm' | 'md'; + DeviceManager?: React.FC; } interface JsonConfigState { @@ -200,7 +203,7 @@ interface JsonConfigState { common?: ioBroker.InstanceCommon; changed: boolean; confirmDialog: boolean; - theme: Theme; + theme: IobTheme; saveConfigDialog: boolean; hash: string; error?: boolean; @@ -220,7 +223,7 @@ class JsonConfig extends Router { updateData: 0, changed: false, confirmDialog: false, - theme: theme(props.themeName), // buttons require special theme + theme: Theme(props.themeName), // buttons require special theme saveConfigDialog: false, hash: '_', }; @@ -690,6 +693,7 @@ class JsonConfig extends Router { this.setState({ saveConfigDialog }); } }} + DeviceManager={this.props.DeviceManager} /> = (theme: Theme) => { +const styles: Record = (theme: IobTheme) => { // @ts-expect-error The type does exist const light = theme.palette.type === 'light'; const bottomLineColor = light ? 'rgba(0, 0, 0, 0.42)' : 'rgba(255, 255, 255, 0.7)'; diff --git a/packages/jsonConfig/src/JsonConfigComponent/ConfigAccordion.tsx b/packages/jsonConfig/src/JsonConfigComponent/ConfigAccordion.tsx index cc7bd658c..1f0982f0f 100644 --- a/packages/jsonConfig/src/JsonConfigComponent/ConfigAccordion.tsx +++ b/packages/jsonConfig/src/JsonConfigComponent/ConfigAccordion.tsx @@ -6,7 +6,7 @@ import { Accordion, AccordionSummary, AccordionDetails, IconButton, Paper, Toolbar, Tooltip, - Typography, type Theme, + Typography, } from '@mui/material'; import { @@ -18,14 +18,15 @@ import { ExpandMore as ExpandMoreIcon, } from '@mui/icons-material'; -import { Utils, I18n } from '@iobroker/adapter-react-v5'; +import { Utils, I18n, type IobTheme } from '@iobroker/adapter-react-v5'; +import type { ConfigItemAccordion, ConfigItemIndexed, ConfigItemPanel } from '#JC/types'; import ConfigGeneric, { type ConfigGenericProps, type ConfigGenericState } from './ConfigGeneric'; + // eslint-disable-next-line import/no-cycle import ConfigPanel from './ConfigPanel'; -import type {ConfigItemAccordion, ConfigItemIndexed, ConfigItemPanel} from "#JC/types"; -const styles: Record = (theme: Theme) => ({ +const styles: Record = (theme: IobTheme) => ({ fullWidth: { width: '100%', }, diff --git a/packages/jsonConfig/src/JsonConfigComponent/ConfigCertificateSelect.tsx b/packages/jsonConfig/src/JsonConfigComponent/ConfigCertificateSelect.tsx index a9997bfcf..6f9a91a51 100644 --- a/packages/jsonConfig/src/JsonConfigComponent/ConfigCertificateSelect.tsx +++ b/packages/jsonConfig/src/JsonConfigComponent/ConfigCertificateSelect.tsx @@ -31,6 +31,7 @@ class ConfigCertificateSelect extends ConfigGeneric { async componentDidMount() { super.componentDidMount(); + // Important: getCertificates is only available in AdminConnection const certificates = await this.props.socket.getCertificates(); const certsPublicOptions: { label: string; value: string }[] = []; const certsPrivateOptions: { label: string; value: string }[] = []; diff --git a/packages/jsonConfig/src/JsonConfigComponent/ConfigCheckLicense.jsx b/packages/jsonConfig/src/JsonConfigComponent/ConfigCheckLicense.tsx similarity index 89% rename from packages/jsonConfig/src/JsonConfigComponent/ConfigCheckLicense.jsx rename to packages/jsonConfig/src/JsonConfigComponent/ConfigCheckLicense.tsx index a5f216317..09356dc2e 100644 --- a/packages/jsonConfig/src/JsonConfigComponent/ConfigCheckLicense.jsx +++ b/packages/jsonConfig/src/JsonConfigComponent/ConfigCheckLicense.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { withStyles } from '@mui/styles'; +import { type Styles, withStyles } from '@mui/styles'; import { Button, @@ -20,11 +19,12 @@ import { import { Check as IconCheck, Send as IconSend } from '@mui/icons-material'; -import { Confirm as ConfirmDialog, I18n } from '@iobroker/adapter-react-v5'; +import { Confirm as ConfirmDialog, I18n, type IobTheme} from '@iobroker/adapter-react-v5'; -import ConfigGeneric from './ConfigGeneric'; +import type { ConfigItemCheckLicense } from '#JC/types'; +import ConfigGeneric, { type ConfigGenericProps, type ConfigGenericState } from './ConfigGeneric'; -const styles = theme => ({ +const styles: Styles = theme => ({ fullWidth: { width: '100%', }, @@ -55,7 +55,47 @@ const styles = theme => ({ }, }); -class ConfigCheckLicense extends ConfigGeneric { +export interface License { + id: string; + product: string; + time: number; + uuid: string; + validTill: string; + version: string; + usedBy: string; + invoice: string; + json: string; +} + +interface LicenseResult { + id: string; + validName: boolean; + validUuid: boolean; + validVersion: boolean; + validTill: boolean; + license: License; + used?: boolean; +} + +interface ConfigCheckLicenseProps extends ConfigGenericProps { + schema: ConfigItemCheckLicense; + fullWidth?: boolean; +} + +interface ConfigCheckLicenseState extends ConfigGenericState { + showLicenseData: null | Record; + _error: string; + result: null | boolean; + running: boolean; + foundSuitableLicense: boolean; + licenseOfflineCheck: boolean; + showLinkToProfile: boolean; + allLicenses: null | LicenseResult[] + askForUpdate: boolean; +} + + +class ConfigCheckLicense extends ConfigGeneric { async componentDidMount() { super.componentDidMount(); this.setState({ @@ -65,12 +105,15 @@ class ConfigCheckLicense extends ConfigGeneric { foundSuitableLicense: false, licenseOfflineCheck: false, result: null, + allLicenses: null, + askForUpdate: false, + showLinkToProfile: false, }); } renderErrorDialog() { if (this.state._error && !this.state.showLicenseData) { - let content = this.state._error; + let content: string | React.JSX.Element[] = this.state._error; if (this.state.allLicenses) { content = [
{content}
, @@ -123,7 +166,7 @@ class ConfigCheckLicense extends ConfigGeneric { open={!0} maxWidth="xl" fullWidth={this.props.fullWidth !== undefined ? this.props.fullWidth : true} - onClose={() => this.handleOk()} + onClick={() => this.setState({ _error: '', allLicenses: null })} > {I18n.t('ra_Error')} @@ -232,7 +275,7 @@ class ConfigCheckLicense extends ConfigGeneric { return null; } - static parseJwt(token) { + static parseJwt(token: string) { const base64Url = token.split('.')[1]; const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); const jsonPayload = decodeURIComponent( @@ -248,7 +291,12 @@ class ConfigCheckLicense extends ConfigGeneric { } } - static isVersionValid(version, rule, invoice, adapterName) { + static isVersionValid( + version: string, + rule: string, + invoice: string, + adapterName: string, + ) { if (!rule || !version) { return true; } @@ -280,28 +328,28 @@ class ConfigCheckLicense extends ConfigGeneric { return true; } - async findInLicenseManager(adapterName) { + async findInLicenseManager(adapterName: string): Promise { // read if the license manager is supported const licenses = await this.props.socket.getObject('system.licenses'); - const errors = []; + const errors: LicenseResult[] = []; if (licenses?.native?.licenses?.length) { // enable license manager - let useLicense; + let useLicense: License | null = null; const now = Date.now(); - let uuid; + let uuid: string; if (this.props.schema.uuid) { const uuidObj = await this.props.socket.getObject('system.meta.uuid'); uuid = uuidObj?.native?.uuid; } - let version; + let version: string; if (this.props.schema.version) { const aObj = await this.props.socket.getObject(`system.adapter.${adapterName}`); version = aObj?.common?.version; } // find license for vis - licenses.native.licenses.forEach(license => { + licenses.native.licenses.forEach((license: License) => { const validTill = !license.validTill || license.validTill === '0000-00-00 00:00:00' || new Date(license.validTill).getTime() > now; const parts = (license.product || '').split('.'); const validName = parts[1] === adapterName || (adapterName === 'vis-2' && parts[1] === 'vis'); @@ -329,31 +377,7 @@ class ConfigCheckLicense extends ConfigGeneric { return errors; } - static updateLicenses(socket) { - return new Promise((resolve, reject) => { - socket.getRawSocket().emit('updateLicenses', null, null, (err, licenses) => { - if (err === 'permissionError') { - reject(I18n.t('ra_May not trigger "updateLicenses"')); - } else if (err && err.error) { - if (typeof err.error === 'string') { - reject(I18n.t(err.error)); - } else { - reject(JSON.stringify(err.error)); - } - } else if (err) { - if (typeof err === 'string') { - reject(I18n.t(err)); - } else { - reject(JSON.stringify(err)); - } - } else { - resolve(licenses); - } - }); - }); - } - - async checkLicense(license, adapterName) { + async checkLicense(license: string, adapterName: string) { let uuid; if (this.props.schema.uuid) { const uuidObj = await this.props.socket.getObject('system.meta.uuid'); @@ -381,9 +405,19 @@ class ConfigCheckLicense extends ConfigGeneric { signal: controller.signal, }); timeout && clearTimeout(timeout); - let data = await response.text(); + const dataStr = await response.text(); + let data: { + error?: string; + validTill?: string; + /** @deprecated use validTill */ + valid_till?: string; + name?: string; + version?: string; + uuid?: string; + invoice?: string; + }; try { - data = JSON.parse(data); + data = JSON.parse(dataStr); } catch (e) { // ignore } @@ -573,7 +607,8 @@ class ConfigCheckLicense extends ConfigGeneric { if (isYes) { this.setState({ askForUpdate: false }); try { - await ConfigCheckLicense.updateLicenses(this.props.socket); + // updateLicense is available only in AdminConnection + await this.props.socket.updateLicenses(null, null); } catch (e) { window.alert(I18n.t('ra_Cannot read licenses: %s', e)); return; @@ -586,7 +621,7 @@ class ConfigCheckLicense extends ConfigGeneric { />; } - async _onClick(secondRun) { + async _onClick(secondRun?: boolean) { const adapterName = this.props.adapterName === 'vis-2' ? 'vis' : this.props.adapterName; this.setState({ running: true }); let license; @@ -607,7 +642,7 @@ class ConfigCheckLicense extends ConfigGeneric { license = this.props.data.license; } if (license) { - await this.checkLicense(license, adapterName, this.props.schema.uuid); + await this.checkLicense(license, adapterName); } else if (this.props.data.useLicenseManager) { this.setState({ _error: I18n.t('ra_Suitable license not found in license manager'), @@ -645,18 +680,4 @@ class ConfigCheckLicense extends ConfigGeneric { } } -ConfigCheckLicense.propTypes = { - socket: PropTypes.object.isRequired, - themeType: PropTypes.string, - themeName: PropTypes.string, - style: PropTypes.object, - className: PropTypes.string, - data: PropTypes.object.isRequired, - schema: PropTypes.object, - onError: PropTypes.func, - onChange: PropTypes.func, - adapterName: PropTypes.string, - instance: PropTypes.number, -}; - export default withStyles(styles)(ConfigCheckLicense); diff --git a/packages/jsonConfig/src/JsonConfigComponent/ConfigCheckbox.tsx b/packages/jsonConfig/src/JsonConfigComponent/ConfigCheckbox.tsx index cdf5ae415..8cb5118db 100644 --- a/packages/jsonConfig/src/JsonConfigComponent/ConfigCheckbox.tsx +++ b/packages/jsonConfig/src/JsonConfigComponent/ConfigCheckbox.tsx @@ -8,11 +8,10 @@ import { FormControl, } from '@mui/material'; -import { type AdminConnection, I18n } from '@iobroker/adapter-react-v5'; -import type { ThemeName, ThemeType } from '@iobroker/adapter-react-v5/types'; +import { I18n } from '@iobroker/adapter-react-v5'; +import type { ConfigItemCheckbox } from '#JC/types'; import ConfigGeneric, { type ConfigGenericProps, type ConfigGenericState } from './ConfigGeneric'; -import type {ConfigItemCheckbox} from "#JC/types"; const styles: Record = { error: { diff --git a/packages/jsonConfig/src/JsonConfigComponent/ConfigDeviceManager.tsx b/packages/jsonConfig/src/JsonConfigComponent/ConfigDeviceManager.tsx index 510839367..e040b18ae 100644 --- a/packages/jsonConfig/src/JsonConfigComponent/ConfigDeviceManager.tsx +++ b/packages/jsonConfig/src/JsonConfigComponent/ConfigDeviceManager.tsx @@ -1,7 +1,5 @@ import React from 'react'; -import DeviceManager from '@iobroker/dm-gui-components'; - import type { ConfigItemDeviceManager } from '#JC/types'; import ConfigGeneric, { type ConfigGenericProps, type ConfigGenericState } from './ConfigGeneric'; @@ -17,16 +15,23 @@ class ConfigDeviceManager extends ConfigGeneric; + if (this.props.DeviceManager) { + const DeviceManager = this.props.DeviceManager; + return ; + } + + return
+ DeviceManager not found +
; } } diff --git a/packages/jsonConfig/src/JsonConfigComponent/ConfigGeneric.tsx b/packages/jsonConfig/src/JsonConfigComponent/ConfigGeneric.tsx index accea2f6d..37f8a13b3 100644 --- a/packages/jsonConfig/src/JsonConfigComponent/ConfigGeneric.tsx +++ b/packages/jsonConfig/src/JsonConfigComponent/ConfigGeneric.tsx @@ -18,8 +18,9 @@ import { import { Confirm as ConfirmDialog, Icon, Utils, type AdminConnection, I18n, + type Connection, + type ThemeType, type ThemeName, } from '@iobroker/adapter-react-v5'; -import type { ThemeType, ThemeName } from '@iobroker/adapter-react-v5/types'; import type { ConfigItemAny, ConfigItemConfirmData } from '#JC/types'; // because this class is used in adapter-react-v5, do not include here any foreign files like from '../../helpers/utils.ts' @@ -32,7 +33,32 @@ export function isObject(it: any): it is Record { // return it && typeof it === 'object' && !(it instanceof Array); } +export interface DeviceManagerPropsProps { + /* socket object */ + socket: Connection; + /* Instance to communicate with device-manager backend, like `adapterName.X` */ + selectedInstance: string; // adapterName.X + registerHandler?: (handler: null | ((command: string) => void)) => void; + themeName: ThemeName; + themeType: ThemeType; + isFloatComma: boolean; + dateFormat: string; + /** Instance to upload images to, like `adapterName.X` */ + uploadImagesToInstance?: string; + /** Filter devices with this string */ + filter?: string; + /** If this component is used in GUI with own toolbar. `false` if this list is used with multiple instances and true if only with one (in this case, it will monitor alive itself */ + embedded?: boolean; + /** If embedded, this text is shown in the toolbar */ + title?: string; + /** Style of a component that displays all devices */ + style?: React.CSSProperties; + /** Use small cards for devices */ + smallCards?: boolean; +} + export interface ConfigGenericProps { + DeviceManager?: React.FC; adapterName: string; alive: boolean; arrayIndex?: number; diff --git a/packages/jsonConfig/src/JsonConfigComponent/ConfigPanel.tsx b/packages/jsonConfig/src/JsonConfigComponent/ConfigPanel.tsx index 14b232678..83d86301f 100644 --- a/packages/jsonConfig/src/JsonConfigComponent/ConfigPanel.tsx +++ b/packages/jsonConfig/src/JsonConfigComponent/ConfigPanel.tsx @@ -6,12 +6,12 @@ import { Accordion, AccordionSummary, AccordionDetails, - Typography, type Theme, + Typography, } from '@mui/material'; import { ExpandMore as ExpandMoreIcon } from '@mui/icons-material'; -import { Utils } from '@iobroker/adapter-react-v5'; +import { Utils, type IobTheme } from '@iobroker/adapter-react-v5'; import type { ConfigItemPanel } from '#JC/types'; import ConfigGeneric, { type ConfigGenericState, type ConfigGenericProps } from './ConfigGeneric'; @@ -122,7 +122,7 @@ const components = { user: ConfigUser, }; -const styles: Record = (theme: Theme) => ({ +const styles: Record = (theme: IobTheme) => ({ fullWidth: { width: '100%', // height: '100%', diff --git a/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx b/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx index bc5ee2406..57512a0f2 100644 --- a/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx +++ b/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx @@ -10,7 +10,6 @@ import { TextField, Toolbar, Tooltip, Typography, FormHelperText, - type Theme, } from '@mui/material'; import { @@ -28,7 +27,7 @@ import { Close as IconClose, } from '@mui/icons-material'; -import { Utils, I18n } from '@iobroker/adapter-react-v5'; +import { Utils, I18n, type IobTheme } from '@iobroker/adapter-react-v5'; import type {ConfigItemTableIndexed, ConfigItemPanel, ConfigItemTable} from '#JC/types'; import ConfigGeneric, { type ConfigGenericProps, type ConfigGenericState } from './ConfigGeneric'; @@ -37,7 +36,7 @@ import ConfigPanel from './ConfigPanel'; const MAX_SIZE = 1024 * 1024; // 1MB -const styles: Record = (theme: Theme) => ({ +const styles: Record = (theme: IobTheme) => ({ fullWidth: { width: '100%', }, diff --git a/packages/jsonConfig/src/JsonConfigComponent/ConfigTabs.tsx b/packages/jsonConfig/src/JsonConfigComponent/ConfigTabs.tsx index f58245208..a96a73335 100644 --- a/packages/jsonConfig/src/JsonConfigComponent/ConfigTabs.tsx +++ b/packages/jsonConfig/src/JsonConfigComponent/ConfigTabs.tsx @@ -6,7 +6,6 @@ import { Tabs, Tab } from '@mui/material'; import type { ConfigItemTabs } from '#JC/types'; import ConfigGeneric, { type ConfigGenericProps, type ConfigGenericState } from './ConfigGeneric'; import ConfigPanel from './ConfigPanel'; -import {bool} from "prop-types"; const styles: Record = { tabs: { diff --git a/packages/jsonConfig/src/JsonConfigComponent/index.tsx b/packages/jsonConfig/src/JsonConfigComponent/index.tsx index 1b585feb5..d6fe81e0c 100644 --- a/packages/jsonConfig/src/JsonConfigComponent/index.tsx +++ b/packages/jsonConfig/src/JsonConfigComponent/index.tsx @@ -3,10 +3,14 @@ import { withStyles } from '@mui/styles'; import { LinearProgress } from '@mui/material'; -import {type AdminConnection, I18n, Utils} from '@iobroker/adapter-react-v5'; -import type { ThemeName, ThemeType } from '@iobroker/adapter-react-v5/types'; +import { + type AdminConnection, I18n, + Utils, type ThemeName, + type ThemeType, +} from '@iobroker/adapter-react-v5'; import type { ConfigItemPanel, ConfigItemTabs } from '#JC/types'; +import type { DeviceManagerPropsProps } from '#JC/JsonConfigComponent/ConfigGeneric'; import ConfigTabs from './ConfigTabs'; import ConfigPanel from './ConfigPanel'; @@ -41,6 +45,7 @@ interface JsonConfigComponentProps { customs?: Record; classes: Record; className?: string; + DeviceManager?: React.FC; } interface JsonConfigComponentState { @@ -358,6 +363,7 @@ export class JsonConfigComponentClass extends Component this.onError(attr, error)} + DeviceManager={this.props.DeviceManager} />; } if (item.type === 'panel' || @@ -394,6 +400,7 @@ export class JsonConfigComponentClass extends Component this.onError(attr, error)} + DeviceManager={this.props.DeviceManager} />; } diff --git a/packages/jsonConfig/src/JsonConfigComponent/wrapper/Components/CustomModal.jsx b/packages/jsonConfig/src/JsonConfigComponent/wrapper/Components/CustomModal.tsx similarity index 83% rename from packages/jsonConfig/src/JsonConfigComponent/wrapper/Components/CustomModal.jsx rename to packages/jsonConfig/src/JsonConfigComponent/wrapper/Components/CustomModal.tsx index 88b0f8dbc..d58da1897 100644 --- a/packages/jsonConfig/src/JsonConfigComponent/wrapper/Components/CustomModal.jsx +++ b/packages/jsonConfig/src/JsonConfigComponent/wrapper/Components/CustomModal.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; -import { withStyles } from '@mui/styles'; +import {type Styles, withStyles} from '@mui/styles'; import { Dialog, DialogActions, DialogContent, @@ -13,9 +13,9 @@ import { Language as LanguageIcon, } from '@mui/icons-material'; -import { Utils, I18n } from '@iobroker/adapter-react-v5'; +import { Utils, I18n, type IobTheme } from '@iobroker/adapter-react-v5'; -const styles = theme => ({ +const styles: Styles = theme => ({ modalDialog: { minWidth: 400, maxWidth: 800, @@ -40,9 +40,31 @@ const styles = theme => ({ }, }); +interface CustomModalProps { + toggleTranslation?: () => void; + noTranslation?: boolean; + title: string; + fullWidth?: boolean; + help?: string; + maxWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | false; + progress?: boolean; + icon?: any; + applyDisabled?: boolean; + applyButton?: boolean; + classes: Record; + onClose: () => void; + children: any; + titleButtonApply: string; + titleButtonClose: string; + onApply: (value: string) => void; + textInput?: boolean; + defaultValue: string; + overflowHidden?: boolean; +} + const CustomModal = ({ toggleTranslation, noTranslation, title, fullWidth, help, maxWidth, progress, icon, applyDisabled, applyButton, classes, onClose, children, titleButtonApply, titleButtonClose, onApply, textInput, defaultValue, overflowHidden, -}) => { +}: CustomModalProps) => { const [value, setValue] = useState(defaultValue); useEffect(() => { setValue(defaultValue); @@ -102,6 +124,7 @@ const CustomModal = ({ {I18n.t(titleButtonApply)} }