@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.
- package/LICENSE +21 -21
- package/README.md +59 -59
- package/build/Communication.d.ts +18 -11
- package/build/Communication.js +102 -19
- package/build/Communication.js.map +1 -1
- package/build/DeviceActionButton.d.ts +3 -3
- package/build/DeviceActionButton.js +8 -4
- package/build/DeviceActionButton.js.map +1 -1
- package/build/DeviceCard.d.ts +7 -8
- package/build/DeviceCard.js +8 -7
- package/build/DeviceCard.js.map +1 -1
- package/build/DeviceControl.d.ts +16 -15
- package/build/DeviceControl.js +15 -16
- package/build/DeviceControl.js.map +1 -1
- package/build/DeviceList.d.ts +7 -10
- package/build/DeviceList.js +0 -9
- package/build/DeviceList.js.map +1 -1
- package/build/DeviceStatus.d.ts +2 -1
- package/build/DeviceStatus.js +0 -4
- package/build/DeviceStatus.js.map +1 -1
- package/build/InstanceActionButton.d.ts +3 -2
- package/build/InstanceActionButton.js +2 -2
- package/build/InstanceActionButton.js.map +1 -1
- package/build/JsonConfig.d.ts +4 -4
- package/build/JsonConfig.js +3 -3
- package/build/JsonConfig.js.map +1 -1
- package/build/Utils.d.ts +6 -4
- package/build/Utils.js +112 -94
- package/build/Utils.js.map +1 -1
- package/build/i18n/de.json +2 -1
- package/build/i18n/en.json +2 -1
- package/build/i18n/es.json +2 -1
- package/build/i18n/fr.json +2 -1
- package/build/i18n/it.json +2 -1
- package/build/i18n/nl.json +2 -1
- package/build/i18n/pl.json +2 -1
- package/build/i18n/pt.json +2 -1
- package/build/i18n/ru.json +2 -1
- package/build/i18n/uk.json +2 -1
- package/build/i18n/zh-cn.json +2 -1
- package/package.json +53 -51
- package/src/Communication.tsx +0 -443
- package/src/DeviceActionButton.tsx +0 -31
- package/src/DeviceCard.tsx +0 -511
- package/src/DeviceControl.tsx +0 -196
- package/src/DeviceImageUpload.tsx +0 -92
- package/src/DeviceList.tsx +0 -344
- package/src/DeviceStatus.tsx +0 -156
- package/src/InstanceActionButton.tsx +0 -25
- package/src/JsonConfig.tsx +0 -99
- package/src/TooltipButton.tsx +0 -34
- package/src/Utils.tsx +0 -187
- package/src/i18n/de.json +0 -21
- package/src/i18n/en.json +0 -21
- package/src/i18n/es.json +0 -21
- package/src/i18n/fr.json +0 -21
- package/src/i18n/i18n.d.ts +0 -26
- package/src/i18n/it.json +0 -21
- package/src/i18n/nl.json +0 -21
- package/src/i18n/pl.json +0 -21
- package/src/i18n/pt.json +0 -21
- package/src/i18n/ru.json +0 -21
- package/src/i18n/uk.json +0 -21
- package/src/i18n/zh-cn.json +0 -21
- package/src/index.ts +0 -3
package/src/DeviceControl.tsx
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import React, { Component } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
Button, Fab,
|
|
4
|
-
Switch,
|
|
5
|
-
} from '@mui/material';
|
|
6
|
-
import { renderIcon, getTranslation } from './Utils';
|
|
7
|
-
import type { ControlBase, ControlState } from '@iobroker/dm-utils/build/types/base';
|
|
8
|
-
|
|
9
|
-
interface DeviceControlProps {
|
|
10
|
-
deviceId: string;
|
|
11
|
-
control: any;
|
|
12
|
-
socket: any;
|
|
13
|
-
controlHandler: (deviceId: string, control: ControlBase, state: ControlState) => () => Promise<ioBroker.State | null>;
|
|
14
|
-
controlStateHandler: (deviceId: string, control: ControlBase) => () => Promise<ioBroker.State | null>;
|
|
15
|
-
colors: any;
|
|
16
|
-
disabled?: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface DeviceControlState {
|
|
20
|
-
value: any;
|
|
21
|
-
ts: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Device Control component
|
|
26
|
-
* @param {object} props - Parameters
|
|
27
|
-
* @param {object} props.control - Control object
|
|
28
|
-
* @param {object} props.socket - Socket object
|
|
29
|
-
* @param {object} props.controlHandler - Control handler to set the state
|
|
30
|
-
* @param {object} props.controlStateHandler - Control handler to read the state
|
|
31
|
-
* @returns {React.JSX.Element|null}
|
|
32
|
-
* @constructor
|
|
33
|
-
*/
|
|
34
|
-
export default class DeviceControl extends Component<DeviceControlProps, DeviceControlState> {
|
|
35
|
-
constructor(props: DeviceControlProps) {
|
|
36
|
-
super(props);
|
|
37
|
-
this.state = {
|
|
38
|
-
value: props.control.state?.val,
|
|
39
|
-
ts: props.control.state?.ts,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
componentDidMount() {
|
|
44
|
-
if (this.props.control.stateId) {
|
|
45
|
-
this.props.socket.subscribeState(this.props.control.stateId, this.stateHandler);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
stateHandler = async (id: string, state: ioBroker.State) => {
|
|
50
|
-
if (id === this.props.control.stateId && state) {
|
|
51
|
-
// request new state
|
|
52
|
-
const newState: ioBroker.State | null = await (this.props.controlStateHandler(this.props.deviceId, this.props.control)());
|
|
53
|
-
if (newState?.ts && (!this.state.ts || newState.ts > this.state.ts)) {
|
|
54
|
-
this.setState({
|
|
55
|
-
value: newState.val,
|
|
56
|
-
ts: newState.ts,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
componentWillUnmount() {
|
|
63
|
-
if (this.props.control.stateId) {
|
|
64
|
-
this.props.socket.unsubscribeState(this.props.control.stateId, this.stateHandler);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
static getDerivedStateFromProps(props: DeviceControlProps, state: DeviceControlState) {
|
|
69
|
-
if (props.control.state?.ts && (!state.ts || props.control.state?.ts > state.ts)) {
|
|
70
|
-
return {
|
|
71
|
-
value: props.control.state.val,
|
|
72
|
-
ts: props.control.state.ts,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async sendControl(deviceId: string, control: ControlBase, value: ControlState) {
|
|
80
|
-
const result = await (this.props.controlHandler(deviceId, control, value)());
|
|
81
|
-
if (result?.ts && (!this.state.ts || result?.ts > this.state.ts)) {
|
|
82
|
-
this.setState({
|
|
83
|
-
value: result.val,
|
|
84
|
-
ts: result.ts,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
renderButton() {
|
|
90
|
-
const tooltip = getTranslation(this.props.control.description);
|
|
91
|
-
const icon = renderIcon(this.props.control, this.props.colors, this.state.value);
|
|
92
|
-
|
|
93
|
-
if (!this.props.control.label) {
|
|
94
|
-
return <Fab
|
|
95
|
-
disabled={this.props.disabled}
|
|
96
|
-
title={tooltip}
|
|
97
|
-
onClick={() => this.sendControl(this.props.deviceId, this.props.control, true)}
|
|
98
|
-
>
|
|
99
|
-
{icon}
|
|
100
|
-
</Fab>;
|
|
101
|
-
}
|
|
102
|
-
return <Button
|
|
103
|
-
disabled={this.props.disabled}
|
|
104
|
-
title={tooltip}
|
|
105
|
-
onClick={() => this.sendControl(this.props.deviceId, this.props.control, true)}
|
|
106
|
-
startIcon={icon}
|
|
107
|
-
>
|
|
108
|
-
{this.props.control.label}
|
|
109
|
-
</Button>;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
renderSwitch() {
|
|
113
|
-
const tooltip = getTranslation(this.props.control.description);
|
|
114
|
-
// const icon = renderIcon(this.props.control, this.props.colors, this.state.value);
|
|
115
|
-
|
|
116
|
-
return <Switch
|
|
117
|
-
disabled={this.props.disabled}
|
|
118
|
-
title={tooltip}
|
|
119
|
-
checked={this.state.value}
|
|
120
|
-
onChange={e => this.sendControl(this.props.deviceId, this.props.control, e.target.checked)}
|
|
121
|
-
/>;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
getColor() {
|
|
125
|
-
let color;
|
|
126
|
-
if (this.state.value) {
|
|
127
|
-
color = this.props.control.colorOn || 'primary';
|
|
128
|
-
} else if (this.props.control.type === 'switch') {
|
|
129
|
-
color = this.props.control.color;
|
|
130
|
-
}
|
|
131
|
-
if (color === 'primary') {
|
|
132
|
-
return this.props.colors.primary;
|
|
133
|
-
}
|
|
134
|
-
if (color === 'secondary') {
|
|
135
|
-
return this.props.colors.secondary;
|
|
136
|
-
}
|
|
137
|
-
return color;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
renderSelect() {
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
renderSlider() {
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
renderColor() {
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
renderIcon() {
|
|
153
|
-
const tooltip = getTranslation(this.props.control.description);
|
|
154
|
-
const icon = renderIcon(this.props.control, this.props.colors, this.state.value);
|
|
155
|
-
const color = this.getColor();
|
|
156
|
-
|
|
157
|
-
if (!this.props.control.label) {
|
|
158
|
-
return <Fab
|
|
159
|
-
disabled={this.props.disabled}
|
|
160
|
-
size="small"
|
|
161
|
-
title={tooltip}
|
|
162
|
-
color={color === this.props.colors.primary ? 'primary' : (color === this.props.colors.secondary ? 'secondary' : undefined)}
|
|
163
|
-
style={color === this.props.colors.primary || color === this.props.colors.secondary ? undefined : { color }}
|
|
164
|
-
onClick={() => this.sendControl(this.props.deviceId, this.props.control, !this.state.value)}
|
|
165
|
-
>
|
|
166
|
-
{icon}
|
|
167
|
-
</Fab>;
|
|
168
|
-
}
|
|
169
|
-
return <Button
|
|
170
|
-
disabled={this.props.disabled}
|
|
171
|
-
title={tooltip}
|
|
172
|
-
color={color === this.props.colors.primary ? 'primary' : (color === this.props.colors.secondary ? 'secondary' : undefined)}
|
|
173
|
-
style={color === this.props.colors.primary || color === this.props.colors.secondary ? undefined : { color }}
|
|
174
|
-
onClick={() => this.sendControl(this.props.deviceId, this.props.control, !this.state.value)}
|
|
175
|
-
startIcon={icon}
|
|
176
|
-
>
|
|
177
|
-
{this.props.control.label}
|
|
178
|
-
</Button>;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
render() {
|
|
182
|
-
if (this.props.control.type === 'button') {
|
|
183
|
-
return this.renderButton();
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (this.props.control.type === 'icon') {
|
|
187
|
-
return this.renderIcon();
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (this.props.control.type === 'switch') {
|
|
191
|
-
return this.renderSwitch();
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return <div style={{ color: 'red' }}>{this.props.control.type}</div>;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import React, { type ChangeEvent, type ChangeEventHandler } from 'react';
|
|
2
|
-
import type { Connection } from '@iobroker/adapter-react-v5';
|
|
3
|
-
|
|
4
|
-
interface DeviceImageUploadProps {
|
|
5
|
-
socket: Connection;
|
|
6
|
-
manufacturer?: string;
|
|
7
|
-
model?: string;
|
|
8
|
-
deviceId: string;
|
|
9
|
-
onImageSelect: (image: string) => void;
|
|
10
|
-
uploadImagesToInstance: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function DeviceImageUpload(params: DeviceImageUploadProps): React.JSX.Element | null {
|
|
14
|
-
const {
|
|
15
|
-
socket, manufacturer, model, deviceId, onImageSelect, uploadImagesToInstance,
|
|
16
|
-
} = params;
|
|
17
|
-
|
|
18
|
-
const handleImageUpload: ChangeEventHandler<HTMLInputElement> = async (event: ChangeEvent<HTMLInputElement>) => {
|
|
19
|
-
const target = event.target as HTMLInputElement;
|
|
20
|
-
const files: FileList | null = target.files;
|
|
21
|
-
if (!files || files.length === 0) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const file = files[0];
|
|
26
|
-
|
|
27
|
-
if (file) {
|
|
28
|
-
const reader = new FileReader();
|
|
29
|
-
|
|
30
|
-
reader.onload = async e => {
|
|
31
|
-
if (!e.target || !e.target.result) {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const img = new Image();
|
|
36
|
-
img.src = e.target.result as string;
|
|
37
|
-
|
|
38
|
-
img.onload = async () => {
|
|
39
|
-
const maxWidth = 50;
|
|
40
|
-
const maxHeight = 50;
|
|
41
|
-
let width = img.width;
|
|
42
|
-
let height = img.height;
|
|
43
|
-
|
|
44
|
-
if (width > height) {
|
|
45
|
-
if (width > maxWidth) {
|
|
46
|
-
height *= maxWidth / width;
|
|
47
|
-
width = maxWidth;
|
|
48
|
-
}
|
|
49
|
-
} else if (height > maxHeight) {
|
|
50
|
-
width *= maxHeight / height;
|
|
51
|
-
height = maxHeight;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const canvas = document.createElement('canvas');
|
|
55
|
-
const ctx = canvas.getContext('2d');
|
|
56
|
-
if (ctx) {
|
|
57
|
-
canvas.width = width;
|
|
58
|
-
canvas.height = height;
|
|
59
|
-
ctx.drawImage(img, 0, 0, width, height);
|
|
60
|
-
|
|
61
|
-
const resizedImage = canvas.toDataURL('image/webp');
|
|
62
|
-
|
|
63
|
-
// Build the file name from a manufacturer and model, if not available, use device id
|
|
64
|
-
const fileName = `${manufacturer ? `${manufacturer}_` : ''}${model || deviceId}`;
|
|
65
|
-
const base64Data = resizedImage.replace(/^data:image\/webp;base64,/, '');
|
|
66
|
-
const response = await socket.writeFile64(uploadImagesToInstance, fileName, base64Data);
|
|
67
|
-
console.log(`saveImage response: ${JSON.stringify(response)}`);
|
|
68
|
-
|
|
69
|
-
onImageSelect && onImageSelect(resizedImage);
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
reader.readAsDataURL(file);
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const imageUploadButtonStyle: React.CSSProperties = {
|
|
79
|
-
// make the button invisible but still clickable
|
|
80
|
-
opacity: 0,
|
|
81
|
-
position: 'absolute',
|
|
82
|
-
width: '45px',
|
|
83
|
-
height: '45px',
|
|
84
|
-
zIndex: 3,
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
return <div>
|
|
88
|
-
<input style={imageUploadButtonStyle} type="file" accept="image/*" onChange={handleImageUpload} />
|
|
89
|
-
</div>;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export default DeviceImageUpload;
|
package/src/DeviceList.tsx
DELETED
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
IconButton, InputAdornment, TextField,
|
|
4
|
-
Toolbar, Tooltip, LinearProgress,
|
|
5
|
-
} from '@mui/material';
|
|
6
|
-
|
|
7
|
-
import { Clear, Refresh } from '@mui/icons-material';
|
|
8
|
-
|
|
9
|
-
import { I18n } from '@iobroker/adapter-react-v5';
|
|
10
|
-
import type { DeviceInfo, InstanceDetails } from '@iobroker/dm-utils';
|
|
11
|
-
|
|
12
|
-
import DeviceCard from './DeviceCard';
|
|
13
|
-
import { getTranslation } from './Utils';
|
|
14
|
-
import Communication, { type CommunicationProps, type CommunicationState } from './Communication';
|
|
15
|
-
import InstanceActionButton from './InstanceActionButton';
|
|
16
|
-
|
|
17
|
-
import de from './i18n/de.json';
|
|
18
|
-
import en from './i18n/en.json';
|
|
19
|
-
import ru from './i18n/ru.json';
|
|
20
|
-
import pt from './i18n/pt.json';
|
|
21
|
-
import nl from './i18n/nl.json';
|
|
22
|
-
import fr from './i18n/fr.json';
|
|
23
|
-
import it from './i18n/it.json';
|
|
24
|
-
import es from './i18n/es.json';
|
|
25
|
-
import pl from './i18n/pl.json';
|
|
26
|
-
import uk from './i18n/uk.json';
|
|
27
|
-
import zhCn from './i18n/zh-cn.json';
|
|
28
|
-
|
|
29
|
-
interface DeviceListProps extends CommunicationProps {
|
|
30
|
-
/* Instance to upload images to, like `adapterName.X` */
|
|
31
|
-
uploadImagesToInstance?: string;
|
|
32
|
-
/* Filter devices with this string */
|
|
33
|
-
filter?: string;
|
|
34
|
-
/* If this component is used in GUI with own toolbar. `false` if this list is used with multiple instances and true if only with one (in this case, it will monitor alive itself */
|
|
35
|
-
embedded?: boolean;
|
|
36
|
-
/* If embedded, this text is shown in the toolbar */
|
|
37
|
-
title?: string;
|
|
38
|
-
/* Style of a component that displays all devices */
|
|
39
|
-
style?: React.CSSProperties;
|
|
40
|
-
/* Use small cards for devices */
|
|
41
|
-
smallCards?: boolean;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface DeviceListState extends CommunicationState {
|
|
45
|
-
devices: DeviceInfo[];
|
|
46
|
-
filteredDevices: DeviceInfo[];
|
|
47
|
-
filter: string;
|
|
48
|
-
instanceInfo: InstanceDetails;
|
|
49
|
-
loading: boolean;
|
|
50
|
-
alive: boolean | null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Device List Component
|
|
55
|
-
* @param {object} params - Component parameters
|
|
56
|
-
* @param {object} params.socket - socket object
|
|
57
|
-
* @param {string} params.selectedInstance - Selected instance
|
|
58
|
-
* @param {string} params.uploadImagesToInstance - Instance to upload images to
|
|
59
|
-
* @param {string} params.filter - Filter
|
|
60
|
-
* @param {string} params.empbedded - true if this list used with multiple instances and false if only with one
|
|
61
|
-
* @param {string} params.title - Title in appbar (only in non-embedded mode)
|
|
62
|
-
* @param {string} params.style - Style of devices list
|
|
63
|
-
* @returns {*[]} - Array of device cards
|
|
64
|
-
*/
|
|
65
|
-
export default class DeviceList extends Communication<DeviceListProps, DeviceListState> {
|
|
66
|
-
static i18nInitialized = false;
|
|
67
|
-
|
|
68
|
-
private lastPropsFilter: string | undefined;
|
|
69
|
-
|
|
70
|
-
private lastInstance: string;
|
|
71
|
-
|
|
72
|
-
private filterTimeout: ReturnType<typeof setTimeout> | null;
|
|
73
|
-
|
|
74
|
-
private readonly language: ioBroker.Languages;
|
|
75
|
-
|
|
76
|
-
constructor(props: DeviceListProps) {
|
|
77
|
-
super(props);
|
|
78
|
-
|
|
79
|
-
if (!DeviceList.i18nInitialized) {
|
|
80
|
-
DeviceList.i18nInitialized = true;
|
|
81
|
-
I18n.extendTranslations({
|
|
82
|
-
en,
|
|
83
|
-
de,
|
|
84
|
-
ru,
|
|
85
|
-
pt,
|
|
86
|
-
nl,
|
|
87
|
-
fr,
|
|
88
|
-
it,
|
|
89
|
-
es,
|
|
90
|
-
pl,
|
|
91
|
-
uk,
|
|
92
|
-
'zh-cn': zhCn,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
Object.assign(this.state, {
|
|
97
|
-
devices: [],
|
|
98
|
-
filteredDevices: [],
|
|
99
|
-
filter: '',
|
|
100
|
-
instanceInfo: null,
|
|
101
|
-
loading: null,
|
|
102
|
-
alive: null,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
this.lastPropsFilter = this.props.filter;
|
|
106
|
-
this.lastInstance = this.props.selectedInstance;
|
|
107
|
-
this.filterTimeout = null;
|
|
108
|
-
this.language = I18n.getLanguage();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async componentDidMount() {
|
|
112
|
-
let alive = false;
|
|
113
|
-
if (this.state.alive === null) {
|
|
114
|
-
try {
|
|
115
|
-
// check if instance is alive
|
|
116
|
-
const stateAlive = await this.props.socket.getState(`system.adapter.${this.props.selectedInstance}.alive`);
|
|
117
|
-
if (stateAlive?.val) {
|
|
118
|
-
alive = true;
|
|
119
|
-
}
|
|
120
|
-
} catch (error) {
|
|
121
|
-
console.error(error);
|
|
122
|
-
}
|
|
123
|
-
this.setState({ alive }, () => this.props.socket.subscribeState(`system.adapter.${this.props.selectedInstance}.alive`, this.aliveHandler));
|
|
124
|
-
if (!alive) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
} else {
|
|
128
|
-
alive = this.state.alive;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (!this.props.embedded && alive) {
|
|
132
|
-
try {
|
|
133
|
-
const instanceInfo = await this.loadInstanceInfos();
|
|
134
|
-
this.setState({ instanceInfo });
|
|
135
|
-
} catch (error) {
|
|
136
|
-
console.error(error);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
if (alive) {
|
|
140
|
-
this.loadData();
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
componentWillUnmount() {
|
|
145
|
-
this.props.socket.unsubscribeState(`system.adapter.${this.props.selectedInstance}.alive`, this.aliveHandler);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
aliveHandler: ioBroker.StateChangeHandler = (id: string, state: ioBroker.State | null | undefined) => {
|
|
149
|
-
if (id === `system.adapter.${this.props.selectedInstance}.alive`) {
|
|
150
|
-
const alive = !!state?.val;
|
|
151
|
-
if (alive !== this.state.alive) {
|
|
152
|
-
this.setState({ alive }, () => {
|
|
153
|
-
if (alive) {
|
|
154
|
-
this.componentDidMount().catch(console.error);
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Load devices
|
|
163
|
-
*/
|
|
164
|
-
loadData(): void {
|
|
165
|
-
this.setState({ loading: true }, async () => {
|
|
166
|
-
console.log(`Loading devices for ${this.props.selectedInstance}...`);
|
|
167
|
-
let devices;
|
|
168
|
-
try {
|
|
169
|
-
devices = await this.loadDevices();
|
|
170
|
-
|
|
171
|
-
if (!devices || !Array.isArray(devices)) {
|
|
172
|
-
console.error(
|
|
173
|
-
`Message returned from sendTo() doesn't look like one from DeviceManagement, did you accidentally handle the message in your adapter? ${JSON.stringify(
|
|
174
|
-
devices,
|
|
175
|
-
)}`,
|
|
176
|
-
);
|
|
177
|
-
devices = [];
|
|
178
|
-
}
|
|
179
|
-
} catch (error) {
|
|
180
|
-
console.error(error);
|
|
181
|
-
devices = [];
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
this.setState({ devices, loading: false }, () =>
|
|
185
|
-
this.applyFilter());
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
getText(text: ioBroker.StringOrTranslated): string {
|
|
190
|
-
if (typeof text === 'object') {
|
|
191
|
-
return text[this.language] || text.en;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return text;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
applyFilter() {
|
|
198
|
-
const filter = this.props.embedded ? this.props.filter : this.state.filter;
|
|
199
|
-
|
|
200
|
-
// filter devices name
|
|
201
|
-
if (filter) {
|
|
202
|
-
const filteredDevices = this.state.devices.filter(device =>
|
|
203
|
-
this.getText(device.name).toLowerCase().includes(filter.toLowerCase()));
|
|
204
|
-
this.setState({ filteredDevices });
|
|
205
|
-
} else {
|
|
206
|
-
this.setState({ filteredDevices: this.state.devices });
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
handleFilterChange(filter: string) {
|
|
211
|
-
this.setState({ filter }, () => {
|
|
212
|
-
this.filterTimeout && clearTimeout(this.filterTimeout);
|
|
213
|
-
this.filterTimeout = setTimeout(() => {
|
|
214
|
-
this.filterTimeout = null;
|
|
215
|
-
this.applyFilter();
|
|
216
|
-
}, 250);
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
renderContent(): React.JSX.Element | React.JSX.Element[] | null {
|
|
221
|
-
/** @type {object} */
|
|
222
|
-
const emptyStyle = {
|
|
223
|
-
padding: 25,
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
if (this.props.embedded && this.lastPropsFilter !== this.props.filter) {
|
|
227
|
-
this.lastPropsFilter = this.props.filter;
|
|
228
|
-
setTimeout(() => this.applyFilter(), 50);
|
|
229
|
-
}
|
|
230
|
-
if (this.props.embedded && this.lastInstance !== this.props.selectedInstance) {
|
|
231
|
-
this.lastInstance = this.props.selectedInstance;
|
|
232
|
-
setTimeout(() => this.loadData(), 50);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
let list;
|
|
236
|
-
if (!this.props.embedded && !this.state.alive) {
|
|
237
|
-
list = <div style={emptyStyle}>
|
|
238
|
-
<span>{getTranslation('instanceNotAlive')}</span>
|
|
239
|
-
</div>;
|
|
240
|
-
} else if (!this.state.devices.length && this.props.selectedInstance) {
|
|
241
|
-
list = <div style={emptyStyle}>
|
|
242
|
-
<span>{getTranslation('noDevicesFoundText')}</span>
|
|
243
|
-
</div>;
|
|
244
|
-
} else if (this.state.devices.length && !this.state.filteredDevices.length) {
|
|
245
|
-
list = <div style={emptyStyle}>
|
|
246
|
-
<span>{getTranslation('allDevicesFilteredOut')}</span>
|
|
247
|
-
</div>;
|
|
248
|
-
} else {
|
|
249
|
-
list = this.state.filteredDevices.map(device => <DeviceCard
|
|
250
|
-
alive={!!this.state.alive}
|
|
251
|
-
key={device.id}
|
|
252
|
-
id={device.id}
|
|
253
|
-
title={this.getText(device.name)}
|
|
254
|
-
device={device}
|
|
255
|
-
instanceId={this.props.selectedInstance}
|
|
256
|
-
uploadImagesToInstance={this.props.uploadImagesToInstance}
|
|
257
|
-
deviceHandler={this.deviceHandler}
|
|
258
|
-
controlHandler={this.controlHandler}
|
|
259
|
-
controlStateHandler={this.controlStateHandler}
|
|
260
|
-
socket={this.props.socket}
|
|
261
|
-
themeName={this.props.themeName}
|
|
262
|
-
themeType={this.props.themeType}
|
|
263
|
-
isFloatComma={this.props.isFloatComma}
|
|
264
|
-
dateFormat={this.props.dateFormat}
|
|
265
|
-
/>);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (this.props.embedded) {
|
|
269
|
-
return <>
|
|
270
|
-
{this.state.loading ? <LinearProgress style={{ width: '100%' }} /> : null}
|
|
271
|
-
{list}
|
|
272
|
-
</>;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return <div style={{ width: '100%', height: '100%', overflow: 'hidden' }}>
|
|
276
|
-
<Toolbar variant="dense" style={{ backgroundColor: '#777', display: 'flex' }}>
|
|
277
|
-
{this.props.title}
|
|
278
|
-
{this.props.selectedInstance ? <Tooltip title={getTranslation('refreshTooltip')}>
|
|
279
|
-
<span>
|
|
280
|
-
<IconButton
|
|
281
|
-
onClick={() => this.loadData()}
|
|
282
|
-
disabled={!this.state.alive}
|
|
283
|
-
size="small"
|
|
284
|
-
>
|
|
285
|
-
<Refresh />
|
|
286
|
-
</IconButton>
|
|
287
|
-
</span>
|
|
288
|
-
</Tooltip> : null}
|
|
289
|
-
{this.state.alive && this.state.instanceInfo?.actions?.length ? <div style={{ marginLeft: 20 }}>
|
|
290
|
-
{this.state.instanceInfo.actions.map(action =>
|
|
291
|
-
<InstanceActionButton
|
|
292
|
-
key={action.id}
|
|
293
|
-
action={action}
|
|
294
|
-
instanceHandler={this.instanceHandler}
|
|
295
|
-
/>)}
|
|
296
|
-
</div> : null}
|
|
297
|
-
|
|
298
|
-
<div style={{ flexGrow: 1 }} />
|
|
299
|
-
|
|
300
|
-
{this.state.alive ? <TextField
|
|
301
|
-
variant="standard"
|
|
302
|
-
style={{ width: 200 }}
|
|
303
|
-
size="small"
|
|
304
|
-
label={getTranslation('filterLabelText')}
|
|
305
|
-
onChange={e => this.handleFilterChange(e.target.value)}
|
|
306
|
-
value={this.state.filter}
|
|
307
|
-
autoComplete="off"
|
|
308
|
-
inputProps={{
|
|
309
|
-
autoComplete: 'new-password',
|
|
310
|
-
form: { autoComplete: 'off' },
|
|
311
|
-
}}
|
|
312
|
-
// eslint-disable-next-line react/jsx-no-duplicate-props
|
|
313
|
-
InputProps={{
|
|
314
|
-
endAdornment: this.state.filter ? <InputAdornment position="end">
|
|
315
|
-
<IconButton
|
|
316
|
-
onClick={() => this.handleFilterChange('')}
|
|
317
|
-
edge="end"
|
|
318
|
-
>
|
|
319
|
-
<Clear />
|
|
320
|
-
</IconButton>
|
|
321
|
-
</InputAdornment> : null,
|
|
322
|
-
}}
|
|
323
|
-
/> : null}
|
|
324
|
-
</Toolbar>
|
|
325
|
-
<div
|
|
326
|
-
style={{
|
|
327
|
-
width: '100%',
|
|
328
|
-
height: 'calc(100% - 56px)',
|
|
329
|
-
marginTop: 8,
|
|
330
|
-
overflow: 'auto',
|
|
331
|
-
// justifyContent: 'center',
|
|
332
|
-
// alignItems: 'stretch',
|
|
333
|
-
// display: 'grid',
|
|
334
|
-
// columnGap: 8,
|
|
335
|
-
// rowGap: 8,
|
|
336
|
-
...this.props.style,
|
|
337
|
-
}}
|
|
338
|
-
>
|
|
339
|
-
{this.state.loading ? <LinearProgress style={{ width: '100%' }} /> : null}
|
|
340
|
-
{list}
|
|
341
|
-
</div>
|
|
342
|
-
</div>;
|
|
343
|
-
}
|
|
344
|
-
}
|