@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,1040 @@
1
+ import React, { createRef, type RefObject } from 'react';
2
+ import { /* lighten, */ withStyles } from '@mui/styles';
3
+ import Dropzone from 'react-dropzone';
4
+
5
+ import {
6
+ Button, Dialog, DialogActions, DialogContent, DialogTitle,
7
+ IconButton, InputAdornment, Paper,
8
+ Table, TableBody, TableCell, TableContainer,
9
+ TableHead, TableRow, TableSortLabel,
10
+ TextField, Toolbar, Tooltip,
11
+ Typography,
12
+ FormHelperText,
13
+ type Theme,
14
+ } from '@mui/material';
15
+
16
+ import {
17
+ Add as AddIcon,
18
+ Delete as DeleteIcon,
19
+ Close as CloseIcon,
20
+ ArrowUpward as UpIcon,
21
+ ArrowDownward as DownIcon,
22
+ FilterAlt as IconFilterOn,
23
+ FilterAltOff as IconFilterOff,
24
+ ContentCopy as CopyContentIcon,
25
+ Download as ExportIcon,
26
+ Warning as ErrorIcon,
27
+ UploadFile as ImportIcon,
28
+ Close as IconClose,
29
+ } from '@mui/icons-material';
30
+
31
+ import { Utils, I18n } from '@iobroker/adapter-react-v5';
32
+
33
+ import type {ConfigItemTableIndexed, ConfigItemPanel, ConfigItemTable} from '#JC/types';
34
+ import ConfigGeneric, { type ConfigGenericProps, type ConfigGenericState } from './ConfigGeneric';
35
+ // eslint-disable-next-line import/no-cycle
36
+ import ConfigPanel from './ConfigPanel';
37
+
38
+ const MAX_SIZE = 1024 * 1024; // 1MB
39
+
40
+ const styles: Record<string, any> = (theme: Theme) => ({
41
+ fullWidth: {
42
+ width: '100%',
43
+ },
44
+ root: {
45
+ width: '100%',
46
+ },
47
+ paper: {
48
+ width: '100%',
49
+ marginBottom: theme.spacing(2),
50
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
51
+ },
52
+ headerText: {
53
+ width: '100%',
54
+ },
55
+ table: {
56
+ minWidth: 750,
57
+ },
58
+ visuallyHidden: {
59
+ border: 0,
60
+ clip: 'rect(0 0 0 0)',
61
+ height: 1,
62
+ margin: -1,
63
+ overflow: 'hidden',
64
+ padding: 0,
65
+ position: 'absolute',
66
+ top: 20,
67
+ width: 1,
68
+ },
69
+ label: {
70
+ display: 'flex',
71
+ justifyContent: 'space-between',
72
+ },
73
+ highlight:
74
+ theme.palette.mode === 'light'
75
+ ? {
76
+ color: theme.palette.secondary.main,
77
+ // backgroundColor: lighten(theme.palette.secondary.light, 0.85),
78
+ }
79
+ : {
80
+ color: theme.palette.text.primary,
81
+ backgroundColor: theme.palette.secondary.dark,
82
+ },
83
+ title: {
84
+ flex: '1 1 100%',
85
+ },
86
+ rootTool: {
87
+ paddingLeft: theme.spacing(2),
88
+ paddingRight: theme.spacing(1),
89
+ },
90
+ silver: {
91
+ opacity: 0.2,
92
+ },
93
+ flex: {
94
+ display: 'flex',
95
+ alignItems: 'baseline',
96
+ },
97
+ filteredOut: {
98
+ padding: 10,
99
+ display: 'flex',
100
+ textAlign: 'center',
101
+ },
102
+ buttonEmpty: {
103
+ width: 34,
104
+ display: 'inline-block',
105
+ },
106
+ buttonCell: {
107
+ whiteSpace: 'nowrap',
108
+ },
109
+
110
+ dropZone: {
111
+ width: '100%',
112
+ height: 100,
113
+ position: 'relative',
114
+ },
115
+ dropZoneEmpty: {
116
+
117
+ },
118
+ uploadDiv: {
119
+ position: 'relative',
120
+ width: '100%',
121
+ height: 300,
122
+ opacity: 0.9,
123
+ marginTop: 30,
124
+ cursor: 'pointer',
125
+ outline: 'none',
126
+ },
127
+ uploadDivDragging: {
128
+ opacity: 1,
129
+ background: 'rgba(128,255,128,0.1)',
130
+ },
131
+ image: {
132
+ objectFit: 'contain',
133
+ margin: 'auto',
134
+ display: 'flex',
135
+ width: '100%',
136
+ height: '100%',
137
+ },
138
+ uploadCenterDiv: {
139
+ margin: 5,
140
+ border: '3px dashed grey',
141
+ borderRadius: 5,
142
+ width: 'calc(100% - 10px)',
143
+ height: 'calc(100% - 10px)',
144
+ minHeight: 300,
145
+ position: 'relative',
146
+ display: 'flex',
147
+ },
148
+ uploadCenterIcon: {
149
+ paddingTop: 10,
150
+ width: 48,
151
+ height: 48,
152
+ },
153
+ uploadCenterText: {
154
+ fontSize: 16,
155
+ },
156
+ uploadCenterTextAndIcon: {
157
+ textAlign: 'center',
158
+ position: 'absolute',
159
+ top: 0,
160
+ bottom: 0,
161
+ left: 0,
162
+ right: 0,
163
+ display: 'flex',
164
+ flexDirection: 'column',
165
+ alignItems: 'center',
166
+ justifyContent: 'center',
167
+ },
168
+ buttonRemoveWrapper: {
169
+ position: 'absolute',
170
+ zIndex: 222,
171
+ right: 0,
172
+ },
173
+ error: {
174
+ border: '2px solid red',
175
+ boxSizing: 'border-box',
176
+ },
177
+ });
178
+
179
+ function objectToArray(object: Record<string, any>, nameOfFirstAttr: string, nameOfSecondAttr?: string) {
180
+ nameOfFirstAttr = nameOfFirstAttr || 'key';
181
+
182
+ const array: Record<string, any>[] = [];
183
+ Object.keys(object).forEach(key => {
184
+ const item: Record<string, any> = {};
185
+ item[nameOfFirstAttr] = key;
186
+
187
+ if (nameOfSecondAttr) {
188
+ item[nameOfSecondAttr] = object[key];
189
+ array.push(item);
190
+ } else {
191
+ array.push(Object.assign(item, object[key]));
192
+ }
193
+ });
194
+
195
+ return array;
196
+ }
197
+
198
+ function arrayToObject(array: Record<string, any>[], nameOfFirstAttr: string, nameOfSecondAttr?: string) {
199
+ nameOfFirstAttr = nameOfFirstAttr || 'key';
200
+
201
+ const object: Record<string, any> = {};
202
+
203
+ array.forEach((row: Record<string, any>) => {
204
+ let key = row[nameOfFirstAttr];
205
+ if (key === null || key === undefined) {
206
+ key = '';
207
+ }
208
+ delete row[nameOfFirstAttr];
209
+
210
+ if (nameOfSecondAttr) {
211
+ object[key] = row[nameOfSecondAttr];
212
+ } else {
213
+ object[key] = row;
214
+ }
215
+ });
216
+
217
+ return object;
218
+ }
219
+
220
+ interface ConfigTableProps extends ConfigGenericProps {
221
+ schema: ConfigItemTable;
222
+ }
223
+
224
+ interface ConfigTableState extends ConfigGenericState {
225
+ value: Record<string, any>[];
226
+ visibleValue: number[] | null;
227
+ orderBy: string;
228
+ order: 'asc' | 'desc';
229
+ iteration: number;
230
+ filterOn: string[];
231
+ errorMessage: string;
232
+ showImportDialog: boolean;
233
+ showTypeOfImportDialog: Record<string, any>[] | false;
234
+ instanceObj: ioBroker.InstanceObject;
235
+ customObj: Record<string, any>;
236
+ uploadFile: boolean | 'dragging';
237
+ icon: boolean;
238
+ }
239
+
240
+ function encrypt(secret: string, value: string): string {
241
+ let result = '';
242
+ for (let i = 0; i < value.length; i++) {
243
+ result += String.fromCharCode(secret[i % secret.length].charCodeAt(0) ^ value.charCodeAt(i));
244
+ }
245
+ return result;
246
+ }
247
+ function decrypt(secret: string, value: string): string {
248
+ let result = '';
249
+ for (let i = 0; i < value.length; i++) {
250
+ result += String.fromCharCode(secret[i % secret.length].charCodeAt(0) ^ value.charCodeAt(i));
251
+ }
252
+ return result;
253
+ }
254
+
255
+ class ConfigTable extends ConfigGeneric<ConfigTableProps, ConfigTableState> {
256
+ private readonly filterRefs: Record<string, RefObject<HTMLInputElement>>;
257
+
258
+ private typingTimer: ReturnType<typeof setTimeout> | null = null;
259
+
260
+ private secret: string = 'Zgfr56gFe87jJOM';
261
+
262
+ constructor(props: ConfigTableProps) {
263
+ super(props);
264
+ this.filterRefs = {};
265
+ this.props.schema.items = this.props.schema.items || [];
266
+ this.props.schema.items.forEach((el: ConfigItemTableIndexed) => {
267
+ if (el.filter) {
268
+ this.filterRefs[el.attr] = createRef();
269
+ }
270
+ });
271
+ }
272
+
273
+ /**
274
+ * React lifecycle hook, called once as component is mounted
275
+ */
276
+ async componentDidMount(): Promise<void> {
277
+ super.componentDidMount();
278
+ const _value: Record<string, any>[] | Record<string, any> = ConfigGeneric.getValue(this.props.data, this.props.attr) || [];
279
+ let value: Record<string, any>[];
280
+
281
+ // if the list is given as an object
282
+ if (this.props.schema.objKeyName) {
283
+ value = objectToArray(_value as Record<string, any>, this.props.schema.objKeyName, this.props.schema.objValueName);
284
+ } else {
285
+ value = _value as Record<string, any>[];
286
+ }
287
+
288
+ if (!Array.isArray(_value)) {
289
+ value = [];
290
+ }
291
+
292
+ if (this.props.schema.encryptedAttributes) {
293
+ const systemConfig = await this.props.socket.getCompactSystemConfig();
294
+ this.secret = systemConfig?.native.secret || this.secret;
295
+
296
+ _value.forEach((el: Record<string, any>) => {
297
+ this.props.schema.encryptedAttributes.forEach((attr: string) => {
298
+ if (el[attr]) {
299
+ el[attr] = decrypt(this.secret, el[attr]);
300
+ }
301
+ });
302
+ });
303
+ }
304
+
305
+ this.setState({
306
+ value,
307
+ visibleValue: null,
308
+ orderBy: /* this.props.schema.items.length ? this.props.schema.items[0].attr : */'',
309
+ order: 'asc',
310
+ iteration: 0,
311
+ filterOn: [],
312
+ }, () => this.validateUniqueProps());
313
+ }
314
+
315
+ componentWillUnmount() {
316
+ this.typingTimer && clearTimeout(this.typingTimer);
317
+ this.typingTimer = null;
318
+ super.componentWillUnmount();
319
+ }
320
+
321
+ itemTable(attrItem: string, data: Record<string, any>, idx: number) {
322
+ const { value } = this.state;
323
+ const { schema } = this.props;
324
+ const schemaForAttribute = schema.items && schema.items.find((el: ConfigItemTableIndexed) => el.attr === attrItem);
325
+
326
+ if (!schemaForAttribute) {
327
+ return null;
328
+ }
329
+
330
+ const schemaItem = {
331
+ items: {
332
+ [attrItem]: schemaForAttribute,
333
+ },
334
+ };
335
+
336
+ return <ConfigPanel
337
+ index={idx + this.state.iteration}
338
+ arrayIndex={idx}
339
+ changed={this.props.changed}
340
+ globalData={this.props.data}
341
+ socket={this.props.socket}
342
+ adapterName={this.props.adapterName}
343
+ instance={this.props.instance}
344
+ common={this.props.common}
345
+ alive={this.props.alive}
346
+ themeType={this.props.themeType}
347
+ themeName={this.props.themeName}
348
+ data={data}
349
+ table
350
+ custom
351
+ schema={schemaItem as ConfigItemPanel}
352
+ systemConfig={this.props.systemConfig}
353
+ dateFormat={this.props.dateFormat}
354
+ isFloatComma={this.props.isFloatComma}
355
+ imagePrefix={this.props.imagePrefix}
356
+ onCommandRunning={this.props.onCommandRunning}
357
+ forceUpdate={this.props.forceUpdate}
358
+ originalData={this.props.originalData}
359
+ customs={this.props.customs}
360
+ onChange={(attr: string, valueChange: any) => {
361
+ const newObj = JSON.parse(JSON.stringify(value));
362
+ newObj[idx][attr] = valueChange;
363
+ this.setState({ value: newObj }, () => {
364
+ this.validateUniqueProps();
365
+ this.onChangeWrapper(newObj, true);
366
+ });
367
+ }}
368
+ onError={(error: string, attr?: string) => this.onError(error, attr)}
369
+ />;
370
+ }
371
+
372
+ /**
373
+ * Validate that columns configured in `uniqueColumns` have unique values
374
+ */
375
+ validateUniqueProps() {
376
+ if (!this.props.schema.uniqueColumns) {
377
+ return;
378
+ }
379
+
380
+ for (const uniqueCol of this.props.schema.uniqueColumns) {
381
+ /** @type {string[]} */
382
+ const allVals: (string | number)[] = [];
383
+ const found = this.state.value.find(entry => {
384
+ const val = entry[uniqueCol];
385
+ if (allVals.includes(val)) {
386
+ this.onError(uniqueCol, 'is not unique');
387
+ this.setState({ errorMessage: I18n.t('Non-allowed duplicate entry "%s" in column "%s"', val, uniqueCol) });
388
+ return true;
389
+ }
390
+ allVals.push(val);
391
+ return false;
392
+ });
393
+
394
+ if (!found) {
395
+ this.onError(uniqueCol, null);
396
+ this.setState({ errorMessage: '' });
397
+ }
398
+ }
399
+ }
400
+
401
+ static descendingComparator(a: Record<string, any>, b: Record<string, any>, orderBy: string): number {
402
+ if (b[orderBy] < a[orderBy]) {
403
+ return -1;
404
+ }
405
+ if (b[orderBy] > a[orderBy]) {
406
+ return 1;
407
+ }
408
+ return 0;
409
+ }
410
+
411
+ static getComparator(order: 'desc' | 'asc', orderBy: string) {
412
+ return order === 'desc'
413
+ ? (a: Record<string, any>, b: Record<string, any>) => ConfigTable.descendingComparator(a, b, orderBy)
414
+ : (a: Record<string, any>, b: Record<string, any>) => -ConfigTable.descendingComparator(a, b, orderBy);
415
+ }
416
+
417
+ static getFilterValue(el: React.RefObject<HTMLInputElement>) {
418
+ return (el?.current?.children[0]?.children[0] as HTMLInputElement)?.value;
419
+ }
420
+
421
+ static setFilterValue(el: React.RefObject<HTMLInputElement>, filterValue: string) {
422
+ return (el.current.children[0].children[0] as HTMLInputElement).value = filterValue;
423
+ }
424
+
425
+ handleRequestSort = (property: string, orderCheck: boolean = false) => {
426
+ const { order, orderBy } = this.state;
427
+ if (orderBy) {
428
+ const isAsc = orderBy === property && order === 'asc';
429
+ const newOrder = orderCheck ? order : (isAsc ? 'desc' : 'asc');
430
+ const newValue = this.stableSort(newOrder, property);
431
+ this.setState({ order: newOrder, orderBy: property, iteration: this.state.iteration + 10000 }, () =>
432
+ this.applyFilter(false, newValue));
433
+ }
434
+ };
435
+
436
+ stableSort = (order: 'desc' | 'asc', orderBy: string) => {
437
+ const { value } = this.state;
438
+ const comparator = ConfigTable.getComparator(order, orderBy);
439
+ const stabilizedThis = value.map((el, index) => ({ el, index }));
440
+
441
+ stabilizedThis.sort((a, b) => {
442
+ const order_ = comparator(a.el, b.el);
443
+ if (order_ !== 0) {
444
+ return order_;
445
+ }
446
+ return a.index - b.index;
447
+ });
448
+
449
+ return stabilizedThis.map(el => el.el);
450
+ };
451
+
452
+ enhancedTableHead(buttonsWidth: number, doAnyFilterSet: boolean) {
453
+ const { schema, classes } = this.props;
454
+ const { order, orderBy } = this.state;
455
+ return <TableHead>
456
+ <TableRow>
457
+ {schema.items && schema.items.map((headCell: ConfigItemTableIndexed, i: number) =>
458
+ <TableCell
459
+ style={{ width: typeof headCell.width === 'string' && headCell.width.endsWith('%') ? headCell.width : headCell.width }}
460
+ key={`${headCell.attr}_${i}`}
461
+ align="left"
462
+ sortDirection={orderBy === headCell.attr ? order : false}
463
+ >
464
+ <div className={classes.flex} style={schema.showFirstAddOnTop ? { flexDirection: 'column' } : undefined}>
465
+ {!i && !schema.noDelete ? <Tooltip title={doAnyFilterSet ? I18n.t('ra_Cannot add items with set filter') : I18n.t('ra_Add row')}>
466
+ <span>
467
+ <IconButton size="small" color="primary" disabled={!!doAnyFilterSet && !this.props.schema.allowAddByFilter} onClick={this.onAdd}>
468
+ <AddIcon />
469
+ </IconButton>
470
+ </span>
471
+ </Tooltip> : null}
472
+ {headCell.sort && <TableSortLabel
473
+ active
474
+ className={Utils.clsx(orderBy !== headCell.attr && classes.silver)}
475
+ direction={orderBy === headCell.attr ? order : 'asc'}
476
+ onClick={() => this.handleRequestSort(headCell.attr)}
477
+ />}
478
+ {headCell.filter && this.state.filterOn.includes(headCell.attr) ?
479
+ <TextField
480
+ variant="standard"
481
+ ref={this.filterRefs[headCell.attr]}
482
+ onChange={() => this.applyFilter()}
483
+ title={I18n.t('ra_You can filter entries by entering here some text')}
484
+ InputProps={{
485
+ endAdornment: ConfigTable.getFilterValue(this.filterRefs[headCell.attr]) && <InputAdornment position="end">
486
+ <IconButton
487
+ size="small"
488
+ onClick={() => {
489
+ ConfigTable.setFilterValue(this.filterRefs[headCell.attr], '');
490
+ this.applyFilter();
491
+ }}
492
+ >
493
+ <CloseIcon />
494
+ </IconButton>
495
+ </InputAdornment>,
496
+ }}
497
+ fullWidth
498
+ placeholder={this.getText(headCell.title)}
499
+ />
500
+ : <span className={this.props.classes.headerText}>{this.getText(headCell.title)}</span>}
501
+ {headCell.filter ? <IconButton
502
+ title={I18n.t('ra_Show/hide filter input')}
503
+ size="small"
504
+ onClick={() => {
505
+ const filterOn = [...this.state.filterOn];
506
+ const pos = this.state.filterOn.indexOf(headCell.attr);
507
+ if (pos === -1) {
508
+ filterOn.push(headCell.attr);
509
+ } else {
510
+ filterOn.splice(pos, 1);
511
+ }
512
+ this.setState({ filterOn }, () => {
513
+ if (pos && ConfigTable.getFilterValue(this.filterRefs[headCell.attr])) {
514
+ ConfigTable.setFilterValue(this.filterRefs[headCell.attr], '');
515
+ this.applyFilter();
516
+ }
517
+ });
518
+ }}
519
+ >
520
+ {this.state.filterOn.includes(headCell.attr) ? <IconFilterOff /> : <IconFilterOn />}
521
+ </IconButton> : null}
522
+ </div>
523
+ </TableCell>)}
524
+ {!schema.noDelete && <TableCell
525
+ style={{
526
+ paddingLeft: 20, paddingRight: 20, width: buttonsWidth, textAlign: 'right',
527
+ }}
528
+ padding="checkbox"
529
+ >
530
+ {schema.import ? <IconButton
531
+ style={{ marginRight: 10 }}
532
+ size="small"
533
+ onClick={() => this.setState({ showImportDialog: true })}
534
+ title={I18n.t('ra_import data from %s file', 'CSV')}
535
+ >
536
+ <ImportIcon />
537
+ </IconButton> : null}
538
+ {schema.export ? <IconButton
539
+ style={{ marginRight: 10 }}
540
+ size="small"
541
+ onClick={() => this.onExport()}
542
+ title={I18n.t('ra_Export data to %s file', 'CSV')}
543
+ >
544
+ <ExportIcon />
545
+ </IconButton> : null}
546
+ <IconButton disabled size="small">
547
+ <DeleteIcon />
548
+ </IconButton>
549
+ </TableCell>}
550
+ </TableRow>
551
+ </TableHead>;
552
+ }
553
+
554
+ onDelete = (index: number) => () => {
555
+ const newValue = JSON.parse(JSON.stringify(this.state.value));
556
+ newValue.splice(index, 1);
557
+
558
+ this.setState({ value: newValue, iteration: this.state.iteration + 10_000 }, () =>
559
+ this.applyFilter(false, null, () =>
560
+ this.onChangeWrapper(newValue)));
561
+ };
562
+
563
+ onExport() {
564
+ const { schema } = this.props;
565
+ const { value } = this.state;
566
+ const cols = schema.items.map((it: ConfigItemTableIndexed) => it.attr);
567
+ const lines = [cols.join(';')];
568
+ value.forEach(row => {
569
+ const line: string[] = [];
570
+ schema.items.forEach((it: ConfigItemTableIndexed) => {
571
+ if (row[it.attr].includes(';')) {
572
+ line.push(`"${row[it.attr]}"`);
573
+ } else {
574
+ line.push(row[it.attr]);
575
+ }
576
+ });
577
+ lines.push(line.join(';'));
578
+ });
579
+ const el = document.createElement('a');
580
+ el.setAttribute('href', `data:text/csv;charset=utf-8,${encodeURIComponent(lines.join('\n'))}`);
581
+ const now = new Date();
582
+ el.setAttribute(
583
+ 'download',
584
+ `${now.getFullYear()}_${(now.getMonth() + 1).toString().padStart(2, '0')}_${now.getDate().toString().padStart(2, '0')}_${this.props.adapterName}.${this.props.instance}_${this.props.attr}.csv`,
585
+ );
586
+
587
+ el.style.display = 'none';
588
+ document.body.appendChild(el);
589
+
590
+ el.click();
591
+
592
+ document.body.removeChild(el);
593
+ }
594
+
595
+ onImport(text: string): void {
596
+ const lines = text.split('\n').map((line: string) => line.replace('\r', '').trim());
597
+ // the first line is header
598
+ const { schema } = this.props;
599
+
600
+ const header = lines.shift()
601
+ .split(';')
602
+ .filter(it => it && schema.items.find((it2: ConfigItemTableIndexed) => it2.attr === it));
603
+
604
+ const values: Record<string, any>[] = [];
605
+ lines.forEach((line: string) => {
606
+ const parts: string[] = line.split(';');
607
+ const obj: Record<string, string | number | boolean> = {};
608
+ for (let p = 0; p < parts.length; p++) {
609
+ let value = parts[p];
610
+ if (value.startsWith('"')) {
611
+ value = value.substring(1);
612
+ while (p < parts.length && !value.endsWith('"')) {
613
+ value += `;${parts[++p]}`;
614
+ }
615
+ value = value.substring(0, value.length - 1);
616
+ }
617
+
618
+ let val: string | number | boolean = value;
619
+
620
+ if (value === 'true') {
621
+ val = true;
622
+ } else if (value === 'false') {
623
+ val = false;
624
+ // eslint-disable-next-line no-restricted-properties
625
+ } else if (window.isFinite(value as any as number)) {
626
+ const attr = this.props.schema.items.find((it: ConfigItemTableIndexed) => it.attr === header[p]);
627
+ if (attr && attr.type === 'number') {
628
+ // if a type of attribute is a "number"
629
+ val = parseFloat(value);
630
+ } else {
631
+ val = value;
632
+ }
633
+ } else {
634
+ val = value;
635
+ }
636
+
637
+ obj[header[p]] = val;
638
+ }
639
+ values.push(obj);
640
+ });
641
+
642
+ if (values.length) {
643
+ if (this.state.value?.length) {
644
+ this.setState({ showTypeOfImportDialog: values, showImportDialog: false });
645
+ } else {
646
+ this.setState({ value: values, showImportDialog: false });
647
+ }
648
+ } else {
649
+ window.alert('ra_No data found in file');
650
+ }
651
+ }
652
+
653
+ onClone = (index: number) => () => {
654
+ const newValue = JSON.parse(JSON.stringify(this.state.value));
655
+ const cloned = JSON.parse(JSON.stringify(newValue[index]));
656
+ if (typeof this.props.schema.clone === 'string' && typeof cloned[this.props.schema.clone] === 'string') {
657
+ let i = 1;
658
+ let text = cloned[this.props.schema.clone];
659
+ const pattern = text.match(/(\d+)$/);
660
+ if (pattern) {
661
+ text = text.replace(pattern[0], '');
662
+ i = parseInt(pattern[0], 10) + 1;
663
+ } else {
664
+ text += '_';
665
+ }
666
+ // eslint-disable-next-line no-loop-func
667
+ while (newValue.find((it: Record<string, any>) => it[this.props.schema.clone as string] === text + i.toString())) {
668
+ i++;
669
+ }
670
+ cloned[this.props.schema.clone] = `${cloned[this.props.schema.clone]}_${i}`;
671
+ }
672
+
673
+ newValue.splice(index, 0, cloned);
674
+
675
+ this.setState({ value: newValue, iteration: this.state.iteration + 10000 }, () =>
676
+ this.applyFilter(false, null, () =>
677
+ this.onChangeWrapper(newValue)));
678
+ };
679
+
680
+ onChangeWrapper = (newValue: Record<string, any>[], updateVisible?: boolean) => {
681
+ this.typingTimer && clearTimeout(this.typingTimer);
682
+
683
+ this.typingTimer = setTimeout((value, _updateVisible) => {
684
+ this.typingTimer = null;
685
+
686
+ if (this.props.schema.encryptedAttributes) {
687
+ const _value = JSON.parse(JSON.stringify(value));
688
+ _value.forEach((el: Record<string, any>) => {
689
+ this.props.schema.encryptedAttributes.forEach((attr: string) => {
690
+ if (el[attr]) {
691
+ el[attr] = encrypt(this.secret, el[attr]);
692
+ }
693
+ });
694
+ });
695
+
696
+ if (this.props.schema.objKeyName) {
697
+ const objValue = arrayToObject(_value, this.props.schema.objKeyName, this.props.schema.objValueName);
698
+ this.onChange(this.props.attr, objValue);
699
+ } else {
700
+ this.onChange(this.props.attr, _value);
701
+ }
702
+ } else if (this.props.schema.objKeyName) {
703
+ const objValue = arrayToObject(JSON.parse(JSON.stringify(value)), this.props.schema.objKeyName, this.props.schema.objValueName);
704
+ this.onChange(this.props.attr, objValue);
705
+ } else {
706
+ this.onChange(this.props.attr, value);
707
+ }
708
+
709
+ if (_updateVisible) {
710
+ this.applyFilter(false, value);
711
+ this.handleRequestSort(this.state.orderBy, true);
712
+ }
713
+ }, 300, newValue, updateVisible);
714
+ };
715
+
716
+ onAdd = () => {
717
+ const { schema } = this.props;
718
+ const newValue = JSON.parse(JSON.stringify(this.state.value));
719
+ const newItem = schema.items?.reduce((accumulator: Record<string, any>, currentValue: ConfigItemTableIndexed) => {
720
+ let defaultValue;
721
+ if (currentValue.defaultFunc) {
722
+ if (this.props.custom) {
723
+ defaultValue = currentValue.defaultFunc ? this.executeCustom(
724
+ currentValue.defaultFunc,
725
+ this.props.data,
726
+ this.props.customObj,
727
+ this.props.instanceObj,
728
+ newValue.length,
729
+ this.props.data,
730
+ ) : this.props.schema.default;
731
+ } else {
732
+ defaultValue = currentValue.defaultFunc ? this.execute(currentValue.defaultFunc, this.props.schema.default, this.props.data, newValue.length, this.props.data) : this.props.schema.default;
733
+ }
734
+ } else {
735
+ defaultValue = currentValue.default === undefined ? null : currentValue.default;
736
+ }
737
+
738
+ accumulator[currentValue.attr] = defaultValue;
739
+ return accumulator;
740
+ }, {});
741
+
742
+ newValue.push(newItem);
743
+
744
+ this.setState({ value: newValue }, () =>
745
+ this.applyFilter(false, null, () =>
746
+ this.onChangeWrapper(newValue)));
747
+ };
748
+
749
+ isAnyFilterSet(): boolean {
750
+ return !!Object.keys(this.filterRefs).find(attr => ConfigTable.getFilterValue(this.filterRefs[attr]));
751
+ }
752
+
753
+ applyFilter = (clear?: boolean, value?: Record<string, any>[], cb?: () => void): void => {
754
+ value = value || this.state.value;
755
+ let visibleValue = value.map((_, i) => i);
756
+ Object.keys(this.filterRefs).forEach(attr => {
757
+ let valueInputRef = ConfigTable.getFilterValue(this.filterRefs[attr]);
758
+ if (!clear && valueInputRef) {
759
+ valueInputRef = valueInputRef.toLowerCase();
760
+ visibleValue = visibleValue.filter(idx => value[idx] && value[idx][attr] && value[idx][attr].toLowerCase().includes(valueInputRef));
761
+ } else if (this.filterRefs[attr].current) {
762
+ ConfigTable.setFilterValue(this.filterRefs[attr], '');
763
+ }
764
+ });
765
+
766
+ if (visibleValue.length === value.length) {
767
+ visibleValue = null;
768
+ }
769
+
770
+ if (visibleValue === null && this.state.visibleValue === null) {
771
+ cb && cb();
772
+ return;
773
+ }
774
+
775
+ if (JSON.stringify(visibleValue) !== JSON.stringify(this.state.visibleValue)) {
776
+ this.setState({ visibleValue }, () => cb && cb());
777
+ } else {
778
+ cb && cb();
779
+ }
780
+ };
781
+
782
+ onMoveUp(idx: number) {
783
+ const newValue = JSON.parse(JSON.stringify(this.state.value));
784
+ const item = newValue[idx];
785
+ newValue.splice(idx, 1);
786
+ newValue.splice(idx - 1, 0, item);
787
+ this.setState({ value: newValue, iteration: this.state.iteration + 10000 }, () =>
788
+ this.applyFilter(false, null, () =>
789
+ this.onChangeWrapper(newValue)));
790
+ }
791
+
792
+ onMoveDown(idx: number) {
793
+ const newValue = JSON.parse(JSON.stringify(this.state.value));
794
+ const item = newValue[idx];
795
+ newValue.splice(idx, 1);
796
+ newValue.splice(idx + 1, 0, item);
797
+ this.setState({ value: newValue, iteration: this.state.iteration + 10000 }, () =>
798
+ this.applyFilter(false, null, () =>
799
+ this.onChangeWrapper(newValue)));
800
+ }
801
+
802
+ onDrop(acceptedFiles: File[]) {
803
+ const file = acceptedFiles[0];
804
+ const reader = new FileReader();
805
+
806
+ reader.onabort = () => console.log('file reading was aborted');
807
+ reader.onerror = () => console.log('file reading has failed');
808
+ reader.onload = () => {
809
+ if (file.size > MAX_SIZE) {
810
+ window.alert(I18n.t('ra_File is too big. Max %sk allowed. Try use SVG.', Math.round(MAX_SIZE / 1024)));
811
+ return;
812
+ }
813
+ const text = new Uint8Array(reader.result as ArrayBufferLike)
814
+ .reduce((data, byte) => data + String.fromCharCode(byte), '');
815
+
816
+ this.onImport(text);
817
+ };
818
+ reader.readAsArrayBuffer(file);
819
+ }
820
+
821
+ showTypeOfImportDialog() {
822
+ if (!this.state.showTypeOfImportDialog) {
823
+ return null;
824
+ }
825
+ return <Dialog
826
+ open={!0}
827
+ onClose={() => this.setState({ showTypeOfImportDialog: false })}
828
+ maxWidth="md"
829
+ >
830
+ <DialogTitle>{I18n.t('ra_Append or replace?')}</DialogTitle>
831
+ <DialogContent>
832
+ {I18n.t('ra_Append %s entries or replace existing?', this.state.showTypeOfImportDialog.length)}
833
+ </DialogContent>
834
+ <DialogActions>
835
+ <Button
836
+ variant="contained"
837
+ color="primary"
838
+ autoFocus
839
+ onClick={() => {
840
+ const value = JSON.parse(JSON.stringify(this.state.value));
841
+
842
+ (this.state.showTypeOfImportDialog as Record<string, any>[])
843
+ .forEach((obj: Record<string, any>) => value.push(obj));
844
+
845
+ this.setState({
846
+ value,
847
+ iteration: this.state.iteration + 10000,
848
+ showTypeOfImportDialog: false,
849
+ }, () =>
850
+ this.applyFilter(false, null, () =>
851
+ this.onChangeWrapper(value)));
852
+ }}
853
+ >
854
+ {I18n.t('ra_Append')}
855
+ </Button>
856
+ <Button
857
+ variant="contained"
858
+ color="secondary"
859
+ autoFocus
860
+ onClick={() => {
861
+ const value: Record<string, any>[] = this.state.showTypeOfImportDialog as Record<string, any>[];
862
+ this.setState({
863
+ value,
864
+ iteration: this.state.iteration + 10000,
865
+ showTypeOfImportDialog: false,
866
+ }, () =>
867
+ this.applyFilter(false, null, () =>
868
+ this.onChangeWrapper(value)));
869
+ }}
870
+ >
871
+ {I18n.t('ra_Replace')}
872
+ </Button>
873
+ </DialogActions>
874
+ </Dialog>;
875
+ }
876
+
877
+ showImportDialog() {
878
+ if (!this.state.showImportDialog) {
879
+ return null;
880
+ }
881
+ return <Dialog
882
+ open={!0}
883
+ onClose={() => this.setState({ showImportDialog: false })}
884
+ maxWidth="md"
885
+ fullWidth
886
+ >
887
+ <DialogTitle>{I18n.t('ra_Import from %s', 'CSV')}</DialogTitle>
888
+ <DialogContent>
889
+ <Dropzone
890
+ multiple={false}
891
+ accept={{ 'text/csv': ['.csv'] }}
892
+ maxSize={MAX_SIZE}
893
+ onDragEnter={() => this.setState({ uploadFile: 'dragging' })}
894
+ onDragLeave={() => this.setState({ uploadFile: true })}
895
+ onDrop={(acceptedFiles, errors) => {
896
+ this.setState({ uploadFile: false });
897
+ if (!acceptedFiles.length) {
898
+ window.alert((errors && errors[0] && errors[0].errors && errors[0].errors[0] && errors[0].errors[0].message) || I18n.t('ra_Cannot upload'));
899
+ } else {
900
+ this.onDrop(acceptedFiles);
901
+ }
902
+ }}
903
+ >
904
+ {({ getRootProps, getInputProps }) => <div
905
+ className={Utils.clsx(
906
+ this.props.classes.uploadDiv,
907
+ this.state.uploadFile === 'dragging' && this.props.classes.uploadDivDragging,
908
+ this.props.classes.dropZone,
909
+ !this.state.icon && this.props.classes.dropZoneEmpty,
910
+ )}
911
+ {...getRootProps()}
912
+ >
913
+ <input {...getInputProps()} />
914
+ <div className={Utils.clsx(this.props.classes.uploadCenterDiv)}>
915
+ <div className={this.props.classes.uploadCenterTextAndIcon}>
916
+ <ImportIcon className={this.props.classes.uploadCenterIcon} />
917
+ <div className={this.props.classes.uploadCenterText}>
918
+ {
919
+ this.state.uploadFile === 'dragging' ? I18n.t('ra_Drop file here') :
920
+ I18n.t('ra_Place your files here or click here to open the browse dialog')
921
+ }
922
+ </div>
923
+ </div>
924
+ </div>
925
+ </div>}
926
+ </Dropzone>
927
+ </DialogContent>
928
+ <DialogActions>
929
+ <Button
930
+ variant="contained"
931
+ onClick={() => this.setState({ showImportDialog: false })}
932
+ color="primary"
933
+ startIcon={<IconClose />}
934
+ >
935
+ {I18n.t('Cancel')}
936
+ </Button>
937
+ </DialogActions>
938
+ </Dialog>;
939
+ }
940
+
941
+ renderItem(/* error, disabled, defaultValue */) {
942
+ const { classes, schema } = this.props;
943
+ let { visibleValue } = this.state;
944
+
945
+ if (!this.state.value) {
946
+ return null;
947
+ }
948
+
949
+ visibleValue = visibleValue || this.state.value.map((_, i) => i);
950
+
951
+ const doAnyFilterSet = this.isAnyFilterSet();
952
+
953
+ return <Paper className={classes.paper}>
954
+ {this.showImportDialog()}
955
+ {this.showTypeOfImportDialog()}
956
+ {schema.label ? <div className={classes.label}>
957
+ <Toolbar
958
+ variant="dense"
959
+ className={classes.rootTool}
960
+ >
961
+ <Typography className={classes.title} variant="h6" id="tableTitle" component="div">
962
+ {this.getText(schema.label)}
963
+ </Typography>
964
+ </Toolbar>
965
+ </div> : null}
966
+ <TableContainer>
967
+ <Table className={classes.table} size="small">
968
+ {this.enhancedTableHead(!doAnyFilterSet && !this.state.orderBy ? 120 : 64, doAnyFilterSet)}
969
+ <TableBody>
970
+ {visibleValue.map((idx, i) =>
971
+ <TableRow
972
+ hover
973
+ key={`${idx}_${i}`}
974
+ >
975
+ {schema.items && schema.items.map((headCell: ConfigItemTableIndexed) =>
976
+ <TableCell key={`${headCell.attr}_${idx}`} align="left">
977
+ {this.itemTable(headCell.attr, this.state.value[idx], idx)}
978
+ </TableCell>)}
979
+ {!schema.noDelete && <TableCell align="left" className={classes.buttonCell}>
980
+ {!doAnyFilterSet && !this.state.orderBy ? (i ? <Tooltip title={I18n.t('ra_Move up')}>
981
+ <IconButton size="small" onClick={() => this.onMoveUp(idx)}>
982
+ <UpIcon />
983
+ </IconButton>
984
+ </Tooltip> : <div className={classes.buttonEmpty} />) : null}
985
+ {!doAnyFilterSet && !this.state.orderBy ? (i < visibleValue.length - 1 ? <Tooltip title={I18n.t('ra_Move down')}>
986
+ <IconButton size="small" onClick={() => this.onMoveDown(idx)}>
987
+ <DownIcon />
988
+ </IconButton>
989
+ </Tooltip> : <div className={classes.buttonEmpty} />) : null}
990
+ <Tooltip title={I18n.t('ra_Delete current row')}>
991
+ <IconButton size="small" onClick={this.onDelete(idx)}>
992
+ <DeleteIcon />
993
+ </IconButton>
994
+ </Tooltip>
995
+ {this.props.schema.clone ? <Tooltip title={I18n.t('ra_Clone current row')}>
996
+ <IconButton size="small" onClick={this.onClone(idx)}>
997
+ <CopyContentIcon />
998
+ </IconButton>
999
+ </Tooltip> : null}
1000
+ </TableCell>}
1001
+ </TableRow>)}
1002
+ {!schema.noDelete && visibleValue.length >= (schema.showSecondAddAt || 5) ?
1003
+ <TableRow>
1004
+ <TableCell colSpan={schema.items.length + 1}>
1005
+ <Tooltip title={doAnyFilterSet ? I18n.t('ra_Cannot add items with set filter') : I18n.t('ra_Add row')}>
1006
+ <span>
1007
+ <IconButton size="small" color="primary" disabled={!!doAnyFilterSet && !this.props.schema.allowAddByFilter} onClick={this.onAdd}>
1008
+ <AddIcon />
1009
+ </IconButton>
1010
+ </span>
1011
+ </Tooltip>
1012
+ </TableCell>
1013
+ </TableRow> : null}
1014
+ </TableBody>
1015
+ </Table>
1016
+ {!visibleValue.length && this.state.value.length ?
1017
+ <div className={classes.filteredOut}>
1018
+ <Typography className={classes.title} variant="h6" id="tableTitle" component="div">
1019
+ {I18n.t('ra_All items are filtered out')}
1020
+ <IconButton
1021
+ size="small"
1022
+ onClick={() => this.applyFilter(true)}
1023
+ >
1024
+ <CloseIcon />
1025
+ </IconButton>
1026
+ </Typography>
1027
+ </div> : null}
1028
+ </TableContainer>
1029
+ {schema.help ?
1030
+ <FormHelperText>{this.renderHelp(this.props.schema.help, this.props.schema.helpLink, this.props.schema.noTranslation)}</FormHelperText>
1031
+ : null}
1032
+ {this.state.errorMessage ? <div style={{ display: 'flex', padding: '5px' }}>
1033
+ <ErrorIcon color="error" />
1034
+ <span style={{ color: 'red', alignSelf: 'center' }}>{this.state.errorMessage}</span>
1035
+ </div> : null}
1036
+ </Paper>;
1037
+ }
1038
+ }
1039
+
1040
+ export default withStyles(styles)(ConfigTable);