@iobroker/json-config 6.17.6 → 6.17.13

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.
Files changed (173) hide show
  1. package/LICENSE +21 -21
  2. package/build/JsonConfig.d.ts +1 -1
  3. package/build/JsonConfig.js +18 -19
  4. package/build/JsonConfig.js.map +1 -1
  5. package/build/JsonConfigComponent/ChipInput.d.ts +89 -11
  6. package/build/JsonConfigComponent/ChipInput.js +48 -137
  7. package/build/JsonConfigComponent/ChipInput.js.map +1 -1
  8. package/build/JsonConfigComponent/ConfigAccordion.d.ts +7 -14
  9. package/build/JsonConfigComponent/ConfigAccordion.js +10 -21
  10. package/build/JsonConfigComponent/ConfigAccordion.js.map +1 -1
  11. package/build/JsonConfigComponent/ConfigAlive.d.ts +5 -1
  12. package/build/JsonConfigComponent/ConfigAlive.js +0 -1
  13. package/build/JsonConfigComponent/ConfigAlive.js.map +1 -1
  14. package/build/JsonConfigComponent/ConfigAutocomplete.d.ts +5 -29
  15. package/build/JsonConfigComponent/ConfigAutocomplete.js +7 -5
  16. package/build/JsonConfigComponent/ConfigAutocomplete.js.map +1 -1
  17. package/build/JsonConfigComponent/ConfigAutocompleteSendTo.d.ts +5 -14
  18. package/build/JsonConfigComponent/ConfigAutocompleteSendTo.js +3 -3
  19. package/build/JsonConfigComponent/ConfigAutocompleteSendTo.js.map +1 -1
  20. package/build/JsonConfigComponent/ConfigCRON.d.ts +1 -1
  21. package/build/JsonConfigComponent/ConfigCertCollection.d.ts +7 -13
  22. package/build/JsonConfigComponent/ConfigCertCollection.js +8 -18
  23. package/build/JsonConfigComponent/ConfigCertCollection.js.map +1 -1
  24. package/build/JsonConfigComponent/ConfigCertificateSelect.d.ts +7 -13
  25. package/build/JsonConfigComponent/ConfigCertificateSelect.js +5 -18
  26. package/build/JsonConfigComponent/ConfigCertificateSelect.js.map +1 -1
  27. package/build/JsonConfigComponent/ConfigCertificates.d.ts +7 -13
  28. package/build/JsonConfigComponent/ConfigCertificates.js +7 -18
  29. package/build/JsonConfigComponent/ConfigCertificates.js.map +1 -1
  30. package/build/JsonConfigComponent/ConfigCheckbox.d.ts +3 -11
  31. package/build/JsonConfigComponent/ConfigCheckbox.js +2 -2
  32. package/build/JsonConfigComponent/ConfigCheckbox.js.map +1 -1
  33. package/build/JsonConfigComponent/ConfigChip.d.ts +1 -1
  34. package/build/JsonConfigComponent/ConfigColor.d.ts +13 -20
  35. package/build/JsonConfigComponent/ConfigColor.js +9 -23
  36. package/build/JsonConfigComponent/ConfigColor.js.map +1 -1
  37. package/build/JsonConfigComponent/ConfigCoordinates.d.ts +8 -13
  38. package/build/JsonConfigComponent/ConfigCoordinates.js +29 -39
  39. package/build/JsonConfigComponent/ConfigCoordinates.js.map +1 -1
  40. package/build/JsonConfigComponent/ConfigCustom.d.ts +4 -50
  41. package/build/JsonConfigComponent/ConfigCustom.js +0 -1
  42. package/build/JsonConfigComponent/ConfigCustom.js.map +1 -1
  43. package/build/JsonConfigComponent/ConfigDatePicker.d.ts +8 -3
  44. package/build/JsonConfigComponent/ConfigDatePicker.js +21 -13
  45. package/build/JsonConfigComponent/ConfigDatePicker.js.map +1 -1
  46. package/build/JsonConfigComponent/ConfigDeviceManager.d.ts +5 -1
  47. package/build/JsonConfigComponent/ConfigDeviceManager.js +1 -1
  48. package/build/JsonConfigComponent/ConfigDeviceManager.js.map +1 -1
  49. package/build/JsonConfigComponent/ConfigFile.d.ts +1 -1
  50. package/build/JsonConfigComponent/ConfigFunc.d.ts +1 -1
  51. package/build/JsonConfigComponent/ConfigGeneric.d.ts +46 -32
  52. package/build/JsonConfigComponent/ConfigGeneric.js +23 -12
  53. package/build/JsonConfigComponent/ConfigGeneric.js.map +1 -1
  54. package/build/JsonConfigComponent/ConfigIP.d.ts +1 -1
  55. package/build/JsonConfigComponent/ConfigImageSendTo.d.ts +1 -1
  56. package/build/JsonConfigComponent/ConfigInstanceSelect.d.ts +1 -1
  57. package/build/JsonConfigComponent/ConfigInterface.d.ts +1 -1
  58. package/build/JsonConfigComponent/ConfigJsonEditor.d.ts +1 -1
  59. package/build/JsonConfigComponent/ConfigLanguage.d.ts +3 -2
  60. package/build/JsonConfigComponent/ConfigLanguage.js +2 -2
  61. package/build/JsonConfigComponent/ConfigLanguage.js.map +1 -1
  62. package/build/JsonConfigComponent/ConfigNumber.d.ts +3 -13
  63. package/build/JsonConfigComponent/ConfigNumber.js +2 -2
  64. package/build/JsonConfigComponent/ConfigNumber.js.map +1 -1
  65. package/build/JsonConfigComponent/ConfigObjectId.d.ts +1 -1
  66. package/build/JsonConfigComponent/ConfigPanel.d.ts +7 -2
  67. package/build/JsonConfigComponent/ConfigPanel.js +5 -40
  68. package/build/JsonConfigComponent/ConfigPanel.js.map +1 -1
  69. package/build/JsonConfigComponent/ConfigPassword.d.ts +1 -1
  70. package/build/JsonConfigComponent/ConfigPort.d.ts +6 -1
  71. package/build/JsonConfigComponent/ConfigPort.js +2 -3
  72. package/build/JsonConfigComponent/ConfigPort.js.map +1 -1
  73. package/build/JsonConfigComponent/ConfigRoom.d.ts +1 -1
  74. package/build/JsonConfigComponent/ConfigSelect.d.ts +1 -1
  75. package/build/JsonConfigComponent/ConfigSelectSendTo.d.ts +7 -16
  76. package/build/JsonConfigComponent/ConfigSelectSendTo.js +15 -61
  77. package/build/JsonConfigComponent/ConfigSelectSendTo.js.map +1 -1
  78. package/build/JsonConfigComponent/ConfigSendto.d.ts +3 -20
  79. package/build/JsonConfigComponent/ConfigSendto.js +8 -8
  80. package/build/JsonConfigComponent/ConfigSendto.js.map +1 -1
  81. package/build/JsonConfigComponent/ConfigSetState.d.ts +1 -1
  82. package/build/JsonConfigComponent/ConfigSlider.d.ts +1 -1
  83. package/build/JsonConfigComponent/ConfigStaticDivider.d.ts +1 -1
  84. package/build/JsonConfigComponent/ConfigStaticHeader.d.ts +1 -1
  85. package/build/JsonConfigComponent/ConfigStaticImage.d.ts +1 -1
  86. package/build/JsonConfigComponent/ConfigStaticText.d.ts +1 -1
  87. package/build/JsonConfigComponent/ConfigTable.d.ts +4 -25
  88. package/build/JsonConfigComponent/ConfigTable.js +5 -4
  89. package/build/JsonConfigComponent/ConfigTable.js.map +1 -1
  90. package/build/JsonConfigComponent/ConfigTabs.d.ts +8 -33
  91. package/build/JsonConfigComponent/ConfigTabs.js +10 -43
  92. package/build/JsonConfigComponent/ConfigTabs.js.map +1 -1
  93. package/build/JsonConfigComponent/ConfigText.d.ts +7 -13
  94. package/build/JsonConfigComponent/ConfigText.js +13 -18
  95. package/build/JsonConfigComponent/ConfigText.js.map +1 -1
  96. package/build/JsonConfigComponent/ConfigTextSendTo.d.ts +7 -9
  97. package/build/JsonConfigComponent/ConfigTextSendTo.js +11 -21
  98. package/build/JsonConfigComponent/ConfigTextSendTo.js.map +1 -1
  99. package/build/JsonConfigComponent/ConfigTimePicker.d.ts +9 -3
  100. package/build/JsonConfigComponent/ConfigTimePicker.js +28 -18
  101. package/build/JsonConfigComponent/ConfigTimePicker.js.map +1 -1
  102. package/build/JsonConfigComponent/ConfigTopic.d.ts +1 -1
  103. package/build/JsonConfigComponent/ConfigUUID.d.ts +12 -13
  104. package/build/JsonConfigComponent/ConfigUUID.js +1 -6
  105. package/build/JsonConfigComponent/ConfigUUID.js.map +1 -1
  106. package/build/JsonConfigComponent/ConfigUser.d.ts +1 -1
  107. package/build/JsonConfigComponent/index.d.ts +66 -25
  108. package/build/JsonConfigComponent/index.js +48 -61
  109. package/build/JsonConfigComponent/index.js.map +1 -1
  110. package/build/index.d.ts +2 -2
  111. package/build/index.js +2 -2
  112. package/build/index.js.map +1 -1
  113. package/package.json +27 -26
  114. package/src/JsonConfig.tsx +710 -0
  115. package/src/JsonConfigComponent/ChipInput.tsx +752 -0
  116. package/src/JsonConfigComponent/ConfigAccordion.tsx +278 -0
  117. package/src/JsonConfigComponent/ConfigAlive.tsx +74 -0
  118. package/src/JsonConfigComponent/ConfigAutocomplete.tsx +108 -0
  119. package/src/JsonConfigComponent/ConfigAutocompleteSendTo.tsx +183 -0
  120. package/src/JsonConfigComponent/ConfigCRON.jsx +101 -0
  121. package/src/JsonConfigComponent/ConfigCertCollection.tsx +102 -0
  122. package/src/JsonConfigComponent/ConfigCertificateSelect.tsx +92 -0
  123. package/src/JsonConfigComponent/ConfigCertificates.tsx +202 -0
  124. package/src/JsonConfigComponent/ConfigCheckLicense.jsx +662 -0
  125. package/src/JsonConfigComponent/ConfigCheckbox.tsx +67 -0
  126. package/src/JsonConfigComponent/ConfigChip.jsx +81 -0
  127. package/src/JsonConfigComponent/ConfigColor.tsx +86 -0
  128. package/src/JsonConfigComponent/ConfigCoordinates.tsx +234 -0
  129. package/src/JsonConfigComponent/ConfigCustom.tsx +246 -0
  130. package/src/JsonConfigComponent/ConfigDatePicker.tsx +48 -0
  131. package/src/JsonConfigComponent/ConfigDeviceManager.tsx +33 -0
  132. package/src/JsonConfigComponent/ConfigFile.jsx +181 -0
  133. package/src/JsonConfigComponent/ConfigFileSelector.jsx +520 -0
  134. package/src/JsonConfigComponent/ConfigFunc.jsx +90 -0
  135. package/src/JsonConfigComponent/ConfigGeneric.tsx +1027 -0
  136. package/src/JsonConfigComponent/ConfigIP.jsx +96 -0
  137. package/src/JsonConfigComponent/ConfigImageSendTo.jsx +79 -0
  138. package/src/JsonConfigComponent/ConfigImageUpload.jsx +114 -0
  139. package/src/JsonConfigComponent/ConfigInstanceSelect.jsx +172 -0
  140. package/src/JsonConfigComponent/ConfigInterface.jsx +112 -0
  141. package/src/JsonConfigComponent/ConfigJsonEditor.jsx +103 -0
  142. package/src/JsonConfigComponent/ConfigLanguage.tsx +153 -0
  143. package/src/JsonConfigComponent/ConfigLicense.jsx +148 -0
  144. package/src/JsonConfigComponent/ConfigNumber.tsx +207 -0
  145. package/src/JsonConfigComponent/ConfigObjectId.jsx +113 -0
  146. package/src/JsonConfigComponent/ConfigPanel.tsx +360 -0
  147. package/src/JsonConfigComponent/ConfigPassword.jsx +160 -0
  148. package/src/JsonConfigComponent/ConfigPattern.jsx +50 -0
  149. package/src/JsonConfigComponent/ConfigPort.tsx +232 -0
  150. package/src/JsonConfigComponent/ConfigRoom.jsx +90 -0
  151. package/src/JsonConfigComponent/ConfigSelect.jsx +124 -0
  152. package/src/JsonConfigComponent/ConfigSelectSendTo.tsx +251 -0
  153. package/src/JsonConfigComponent/ConfigSendto.tsx +340 -0
  154. package/src/JsonConfigComponent/ConfigSetState.jsx +116 -0
  155. package/src/JsonConfigComponent/ConfigSlider.jsx +97 -0
  156. package/src/JsonConfigComponent/ConfigStaticDivider.jsx +51 -0
  157. package/src/JsonConfigComponent/ConfigStaticHeader.jsx +63 -0
  158. package/src/JsonConfigComponent/ConfigStaticImage.jsx +48 -0
  159. package/src/JsonConfigComponent/ConfigStaticText.jsx +72 -0
  160. package/src/JsonConfigComponent/ConfigTable.tsx +1040 -0
  161. package/src/JsonConfigComponent/ConfigTabs.tsx +150 -0
  162. package/src/JsonConfigComponent/ConfigText.tsx +188 -0
  163. package/src/JsonConfigComponent/ConfigTextSendTo.tsx +102 -0
  164. package/src/JsonConfigComponent/ConfigTimePicker.tsx +63 -0
  165. package/src/JsonConfigComponent/ConfigTopic.jsx +78 -0
  166. package/src/JsonConfigComponent/ConfigUUID.tsx +55 -0
  167. package/src/JsonConfigComponent/ConfigUser.jsx +104 -0
  168. package/src/JsonConfigComponent/index.tsx +435 -0
  169. package/src/JsonConfigComponent/wrapper/Components/CustomModal.jsx +145 -0
  170. package/src/JsonConfigComponent/wrapper/Components/Editor.jsx +65 -0
  171. package/src/Utils.jsx +1683 -0
  172. package/src/index.tsx +14 -0
  173. package/src/types.d.ts +372 -0
