@iobroker/dm-gui-components 7.3.2 → 7.4.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.
@@ -1,7 +1,6 @@
1
1
  import React, { Component } from 'react';
2
- import type { Connection, ThemeName, ThemeType, IobTheme } from '@iobroker/adapter-react-v5';
3
- import { type ConfigItemPanel } from '@iobroker/json-config';
4
- import type { ActionBase, ControlBase, ControlState, DeviceInfo, InstanceDetails } from '@iobroker/dm-utils';
2
+ import { type Connection, type ThemeName, type ThemeType, type IobTheme } from '@iobroker/adapter-react-v5';
3
+ import type { ActionBase, ControlBase, ControlState, DeviceInfo, InstanceDetails, JsonFormSchema, ActionButton } from '@iobroker/dm-utils';
5
4
  declare module '@mui/material/Button' {
6
5
  interface ButtonPropsColorOverrides {
7
6
  grey: true;
@@ -20,10 +19,17 @@ export type CommunicationProps = {
20
19
  dateFormat: string;
21
20
  };
22
21
  interface CommunicationForm {
23
- title?: string | null | undefined;
24
- schema?: ConfigItemPanel;
22
+ title?: ioBroker.StringOrTranslated | null | undefined;
23
+ label?: ioBroker.StringOrTranslated | null | undefined;
24
+ noTranslation?: boolean;
25
+ schema: JsonFormSchema;
25
26
  data?: Record<string, any>;
27
+ buttons?: (ActionButton | 'apply' | 'cancel')[];
28
+ }
29
+ interface CommunicationFormInState extends CommunicationForm {
26
30
  handleClose?: (data?: Record<string, any>) => void;
31
+ originalData: string;
32
+ changed: boolean;
27
33
  }
28
34
  interface InputAction extends ActionBase {
29
35
  /** If it is a device action */
@@ -42,7 +48,7 @@ export type CommunicationState = {
42
48
  message: string;
43
49
  handleClose: (confirmation?: boolean) => void;
44
50
  } | null;
45
- form: CommunicationForm | null;
51
+ form: CommunicationFormInState | null;
46
52
  progress: {
47
53
  open: boolean;
48
54
  progress: number;
@@ -63,11 +69,13 @@ interface Message {
63
69
  * Device List Component
64
70
  */
65
71
  declare class Communication<P extends CommunicationProps, S extends CommunicationState> extends Component<P, S> {
72
+ private responseTimeout;
66
73
  instanceHandler: (action: ActionBase) => () => void;
67
74
  deviceHandler: (deviceId: string, action: ActionBase, refresh: () => void) => () => void;
68
75
  controlHandler: (deviceId: string, control: ControlBase, state: ControlState) => () => Promise<ioBroker.State | null>;
69
76
  controlStateHandler: (deviceId: string, control: ControlBase) => () => Promise<ioBroker.State | null>;
70
77
  constructor(props: P);
78
+ componentWillUnmount(): void;
71
79
  loadData(): void;
72
80
  sendActionToInstance: (command: `dm:${string}`, messageToSend: Message, refresh?: () => void) => void;
73
81
  sendControlToInstance: (command: string, messageToSend: {
@@ -80,11 +88,14 @@ declare class Communication<P extends CommunicationProps, S extends Communicatio
80
88
  renderMessageDialog(): React.JSX.Element | null;
81
89
  renderConfirmDialog(): React.JSX.Element | null;
82
90
  renderSnackbar(): React.JSX.Element;
91
+ getOkButton(button?: ActionButton | 'apply' | 'cancel'): React.JSX.Element;
92
+ getCancelButton(button?: ActionButton | 'apply' | 'cancel'): React.JSX.Element;
83
93
  renderFormDialog(): React.JSX.Element | null;
84
94
  renderProgressDialog(): React.JSX.Element | null;
85
95
  renderContent(): React.JSX.Element | React.JSX.Element[] | null;
86
96
  renderSpinner(): React.JSX.Element | null;
87
97
  renderConfirmationDialog(): React.JSX.Element | null;
98
+ onShowInputOk(): void;
88
99
  renderInputDialog(): React.JSX.Element | null;
89
100
  render(): React.JSX.Element;
90
101
  }
@@ -1,12 +1,14 @@
1
1
  import React, { Component } from 'react';
2
2
  import { Backdrop, Box, Button, Checkbox, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, FormControl, FormControlLabel, Grid2, IconButton, Input, InputAdornment, InputLabel, LinearProgress, MenuItem, Select, Slider, Snackbar, TextField, Typography, } from '@mui/material';
3
3
  import { Close, Check } from '@mui/icons-material';
4
+ import { I18n, Icon, } from '@iobroker/adapter-react-v5';
4
5
  import { getTranslation } from './Utils';
5
6
  import JsonConfig from './JsonConfig';
6
7
  /**
7
8
  * Device List Component
8
9
  */
9
10
  class Communication extends Component {
11
+ responseTimeout = null;
10
12
  // eslint-disable-next-line react/no-unused-class-component-methods
11
13
  instanceHandler;
12
14
  // eslint-disable-next-line react/no-unused-class-component-methods
@@ -63,6 +65,12 @@ class Communication extends Component {
63
65
  this.props.registerHandler(() => this.loadData());
64
66
  }
65
67
  }
68
+ componentWillUnmount() {
69
+ if (this.responseTimeout) {
70
+ clearTimeout(this.responseTimeout);
71
+ this.responseTimeout = null;
72
+ }
73
+ }
66
74
  // eslint-disable-next-line class-methods-use-this
67
75
  loadData() {
68
76
  console.error('loadData not implemented');
@@ -70,7 +78,15 @@ class Communication extends Component {
70
78
  sendActionToInstance = (command, messageToSend, refresh) => {
71
79
  const send = async () => {
72
80
  this.setState({ showSpinner: true });
81
+ this.responseTimeout = setTimeout(() => {
82
+ this.setState({ showSpinner: false });
83
+ window.alert(I18n.t('ra_No response from the backend'));
84
+ }, 5000);
73
85
  const response = await this.props.socket.sendTo(this.props.selectedInstance, command, messageToSend);
86
+ if (this.responseTimeout) {
87
+ clearTimeout(this.responseTimeout);
88
+ this.responseTimeout = null;
89
+ }
74
90
  const type = response.type;
75
91
  console.log(`Response: ${response.type}`);
76
92
  switch (type) {
@@ -82,6 +98,7 @@ class Communication extends Component {
82
98
  message: response.message,
83
99
  handleClose: () => this.setState({ message: null }, () => this.sendActionToInstance('dm:actionProgress', { origin: response.origin }, refresh)),
84
100
  },
101
+ showSpinner: false,
85
102
  });
86
103
  }
87
104
  break;
@@ -96,15 +113,28 @@ class Communication extends Component {
96
113
  confirm,
97
114
  }, refresh)),
98
115
  },
116
+ showSpinner: false,
99
117
  });
100
118
  }
101
119
  break;
102
120
  case 'form':
103
121
  console.log('Form received');
104
122
  if (response.form) {
123
+ const data = response.form.data;
124
+ const originalData = {};
125
+ if (data) {
126
+ Object.keys(data).forEach(key => {
127
+ if (data[key] !== undefined) {
128
+ originalData[key] = data[key];
129
+ }
130
+ });
131
+ }
132
+ response.form.data = JSON.parse(JSON.stringify(originalData));
105
133
  this.setState({
106
134
  form: {
107
135
  ...response.form,
136
+ changed: false,
137
+ originalData: JSON.stringify(originalData),
108
138
  handleClose: (data) => this.setState({ form: null }, () => {
109
139
  console.log(`Form ${JSON.stringify(data)}`);
110
140
  this.sendActionToInstance('dm:actionProgress', {
@@ -113,6 +143,7 @@ class Communication extends Component {
113
143
  }, refresh);
114
144
  }),
115
145
  },
146
+ showSpinner: false,
116
147
  });
117
148
  }
118
149
  break;
@@ -120,10 +151,10 @@ class Communication extends Component {
120
151
  if (response.progress) {
121
152
  if (this.state.progress) {
122
153
  const progress = { ...this.state.progress, ...response.progress };
123
- this.setState({ progress });
154
+ this.setState({ progress, showSpinner: false });
124
155
  }
125
156
  else {
126
- this.setState({ progress: response.progress });
157
+ this.setState({ progress: response.progress, showSpinner: false });
127
158
  }
128
159
  }
129
160
  this.sendActionToInstance('dm:actionProgress', { origin: response.origin }, refresh);
@@ -153,9 +184,11 @@ class Communication extends Component {
153
184
  }
154
185
  if (response.result.error) {
155
186
  console.error(`Error: ${response.result.error.message}`);
156
- this.setState({ showToast: response.result.error.message });
187
+ this.setState({ showToast: response.result.error.message, showSpinner: false });
188
+ }
189
+ else {
190
+ this.setState({ showSpinner: false });
157
191
  }
158
- this.setState({ showSpinner: false });
159
192
  break;
160
193
  default:
161
194
  console.log(`Unknown response type: ${type}`);
@@ -216,24 +249,52 @@ class Communication extends Component {
216
249
  renderSnackbar() {
217
250
  return (React.createElement(Snackbar, { open: !!this.state.showToast, autoHideDuration: 6000, onClose: () => this.setState({ showToast: null }), message: this.state.showToast }));
218
251
  }
252
+ getOkButton(button) {
253
+ if (typeof button === 'string') {
254
+ button = undefined;
255
+ }
256
+ return (React.createElement(Button, { key: "apply", disabled: !this.state.form?.changed, variant: button?.variant || 'contained', color: button?.color || 'primary', onClick: () => this.state.form?.handleClose && this.state.form.handleClose(this.state.form?.data), startIcon: button?.icon ? React.createElement(Icon, { src: button?.icon }) : undefined }, getTranslation(button?.label || 'okButtonText', button?.noTranslation)));
257
+ }
258
+ getCancelButton(button) {
259
+ if (typeof button === 'string') {
260
+ button = undefined;
261
+ }
262
+ return (React.createElement(Button, { key: "cancel", variant: button?.variant || 'contained', color: button?.color || 'grey', onClick: () => this.state.form?.handleClose && this.state.form.handleClose(), startIcon: button?.icon ? React.createElement(Icon, { src: button?.icon }) : undefined }, getTranslation(button?.label || 'cancelButtonText', button?.noTranslation)));
263
+ }
219
264
  renderFormDialog() {
220
- if (!this.state.form || !this.state.form.schema || !this.state.form.data) {
265
+ if (!this.state.form || !this.state.form.schema) {
221
266
  return null;
222
267
  }
268
+ let buttons;
269
+ if (this.state.form.buttons) {
270
+ buttons = [];
271
+ this.state.form.buttons.forEach((button) => {
272
+ if (button === 'apply' || button.type === 'apply') {
273
+ buttons.push(this.getOkButton(button));
274
+ }
275
+ else {
276
+ buttons.push(this.getCancelButton(button));
277
+ }
278
+ });
279
+ }
280
+ else {
281
+ buttons = [this.getOkButton(), this.getCancelButton()];
282
+ }
223
283
  return (React.createElement(Dialog, { open: !0, onClose: () => this.state.form?.handleClose && this.state.form.handleClose(), hideBackdrop: true },
224
- this.state.form?.title ? React.createElement(DialogTitle, null, getTranslation(this.state.form?.title)) : null,
284
+ this.state.form?.title ? (React.createElement(DialogTitle, null, getTranslation(this.state.form?.label || this.state.form?.title, this.state.form.noTranslation))) : null,
225
285
  React.createElement(DialogContent, null,
226
- React.createElement(JsonConfig, { instanceId: this.props.selectedInstance, schema: this.state.form.schema, data: this.state.form.data, socket: this.props.socket, onChange: (data) => {
286
+ React.createElement(JsonConfig, { instanceId: this.props.selectedInstance, schema: this.state.form.schema, data: this.state.form.data || {}, socket: this.props.socket, onChange: (data) => {
227
287
  console.log('handleFormChange', { data });
228
- const form = { ...this.state.form };
288
+ const form = {
289
+ ...this.state.form,
290
+ };
229
291
  if (form) {
230
292
  form.data = data;
293
+ form.changed = JSON.stringify(data) !== form.originalData;
231
294
  this.setState({ form });
232
295
  }
233
296
  }, themeName: this.props.themeName, themeType: this.props.themeType, theme: this.props.theme, isFloatComma: this.props.isFloatComma, dateFormat: this.props.dateFormat })),
234
- React.createElement(DialogActions, null,
235
- React.createElement(Button, { variant: "contained", color: "primary", onClick: () => this.state.form?.handleClose && this.state.form.handleClose(this.state.form?.data), autoFocus: true }, getTranslation('okButtonText')),
236
- React.createElement(Button, { variant: "contained", color: "grey", onClick: () => this.state.form?.handleClose && this.state.form.handleClose() }, getTranslation('cancelButtonText')))));
297
+ React.createElement(DialogActions, null, buttons)));
237
298
  }
238
299
  renderProgressDialog() {
239
300
  if (!this.state.progress?.open) {
@@ -247,11 +308,7 @@ class Communication extends Component {
247
308
  return null;
248
309
  }
249
310
  renderSpinner() {
250
- if (!this.state.showSpinner &&
251
- !this.state.progress?.open &&
252
- !this.state.message &&
253
- !this.state.confirm &&
254
- !this.state.form) {
311
+ if (!this.state.showSpinner) {
255
312
  return null;
256
313
  }
257
314
  return (React.createElement(Backdrop, { style: { zIndex: 1000 }, open: !0 },
@@ -282,6 +339,35 @@ class Communication extends Component {
282
339
  }, autoFocus: true, startIcon: React.createElement(Check, null) }, getTranslation('yesButtonText')),
283
340
  React.createElement(Button, { variant: "contained", color: "grey", onClick: () => this.setState({ showConfirmation: null }), startIcon: React.createElement(Close, null) }, getTranslation('cancelButtonText')))));
284
341
  }
342
+ onShowInputOk() {
343
+ if (!this.state.showInput) {
344
+ return;
345
+ }
346
+ const showInput = this.state.showInput;
347
+ this.setState({ showInput: null }, () => {
348
+ if (showInput.deviceId) {
349
+ this.sendActionToInstance('dm:deviceAction', {
350
+ actionId: showInput.id,
351
+ deviceId: showInput.deviceId,
352
+ value: showInput.inputBefore?.type === 'checkbox'
353
+ ? !!this.state.inputValue
354
+ : showInput.inputBefore?.type === 'number'
355
+ ? parseFloat(this.state.inputValue) || 0
356
+ : this.state.inputValue,
357
+ }, showInput.refresh);
358
+ }
359
+ else {
360
+ this.sendActionToInstance('dm:instanceAction', {
361
+ actionId: showInput.id,
362
+ value: showInput.inputBefore?.type === 'checkbox'
363
+ ? !!this.state.inputValue
364
+ : showInput.inputBefore?.type === 'number'
365
+ ? parseFloat(this.state.inputValue) || 0
366
+ : this.state.inputValue,
367
+ });
368
+ }
369
+ });
370
+ }
285
371
  renderInputDialog() {
286
372
  if (!this.state.showInput || !this.state.showInput.inputBefore) {
287
373
  return null;
@@ -317,7 +403,11 @@ class Communication extends Component {
317
403
  React.createElement(IconButton, { size: "small", onClick: () => this.setState({ inputValue: '' }) },
318
404
  React.createElement(Close, null)))) : null,
319
405
  },
320
- }, type: this.state.showInput.inputBefore.type === 'number' ? 'number' : 'text', fullWidth: true, value: this.state.inputValue, onChange: e => this.setState({ inputValue: e.target.value }) })) : null,
406
+ }, type: this.state.showInput.inputBefore.type === 'number' ? 'number' : 'text', fullWidth: true, value: this.state.inputValue, onChange: e => this.setState({ inputValue: e.target.value }), onKeyUp: (e) => {
407
+ if (e.key === 'Enter') {
408
+ this.onShowInputOk();
409
+ }
410
+ } })) : null,
321
411
  this.state.showInput.inputBefore.type === 'checkbox' ? (React.createElement(FormControlLabel, { control: React.createElement(Checkbox, { checked: !!this.state.inputValue, autoFocus: true, onChange: e => this.setState({ inputValue: e.target.checked }) }), label: getTranslation(this.state.showInput.inputBefore.label) })) : null,
