@iobroker/adapter-react-v5 8.0.7 → 8.0.9

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.
@@ -5,10 +5,9 @@
5
5
  *
6
6
  */
7
7
  import React, { Component, createRef } from 'react';
8
- import SVG from 'react-inlinesvg';
9
- import { Badge, Box, Button, Checkbox, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Fab, FormControl, FormControlLabel, Grid2, IconButton, Input, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Menu, MenuItem, Paper, Select, Snackbar, Switch, TextField, Tooltip, } from '@mui/material';
8
+ import { Badge, Box, Button, Checkbox, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Fab, FormControlLabel, Grid2, IconButton, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Menu, MenuItem, Paper, Snackbar, Switch, TextField, Tooltip, } from '@mui/material';
10
9
  // Icons
11
- import { Add as AddIcon, ArrowRight as ArrowRightIcon, BedroomParent, BorderColor, Build as BuildIcon, CalendarToday as IconSchedule, Check as IconCheck, Close as IconClose, Code as IconScript, Construction, CreateNewFolder as IconFolder, Delete as IconDelete, Description as IconMeta, Edit as IconEdit, Error as IconError, FindInPage, FormatItalic as IconValueEdit, Info as IconInfo, Link as IconLink, ListAlt as IconEnum, LooksOne as LooksOneIcon, PersonOutlined as IconUser, Publish as PublishIcon, Refresh as RefreshIcon, Router as IconHost, Settings as IconConfig, SettingsApplications as IconSystem, DataObject as IconData, ShowChart as IconChart, SupervisedUserCircle as IconGroup, TextFields as TextFieldsIcon, ViewColumn as IconColumns, Wifi as IconConnection, WifiOff as IconDisconnected, DriveFileRenameOutline, } from '@mui/icons-material';
10
+ import { Add as AddIcon, ArrowRight as ArrowRightIcon, BedroomParent, BorderColor, Build as BuildIcon, CalendarToday as IconSchedule, Check as IconCheck, Close as IconClose, Code as IconScript, Construction, CreateNewFolder as IconFolder, Delete as IconDelete, Description as IconMeta, Edit as IconEdit, Error as IconError, FindInPage, FormatItalic as IconValueEdit, Link as IconLink, ListAlt as IconEnum, LooksOne as LooksOneIcon, PersonOutlined as IconUser, Publish as PublishIcon, Refresh as RefreshIcon, Router as IconHost, Settings as IconConfig, ShowChart as IconChart, SupervisedUserCircle as IconGroup, TextFields as TextFieldsIcon, ViewColumn as IconColumns, Wifi as IconConnection, WifiOff as IconDisconnected, DriveFileRenameOutline, ContentPaste, UploadFile, } from '@mui/icons-material';
12
11
  import { IconExpert } from '../icons/IconExpert';
13
12
  import { IconAdapter } from '../icons/IconAdapter';
14
13
  import { IconChannel } from '../icons/IconChannel';
@@ -28,15 +27,10 @@ import { Utils } from './Utils'; // @iobroker/adapter-react-v5/Components/Utils
28
27
  import { TabContainer } from './TabContainer';
29
28
  import { TabContent } from './TabContent';
30
29
  import { TabHeader } from './TabHeader';
31
- const ICON_SIZE = 24;
32
- const ROW_HEIGHT = 32;
30
+ import { CustomFilterInput, CustomFilterSelect, ButtonIcon, applyFilter, binarySearch, buildTree, findEnumsForObjectAsIds, findFunctionsForObject, findNode, findRoomsForObject, formatValue, generateFile, getCustomValue, getIdFieldTooltip, getName, getObjectTooltip, getSelectIdIconFromObjects, getValueStyle, getVisibleItems, isNonExpertId, setCustomValue, prepareSparkData, COLOR_NAME_USERDATA, COLOR_NAME_ALIAS, COLOR_NAME_JAVASCRIPT, COLOR_NAME_SYSTEM, COLOR_NAME_SYSTEM_ADAPTER, ICON_SIZE, ROW_HEIGHT, styles as utilStyles, } from './objectBrowserUtils';
31
+ export { getSelectIdIconFromObjects };
33
32
  const ITEM_LEVEL = 16;
34
33
  const SMALL_BUTTON_SIZE = 20;
35
- const COLOR_NAME_USERDATA = (themeType) => (themeType === 'dark' ? '#62ff25' : '#37c400');
36
- const COLOR_NAME_ALIAS = (themeType) => (themeType === 'dark' ? '#ee56ff' : '#a204b4');
37
- const COLOR_NAME_JAVASCRIPT = (themeType) => (themeType === 'dark' ? '#fff46e' : '#b89101');
38
- const COLOR_NAME_SYSTEM = (themeType) => (themeType === 'dark' ? '#ff6d69' : '#ff6d69');
39
- const COLOR_NAME_SYSTEM_ADAPTER = (themeType) => (themeType === 'dark' ? '#5773ff' : '#5773ff');
40
34
  const COLOR_NAME_ERROR_DARK = '#ff413c';
41
35
  const COLOR_NAME_ERROR_LIGHT = '#86211f';
42
36
  const COLOR_NAME_CONNECTED_DARK = '#57ff45';
@@ -232,18 +226,6 @@ const styles = {
232
226
  height: SMALL_BUTTON_SIZE,
233
227
  },
234
228
  cellIdIconOwn: {},
