@iobroker/dm-gui-components 10.0.22 → 10.1.0

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 (46) hide show
  1. package/README.md +13 -0
  2. package/build/AnimatedIcons.d.ts +37 -0
  3. package/build/AnimatedIcons.js +108 -0
  4. package/build/AnimatedIcons.js.map +1 -0
  5. package/build/Communication.d.ts +7 -0
  6. package/build/Communication.js +43 -11
  7. package/build/Communication.js.map +1 -1
  8. package/build/DeviceCard.d.ts +25 -13
  9. package/build/DeviceCard.js +177 -282
  10. package/build/DeviceCard.js.map +1 -1
  11. package/build/DeviceControl.js +38 -26
  12. package/build/DeviceControl.js.map +1 -1
  13. package/build/DeviceImageUpload.d.ts +5 -1
  14. package/build/DeviceImageUpload.js +79 -62
  15. package/build/DeviceImageUpload.js.map +1 -1
  16. package/build/DeviceList.d.ts +8 -0
  17. package/build/DeviceList.js +74 -59
  18. package/build/DeviceList.js.map +1 -1
  19. package/build/DeviceStatus.d.ts +20 -4
  20. package/build/DeviceStatus.js +146 -191
  21. package/build/DeviceStatus.js.map +1 -1
  22. package/build/StatusIndicator.js +9 -37
  23. package/build/StatusIndicator.js.map +1 -1
  24. package/build/StatusPill.d.ts +18 -0
  25. package/build/StatusPill.js +30 -0
  26. package/build/StatusPill.js.map +1 -0
  27. package/build/Switch.d.ts +3 -0
  28. package/build/Switch.js +36 -26
  29. package/build/Switch.js.map +1 -1
  30. package/build/Utils.js +92 -60
  31. package/build/Utils.js.map +1 -1
  32. package/build/i18n/de.json +3 -0
  33. package/build/i18n/en.json +3 -0
  34. package/build/i18n/es.json +3 -0
  35. package/build/i18n/fr.json +3 -0
  36. package/build/i18n/it.json +3 -0
  37. package/build/i18n/nl.json +3 -0
  38. package/build/i18n/pl.json +3 -0
  39. package/build/i18n/pt.json +3 -0
  40. package/build/i18n/ru.json +3 -0
  41. package/build/i18n/uk.json +3 -0
  42. package/build/i18n/zh-cn.json +3 -0
  43. package/build/styles.d.ts +17 -0
  44. package/build/styles.js +226 -0
  45. package/build/styles.js.map +1 -0
  46. package/package.json +10 -10
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
- import { IconButton, InputAdornment, TextField, Toolbar, Tooltip, LinearProgress, Select, MenuItem, Card, CardActionArea, CardContent, Typography, Menu, Checkbox, ListSubheader, } from '@mui/material';
3
- import { ArrowBack, Clear, QuestionMark, Refresh, FilterAlt, FilterAltOff, SystemUpdateAlt, BatteryAlert, Tune, } from '@mui/icons-material';
4
- import { I18n, DeviceTypeIcon, Icon, InfoBox } from '@iobroker/gui-components';
2
+ import { Button, ButtonBase, IconButton, InputAdornment, TextField, Toolbar, Tooltip, LinearProgress, Select, MenuItem, Typography, Menu, Checkbox, ListSubheader, } from '@mui/material';
3
+ import { ArrowBack, Clear, DevicesOther, QuestionMark, FilterAltOff, Tune } from '@mui/icons-material';
4
+ import { I18n, DeviceTypeIcon, Icon, IconConnectionState, IconFilter, IconRefresh, InfoBox, } from '@iobroker/gui-components';
5
5
  import DeviceCard, { DeviceCardSkeleton, CARD_WIDTH, CARD_MIN_HEIGHT, CARD_MARGIN, SMALL_CARD_WIDTH, SMALL_CARD_MIN_HEIGHT, SMALL_CARD_MARGIN, } from './DeviceCard';
6
6
  import { getTranslation, renderIcon } from './Utils';
7
7
  import Communication from './Communication';
@@ -10,6 +10,8 @@ import { StatusIndicators } from './StatusIndicator';
10
10
  import { StateOrObjectHandler } from './StateOrObjectHandler';
11
11
  import { DeviceFieldsResolver } from './DeviceFields';
12
12
  import { LazyRender, LazyRenderObserver } from './LazyRender';
13
+ import { IconBattery, IconUpdate } from './AnimatedIcons';
14
+ import { ensureDmStyles, getThemeVars } from './styles';
13
15
  import de from './i18n/de.json';
14
16
  import en from './i18n/en.json';