@@ -0,0 +1,520 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { withStyles } from '@mui/styles';
4
+ import Dropzone from 'react-dropzone';
5
+
6
+ import {
7
+ InputLabel,
8
+ MenuItem,
9
+ FormHelperText,
10
+ FormControl,
11
+ Select,
12
+ IconButton,
13
+ ListItemText,
14
+ ListItemIcon,
15
+ } from '@mui/material';
16
+
17
+ import {
18
+ Refresh as IconRefresh,
19
+ UploadFile as IconUpload,
20
+ Delete as IconDelete,
21
+ PlayArrow as IconPlay,
22
+ MusicNote as IconAudio,
23
+ Videocam as IconVideo,
24
+ Article as IconText,
25
+ Code as IconCode,
26
+ } from '@mui/icons-material';
27
+ import { FaFileUpload as UploadIcon } from 'react-icons/fa';
28
+
29
+ import { Confirm as ConfirmDialog, Utils, I18n } from '@iobroker/adapter-react-v5';
30
+
31
+ import ConfigGeneric from './ConfigGeneric';
32
+
33
+ const styles = () => ({
34
+ fullWidth: {
35
+ width: '100%',
36
+ },
37
+ fullWidthOneButton: {
38
+ width: 'calc(100% - 42px)',
39
+ },
40
+ fullWidthTwoButtons: {
41
+ width: 'calc(100% - 84px)',
42
+ },
43
+ fullWidthThreeButtons: {
44
+ width: 'calc(100% - 126x)',
45
+ },
46
+ dropZone: {
47
+ width: '100%',
48
+ height: '100%',
49
+ position: 'absolute',
50
+ },
51
+ dropZoneEmpty: {
52
+
53
+ },
54
+ image: {
55
+ objectFit: 'contain',
56
+ margin: 'auto',
57
+ display: 'flex',
58
+ width: '100%',
59
+ height: '100%',
60
+ },
61
+ uploadDiv: {
62
+ position: 'relative',
63
+ width: '100%',
64
+ },
65
+ uploadDivDragging: {
66
+ opacity: 1,
67
+ background: 'rgba(128,255,128,0.1)',
68
+ },
69
+ uploadCenterDiv: {
70
+ margin: 5,
71
+ border: '3px dashed grey',
72
+ borderRadius: 5,
73
+ width: '100%',
74
+ height: '100%',
75
+ position: 'absolute',
76
+ display: 'flex',
77
+ },
78
+ uploadCenterIcon: {
79
+ paddingTop: 10,
80
+ width: 48,
81
+ height: 48,
82
+ },
83
+ uploadCenterText: {
84
+ fontSize: 16,
85
+ },
86
+ uploadCenterTextAndIcon: {
87
+ textAlign: 'center',
88
+ position: 'absolute',
89
+ top: 0,
90
+ bottom: 0,
91
+ left: 0,
92
+ right: 0,
93
+ display: 'flex',
94
+ flexDirection: 'column',
95
+ alignItems: 'center',
96
+ justifyContent: 'center',
97
+ },
98
+ disabledOpacity: {
99
+ opacity: 0.3,
100
+ cursor: 'default',
101
+ },
102
+ error: {
103
+ border: '2px solid red',
104
+ },
105
+ deleteButton: {
106
+
107
+ },
108
+ selectedImage: {
109
+ height: 40,
110
+ width: 40,
111
+ display: 'inline-block',
112
+ marginRight: 8,
113
+ },
114
+ });
115
+
116
+ const IMAGE_EXT = ['jpg', 'jpeg', 'svg', 'png', 'webp', 'gif', 'apng', 'avif', 'webp'];
117
+ const AUDIO_EXT = ['mp3', 'ogg', 'wav', 'aac'];
118
+ const VIDEO_EXT = ['avi', 'mp4', 'mov'];
119
+ const DOC_EXT = ['txt', 'log', 'html', 'htm'];
120
+ const JS_EXT = ['json', 'js', 'ts'];
121
+
122
+ class ConfigFileSelector extends ConfigGeneric {
123
+ constructor(props) {
124
+ super(props);
125
+ this.dropzoneRef = React.createRef();
126
+ this.imagePrefix = this.props.imagePrefix === undefined ? './files' : this.props.imagePrefix;
127
+ }
128
+
129
+ componentDidMount() {
130
+ super.componentDidMount();
131
+
132
+ this.objectID = (this.props.schema.objectID || '0_userdata.0').replace('%INSTANCE%', this.props.instance);
133
+ this.path = this.props.schema.upload;
134
+ if (this.path) {
135
+ if (this.path === '/') {
136
+ this.path = '';
137
+ } else if (!this.path.endsWith('/')) {
138
+ this.path = `${this.path}/`;
139
+ }
140
+ }
141
+
142
+ // read files
143
+ this.updateFiles()
144
+ .then(() => {
145
+ const value = ConfigGeneric.getValue(this.props.data, this.props.attr);
146
+ this.setState({ value });
147
+ });
148
+ }
149
+
150
+ updateFiles() {
151
+ return this.readFiles(this.props.schema.pattern)
152
+ .then(files => this.setState({ files }));
153
+ }
154
+
155
+ async readFolder(folderName, files, filter) {
156
+ try {
157
+ const dirFiles = await this.props.socket.readDir(this.objectID, folderName.replace(/^\//, '') || null);
158
+ for (let f = 0; f < dirFiles.length; f++) {
159
+ const file = dirFiles[f];
160
+ if (file.isDir) {
161
+ // read it
162
+ await this.readFolder(`${folderName + file.file}/`, files, filter);
163
+ } else {
164
+ let ok = false;
165
+ if (filter === '*.*' && file.file.includes('.')) {
166
+ ok = true;
167
+ } else if (!filter || filter === '*') {
168
+ ok = true;
169
+ } else if (filter === '.*' && file.file.startsWith('.')) {
170
+ ok = true;
171
+ } else {
172
+ const regExp = new RegExp(`^${filter.replace(/\./g, '\\.').replace(/\*/g, '.*')}$`);
173
+ ok = regExp.test(file.file);
174
+ }
175
+
176
+ ok && files.push({ name: folderName + file.file, size: file.stats ? Utils.formatBytes(file.stats.size) : '--' });
177
+ }
178
+ }
179
+ } catch (e) {
180
+ console.error(`Cannot read "${folderName}": ${e}`);
181
+ }
182
+
183
+ return files;
184
+ }
185
+
186
+ async readFiles(pattern) {
187
+ const files = [];
188
+ pattern = pattern || this.props.schema.pattern;
189
+ if (!pattern) {
190
+ pattern = '**/*.*';
191
+ }
192
+ let filter;
193
+ const pos = pattern.lastIndexOf('/');
194
+ if (pos === -1) {
195
+ filter = pattern;
196
+ } else {
197
+ filter = pattern.substring(pos + 1);
198
+ }
199
+
200
+ if (pattern.startsWith('**')) {
201
+ // read all folders
202
+ await this.readFolder('/', files, filter);
203
+ } else {
204
+ const pos_ = pattern.lastIndexOf('/');
205
+ if (pos_ === -1) {
206
+ await this.readFolder('/', files, filter);
207
+ } else {
208
+ const folder = pattern.substring(0, pos_ + 1);
209
+ await this.readFolder(folder, files, filter);
210
+ }
211
+ }
212
+
213
+ return files;
214
+ }
215
+
216
+ onDrop(acceptedFiles) {
217
+ const file = acceptedFiles[0];
218
+ const reader = new FileReader();
219
+ const maxSize = this.props.schema.maxSize || (2 * 1024 * 1024);
220
+
221
+ reader.onabort = () => console.log('file reading was aborted');
222
+ reader.onerror = () => console.log('file reading has failed');
223
+ reader.onload = () => {
224
+ let ext = `image/${file.name.split('.').pop().toLowerCase()}`;
225
+ if (ext === 'image/jpg') {
226
+ ext = 'image/jpeg';
227
+ } else if (ext.includes('svg')) {
228
+ ext = 'image/svg+xml';
229
+ }
230
+ if (file.size > maxSize) {
231
+ window.alert(I18n.t('File is too big. Max %sk allowed. Try use SVG.', Math.round(maxSize / 1024)));
232
+ return;
233
+ }
234
+ const base64 = `data:${ext};base64,${btoa(
235
+ new Uint8Array(reader.result)
236
+ .reduce((data, byte) => data + String.fromCharCode(byte), ''),
237
+ )}`;
238
+
239
+ this.props.socket.writeFile64(this.objectID, this.path + file.name, base64)
240
+ .then(() => this.updateFiles())
241
+ .catch(e => window.alert(`Cannot upload file: ${e}`));
242
+ };
243
+ reader.readAsArrayBuffer(file);
244
+ }
245
+
246
+ renderDeleteDialog() {
247
+ if (!this.state.deleteFile) {
248
+ return null;
249
+ }
250
+ return <ConfirmDialog
251
+ title={I18n.t('ra_Are you sure?')}
252
+ text={I18n.t('ra_File will be deleted')}
253
+ ok={I18n.t('ra_Delete')}
254
+ cancel={I18n.t('ra_Cancel')}
255
+ onClose={isOk => {
256
+ const deleteFile = this.state.deleteFile;
257
+ this.setState({ deleteFile: false }, () => {
258
+ if (isOk) {
259
+ this.props.socket.deleteFile(this.objectID, deleteFile)
260
+ .then(() => this.updateFiles())
261
+ .catch(e => window.alert(`Cannot delete file: ${e}`));
262
+ }
263
+ });
264
+ }}
265
+ />;
266
+ }
267
+
268
+ static base64ToArrayBuffer(base64) {
269
+ const binaryString = window.atob(base64);
270
+ const len = binaryString.length;
271
+ const bytes = new Uint8Array(len);
272
+ for (let i = 0; i < len; i++) {
273
+ bytes[i] = binaryString.charCodeAt(i);
274
+ }
275
+ return bytes.buffer;
276
+ }
277
+
278
+ loadFile() {
279
+ return this.props.socket.readFile(this.objectID, this.state.value, true);
280
+ }
281
+
282
+ play() {
283
+ this.loadFile()
284
+ .then(data => {
285
+ if (typeof AudioContext !== 'undefined') {
286
+ const context = new AudioContext();
287
+ const buf = ConfigFileSelector.base64ToArrayBuffer(data.file);
288
+ context.decodeAudioData(buf, buffer => {
289
+ const source = context.createBufferSource(); // creates a sound source
290
+ source.buffer = buffer; // tell the source which sound to play
291
+ source.connect(context.destination); // connect the source to the context's destination (the speakers)
292
+ source.start(0);
293
+ }, err => window.alert(`Cannot play: ${err}`));
294
+ }
295
+ });
296
+ }
297
+
298
+ getIcon(item) {
299
+ if (!item || !item.extension) {
300
+ return null;
301
+ }
302
+ if (IMAGE_EXT.includes(item.extension)) {
303
+ return <div
304
+ className={this.props.classes.selectedImage}
305
+ style={{
306
+ backgroundImage: `url(${this.imagePrefix}/${this.objectID}/${item.value})`,
307
+ backgroundSize: 'contain',
308
+ backgroundRepeat: 'no-repeat',
309
+ }}
310
+ />;
311
+ } if (AUDIO_EXT.includes(item.extension)) {
312
+ return <IconAudio />;
313
+ } if (DOC_EXT.includes(item.extension)) {
314
+ return <IconText />;
315
+ } if (VIDEO_EXT.includes(item.extension)) {
316
+ return <IconVideo />;
317
+ } if (JS_EXT.includes(item.extension)) {
318
+ return <IconCode />;
319
+ }
320
+ return null;
321
+ }
322
+
323
+ renderItem(error, disabled) {
324
+ if (!this.state.files) {
325
+ return null;
326
+ }
327
+ const folders = [];
328
+ if (!this.props.schema.withFolder) {
329
+ this.state.files.forEach(file => {
330
+ const pos = file.name.lastIndexOf('/');
331
+ if (pos === -1) {
332
+ if (!folders.includes('/')) {
333
+ folders.push('/');
334
+ }
335
+ } else {
336
+ const folder = file.name.substring(0, pos + 1);
337
+ if (!folders.includes(folder)) {
338
+ folders.push(folder);
339
+ }
340
+ }
341
+ });
342
+ }
343
+
344
+ /** @typedef {{ value: string, label: string, extension?: string }} Item */
345
+ /** @type {Item[]} */
346
+ const selectOptions = this.state.files
347
+ .map(file => ({
348
+ value: file.name,
349
+ label: !this.props.schema.withFolder && folders.length === 1 ? `${file.name.substring(folders[0].length)}` : `${file.name}${this.props.schema.noSize ? '' : `(${file.size})`}`,
350
+ extension: file.name.toLowerCase().split('.').pop(),
351
+ }));
352
+
353
+ if (!this.props.schema.noNone) {
354
+ selectOptions.unshift({ label: I18n.t('ra_none'), value: '' });
355
+ }
356
+
357
+ /** @type {Item | undefined } */
358
+ const item = selectOptions.find(_item => _item.value === this.state.value);
359
+
360
+ let buttons = 0;
361
+
362
+ if (this.props.schema.upload) {
363
+ buttons++;
364
+ }
365
+ if (this.props.schema.refresh) {
366
+ buttons++;
367
+ }
368
+ const play = this.state.value && (this.state.value.endsWith('.mp3') || this.state.value.endsWith('.ogg') || this.state.value.endsWith('.wav'));
369
+ // show play button
370
+ if (play) {
371
+ buttons++;
372
+ }
373
+
374
+ const element = <div className={this.props.classes.fullWidth}>
375
+ <FormControl variant="standard" style={{ width: `calc(100% - ${buttons * 42}px)` }}>
376
+ {this.props.schema.label ? <InputLabel>{this.getText(this.props.schema.label)}</InputLabel> : null}
377
+ <Select
378
+ variant="standard"
379
+ error={!!error}
380
+ disabled={!!disabled}
381
+ value={this.state.value || '_'}
382
+ renderValue={() => <>
383
+ {this.getIcon(item)}
384
+ <span>{item?.label || ''}</span>
385
+ </>}
386
+ onChange={e => {
387
+ this.setState({ value: e.target.value === '_' ? '' : e.target.value }, () =>
388
+ this.onChange(this.props.attr, this.state.value));
389
+ }}
390
+ >
391
+ {selectOptions.map(it => <MenuItem key={it.value} value={it.value}>
392
+ <ListItemIcon>{this.getIcon(it)}</ListItemIcon>
393
+ <ListItemText>{it.label}</ListItemText>
394
+ {this.props.schema.delete && item.value ?
395
+ <IconButton
396
+ className={this.props.classes.deleteButton}
397
+ size="small"
398
+ onClick={() => this.setState({ deleteFile: item.value })}
399
+ >
400
+ <IconDelete />
401
+ </IconButton> : null}
402
+ </MenuItem>)}
403
+ </Select>
404
+ {this.props.schema.help ? <FormHelperText>{this.renderHelp(this.props.schema.help, this.props.schema.helpLink, this.props.schema.noTranslation)}</FormHelperText> : null}
405
+ </FormControl>
406
+ { this.props.schema.refresh && <IconButton onClick={() => this.updateFiles()}><IconRefresh /></IconButton> }
407
+ { this.props.schema.upload && <IconButton onClick={() => this.dropzoneRef.current?.open()}><IconUpload /></IconButton> }
408
+ { play && <IconButton style={{ color: '#00FF00' }} onClick={() => this.play()}><IconPlay /></IconButton> }
409
+ </div>;
410
+
411
+ if (!this.props.schema.upload) {
412
+ return <>
413
+ {element}
414
+ {this.renderDeleteDialog()}
415
+ </>;
416
+ }
417
+ let accept = { '*/*': [] };
418
+ if (this.props.schema.fileTypes === 'image') {
419
+ accept = {
420
+ 'image/*': ['.png', '.jpg', '.svg', '.gif', '.apng', '.avif', '.webp'],
421
+ };
422
+ } else if (this.props.schema.fileTypes === 'audio') {
423
+ accept = {
424
+ 'audio/*': ['.mp3', '.ogg', '.wav', '.mp4'],
425
+ };
426
+ } else if (this.props.schema.fileTypes === 'text') {
427
+ accept = {
428
+ 'text/plain': ['.txt'],
429
+ };
430
+ }
431
+ if (this.props.schema.pattern) {
432
+ const last = this.props.schema.pattern.split('/').pop().toLowerCase().replace(/.*\./, '');
433
+ if (last === 'png' || last === 'jpg' || last === 'svg' || last === 'gif' || last === 'apng' || last === 'avif' || last === 'webp') {
434
+ accept = {
435
+ 'image/*': ['.png', '.jpg', '.svg', '.gif', '.apng', '.avif', '.webp'],
436
+ };
437
+ } else if (last === 'mp3' || last === 'ogg' || last === 'wav') {
438
+ accept = {
439
+ 'audio/*': ['.mp3', '.ogg', '.wav', '.mp4'],
440
+ };
441
+ } else if (last === 'ics') {
442
+ accept = {
443
+ 'text/calendar': ['.ics'],
444
+ };
445
+ } else if (last === 'txt') {
446
+ accept = {
447
+ 'text/plain': ['.txt'],
448
+ };
449
+ } else if (last === 'pem') {
450
+ accept = {
451
+ 'text/plain': ['.pem'],
452
+ };
453
+ } else {
454
+ accept = {
455
+ '*/*': [`.${last}`],
456
+ };
457
+ }
458
+ }
459
+
460
+ return <Dropzone
461
+ ref={this.dropzoneRef}
462
+ multiple={false}
463
+ accept={accept}
464
+ noKeyboard
465
+ noClick
466
+ maxSize={this.props.schema.maxSize || 2 * 1024 * 1024}
467
+ onDragEnter={() => {
468
+ this.setState({ uploadFile: 'dragging' });
469
+ }}
470
+ onDragLeave={() => this.setState({ uploadFile: true })}
471
+ onDrop={(acceptedFiles, errors) => {
472
+ this.setState({ uploadFile: false });
473
+ if (!acceptedFiles.length) {
474
+ window.alert((errors && errors[0] && errors[0].errors && errors[0].errors[0] && errors[0].errors[0].message) || I18n.t('Cannot upload'));
475
+ } else {
476
+ this.onDrop(acceptedFiles);
477
+ }
478
+ }}
479
+ >
480
+ {({ getRootProps, getInputProps }) => <div
481
+ className={Utils.clsx(
482
+ this.props.classes.uploadDiv,
483
+ this.state.uploadFile === 'dragging' && this.props.classes.uploadDivDragging,
484
+ disabled && this.props.classes.disabledOpacity,
485
+ )}
486
+ {...getRootProps()}
487
+ >
488
+ <input {...getInputProps()} />
489
+ {this.state.uploadFile === 'dragging' ? <div className={Utils.clsx(this.props.classes.uploadCenterDiv, this.state.uploadError && this.props.classes.error)}>
490
+ <div className={this.props.classes.uploadCenterTextAndIcon}>
491
+ <UploadIcon className={this.props.classes.uploadCenterIcon} />
492
+ <div className={this.props.classes.uploadCenterText}>
493
+ {
494
+ this.state.uploadFile === 'dragging' ? I18n.t('ra_Drop file here') :
495
+ I18n.t('ra_Place your files here or click here to open the browse dialog')
496
+ }
497
+ </div>
498
+ </div>
499
+ </div> : null}
500
+ {element}
501
+ {this.renderDeleteDialog()}
502
+ </div>}
503
+ </Dropzone>;
504
+ }
505
+ }
506
+
507
+ ConfigFileSelector.propTypes = {
508
+ socket: PropTypes.object.isRequired,
509
+ themeType: PropTypes.string,
510
+ themeName: PropTypes.string,
511
+ style: PropTypes.object,
512
+ className: PropTypes.string,
513
+ data: PropTypes.object.isRequired,
514
+ schema: PropTypes.object,
515
+ onError: PropTypes.func,
516
+ onChange: PropTypes.func,
517
+ imagePrefix: PropTypes.func,
518
+ };
519
+
520
+ export default withStyles(styles)(ConfigFileSelector);
@@ -0,0 +1,90 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { withStyles } from '@mui/styles';
4
+
5
+ import {
6
+ InputLabel,
7
+ MenuItem,
8
+ FormHelperText,
9
+ FormControl,
10
+ Select,
11
+ } from '@mui/material';
12
+
13
+ import { TextWithIcon, I18n } from '@iobroker/adapter-react-v5';
14
+
15
+ import ConfigGeneric from './ConfigGeneric';
16
+
17
+ const styles = () => ({
18
+ fullWidth: {
19
+ width: '100%',
20
+ },
21
+ });
22
+
23
+ class ConfigFunc extends ConfigGeneric {
24
+ componentDidMount() {
25
+ super.componentDidMount();
26
+ const value = ConfigGeneric.getValue(this.props.data, this.props.attr);
27
+
28
+ this.props.socket.getEnums('functions')
29
+ .then(enums => {
30
+ const selectOptions = Object.keys(enums)
31
+ .map(id => ({
32
+ value: this.props.schema.short ? id.replace('enum.functions.', '') : id,
33
+ label: this.getText(enums[id].common.name),
34
+ obj: enums[id],
35
+ }));
36
+
37
+ if (this.props.schema.allowDeactivate !== false) {
38
+ selectOptions.unshift({ label: I18n.t(ConfigGeneric.NONE_LABEL), value: ConfigGeneric.NONE_VALUE });
39
+ }
40
+
41
+ this.setState({ value, selectOptions });
42
+ });
43
+ }
44
+
45
+ renderItem(error, disabled /* , defaultValue */) {
46
+ if (!this.state.selectOptions) {
47
+ return null;
48
+ }
49
+
50
+ const item = this.state.selectOptions.find(it => it.value === this.state.value);
51
+
52
+ return <FormControl
53
+ variant="standard"
54
+ className={this.props.classes.fullWidth}
55
+ >
56
+ {this.props.schema.label ? <InputLabel>{this.getText(this.props.schema.label)}</InputLabel> : null}
57
+ <Select
58
+ variant="standard"
59
+ error={!!error}
60
+ disabled={!!disabled}
61
+ value={this.state.value || '_'}
62
+ renderValue={() => (item ? (item.obj ? <TextWithIcon value={item.obj} themeType={this.props.themeType} lang={I18n.getLanguage()} /> : item.label) : '')}
63
+ onChange={e => {
64
+ this.setState({ value: e.target.value === '_' ? '' : e.target.value }, () =>
65
+ this.onChange(this.props.attr, this.state.value));
66
+ }}
67
+ >
68
+ {this.state.selectOptions.map(it =>
69
+ <MenuItem key={it.value} value={it.value} style={it.value === ConfigGeneric.DIFFERENT_VALUE ? { opacity: 0.5 } : {}}>
70
+ {it.obj ? <TextWithIcon value={it.obj} themeType={this.props.themeType} lang={I18n.getLanguage()} /> : it.label}
71
+ </MenuItem>)}
72
+ </Select>
73
+ {this.props.schema.help ? <FormHelperText>{this.renderHelp(this.props.schema.help, this.props.schema.helpLink, this.props.schema.noTranslation)}</FormHelperText> : null}
74
+ </FormControl>;
75
+ }
76
+ }
77
+
78
+ ConfigFunc.propTypes = {
79
+ socket: PropTypes.object.isRequired,
80
+ themeType: PropTypes.string,
81
+ themeName: PropTypes.string,
82
+ style: PropTypes.object,
83
+ className: PropTypes.string,
84
+ data: PropTypes.object.isRequired,
85
+ schema: PropTypes.object,
86
+ onError: PropTypes.func,
87
+ onChange: PropTypes.func,
88
+ };
89
+
90
+ export default withStyles(styles)(ConfigFunc);