@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,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;
@@ -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
- }