@iobroker/dm-gui-components 0.0.3 → 0.0.5

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.
@@ -4,6 +4,7 @@ import { ActionBase, ControlBase, ControlState } from '@iobroker/dm-utils/build/
4
4
  export type CommunicationProps = {
5
5
  socket: Connection;
6
6
  selectedInstance: string;
7
+ registerHandler?: (handler: null | ((command: string) => void)) => void;
7
8
  };
8
9
  interface CommunicationForm {
9
10
  title?: string | null | undefined;
@@ -35,13 +36,13 @@ export type CommunicationState = {
35
36
  * @param {string} params.selectedInstance - Selected instance
36
37
  * @returns {*[]} - Array of device cards
37
38
  */
38
- declare abstract class Communication<P extends CommunicationProps, S extends CommunicationState> extends Component<P, S> {
39
+ declare class Communication<P extends CommunicationProps, S extends CommunicationState> extends Component<P, S> {
39
40
  instanceHandler: (action: ActionBase<'api'>) => () => void;
40
41
  deviceHandler: (deviceId: string, action: ActionBase<'api'>, refresh: () => void) => () => void;
41
42
  controlHandler: (deviceId: string, control: ControlBase, state: ControlState) => () => Promise<ioBroker.State | null>;
42
43
  controlStateHandler: (deviceId: string, control: ControlBase) => () => Promise<ioBroker.State | null>;
43
44
  constructor(props: P);
44
- abstract loadData(): Promise<void>;
45
+ loadData(): Promise<void>;
45
46
  sendActionToInstance: (command: string, messageToSend: any, refresh?: () => void) => void;
46
47
  sendControlToInstance: (command: string, messageToSend: {
47
48
  deviceId: string;
package/Communication.js CHANGED
@@ -162,14 +162,25 @@ class Communication extends react_1.Component {
162
162
  form: null,
163
163
  progress: null,
164
164
  };
165
+ // eslint-disable-next-line react/no-unused-class-component-methods
165
166
  this.instanceHandler = action => () => this.sendActionToInstance('dm:instanceAction', { actionId: action.id });
167
+ // eslint-disable-next-line react/no-unused-class-component-methods
166
168
  this.deviceHandler = (deviceId, action, refresh) => () => this.sendActionToInstance('dm:deviceAction', { deviceId, actionId: action.id }, refresh);
169
+ // eslint-disable-next-line react/no-unused-class-component-methods
167
170
  this.controlHandler = (deviceId, control, state) => () => this.sendControlToInstance('dm:deviceControl', { deviceId, controlId: control.id, state });
171
+ // eslint-disable-next-line react/no-unused-class-component-methods
168
172
  this.controlStateHandler = (deviceId, control) => () => this.sendControlToInstance('dm:deviceControlState', { deviceId, controlId: control.id });
173
+ this.props.registerHandler && this.props.registerHandler(() => this.loadData());
169
174
  }
175
+ // eslint-disable-next-line class-methods-use-this
176
+ loadData() {
177
+ return Promise.resolve();
178
+ }
179
+ // eslint-disable-next-line react/no-unused-class-component-methods
170
180
  loadDevices() {
171
181
  return this.props.socket.sendTo(this.props.selectedInstance, 'dm:listDevices');
172
182
  }
183
+ // eslint-disable-next-line react/no-unused-class-component-methods
173
184
  loadInstanceInfos() {
174
185
  return this.props.socket.sendTo(this.props.selectedInstance, 'dm:instanceInfo');
175
186
  }
@@ -189,7 +200,6 @@ class Communication extends react_1.Component {
189
200
  if (!this.state.confirm) {
190
201
  return null;
191
202
  }
192
- // @ts-ignore
193
203
  return react_1.default.createElement(material_1.Dialog, { open: !0, onClose: () => { var _a; return (_a = this.state.confirm) === null || _a === void 0 ? void 0 : _a.handleClose(); }, hideBackdrop: true, "aria-describedby": "confirm-dialog-description" },
194
204
  react_1.default.createElement(material_1.DialogContent, null,
195
205
  react_1.default.createElement(material_1.DialogContentText, { id: "confirm-dialog-description" }, (0, Utils_1.getTranslation)((_a = this.state.confirm) === null || _a === void 0 ? void 0 : _a.message))),
@@ -232,6 +242,7 @@ class Communication extends react_1.Component {
232
242
  return react_1.default.createElement(material_1.Dialog, { open: !0, onClose: () => { }, hideBackdrop: true },
233
243
  react_1.default.createElement(material_1.LinearProgress, { variant: "determinate", value: ((_b = this.state.progress) === null || _b === void 0 ? void 0 : _b.progress) || 0 }));
234
244
  }
245
+ // eslint-disable-next-line class-methods-use-this
235
246
  renderContent() {
236
247
  return null;
237
248
  }
@@ -5,6 +5,7 @@ interface DeviceActionButtonProps {
5
5
  action: any;
6
6
  refresh: () => void;
7
7
  deviceHandler: (deviceId: string, action: ActionBase<'api'>, refresh: () => void) => () => void;
8
+ disabled?: boolean;
8
9
  }
9
10
  export default function DeviceActionButton(props: DeviceActionButtonProps): React.JSX.Element;
10
11
  export {};
@@ -7,9 +7,9 @@ const react_1 = __importDefault(require("react"));
7
7
  const TooltipButton_js_1 = __importDefault(require("./TooltipButton.js"));
8
8
  const Utils_js_1 = require("./Utils.js");
9
9
  function DeviceActionButton(props) {
10
- const { deviceId, action, refresh, deviceHandler, } = props;
10
+ const { deviceId, action, refresh, deviceHandler, disabled, } = props;
11
11
  const tooltip = (0, Utils_js_1.getTranslation)(action.description);
12
12
  const icon = (0, Utils_js_1.renderIcon)(action);
13
- return react_1.default.createElement(TooltipButton_js_1.default, { label: action.label || (icon ? null : action.id), tooltip: tooltip, disabled: action.disabled, Icon: icon, onClick: deviceHandler(deviceId, action, refresh) });
13
+ return react_1.default.createElement(TooltipButton_js_1.default, { label: action.label || (icon ? null : action.id), tooltip: tooltip, disabled: disabled || action.disabled, Icon: icon, onClick: deviceHandler(deviceId, action, refresh) });
14
14
  }
15
15
  exports.default = DeviceActionButton;
package/DeviceCard.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import React from 'react';
1
+ import React, { Component } from 'react';
2
2
  import { Connection } from '@iobroker/adapter-react-v5';
3
- import { DeviceInfo } from '@iobroker/dm-utils';
3
+ import { DeviceDetails, DeviceInfo } from '@iobroker/dm-utils';
4
4
  import { ActionBase, ControlBase, ControlState } from '@iobroker/dm-utils/build/types/base';
5
5
  interface DeviceCardProps {
6
6
  title?: string;
@@ -12,9 +12,42 @@ interface DeviceCardProps {
12
12
  deviceHandler: (deviceId: string, action: ActionBase<'api'>, refresh: () => void) => () => void;
13
13
  controlHandler: (deviceId: string, control: ControlBase, state: ControlState) => () => Promise<ioBroker.State | null>;
14
14
  controlStateHandler: (deviceId: string, control: ControlBase) => () => Promise<ioBroker.State | null>;
15
+ smallCards?: boolean;
16
+ alive: boolean;
17
+ }
18
+ interface DeviceCardState {
19
+ open: boolean;
20
+ details: DeviceDetails | null;
21
+ data: Record<string, any>;
22
+ icon: string | undefined;
23
+ showControlDialog: boolean;
15
24
  }
16
25
  /**
17
26
  * Device Card Component
18
27
  */
19
- export default function DeviceCard(params: DeviceCardProps): React.JSX.Element;
20
- export {};
28
+ declare class DeviceCard extends Component<DeviceCardProps, DeviceCardState> {
29
+ constructor(props: DeviceCardProps);
30
+ fetchIcon(): Promise<void>;
31
+ componentDidMount(): void;
32
+ /**
33
+ * Load the device details
34
+ */
35
+ loadDetails(): Promise<void>;
36
+ /**
37
+ * Refresh the device details
38
+ */
39
+ refresh: () => void;
40
+ /**
41
+ * Copy the device ID to the clipboard
42
+ * @returns {void}
43
+ */
44
+ copyToClipboard: () => Promise<void>;
45
+ renderDialog(): React.JSX.Element;
46
+ renderControlDialog(): React.JSX.Element;
47
+ renderControls(): React.JSX.Element;
48
+ renderActions(): React.JSX.Element[];
49
+ renderSmall(): React.JSX.Element;
50
+ renderBig(): React.JSX.Element;
51
+ render(): React.JSX.Element;
52
+ }
53
+ export default DeviceCard;
package/DeviceCard.js CHANGED
@@ -47,162 +47,291 @@ function getText(text) {
47
47
  /**
48
48
  * Device Card Component
49
49
  */
50
- function DeviceCard(params) {
51
- var _a, _b, _c, _d;
52
- const { title, device, instanceId, socket, deviceHandler, uploadImagesToInstance, controlHandler, controlStateHandler, } = params;
53
- const [open, setOpen] = (0, react_1.useState)(false);
54
- const [details, setDetails] = (0, react_1.useState)(null);
55
- const [data, setData] = (0, react_1.useState)({});
56
- const [icon, setIcon] = (0, react_1.useState)(device.icon);
57
- const [showControlDialog, setShowControlDialog] = (0, react_1.useState)(false);
58
- // const [uploadedImage, setUploadedImage] = useState(null);
59
- const hasDetails = device.hasDetails;
60
- const status = !device.status ? [] : Array.isArray(device.status) ? device.status : [device.status];
61
- const colors = { primary: '#111', secondary: '#888' };
62
- (0, react_1.useEffect)(() => {
63
- async function fetchIcon() {
64
- if (!device.icon) {
65
- // try to load the icon from file storage
66
- const fileName = `${device.manufacturer ? `${device.manufacturer}_` : ''}${device.model ? device.model : device.id}`;
67
- try {
68
- const file = await socket.readFile(instanceId.replace('system.adapter.', ''), `${fileName}.webp`, true);
69
- setIcon(`data:image/${file.mimeType},${file}`);
70
- // const response = await fetch(url);
71
- // if (response.ok) {
72
- // const blob = await response.blob();
73
- // const reader = new FileReader();
74
- // reader.onloadend = () => {
75
- // setIcon(reader.result);
76
- // };
77
- // reader.readAsDataURL(blob);
78
- // } else {
79
- // throw new Error('Response not ok');
80
- // }
81
- }
82
- catch (error) {
83
- setIcon('');
84
- }
50
+ class DeviceCard extends react_1.Component {
51
+ constructor(props) {
52
+ super(props);
53
+ /**
54
+ * Refresh the device details
55
+ */
56
+ this.refresh = () => {
57
+ this.setState({ details: null });
58
+ this.loadDetails().catch(console.error);
59
+ };
60
+ /**
61
+ * Copy the device ID to the clipboard
62
+ * @returns {void}
63
+ */
64
+ this.copyToClipboard = async () => {
65
+ const textToCopy = this.props.device.id;
66
+ adapter_react_v5_1.Utils.copyToClipboard(textToCopy);
67
+ alert(`${(0, Utils_1.getTranslation)('copied')} ${textToCopy} ${(0, Utils_1.getTranslation)('toClipboard')}!`);
68
+ };
69
+ this.state = {
70
+ open: false,
71
+ details: null,
72
+ data: {},
73
+ icon: props.device.icon,
74
+ showControlDialog: false,
75
+ };
76
+ }
77
+ async fetchIcon() {
78
+ if (!this.props.device.icon) {
79
+ // try to load the icon from file storage
80
+ const fileName = `${this.props.device.manufacturer ? `${this.props.device.manufacturer}_` : ''}${this.props.device.model ? this.props.device.model : this.props.device.id}`;
81
+ try {
82
+ const file = await this.props.socket.readFile(this.props.instanceId.replace('system.adapter.', ''), `${fileName}.webp`, true);
83
+ this.setState({ icon: `data:image/${file.mimeType},${file}` });
84
+ // const response = await fetch(url);
85
+ // if (response.ok) {
86
+ // const blob = await response.blob();
87
+ // const reader = new FileReader();
88
+ // reader.onloadend = () => {
89
+ // setIcon(reader.result);
90
+ // };
91
+ // reader.readAsDataURL(blob);
92
+ // } else {
93
+ // throw new Error('Response not ok');
94
+ // }
95
+ }
96
+ catch (error) {
97
+ this.state.icon && this.setState({ icon: '' });
85
98
  }
86
99
  }
87
- fetchIcon()
100
+ }
101
+ componentDidMount() {
102
+ this.fetchIcon()
88
103
  .catch(e => console.error(e));
89
- }, [device, instanceId]);
104
+ }
90
105
  /**
91
106
  * Load the device details
92
107
  */
93
- const loadDetails = async () => {
94
- console.log(`Loading device details for ${device.id}... from ${instanceId}`);
95
- const result = await socket.sendTo(instanceId, 'dm:deviceDetails', device.id);
96
- console.log(`Got device details for ${device.id}:`, result);
97
- setDetails(result);
98
- };
99
- /**
100
- * Refresh the device details
101
- */
102
- const refresh = () => {
103
- setDetails(null);
104
- loadDetails().catch(console.error);
105
- };
106
- /**
107
- * Open the modal
108
- */
109
- const openModal = () => {
110
- if (!open) {
111
- loadDetails().catch(console.error);
112
- setOpen(true);
108
+ async loadDetails() {
109
+ console.log(`Loading device details for ${this.props.device.id}... from ${this.props.instanceId}`);
110
+ const details = await this.props.socket.sendTo(this.props.instanceId, 'dm:deviceDetails', this.props.device.id);
111
+ console.log(`Got device details for ${this.props.device.id}:`, details);
112
+ this.setState({ details, data: (details === null || details === void 0 ? void 0 : details.data) || {} });
113
+ }
114
+ ;
115
+ renderDialog() {
116
+ if (!this.state.open || !this.state.details) {
117
+ return null;
113
118
  }
114
- };
115
- /**
116
- * Close the modal
117
- */
118
- const handleClose = () => setOpen(false);
119
- const handleImageClick = async (imageData) => imageData && setIcon(imageData);
120
- /**
121
- * Copy the device ID to the clipboard
122
- * @returns {void}
123
- */
124
- const copyToClipboard = async () => {
125
- const textToCopy = device.id;
126
- adapter_react_v5_1.Utils.copyToClipboard(textToCopy);
127
- alert(`${(0, Utils_1.getTranslation)('copied')} ${textToCopy} ${(0, Utils_1.getTranslation)('toClipboard')}!`);
128
- };
129
- react_1.default.useEffect(() => setData((details === null || details === void 0 ? void 0 : details.data) || {}), [details]);
130
- const renderedDialog = open && details ? react_1.default.createElement(material_1.Dialog, { open: !0, maxWidth: "md", onClose: handleClose },
131
- react_1.default.createElement(material_1.DialogContent, null,
132
- react_1.default.createElement(JsonConfig_1.default, { instanceId: instanceId, socket: socket, schema: details.schema, data: data, onChange: setData })),
133
- react_1.default.createElement(material_1.DialogActions, null,
134
- react_1.default.createElement(material_1.Button, { variant: "contained", color: "primary", onClick: handleClose, autoFocus: true }, (0, Utils_1.getTranslation)('closeButtonText')))) : null;
135
- let renderedControls;
136
- let controlDialog;
137
- const firstControl = (_a = device.controls) === null || _a === void 0 ? void 0 : _a[0];
138
- if (((_b = device.controls) === null || _b === void 0 ? void 0 : _b.length) === 1 && firstControl && ((firstControl.type === 'icon' || firstControl.type === 'switch') && !firstControl.label)) {
139
- // control can be placed in button icon
140
- renderedControls = react_1.default.createElement(DeviceControl_1.default, { control: firstControl, colors: colors, socket: socket, deviceId: device.id, controlHandler: controlHandler, controlStateHandler: controlStateHandler });
119
+ return react_1.default.createElement(material_1.Dialog, { open: !0, maxWidth: "md", onClose: () => this.setState({ open: false }) },
120
+ react_1.default.createElement(material_1.DialogContent, null,
121
+ react_1.default.createElement(JsonConfig_1.default, { instanceId: this.props.instanceId, socket: this.props.socket, schema: this.state.details.schema, data: this.state.data, onChange: (data) => this.setState({ data }) })),
122
+ react_1.default.createElement(material_1.DialogActions, null,
123
+ react_1.default.createElement(material_1.Button, { disabled: !this.props.alive, variant: "contained", color: "primary", onClick: () => this.setState({ open: false }), autoFocus: true }, (0, Utils_1.getTranslation)('closeButtonText'))));
141
124
  }
142
- else if ((_c = device.controls) === null || _c === void 0 ? void 0 : _c.length) {
143
- // place button and show controls dialog
144
- renderedControls = react_1.default.createElement(material_1.Fab, { size: "small", onClick: () => setShowControlDialog(true) },
145
- react_1.default.createElement(icons_material_1.VideogameAsset, null));
146
- if (showControlDialog) {
147
- controlDialog = react_1.default.createElement(material_1.Dialog, { open: !0, onClose: () => setShowControlDialog(false) },
148
- react_1.default.createElement(material_1.DialogTitle, null,
149
- title,
150
- react_1.default.createElement(material_1.IconButton, { style: {
151
- position: 'absolute',
152
- top: 5,
153
- right: 5,
154
- zIndex: 10,
155
- }, onClick: () => setShowControlDialog(false) },
156
- react_1.default.createElement(icons_material_1.Close, null))),
157
- react_1.default.createElement(material_1.DialogContent, null, device.controls.map(control => react_1.default.createElement(DeviceControl_1.default, { key: control.id, control: control, socket: socket, colors: colors, deviceId: device.id, controlHandler: controlHandler, controlStateHandler: controlStateHandler }))));
125
+ renderControlDialog() {
126
+ var _a;
127
+ if (!this.state.showControlDialog || !this.props.alive) {
128
+ return null;
129
+ }
130
+ const colors = { primary: '#111', secondary: '#888' };
131
+ return react_1.default.createElement(material_1.Dialog, { open: !0, onClose: () => this.setState({ showControlDialog: false }) },
132
+ react_1.default.createElement(material_1.DialogTitle, null,
133
+ this.props.title,
134
+ react_1.default.createElement(material_1.IconButton, { style: {
135
+ position: 'absolute',
136
+ top: 5,
137
+ right: 5,
138
+ zIndex: 10,
139
+ }, onClick: () => this.setState({ showControlDialog: false }) },
140
+ react_1.default.createElement(icons_material_1.Close, null))),
141
+ react_1.default.createElement(material_1.DialogContent, { style: { display: 'flex', flexDirection: 'column' } }, (_a = this.props.device.controls) === null || _a === void 0 ? void 0 : _a.map(control => react_1.default.createElement(DeviceControl_1.default, { disabled: false, key: control.id, control: control, socket: this.props.socket, colors: colors, deviceId: this.props.device.id, controlHandler: this.props.controlHandler, controlStateHandler: this.props.controlStateHandler }))));
142
+ }
143
+ renderControls() {
144
+ var _a, _b, _c;
145
+ const colors = { primary: '#111', secondary: '#888' };
146
+ const firstControl = (_a = this.props.device.controls) === null || _a === void 0 ? void 0 : _a[0];
147
+ if (((_b = this.props.device.controls) === null || _b === void 0 ? void 0 : _b.length) === 1 && firstControl && ((firstControl.type === 'icon' || firstControl.type === 'switch') && !firstControl.label)) {
148
+ // control can be placed in button icon
149
+ return react_1.default.createElement(DeviceControl_1.default, { disabled: !this.props.alive, control: firstControl, colors: colors, socket: this.props.socket, deviceId: this.props.device.id, controlHandler: this.props.controlHandler, controlStateHandler: this.props.controlStateHandler });
150
+ }
151
+ if ((_c = this.props.device.controls) === null || _c === void 0 ? void 0 : _c.length) {
152
+ // place button and show controls dialog
153
+ return react_1.default.createElement(material_1.Fab, { size: "small", disabled: !this.props.alive, onClick: () => this.setState({ showControlDialog: true }) },
154
+ react_1.default.createElement(icons_material_1.VideogameAsset, null));
158
155
  }
156
+ return null;
159
157
  }
160
- const renderedActions = ((_d = device.actions) === null || _d === void 0 ? void 0 : _d.length) ? device.actions.map(a => react_1.default.createElement(DeviceActionButton_1.default, { key: a.id, deviceId: device.id, action: a, deviceHandler: deviceHandler, refresh: refresh })) : null;
161
- return react_1.default.createElement(material_1.Card, { sx: {
162
- maxWidth: 345,
163
- minWidth: 200,
164
- } },
165
- react_1.default.createElement(material_1.CardHeader, { sx: theme => ({
166
- backgroundColor: device.color || theme.palette.secondary.main,
167
- color: device.color ? adapter_react_v5_1.Utils.invertColor(device.color, true) : theme.palette.secondary.contrastText,
168
- }), avatar: react_1.default.createElement("div", null,
169
- uploadImagesToInstance ? react_1.default.createElement(DeviceImageUpload_1.default, { uploadImagesToInstance: uploadImagesToInstance, deviceId: device.id, manufacturer: getText(device.manufacturer), model: getText(device.model), onImageSelect: handleImageClick, socket: socket }) : null,
170
- icon ? react_1.default.createElement(adapter_react_v5_1.Icon, { src: icon }) : react_1.default.createElement(NoImageIcon, null)), action: hasDetails ? react_1.default.createElement(material_1.IconButton, { "aria-label": "settings", onClick: openModal },
171
- react_1.default.createElement(icons_material_1.MoreVert, null)) : null, title: title, subheader: device.manufacturer ? react_1.default.createElement("span", null,
172
- react_1.default.createElement("b", { style: { marginRight: 4 } },
173
- (0, Utils_1.getTranslation)('manufacturer'),
174
- ":"),
175
- getText(device.manufacturer)) : null }),
176
- react_1.default.createElement(material_1.CardContent, { style: { position: 'relative' } },
177
- (status === null || status === void 0 ? void 0 : status.length) ? react_1.default.createElement("div", { style: {
178
- display: 'flex',
179
- position: 'absolute',
180
- top: -11,
181
- background: '#88888880',
182
- padding: '0 8px',
183
- borderRadius: 5,
184
- width: 'calc(100% - 46px)',
185
- } }, status.map((s, i) => react_1.default.createElement(DeviceStatus_1.default, { key: i, status: s }))) : null,
186
- react_1.default.createElement("div", null,
187
- react_1.default.createElement(material_1.Typography, { variant: "body1" },
188
- react_1.default.createElement("div", { onClick: copyToClipboard },
189
- react_1.default.createElement("b", null, "ID:"),
190
- react_1.default.createElement("span", { style: { marginLeft: 4 } }, device.id.replace(/.*\.\d\./, ''))),
191
- device.manufacturer ? react_1.default.createElement("div", null,
158
+ renderActions() {
159
+ var _a;
160
+ return ((_a = this.props.device.actions) === null || _a === void 0 ? void 0 : _a.length) ? this.props.device.actions.map(a => react_1.default.createElement(DeviceActionButton_1.default, { disabled: !this.props.alive, key: a.id, deviceId: this.props.device.id, action: a, deviceHandler: this.props.deviceHandler, refresh: this.refresh })) : null;
161
+ }
162
+ renderSmall() {
163
+ const hasDetails = this.props.device.hasDetails;
164
+ const status = !this.props.device.status ? [] : Array.isArray(this.props.device.status) ? this.props.device.status : [this.props.device.status];
165
+ return react_1.default.createElement(material_1.Card, { sx: {
166
+ maxWidth: 345,
167
+ minWidth: 200,
168
+ } },
169
+ react_1.default.createElement(material_1.CardHeader, { sx: theme => ({
170
+ backgroundColor: this.props.device.color || theme.palette.secondary.main,
171
+ color: this.props.device.color ? adapter_react_v5_1.Utils.invertColor(this.props.device.color, true) : theme.palette.secondary.contrastText,
172
+ maxWidth: 345,
173
+ }), avatar: react_1.default.createElement("div", null,
174
+ this.props.uploadImagesToInstance ? react_1.default.createElement(DeviceImageUpload_1.default, { uploadImagesToInstance: this.props.uploadImagesToInstance, deviceId: this.props.device.id, manufacturer: getText(this.props.device.manufacturer), model: getText(this.props.device.model), onImageSelect: async (imageData) => imageData && this.setState({ icon: imageData }), socket: this.props.socket }) : null,
175
+ this.state.icon ? react_1.default.createElement(adapter_react_v5_1.Icon, { src: this.state.icon }) : react_1.default.createElement(NoImageIcon, null)), action: hasDetails ? react_1.default.createElement(material_1.IconButton, { "aria-label": "settings", onClick: () => {
176
+ if (!this.state.open) {
177
+ this.loadDetails().catch(console.error);
178
+ this.setState({ open: true });
179
+ }
180
+ } },
181
+ react_1.default.createElement(icons_material_1.MoreVert, null)) : null, title: this.props.title, subheader: this.props.device.manufacturer ? react_1.default.createElement("span", null,
182
+ react_1.default.createElement("b", { style: { marginRight: 4 } },
183
+ (0, Utils_1.getTranslation)('manufacturer'),
184
+ ":"),
185
+ getText(this.props.device.manufacturer)) : null }),
186
+ react_1.default.createElement(material_1.CardContent, { style: { position: 'relative' } },
187
+ (status === null || status === void 0 ? void 0 : status.length) ? react_1.default.createElement("div", { style: {
188
+ display: 'flex',
189
+ position: 'absolute',
190
+ top: -11,
191
+ background: '#88888880',
192
+ padding: '0 8px',
193
+ borderRadius: 5,
194
+ width: 'calc(100% - 46px)',
195
+ } }, status.map((s, i) => react_1.default.createElement(DeviceStatus_1.default, { key: i, status: s }))) : null,
196
+ react_1.default.createElement("div", null,
197
+ react_1.default.createElement(material_1.Typography, { variant: "body1" },
198
+ react_1.default.createElement("div", { onClick: this.copyToClipboard },
199
+ react_1.default.createElement("b", null, "ID:"),
200
+ react_1.default.createElement("span", { style: { marginLeft: 4 } }, this.props.device.id.replace(/.*\.\d\./, ''))),
201
+ this.props.device.manufacturer ? react_1.default.createElement("div", null,
202
+ react_1.default.createElement("b", { style: { marginRight: 4 } },
203
+ (0, Utils_1.getTranslation)('manufacturer'),
204
+ ":"),
205
+ getText(this.props.device.manufacturer)) : null,
206
+ this.props.device.model ? react_1.default.createElement("div", null,
207
+ react_1.default.createElement("b", { style: { marginRight: 4 } },
208
+ (0, Utils_1.getTranslation)('model'),
209
+ ":"),
210
+ getText(this.props.device.model)) : null))),
211
+ react_1.default.createElement(material_1.CardActions, { disableSpacing: true },
212
+ this.renderActions(),
213
+ react_1.default.createElement("div", { style: { flexGrow: 1 } }),
214
+ this.renderControls()),
215
+ this.renderDialog(),
216
+ this.renderControlDialog());
217
+ }
218
+ renderBig() {
219
+ var _a;
220
+ const cardStyle = {
221
+ // backgroundColor: '#fafafa',
222
+ width: 300,
223
+ minHeight: 280,
224
+ margin: 10,
225
+ overflow: 'hidden',
226
+ };
227
+ /** @type {CSSProperties} */
228
+ const headerStyle = {
229
+ display: 'flex',
230
+ position: 'relative',
231
+ justifyContent: 'space-between',
232
+ minHeight: 60,
233
+ color: '#000',
234
+ padding: '0 10px 0 10px',
235
+ backgroundColor: '#77c7ff8c',
236
+ borderRadius: '4px 4px 0 0',
237
+ };
238
+ /** @type {CSSProperties} */
239
+ const imgAreaStyle = {
240
+ height: 45,
241
+ width: 45,
242
+ margin: 'auto',
243
+ justifyContent: 'center',
244
+ display: 'grid',
245
+ };
246
+ /** @type {CSSProperties} */
247
+ const imgStyle = {
248
+ zIndex: 2,
249
+ maxWidth: '100%',
250
+ maxHeight: '100%',
251
+ };
252
+ /** @type {CSSProperties} */
253
+ const titleStyle = {
254
+ color: '#333',
255
+ width: '100%',
256
+ fontSize: 16,
257
+ fontWeight: 'bold',
258
+ paddingTop: 16,
259
+ paddingLeft: 8,
260
+ whiteSpace: 'nowrap',
261
+ overflow: 'hidden',
262
+ textOverflow: 'ellipsis',
263
+ };
264
+ /** @type {CSSProperties} */
265
+ const detailsButtonStyle = {
266
+ right: 20,
267
+ bottom: -20,
268
+ position: 'absolute',
269
+ };
270
+ /** @type {CSSProperties} */
271
+ const bodyStyle = {
272
+ height: 'calc(100% - 116px)',
273
+ };
274
+ /** @type {CSSProperties} */
275
+ const deviceInfoStyle = {
276
+ padding: '20px 16px 0 16px',
277
+ height: 133,
278
+ };
279
+ /** @type {CSSProperties} */
280
+ const statusStyle = {
281
+ padding: '15px 15px 0 15px',
282
+ height: 41,
283
+ };
284
+ const status = !this.props.device.status ? [] : Array.isArray(this.props.device.status) ? this.props.device.status : [this.props.device.status];
285
+ return react_1.default.createElement(material_1.Paper, { style: cardStyle, key: this.props.id },
286
+ react_1.default.createElement("div", { style: headerStyle },
287
+ react_1.default.createElement("div", { style: imgAreaStyle },
288
+ this.props.uploadImagesToInstance ? react_1.default.createElement(DeviceImageUpload_1.default, { uploadImagesToInstance: this.props.uploadImagesToInstance, deviceId: this.props.device.id, manufacturer: getText(this.props.device.manufacturer), model: getText(this.props.device.model), onImageSelect: async (imageData) => imageData && this.setState({ icon: imageData }), socket: this.props.socket }) : null,
289
+ react_1.default.createElement(adapter_react_v5_1.Icon, { src: this.state.icon, style: imgStyle })),
290
+ react_1.default.createElement("div", { style: titleStyle }, this.props.title),
291
+ this.props.device.hasDetails ? react_1.default.createElement(material_1.Fab, { disabled: !this.props.alive, size: "small", style: detailsButtonStyle, onClick: () => {
292
+ if (!this.state.open) {
293
+ this.loadDetails().catch(console.error);
294
+ this.setState({ open: true });
295
+ }
296
+ }, color: "primary" },
297
+ react_1.default.createElement(icons_material_1.MoreVert, null)) : null),
298
+ react_1.default.createElement("div", { style: statusStyle }, status.map((s, i) => react_1.default.createElement(DeviceStatus_1.default, { key: i, status: s }))),
299
+ react_1.default.createElement("div", { style: bodyStyle },
300
+ react_1.default.createElement(material_1.Typography, { variant: "body1", style: deviceInfoStyle },
301
+ react_1.default.createElement("div", { onClick: this.copyToClipboard },
302
+ react_1.default.createElement("b", { style: { marginRight: 4 } }, "ID:"),
303
+ this.props.device.id.replace(/.*\.\d\./, '')),
304
+ this.props.device.manufacturer ? react_1.default.createElement("div", null,
192
305
  react_1.default.createElement("b", { style: { marginRight: 4 } },
193
306
  (0, Utils_1.getTranslation)('manufacturer'),
194
307
  ":"),
195
- getText(device.manufacturer)) : null,
196
- device.model ? react_1.default.createElement("div", null,
308
+ getText(this.props.device.manufacturer)) : null,
309
+ this.props.device.model ? react_1.default.createElement("div", null,
197
310
  react_1.default.createElement("b", { style: { marginRight: 4 } },
198
311
  (0, Utils_1.getTranslation)('model'),
199
312
  ":"),
200
- getText(device.model)) : null))),
201
- react_1.default.createElement(material_1.CardActions, { disableSpacing: true },
202
- renderedActions,
203
- react_1.default.createElement("div", { style: { flexGrow: 1 } }),
204
- renderedControls),
205
- renderedDialog,
206
- controlDialog);
313
+ getText(this.props.device.model)) : null),
314
+ !!((_a = this.props.device.actions) === null || _a === void 0 ? void 0 : _a.length) && react_1.default.createElement("div", { style: {
315
+ flex: 1,
316
+ position: 'relative',
317
+ display: 'flex',
318
+ gap: 8,
319
+ paddingBottom: 5,
320
+ height: 34,
321
+ paddingLeft: 10,
322
+ paddingRight: 10,
323
+ } },
324
+ this.renderActions(),
325
+ react_1.default.createElement("div", { style: { flexGrow: 1 } }),
326
+ this.renderControls())),
327
+ this.renderDialog(),
328
+ this.renderControlDialog());
329
+ }
330
+ render() {
331
+ if (this.props.smallCards) {
332
+ return this.renderSmall();
333
+ }
334
+ return this.renderBig();
335
+ }
207
336
  }
208
337
  exports.default = DeviceCard;
@@ -7,6 +7,7 @@ interface DeviceControlProps {
7
7
  controlHandler: (deviceId: string, control: ControlBase, state: ControlState) => () => Promise<ioBroker.State | null>;
8
8
  controlStateHandler: (deviceId: string, control: ControlBase) => () => Promise<ioBroker.State | null>;
9
9
  colors: any;
10
+ disabled?: boolean;
10
11
  }
11
12
  interface DeviceControlState {
12
13
  value: any;
package/DeviceControl.js CHANGED
@@ -44,7 +44,7 @@ class DeviceControl extends react_1.Component {
44
44
  if (id === this.props.control.stateId && state) {
45
45
  // request new state
46
46
  const newState = await (this.props.controlStateHandler(this.props.deviceId, this.props.control)());
47
- if ((newState === null || newState === void 0 ? void 0 : newState.ts) && newState.ts > this.state.ts) {
47
+ if ((newState === null || newState === void 0 ? void 0 : newState.ts) && (!this.state.ts || newState.ts > this.state.ts)) {
48
48
  this.setState({
49
49
  value: newState.val,
50
50
  ts: newState.ts,
@@ -68,8 +68,8 @@ class DeviceControl extends react_1.Component {
68
68
  }
69
69
  }
70
70
  static getDerivedStateFromProps(props, state) {
71
- var _a;
72
- if (((_a = props.control.state) === null || _a === void 0 ? void 0 : _a.ts) > state.ts) {
71
+ var _a, _b;
72
+ if (((_a = props.control.state) === null || _a === void 0 ? void 0 : _a.ts) && (!state.ts || ((_b = props.control.state) === null || _b === void 0 ? void 0 : _b.ts) > state.ts)) {
73
73
  return {
74
74
  value: props.control.state.val,
75
75
  ts: props.control.state.ts,
@@ -79,7 +79,7 @@ class DeviceControl extends react_1.Component {
79
79
  }
80
80
  async sendControl(deviceId, control, value) {
81
81
  const result = await (this.props.controlHandler(deviceId, control, value)());
82
- if ((result === null || result === void 0 ? void 0 : result.ts) && (result === null || result === void 0 ? void 0 : result.ts) > this.state.ts) {
82
+ if ((result === null || result === void 0 ? void 0 : result.ts) && (!this.state.ts || (result === null || result === void 0 ? void 0 : result.ts) > this.state.ts)) {
83
83
  this.setState({
84
84
  value: result.val,
85
85
  ts: result.ts,
@@ -90,14 +90,14 @@ class DeviceControl extends react_1.Component {
90
90
  const tooltip = (0, Utils_1.getTranslation)(this.props.control.description);
91
91
  const icon = (0, Utils_1.renderIcon)(this.props.control, this.props.colors, this.state.value);
92
92
  if (!this.props.control.label) {
93
- return react_1.default.createElement(material_1.Fab, { title: tooltip, onClick: () => this.sendControl(this.props.deviceId, this.props.control, true) }, icon);
93
+ return react_1.default.createElement(material_1.Fab, { disabled: this.props.disabled, title: tooltip, onClick: () => this.sendControl(this.props.deviceId, this.props.control, true) }, icon);
94
94
  }
95
- return react_1.default.createElement(material_1.Button, { title: tooltip, onClick: () => this.sendControl(this.props.deviceId, this.props.control, true), startIcon: icon }, this.props.control.label);
95
+ return react_1.default.createElement(material_1.Button, { disabled: this.props.disabled, title: tooltip, onClick: () => this.sendControl(this.props.deviceId, this.props.control, true), startIcon: icon }, this.props.control.label);
96
96
  }
97
97
  renderSwitch() {
98
98
  const tooltip = (0, Utils_1.getTranslation)(this.props.control.description);
99
99
  // const icon = renderIcon(this.props.control, this.props.colors, this.state.value);
100
- return react_1.default.createElement(material_1.Switch, { title: tooltip, checked: this.state.value, onChange: e => this.sendControl(this.props.deviceId, this.props.control, e.target.checked) });
100
+ return react_1.default.createElement(material_1.Switch, { disabled: this.props.disabled, title: tooltip, checked: this.state.value, onChange: e => this.sendControl(this.props.deviceId, this.props.control, e.target.checked) });
101
101
  }
102
102
  getColor() {
103
103
  let color;
@@ -126,9 +126,9 @@ class DeviceControl extends react_1.Component {
126
126
  const icon = (0, Utils_1.renderIcon)(this.props.control, this.props.colors, this.state.value);
127
127
  const color = this.getColor();
128
128
  if (!this.props.control.label) {
129
- return react_1.default.createElement(material_1.Fab, { size: "small", title: tooltip, color: color === this.props.colors.primary ? 'primary' : (color === this.props.colors.secondary ? 'secondary' : undefined), style: color === this.props.colors.primary || color === this.props.colors.secondary ? undefined : { color }, onClick: () => this.sendControl(this.props.deviceId, this.props.control, !this.state.value) }, icon);
129
+ return react_1.default.createElement(material_1.Fab, { disabled: this.props.disabled, size: "small", title: tooltip, color: color === this.props.colors.primary ? 'primary' : (color === this.props.colors.secondary ? 'secondary' : undefined), style: color === this.props.colors.primary || color === this.props.colors.secondary ? undefined : { color }, onClick: () => this.sendControl(this.props.deviceId, this.props.control, !this.state.value) }, icon);
130
130
  }
131
- return react_1.default.createElement(material_1.Button, { title: tooltip, color: color === this.props.colors.primary ? 'primary' : (color === this.props.colors.secondary ? 'secondary' : undefined), style: color === this.props.colors.primary || color === this.props.colors.secondary ? undefined : { color }, onClick: () => this.sendControl(this.props.deviceId, this.props.control, !this.state.value), startIcon: icon }, this.props.control.label);
131
+ return react_1.default.createElement(material_1.Button, { disabled: this.props.disabled, title: tooltip, color: color === this.props.colors.primary ? 'primary' : (color === this.props.colors.secondary ? 'secondary' : undefined), style: color === this.props.colors.primary || color === this.props.colors.secondary ? undefined : { color }, onClick: () => this.sendControl(this.props.deviceId, this.props.control, !this.state.value), startIcon: icon }, this.props.control.label);
132
132
  }
133
133
  render() {
134
134
  if (this.props.control.type === 'button') {
package/DeviceList.d.ts CHANGED
@@ -6,7 +6,8 @@ interface DeviceListProps extends CommunicationProps {
6
6
  filter?: string;
7
7
  embedded?: boolean;
8
8
  title?: string;
9
- style: React.CSSProperties;
9
+ style?: React.CSSProperties;
10
+ smallCards?: boolean;
10
11
  }
11
12
  interface DeviceListState extends CommunicationState {
12
13
  devices: DeviceInfo[];
package/DeviceList.js CHANGED
@@ -66,7 +66,6 @@ class DeviceList extends Communication_1.default {
66
66
  'zh-cn': zh_cn_json_1.default,
67
67
  });
68
68
  }
69
- // @ts-ignore
70
69
  Object.assign(this.state, {
71
70
  devices: [],
72
71
  filteredDevices: [],
@@ -76,39 +75,51 @@ class DeviceList extends Communication_1.default {
76
75
  alive: null,
77
76
  });
78
77
  this.lastPropsFilter = this.props.filter;
79
- this.lastInstance = '';
78
+ this.lastInstance = this.props.selectedInstance;
80
79
  this.filterTimeout = null;
81
80
  this.language = adapter_react_v5_1.I18n.getLanguage();
82
81
  }
83
82
  async componentDidMount() {
84
- if (!this.props.embedded) {
83
+ let alive = false;
84
+ if (this.state.alive === null) {
85
85
  try {
86
86
  // check if instance is alive
87
- const alive = await this.props.socket.getState(`system.adapter.${this.props.selectedInstance}.alive`);
88
- this.props.socket.unsubscribeState(`system.adapter.${this.props.selectedInstance}.alive`, this.aliveHandler);
89
- if (!alive || !alive.val) {
90
- this.setState({ alive: false });
91
- return;
87
+ const stateAlive = await this.props.socket.getState(`system.adapter.${this.props.selectedInstance}.alive`);
88
+ if (stateAlive === null || stateAlive === void 0 ? void 0 : stateAlive.val) {
89
+ alive = true;
92
90
  }
93
- const instanceInfo = await this.loadInstanceInfos();
94
- this.setState({ alive: true, instanceInfo });
95
91
  }
96
92
  catch (error) {
97
93
  console.error(error);
98
- this.setState({ alive: false });
99
94
  }
95
+ this.setState({ alive }, () => this.props.socket.subscribeState(`system.adapter.${this.props.selectedInstance}.alive`, this.aliveHandler));
96
+ if (!alive) {
97
+ return;
98
+ }
99
+ }
100
+ else {
101
+ alive = this.state.alive;
100
102
  }
101
- try {
102
- await this.loadData();
103
+ if (!this.props.embedded && alive) {
104
+ try {
105
+ const instanceInfo = await this.loadInstanceInfos();
106
+ this.setState({ instanceInfo });
107
+ }
108
+ catch (error) {
109
+ console.error(error);
110
+ }
103
111
  }
104
- catch (error) {
105
- console.error(error);
112
+ if (alive) {
113
+ try {
114
+ await this.loadData();
115
+ }
116
+ catch (error) {
117
+ console.error(error);
118
+ }
106
119
  }
107
120
  }
108
121
  componentWillUnmount() {
109
- if (!this.props.embedded) {
110
- this.props.socket.unsubscribeState(`system.adapter.${this.props.selectedInstance}.alive`, this.aliveHandler);
111
- }
122
+ this.props.socket.unsubscribeState(`system.adapter.${this.props.selectedInstance}.alive`, this.aliveHandler);
112
123
  }
113
124
  /**
114
125
  * Load devices
@@ -176,7 +187,7 @@ class DeviceList extends Communication_1.default {
176
187
  react_1.default.createElement("span", null, (0, Utils_1.getTranslation)('allDevicesFilteredOut')));
177
188
  }
178
189
  else {
179
- list = this.state.filteredDevices.map(device => react_1.default.createElement(DeviceCard_1.default, { key: device.id, id: device.id, title: this.getText(device.name), device: device, instanceId: this.props.selectedInstance, uploadImagesToInstance: this.props.uploadImagesToInstance, deviceHandler: this.deviceHandler, controlHandler: this.controlHandler, controlStateHandler: this.controlStateHandler, socket: this.props.socket }));
190
+ list = this.state.filteredDevices.map(device => react_1.default.createElement(DeviceCard_1.default, { alive: !!this.state.alive, key: device.id, id: device.id, title: this.getText(device.name), device: device, instanceId: this.props.selectedInstance, uploadImagesToInstance: this.props.uploadImagesToInstance, deviceHandler: this.deviceHandler, controlHandler: this.controlHandler, controlStateHandler: this.controlStateHandler, socket: this.props.socket }));
180
191
  }
181
192
  if (this.props.embedded) {
182
193
  return list;
package/README.md CHANGED
@@ -20,6 +20,12 @@ render() {
20
20
  -->
21
21
 
22
22
  ## Changelog
23
+ ### 0.0.5 (2023-12-14)
24
+ * (bluefox) Added alive flag
25
+
26
+ ### 0.0.4 (2023-12-12)
27
+ * (bluefox) return the style of big cards
28
+
23
29
  ### 0.0.3 (2023-12-12)
24
30
  * (bluefox) initial commit
25
31
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iobroker/dm-gui-components",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "ReactJS components to develop admin interface for ioBroker device manager.",
5
5
  "author": {
6
6
  "name": "Jey Cee",
@@ -36,8 +36,8 @@
36
36
  "dependencies": {
37
37
  "react": "^18.2.0",
38
38
  "react-dom": "^18.2.0",
39
- "@iobroker/adapter-react-v5": "^4.7.13",
40
- "@types/react": "^18.2.43",
39
+ "@iobroker/adapter-react-v5": "^4.7.15",
40
+ "@types/react": "^18.2.45",
41
41
  "@types/react-dom": "^18.2.17",
42
42
  "eslint": "^8.55.0",
43
43
  "eslint-config-airbnb": "^19.0.4",