@iobroker/dm-gui-components 6.17.13 → 7.0.1

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 (65) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +59 -59
  3. package/build/Communication.d.ts +18 -11
  4. package/build/Communication.js +102 -19
  5. package/build/Communication.js.map +1 -1
  6. package/build/DeviceActionButton.d.ts +3 -3
  7. package/build/DeviceActionButton.js +8 -4
  8. package/build/DeviceActionButton.js.map +1 -1
  9. package/build/DeviceCard.d.ts +7 -8
  10. package/build/DeviceCard.js +8 -7
  11. package/build/DeviceCard.js.map +1 -1
  12. package/build/DeviceControl.d.ts +16 -15
  13. package/build/DeviceControl.js +15 -16
  14. package/build/DeviceControl.js.map +1 -1
  15. package/build/DeviceList.d.ts +7 -10
  16. package/build/DeviceList.js +0 -9
  17. package/build/DeviceList.js.map +1 -1
  18. package/build/DeviceStatus.d.ts +2 -1
  19. package/build/DeviceStatus.js +0 -4
  20. package/build/DeviceStatus.js.map +1 -1
  21. package/build/InstanceActionButton.d.ts +3 -2
  22. package/build/InstanceActionButton.js +2 -2
  23. package/build/InstanceActionButton.js.map +1 -1
  24. package/build/JsonConfig.d.ts +4 -4
  25. package/build/JsonConfig.js +3 -3
  26. package/build/JsonConfig.js.map +1 -1
  27. package/build/Utils.d.ts +6 -4
  28. package/build/Utils.js +112 -94
  29. package/build/Utils.js.map +1 -1
  30. package/build/i18n/de.json +2 -1
  31. package/build/i18n/en.json +2 -1
  32. package/build/i18n/es.json +2 -1
  33. package/build/i18n/fr.json +2 -1
  34. package/build/i18n/it.json +2 -1
  35. package/build/i18n/nl.json +2 -1
  36. package/build/i18n/pl.json +2 -1
  37. package/build/i18n/pt.json +2 -1
  38. package/build/i18n/ru.json +2 -1
  39. package/build/i18n/uk.json +2 -1
  40. package/build/i18n/zh-cn.json +2 -1
  41. package/package.json +53 -51
  42. package/src/Communication.tsx +0 -443
  43. package/src/DeviceActionButton.tsx +0 -31
  44. package/src/DeviceCard.tsx +0 -511
  45. package/src/DeviceControl.tsx +0 -196
  46. package/src/DeviceImageUpload.tsx +0 -92
  47. package/src/DeviceList.tsx +0 -344
  48. package/src/DeviceStatus.tsx +0 -156
  49. package/src/InstanceActionButton.tsx +0 -25
  50. package/src/JsonConfig.tsx +0 -99
  51. package/src/TooltipButton.tsx +0 -34
  52. package/src/Utils.tsx +0 -187
  53. package/src/i18n/de.json +0 -21
  54. package/src/i18n/en.json +0 -21
  55. package/src/i18n/es.json +0 -21
  56. package/src/i18n/fr.json +0 -21
  57. package/src/i18n/i18n.d.ts +0 -26
  58. package/src/i18n/it.json +0 -21
  59. package/src/i18n/nl.json +0 -21
  60. package/src/i18n/pl.json +0 -21
  61. package/src/i18n/pt.json +0 -21
  62. package/src/i18n/ru.json +0 -21
  63. package/src/i18n/uk.json +0 -21
  64. package/src/i18n/zh-cn.json +0 -21
  65. package/src/index.ts +0 -3
