diff --git a/.eslintrc.json b/.eslintrc.json
index 6efbca4..7e45b26 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -5,6 +5,7 @@
"mocha": true
},
"extends": "eslint:recommended",
+ "ignorePatterns": ["src/**/*.*", "gulpfile.js"],
"rules": {
"indent": [
"error",
diff --git a/.gitignore b/.gitignore
index 72f06f0..912921d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,5 @@ admin/i18n/*/flat.txt
system.config.json
*.code-workspace
package-lock.json
+/src/build
+/admin/custom
diff --git a/.releaseconfig.json b/.releaseconfig.json
index ec98e71..450e512 100644
--- a/.releaseconfig.json
+++ b/.releaseconfig.json
@@ -1,4 +1,7 @@
{
"dry": false,
- "plugins": ["iobroker", "license"]
+ "plugins": ["iobroker", "license"],
+ "exec": {
+ "before_commit": "npm run build"
+ }
}
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 5357396..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-os:
- - linux
- - osx
- - windows
-language: node_js
-node_js:
- - '12'
- - '14'
- - '16'
-env:
- - CXX=g++-6
-addons:
- apt:
- sources:
- - ubuntu-toolchain-r-test
- packages:
- - g++-6
-before_install:
- - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then CC=gcc-6; fi'
- - 'if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then CC=g++-6; fi'
-before_script:
- - export NPMVERSION=$(echo "$($(which npm) -v)"|cut -c1)
- - 'if [[ $NPMVERSION == 5 ]]; then npm install -g npm; fi'
- - npm -v
-script:
- - 'npm run test:package'
- - 'npm run test:unit'
- - 'export DEBUG=testing:*'
- - 'npm run test:integration'
diff --git a/admin/jsonCustom.json b/admin/jsonCustom.json
index c64919a..a6c48e5 100644
--- a/admin/jsonCustom.json
+++ b/admin/jsonCustom.json
@@ -6,58 +6,12 @@
"hidden": "data.isLinked !== undefined",
"type": "panel",
"items": {
- "_prefixId": {
- "type": "autocompleteSendTo",
- "label": "prefix for id of linked object",
- "sm": 8,
- "noMultiEdit": true,
- "freeSolo": true,
- "noTranslation": true,
- "command": "suggestions_prefixId",
- "maxLength": 255,
- "defaultFunc": {
- "func": "(data.linkedId || '').includes('.') ? data.linkedId.substring(0, data.linkedId.lastIndexOf('.')) : ''",
- "alsoDependsOn": [
- "linkedId"
- ]
- },
- "validator": "data._prefixId && data._prefixId.length > 0 && !(/[*?\"'`´,;:<>#/{}ß\\[\\]\\s]/).test(data._prefixId) || data._prefixId === ''",
- "help": "${data._prefixId && data._prefixId.length > 0 && !(/[*?\"'`´,;:<>#/{}ß\\[\\]\\s]/).test(data._prefixId) || data._prefixId === '' ? '' : 'the following chars are not allowed \\'*?\"\\'`´,;:<>#/{}ß[] \\''}"
- },
- "_stateId": {
- "type": "autocompleteSendTo",
- "label": "id of linked object",
- "sm": 4,
- "noMultiEdit": true,
- "freeSolo": true,
- "noTranslation": true,
- "command": "suggestions_stateId",
- "maxLength": 255,
- "defaultFunc": {
- "func": "(data.linkedId || '').split('.').pop()",
- "alsoDependsOn": [
- "linkedId"
- ]
- },
- "validator": "data._stateId && data._stateId.length > 0 && !(/[*?\"'`´,;:<>#/{}ß\\[\\]\\s]/).test(data._stateId)",
- "help": "${data._stateId && data._stateId.length > 0 && !(/[*?\"'`´,;:<>#/{}ß\\[\\]\\s]/).test(data._stateId) ? '' : 'please enter a valid id, the following chars are not allowed \\'*?\"\\'`´,;:<>#/{}ß[] \\''}"
- },
"linkedId": {
- "type": "text",
- "label": "composite id of linked object",
- "sm": 12,
- "noMultiEdit": true,
- "maxLength": 255,
- "disabled": "true",
- "defaultFunc": "data.linkedId || customObj._id.substring(customObj._id.lastIndexOf('.') + 1)",
- "onChange": {
- "alsoDependsOn": [
- "_prefixId",
- "_stateId"
- ],
- "calculateFunc": "data._prefixId.length > 0 && data._stateId.length > 0 ? (data._prefixId || '') + '.' + (data._stateId || '') : (data._stateId || '')",
- "ignoreOwnChanges": true
- }
+ "type": "custom",
+ "name": "AdminComponentLinkedDevicesSet/Components/LinkedIdComponent",
+ "i18n": false,
+ "url": "custom/customComponents.js",
+ "sm": 12
},
"name": {
"type": "autocompleteSendTo",
diff --git a/gulpfile.js b/gulpfile.js
index 3e2be2b..7876143 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -1,3 +1,88 @@
-"use strict";
+const gulp = require('gulp');
+const fs = require('fs');
+const cp = require('child_process');
+const del = require('del');
+const src = `${__dirname}/src/`;
-module.exports = require("@iobroker/adapter-dev/gulp")();
\ No newline at end of file
+function npmInstall() {
+ return new Promise((resolve, reject) => {
+ // Install node modules
+ const cwd = src.replace(/\\/g, '/');
+
+ const cmd = `npm install -f`;
+ console.log(`"${cmd} in ${cwd}`);
+
+ // System call used for update of js-controller itself,
+ // because during installation npm packet will be deleted too, but some files must be loaded even during the install process.
+ const exec = cp.exec;
+ const child = exec(cmd, {cwd});
+
+ child.stderr.pipe(process.stderr);
+ child.stdout.pipe(process.stdout);
+
+ child.on('exit', (code /* , signal */) => {
+ // code 1 is strange error that cannot be explained. Everything is installed but error :(
+ if (code && code !== 1) {
+ reject('Cannot install: ' + code);
+ } else {
+ console.log(`"${cmd} in ${cwd} finished.`);
+ // command succeeded
+ resolve();
+ }
+ });
+ });
+}
+
+function build() {
+ const version = JSON.parse(fs.readFileSync(__dirname + '/package.json').toString('utf8')).version;
+ const data = JSON.parse(fs.readFileSync(src + 'package.json').toString('utf8'));
+
+ data.version = version;
+
+ fs.writeFileSync(src + 'package.json', JSON.stringify(data, null, 4));
+
+ return new Promise((resolve, reject) => {
+ const options = {
+ stdio: 'pipe',
+ cwd: src
+ };
+
+ console.log(options.cwd);
+
+ let script = src + 'node_modules/@craco/craco/bin/craco.js';
+ if (!fs.existsSync(script)) {
+ script = __dirname + '/node_modules/@craco/craco/bin/craco.js';
+ }
+ if (!fs.existsSync(script)) {
+ console.error('Cannot find execution file: ' + script);
+ reject('Cannot find execution file: ' + script);
+ } else {
+ const child = cp.fork(script, ['build'], options);
+ child.stdout.on('data', data => console.log(data.toString()));
+ child.stderr.on('data', data => console.log(data.toString()));
+ child.on('close', code => {
+ console.log(`child process exited with code ${code}`);
+ code ? reject(`Exit code: ${code}`) : resolve();
+ });
+ }
+ });
+}
+
+gulp.task('0-clean', () => del(['admin/custom/*', 'admin/custom/**/*', 'src/build/**/*']));
+
+gulp.task('1-npm', async () => npmInstall());
+gulp.task('2-compile', async () => build());
+
+gulp.task('3-copy', () => Promise.all([
+ gulp.src(['src/build/static/js/*.js']).pipe(gulp.dest('admin/custom/static/js')),
+ gulp.src(['src/build/static/js/*.map']).pipe(gulp.dest('admin/custom/static/js')),
+ gulp.src(['src/build/customComponents.js']).pipe(gulp.dest('admin/custom')),
+ gulp.src(['src/build/customComponents.js.map']).pipe(gulp.dest('admin/custom')),
+ gulp.src(['src/src/i18n/*.json']).pipe(gulp.dest('admin/custom/i18n')),
+]));
+
+gulp.task('build', gulp.series(['0-clean', '1-npm', '2-compile', '3-copy']));
+
+gulp.task('default', gulp.series(['build']));
+
+// gulp.task('translate', async () => require('@iobroker/adapter-dev').translate());
\ No newline at end of file
diff --git a/package.json b/package.json
index 8d484fb..6a15f86 100644
--- a/package.json
+++ b/package.json
@@ -45,12 +45,14 @@
"chai": "^4.3.6",
"eslint": "^7.32.0",
"gulp": "^4.0.2",
- "mocha": "^9.2.2"
+ "mocha": "^9.2.2",
+ "del": "^6.1.1"
},
"main": "main.js",
"scripts": {
"test": "node node_modules/mocha/bin/mocha --exit",
"translate": "node node_modules/gulp/bin/gulp.js adminWords2languages",
+ "build": "gulp",
"release": "release-script",
"release-patch": "release-script patch --c .releaseconfig.json --all",
"release-minor": "release-script minor --c .releaseconfig.json --all",
diff --git a/src/craco.config.js b/src/craco.config.js
new file mode 100644
index 0000000..407996d
--- /dev/null
+++ b/src/craco.config.js
@@ -0,0 +1,33 @@
+const CracoEsbuildPlugin = require('craco-esbuild');
+const { ProvidePlugin } = require('webpack');
+const cracoModuleFederation = require('craco-module-federation');
+
+module.exports = {
+ plugins: [
+ { plugin: CracoEsbuildPlugin },
+ { plugin: cracoModuleFederation, options: { useNamedChunkIds: true } }
+ ],
+ devServer: {
+ proxy: {
+ '/files': 'http://localhost:8081',
+ '/adapter': 'http://localhost:8081',
+ '/session': 'http://localhost:8081',
+ '/log': 'http://localhost:8081',
+ '/lib': 'http://localhost:8081',
+ }
+ },
+ webpack: {
+ output: {
+ publicPath: './',
+ },
+ plugins: [
+ new ProvidePlugin({
+ React: 'react',
+ }),
+ ],
+ configure: webpackConfig => {
+ webpackConfig.output.publicPath = './';
+ return webpackConfig;
+ },
+ },
+};
diff --git a/src/modulefederation.config.js b/src/modulefederation.config.js
new file mode 100644
index 0000000..0597eea
--- /dev/null
+++ b/src/modulefederation.config.js
@@ -0,0 +1,21 @@
+const makeShared = pkgs => {
+ const result = {};
+ pkgs.forEach(
+ packageName => {
+ result[packageName] = {
+ requiredVersion: '*',
+ singleton: true,
+ };
+ },
+ );
+ return result;
+};
+
+module.exports = {
+ name: 'AdminComponentLinkedDevicesSet',
+ filename: 'customComponents.js',
+ exposes: {
+ './Components': './src/Components.jsx',
+ },
+ shared: makeShared(['@mui/material', '@mui/styles', '@iobroker/adapter-react-v5', 'react', 'react-dom', 'prop-types'])
+};
diff --git a/src/package.json b/src/package.json
new file mode 100644
index 0000000..e585ee9
--- /dev/null
+++ b/src/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "iobroker-admin-component-easy-access",
+ "private": true,
+ "version": "1.5.5",
+ "scripts": {
+ "start": "set PORT=4173 && craco start",
+ "build": "craco build"
+ },
+ "dependencies": {},
+ "devDependencies": {
+ "@emotion/react": "^11.9.0",
+ "@emotion/styled": "^11.8.1",
+ "@iobroker/adapter-react-v5": "^3.0.14",
+ "@mui/material": "^5.8.1",
+ "@mui/styles": "^5.8.0",
+ "prop-types": "^15.8.1",
+ "react": "^18.1.0",
+ "react-ace": "^10.1.0",
+ "react-dom": "^18.1.0",
+ "react-scripts": "^5.0.1",
+ "react-refresh": "^0.14.0",
+ "@originjs/vite-plugin-federation": "^1.1.6",
+ "@rollup/plugin-babel": "^5.3.1",
+ "@rollup/plugin-commonjs": "^22.0.0",
+ "@rollup/plugin-node-resolve": "^13.3.0",
+ "@rollup/plugin-replace": "^4.0.0",
+ "@types/react": "^18.0.9",
+ "@types/react-dom": "^18.0.5",
+ "@craco/craco": "^6.4.3",
+ "craco-esbuild": "^0.5.1",
+ "craco-module-federation": "^1.1.0"
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/src/public/index.html b/src/public/index.html
new file mode 100644
index 0000000..56783b2
--- /dev/null
+++ b/src/public/index.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+ Admin App
+
+
+
+
+
+
+
diff --git a/src/src/App.jsx b/src/src/App.jsx
new file mode 100644
index 0000000..9ccf778
--- /dev/null
+++ b/src/src/App.jsx
@@ -0,0 +1,78 @@
+// this file used only for simulation and not used in end build
+
+import React from 'react';
+import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles';
+
+import withStyles from '@mui/styles/withStyles';
+
+import GenericApp from '@iobroker/adapter-react-v5/GenericApp';
+import I18n from '@iobroker/adapter-react-v5/i18n';
+import Loader from '@iobroker/adapter-react-v5/Components/Loader';
+
+import LinkedIdComponent from './LinkedIdComponent';
+
+const styles = theme => ({
+ app: {
+ backgroundColor: theme.palette.background.default,
+ color: theme.palette.text.primary,
+ height: '100%',
+ },
+ item: {
+ padding: 50,
+ width: 400,
+ }
+});
+
+class App extends GenericApp {
+ constructor(props) {
+ const extendedProps = { ...props };
+ super(props, extendedProps);
+
+ this.state = {
+ data: { myCustomAttribute: 'prefix1.prefix2.state' },
+ theme: this.createTheme(),
+ };
+
+ I18n.setLanguage((navigator.language || navigator.userLanguage || 'en').substring(0, 2).toLowerCase());
+ }
+
+ render() {
+ if (!this.state.loaded) {
+ return
+
+
+
+ ;
+ }
+
+ return
+
+
+
+ {}}
+ customObj={{ _id: 'test.0.myPrefix.myState' }}
+ alive
+ adapterName="linkeddevices"
+ instance="0"
+ schema={{
+ name: 'AdminComponentLinkedDevicesSet/Components/LinkedIdComponent',
+ type: 'custom',
+ }}
+ onChange={data => {
+ this.setState({ data });
+ }}
+ />
+
+
+
+ ;
+ }
+}
+
+export default withStyles(styles)(App);
\ No newline at end of file
diff --git a/src/src/Components.jsx b/src/src/Components.jsx
new file mode 100644
index 0000000..d2c6f05
--- /dev/null
+++ b/src/src/Components.jsx
@@ -0,0 +1,3 @@
+import LinkedIdComponent from './LinkedIdComponent';
+
+export default { LinkedIdComponent };
\ No newline at end of file
diff --git a/src/src/LinkedIdComponent.jsx b/src/src/LinkedIdComponent.jsx
new file mode 100644
index 0000000..0f8b05f
--- /dev/null
+++ b/src/src/LinkedIdComponent.jsx
@@ -0,0 +1,203 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withStyles } from '@mui/styles';
+
+import { Autocomplete, TextField, Grid } from '@mui/material';
+
+// important to make from package and not from some children.
+// invalid
+// import ConfigGeneric from '@iobroker/adapter-react-v5/ConfigGeneric';
+// valid
+import { ConfigGeneric, I18n } from '@iobroker/adapter-react-v5';
+
+const styles = theme => ({
+ table: {
+ minWidth: 400
+ },
+ header: {
+ fontSize: 16,
+ fontWeight: 'bold'
+ }
+});
+
+class LinkedIdComponent extends ConfigGeneric {
+ componentDidMount() {
+ super.componentDidMount();
+ let value = ConfigGeneric.getValue(this.props.data, this.props.attr);
+ value = value || this.props.customObj._id.substring(this.props.customObj._id.lastIndexOf('.') + 1);
+
+ this.setState({ value, suggestions_stateId: [], suggestions_prefixId: [] });
+
+ // Send request to instance
+ this.askInstance('suggestions_stateId')
+ .then(() => this.askInstance('suggestions_prefixId'));
+ }
+
+ askInstance(command) {
+ if (this.props.alive) {
+ return this.props.socket.sendTo(`${this.props.adapterName}.${this.props.instance}`, command, null)
+ .then(list => {
+ const options = [];
+ if (list && Array.isArray(list)) {
+ list.forEach(item =>
+ options.push({label: item, value: item}));
+ }
+ this.setState({[command]: options});
+ });
+ } else {
+ return Promise.resolve(null);
+ }
+ }
+
+ renderPrefixId() {
+ let prefixId = (this.state.value || '').includes('.') ? this.state.value.substring(0, this.state.value.lastIndexOf('.')) : '';
+ let options = JSON.parse(JSON.stringify(this.state.suggestions_prefixId || []));
+ let item = prefixId !== null && prefixId !== undefined &&
+ //eslint-disable-next-line
+ options.find(item => item.value === prefixId); // let "==" be and not ===
+
+ if (prefixId !== null && prefixId !== undefined && !item) {
+ item = {value: prefixId, label: prefixId};
+ options.push(item);
+ }
+ item = item || null;
+
+ const noError = (prefixId && prefixId.length > 0 && !(/[*?"'`´,;:<>#/{}ß\[\]\s]/).test(prefixId)) || prefixId === '';
+
+ return
+ (option && option.label) || ''}
+ className={this.props.classes.indeterminate}
+ onInputChange={e => {
+ if (e) {
+ const val = e.target.value;
+ let prefixId = (this.state.value || '').includes('.') ? this.state.value.substring(0, this.state.value.lastIndexOf('.')) : '';
+ if (val !== prefixId) {
+ let stateId = (this.state.value || '').split('.').pop();
+ const value = LinkedIdComponent.calcValue(val, stateId);
+ this.setState({ value }, () => this.onChange(this.props.attr, value));
+ }
+ }
+ }}
+ onChange={(_, value) => {
+ const val = typeof value === 'object' ? (value ? value.value : '') : value;
+ let prefixId = (this.state.value || '').includes('.') ? this.state.value.substring(0, this.state.value.lastIndexOf('.')) : '';
+ if (val !== prefixId) {
+ let stateId = (this.state.value || '').split('.').pop();
+ const value = LinkedIdComponent.calcValue(val, stateId);
+ this.setState({ value }, () =>
+ this.onChange(this.props.attr, value));
+ }
+ }}
+ renderInput={(params) =>
+ }
+ />
+ ;
+ }
+
+ static calcValue(prefix, stateId) {
+ return prefix && stateId ? `${prefix}.${stateId}` : (stateId || '');
+ }
+
+ renderStateId() {
+ let stateId = (this.state.value || '').split('.').pop();
+ let options = JSON.parse(JSON.stringify(this.state.suggestions_stateId || []));
+ let item = stateId !== null && stateId !== undefined &&
+ //eslint-disable-next-line
+ options.find(item => item.value === stateId); // let "==" be and not ===
+
+ if (stateId !== null && stateId !== undefined && !item) {
+ item = {value: stateId, label: stateId};
+ options.push(item);
+ }
+ item = item || null;
+
+ const noError = stateId && stateId.length > 0 && !(/[*?"'`´,;:<>#/{}ß\[\]\s]/).test(stateId);
+
+ return
+ (option && option.label) || ''}
+ className={this.props.classes.indeterminate}
+ onInputChange={e => {
+ if (e) {
+ const val = e.target.value;
+ let stateId = (this.state.value || '').split('.').pop();
+ if (val !== stateId) {
+ let prefixId = (this.state.value || '').includes('.') ? this.state.value.substring(0, this.state.value.lastIndexOf('.')) : '';
+ const value = LinkedIdComponent.calcValue(prefixId, val);
+ this.setState({ value }, () => this.onChange(this.props.attr, value));
+ }
+ }
+ }}
+ onChange={(_, value) => {
+ const val = typeof value === 'object' ? (value ? value.value : '') : value;
+ let stateId = (this.state.value || '').split('.').pop();
+ if (val !== stateId) {
+ let prefixId = (this.state.value || '').includes('.') ? this.state.value.substring(0, this.state.value.lastIndexOf('.')) : '';
+ const value = LinkedIdComponent.calcValue(prefixId, val);
+ this.setState({ value }, () => this.onChange(this.props.attr, value));
+ }
+ }}
+ renderInput={(params) =>
+ }
+ />
+ ;
+ }
+
+ renderItem() {
+ return
+ {this.renderPrefixId()}
+ {this.renderStateId()}
+
+
+
+ ;
+ }
+}
+
+LinkedIdComponent.propTypes = {
+ socket: PropTypes.object.isRequired,
+ themeType: PropTypes.string,
+ themeName: PropTypes.string,
+ style: PropTypes.object,
+ className: PropTypes.string,
+ data: PropTypes.object.isRequired,
+ attr: PropTypes.string,
+ schema: PropTypes.object,
+ onError: PropTypes.func,
+ onChange: PropTypes.func,
+};
+
+export default withStyles(styles)(LinkedIdComponent);
\ No newline at end of file
diff --git a/src/src/bootstrap.jsx b/src/src/bootstrap.jsx
new file mode 100644
index 0000000..d8e9845
--- /dev/null
+++ b/src/src/bootstrap.jsx
@@ -0,0 +1,32 @@
+// this file used only for simulation and not used in end build
+
+/* eslint-disable */
+import React from 'react';
+import { createRoot } from 'react-dom/client';
+import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles';
+import Utils from '@iobroker/adapter-react-v5/Components/Utils';
+import App from './App';
+import theme from './theme';
+
+window.adapterName = 'adapter-component-linked-devices';
+let themeName = Utils.getThemeName();
+
+function build() {
+ const container = document.getElementById('root');
+ const root = createRoot(container);
+ return root.render(
+
+
+ {
+ themeName = _theme;
+ build();
+ }}
+ />
+
+
+ );
+}
+
+build();
\ No newline at end of file
diff --git a/src/src/i18n/de.json b/src/src/i18n/de.json
new file mode 100644
index 0000000..e8dbd1c
--- /dev/null
+++ b/src/src/i18n/de.json
@@ -0,0 +1,7 @@
+{
+ "Prefix for ID of linked object": "Präfix für die ID des verknüpften Objekts",
+ "error_prefix": "folgende Zeichen sind nicht erlaubt '*?\"`´,;:<>#/{}ß[] und Leerzeichen",
+ "ID of linked object": "ID des verknüpften Objekts",
+ "error_stateId": "Bitte geben Sie eine gültige ID ein, die folgenden Zeichen sind nicht erlaubt *?\"'`´,;:<>#/{}ß[] und Leerzeichen",
+ "Composite id of linked object": "Zusammengesetzte ID des verknüpften Objekts"
+}
\ No newline at end of file
diff --git a/src/src/i18n/en.json b/src/src/i18n/en.json
new file mode 100644
index 0000000..c11bada
--- /dev/null
+++ b/src/src/i18n/en.json
@@ -0,0 +1,7 @@
+{
+ "Prefix for ID of linked object": "Prefix for ID of linked object",
+ "error_prefix": "the following chars are not allowed '*?\"`´,;:<>#/{}ß[] and spaces",
+ "ID of linked object": "ID of linked object",
+ "error_stateId": "please enter a valid id, the following chars are not allowed *?\"'`´,;:<>#/{}ß[] and spaces",
+ "Composite id of linked object": "Composite id of linked object"
+}
\ No newline at end of file
diff --git a/src/src/i18n/es.json b/src/src/i18n/es.json
new file mode 100644
index 0000000..839aa70
--- /dev/null
+++ b/src/src/i18n/es.json
@@ -0,0 +1,7 @@
+{
+ "Prefix for ID of linked object": "Prefijo para ID de objeto vinculado",
+ "error_prefix": "los siguientes caracteres no están permitidos '*?\"`´,;:<>#/{}ß[] y espacios",
+ "ID of linked object": "ID del objeto vinculado",
+ "error_stateId": "ingrese una identificación válida, los siguientes caracteres no están permitidos *?\"'`´,;:<>#/{}ß[] y espacios",
+ "Composite id of linked object": "ID compuesto del objeto vinculado"
+}
\ No newline at end of file
diff --git a/src/src/i18n/fr.json b/src/src/i18n/fr.json
new file mode 100644
index 0000000..82d27bf
--- /dev/null
+++ b/src/src/i18n/fr.json
@@ -0,0 +1,7 @@
+{
+ "Prefix for ID of linked object": "Préfixe pour l'ID de l'objet lié",
+ "error_prefix": "les caractères suivants ne sont pas autorisés '*?\"`´,;:<>#/{}ß[] et espaces",
+ "ID of linked object": "ID de l'objet lié",
+ "error_stateId": "veuillez saisir un identifiant valide, les caractères suivants ne sont pas autorisés *?\"'`´,;:<>#/{}ß[] et espaces",
+ "Composite id of linked object": "ID composite de l'objet lié"
+}
\ No newline at end of file
diff --git a/src/src/i18n/it.json b/src/src/i18n/it.json
new file mode 100644
index 0000000..1f106e6
--- /dev/null
+++ b/src/src/i18n/it.json
@@ -0,0 +1,7 @@
+{
+ "Prefix for ID of linked object": "Prefisso per l'ID dell'oggetto collegato",
+ "error_prefix": "i seguenti caratteri non sono consentiti '*?\"`´,;:<>#/{}ß[] e spazi",
+ "ID of linked object": "ID dell'oggetto collegato",
+ "error_stateId": "inserisci un ID valido, i seguenti caratteri non sono consentiti *?\"'`´,;:<>#/{}ß[] e spazi",
+ "Composite id of linked object": "ID composito dell'oggetto collegato"
+}
\ No newline at end of file
diff --git a/src/src/i18n/nl.json b/src/src/i18n/nl.json
new file mode 100644
index 0000000..3621eb4
--- /dev/null
+++ b/src/src/i18n/nl.json
@@ -0,0 +1,7 @@
+{
+ "Prefix for ID of linked object": "Prefix voor ID van gekoppeld object",
+ "error_prefix": "de volgende tekens zijn niet toegestaan '*?\"`´,;:<>#/{}ß[] en spaties",
+ "ID of linked object": "ID van gekoppeld object",
+ "error_stateId": "voer een geldige id in, de volgende tekens zijn niet toegestaan *?\"''´,;:<>#/{}ß[] en spaties",
+ "Composite id of linked object": "Samengestelde id van gekoppeld object"
+}
\ No newline at end of file
diff --git a/src/src/i18n/pl.json b/src/src/i18n/pl.json
new file mode 100644
index 0000000..e17ce39
--- /dev/null
+++ b/src/src/i18n/pl.json
@@ -0,0 +1,7 @@
+{
+ "Prefix for ID of linked object": "Prefiks dla identyfikatora połączonego obiektu",
+ "error_prefix": "następujące znaki nie są dozwolone '*?\"`',;:<>#/{}ß[] i spacje",
+ "ID of linked object": "Identyfikator połączonego obiektu",
+ "error_stateId": "proszę podać poprawny identyfikator, następujące znaki nie są dozwolone *?\"'`´,;:<>#/{}ß[] i spacje",
+ "Composite id of linked object": "Złożony identyfikator połączonego obiektu"
+}
\ No newline at end of file
diff --git a/src/src/i18n/pt.json b/src/src/i18n/pt.json
new file mode 100644
index 0000000..ea9426b
--- /dev/null
+++ b/src/src/i18n/pt.json
@@ -0,0 +1,7 @@
+{
+ "Prefix for ID of linked object": "Prefixo para ID do objeto vinculado",
+ "error_prefix": "os seguintes caracteres não são permitidos '*?\"`´,;:<>#/{}ß[] e espaços",
+ "ID of linked object": "ID do objeto vinculado",
+ "error_stateId": "digite um id válido, os seguintes caracteres não são permitidos *?\"'`´,;:<>#/{}ß[] e espaços",
+ "Composite id of linked object": "ID composto do objeto vinculado"
+}
\ No newline at end of file
diff --git a/src/src/i18n/ru.json b/src/src/i18n/ru.json
new file mode 100644
index 0000000..b863bec
--- /dev/null
+++ b/src/src/i18n/ru.json
@@ -0,0 +1,7 @@
+{
+ "Prefix for ID of linked object": "Префикс для идентификатора связанного объекта",
+ "error_prefix": "следующие символы не допускаются '*?\"`´,;:<>#/{}ß[] и пробелы",
+ "ID of linked object": "ID связанного объекта",
+ "error_stateId": "пожалуйста, введите действительный идентификатор, следующие символы не допускаются *?\"'`´,;:<>#/{}ß[] и пробелы",
+ "Composite id of linked object": "Составной идентификатор связанного объекта"
+}
\ No newline at end of file
diff --git a/src/src/i18n/zh-cn.json b/src/src/i18n/zh-cn.json
new file mode 100644
index 0000000..6c2fc66
--- /dev/null
+++ b/src/src/i18n/zh-cn.json
@@ -0,0 +1,7 @@
+{
+ "Prefix for ID of linked object": "链接对象的 ID 前缀",
+ "error_prefix": "不允许使用以下字符 '*?\"`´,;:<>#/{}ß[] 和空格",
+ "ID of linked object": "链接对象的 ID",
+ "error_stateId": "请输入有效的 id,不允许使用以下字符 *?\"'`´,;:<>#/{}ß[] 和空格",
+ "Composite id of linked object": "链接对象的复合 id"
+}
\ No newline at end of file
diff --git a/src/src/index.jsx b/src/src/index.jsx
new file mode 100644
index 0000000..cebfcab
--- /dev/null
+++ b/src/src/index.jsx
@@ -0,0 +1,3 @@
+// this file used only for simulation and not used in end build
+
+import('./bootstrap');
diff --git a/src/src/theme.js b/src/src/theme.js
new file mode 100644
index 0000000..7fc8612
--- /dev/null
+++ b/src/src/theme.js
@@ -0,0 +1,19 @@
+// this file used only for simulation and not used in end build
+import Theme from '@iobroker/adapter-react-v5/Theme';
+
+export default type => {
+ const danger = '#dd5325';
+ const success = '#73b6a8';
+ const theme = { ...Theme(type) };
+ if (!theme) {
+ return theme;
+ }
+ theme.palette.text.danger = {
+ color: danger,
+ };
+ theme.palette.text.success = {
+ color: success,
+ };
+
+ return theme;
+};
\ No newline at end of file