235
- cellIdTooltip: {
236
- fontSize: 14,
237
- },
238
- cellIdTooltipLink: {
239
- color: '#7ec2fd',
240
- '&:hover': {
241
- color: '#7ec2fd',
242
- },
243
- '&:visited': {
244
- color: '#7ec2fd',
245
- },
246
- },
247
229
  cellCopyButton: {
248
230
  width: SMALL_BUTTON_SIZE,
249
231
  height: SMALL_BUTTON_SIZE,
@@ -508,9 +490,6 @@ const styles = {
508
490
  height: 24,
509
491
  marginRight: 4,
510
492
  },
511
- selectNone: {
512
- opacity: 0.5,
513
- },
514
493
  itemSelected: (theme) => ({
515
494
  background: `${theme.palette.primary.main} !important`,
516
495
  color: `${Utils.invertColor(theme.palette.primary.main, true)} !important`,
@@ -527,25 +506,6 @@ const styles = {
527
506
  // paddingLeft: 5,
528
507
  fontSize: 16,
529
508
  },
530
- headerCellInput: {
531
- width: 'calc(100% - 5px)',
532
- height: ROW_HEIGHT,
533
- pt: 0,
534
- '& .itemIcon': {
535
- verticalAlign: 'middle',
536
- width: ICON_SIZE,
537
- height: ICON_SIZE,
538
- display: 'inline-block',
539
- },
540
- },
541
- headerCellSelectItem: {
542
- '& .itemIcon': {
543
- width: ICON_SIZE,
544
- height: ICON_SIZE,
545
- mr: '5px',
546
- display: 'inline-block',
547
- },
548
- },
549
509
  visibleButtons: {
550
510
  color: '#2196f3',
551
511
  opacity: 0.7,
@@ -578,1344 +538,131 @@ const styles = {
578
538
  minWidth: 0,
579
539
  },
580
540
  buttonDiv: {
581
- display: 'flex',
582
- height: '100%',
583
- alignItems: 'center',
584
- },
585
- aclText: {
586
- fontSize: 13,
587
- marginTop: 6,
588
- },
589
- rightsObject: {
590
- color: '#55ff55',
591
- paddingLeft: 3,
592
- },
593
- rightsState: {
594
- color: '#86b6ff',
595
- paddingLeft: 3,
596
- },
597
- textCenter: {
598
- padding: 12,
599
- textAlign: 'center',
600
- },
601
- tooltipAccessControl: {
602
- display: 'flex',
603
- flexDirection: 'column',
604
- },
605
- fontSizeTitle: {
606
- '@media screen and (max-width: 465px)': {
607
- '& *': {
608
- fontSize: 12,
609
- },
610
- },
611
- },
612
- draggable: {
613
- cursor: 'copy',
614
- },
615
- nonDraggable: {
616
- cursor: 'no-drop',
617
- },
618
- selectClearButton: {
619
- position: 'absolute',
620
- top: 0,
621
- right: 0,
622
- borderRadius: 5,
623
- backgroundColor: 'background.default',
624
- },
625
- iconDeviceConnected: (theme) => ({
626
- color: theme.palette.mode === 'dark' ? COLOR_NAME_CONNECTED_DARK : COLOR_NAME_CONNECTED_LIGHT,
627
- opacity: 0.8,
628
- position: 'absolute',
629
- top: 4,
630
- right: 32,
631
- width: 20,
632
- }),
633
- iconDeviceDisconnected: (theme) => ({
634
- color: theme.palette.mode === 'dark' ? COLOR_NAME_DISCONNECTED_DARK : COLOR_NAME_DISCONNECTED_LIGHT,
635
- opacity: 0.8,
636
- position: 'absolute',
637
- top: 4,
638
- right: 32,
639
- width: 20,
640
- }),
641
- iconDeviceError: (theme) => ({
642
- color: theme.palette.mode === 'dark' ? COLOR_NAME_ERROR_DARK : COLOR_NAME_ERROR_LIGHT,
643
- opacity: 0.8,
644
- position: 'absolute',
645
- top: 4,
646
- right: 50,
647
- width: 20,
648
- }),
649
- resizeHandle: {
650
- display: 'block',
651
- position: 'absolute',
652
- cursor: 'col-resize',
653
- width: 7,
654
- top: 2,
655
- bottom: 2,
656
- zIndex: 1,
657
- },
658
- resizeHandleRight: {
659
- right: 3,
660
- borderRight: '2px dotted #888',
661
- '&:hover': {
662
- borderColor: '#ccc',
663
- borderRightStyle: 'solid',
664
- },
665
- '&.active': {
666
- borderColor: '#517ea5',
667
- borderRightStyle: 'solid',
668
- },
669
- },
670
- invertedBackground: (theme) => ({
671
- backgroundColor: theme.palette.mode === 'dark' ? '#9a9a9a' : '#565656',
672
- padding: '0 3px',
673
- borderRadius: '2px 0 0 2px',
674
- }),
675
- invertedBackgroundFlex: (theme) => ({
676
- backgroundColor: theme.palette.mode === 'dark' ? '#9a9a9a' : '#565656',
677
- borderRadius: '0 2px 2px 0',
678
- }),
679
- contextMenuEdit: (theme) => ({
680
- color: theme.palette.mode === 'dark' ? '#ffee48' : '#cbb801',
681
- }),
682
- contextMenuEditValue: (theme) => ({
683
- color: theme.palette.mode === 'dark' ? '#5dff45' : '#1cd301',
684
- }),
685
- contextMenuView: (theme) => ({
686
- color: theme.palette.mode === 'dark' ? '#FFF' : '#000',
687
- }),
688
- contextMenuCustom: (theme) => ({
689
- color: theme.palette.mode === 'dark' ? '#42eaff' : '#01bbc2',
690
- }),
691
- contextMenuACL: (theme) => ({
692
- color: theme.palette.mode === 'dark' ? '#e079ff' : '#500070',
693
- }),
694
- contextMenuRoom: (theme) => ({
695
- color: theme.palette.mode === 'dark' ? '#ff9a33' : '#642a00',
696
- }),
697
- contextMenuRole: (theme) => ({
698
- color: theme.palette.mode === 'dark' ? '#ffdb43' : '#562d00',
699
- }),
700
- contextMenuDelete: (theme) => ({
701
- color: theme.palette.mode === 'dark' ? '#ff4f4f' : '#cf0000',
702
- }),
703
- contextMenuKeys: {
704
- marginLeft: 8,
705
- opacity: 0.7,
706
- fontSize: 'smaller',
707
- },
708
- contextMenuWithSubMenu: {
709
- display: 'flex',
710
- },
711
- };
712
- function ButtonIcon(props) {
713
- return (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 436 436", style: props?.style, width: "24", height: "24", className: "admin-button" },
714
- React.createElement("g", { fill: "currentColor" },
715
- React.createElement("path", { d: "m195.23077,24.30769c-36,3 -67,12 -96,26c-49,24 -82,61 -93,104l-3,11l-1,50c0,46 0,49 2,59l5,20c21,58 84,103 165,116c16,3 53,4 70,2c60,-6 111,-28 147,-64c21,-21 36,-49 40,-74a866,866 0 0 0 1,-104c-3,-18 -6,-28 -13,-43c-26,-52 -87,-90 -162,-101c-16,-2 -48,-3 -63,-2l1,0zm60,23c36,5 70,18 95,35c31,20 51,47 59,77c2,7 2,11 2,25c1,15 0,18 -2,26c-19,69 -104,117 -200,114c-47,-2 -90,-15 -124,-38c-31,-20 -51,-47 -59,-77c-3,-11 -4,-32 -2,-43c8,-42 41,-78 91,-101a260,260 0 0 1 140,-19l0,1zm-221,222c21,26 57,49 95,62c81,27 174,14 239,-32c14,-10 31,-27 41,-41c2,-2 2,-2 2,7c-1,23 -16,50 -38,72c-78,74 -233,74 -311,-1a121,121 0 0 1 -39,-76l0,-6l3,4l8,11z" }),
716
- React.createElement("path", { d: "m201.23077,47.30769c-40,3 -79,19 -104,44c-55,55 -38,133 37,171c52,26 122,24 172,-5c30,-17 51,-42 58,-71c3,-11 3,-34 0,-45c-6,-23 -21,-44 -40,-60l-27,-16a184,184 0 0 0 -96,-18zm30,21c56,5 100,35 112,75c4,11 4,30 0,41c-8,25 -26,45 -54,59a166,166 0 0 1 -160,-8a98,98 0 0 1 -41,-53c-5,-18 -2,-39 8,-57c23,-39 79,-62 135,-57z" }))));
717
- }
718
- /** Converts ioB pattern into regex */
719
- export function pattern2RegEx(pattern) {
720
- pattern = (pattern || '').toString();
721
- const startsWithWildcard = pattern[0] === '*';
722
- const endsWithWildcard = pattern[pattern.length - 1] === '*';
723
- pattern = pattern.replace(/[-/\\^$+?.()|[\]{}]/g, '\\$&').replace(/\*/g, '.*');
724
- return (startsWithWildcard ? '' : '^') + pattern + (endsWithWildcard ? '' : '$');
725
- }
726
- /**
727
- * Function that walks through all keys of an object or array and applies a function to each key.
728
- */
729
- function walkThroughArray(object, iteratee) {
730
- const copiedObject = [];
731
- for (let index = 0; index < object.length; index++) {
732
- iteratee(copiedObject, object[index], index);
733
- }
734
- return copiedObject;
735
- }
736
- /**
737
- * Function that walks through all keys of an object or array and applies a function to each key.
738
- */
739
- function walkThroughObject(object, iteratee) {
740
- const copiedObject = {};
741
- for (const key in object) {
742
- if (Object.prototype.hasOwnProperty.call(object, key)) {
743
- iteratee(copiedObject, object[key], key);
744
- }
745
- }
746
- return copiedObject;
747
- }
748
- /**
749
- * Function to reduce an object primarily by a given list of keys
750
- */
751
- function filterObject(
752
- /** The objects which should be filtered */
753
- obj,
754
- /** The keys which should be excluded */
755
- filterKeys,
756
- /** Whether translations should be reduced to only the english value */
757
- excludeTranslations) {
758
- if (Array.isArray(obj)) {
759
- return walkThroughArray(obj, (result, value, key) => {
760
- if (value === undefined || value === null) {
761
- return;
762
- }
763
- // if the key is an object, run it through the inner function - omitFromObject
764
- const isObject = typeof value === 'object';
765
- if (excludeTranslations && isObject) {
766
- if (typeof value.en === 'string' && typeof value.de === 'string') {
767
- result[key] = value.en;
768
- return;
769
- }
770
- }
771
- result[key] = isObject ? filterObject(value, filterKeys, excludeTranslations) : value;
772
- });
773
- }
774
- return walkThroughObject(obj, (result, value, key) => {
775
- if (value === undefined || value === null) {
776
- return;
777
- }
778
- if (filterKeys.includes(key)) {
779
- return;
780
- }
781
- // if the key is an object, run it through the inner function - omitFromObject
782
- const isObject = typeof value === 'object';
783
- if (excludeTranslations && isObject) {
784
- if (typeof value.en === 'string' && typeof value.de === 'string') {
785
- result[key] = value.en;
786
- return;
787
- }
788
- }
789
- result[key] = isObject ? filterObject(value, filterKeys, excludeTranslations) : value;
790
- });
791
- }
792
- // It is an export function and used somewhere else
793
- export function filterRoles(roleArray, type, defaultRoles) {
794
- const bigRoleArray = [];
795
- roleArray.forEach(role => (role.type === 'mixed' || role.type) === type &&
796
- !bigRoleArray.includes(role.role) &&
797
- bigRoleArray.push(role.role));
798
- defaultRoles?.forEach(role => (role.type === 'mixed' || role.type) === type &&
799
- !bigRoleArray.includes(role.role) &&
800
- bigRoleArray.push(role.role));
801
- bigRoleArray.sort();
802
- return bigRoleArray;
803
- }
804
- /**
805
- * Function to generate a json-file for an object and trigger download it
806
- */
807
- function generateFile(
808
- /** The desired filename */
809
- fileName,
810
- /** The objects which should be downloaded */
811
- obj,
812
- /** Options to filter/reduce the output */
813
- options) {
814
- const el = document.createElement('a');
815
- const filterKeys = [];
816
- if (options.excludeSystemRepositories) {
817
- filterKeys.push('system.repositories');
818
- }
819
- const filteredObject = filterKeys.length > 0 || options.excludeTranslations
820
- ? filterObject(obj, filterKeys, options.excludeTranslations)
821
- : obj;
822
- const data = options.beautify ? JSON.stringify(filteredObject, null, 2) : JSON.stringify(filteredObject);
823
- el.setAttribute('href', `data:application/json;charset=utf-8,${encodeURIComponent(data)}`);
824
- el.setAttribute('download', fileName);
825
- el.style.display = 'none';
826
- document.body.appendChild(el);
827
- el.click();
828
- document.body.removeChild(el);
829
- }
830
- class CustomFilterSelect extends Component {
831
- hasIcons;
832
- timer = null;
833
- constructor(props) {
834
- super(props);
835
- this.state = {
836
- value: props.initialValue || [],
837
- };
838
- this.hasIcons = !!props.values?.find(item => item.icon);
839
- }
840
- componentWillUnmount() {
841
- if (this.timer) {
842
- clearTimeout(this.timer);
843
- }
844
- }
845
- render() {
846
- return (React.createElement("div", { style: { position: 'relative' } },
847
- React.createElement(Select, { variant: "standard", key: this.props.name, sx: styles.headerCellInput, className: "no-underline", multiple: true, renderValue: value => {
848
- if (!value?.length) {
849
- return this.props.name === 'custom'
850
- ? this.props.texts.showAll
851
- : this.props.texts[`filter_${this.props.name}`];
852
- }
853
- return value.map(val => {
854
- const item = this.props.values.find(i => typeof i === 'object' ? i.value === val : i === val);
855
- let id;
856
- let _name;
857
- let icon;
858
- if (typeof item === 'object') {
859
- id = item.value;
860
- _name = item.name;
861
- icon = item.icon;
862
- }
863
- else {
864
- id = item;
865
- _name = item;
866
- }
867
- return (React.createElement(Box, { component: "span", sx: styles.headerCellSelectItem, key: id },
868
- icon || (this.hasIcons ? React.createElement("div", { className: "itemIcon" }) : null),
869
- _name));
870
- });
871
- }, value: this.state.value, onChange: event => {
872
- let selectedValues = event.target.value;
873
- // '_' may be selected only alone
874
- if (this.state.value[0] === '_' && selectedValues.includes('_') && selectedValues.length > 1) {
875
- const pos = selectedValues.indexOf('_');
876
- if (pos !== -1) {
877
- selectedValues.splice(pos, 1);
878
- }
879
- }
880
- else if (this.state.value[0] !== '_' && selectedValues.includes('_')) {
881
- selectedValues = ['_'];
882
- }
883
- // '_' may be selected only alone
884
- if (selectedValues.includes('')) {
885
- selectedValues = [];
886
- }
887
- this.setState({ value: selectedValues }, () => {
888
- if (this.timer) {
889
- clearTimeout(this.timer);
890
- }
891
- this.timer = setTimeout(() => {
892
- this.timer = null;
893
- this.props.onChange(this.props.name, selectedValues);
894
- }, 400);
895
- });
896
- }, onClose: () => {
897
- if (this.timer) {
898
- clearTimeout(this.timer);
899
- this.timer = null;
900
- this.props.onChange(this.props.name, this.state.value);
901
- }
902
- }, inputProps: { name: this.props.name, id: this.props.name }, displayEmpty: true },
903
- React.createElement(MenuItem, { key: "empty", value: "" },
904
- React.createElement("span", { style: styles.selectNone }, this.props.name === 'custom'
905
- ? this.props.texts.showAll
906
- : this.props.texts[`filter_${this.props.name}`])),
907
- this.props.values?.map(item => {
908
- let id;
909
- let _name;
910
- let icon;
911
- if (typeof item === 'object') {
912
- id = item.value;
913
- _name = item.name;
914
- icon = item.icon;
915
- }
916
- else {
917
- id = item;
918
- _name = item;
919
- }
920
- return (React.createElement(MenuItem, { sx: styles.headerCellSelectItem, key: id, value: id },
921
- icon || (this.hasIcons ? React.createElement("div", { className: "itemIcon" }) : null),
922
- _name));
923
- })),
924
- this.state.value.length ? (React.createElement(Box, { component: "div", sx: styles.selectClearButton },
925
- React.createElement(IconButton, { size: "small", onClick: () => {
926
- if (this.timer) {
927
- clearTimeout(this.timer);
928
- this.timer = null;
929
- }
930
- this.setState({ value: [] }, () => this.props.onChange(this.props.name, undefined));
931
- } },
932
- React.createElement(IconClose, null)))) : null));
933
- }
934
- }
935
- class CustomFilterInput extends Component {
936
- timer = null;
937
- constructor(props) {
938
- super(props);
939
- this.state = {
940
- value: props.initialValue || '',
941
- };
942
- }
943
- componentWillUnmount() {
944
- if (this.timer) {
945
- clearTimeout(this.timer);
946
- }
947
- }
948
- render() {
949
- return (React.createElement(FormControl, { sx: this.props.styles, key: this.props.name, title: this.props.t('ra_You can use * as wildcard'), margin: "dense" },
950
- React.createElement(Input, { classes: { underline: 'no-underline' }, id: this.props.name, placeholder: this.props.texts[`filter_${this.props.name}`], value: this.state.value, onChange: event => {
951
- const selectedValues = event.target.value;
952
- this.setState({ value: selectedValues }, () => {
953
- if (this.timer) {
954
- clearTimeout(this.timer);
955
- }
956
- this.timer = setTimeout(() => {
957
- this.timer = null;
958
- this.props.onChange(this.props.name, selectedValues);
959
- }, 400);
960
- });
961
- }, onBlur: () => {
962
- if (this.timer) {
963
- clearTimeout(this.timer);
964
- this.timer = null;
965
- this.props.onChange(this.props.name, this.state.value);
966
- }
967
- }, autoComplete: "off" }),
968
- this.state.value ? (React.createElement("div", { style: {
969
- position: 'absolute',
970
- right: 0,
971
- } },
972
- React.createElement(IconButton, { size: "small", onClick: () => {
973
- if (this.timer) {
974
- clearTimeout(this.timer);
975
- this.timer = null;
976
- }
977
- this.setState({ value: '' }, () => this.props.onChange(this.props.name, undefined));
978
- } },
979
- React.createElement(IconClose, null)))) : null));
980
- }
981
- }
982
- // d=data, t=target, s=start, e=end, m=middle
983
- function binarySearch(list, find, _start, _end) {
984
- _start ||= 0;
985
- if (_end === undefined) {
986
- _end = list.length - 1;
987
- if (!_end) {
988
- return list[0] === find;
989
- }
990
- }
991
- const middle = Math.floor((_start + _end) / 2);
992
- if (find === list[middle]) {
993
- return true;
994
- }
995
- if (_end - 1 === _start) {
996
- return list[_start] === find || list[_end] === find;
997
- }
998
- if (find > list[middle]) {
999
- return binarySearch(list, find, middle, _end);
1000
- }
1001
- if (find < list[middle]) {
1002
- return binarySearch(list, find, _start, middle);
1003
- }
1004
- return false;
1005
- }
1006
- function getName(name, lang) {
1007
- if (typeof name === 'object') {
1008
- if (!name) {
1009
- return '';
1010
- }
1011
- return (name[lang] || name.en || '').toString();
1012
- }
1013
- return name ? name.toString() : '';
1014
- }
1015
- export function getSelectIdIconFromObjects(objects, id, lang, imagePrefix) {
1016
- // `admin` has prefix '.' and `web` has '../..'
1017
- imagePrefix ||= '.'; // http://localhost:8081';
1018
- let src = '';
1019
- const _id_ = `system.adapter.${id}`;
1020
- const aIcon = id && objects[_id_]?.common?.icon;
1021
- if (aIcon) {
1022
- // if not BASE64
1023
- if (!aIcon.startsWith('data:image/')) {
1024
- if (aIcon.includes('.')) {
1025
- const name = objects[_id_].common.name;
1026
- if (typeof name === 'object') {
1027
- src = `${imagePrefix}/adapter/${name[lang] || name.en}/${aIcon}`;
1028
- }
1029
- else {
1030
- src = `${imagePrefix}/adapter/${name}/${aIcon}`;
1031
- }
1032
- }
1033
- else if (aIcon && aIcon.length < 3) {
1034
- return aIcon; // utf-8
1035
- }
1036
- else {
1037
- return null; // '<i class="material-icons iob-list-icon">' + objects[_id_].common.icon + '</i>';
1038
- }
1039
- }
1040
- else if (aIcon.startsWith('data:image/svg')) {
1041
- src = (React.createElement(SVG, { className: "iconOwn", src: aIcon, width: 28, height: 28 }));
1042
- }
1043
- else {
1044
- src = aIcon;
1045
- }
1046
- }
1047
- else {
1048
- const common = objects[id] && objects[id].common;
1049
- if (common) {
1050
- const cIcon = common.icon;
1051
- if (cIcon) {
1052
- if (!cIcon.startsWith('data:image/')) {
1053
- if (cIcon.includes('.')) {
1054
- let instance;
1055
- if (objects[id].type === 'instance' || objects[id].type === 'adapter') {
1056
- if (typeof common.name === 'object') {
1057
- src = `${imagePrefix}/adapter/${common.name[lang] || common.name.en}/${cIcon}`;
1058
- }
1059
- else {
1060
- src = `${imagePrefix}/adapter/${common.name}/${cIcon}`;
1061
- }
1062
- }
1063
- else if (id && id.startsWith('system.adapter.')) {
1064
- instance = id.split('.', 3);
1065
- if (cIcon[0] === '/') {
1066
- instance[2] += cIcon;
1067
- }
1068
- else {
1069
- instance[2] += `/${cIcon}`;
1070
- }
1071
- src = `${imagePrefix}/adapter/${instance[2]}`;
1072
- }
1073
- else {
1074
- instance = id.split('.', 2);
1075
- if (cIcon[0] === '/') {
1076
- instance[0] += cIcon;
1077
- }
1078
- else {
1079
- instance[0] += `/${cIcon}`;
1080
- }
1081
- src = `${imagePrefix}/adapter/${instance[0]}`;
1082
- }
1083
- }
1084
- else if (aIcon && aIcon.length < 3) {
1085
- return aIcon; // utf-8
1086
- }
1087
- else {
1088
- return null;
1089
- }
1090
- }
1091
- else if (cIcon.startsWith('data:image/svg')) {
1092
- // if base 64 image
1093
- src = (React.createElement(SVG, { className: "iconOwn", src: cIcon, width: 28, height: 28 }));
1094
- }
1095
- else {
1096
- src = cIcon;
1097
- }
1098
- }
1099
- }
1100
- }
1101
- return src || null;
1102
- }
1103
- function applyFilter(item, filters, lang, objects, context, counter, customFilter, selectedTypes, _depth) {
1104
- _depth ||= 0;
1105
- let filteredOut = false;
1106
- if (!context) {
1107
- context = {};
1108
- if (filters.id) {
1109
- const id = filters.id.toLowerCase();
1110
- if (id.includes('*')) {
1111
- context.idRx = new RegExp(pattern2RegEx(filters.id), 'i');
1112
- }
1113
- else {
1114
- context.id = id;
1115
- }
1116
- }
1117
- if (filters.name) {
1118
- const name = filters.name.toLowerCase();
1119
- if (name.includes('*')) {
1120
- context.nameRx = new RegExp(pattern2RegEx(name), 'i');
1121
- }
1122
- else {
1123
- context.name = name;
1124
- }
1125
- }
1126
- if (filters.type?.length) {
1127
- context.type = filters.type.map(f => f.toLowerCase());
1128
- }
1129
- if (filters.custom?.length) {
1130
- context.custom = filters.custom.map(c => c.toLowerCase());
1131
- }
1132
- if (filters.role?.length) {
1133
- context.role = filters.role.map(r => r.toLowerCase());
1134
- }
1135
- if (filters.room?.length) {
1136
- context.room = [];
1137
- filters.room.forEach(room => {
1138
- context.room = context.room.concat(objects[room]?.common?.members || []);
1139
- });
1140
- }
1141
- if (filters.func?.length) {
1142
- context.func = [];
1143
- filters.func.forEach(func => {
1144
- context.func = context.func.concat(objects[func]?.common?.members || []);
1145
- });
1146
- }
1147
- }
1148
- const data = item.data;
1149
- if (data?.id) {
1150
- const common = data.obj?.common;
1151
- if (customFilter) {
1152
- if (customFilter.type) {
1153
- if (typeof customFilter.type === 'string') {
1154
- if (!data.obj || customFilter.type !== data.obj.type) {
1155
- filteredOut = true;
1156
- }
1157
- }
1158
- else if (Array.isArray(customFilter.type)) {
1159
- if (!data.obj || !customFilter.type.includes(data.obj.type)) {
1160
- filteredOut = true;
1161
- }
1162
- }
1163
- }
1164
- if (!filteredOut && customFilter.common?.type) {
1165
- if (!common?.type) {
1166
- filteredOut = true;
1167
- }
1168
- else if (typeof customFilter.common.type === 'string') {
1169
- if (customFilter.common.type !== common.type) {
1170
- filteredOut = true;
1171
- }
1172
- }
1173
- else if (Array.isArray(customFilter.common.type)) {
1174
- if (!customFilter.common.type.includes(common.type)) {
1175
- filteredOut = true;
1176
- }
1177
- }
1178
- }
1179
- if (!filteredOut && customFilter.common?.role) {
1180
- if (!common?.role) {
1181
- filteredOut = true;
1182
- }
1183
- else if (typeof customFilter.common.role === 'string') {
1184
- if (common.role.startsWith(customFilter.common.role)) {
1185
- filteredOut = true;
1186
- }
1187
- }
1188
- else if (Array.isArray(customFilter.common.role)) {
1189
- if (!customFilter.common.role.find(role => common.role.startsWith(role))) {
1190
- filteredOut = true;
1191
- }
1192
- }
1193
- }
1194
- if (!filteredOut && customFilter.common?.custom === '_' && common?.custom) {
1195
- filteredOut = true;
1196
- }
1197
- else if (!filteredOut && customFilter.common?.custom && customFilter.common?.custom !== '_') {
1198
- const filterOfCustom = customFilter.common.custom;
1199
- if (!common?.custom) {
1200
- filteredOut = true;
1201
- }
1202
- else if (filterOfCustom === '_dataSources') {
1203
- // TODO: make it configurable
1204
- if (!Object.keys(common.custom).find(id => id.startsWith('history.') || id.startsWith('sql.') || id.startsWith('influxdb.'))) {
1205
- filteredOut = true;
1206
- }
1207
- }
1208
- else if (Array.isArray(filterOfCustom)) {
1209
- // here are ['influxdb.', 'telegram.']
1210
- const customs = Object.keys(common.custom); // here are ['influxdb.0', 'telegram.2']
1211
- if (filterOfCustom.find(cst => customs.find(id => id.startsWith(cst)))) {
1212
- filteredOut = true;
1213
- }
1214
- }
1215
- else if (filterOfCustom !== true &&
1216
- !Object.keys(common.custom).find(id => id.startsWith(filterOfCustom))) {
1217
- filteredOut = true;
1218
- }
1219
- }
1220
- }
1221
- if (!filteredOut && !filters.expertMode) {
1222
- filteredOut =
1223
- data.id === 'system' ||
1224
- data.id === 'enum' ||
1225
- // (data.obj && data.obj.type === 'meta') ||
1226
- data.id.startsWith('system.') ||
1227
- data.id.startsWith('enum.') ||
1228
- data.id.startsWith('_design/') ||
1229
- data.id.endsWith('.admin') ||
1230
- !!common?.expert;
1231
- }
1232
- if (!filteredOut && context.id) {
1233
- if (data.fID === undefined) {
1234
- data.fID = data.id.toLowerCase();
1235
- }
1236
- filteredOut = !data.fID.includes(context.id);
1237
- }
1238
- if (!filteredOut && context.idRx) {
1239
- filteredOut = !context.idRx.test(data.id);
1240
- }
1241
- if (!filteredOut && context.name) {
1242
- if (common) {
1243
- if (data.fName === undefined) {
1244
- data.fName = getName(common.name, lang) || '';
1245
- data.fName = data.fName.toLowerCase();
1246
- }
1247
- filteredOut = !data.fName.includes(context.name);
1248
- }
1249
- else {
1250
- filteredOut = true;
1251
- }
1252
- }
1253
- if (!filteredOut && context.nameRx) {
1254
- if (common) {
1255
- if (data.fName === undefined) {
1256
- data.fName = getName(common.name, lang) || '';
1257
- data.fName = data.fName.toLowerCase();
1258
- }
1259
- filteredOut = !context.nameRx.test(data.fName);
1260
- }
1261
- }
1262
- if (!filteredOut && filters.role?.length && common) {
1263
- filteredOut = !(typeof common.role === 'string' && context.role.find(role => common.role.startsWith(role)));
1264
- }
1265
- if (!filteredOut && context.room?.length) {
1266
- filteredOut = !context.room.find(id => id === data.id || data.id.startsWith(`${id}.`));
1267
- }
1268
- if (!filteredOut && context.func?.length) {
1269
- filteredOut = !context.func.find(id => id === data.id || data.id.startsWith(`${id}.`));
1270
- }
1271
- if (!filteredOut && context.type?.length) {
1272
- filteredOut = !(data.obj?.type && context.type.includes(data.obj.type));
1273
- }
1274
- if (!filteredOut && selectedTypes) {
1275
- filteredOut = !(data.obj?.type && selectedTypes.includes(data.obj.type));
1276
- }
1277
- if (!filteredOut && context.custom?.length) {
1278
- if (common) {
1279
- if (context.custom[0] === '_') {
1280
- filteredOut = !!common.custom;
1281
- }
1282
- else if (common.custom) {
1283
- filteredOut = !context.custom.find(custom => common.custom[custom]);
1284
- }
1285
- else {
1286
- filteredOut = true;
1287
- }
1288
- }
1289
- else {
1290
- filteredOut = context.custom[0] !== '_';
1291
- }
1292
- }
1293
- }
1294
- data.visible = !filteredOut;
1295
- data.hasVisibleChildren = false;
1296
- if (item.children && _depth < 20) {
1297
- item.children.forEach(_item => {
1298
- const visible = applyFilter(_item, filters, lang, objects, context, counter, customFilter, selectedTypes, _depth + 1);
1299
- if (visible) {
1300
- data.hasVisibleChildren = true;
1301
- }
1302
- });
1303
- }
1304
- // const visible = data.visible || data.hasVisibleChildren;
1305
- data.sumVisibility = data.visible || data.hasVisibleChildren; // || data.hasVisibleParent;
1306
- if (counter && data.sumVisibility) {
1307
- counter.count++;
1308
- }
1309
- // show all children of visible object with opacity 0.5
1310
- if (data.id && data.sumVisibility && item.children) {
1311
- item.children.forEach(_item => (_item.data.hasVisibleParent = true));
1312
- }
1313
- return data.visible || data.hasVisibleChildren;
1314
- }
1315
- function getVisibleItems(item, type, objects, _result) {
1316
- _result ||= [];
1317
- const data = item.data;
1318
- if (data.sumVisibility) {
1319
- if (data.id && objects[data.id] && (!type || objects[data.id].type === type)) {
1320
- _result.push(data.id);
1321
- }
1322
- item.children?.forEach(_item => getVisibleItems(_item, type, objects, _result));
1323
- }
1324
- return _result;
1325
- }
1326
- function getSystemIcon(objects, id, level, themeType, lang, imagePrefix) {
1327
- let icon;
1328
- // system or design has special icons
1329
- if (id === 'alias' || id === 'alias.0') {
1330
- icon = (React.createElement(IconLink, { className: "iconOwn", style: { color: COLOR_NAME_ALIAS(themeType) } }));
1331
- }
1332
- else if (id === '0_userdata' || id === '0_userdata.0') {
1333
- icon = (React.createElement(IconData, { className: "iconOwn", style: { color: COLOR_NAME_USERDATA(themeType) } }));
1334
- }
1335
- else if (id.startsWith('_design/') || id === 'system') {
1336
- icon = (React.createElement(IconSystem, { className: "iconOwn", style: { color: COLOR_NAME_SYSTEM(themeType) } }));
1337
- }
1338
- else if (id === 'system.adapter') {
1339
- icon = (React.createElement(IconSystem, { className: "iconOwn", style: { color: COLOR_NAME_SYSTEM_ADAPTER(themeType) } }));
1340
- }
1341
- else if (id === 'system.group') {
1342
- icon = React.createElement(IconGroup, { className: "iconOwn" });
1343
- }
1344
- else if (id === 'system.user') {
1345
- icon = React.createElement(IconUser, { className: "iconOwn" });
1346
- }
1347
- else if (id === 'system.host') {
1348
- icon = React.createElement(IconHost, { className: "iconOwn" });
1349
- }
1350
- else if (id.endsWith('.connection') || id.endsWith('.connected')) {
1351
- icon = React.createElement(IconConnection, { className: "iconOwn" });
1352
- }
1353
- else if (id.endsWith('.info')) {
1354
- icon = React.createElement(IconInfo, { className: "iconOwn" });
1355
- }
1356
- else if (objects[id] && objects[id].type === 'meta') {
1357
- icon = React.createElement(IconMeta, { className: "iconOwn" });
1358
- }
1359
- else if (level < 2) {
1360
- // detect "cloud.0"
1361
- if (objects[`system.adapter.${id}`]) {
1362
- icon = getSelectIdIconFromObjects(objects, `system.adapter.${id}`, lang, imagePrefix);
1363
- }
1364
- }
1365
- return icon || null;
1366
- }
1367
- function getObjectTooltip(data, lang) {
1368
- if (data?.obj?.common?.desc) {
1369
- return getName(data.obj.common.desc, lang) || null;
1370
- }
1371
- return null;
1372
- }
1373
- function getIdFieldTooltip(data, lang) {
1374
- const tooltip = getObjectTooltip(data, lang);
1375
- if (tooltip?.startsWith('http')) {
1376
- return (React.createElement(Box, { component: "a", sx: styles.cellIdTooltipLink, href: tooltip, target: "_blank", rel: "noreferrer" }, tooltip));
1377
- }
1378
- return React.createElement("span", { style: styles.cellIdTooltip }, tooltip || data.id || '');
1379
- }
1380
- function buildTree(objects, options) {
1381
- const imagePrefix = options.imagePrefix || '.';
1382
- let ids = Object.keys(objects);
1383
- ids.sort((a, b) => {
1384
- if (a === b) {
1385
- return 0;
1386
- }
1387
- a = a.replace(/\./g, '!!!');
1388
- b = b.replace(/\./g, '!!!');
1389
- if (a > b) {
1390
- return 1;
1391
- }
1392
- return -1;
1393
- });
1394
- if (options.root) {
1395
- ids = ids.filter(id => id === options.root || id.startsWith(`${options.root}.`));
1396
- }
1397
- // find empty nodes and create names for it
1398
- let currentPathArr = [];
1399
- let currentPath = '';
1400
- let currentPathLen = 0;
1401
- const root = {
1402
- data: {
1403
- name: '',
1404
- id: '',
1405
- },
1406
- children: [],
1407
- };
1408
- const info = {
1409
- funcEnums: [],
1410
- roomEnums: [],
1411
- roles: [],
1412
- ids: [],
1413
- types: [],
1414
- objects,
1415
- customs: ['_'],
1416
- enums: [],
1417
- hasSomeCustoms: false,
1418
- aliasesMap: {},
1419
- };
1420
- let cRoot = root;
1421
- for (let i = 0; i < ids.length; i++) {
1422
- const id = ids[i];
1423
- if (!id) {
1424
- continue;
1425
- }
1426
- const obj = objects[id];
1427
- const parts = id.split('.');
1428
- if (obj.type && !info.types.includes(obj.type)) {
1429
- info.types.push(obj.type);
1430
- }
1431
- if (obj) {
1432
- const common = obj.common;
1433
- const role = common?.role;
1434
- if (role && !info.roles.find(it => it.role === role)) {
1435
- if (typeof role !== 'string') {
1436
- console.warn(`Invalid role type "${typeof role}" in "${obj._id}"`);
1437
- }
1438
- else {
1439
- info.roles.push({ role, type: common.type });
1440
- }
1441
- }
1442
- else if (id.startsWith('enum.rooms.')) {
1443
- info.roomEnums.push(id);
1444
- info.enums.push(id);
1445
- }
1446
- else if (id.startsWith('enum.functions.')) {
1447
- info.funcEnums.push(id);
1448
- info.enums.push(id);
1449
- }
1450
- else if (obj.type === 'enum') {
1451
- info.enums.push(id);
1452
- }
1453
- else if (obj.type === 'instance' && common && (common.supportCustoms || common.adminUI?.custom)) {
1454
- info.hasSomeCustoms = true;
1455
- info.customs.push(id.substring('system.adapter.'.length));
1456
- }
1457
- // Build a map of aliases
1458
- if (id.startsWith('alias.') && obj.common.alias?.id) {
1459
- if (typeof obj.common.alias.id === 'string') {
1460
- const usedId = obj.common.alias.id;
1461
- if (!info.aliasesMap[usedId]) {
1462
- info.aliasesMap[usedId] = [id];
1463
- }
1464
- else if (!info.aliasesMap[usedId].includes(id)) {
1465
- info.aliasesMap[usedId].push(id);
1466
- }
1467
- }
1468
- else {
1469
- const readId = obj.common.alias.id.read;
1470
- if (readId) {
1471
- if (!info.aliasesMap[readId]) {
1472
- info.aliasesMap[readId] = [id];
1473
- }
1474
- else if (!info.aliasesMap[readId].includes(id)) {
1475
- info.aliasesMap[readId].push(id);
1476
- }
1477
- }
1478
- const writeId = obj.common.alias.id.write;
1479
- if (writeId) {
1480
- if (!info.aliasesMap[writeId]) {
1481
- info.aliasesMap[writeId] = [id];
1482
- }
1483
- else if (!info.aliasesMap[writeId].includes(id)) {
1484
- info.aliasesMap[writeId].push(id);
1485
- }
1486
- }
1487
- }
1488
- }
1489
- }
1490
- info.ids.push(id);
1491
- let repeat;
1492
- // if next level
1493
- do {
1494
- repeat = false;
1495
- // If the current level is still OK, and we can add ID to children
1496
- if (!currentPath || id.startsWith(`${currentPath}.`)) {
1497
- // if more than one level added
1498
- if (parts.length - currentPathLen > 1) {
1499
- let curPath = currentPath;
1500
- // generate missing levels
1501
- for (let k = currentPathLen; k < parts.length - 1; k++) {
1502
- curPath += (curPath ? '.' : '') + parts[k];
1503
- // level does not exist
1504
- if (!binarySearch(info.ids, curPath)) {
1505
- const _cRoot = {
1506
- data: {
1507
- name: parts[k],
1508
- parent: cRoot,
1509
- id: curPath,
1510
- obj: objects[curPath],
1511
- level: k,
1512
- icon: getSystemIcon(objects, curPath, k, options.themeType, options.lang, imagePrefix),
1513
- generated: true,
1514
- },
1515
- };
1516
- cRoot.children ||= [];
1517
- cRoot.children.push(_cRoot);
1518
- cRoot = _cRoot;
1519
- info.ids.push(curPath); // IDs will be added by alphabet
1520
- }
1521
- else if (cRoot.children) {
1522
- cRoot = cRoot.children.find(item => item.data.name === parts[k]);
1523
- }
1524
- }
1525
- }
1526
- const _cRoot = {
1527
- data: {
1528
- name: parts[parts.length - 1],
1529
- title: getName(obj?.common?.name, options.lang),
1530
- obj,
1531
- parent: cRoot,
1532
- icon: getSelectIdIconFromObjects(objects, id, options.lang, imagePrefix) ||
1533
- getSystemIcon(objects, id, 0, options.themeType, options.lang, imagePrefix),
1534
- id,
1535
- hasCustoms: !!(obj.common?.custom && Object.keys(obj.common.custom).length),
1536
- level: parts.length - 1,
1537
- generated: false,
1538
- button: obj.type === 'state' &&
1539
- !!obj.common?.role &&
1540
- typeof obj.common.role === 'string' &&
1541
- obj.common.role.startsWith('button') &&
1542
- obj.common?.write !== false,
1543
- switch: obj.type === 'state' &&
1544
- obj.common?.type === 'boolean' &&
1545
- obj.common?.write !== false &&
1546
- obj.common?.read !== false,
1547
- url: !!obj.common?.role &&
1548
- typeof obj.common.role === 'string' &&
1549
- obj.common.role.startsWith('url'),
1550
- },
1551
- };
1552
- cRoot.children ||= [];
1553
- cRoot.children.push(_cRoot);
1554
- cRoot = _cRoot;
1555
- currentPathLen = parts.length;
1556
- currentPathArr = parts;
1557
- currentPath = id;
1558
- }
1559
- else {
1560
- let u = 0;
1561
- while (currentPathArr[u] === parts[u]) {
1562
- u++;
1563
- }
1564
- if (u > 0) {
1565
- let move = currentPathArr.length;
1566
- currentPathArr = currentPathArr.splice(0, u);
1567
- currentPathLen = u;
1568
- currentPath = currentPathArr.join('.');
1569
- while (move > u) {
1570
- if (cRoot.data.parent) {
1571
- cRoot = cRoot.data.parent;
1572
- }
1573
- else {
1574
- console.error(`Parent is null for ${id} ${currentPath} ${currentPathArr.join('.')}`);
1575
- }
1576
- move--;
1577
- }
1578
- }
1579
- else {
1580
- cRoot = root;
1581
- currentPathArr = [];
1582
- currentPath = '';
1583
- currentPathLen = 0;
1584
- }
1585
- repeat = true;
1586
- }
1587
- } while (repeat);
1588
- }
1589
- info.roomEnums.sort((a, b) => {
1590
- const aName = getName(objects[a]?.common?.name, options.lang) || a.split('.').pop();
1591
- const bName = getName(objects[b]?.common?.name, options.lang) || b.split('.').pop();
1592
- if (aName > bName) {
1593
- return 1;
1594
- }
1595
- if (aName < bName) {
1596
- return -1;
1597
- }
1598
- return 0;
1599
- });
1600
- info.funcEnums.sort((a, b) => {
1601
- const aName = getName(objects[a]?.common?.name, options.lang) || a.split('.').pop();
1602
- const bName = getName(objects[b]?.common?.name, options.lang) || b.split('.').pop();
1603
- if (aName > bName) {
1604
- return 1;
1605
- }
1606
- if (aName < bName) {
1607
- return -1;
1608
- }
1609
- return 0;
1610
- });
1611
- info.roles.sort((a, b) => a.role.localeCompare(b.role));
1612
- info.types.sort();
1613
- return { info, root };
1614
- }
1615
- function findNode(root, id, _parts, _path, _level) {
1616
- if (root.data.id === id) {
1617
- return root;
1618
- }
1619
- if (!_parts) {
1620
- _parts = id.split('.');
1621
- _level = 0;
1622
- _path = _parts[_level];
1623
- }
1624
- if (!root.children && root.data.id !== id) {
1625
- return null;
1626
- }
1627
- let found;
1628
- if (root.children) {
1629
- for (let i = 0; i < root.children.length; i++) {
1630
- const _id = root.children[i].data.id;
1631
- if (_id === _path) {
1632
- found = root.children[i];
1633
- break;
1634
- }
1635
- else if (_id > _path) {
1636
- break;
1637
- }
1638
- }
1639
- }
1640
- if (found) {
1641
- _level ||= 0;
1642
- return findNode(found, id, _parts, `${_path}.${_parts[_level + 1]}`, _level + 1);
1643
- }
1644
- return null;
1645
- }
1646
- function findRoomsForObject(info, id, lang, rooms) {
1647
- if (!id) {
1648
- return { rooms: [], per: false };
1649
- }
1650
- rooms ||= [];
1651
- for (const room of info.roomEnums) {
1652
- const common = info.objects[room]?.common;
1653
- if (!common) {
1654
- continue;
1655
- }
1656
- const name = getName(common.name, lang);
1657
- if (common.members?.includes(id) && !rooms.includes(name)) {
1658
- rooms.push(name);
1659
- }
1660
- }
1661
- let ownEnums;
1662
- // Check parent
1663
- const parts = id.split('.');
1664
- parts.pop();
1665
- id = parts.join('.');
1666
- if (info.objects[id]) {
1667
- ownEnums = rooms.length;
1668
- findRoomsForObject(info, id, lang, rooms);
1669
- }
1670
- return { rooms, per: !ownEnums }; // per is if the enums are from parent
1671
- }
1672
- function findEnumsForObjectAsIds(info, id, enumName, funcs) {
1673
- if (!id) {
1674
- return [];
1675
- }
1676
- funcs ||= [];
1677
- for (let i = 0; i < info[enumName].length; i++) {
1678
- const common = info.objects[info[enumName][i]]?.common;
1679
- if (common?.members?.includes(id) && !funcs.includes(info[enumName][i])) {
1680
- funcs.push(info[enumName][i]);
1681
- }
1682
- }
1683
- funcs.sort();
1684
- return funcs;
1685
- }
1686
- function findFunctionsForObject(info, id, lang, funcs) {
1687
- if (!id) {
1688
- return { funcs: [], pef: false };
1689
- }
1690
- funcs ||= [];
1691
- for (let i = 0; i < info.funcEnums.length; i++) {
1692
- const common = info.objects[info.funcEnums[i]]?.common;
1693
- if (!common) {
1694
- continue;
1695
- }
1696
- const name = getName(common.name, lang);
1697
- if (common.members?.includes(id) && !funcs.includes(name)) {
1698
- funcs.push(name);
1699
- }
1700
- }
1701
- let ownEnums;
1702
- // Check parent
1703
- const parts = id.split('.');
1704
- parts.pop();
1705
- id = parts.join('.');
1706
- if (info.objects[id]) {
1707
- ownEnums = funcs.length;
1708
- findFunctionsForObject(info, id, lang, funcs);
1709
- }
1710
- return { funcs, pef: !ownEnums };
1711
- }
1712
- /*
1713
- function quality2text(q) {
1714
- if (!q) {
1715
- return 'ok';
1716
- }
1717
- const custom = q & 0xFFFF0000;
1718
- let text = '';
1719
- if (q & 0x40) text += 'device';
1720
- if (q & 0x80) text += 'sensor';
1721
- if (q & 0x01) text += ' bad';
1722
- if (q & 0x02) text += ' not connected';
1723
- if (q & 0x04) text += ' error';
1724
-
1725
- return text + (custom ? '|0x' + (custom >> 16).toString(16).toUpperCase() : '') + ' [0x' + q.toString(16).toUpperCase() + ']';
1726
- }
1727
- */
1728
- /**
1729
- * Format a state value for visualization
1730
- */
1731
- function formatValue(options) {
1732
- const { dateFormat, state, isFloatComma, texts, obj } = options;
1733
- const states = Utils.getStates(obj);
1734
- const isCommon = obj.common;
1735
- let fileViewer;
1736
- let v =
1737
- // @ts-expect-error deprecated from js-controller 6
1738
- isCommon?.type === 'file'
1739
- ? '[file]'
1740
- : !state || state.val === null
1741
- ? '(null)'
1742
- : state.val === undefined
1743
- ? '[undef]'
1744
- : state.val;
1745
- const type = typeof v;
1746
- if (isCommon?.role && typeof isCommon.role === 'string' && isCommon.role.match(/^value\.time|^date/)) {
1747
- if (v && typeof v === 'string') {
1748
- if (Utils.isStringInteger(v)) {
1749
- // we assume a unix ts
1750
- v = new Date(parseInt(v, 10)).toString();
1751
- }
1752
- else {
1753
- // check if parsable by new date
1754
- try {
1755
- const parsedDate = new Date(v);
1756
- if (Utils.isValidDate(parsedDate)) {
1757
- v = parsedDate.toString();
1758
- }
1759
- }
1760
- catch {
1761
- // ignore
1762
- }
1763
- }
1764
- }
1765
- else {
1766
- if (v > 946681200 && v < 946681200000) {
1767
- // '2000-01-01T00:00:00' => 946681200000
1768
- v *= 1_000; // maybe the time is in seconds (UNIX time)
1769
- }
1770
- // "null" and undefined could not be here. See `let v = (isCommon && isCommon.type === 'file') ....` above
1771
- v = v ? new Date(v).toString() : v;
1772
- }
1773
- }
1774
- else if (isCommon?.role && typeof isCommon.role === 'string' && isCommon.role.match(/^value\.duration/)) {
1775
- // Format duration values in HH:mm:ss format
1776
- if (typeof v === 'number' && v >= 0) {
1777
- const hours = Math.floor(v / 3600);
1778
- const minutes = Math.floor((v % 3600) / 60);
1779
- const seconds = Math.floor(v % 60);
1780
- v = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
1781
- }
1782
- else if (typeof v === 'string' && Utils.isStringInteger(v)) {
1783
- const numValue = parseInt(v, 10);
1784
- if (numValue >= 0) {
1785
- const hours = Math.floor(numValue / 3600);
1786
- const minutes = Math.floor((numValue % 3600) / 60);
1787
- const seconds = Math.floor(numValue % 60);
1788
- v = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
1789
- }
1790
- }
1791
- }
1792
- else {
1793
- if (type === 'number') {
1794
- if (!Number.isInteger(v)) {
1795
- v = Math.round(v * 100_000_000) / 100_000_000; // remove 4.00000000000000001
1796
- if (isFloatComma) {
1797
- v = v.toString().replace('.', ',');
1798
- }
1799
- }
1800
- }
1801
- else if (type === 'object') {
1802
- v = JSON.stringify(v);
1803
- }
1804
- else if (type !== 'string') {
1805
- v = v.toString();
1806
- }
1807
- else if (v.startsWith('data:image/')) {
1808
- fileViewer = 'image';
1809
- }
1810
- if (typeof v !== 'string') {
1811
- v = v.toString();
1812
- }
1813
- }
1814
- const valText = { v: v };
1815
- // try to replace number with "common.states"
1816
- if (states && states[v] !== undefined) {
1817
- if (v !== states[v]) {
1818
- valText.s = v;
1819
- v = states[v];
1820
- valText.v = v;
1821
- }
1822
- }
1823
- if (valText.v?.length > 40) {
1824
- valText.c = valText.v;
1825
- valText.v = `${valText.v.substring(0, 40)}...`;
1826
- }
1827
- if (isCommon?.unit) {
1828
- valText.u = isCommon.unit;
1829
- }
1830
- let valFull;
1831
- if (options.full) {
1832
- if (typeof v === 'string' && v.length > 100) {
1833
- valFull = [{ t: texts.value, v: `${v.substring(0, 100)}...` }];
1834
- }
1835
- else {
1836
- valFull = [{ t: texts.value, v }];
1837
- }
1838
- if (state) {
1839
- if (state.ack !== undefined && state.ack !== null) {
1840
- valFull.push({ t: texts.ack, v: state.ack.toString() });
1841
- }
1842
- if (state.ts) {
1843
- valFull.push({ t: texts.ts, v: state.ts ? Utils.formatDate(new Date(state.ts), dateFormat) : '' });
1844
- }
1845
- if (state.lc) {
1846
- valFull.push({ t: texts.lc, v: state.lc ? Utils.formatDate(new Date(state.lc), dateFormat) : '' });
1847
- }
1848
- if (state.from) {
1849
- let from = state.from.toString();
1850
- if (from.startsWith('system.adapter.')) {
1851
- from = from.substring(15);
1852
- }
1853
- valFull.push({ t: texts.from, v: from });
1854
- }
1855
- if (state.user) {
1856
- let user = state.user.toString();
1857
- if (user.startsWith('system.user.')) {
1858
- user = user.substring(12);
1859
- }
1860
- valFull.push({ t: texts.user, v: user });
1861
- }
1862
- if (state.c) {
1863
- valFull.push({ t: texts.c, v: state.c });
1864
- }
1865
- valFull.push({ t: texts.quality, v: Utils.quality2text(state.q || 0).join(', '), nbr: true });
1866
- }
1867
- }
1868
- return {
1869
- valText,
1870
- valFull,
1871
- fileViewer,
1872
- };
1873
- }
1874
- /**
1875
- * Get CSS style for given state value
1876
- */
1877
- function getValueStyle(options) {
1878
- const { state /* , isExpertMode, isButton */ } = options;
1879
- const color = state?.ack ? (state.q ? '#ffa500' : '') : '#ff2222c9';
1880
- // do not show the color of the button in non-expert mode
1881
- // if (!isExpertMode && isButton) {
1882
- // color = '';
1883
- // }
1884
- return { color };
1885
- }
1886
- function prepareSparkData(values, from) {
1887
- // set one point every hour
1888
- let time = from;
1889
- let i = 1;
1890
- const v = [];
1891
- while (i < values.length && time < from + 25 * 3600000) {
1892
- // find the interval
1893
- while (values[i - 1].ts < time && time <= values[i].ts && i < values.length) {
1894
- i++;
1895
- }
1896
- if (i === 1 && values[i - 1].ts >= time) {
1897
- // assume the value was always null
1898
- v.push(0);
1899
- }
1900
- else if (i < values.length) {
1901
- if (typeof values[i].val === 'boolean' || typeof values[i - 1].val === 'boolean') {
1902
- v.push(values[i].val ? 1 : 0);
1903
- }
1904
- else {
1905
- // remove nulls
1906
- values[i - 1].val ||= 0;
1907
- values[i].val ||= 0;
1908
- // interpolate
1909
- const nm1 = values[i - 1].val;
1910
- const n = values[i].val;
1911
- const val = nm1 + ((n - nm1) * (time - values[i - 1].ts)) / (values[i].ts - values[i - 1].ts);
1912
- v.push(val);
1913
- }
1914
- }
1915
- time += 3600000;
1916
- }
1917
- return v;
1918
- }
541
+ display: 'flex',
542
+ height: '100%',
543
+ alignItems: 'center',
544
+ },
545
+ aclText: {
546
+ fontSize: 13,
547
+ marginTop: 6,
548
+ },
549
+ rightsObject: {
550
+ color: '#55ff55',
551
+ paddingLeft: 3,
552
+ },
553
+ rightsState: {
554
+ color: '#86b6ff',
555
+ paddingLeft: 3,
556
+ },
557
+ textCenter: {
558
+ padding: 12,
559
+ textAlign: 'center',
560
+ },
561
+ tooltipAccessControl: {
562
+ display: 'flex',
563
+ flexDirection: 'column',
564
+ },
565
+ fontSizeTitle: {
566
+ '@media screen and (max-width: 465px)': {
567
+ '& *': {
568
+ fontSize: 12,
569
+ },
570
+ },
571
+ },
572
+ draggable: {
573
+ cursor: 'copy',
574
+ },
575
+ nonDraggable: {
576
+ cursor: 'no-drop',
577
+ },
578
+ iconDeviceConnected: (theme) => ({
579
+ color: theme.palette.mode === 'dark' ? COLOR_NAME_CONNECTED_DARK : COLOR_NAME_CONNECTED_LIGHT,
580
+ opacity: 0.8,
581
+ position: 'absolute',
582
+ top: 4,
583
+ right: 32,
584
+ width: 20,
585
+ }),
586
+ iconDeviceDisconnected: (theme) => ({
587
+ color: theme.palette.mode === 'dark' ? COLOR_NAME_DISCONNECTED_DARK : COLOR_NAME_DISCONNECTED_LIGHT,
588
+ opacity: 0.8,
589
+ position: 'absolute',
590
+ top: 4,
591
+ right: 32,
592
+ width: 20,
593
+ }),
594
+ iconDeviceError: (theme) => ({
595
+ color: theme.palette.mode === 'dark' ? COLOR_NAME_ERROR_DARK : COLOR_NAME_ERROR_LIGHT,
596
+ opacity: 0.8,
597
+ position: 'absolute',
598
+ top: 4,
599
+ right: 50,
600
+ width: 20,
601
+ }),
602
+ resizeHandle: {
603
+ display: 'block',
604
+ position: 'absolute',
605
+ cursor: 'col-resize',
606
+ width: 7,
607
+ top: 2,
608
+ bottom: 2,
609
+ zIndex: 1,
610
+ },
611
+ resizeHandleRight: {
612
+ right: 3,
613
+ borderRight: '2px dotted #888',
614
+ '&:hover': {
615
+ borderColor: '#ccc',
616
+ borderRightStyle: 'solid',
617
+ },
618
+ '&.active': {
619
+ borderColor: '#517ea5',
620
+ borderRightStyle: 'solid',
621
+ },
622
+ },
623
+ invertedBackground: (theme) => ({
624
+ backgroundColor: theme.palette.mode === 'dark' ? '#9a9a9a' : '#565656',
625
+ padding: '0 3px',
626
+ borderRadius: '2px 0 0 2px',
627
+ }),
628
+ invertedBackgroundFlex: (theme) => ({
629
+ backgroundColor: theme.palette.mode === 'dark' ? '#9a9a9a' : '#565656',
630
+ borderRadius: '0 2px 2px 0',
631
+ }),
632
+ contextMenuEdit: (theme) => ({
633
+ color: theme.palette.mode === 'dark' ? '#ffee48' : '#cbb801',
634
+ }),
635
+ contextMenuEditValue: (theme) => ({
636
+ color: theme.palette.mode === 'dark' ? '#5dff45' : '#1cd301',
637
+ }),
638
+ contextMenuView: (theme) => ({
639
+ color: theme.palette.mode === 'dark' ? '#FFF' : '#000',
640
+ }),
641
+ contextMenuCustom: (theme) => ({
642
+ color: theme.palette.mode === 'dark' ? '#42eaff' : '#01bbc2',
643
+ }),
644
+ contextMenuACL: (theme) => ({
645
+ color: theme.palette.mode === 'dark' ? '#e079ff' : '#500070',
646
+ }),
647
+ contextMenuRoom: (theme) => ({
648
+ color: theme.palette.mode === 'dark' ? '#ff9a33' : '#642a00',
649
+ }),
650
+ contextMenuRole: (theme) => ({
651
+ color: theme.palette.mode === 'dark' ? '#ffdb43' : '#562d00',
652
+ }),
653
+ contextMenuDelete: (theme) => ({
654
+ color: theme.palette.mode === 'dark' ? '#ff4f4f' : '#cf0000',
655
+ }),
656
+ contextMenuKeys: {
657
+ marginLeft: 8,
658
+ opacity: 0.7,
659
+ fontSize: 'smaller',
660
+ },
661
+ contextMenuWithSubMenu: {
662
+ display: 'flex',
663
+ },
664
+ ...utilStyles,
665
+ };
1919
666
  export const ITEM_IMAGES = {
1920
667
  state: (React.createElement(IconState, { className: "itemIcon", style: { verticalAlign: 'middle' } })),
1921
668
  channel: (React.createElement(IconChannel, { className: "itemIcon", style: { verticalAlign: 'middle' } })),
@@ -2077,7 +824,7 @@ export class ObjectBrowserClass extends Component {
2077
824
  systemConfig;
2078
825
  objects;
2079
826
  defaultHistory = '';
2080
- cltrPressed = false;
827
+ ctrlPressed = false;
2081
828
  columnsVisibility = {};
2082
829
  changedIds = null;
2083
830
  contextMenu = null;
@@ -2085,8 +832,6 @@ export class ObjectBrowserClass extends Component {
2085
832
  styles = {};
2086
833
  expertMode = false;
2087
834
  customColumnDialog = null;
2088
- /** Namespaces which are allowed to be edited by non-expert users */
2089
- static NON_EXPERT_NAMESPACES = ['0_userdata.0.', 'alias.0.'];
2090
835
  constructor(props) {
2091
836
  super(props);
2092
837
  const lastSelectedItemStr = this.localStorage.getItem(`${props.dialogName || 'App'}.objectSelected`) || '';
@@ -2230,46 +975,48 @@ export class ObjectBrowserClass extends Component {
2230
975
  // ignore
2231
976
  }
2232
977
  this.state = {
2233
- loaded: false,
2234
- foldersFirst,
2235
- selected,
2236
- selectedNonObject: this.localStorage.getItem(`${props.dialogName || 'App'}.selectedNonObject`) || '',
2237
- filter,
2238
- filterKey: 0,
2239
- focused: this.localStorage.getItem(`${props.dialogName || 'App'}.focused`) || '',
2240
- depth: 0,
2241
- expandAllVisible: false,
2242
- expanded,
2243
- toast: '',
2244
- scrollBarWidth: 16,
2245
- customDialog,
2246
- editObjectDialog: '',
2247
- editObjectAlias: false, // open the edit object dialog on alias tab
2248
- viewFileDialog: '',
2249
- showAliasEditor: '',
2250
- enumDialog: null,
2251
- roleDialog: null,
2252
- statesView,
978
+ aliasMenu: '',
979
+ beautifyJsonExport: true,
2253
980
  columns,
2254
- columnsForAdmin: null,
2255
- columnsSelectorShow: false,
2256
981
  columnsAuto: this.localStorage.getItem(`${props.dialogName || 'App'}.columnsAuto`) !== 'false',
2257
- columnsWidths,
2258
982
  columnsDialogTransparent: 100,
2259
983
  columnsEditCustomDialog: null,
984
+ columnsForAdmin: null,
985
+ columnsSelectorShow: false,
986
+ columnsWidths,
2260
987
  customColumnDialogValueChanged: false,
2261
- showExportDialog: false,
2262
- showAllExportOptions: false,
2263
- linesEnabled: this.localStorage.getItem(`${props.dialogName || 'App'}.lines`) === 'true',
2264
- showDescription: this.localStorage.getItem(`${props.dialogName || 'App'}.desc`) !== 'false',
2265
- showContextMenu: null,
2266
- noStatesByExportImport: false,
2267
- beautifyJsonExport: true,
988
+ customDialog,
989
+ depth: 0,
990
+ editObjectAlias: false, // open the edit object dialog on alias tab
991
+ editObjectDialog: '',
992
+ enumDialog: null,
2268
993
  excludeSystemRepositoriesFromExport: true,
2269
994
  excludeTranslations: false,
2270
- tooltipInfo: null,
2271
- aliasMenu: '',
995
+ expandAllVisible: false,
996
+ expanded,
997
+ filter,
998
+ filterKey: 0,
999
+ focused: this.localStorage.getItem(`${props.dialogName || 'App'}.focused`) || '',
1000
+ foldersFirst,
1001
+ linesEnabled: this.localStorage.getItem(`${props.dialogName || 'App'}.lines`) === 'true',
1002
+ loaded: false,
1003
+ noStatesByExportImport: false,
1004
+ roleDialog: null,
1005
+ scrollBarWidth: 16,
1006
+ selected,
1007
+ selectedNonObject: this.localStorage.getItem(`${props.dialogName || 'App'}.selectedNonObject`) || '',
1008
+ showAliasEditor: '',
1009
+ showAllExportOptions: false,
1010
+ showContextMenu: null,
1011
+ showDescription: this.localStorage.getItem(`${props.dialogName || 'App'}.desc`) !== 'false',
1012
+ showExportDialog: false,
1013
+ showImportDialog: false,
1014
+ showImportMenu: null,
2272
1015
  showRenameDialog: null,
1016
+ statesView,
1017
+ toast: '',
1018
+ tooltipInfo: null,
1019
+ viewFileDialog: '',
2273
1020
  };
2274
1021
  this.texts = {
2275
1022
  name: props.t('ra_Name'),
@@ -2494,14 +1241,6 @@ export class ObjectBrowserClass extends Component {
2494
1241
  this.showError(error);
2495
1242
  }
2496
1243
  }
2497
- /**
2498
- * Check if it is a non-expert id
2499
- */
2500
- static isNonExpertId(
2501
- /** id to test */
2502
- id) {
2503
- return !!ObjectBrowserClass.NON_EXPERT_NAMESPACES.find(saveNamespace => id.startsWith(saveNamespace));
2504
- }
2505
1244
  expandAllSelected(cb) {
2506
1245
  const expanded = [...this.state.expanded];
2507
1246
  let changed = false;
@@ -2590,6 +1329,7 @@ export class ObjectBrowserClass extends Component {
2590
1329
  }
2591
1330
  }
2592
1331
  }
1332
+ // This function is used
2593
1333
  static getDerivedStateFromProps(props, state) {
2594
1334
  const newState = {};
2595
1335
  let changed = false;
@@ -2623,14 +1363,14 @@ export class ObjectBrowserClass extends Component {
2623
1363
  }
2624
1364
  }
2625
1365
  onKeyPress = (event) => {
2626
- if (event.type === 'keydown' && event.ctrlKey && !this.cltrPressed) {
2627
- this.cltrPressed = true;
1366
+ if (event.type === 'keydown' && event.ctrlKey && !this.ctrlPressed) {
1367
+ this.ctrlPressed = true;
2628
1368
  if (this.tableRef.current) {
2629
1369
  this.tableRef.current.className = 'highlight-link';
2630
1370
  }
2631
1371
  }
2632
- else if (event.type === 'keyup' && !event.ctrlKey && this.cltrPressed) {
2633
- this.cltrPressed = false;
1372
+ else if (event.type === 'keyup' && !event.ctrlKey && this.ctrlPressed) {
1373
+ this.ctrlPressed = false;
2634
1374
  if (this.tableRef.current) {
2635
1375
  this.tableRef.current.className = '';
2636
1376
  }
@@ -3581,76 +2321,78 @@ export class ObjectBrowserClass extends Component {
3581
2321
  const ObjectMoveRenameDialog = this.props.objectMoveRenameDialog;
3582
2322
  return (React.createElement(ObjectMoveRenameDialog, { expertMode: this.props.expertMode, onClose: () => this.setState({ showRenameDialog: null }), id: this.state.showRenameDialog.id, childrenIds: this.state.showRenameDialog.childrenIds, theme: this.props.theme, socket: this.props.socket, t: this.props.t, objectType: this.objects[this.state.showRenameDialog.id]?.type }));
3583
2323
  }
3584
- handleJsonUpload(evt) {
3585
- const target = evt.target;
3586
- const f = target.files?.length && target.files[0];
3587
- if (f) {
3588
- const r = new FileReader();
3589
- r.onload = async (e) => {
3590
- const contents = e.target?.result;
2324
+ async parseJsonFile(contents) {
2325
+ try {
2326
+ const json = JSON.parse(contents);
2327
+ const len = Object.keys(json).length;
2328
+ const id = json._id;
2329
+ // it could be a single object or many objects
2330
+ if (id === undefined && len) {
2331
+ // many objects
2332
+ await this.loadObjects(json);
2333
+ window.alert(this.props.t('ra_%s object(s) processed', len));
2334
+ }
2335
+ else {
2336
+ // it is only one object in form
2337
+ // {
2338
+ // "_id": "xxx",
2339
+ // "common": "yyy",
2340
+ // "native": "zzz"
2341
+ // "val": JSON.stringify(value)
2342
+ // "ack": true
2343
+ // }
2344
+ if (!id) {
2345
+ return window.alert(this.props.t('ra_Invalid structure'));
2346
+ }
3591
2347
  try {
3592
- const json = JSON.parse(contents);
3593
- const len = Object.keys(json).length;
3594
- const id = json._id;
3595
- // it could be a single object or many objects
3596
- if (id === undefined && len) {
3597
- // many objects
3598
- await this.loadObjects(json);
3599
- window.alert(this.props.t('ra_%s object(s) processed', len));
2348
+ let enums;
2349
+ let val;
2350
+ let ack;
2351
+ if (json.common.enums) {
2352
+ enums = json.common.enums;
2353
+ delete json.common.enums;
3600
2354
  }
3601
- else {
3602
- // it is only one object in form
3603
- // {
3604
- // "_id": "xxx",
3605
- // "common": "yyy",
3606
- // "native": "zzz"
3607
- // "val": JSON.stringify(value)
3608
- // "ack": true
3609
- // }
3610
- if (!id) {
3611
- return window.alert(this.props.t('ra_Invalid structure'));
2355
+ if (json.val) {
2356
+ val = json.val;
2357
+ delete json.val;
2358
+ }
2359
+ if (json.ack !== undefined) {
2360
+ ack = json.ack;
2361
+ delete json.ack;
2362
+ }
2363
+ await this.props.socket.setObject(json._id, json);
2364
+ if (json.type === 'state') {
2365
+ if (val !== undefined && val !== null) {
2366
+ await this.props.socket.setState(json._id, val, ack === undefined ? true : ack);
3612
2367
  }
3613
- try {
3614
- let enums;
3615
- let val;
3616
- let ack;
3617
- if (json.common.enums) {
3618
- enums = json.common.enums;
3619
- delete json.common.enums;
3620
- }
3621
- if (json.val) {
3622
- val = json.val;
3623
- delete json.val;
3624
- }
3625
- if (json.ack !== undefined) {
3626
- ack = json.ack;
3627
- delete json.ack;
3628
- }
3629
- await this.props.socket.setObject(json._id, json);
3630
- if (json.type === 'state') {
3631
- if (val !== undefined && val !== null) {
3632
- await this.props.socket.setState(json._id, val, ack === undefined ? true : ack);
3633
- }
3634
- else {
3635
- const state = await this.props.socket.getState(json._id);
3636
- if (!state || state.val === null || state.val === undefined) {
3637
- await this.props.socket.setState(json._id, json.common.def === undefined ? null : json.common.def, true);
3638
- }
3639
- }
3640
- }
3641
- if (enums) {
3642
- await this._createAllEnums(enums, json._id);
2368
+ else {
2369
+ const state = await this.props.socket.getState(json._id);
2370
+ if (!state || state.val === null || state.val === undefined) {
2371
+ await this.props.socket.setState(json._id, json.common.def === undefined ? null : json.common.def, true);
3643
2372
  }
3644
- window.alert(this.props.t('ra_%s was imported', json._id));
3645
- }
3646
- catch (err) {
3647
- window.alert(err);
3648
2373
  }
3649
2374
  }
2375
+ if (enums) {
2376
+ await this._createAllEnums(enums, json._id);
2377
+ }
2378
+ window.alert(this.props.t('ra_%s was imported', json._id));
3650
2379
  }
3651
2380
  catch (err) {
3652
2381
  window.alert(err);
3653
2382
  }
2383
+ }
2384
+ }
2385
+ catch (err) {
2386
+ window.alert(err);
2387
+ }
2388
+ }
2389
+ handleJsonUpload(evt) {
2390
+ const target = evt.target;
2391
+ const f = target.files?.length && target.files[0];
2392
+ if (f) {
2393
+ const r = new FileReader();
2394
+ r.onload = (e) => {
2395
+ void this.parseJsonFile(e.target?.result);
3654
2396
  return null;
3655
2397
  };
3656
2398
  r.readAsText(f);
@@ -3728,6 +2470,26 @@ export class ObjectBrowserClass extends Component {
3728
2470
  }
3729
2471
  return value.length ? value : t('ra_Add new child object to selected parent');
3730
2472
  };
2473
+ onOpenFile() {
2474
+ const input = document.createElement('input');
2475
+ input.setAttribute('type', 'file');
2476
+ input.setAttribute('id', 'files');
2477
+ input.setAttribute('opacity', '0');
2478
+ input.addEventListener('change', (e) => this.handleJsonUpload(e), false);
2479
+ input.click();
2480
+ }
2481
+ renderInputJsonDialog() {
2482
+ const ObjectBrowserInsertJsonObjects = this.props.objectBrowserInsertJsonObjects;
2483
+ if (!this.state.showImportDialog) {
2484
+ return null;
2485
+ }
2486
+ return (React.createElement(ObjectBrowserInsertJsonObjects, { onClose: (text) => {
2487
+ this.setState({ showImportDialog: false });
2488
+ if (text) {
2489
+ void this.parseJsonFile(text);
2490
+ }
2491
+ }, themeName: this.props.themeName, themeType: this.props.themeType, t: this.props.t }));
2492
+ }
3731
2493
  /**
3732
2494
  * Renders the toolbar.
3733
2495
  */
@@ -3819,15 +2581,24 @@ export class ObjectBrowserClass extends Component {
3819
2581
  }), size: "large" },
3820
2582
  React.createElement(AddIcon, null))))) : null,
3821
2583
  this.props.objectImportExport && (React.createElement(Tooltip, { title: this.props.t('ra_Add objects tree from JSON file'), slotProps: { popper: { sx: styles.tooltip } } },
3822
- React.createElement(IconButton, { onClick: () => {
3823
- const input = document.createElement('input');
3824
- input.setAttribute('type', 'file');
3825
- input.setAttribute('id', 'files');
3826
- input.setAttribute('opacity', '0');
3827
- input.addEventListener('change', (e) => this.handleJsonUpload(e), false);
3828
- input.click();
2584
+ React.createElement(IconButton, { onClick: e => {
2585
+ if (this.props.objectBrowserInsertJsonObjects) {
2586
+ this.setState({ showImportMenu: e.currentTarget });
2587
+ }
2588
+ else {
2589
+ this.onOpenFile();
2590
+ }
3829
2591
  }, size: "large" },
3830
2592
  React.createElement(PublishIcon, null)))),
2593
+ this.props.objectBrowserInsertJsonObjects ? (React.createElement(Menu, { anchorEl: this.state.showImportMenu, open: !!this.state.showImportMenu, onClose: () => this.setState({ showImportMenu: null }) },
2594
+ React.createElement(MenuItem, { onClick: () => this.setState({ showImportMenu: null }, () => this.onOpenFile()) },
2595
+ React.createElement(ListItemIcon, null,
2596
+ React.createElement(UploadFile, null)),
2597
+ React.createElement(ListItemText, null, this.props.t('ra_From file'))),
2598
+ React.createElement(MenuItem, { onClick: () => this.setState({ showImportMenu: null, showImportDialog: true }) },
2599
+ React.createElement(ListItemIcon, null,
2600
+ React.createElement(ContentPaste, null)),
2601
+ React.createElement(ListItemText, null, this.props.t('ra_From text'))))) : null,
3831
2602
  this.props.objectImportExport &&
3832
2603
  (!!this.state.selected.length || this.state.selectedNonObject) && (React.createElement(Tooltip, { title: this.props.t('ra_Save objects tree as JSON file'), slotProps: { popper: { sx: styles.tooltip } } },
3833
2604
  React.createElement(IconButton, { onClick: () => this.setState({ showExportDialog: this._getSelectedIdsForExport().length }), size: "large" },
@@ -3983,7 +2754,7 @@ export class ObjectBrowserClass extends Component {
3983
2754
  (item.data.obj.type === 'state'
3984
2755
  ? this.systemConfig.common.defaultNewAcl.state
3985
2756
  : this.systemConfig.common.defaultNewAcl.object);
3986
- const showEdit = this.state.filter.expertMode || ObjectBrowserClass.isNonExpertId(item.data.id);
2757
+ const showEdit = this.state.filter.expertMode || isNonExpertId(item.data.id);
3987
2758
  return [
3988
2759
  this.state.filter.expertMode && this.props.objectEditOfAccessControl ? (React.createElement(Tooltip, { key: "acl", title: item.data.aclTooltip, slotProps: { popper: { sx: styles.tooltip } } },
3989
2760
  React.createElement(IconButton, { sx: {
@@ -4380,7 +3151,7 @@ export class ObjectBrowserClass extends Component {
4380
3151
  this.props.socket
4381
3152
  .getObject(this.state.columnsEditCustomDialog?.obj?._id || '')
4382
3153
  .then(obj => {
4383
- if (obj && ObjectBrowserClass.setCustomValue(obj, this.state.columnsEditCustomDialog?.it, value)) {
3154
+ if (obj && setCustomValue(obj, this.state.columnsEditCustomDialog?.it, value)) {
4384
3155
  return this.props.socket.setObject(obj._id, obj);
4385
3156
  }
4386
3157
  throw new Error(this.props.t('ra_Cannot update attribute, because not found in the object'));
@@ -4398,7 +3169,7 @@ export class ObjectBrowserClass extends Component {
4398
3169
  return null;
4399
3170
  }
4400
3171
  if (!this.customColumnDialog) {
4401
- const value = ObjectBrowserClass.getCustomValue(this.state.columnsEditCustomDialog.obj, this.state.columnsEditCustomDialog.it);
3172
+ const value = getCustomValue(this.state.columnsEditCustomDialog.obj, this.state.columnsEditCustomDialog.it);
4402
3173
  this.customColumnDialog = {
4403
3174
  type: (this.state.columnsEditCustomDialog.it.type || typeof value),
4404
3175
  initValue: (value === null || value === undefined ? '' : value).toString(),
@@ -4427,111 +3198,11 @@ export class ObjectBrowserClass extends Component {
4427
3198
  React.createElement(Button, { variant: "contained", onClick: () => this.onColumnsEditCustomDialogClose(true), disabled: !this.state.customColumnDialogValueChanged, color: "primary", startIcon: React.createElement(IconCheck, null) }, this.props.t('ra_Update')),
4428
3199
  React.createElement(Button, { color: "grey", variant: "contained", onClick: () => this.onColumnsEditCustomDialogClose(), startIcon: React.createElement(IconClose, null) }, this.props.t('ra_Cancel')))));
4429
3200
  }
4430
- static getCustomValue(obj, it) {
4431
- if (obj?._id?.startsWith(`${it.adapter}.`) && it.path.length > 1) {
4432
- const p = it.path;
4433
- let value;
4434
- const anyObj = obj;
4435
- if (anyObj[p[0]] && typeof anyObj[p[0]] === 'object') {
4436
- if (p.length === 2) {
4437
- // most common case
4438
- value = anyObj[p[0]][p[1]];
4439
- }
4440
- else if (p.length === 3) {
4441
- value =
4442
- anyObj[p[0]][p[1]] && typeof anyObj[p[0]][p[1]] === 'object' ? anyObj[p[0]][p[1]][p[2]] : null;
4443
- }
4444
- else if (p.length === 4) {
4445
- value =
4446
- anyObj[p[0]][p[1]] && typeof anyObj[p[0]][p[1]] === 'object' && anyObj[p[0]][p[1]][p[2]]
4447
- ? anyObj[p[0]][p[1]][p[2]][p[3]]
4448
- : null;
4449
- }
4450
- else if (p.length === 5) {
4451
- value =
4452
- anyObj[p[0]][p[1]] &&
4453
- typeof anyObj[p[0]][p[1]] === 'object' &&
4454
- anyObj[p[0]][p[1]][p[2]] &&
4455
- anyObj[p[0]][p[1]][p[2]][p[3]]
4456
- ? anyObj[p[0]][p[1]][p[2]][p[3]][p[4]]
4457
- : null;
4458
- }
4459
- else if (p.length === 6) {
4460
- value =
4461
- anyObj[p[0]][p[1]] &&
4462
- typeof anyObj[p[0]][p[1]] === 'object' &&
4463
- anyObj[p[0]][p[1]][p[2]] &&
4464
- anyObj[p[0]][p[1]][p[2]][p[3]] &&
4465
- anyObj[p[0]][p[1]][p[2]][p[3]][p[4]]
4466
- ? anyObj[p[0]][p[1]][p[2]][p[3]][p[4]][p[5]]
4467
- : null;
4468
- }
4469
- if (value === undefined || value === null) {
4470
- return null;
4471
- }
4472
- return value;
4473
- }
4474
- }
4475
- return null;
4476
- }
4477
- static setCustomValue(obj, it, value) {
4478
- if (obj?._id?.startsWith(`${it.adapter}.`) && it.path.length > 1) {
4479
- const p = it.path;
4480
- const anyObj = obj;
4481
- if (anyObj[p[0]] && typeof anyObj[p[0]] === 'object') {
4482
- if (p.length === 2) {
4483
- // most common case
4484
- anyObj[p[0]][p[1]] = value;
4485
- return true;
4486
- }
4487
- if (p.length === 3) {
4488
- if (anyObj[p[0]][p[1]] && typeof anyObj[p[0]][p[1]] === 'object') {
4489
- anyObj[p[0]][p[1]][p[2]] = value;
4490
- return true;
4491
- }
4492
- }
4493
- else if (p.length === 4) {
4494
- if (anyObj[p[0]][p[1]] &&
4495
- typeof anyObj[p[0]][p[1]] === 'object' &&
4496
- anyObj[p[0]][p[1]][p[2]] &&
4497
- typeof anyObj[p[0]][p[1]][p[2]] === 'object') {
4498
- anyObj[p[0]][p[1]][p[2]][p[3]] = value;
4499
- return true;
4500
- }
4501
- }
4502
- else if (p.length === 5) {
4503
- if (anyObj[p[0]][p[1]] &&
4504
- typeof anyObj[p[0]][p[1]] === 'object' &&
4505
- anyObj[p[0]][p[1]][p[2]] &&
4506
- typeof anyObj[p[0]][p[1]][p[2]] === 'object' &&
4507
- anyObj[p[0]][p[1]][p[2]][p[3]] &&
4508
- typeof anyObj[p[0]][p[1]][p[2]][p[3]] === 'object') {
4509
- anyObj[p[0]][p[1]][p[2]][p[3]][p[4]] = value;
4510
- return true;
4511
- }
4512
- }
4513
- else if (p.length === 6) {
4514
- if (anyObj[p[0]][p[1]] &&
4515
- typeof anyObj[p[0]][p[1]] === 'object' &&
4516
- anyObj[p[0]][p[1]][p[2]] &&
4517
- typeof anyObj[p[0]][p[1]][p[2]] === 'object' &&
4518
- anyObj[p[0]][p[1]][p[2]][p[3]] &&
4519
- typeof anyObj[p[0]][p[1]][p[2]][p[3]] === 'object' &&
4520
- anyObj[p[0]][p[1]][p[2]][p[3]][p[4]] &&
4521
- typeof anyObj[p[0]][p[1]][p[2]][p[3]][p[4]] === 'object') {
4522
- anyObj[p[0]][p[1]][p[2]][p[3]][p[4]][p[5]] = value;
4523
- return true;
4524
- }
4525
- }
4526
- }
4527
- }
4528
- return false;
4529
- }
4530
3201
  /**
4531
3202
  * Renders a custom value.
4532
3203
  */
4533
3204
  renderCustomValue(obj, it, item) {
4534
- const text = ObjectBrowserClass.getCustomValue(obj, it);
3205
+ const text = getCustomValue(obj, it);
4535
3206
  if (text !== null && text !== undefined) {
4536
3207
  if (it.edit && !this.props.notEditable && (!it.objTypes || it.objTypes.includes(obj.type))) {
4537
3208
  return (React.createElement(Box, { component: "div", style: {
@@ -5910,7 +4581,7 @@ export class ObjectBrowserClass extends Component {
5910
4581
  key: '0',
5911
4582
  visibility: !!(this.props.objectBrowserEditObject &&
5912
4583
  obj &&
5913
- (this.state.filter.expertMode || ObjectBrowserClass.isNonExpertId(id))),
4584
+ (this.state.filter.expertMode || isNonExpertId(id))),
5914
4585
  icon: (React.createElement(IconEdit, { fontSize: "small", style: this.styles.contextMenuEdit })),
5915
4586
  label: this.texts.editObject,
5916
4587
  onClick: () => this.setState({ editObjectDialog: item.data.id, showContextMenu: null, editObjectAlias: false }),
@@ -5921,7 +4592,7 @@ export class ObjectBrowserClass extends Component {
5921
4592
  !this.props.notEditable &&
5922
4593
  obj &&
5923
4594
  obj.type === 'state' &&
5924
- // @ts-expect-error deprecated from js-controller 6
4595
+ // deprecated from js-controller 6
5925
4596
  obj.common?.type !== 'file' &&
5926
4597
  (this.state.filter.expertMode || obj.common.write !== false)),
5927
4598
  icon: (React.createElement(IconValueEdit, { fontSize: "small", style: this.styles.contextMenuEditValue })),
@@ -5939,7 +4610,7 @@ export class ObjectBrowserClass extends Component {
5939
4610
  VIEW: {
5940
4611
  visibility: !!this.props.objectBrowserViewFile &&
5941
4612
  obj?.type === 'state' &&
5942
- // @ts-expect-error deprecated from js-controller 6
4613
+ // deprecated from js-controller 6
5943
4614
  obj.common?.type === 'file',
5944
4615
  icon: (React.createElement(FindInPage, { fontSize: "small", style: this.styles.contextMenuView })),
5945
4616
  label: this.props.t('ra_View file'),
@@ -5951,7 +4622,7 @@ export class ObjectBrowserClass extends Component {
5951
4622
  this.info.hasSomeCustoms &&
5952
4623
  obj &&
5953
4624
  obj.type === 'state' &&
5954
- // @ts-expect-error deprecated from js-controller 6
4625
+ // deprecated from js-controller 6
5955
4626
  obj.common?.type !== 'file'),
5956
4627
  icon: (React.createElement(IconConfig, { fontSize: "small", style: item.data.hasCustoms
5957
4628
  ? this.styles.cellButtonsButtonWithCustoms
@@ -6027,7 +4698,7 @@ export class ObjectBrowserClass extends Component {
6027
4698
  this.props.objectBrowserAliasEditor &&
6028
4699
  this.props.objectBrowserEditObject &&
6029
4700
  obj?.type === 'state' &&
6030
- // @ts-expect-error deprecated from js-controller 6
4701
+ // deprecated from js-controller 6
6031
4702
  obj.common?.type !== 'file'),
6032
4703
  icon: (React.createElement(IconLink, { style: obj?.common?.alias
6033
4704
  ? this.styles.cellButtonsButtonWithCustoms
@@ -6340,6 +5011,7 @@ export class ObjectBrowserClass extends Component {
6340
5011
  this.renderErrorDialog(),
6341
5012
  this.renderExportDialog(),
6342
5013
  this.renderRenameDialog(),
5014
+ this.renderInputJsonDialog(),
6343
5015
  this.state.modalNewObj && this.props.modalNewObject && this.props.modalNewObject(this),
6344
5016
  this.state.modalEditOfAccess &&
6345
5017
  this.state.modalEditOfAccessObjData &&