@iobroker/json-config 6.17.12 → 6.17.14

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 (61) hide show
  1. package/package.json +27 -27
  2. package/src/JsonConfig.tsx +0 -710
  3. package/src/JsonConfigComponent/ChipInput.tsx +0 -752
  4. package/src/JsonConfigComponent/ConfigAccordion.tsx +0 -278
  5. package/src/JsonConfigComponent/ConfigAlive.tsx +0 -74
  6. package/src/JsonConfigComponent/ConfigAutocomplete.tsx +0 -108
  7. package/src/JsonConfigComponent/ConfigAutocompleteSendTo.tsx +0 -183
  8. package/src/JsonConfigComponent/ConfigCRON.jsx +0 -101
  9. package/src/JsonConfigComponent/ConfigCertCollection.tsx +0 -102
  10. package/src/JsonConfigComponent/ConfigCertificateSelect.tsx +0 -92
  11. package/src/JsonConfigComponent/ConfigCertificates.tsx +0 -202
  12. package/src/JsonConfigComponent/ConfigCheckLicense.jsx +0 -662
  13. package/src/JsonConfigComponent/ConfigCheckbox.tsx +0 -67
  14. package/src/JsonConfigComponent/ConfigChip.jsx +0 -81
  15. package/src/JsonConfigComponent/ConfigColor.tsx +0 -86
  16. package/src/JsonConfigComponent/ConfigCoordinates.tsx +0 -234
  17. package/src/JsonConfigComponent/ConfigCustom.tsx +0 -246
  18. package/src/JsonConfigComponent/ConfigDatePicker.tsx +0 -48
  19. package/src/JsonConfigComponent/ConfigDeviceManager.tsx +0 -33
  20. package/src/JsonConfigComponent/ConfigFile.jsx +0 -181
  21. package/src/JsonConfigComponent/ConfigFileSelector.jsx +0 -520
  22. package/src/JsonConfigComponent/ConfigFunc.jsx +0 -90
  23. package/src/JsonConfigComponent/ConfigGeneric.tsx +0 -1027
  24. package/src/JsonConfigComponent/ConfigIP.jsx +0 -96
  25. package/src/JsonConfigComponent/ConfigImageSendTo.jsx +0 -79
  26. package/src/JsonConfigComponent/ConfigImageUpload.jsx +0 -114
  27. package/src/JsonConfigComponent/ConfigInstanceSelect.jsx +0 -172
  28. package/src/JsonConfigComponent/ConfigInterface.jsx +0 -112
  29. package/src/JsonConfigComponent/ConfigJsonEditor.jsx +0 -103
  30. package/src/JsonConfigComponent/ConfigLanguage.tsx +0 -153
  31. package/src/JsonConfigComponent/ConfigLicense.jsx +0 -148
  32. package/src/JsonConfigComponent/ConfigNumber.tsx +0 -207
  33. package/src/JsonConfigComponent/ConfigObjectId.jsx +0 -113
  34. package/src/JsonConfigComponent/ConfigPanel.tsx +0 -360
  35. package/src/JsonConfigComponent/ConfigPassword.jsx +0 -160
  36. package/src/JsonConfigComponent/ConfigPattern.jsx +0 -50
  37. package/src/JsonConfigComponent/ConfigPort.tsx +0 -232
  38. package/src/JsonConfigComponent/ConfigRoom.jsx +0 -90
  39. package/src/JsonConfigComponent/ConfigSelect.jsx +0 -124
  40. package/src/JsonConfigComponent/ConfigSelectSendTo.tsx +0 -251
  41. package/src/JsonConfigComponent/ConfigSendto.tsx +0 -340
  42. package/src/JsonConfigComponent/ConfigSetState.jsx +0 -116
  43. package/src/JsonConfigComponent/ConfigSlider.jsx +0 -97
  44. package/src/JsonConfigComponent/ConfigStaticDivider.jsx +0 -51
  45. package/src/JsonConfigComponent/ConfigStaticHeader.jsx +0 -63
  46. package/src/JsonConfigComponent/ConfigStaticImage.jsx +0 -48
  47. package/src/JsonConfigComponent/ConfigStaticText.jsx +0 -72
  48. package/src/JsonConfigComponent/ConfigTable.tsx +0 -1040
  49. package/src/JsonConfigComponent/ConfigTabs.tsx +0 -150
  50. package/src/JsonConfigComponent/ConfigText.tsx +0 -188
  51. package/src/JsonConfigComponent/ConfigTextSendTo.tsx +0 -102
  52. package/src/JsonConfigComponent/ConfigTimePicker.tsx +0 -63
  53. package/src/JsonConfigComponent/ConfigTopic.jsx +0 -78
  54. package/src/JsonConfigComponent/ConfigUUID.tsx +0 -55
  55. package/src/JsonConfigComponent/ConfigUser.jsx +0 -104
  56. package/src/JsonConfigComponent/index.tsx +0 -435
  57. package/src/JsonConfigComponent/wrapper/Components/CustomModal.jsx +0 -145
  58. package/src/JsonConfigComponent/wrapper/Components/Editor.jsx +0 -65
  59. package/src/Utils.jsx +0 -1683
  60. package/src/index.tsx +0 -14
  61. package/src/types.d.ts +0 -372
@@ -1,1040 +0,0 @@
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);