15
17
  import ru from './i18n/ru.json';
@@ -107,6 +109,7 @@ export default class DeviceList extends Communication {
107
109
  hiddenIndicatorsCache = null;
108
110
  constructor(props) {
109
111
  super(props);
112
+ ensureDmStyles();
110
113
  this.stateOrObjectHandler = new StateOrObjectHandler(this.props.socket);
111
114
  this.fieldsResolver = new DeviceFieldsResolver(this.stateOrObjectHandler, this.onFieldsChanged);
112
115
  if (!DeviceList.i18nInitialized) {
@@ -518,46 +521,60 @@ export default class DeviceList extends Communication {
518
521
  }
519
522
  const instanceIds = Object.keys(this.state.dmInstances);
520
523
  if (!instanceIds.length) {
521
- return [
522
- React.createElement("div", { style: { padding: 25 }, key: "noInstances" },
523
- React.createElement("span", null, getTranslation('noInstancesFoundText'))),
524
- ];
525
- }
526
- const backgroundColor = this.props.theme.palette.mode === 'dark' ? '#0b0b0b' : '#d5d5d5';
527
- if (this.props.smallCards) {
528
- return instanceIds.map(id => {
529
- const info = this.state.dmInstances[id];
530
- return (React.createElement(Card, { key: id, sx: { width: 200, margin: '5px', backgroundColor } },
531
- React.createElement(CardActionArea, { onClick: () => this.selectInstance(id), style: { height: '100%' } },
532
- React.createElement(CardContent, { style: {
533
- height: '100%',
534
- display: 'flex',
535
- alignItems: 'center',
536
- gap: 8,
537
- padding: 8,
538
- } },
539
- info.icon ? (React.createElement(Icon, { src: info.icon, style: { width: 32, height: 32, flexShrink: 0 } })) : (React.createElement(QuestionMark, { style: { width: 32, height: 32, flexShrink: 0 } })),
540
- React.createElement("div", { style: { overflow: 'hidden' } },
541
- React.createElement(Typography, { variant: "body1", noWrap: true, style: { fontSize: 14, fontWeight: 'bold' } }, id),
542
- info.title ? (React.createElement(Typography, { variant: "caption", color: "textSecondary", noWrap: true, component: "div" }, info.title)) : null)))));
543
- });
524
+ return [this.renderEmpty('noInstances', getTranslation('noInstancesFoundText'))];
544
525
  }
526
+ const small = !!this.props.smallCards;
545
527
  return instanceIds.map(id => {
546
528
  const info = this.state.dmInstances[id];
547
- return (React.createElement(Card, { key: id, sx: { width: 240, margin: '10px', backgroundColor } },
548
- React.createElement(CardActionArea, { onClick: () => this.selectInstance(id), style: { height: '100%' } },
549
- React.createElement(CardContent, { style: {
550
- height: '100%',
551
- display: 'flex',
552
- flexDirection: 'column',
553
- alignItems: 'center',
554
- gap: 8,
555
- } },
556
- info.icon ? (React.createElement(Icon, { src: info.icon, style: { width: 64, height: 64 } })) : (React.createElement(QuestionMark, { style: { width: 64, height: 64 } })),
557
- React.createElement(Typography, { variant: "h6", style: { textAlign: 'center', wordBreak: 'break-all' } }, id),
558
- info.title ? (React.createElement(Typography, { variant: "body2", color: "textSecondary", style: { textAlign: 'center' } }, info.title)) : null))));
529
+ return (React.createElement("div", { key: id, className: small ? 'dm-card dm-inst dm-inst-small' : 'dm-card dm-inst' },
530
+ React.createElement(ButtonBase, { className: "dm-inst-area", onClick: () => this.selectInstance(id) },
531
+ React.createElement("div", { className: "dm-avatar" }, info.icon ? (React.createElement(Icon, { src: info.icon, style: { width: '100%', height: '100%', objectFit: 'contain' } })) : (React.createElement(QuestionMark, { style: { width: '70%', height: '70%' } }))),
532
+ React.createElement("div", { className: "dm-inst-text" },
533
+ React.createElement("div", { className: "dm-inst-id" }, id),
534
+ info.title ? React.createElement("div", { className: "dm-inst-title" }, info.title) : null))));
559
535
  });
560
536
  }
537
+ /** True if any filter hides devices: the text, the group or one of the toggles */
538
+ hasActiveFilter() {
539
+ return (!!this.state.filterText ||
540
+ this.state.onlyUpdatable ||
541
+ this.state.onlyBatteryProblem ||
542
+ (!!this.state.groupKey && this.state.groupKey !== '_'));
543
+ }
544
+ /** Removes all filters at once */
545
+ resetFilters() {
546
+ if (this.filterTimeout) {
547
+ clearTimeout(this.filterTimeout);
548
+ this.filterTimeout = null;
549
+ }
550
+ window.localStorage.setItem('dm_onlyUpdatable', 'false');
551
+ window.localStorage.setItem('dm_onlyBatteryProblem', 'false');
552
+ window.localStorage.removeItem(`dm_group_${this.state.selectedInstance}`);
553
+ this.setState({
554
+ filter: '',
555
+ filterText: '',
556
+ onlyUpdatable: false,
557
+ onlyBatteryProblem: false,
558
+ groupKey: '',
559
+ });
560
+ }
561
+ /** An empty list says what happened and, if possible, what to do */
562
+ renderEmpty(key, text, icon, action) {
563
+ return (React.createElement("div", { key: key, className: "dm-empty", style: getThemeVars(this.props.theme) },
564
+ icon,
565
+ React.createElement("span", null, text),
566
+ action));
567
+ }
568
+ /** The filter symbol in the toolbar. While a filter is set, a red cross shows that a click removes all filters */
569
+ renderFilterButton() {
570
+ const active = this.hasActiveFilter();
571
+ if (!active) {
572
+ return React.createElement(IconFilter, { active: false });
573
+ }
574
+ return (React.createElement(Tooltip, { title: getTranslation('resetFiltersTooltip'), slotProps: { popper: { sx: { pointerEvents: 'none' } } } },
575
+ React.createElement(IconButton, { color: "inherit", size: "small", onClick: () => this.resetFilters(), style: { margin: -5 } },
576
+ React.createElement(IconFilter, { active: true }))));
577
+ }
561
578
  /**
562
579
  * Apply the text filter and the "only updatable" / "only battery problem" filters.
563
580
  *
@@ -770,9 +787,6 @@ export default class DeviceList extends Communication {
770
787
  return (React.createElement("div", { style: { marginLeft: 20 } }, actions.map(action => (React.createElement(InstanceActionButton, { key: action.id, action: action, instanceHandler: this.instanceHandler })))));
771
788
  }
772
789
  renderContent() {
773
- const emptyStyle = {
774
- padding: 25,
775
- };
776
790
  const deviceGroups = [];
777
791
  const showRootPage = !this.props.embedded && this.props.selectedInstance === undefined && !this.state.selectedInstance;
778
792
  let list;
@@ -782,15 +796,13 @@ export default class DeviceList extends Communication {
782
796
  }
783
797
  else if (!this.props.embedded && !this.state.alive) {
784
798
  list = [
785
- React.createElement("div", { style: emptyStyle, key: "notAlive" },
786
- React.createElement("span", null, getTranslation('instanceNotAlive'))),
799
+ this.renderEmpty('notAlive', getTranslation('instanceNotAlive'),
800
+ // `null` while the state is still unknown: nothing is known yet, so no symbol
801
+ this.state.alive === false ? React.createElement(IconConnectionState, { state: "disconnected" }) : null),
787
802
  ];
788
803
  }
789
804
  else if (!this.state.devices.length && this.state.selectedInstance && !this.state.loading) {
790
- list = [
791
- React.createElement("div", { style: emptyStyle, key: "notFound" },
792
- React.createElement("span", null, getTranslation('noDevicesFoundText'))),
793
- ];
805
+ list = [this.renderEmpty('notFound', getTranslation('noDevicesFoundText'), React.createElement(DevicesOther, null))];
794
806
  }
795
807
  else {
796
808
  // build a device types list
@@ -869,15 +881,11 @@ export default class DeviceList extends Communication {
869
881
  else if (!visibleDevices.length && this.state.devices.length > 0) {
870
882
  // The filter is applied here now, so the list knows that nothing is left instead
871
883
  // of having to hide the message with a CSS rule
872
- list.push(React.createElement("div", { style: emptyStyle, key: "filtered" },
873
- React.createElement("span", null, getTranslation('allDevicesFilteredOut'))));
884
+ list.push(this.renderEmpty('filtered', getTranslation('allDevicesFilteredOut'), React.createElement(IconFilter, { active: !this.props.embedded }), this.props.embedded ? null : (React.createElement(Button, { variant: "outlined", color: "primary", onClick: () => this.resetFilters() }, getTranslation('resetFilters')))));
874
885
  }
875
886
  }
876
887
  else {
877
- list = [
878
- React.createElement("div", { style: emptyStyle, key: "selectInstance" },
879
- React.createElement("span", null, getTranslation('selectInstanceText'))),
880
- ];
888
+ list = [this.renderEmpty('selectInstance', getTranslation('selectInstanceText'))];
881
889
  }
882
890
  }
883
891
  if (this.props.embedded) {
@@ -885,7 +893,14 @@ export default class DeviceList extends Communication {
885
893
  this.state.loading ? React.createElement(LinearProgress, { style: { width: '100%' } }) : null,
886
894
  this.state.apiVersionError ? React.createElement("div", null, I18n.t('apiVersionError')) : list));
887
895
  }
888
- return (React.createElement("div", { style: { width: '100%', height: '100%', overflow: 'hidden', display: 'flex', flexDirection: 'column' } },
896
+ return (React.createElement("div", { style: {
897
+ ...getThemeVars(this.props.theme),
898
+ width: '100%',
899
+ height: '100%',
900
+ overflow: 'hidden',
901
+ display: 'flex',
902
+ flexDirection: 'column',
903
+ } },
889
904
  React.createElement(Toolbar, { variant: "dense", sx: TOOLBAR_SX, style: {
890
905
  display: 'flex',
891
906
  flexWrap: 'wrap',
@@ -905,11 +920,11 @@ export default class DeviceList extends Communication {
905
920
  this.props.selectedInstance === undefined && !this.state.selectedInstance ? (React.createElement(Tooltip, { title: getTranslation('refreshInstanceList'), slotProps: { popper: { sx: { pointerEvents: 'none' } } } },
906
921
  React.createElement("span", null,
907
922
  React.createElement(IconButton, { onClick: () => this.refreshInstanceList(), disabled: !this.state.dmInstances, color: "inherit", size: "small" },
908
- React.createElement(Refresh, null))))) : null,
923
+ React.createElement(IconRefresh, { loading: !this.state.dmInstances }))))) : null,
909
924
  this.state.selectedInstance ? (React.createElement(Tooltip, { title: getTranslation('refreshTooltip'), slotProps: { popper: { sx: { pointerEvents: 'none' } } } },
910
925
  React.createElement("span", null,
911
926
  React.createElement(IconButton, { onClick: () => this.loadAllData(), disabled: !this.state.alive || this.state.apiVersionError, color: "inherit", size: "small" },
912
- React.createElement(Refresh, null))))) : null,
927
+ React.createElement(IconRefresh, { loading: !!this.state.loading }))))) : null,
913
928
  !this.state.apiVersionError && this.state.alive && this.renderInstanceActions(),
914
929
  !this.state.apiVersionError && this.state.alive && this.state.instanceInfo?.indicators?.length ? (React.createElement(StatusIndicators, { indicators: this.state.instanceInfo.indicators.filter(indicator => this.isIndicatorVisible(indicator)), theme: this.props.theme, stateOrObjectHandler: this.stateOrObjectHandler, resolveAction: this.resolveInstanceIndicatorAction, defaultColor: "#fff", style: { marginLeft: 20 } })) : null,
915
930
  React.createElement("div", { style: { flexGrow: 1 } }),
@@ -922,7 +937,7 @@ export default class DeviceList extends Communication {
922
937
  this.setState({ onlyUpdatable });
923
938
  window.localStorage.setItem('dm_onlyUpdatable', onlyUpdatable ? 'true' : 'false');
924
939
  }, size: "small" },
925
- React.createElement(SystemUpdateAlt, null)))) : null,
940
+ React.createElement(IconUpdate, null)))) : null,
926
941
  !this.state.apiVersionError &&
927
942
  this.state.alive &&
928
943
  this.state.devices.some(device => hasBatteryStatus(device.status)) ? (React.createElement(Tooltip, { title: getTranslation('onlyBatteryProblemTooltip'), slotProps: { popper: { sx: { pointerEvents: 'none' } } } },
@@ -931,10 +946,10 @@ export default class DeviceList extends Communication {
931
946
  this.setState({ onlyBatteryProblem });
932
947
  window.localStorage.setItem('dm_onlyBatteryProblem', onlyBatteryProblem ? 'true' : 'false');
933
948
  }, size: "small" },
934
- React.createElement(BatteryAlert, null)))) : null,
949
+ React.createElement(IconBattery, { level: 10 })))) : null,
935
950
  !this.state.apiVersionError && this.state.alive ? this.renderIndicatorSettings() : null,
936
951
  !this.state.apiVersionError && this.state.alive ? (React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' } },
937
- React.createElement(FilterAlt, null),
952
+ this.renderFilterButton(),
938
953
  this.renderFilterFields(),
939
954
  this.renderFilterValue())) : null,
940
955
  React.createElement(Typography, { sx: { display: { xs: 'none', md: 'block' } }, style: {