@iobroker/json-config 6.17.6 → 6.17.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/build/JsonConfig.d.ts +1 -1
- package/build/JsonConfig.js +18 -19
- package/build/JsonConfig.js.map +1 -1
- package/build/JsonConfigComponent/ChipInput.d.ts +89 -11
- package/build/JsonConfigComponent/ChipInput.js +48 -137
- package/build/JsonConfigComponent/ChipInput.js.map +1 -1
- package/build/JsonConfigComponent/ConfigAccordion.d.ts +7 -14
- package/build/JsonConfigComponent/ConfigAccordion.js +10 -21
- package/build/JsonConfigComponent/ConfigAccordion.js.map +1 -1
- package/build/JsonConfigComponent/ConfigAlive.d.ts +5 -1
- package/build/JsonConfigComponent/ConfigAlive.js +0 -1
- package/build/JsonConfigComponent/ConfigAlive.js.map +1 -1
- package/build/JsonConfigComponent/ConfigAutocomplete.d.ts +5 -29
- package/build/JsonConfigComponent/ConfigAutocomplete.js +7 -5
- package/build/JsonConfigComponent/ConfigAutocomplete.js.map +1 -1
- package/build/JsonConfigComponent/ConfigAutocompleteSendTo.d.ts +5 -14
- package/build/JsonConfigComponent/ConfigAutocompleteSendTo.js +3 -3
- package/build/JsonConfigComponent/ConfigAutocompleteSendTo.js.map +1 -1
- package/build/JsonConfigComponent/ConfigCRON.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigCertCollection.d.ts +7 -13
- package/build/JsonConfigComponent/ConfigCertCollection.js +8 -18
- package/build/JsonConfigComponent/ConfigCertCollection.js.map +1 -1
- package/build/JsonConfigComponent/ConfigCertificateSelect.d.ts +7 -13
- package/build/JsonConfigComponent/ConfigCertificateSelect.js +5 -18
- package/build/JsonConfigComponent/ConfigCertificateSelect.js.map +1 -1
- package/build/JsonConfigComponent/ConfigCertificates.d.ts +7 -13
- package/build/JsonConfigComponent/ConfigCertificates.js +7 -18
- package/build/JsonConfigComponent/ConfigCertificates.js.map +1 -1
- package/build/JsonConfigComponent/ConfigCheckbox.d.ts +3 -11
- package/build/JsonConfigComponent/ConfigCheckbox.js +2 -2
- package/build/JsonConfigComponent/ConfigCheckbox.js.map +1 -1
- package/build/JsonConfigComponent/ConfigChip.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigColor.d.ts +13 -20
- package/build/JsonConfigComponent/ConfigColor.js +9 -23
- package/build/JsonConfigComponent/ConfigColor.js.map +1 -1
- package/build/JsonConfigComponent/ConfigCoordinates.d.ts +8 -13
- package/build/JsonConfigComponent/ConfigCoordinates.js +29 -39
- package/build/JsonConfigComponent/ConfigCoordinates.js.map +1 -1
- package/build/JsonConfigComponent/ConfigCustom.d.ts +4 -50
- package/build/JsonConfigComponent/ConfigCustom.js +0 -1
- package/build/JsonConfigComponent/ConfigCustom.js.map +1 -1
- package/build/JsonConfigComponent/ConfigDatePicker.d.ts +8 -3
- package/build/JsonConfigComponent/ConfigDatePicker.js +21 -13
- package/build/JsonConfigComponent/ConfigDatePicker.js.map +1 -1
- package/build/JsonConfigComponent/ConfigDeviceManager.d.ts +5 -1
- package/build/JsonConfigComponent/ConfigDeviceManager.js +1 -1
- package/build/JsonConfigComponent/ConfigDeviceManager.js.map +1 -1
- package/build/JsonConfigComponent/ConfigFile.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigFunc.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigGeneric.d.ts +46 -32
- package/build/JsonConfigComponent/ConfigGeneric.js +23 -12
- package/build/JsonConfigComponent/ConfigGeneric.js.map +1 -1
- package/build/JsonConfigComponent/ConfigIP.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigImageSendTo.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigInstanceSelect.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigInterface.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigJsonEditor.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigLanguage.d.ts +3 -2
- package/build/JsonConfigComponent/ConfigLanguage.js +2 -2
- package/build/JsonConfigComponent/ConfigLanguage.js.map +1 -1
- package/build/JsonConfigComponent/ConfigNumber.d.ts +3 -13
- package/build/JsonConfigComponent/ConfigNumber.js +2 -2
- package/build/JsonConfigComponent/ConfigNumber.js.map +1 -1
- package/build/JsonConfigComponent/ConfigObjectId.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigPanel.d.ts +7 -2
- package/build/JsonConfigComponent/ConfigPanel.js +5 -40
- package/build/JsonConfigComponent/ConfigPanel.js.map +1 -1
- package/build/JsonConfigComponent/ConfigPassword.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigPort.d.ts +6 -1
- package/build/JsonConfigComponent/ConfigPort.js +2 -3
- package/build/JsonConfigComponent/ConfigPort.js.map +1 -1
- package/build/JsonConfigComponent/ConfigRoom.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigSelect.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigSelectSendTo.d.ts +7 -16
- package/build/JsonConfigComponent/ConfigSelectSendTo.js +15 -61
- package/build/JsonConfigComponent/ConfigSelectSendTo.js.map +1 -1
- package/build/JsonConfigComponent/ConfigSendto.d.ts +3 -20
- package/build/JsonConfigComponent/ConfigSendto.js +8 -8
- package/build/JsonConfigComponent/ConfigSendto.js.map +1 -1
- package/build/JsonConfigComponent/ConfigSetState.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigSlider.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigStaticDivider.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigStaticHeader.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigStaticImage.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigStaticText.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigTable.d.ts +4 -25
- package/build/JsonConfigComponent/ConfigTable.js +5 -4
- package/build/JsonConfigComponent/ConfigTable.js.map +1 -1
- package/build/JsonConfigComponent/ConfigTabs.d.ts +8 -33
- package/build/JsonConfigComponent/ConfigTabs.js +10 -43
- package/build/JsonConfigComponent/ConfigTabs.js.map +1 -1
- package/build/JsonConfigComponent/ConfigText.d.ts +7 -13
- package/build/JsonConfigComponent/ConfigText.js +13 -18
- package/build/JsonConfigComponent/ConfigText.js.map +1 -1
- package/build/JsonConfigComponent/ConfigTextSendTo.d.ts +7 -9
- package/build/JsonConfigComponent/ConfigTextSendTo.js +11 -21
- package/build/JsonConfigComponent/ConfigTextSendTo.js.map +1 -1
- package/build/JsonConfigComponent/ConfigTimePicker.d.ts +9 -3
- package/build/JsonConfigComponent/ConfigTimePicker.js +28 -18
- package/build/JsonConfigComponent/ConfigTimePicker.js.map +1 -1
- package/build/JsonConfigComponent/ConfigTopic.d.ts +1 -1
- package/build/JsonConfigComponent/ConfigUUID.d.ts +12 -13
- package/build/JsonConfigComponent/ConfigUUID.js +1 -6
- package/build/JsonConfigComponent/ConfigUUID.js.map +1 -1
- package/build/JsonConfigComponent/ConfigUser.d.ts +1 -1
- package/build/JsonConfigComponent/index.d.ts +66 -25
- package/build/JsonConfigComponent/index.js +48 -61
- package/build/JsonConfigComponent/index.js.map +1 -1
- package/build/index.d.ts +2 -2
- package/build/index.js +2 -2
- package/build/index.js.map +1 -1
- package/package.json +27 -26
- package/src/JsonConfig.tsx +710 -0
- package/src/JsonConfigComponent/ChipInput.tsx +752 -0
- package/src/JsonConfigComponent/ConfigAccordion.tsx +278 -0
- package/src/JsonConfigComponent/ConfigAlive.tsx +74 -0
- package/src/JsonConfigComponent/ConfigAutocomplete.tsx +108 -0
- package/src/JsonConfigComponent/ConfigAutocompleteSendTo.tsx +183 -0
- package/src/JsonConfigComponent/ConfigCRON.jsx +101 -0
- package/src/JsonConfigComponent/ConfigCertCollection.tsx +102 -0
- package/src/JsonConfigComponent/ConfigCertificateSelect.tsx +92 -0
- package/src/JsonConfigComponent/ConfigCertificates.tsx +202 -0
- package/src/JsonConfigComponent/ConfigCheckLicense.jsx +662 -0
- package/src/JsonConfigComponent/ConfigCheckbox.tsx +67 -0
- package/src/JsonConfigComponent/ConfigChip.jsx +81 -0
- package/src/JsonConfigComponent/ConfigColor.tsx +86 -0
- package/src/JsonConfigComponent/ConfigCoordinates.tsx +234 -0
- package/src/JsonConfigComponent/ConfigCustom.tsx +246 -0
- package/src/JsonConfigComponent/ConfigDatePicker.tsx +48 -0
- package/src/JsonConfigComponent/ConfigDeviceManager.tsx +33 -0
- package/src/JsonConfigComponent/ConfigFile.jsx +181 -0
- package/src/JsonConfigComponent/ConfigFileSelector.jsx +520 -0
- package/src/JsonConfigComponent/ConfigFunc.jsx +90 -0
- package/src/JsonConfigComponent/ConfigGeneric.tsx +1027 -0
- package/src/JsonConfigComponent/ConfigIP.jsx +96 -0
- package/src/JsonConfigComponent/ConfigImageSendTo.jsx +79 -0
- package/src/JsonConfigComponent/ConfigImageUpload.jsx +114 -0
- package/src/JsonConfigComponent/ConfigInstanceSelect.jsx +172 -0
- package/src/JsonConfigComponent/ConfigInterface.jsx +112 -0
- package/src/JsonConfigComponent/ConfigJsonEditor.jsx +103 -0
- package/src/JsonConfigComponent/ConfigLanguage.tsx +153 -0
- package/src/JsonConfigComponent/ConfigLicense.jsx +148 -0
- package/src/JsonConfigComponent/ConfigNumber.tsx +207 -0
- package/src/JsonConfigComponent/ConfigObjectId.jsx +113 -0
- package/src/JsonConfigComponent/ConfigPanel.tsx +360 -0
- package/src/JsonConfigComponent/ConfigPassword.jsx +160 -0
- package/src/JsonConfigComponent/ConfigPattern.jsx +50 -0
- package/src/JsonConfigComponent/ConfigPort.tsx +232 -0
- package/src/JsonConfigComponent/ConfigRoom.jsx +90 -0
- package/src/JsonConfigComponent/ConfigSelect.jsx +124 -0
- package/src/JsonConfigComponent/ConfigSelectSendTo.tsx +251 -0
- package/src/JsonConfigComponent/ConfigSendto.tsx +340 -0
- package/src/JsonConfigComponent/ConfigSetState.jsx +116 -0
- package/src/JsonConfigComponent/ConfigSlider.jsx +97 -0
- package/src/JsonConfigComponent/ConfigStaticDivider.jsx +51 -0
- package/src/JsonConfigComponent/ConfigStaticHeader.jsx +63 -0
- package/src/JsonConfigComponent/ConfigStaticImage.jsx +48 -0
- package/src/JsonConfigComponent/ConfigStaticText.jsx +72 -0
- package/src/JsonConfigComponent/ConfigTable.tsx +1040 -0
- package/src/JsonConfigComponent/ConfigTabs.tsx +150 -0
- package/src/JsonConfigComponent/ConfigText.tsx +188 -0
- package/src/JsonConfigComponent/ConfigTextSendTo.tsx +102 -0
- package/src/JsonConfigComponent/ConfigTimePicker.tsx +63 -0
- package/src/JsonConfigComponent/ConfigTopic.jsx +78 -0
- package/src/JsonConfigComponent/ConfigUUID.tsx +55 -0
- package/src/JsonConfigComponent/ConfigUser.jsx +104 -0
- package/src/JsonConfigComponent/index.tsx +435 -0
- package/src/JsonConfigComponent/wrapper/Components/CustomModal.jsx +145 -0
- package/src/JsonConfigComponent/wrapper/Components/Editor.jsx +65 -0
- package/src/Utils.jsx +1683 -0
- package/src/index.tsx +14 -0
- package/src/types.d.ts +372 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import React, { Component } from 'react';
|
|
2
|
+
import { Grid, LinearProgress } from '@mui/material';
|
|
3
|
+
|
|
4
|
+
import { I18n } from '@iobroker/adapter-react-v5';
|
|
5
|
+
import type { ConfigItemCustom } from '#JC/types';
|
|
6
|
+
import type { ConfigGenericProps } from '#JC/JsonConfigComponent/ConfigGeneric';
|
|
7
|
+
|
|
8
|
+
const getOrLoadRemote = (
|
|
9
|
+
remote: string,
|
|
10
|
+
shareScope: string,
|
|
11
|
+
remoteFallbackUrl?: string,
|
|
12
|
+
): Promise<{get: (module: string) => () => Promise<{ default: Record<string, React.Component> }>}> =>
|
|
13
|
+
new Promise((resolve, reject) => {
|
|
14
|
+
// check if remote exists on the global `window`object
|
|
15
|
+
if (!(window as any)[remote]) {
|
|
16
|
+
// search dom to see if remote tag exists, but might still be loading (async)
|
|
17
|
+
const existingRemote: HTMLScriptElement = document.querySelector(`script[data-webpack="${remote}"]`);
|
|
18
|
+
// when remote is loaded.
|
|
19
|
+
const onload = async () => {
|
|
20
|
+
// check if it was initialized
|
|
21
|
+
if ((window as any)[remote]) {
|
|
22
|
+
if (!(window as any)[remote].__initialized) {
|
|
23
|
+
// if share scope doesn't exist (like in webpack 4) then expect shareScope to be a manual object
|
|
24
|
+
// eslint-disable-next-line camelcase
|
|
25
|
+
// @ts-expect-error it is a trick and must be so
|
|
26
|
+
if (typeof __webpack_share_scopes__ === 'undefined') {
|
|
27
|
+
// use a default share scope object passed in manually
|
|
28
|
+
await (window as any)[remote].init(shareScope);
|
|
29
|
+
} else {
|
|
30
|
+
// otherwise, init share scope as usual
|
|
31
|
+
// eslint-disable-next-line
|
|
32
|
+
// @ts-expect-error it is a trick and must be so
|
|
33
|
+
await (window as any)[remote].init((__webpack_share_scopes__ as any)[shareScope]);
|
|
34
|
+
}
|
|
35
|
+
// mark remote as initialized
|
|
36
|
+
(window as any)[remote].__initialized = true;
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
console.error(`Cannot load ${remote}`);
|
|
40
|
+
reject(new Error(`Cannot load ${remote}`));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// resolve promise so marking remote as loaded
|
|
44
|
+
resolve((window as any)[remote]);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
if (existingRemote) {
|
|
48
|
+
// if existing remote but not loaded, hook into its onload and wait for it to be ready
|
|
49
|
+
existingRemote.onload = onload;
|
|
50
|
+
existingRemote.onerror = reject;
|
|
51
|
+
// check if remote fallback exists as param passed to function
|
|
52
|
+
// TODO: should scan public config for a matching key if no override exists
|
|
53
|
+
} else if (remoteFallbackUrl) {
|
|
54
|
+
// inject remote if a fallback exists and call the same onload function
|
|
55
|
+
const d = document;
|
|
56
|
+
const script = d.createElement('script');
|
|
57
|
+
script.type = 'text/javascript';
|
|
58
|
+
// mark as data-webpack so runtime can track it internally
|
|
59
|
+
script.setAttribute('data-webpack', `${remote}`);
|
|
60
|
+
script.async = true;
|
|
61
|
+
script.onerror = reject;
|
|
62
|
+
script.onload = onload;
|
|
63
|
+
script.src = remoteFallbackUrl;
|
|
64
|
+
d.getElementsByTagName('head')[0].appendChild(script);
|
|
65
|
+
} else {
|
|
66
|
+
// no remote and no fallback exist, reject
|
|
67
|
+
reject(new Error(`Cannot Find Remote ${remote} to inject`));
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
// remote already instantiated, resolve
|
|
71
|
+
resolve((window as any)[remote]);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
function loadComponent(
|
|
76
|
+
remote: string,
|
|
77
|
+
sharedScope: string,
|
|
78
|
+
module: string,
|
|
79
|
+
url: string,
|
|
80
|
+
): () => Promise<{ default: Record<string, React.Component> }> {
|
|
81
|
+
return async (): Promise<{ default: Record<string, React.Component> }> => {
|
|
82
|
+
const container = await getOrLoadRemote(remote, sharedScope, url);
|
|
83
|
+
const factory = await container.get(module);
|
|
84
|
+
return factory();
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface ConfigCustomProps extends ConfigGenericProps {
|
|
89
|
+
schema: ConfigItemCustom;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface ConfigCustomState {
|
|
93
|
+
Component: Component | null;
|
|
94
|
+
error: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export default class ConfigCustom extends Component<ConfigCustomProps, ConfigCustomState> {
|
|
98
|
+
static runningLoads: Record<string, Promise<{ default: Record<string, React.Component> }>> = {};
|
|
99
|
+
|
|
100
|
+
constructor(props: ConfigCustomProps) {
|
|
101
|
+
super(props);
|
|
102
|
+
// schema.url - location of Widget
|
|
103
|
+
// schema.name - Component name
|
|
104
|
+
// schema.i18n - i18n
|
|
105
|
+
|
|
106
|
+
this.state = {
|
|
107
|
+
Component: null,
|
|
108
|
+
error: '',
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// load component dynamically
|
|
113
|
+
async componentDidMount() {
|
|
114
|
+
if (!this.props.schema.url) {
|
|
115
|
+
console.error('URL is empty. Cannot load custom component!');
|
|
116
|
+
this.setState({ error: 'URL is empty. Cannot load custom component!' });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let url;
|
|
121
|
+
/*
|
|
122
|
+
if (this.props.schema.url.startsWith('http:') || this.props.schema.url.startsWith('https:')) {
|
|
123
|
+
url = this.props.schema.url;
|
|
124
|
+
} else
|
|
125
|
+
*/
|
|
126
|
+
if (this.props.schema.url.startsWith('./')) {
|
|
127
|
+
url = `${window.location.protocol}//${window.location.host}${this.props.schema.url.replace(/^\./, '')}`;
|
|
128
|
+
} else {
|
|
129
|
+
url = `${window.location.protocol}//${window.location.host}/adapter/${this.props.adapterName}/${this.props.schema.url}`;
|
|
130
|
+
}
|
|
131
|
+
const [uniqueName, fileToLoad, ...componentNameParts] = this.props.schema.name.split('/');
|
|
132
|
+
const componentName = componentNameParts.join('/');
|
|
133
|
+
if (!url) {
|
|
134
|
+
console.error('Cannot find URL for custom component! Please define "url" as "custom/customComponents.js" in the schema');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (!uniqueName || !fileToLoad || !componentName) {
|
|
138
|
+
console.error('Invalid format of "name"! Please define "name" as "ConfigCustomBackItUpSet/Components/AdapterExist" in the schema');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
let setPromise = ConfigCustom.runningLoads[`${url}!${fileToLoad}`];
|
|
142
|
+
|
|
143
|
+
if (!setPromise) {
|
|
144
|
+
let i18nPromise;
|
|
145
|
+
if (this.props.schema.i18n === true) {
|
|
146
|
+
// load i18n from files
|
|
147
|
+
const pos = url.lastIndexOf('/');
|
|
148
|
+
let i18nURL;
|
|
149
|
+
if (pos !== -1) {
|
|
150
|
+
i18nURL = url.substring(0, pos);
|
|
151
|
+
} else {
|
|
152
|
+
i18nURL = url;
|
|
153
|
+
}
|
|
154
|
+
const lang = I18n.getLanguage();
|
|
155
|
+
const file = `${i18nURL}/i18n/${lang}.json`;
|
|
156
|
+
|
|
157
|
+
i18nPromise = fetch(file)
|
|
158
|
+
.then(data => data.json())
|
|
159
|
+
.then(json => I18n.extendTranslations(json, lang))
|
|
160
|
+
.catch(error => {
|
|
161
|
+
if (lang !== 'en') {
|
|
162
|
+
// try to load English
|
|
163
|
+
fetch(`${i18nURL}/i18n/en.json`)
|
|
164
|
+
.then(data => data.json())
|
|
165
|
+
.then(json => I18n.extendTranslations(json, lang))
|
|
166
|
+
.catch(err => console.log(`Cannot load i18n "${file}": ${err}`));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
console.log(`Cannot load i18n "${file}": ${error}`);
|
|
170
|
+
});
|
|
171
|
+
} else if (this.props.schema.i18n && typeof this.props.schema.i18n === 'object') {
|
|
172
|
+
try {
|
|
173
|
+
I18n.extendTranslations(this.props.schema.i18n);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error(`Cannot import i18n: ${error}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
console.log(uniqueName, fileToLoad, componentName);
|
|
180
|
+
setPromise = loadComponent(uniqueName, 'default', `./${fileToLoad}`, url)();
|
|
181
|
+
if (i18nPromise) {
|
|
182
|
+
setPromise = Promise.all([setPromise, i18nPromise])
|
|
183
|
+
.then(result => result[0]);
|
|
184
|
+
}
|
|
185
|
+
// remember promise
|
|
186
|
+
ConfigCustom.runningLoads[`${url}!${fileToLoad}`] = setPromise;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
this.setState({ error: `Cannot import from ${this.props.schema.url}: ${error}` });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const component = (await setPromise).default;
|
|
194
|
+
|
|
195
|
+
if (!component?.[componentName]) {
|
|
196
|
+
const keys = Object.keys(component || {});
|
|
197
|
+
console.error('URL is empty. Cannot load custom component!');
|
|
198
|
+
this.setState({ error: `Component ${this.props.schema.name} not found in ${this.props.schema.url}. Found: ${keys.join(', ')}` });
|
|
199
|
+
} else {
|
|
200
|
+
this.setState({ Component: component[componentName] });
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
this.setState({ error: `Cannot import from ${this.props.schema.url}: ${error}` });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
render() {
|
|
208
|
+
const CustomComponent: Component = this.state.Component;
|
|
209
|
+
|
|
210
|
+
// render temporary placeholder
|
|
211
|
+
if (!CustomComponent) {
|
|
212
|
+
if (this.state.error) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
const schema = this.props.schema || {} as ConfigItemCustom;
|
|
216
|
+
|
|
217
|
+
const item = <Grid
|
|
218
|
+
item
|
|
219
|
+
xs={schema.xs || undefined}
|
|
220
|
+
lg={schema.lg || undefined}
|
|
221
|
+
md={schema.md || undefined}
|
|
222
|
+
sm={schema.sm || undefined}
|
|
223
|
+
style={({
|
|
224
|
+
marginBottom: 0,
|
|
225
|
+
// marginRight: 8,
|
|
226
|
+
textAlign: 'left',
|
|
227
|
+
...schema.style,
|
|
228
|
+
...(this.props.themeType === 'dark' ? schema.darkStyle : {}),
|
|
229
|
+
})}
|
|
230
|
+
>
|
|
231
|
+
{this.state.error ? <div>{this.state.error}</div> : <LinearProgress />}
|
|
232
|
+
</Grid>;
|
|
233
|
+
|
|
234
|
+
if (schema.newLine) {
|
|
235
|
+
return <>
|
|
236
|
+
<div style={{ flexBasis: '100%', height: 0 }} />
|
|
237
|
+
{item}
|
|
238
|
+
</>;
|
|
239
|
+
}
|
|
240
|
+
return item;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// @ts-expect-error No idea how to solve it
|
|
244
|
+
return <CustomComponent {...this.props} />;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { DatePicker } from '@mui/x-date-pickers';
|
|
4
|
+
|
|
5
|
+
import type { ConfigItemDatePicker } from '#JC/types';
|
|
6
|
+
import ConfigGeneric, { type ConfigGenericProps } from './ConfigGeneric';
|
|
7
|
+
|
|
8
|
+
interface ConfigDatePickerProps extends ConfigGenericProps {
|
|
9
|
+
schema: ConfigItemDatePicker;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default class ConfigDatePicker extends ConfigGeneric<ConfigDatePickerProps> {
|
|
13
|
+
componentDidMount() {
|
|
14
|
+
super.componentDidMount();
|
|
15
|
+
const value = ConfigGeneric.getValue(this.props.data, this.props.attr);
|
|
16
|
+
this.setState({ value });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
renderItem(_error: unknown, disabled: boolean /* , defaultValue */): React.JSX.Element {
|
|
20
|
+
return <DatePicker
|
|
21
|
+
sx={theme => ({
|
|
22
|
+
width: '100%',
|
|
23
|
+
borderBottom: `1px solid ${theme.palette.text.primary}`,
|
|
24
|
+
'& fieldset': {
|
|
25
|
+
display: 'none',
|
|
26
|
+
},
|
|
27
|
+
'& input': {
|
|
28
|
+
padding: `${theme.spacing(1.5)} 0 4px 0`,
|
|
29
|
+
},
|
|
30
|
+
'& .MuiInputAdornment-root': {
|
|
31
|
+
marginLeft: 0,
|
|
32
|
+
marginTop: 1, // it is already in spaces
|
|
33
|
+
},
|
|
34
|
+
'& label': {
|
|
35
|
+
transform: 'translate(0px, -9px) scale(0.75)',
|
|
36
|
+
},
|
|
37
|
+
})}
|
|
38
|
+
format={this.props.systemConfig.dateFormat.toLowerCase().replace('mm', 'MM')}
|
|
39
|
+
disabled={!!disabled}
|
|
40
|
+
value={this.state.value as never}
|
|
41
|
+
onChange={value => {
|
|
42
|
+
this.setState({ value }, () =>
|
|
43
|
+
this.onChange(this.props.attr, this.state.value));
|
|
44
|
+
}}
|
|
45
|
+
label={this.getText(this.props.schema.label)}
|
|
46
|
+
/>;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import DeviceManager from '@iobroker/dm-gui-components';
|
|
4
|
+
|
|
5
|
+
import type { ConfigItemDeviceManager } from '#JC/types';
|
|
6
|
+
import ConfigGeneric, { type ConfigGenericProps, type ConfigGenericState } from './ConfigGeneric';
|
|
7
|
+
|
|
8
|
+
interface ConfigDeviceManagerProps extends ConfigGenericProps {
|
|
9
|
+
schema: ConfigItemDeviceManager;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class ConfigDeviceManager extends ConfigGeneric<ConfigDeviceManagerProps, ConfigGenericState> {
|
|
13
|
+
renderItem(): React.JSX.Element | null {
|
|
14
|
+
const schema = this.props.schema;
|
|
15
|
+
|
|
16
|
+
if (!schema) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return <DeviceManager
|
|
21
|
+
uploadImagesToInstance={`${this.props.adapterName}.${this.props.instance}`}
|
|
22
|
+
title={this.getText(this.props.schema.label)}
|
|
23
|
+
socket={this.props.socket}
|
|
24
|
+
selectedInstance={`${this.props.adapterName}.${this.props.instance}`}
|
|
25
|
+
themeName={this.props.themeName}
|
|
26
|
+
themeType={this.props.themeType}
|
|
27
|
+
isFloatComma={this.props.isFloatComma}
|
|
28
|
+
dateFormat={this.props.dateFormat}
|
|
29
|
+
/>;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default ConfigDeviceManager;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { withStyles } from '@mui/styles';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Button,
|
|
7
|
+
TextField,
|
|
8
|
+
IconButton,
|
|
9
|
+
} from '@mui/material';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
Article as IconText,
|
|
13
|
+
Code as IconCode,
|
|
14
|
+
PlayArrow as IconPlay,
|
|
15
|
+
Videocam as IconVideo,
|
|
16
|
+
} from '@mui/icons-material';
|
|
17
|
+
|
|
18
|
+
import { SelectFile as SelectFileDialog } from '@iobroker/adapter-react-v5';
|
|
19
|
+
|
|
20
|
+
import ConfigGeneric from './ConfigGeneric';
|
|
21
|
+
import ConfigFileSelector from './ConfigFileSelector';
|
|
22
|
+
|
|
23
|
+
const styles = () => ({
|
|
24
|
+
fullWidth: {
|
|
25
|
+
width: '100%',
|
|
26
|
+
},
|
|
27
|
+
fullWidthOneButton: {
|
|
28
|
+
width: 'calc(100% - 69px)',
|
|
29
|
+
marginRight: 4,
|
|
30
|
+
},
|
|
31
|
+
fullWidthIcon: {
|
|
32
|
+
width: 'calc(100% - 119px)',
|
|
33
|
+
marginRight: 4,
|
|
34
|
+
},
|
|
35
|
+
selectedImage: {
|
|
36
|
+
height: 40,
|
|
37
|
+
width: 40,
|
|
38
|
+
display: 'inline-block',
|
|
39
|
+
marginRight: 8,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const IMAGE_EXT = ['jpg', 'jpeg', 'svg', 'png', 'webp', 'gif', 'apng', 'avif', 'webp'];
|
|
44
|
+
const AUDIO_EXT = ['mp3', 'ogg', 'wav', 'aac'];
|
|
45
|
+
const VIDEO_EXT = ['avi', 'mp4', 'mov'];
|
|
46
|
+
const DOC_EXT = ['txt', 'log', 'html', 'htm'];
|
|
47
|
+
const JS_EXT = ['json', 'js', 'ts'];
|
|
48
|
+
|
|
49
|
+
class ConfigFile extends ConfigGeneric {
|
|
50
|
+
componentDidMount() {
|
|
51
|
+
super.componentDidMount();
|
|
52
|
+
const value = ConfigGeneric.getValue(this.props.data, this.props.attr);
|
|
53
|
+
this.imagePrefix = this.props.imagePrefix === undefined ? './files' : this.props.imagePrefix;
|
|
54
|
+
this.setState({ value: value ?? '' });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static getDerivedStateFromProps(props, state) {
|
|
58
|
+
const value = ConfigGeneric.getValue(props.data, props.attr);
|
|
59
|
+
if (value === null || value === undefined || value.toString().trim() !== (state.value || '').toString().trim()) {
|
|
60
|
+
return { value: value ?? '' };
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
loadFile() {
|
|
66
|
+
const pos = this.state.value.indexOf('/');
|
|
67
|
+
if (pos !== -1) {
|
|
68
|
+
const adapter = this.state.value.substring(0, pos);
|
|
69
|
+
const path = this.state.value.substring(pos + 1);
|
|
70
|
+
return this.props.socket.readFile(adapter, path, true);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return Promise.resolve(null);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
play() {
|
|
77
|
+
this.loadFile()
|
|
78
|
+
.then(data => {
|
|
79
|
+
if (typeof AudioContext !== 'undefined' && data?.file) {
|
|
80
|
+
const context = new AudioContext();
|
|
81
|
+
const buf = ConfigFileSelector.base64ToArrayBuffer(data.file);
|
|
82
|
+
context.decodeAudioData(buf, buffer => {
|
|
83
|
+
const source = context.createBufferSource(); // creates a sound source
|
|
84
|
+
source.buffer = buffer; // tell the source which sounds to play
|
|
85
|
+
source.connect(context.destination); // connect the source to the context's destination (the speakers)
|
|
86
|
+
source.start(0);
|
|
87
|
+
}, err => window.alert(`Cannot play: ${err}`));
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
getIcon() {
|
|
93
|
+
const extension = this.state.value.split('.').pop().toLowerCase();
|
|
94
|
+
if (IMAGE_EXT.includes(extension)) {
|
|
95
|
+
return <div
|
|
96
|
+
className={this.props.classes.selectedImage}
|
|
97
|
+
style={{
|
|
98
|
+
backgroundImage: `url(${this.imagePrefix}/${this.state.value})`,
|
|
99
|
+
backgroundSize: 'contain',
|
|
100
|
+
backgroundRepeat: 'no-repeat',
|
|
101
|
+
}}
|
|
102
|
+
/>;
|
|
103
|
+
} if (AUDIO_EXT.includes(extension)) {
|
|
104
|
+
return <IconButton style={{ color: '#00FF00' }} onClick={() => this.play()}><IconPlay /></IconButton>;
|
|
105
|
+
} if (DOC_EXT.includes(extension)) {
|
|
106
|
+
return <IconText />;
|
|
107
|
+
} if (VIDEO_EXT.includes(extension)) {
|
|
108
|
+
return <IconVideo />;
|
|
109
|
+
} if (JS_EXT.includes(extension)) {
|
|
110
|
+
return <IconCode />;
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
renderFileBrowser() {
|
|
116
|
+
if (!this.state.showFileBrowser) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
return <SelectFileDialog
|
|
120
|
+
imagePrefix={this.props.imagePrefix}
|
|
121
|
+
socket={this.props.socket}
|
|
122
|
+
selected={this.state.value}
|
|
123
|
+
onClose={() => this.setState({ showFileBrowser: false })}
|
|
124
|
+
onOk={value => {
|
|
125
|
+
this.setState({ value }, () =>
|
|
126
|
+
this.onChange(this.props.attr, this.props.schema.trim === false ? value : (value || '').trim()));
|
|
127
|
+
}}
|
|
128
|
+
selectOnlyFolders={this.props.schema.selectOnlyFolders}
|
|
129
|
+
allowUpload={this.props.schema.allowUpload}
|
|
130
|
+
allowDownload={this.props.schema.allowDownload}
|
|
131
|
+
allowCreateFolder={this.props.schema.allowCreateFolder}
|
|
132
|
+
allowView={this.props.schema.allowView}
|
|
133
|
+
showToolbar={this.props.schema.showToolbar}
|
|
134
|
+
limitPath={this.props.schema.limitPath}
|
|
135
|
+
/>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
renderItem(error, disabled /* , defaultValue */) {
|
|
139
|
+
const icon = this.getIcon();
|
|
140
|
+
|
|
141
|
+
return <div className={this.props.classes.fullWidth}>
|
|
142
|
+
{icon}
|
|
143
|
+
<TextField
|
|
144
|
+
variant="standard"
|
|
145
|
+
className={icon ? this.props.classes.fullWidthIcon : this.props.classes.fullWidthOneButton}
|
|
146
|
+
value={this.state.value === null || this.state.value === undefined ? '' : this.state.value}
|
|
147
|
+
error={!!error}
|
|
148
|
+
disabled={!!disabled}
|
|
149
|
+
inputProps={{
|
|
150
|
+
maxLength: this.props.schema.maxLength || this.props.schema.max || undefined,
|
|
151
|
+
readOnly: !!this.props.schema.disableEdit,
|
|
152
|
+
}}
|
|
153
|
+
onChange={e => {
|
|
154
|
+
const value = e.target.value;
|
|
155
|
+
this.setState({ value }, () =>
|
|
156
|
+
this.onChange(this.props.attr, this.props.schema.trim === false ? value : (value || '').trim()));
|
|
157
|
+
}}
|
|
158
|
+
placeholder={this.getText(this.props.schema.placeholder)}
|
|
159
|
+
label={this.getText(this.props.schema.label)}
|
|
160
|
+
helperText={this.renderHelp(this.props.schema.help, this.props.schema.helpLink, this.props.schema.noTranslation)}
|
|
161
|
+
/>
|
|
162
|
+
<Button variant="outlined" onClick={() => this.setState({ showFileBrowser: true })}>...</Button>
|
|
163
|
+
{this.renderFileBrowser()}
|
|
164
|
+
</div>;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
ConfigFile.propTypes = {
|
|
169
|
+
socket: PropTypes.object.isRequired,
|
|
170
|
+
themeType: PropTypes.string,
|
|
171
|
+
themeName: PropTypes.string,
|
|
172
|
+
style: PropTypes.object,
|
|
173
|
+
className: PropTypes.string,
|
|
174
|
+
data: PropTypes.object.isRequired,
|
|
175
|
+
schema: PropTypes.object,
|
|
176
|
+
onError: PropTypes.func,
|
|
177
|
+
onChange: PropTypes.func,
|
|
178
|
+
imagePrefix: PropTypes.func,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export default withStyles(styles)(ConfigFile);
|