@@ -1,511 +0,0 @@
1
- import React, { Component } from 'react';
2
-
3
- import {
4
- Button, Typography,
5
- Dialog, DialogActions, DialogContent, IconButton,
6
- Fab, DialogTitle, Card, CardActions, CardHeader,
7
- CardContent, Paper,
8
- } from '@mui/material';
9
-
10
- import {
11
- MoreVert as MoreVertIcon,
12
- VideogameAsset as ControlIcon,
13
- Close as CloseIcon,
14
- } from '@mui/icons-material';
15
-
16
- import { Utils, Icon, AdminConnection, I18n } from '@iobroker/adapter-react-v5';
17
- import type { DeviceDetails, DeviceInfo } from '@iobroker/dm-utils';
18
- import type { ActionBase, ControlBase, ControlState } from '@iobroker/dm-utils/build/types/base';
19
- import type { ThemeName, ThemeType } from '@iobroker/adapter-react-v5/types';
20
-
21
- import DeviceActionButton from './DeviceActionButton';
22
- import DeviceControlComponent from './DeviceControl';
23
- import DeviceStatus from './DeviceStatus';
24
- import JsonConfig from './JsonConfig';
25
- import DeviceImageUpload from './DeviceImageUpload';
26
- import { getTranslation } from './Utils';
27
-
28
- const NoImageIcon = (props: { style?: React.CSSProperties, className?: string }) => <svg viewBox="0 0 24 24" width="24" height="24" style={props.style} className={props.className}>
29
- <path
30
- fill="currentColor"
31
- d="M21.9,21.9l-8.49-8.49l0,0L3.59,3.59l0,0L2.1,2.1L0.69,3.51L3,5.83V19c0,1.1,0.9,2,2,2h13.17l2.31,2.31L21.9,21.9z M5,18 l3.5-4.5l2.5,3.01L12.17,15l3,3H5z M21,18.17L5.83,3H19c1.1,0,2,0.9,2,2V18.17z"
32
- />
33
- </svg>;
34
-
35
- interface DeviceCardProps {
36
- title?: string;
37
- /* Device ID */
38
- id: string;
39
- device: DeviceInfo;
40
- instanceId: string;
41
- socket: AdminConnection;
42
- /* Instance, where the images should be uploaded to */
43
- uploadImagesToInstance?: string;
44
- deviceHandler: (deviceId: string, action: ActionBase<'api'>, refresh: () => void) => () => void;
45
- controlHandler: (deviceId: string, control: ControlBase, state: ControlState) => () => Promise<ioBroker.State | null>;
46
- controlStateHandler: (deviceId: string, control: ControlBase) => () => Promise<ioBroker.State | null>;
47
- smallCards?: boolean;
48
- alive: boolean;
49
- themeName: ThemeName;
50
- themeType: ThemeType;
51
- isFloatComma: boolean;
52
- dateFormat: string;
53
- }
54
-
55
- function getText(text: ioBroker.StringOrTranslated | undefined): string | undefined {
56
- if (typeof text === 'object') {
57
- return text[I18n.getLanguage()] || text.en;
58
- }
59
-
60
- return text;
61
- }
62
-
63
- interface DeviceCardState {
64
- open: boolean;
65
- details: DeviceDetails | null;
66
- data: Record<string, any>;
67
- icon: string | undefined;
68
- showControlDialog: boolean;
69
- }
70
-
71
- /**
72
- * Device Card Component
73
- */
74
- class DeviceCard extends Component<DeviceCardProps, DeviceCardState> {
75
- constructor(props: DeviceCardProps) {
76
- super(props);
77
-
78
- this.state = {
79
- open: false,
80
- details: null,
81
- data: {},
82
- icon: props.device.icon,
83
- showControlDialog: false,
84
- };
85
- }
86
-
87
- async fetchIcon() {
88
- if (!this.props.device.icon) {
89
- // try to load the icon from file storage
90
- const fileName = `${this.props.device.manufacturer ? `${this.props.device.manufacturer}_` : ''}${
91
- this.props.device.model ? this.props.device.model : this.props.device.id
92
- }`;
93
-
94
- try {
95
- const file = await this.props.socket.readFile(this.props.instanceId.replace('system.adapter.', ''), `${fileName}.webp`, true);
96
- this.setState({ icon: `data:image/${file.mimeType},${file}` });
97
- // const response = await fetch(url);
98
- // if (response.ok) {
99
- // const blob = await response.blob();
100
- // const reader = new FileReader();
101
- // reader.onloadend = () => {
102
- // setIcon(reader.result);
103
- // };
104
- // reader.readAsDataURL(blob);
105
- // } else {
106
- // throw new Error('Response not ok');
107
- // }
108
- } catch (error) {
109
- this.state.icon && this.setState({ icon: '' });
110
- }
111
- }
112
- }
113
-
114
- componentDidMount() {
115
- this.fetchIcon()
116
- .catch(e => console.error(e));
117
- }
118
-
119
- /**
120
- * Load the device details
121
- */
122
- async loadDetails() {
123
- console.log(`Loading device details for ${this.props.device.id}... from ${this.props.instanceId}`);
124
- const details: DeviceDetails | null = await this.props.socket.sendTo(this.props.instanceId, 'dm:deviceDetails', this.props.device.id);
125
- console.log(`Got device details for ${this.props.device.id}:`, details);
126
- this.setState({ details, data: details?.data || {} });
127
- };
128
-
129
- /**
130
- * Refresh the device details
131
- */
132
- refresh = () => {
133
- this.setState({ details: null });
134
- this.loadDetails().catch(console.error);
135
- };
136
-
137
- /**
138
- * Copy the device ID to the clipboard
139
- * @returns {void}
140
- */
141
- copyToClipboard = async () => {
142
- const textToCopy = this.props.device.id;
143
- Utils.copyToClipboard(textToCopy);
144
- alert(`${getTranslation('copied')} ${textToCopy} ${getTranslation('toClipboard')}!`);
145
- };
146
-
147
- renderDialog() {
148
- if (!this.state.open || !this.state.details) {
149
- return null;
150
- }
151
-
152
- return <Dialog
153
- open={!0}
154
- maxWidth="md"
155
- onClose={() => this.setState({ open: false })}
156
- >
157
- <DialogContent>
158
- <JsonConfig
159
- instanceId={this.props.instanceId}
160
- socket={this.props.socket}
161
- schema={this.state.details.schema}
162
- data={this.state.data}
163
- onChange={(data: Record<string, any>) => this.setState({ data })}
164
- themeName={this.props.themeName}
165
- themeType={this.props.themeType}
166
- isFloatComma={this.props.isFloatComma}
167
- dateFormat={this.props.dateFormat}
168
- />
169
- </DialogContent>
170
- <DialogActions>
171
- <Button
172
- disabled={!this.props.alive}
173
- variant="contained"
174
- color="primary"
175
- onClick={() => this.setState({ open: false })}
176
- autoFocus
177
- >
178
- {getTranslation('closeButtonText')}
179
- </Button>
180
- </DialogActions>
181
- </Dialog>;
182
- }
183
-
184
- renderControlDialog() {
185
- if (!this.state.showControlDialog || !this.props.alive) {
186
- return null;
187
- }
188
- const colors = { primary: '#111', secondary: '#888' };
189
- return <Dialog
190
- open={!0}
191
- onClose={() => this.setState({ showControlDialog: false })}
192
- >
193
- <DialogTitle>
194
- {this.props.title}
195
- <IconButton
196
- style={{
197
- position: 'absolute',
198
- top: 5,
199
- right: 5,
200
- zIndex: 10,
201
- }}
202
- onClick={() => this.setState({ showControlDialog: false })}
203
- >
204
- <CloseIcon />
205
- </IconButton>
206
- </DialogTitle>
207
- <DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
208
- {this.props.device.controls?.map(control =>
209
- <DeviceControlComponent
210
- disabled={false}
211
- key={control.id}
212
- control={control}
213
- socket={this.props.socket}
214
- colors={colors}
215
- deviceId={this.props.device.id}
216
- controlHandler={this.props.controlHandler}
217
- controlStateHandler={this.props.controlStateHandler}
218
- />)}
219
- </DialogContent>
220
- </Dialog>;
221
- }
222
-
223
- renderControls() {
224
- const colors = { primary: '#111', secondary: '#888' };
225
- const firstControl = this.props.device.controls?.[0];
226
- if (this.props.device.controls?.length === 1 && firstControl && ((firstControl.type === 'icon' || firstControl.type === 'switch') && !firstControl.label)) {
227
- // control can be placed in button icon
228
- return <DeviceControlComponent
229
- disabled={!this.props.alive}
230
- control={firstControl}
231
- colors={colors}
232
- socket={this.props.socket}
233
- deviceId={this.props.device.id}
234
- controlHandler={this.props.controlHandler}
235
- controlStateHandler={this.props.controlStateHandler}
236
- />;
237
- }
238
-
239
- if (this.props.device.controls?.length) {
240
- // place button and show controls dialog
241
- return <Fab
242
- size="small"
243
- disabled={!this.props.alive}
244
- onClick={() => this.setState({ showControlDialog: true })}
245
- >
246
- <ControlIcon />
247
- </Fab>;
248
- }
249
- return null;
250
- }
251
-
252
- renderActions() {
253
- return this.props.device.actions?.length ? this.props.device.actions.map(a => <DeviceActionButton
254
- disabled={!this.props.alive}
255
- key={a.id}
256
- deviceId={this.props.device.id}
257
- action={a}
258
- deviceHandler={this.props.deviceHandler}
259
- refresh={this.refresh}
260
- />) : null;
261
- }
262
-
263
- renderSmall() {
264
- const hasDetails = this.props.device.hasDetails;
265
- const status = !this.props.device.status ? [] : Array.isArray(this.props.device.status) ? this.props.device.status : [this.props.device.status];
266
-
267
- return <Card
268
- sx={{
269
- maxWidth: 345,
270
- minWidth: 200,
271
- }}
272
- >
273
- <CardHeader
274
- sx={theme => ({
275
- backgroundColor: this.props.device.color || theme.palette.secondary.main,
276
- color: this.props.device.color ? Utils.invertColor(this.props.device.color, true) : theme.palette.secondary.contrastText,
277
- maxWidth : 345,
278
- })}
279
- avatar={<div>
280
- {this.props.uploadImagesToInstance ? <DeviceImageUpload
281
- uploadImagesToInstance={this.props.uploadImagesToInstance}
282
- deviceId={this.props.device.id}
283
- manufacturer={getText(this.props.device.manufacturer)}
284
- model={getText(this.props.device.model)}
285
- onImageSelect={async (imageData: string) => imageData && this.setState({ icon: imageData })}
286
- socket={this.props.socket}
287
- /> : null}
288
- {this.state.icon ? <Icon src={this.state.icon} /> : <NoImageIcon />}
289
- </div>}
290
- action={
291
- hasDetails ? <IconButton
292
- aria-label="settings"
293
- onClick={() => {
294
- if (!this.state.open) {
295
- this.loadDetails().catch(console.error);
296
- this.setState({ open: true });
297
- }
298
- }}
299
- >
300
- <MoreVertIcon />
301
- </IconButton> : null
302
- }
303
- title={this.props.title}
304
- subheader={this.props.device.manufacturer ? <span>
305
- <b style={{ marginRight: 4 }}>
306
- {getTranslation('manufacturer')}
307
- :
308
- </b>
309
- {getText(this.props.device.manufacturer)}
310
- </span> : null}
311
- />
312
- <CardContent style={{ position: 'relative' }}>
313
- {status?.length ? <div
314
- style={{
315
- display: 'flex',
316
- position: 'absolute',
317
- top: -11,
318
- background: '#88888880',
319
- padding: '0 8px',
320
- borderRadius: 5,
321
- width: 'calc(100% - 46px)',
322
- }}
323
- >
324
- {status.map((s, i) => <DeviceStatus key={i} status={s} />)}
325
- </div> : null}
326
- <div>
327
- <Typography variant="body1">
328
- <div onClick={this.copyToClipboard}>
329
- <b>ID:</b>
330
- <span style={{ marginLeft: 4 }}>{this.props.device.id.replace(/.*\.\d\./, '')}</span>
331
- </div>
332
- {this.props.device.manufacturer ? <div>
333
- <b style={{ marginRight: 4 }}>
334
- {getTranslation('manufacturer')}
335
- :
336
- </b>
337
- {getText(this.props.device.manufacturer)}
338
- </div> : null}
339
- {this.props.device.model ? <div>
340
- <b style={{ marginRight: 4 }}>
341
- {getTranslation('model')}
342
- :
343
- </b>
344
- {getText(this.props.device.model)}
345
- </div> : null}
346
- </Typography>
347
- </div>
348
- </CardContent>
349
- <CardActions disableSpacing>
350
- {this.renderActions()}
351
- <div style={{ flexGrow: 1 }} />
352
- {this.renderControls()}
353
- </CardActions>
354
- {this.renderDialog()}
355
- {this.renderControlDialog()}
356
- </Card>;
357
- }
358
-
359
- renderBig() {
360
- const cardStyle: React.CSSProperties = {
361
- // backgroundColor: '#fafafa',
362
- width: 300,
363
- minHeight: 280,
364
- margin: 10,
365
- overflow: 'hidden',
366
- display: 'inline-block',
367
- };
368
- /** @type {CSSProperties} */
369
- const headerStyle: React.CSSProperties = {
370
- display: 'flex',
371
- position: 'relative',
372
- justifyContent: 'space-between',
373
- minHeight: 60,
374
- color: '#000',
375
- padding: '0 10px 0 10px',
376
- backgroundColor: '#77c7ff8c',
377
- borderRadius: '4px 4px 0 0',
378
- };
379
- /** @type {CSSProperties} */
380
- const imgAreaStyle: React.CSSProperties = {
381
- height: 45,
382
- width: 45,
383
- margin: 'auto',
384
- justifyContent: 'center',
385
- display: 'grid',
386
- };
387
- /** @type {CSSProperties} */
388
- const imgStyle: React.CSSProperties = {
389
- zIndex: 2,
390
- maxWidth: '100%',
391
- maxHeight: '100%',
392
- };
393
- /** @type {CSSProperties} */
394
- const titleStyle: React.CSSProperties = {
395
- color: '#333',
396
- width: '100%',
397
- fontSize: 16,
398
- fontWeight: 'bold',
399
- paddingTop: 16,
400
- paddingLeft: 8,
401
- whiteSpace: 'nowrap',
402
- overflow: 'hidden',
403
- textOverflow: 'ellipsis',
404
- };
405
- /** @type {CSSProperties} */
406
- const detailsButtonStyle: React.CSSProperties = {
407
- right: 20,
408
- bottom: -20,
409
- position: 'absolute',
410
- };
411
- /** @type {CSSProperties} */
412
- const bodyStyle: React.CSSProperties = {
413
- height: 'calc(100% - 116px)',
414
- };
415
- /** @type {CSSProperties} */
416
- const deviceInfoStyle: React.CSSProperties = {
417
- padding: '20px 16px 0 16px',
418
- height: 133,
419
- };
420
- /** @type {CSSProperties} */
421
- const statusStyle: React.CSSProperties = {
422
- padding: '15px 15px 0 15px',
423
- height: 41,
424
- };
425
- const status = !this.props.device.status ? [] : Array.isArray(this.props.device.status) ? this.props.device.status : [this.props.device.status];
426
-
427
- return <Paper style={cardStyle} key={this.props.id}>
428
- <div style={headerStyle}>
429
- <div style={imgAreaStyle}>
430
- {this.props.uploadImagesToInstance ? <DeviceImageUpload
431
- uploadImagesToInstance={this.props.uploadImagesToInstance}
432
- deviceId={this.props.device.id}
433
- manufacturer={getText(this.props.device.manufacturer)}
434
- model={getText(this.props.device.model)}
435
- onImageSelect={async (imageData: string) => imageData && this.setState({ icon: imageData })}
436
- socket={this.props.socket}
437
- /> : null}
438
- <Icon src={this.state.icon} style={imgStyle} />
439
- </div>
440
- <div style={titleStyle}>{this.props.title}</div>
441
- {this.props.device.hasDetails ? <Fab
442
- disabled={!this.props.alive}
443
- size="small"
444
- style={detailsButtonStyle}
445
- onClick={() => {
446
- if (!this.state.open) {
447
- this.loadDetails().catch(console.error);
448
- this.setState({ open: true });
449
- }
450
- }}
451
- color="primary"
452
- >
453
- <MoreVertIcon />
454
- </Fab> : null}
455
- </div>
456
- <div style={statusStyle}>
457
- {status.map((s, i) => <DeviceStatus key={i} status={s} />)}
458
- </div>
459
- <div style={bodyStyle}>
460
- <Typography variant="body1" style={deviceInfoStyle}>
461
- <div onClick={this.copyToClipboard}>
462
- <b style={{ marginRight: 4 }}>ID:</b>
463
- {this.props.device.id.replace(/.*\.\d\./, '')}
464
- </div>
465
- {this.props.device.manufacturer ? <div>
466
- <b style={{ marginRight: 4 }}>
467
- {getTranslation('manufacturer')}
468
- :
469
- </b>
470
- {getText(this.props.device.manufacturer)}
471
- </div> : null}
472
- {this.props.device.model ? <div>
473
- <b style={{ marginRight: 4 }}>
474
- {getTranslation('model')}
475
- :
476
- </b>
477
- {getText(this.props.device.model)}
478
- </div> : null}
479
- </Typography>
480
- {!!this.props.device.actions?.length && <div
481
- style={{
482
- flex: 1,
483
- position: 'relative',
484
- display: 'flex',
485
- gap: 8,
486
- paddingBottom: 5,
487
- height: 34,
488
- paddingLeft: 10,
489
- paddingRight: 10,
490
- }}
491
- >
492
- {this.renderActions()}
493
- <div style={{ flexGrow: 1 }} />
494
- {this.renderControls()}
495
- </div>}
496
- </div>
497
- {this.renderDialog()}
498
- {this.renderControlDialog()}
499
- </Paper>;
500
- }
501
-
502
- render() {
503
- if (this.props.smallCards) {
504
- return this.renderSmall();
505
- }
506
-
507
- return this.renderBig();
508
- }
509
- }
510
-
511
- export default DeviceCard;