322
412
  this.state.showInput.inputBefore.type === 'select' ? (React.createElement(FormControl, { fullWidth: true },
323
413
  React.createElement(InputLabel, null, getTranslation(this.state.showInput.inputBefore.label)),
@@ -326,7 +416,7 @@ class Communication extends Component {
326
416
  React.createElement(Typography, { gutterBottom: true }, getTranslation(this.state.showInput.inputBefore.label)),
327
417
  React.createElement(Grid2, { container: true, spacing: 2, alignItems: "center" },
328
418
  React.createElement(Grid2, null,
329
- React.createElement(Slider, { value: typeof this.state.inputValue === 'number' ? this.state.inputValue : 0, onChange: (event, newValue) => this.setState({ inputValue: newValue }) })),
419
+ React.createElement(Slider, { value: typeof this.state.inputValue === 'number' ? this.state.inputValue : 0, onChange: (_event, newValue) => this.setState({ inputValue: newValue }) })),
330
420
  React.createElement(Grid2, null,
331
421
  React.createElement(Input, { value: this.state.inputValue, size: "small", onChange: e => this.setState({
332
422
  inputValue: e.target.value === '' ? 0 : Number(e.target.value),
@@ -357,48 +447,20 @@ class Communication extends Component {
357
447
  type: 'number',
358
448
  } }))))) : null),
359
449
  React.createElement(DialogActions, null,
360
- React.createElement(Button, { variant: "contained", disabled: okDisabled, color: "primary", onClick: () => {
361
- if (!this.state.showInput) {
362
- return;
363
- }
364
- const showInput = this.state.showInput;
365
- this.setState({ showInput: null }, () => {
366
- if (showInput.deviceId) {
367
- this.sendActionToInstance('dm:deviceAction', {
368
- actionId: showInput.id,
369
- deviceId: showInput.deviceId,
370
- value: showInput.inputBefore?.type === 'checkbox'
371
- ? !!this.state.inputValue
372
- : showInput.inputBefore?.type === 'number'
373
- ? parseFloat(this.state.inputValue) || 0
374
- : this.state.inputValue,
375
- }, showInput.refresh);
376
- }
377
- else {
378
- this.sendActionToInstance('dm:instanceAction', {
379
- actionId: showInput.id,
380
- value: showInput.inputBefore?.type === 'checkbox'
381
- ? !!this.state.inputValue
382
- : showInput.inputBefore?.type === 'number'
383
- ? parseFloat(this.state.inputValue) || 0
384
- : this.state.inputValue,
385
- });
386
- }
387
- });
388
- }, startIcon: React.createElement(Check, null) }, getTranslation('yesButtonText')),
450
+ React.createElement(Button, { variant: "contained", disabled: okDisabled, color: "primary", onClick: () => this.onShowInputOk(), startIcon: React.createElement(Check, null) }, getTranslation('yesButtonText')),
389
451
  React.createElement(Button, { variant: "contained", color: "grey", onClick: () => this.setState({ showInput: null }), startIcon: React.createElement(Close, null) }, getTranslation('cancelButtonText')))));
390
452
  }
391
453
  render() {
392
454
  return (React.createElement(React.Fragment, null,
393
455
  this.renderSnackbar(),
394
456
  this.renderContent(),
395
- this.renderSpinner(),
396
457
  this.renderConfirmDialog(),
397
458
  this.renderMessageDialog(),
398
459
  this.renderFormDialog(),
399
460
  this.renderProgressDialog(),
400
461
  this.renderConfirmationDialog(),
401
- this.renderInputDialog()));
462
+ this.renderInputDialog(),
463
+ this.renderSpinner()));
402
464
  }
403
465
  }
404
466
  export default Communication;