@iobroker/gui-components 8.0.1 → 10.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -22
- package/README.md +834 -543
- package/build/AdminConnection.js.map +1 -1
- package/build/Components/404.js +13 -13
- package/build/Components/404.js.map +1 -1
- package/build/Components/ColorPicker.js.map +1 -1
- package/build/Components/ComplexCron.js +2 -2
- package/build/Components/ComplexCron.js.map +1 -1
- package/build/Components/CopyToClipboard.js.map +1 -1
- package/build/Components/CustomModal.js +1 -1
- package/build/Components/CustomModal.js.map +1 -1
- package/build/Components/DeviceType/DeviceTypeIcon.js +1 -0
- package/build/Components/DeviceType/DeviceTypeIcon.js.map +1 -1
- package/build/Components/DeviceType/DeviceTypeSelector.js +2 -2
- package/build/Components/DeviceType/DeviceTypeSelector.js.map +1 -1
- package/build/Components/DeviceType/i18n/de.json +1 -0
- package/build/Components/DeviceType/i18n/en.json +1 -0
- package/build/Components/DeviceType/i18n/fr.json +1 -0
- package/build/Components/DeviceType/i18n/it.json +1 -0
- package/build/Components/DeviceType/i18n/nl.json +1 -0
- package/build/Components/DeviceType/i18n/pl.json +1 -0
- package/build/Components/DeviceType/i18n/pt.json +1 -0
- package/build/Components/DeviceType/i18n/ru.json +1 -0
- package/build/Components/DeviceType/i18n/uk.json +1 -0
- package/build/Components/DeviceType/i18n/zh-cn.json +1 -0
- package/build/Components/FileBrowser.d.ts +39 -4
- package/build/Components/FileBrowser.js +104 -11
- package/build/Components/FileBrowser.js.map +1 -1
- package/build/Components/FileViewer.js +2 -4
- package/build/Components/FileViewer.js.map +1 -1
- package/build/Components/Icon.d.ts +1 -3
- package/build/Components/Icon.js +11 -11
- package/build/Components/Icon.js.map +1 -1
- package/build/Components/Image.js +8 -4
- package/build/Components/Image.js.map +1 -1
- package/build/Components/InfoBox.js +1 -1
- package/build/Components/InfoBox.js.map +1 -1
- package/build/Components/IobUri.js +5 -5
- package/build/Components/IobUri.js.map +1 -1
- package/build/Components/Loader.d.ts +2 -3
- package/build/Components/Loader.js +25 -257
- package/build/Components/Loader.js.map +1 -1
- package/build/Components/Loaders/Community.d.ts +31 -0
- package/build/Components/Loaders/Community.js +265 -0
- package/build/Components/Loaders/Community.js.map +1 -0
- package/build/Components/Loaders/HA.d.ts +17 -0
- package/build/Components/Loaders/HA.js +99 -0
- package/build/Components/Loaders/HA.js.map +1 -0
- package/build/Components/Loaders/MV.js.map +1 -1
- package/build/Components/Loaders/NW.d.ts +17 -0
- package/build/Components/Loaders/NW.js +70 -0
- package/build/Components/Loaders/NW.js.map +1 -0
- package/build/Components/Loaders/PT.js +103 -103
- package/build/Components/Loaders/PT.js.map +1 -1
- package/build/Components/Loaders/Vendor.js +7 -7
- package/build/Components/Loaders/Vendor.js.map +1 -1
- package/build/Components/MDUtils.js.map +1 -1
- package/build/Components/ObjectBrowser.d.ts +25 -406
- package/build/Components/ObjectBrowser.js +460 -1543
- package/build/Components/ObjectBrowser.js.map +1 -1
- package/build/Components/Router.js.map +1 -1
- package/build/Components/SaveCloseButtons.js.map +1 -1
- package/build/Components/Schedule.d.ts +27 -1
- package/build/Components/Schedule.js +60 -50
- package/build/Components/Schedule.js.map +1 -1
- package/build/Components/SelectWithIcon.js +1 -1
- package/build/Components/SelectWithIcon.js.map +1 -1
- package/build/Components/SimpleCron/cronText.js.map +1 -1
- package/build/Components/SimpleCron/index.js +3 -3
- package/build/Components/SimpleCron/index.js.map +1 -1
- package/build/Components/TabContainer.js +7 -2
- package/build/Components/TabContainer.js.map +1 -1
- package/build/Components/TabContent.d.ts +1 -1
- package/build/Components/TabContent.js +5 -5
- package/build/Components/TabContent.js.map +1 -1
- package/build/Components/TabHeader.js +2 -2
- package/build/Components/TabHeader.js.map +1 -1
- package/build/Components/TableResize.js.map +1 -1
- package/build/Components/TextWithIcon.js.map +1 -1
- package/build/Components/TreeTable.d.ts +1 -0
- package/build/Components/TreeTable.js +16 -6
- package/build/Components/TreeTable.js.map +1 -1
- package/build/Components/UploadImage.js +305 -305
- package/build/Components/UploadImage.js.map +1 -1
- package/build/Components/Utils.d.ts +17 -3
- package/build/Components/Utils.js +167 -9
- package/build/Components/Utils.js.map +1 -1
- package/build/Components/objectBrowser.types.d.ts +496 -0
- package/build/Components/objectBrowserUtils.d.ts +170 -0
- package/build/Components/objectBrowserUtils.js +1375 -0
- package/build/Components/objectBrowserUtils.js.map +1 -0
- package/build/Connection.js.map +1 -1
- package/build/Dialogs/ComplexCron.js.map +1 -1
- package/build/Dialogs/Confirm.js.map +1 -1
- package/build/Dialogs/Cron.js.map +1 -1
- package/build/Dialogs/Error.js.map +1 -1
- package/build/Dialogs/Message.js.map +1 -1
- package/build/Dialogs/SelectFile.d.ts +1 -1
- package/build/Dialogs/SelectFile.js +1 -1
- package/build/Dialogs/SelectFile.js.map +1 -1
- package/build/Dialogs/SelectID.d.ts +1 -1
- package/build/Dialogs/SelectID.js +1 -1
- package/build/Dialogs/SelectID.js.map +1 -1
- package/build/Dialogs/SimpleCron.js.map +1 -1
- package/build/Dialogs/TextInput.js +1 -1
- package/build/Dialogs/TextInput.js.map +1 -1
- package/build/GenericApp.d.ts +6 -4
- package/build/GenericApp.js +22 -16
- package/build/GenericApp.js.map +1 -1
- package/build/Prompt.js +7 -7
- package/build/Prompt.js.map +1 -1
- package/build/Theme.js +57 -11
- package/build/Theme.js.map +1 -1
- package/build/assets/devices/parseNames.js +1 -0
- package/build/assets/devices/parseNames.js.map +1 -1
- package/build/assets/rooms/parseNames.js +1 -0
- package/build/assets/rooms/parseNames.js.map +1 -1
- package/build/dictionary.js.map +1 -1
- package/build/globals.d.ts +2 -0
- package/build/i18n/de.json +5 -66
- package/build/i18n/en.json +5 -66
- package/build/i18n/es.json +5 -66
- package/build/i18n/fr.json +5 -66
- package/build/i18n/it.json +5 -61
- package/build/i18n/nl.json +5 -61
- package/build/i18n/pl.json +5 -61
- package/build/i18n/pt.json +5 -61
- package/build/i18n/ru.json +5 -61
- package/build/i18n/uk.json +5 -61
- package/build/i18n/zh-cn.json +5 -61
- package/build/i18n.js +12 -15
- package/build/i18n.js.map +1 -1
- package/build/index.css +56 -56
- package/build/index.d.ts +9 -4
- package/build/index.js +5 -2
- package/build/index.js.map +1 -1
- package/build/modulefederation.admin.config.d.ts +1 -0
- package/build/modulefederation.admin.config.js +1 -1
- package/build/modulefederation.admin.config.js.map +1 -1
- package/build/types.d.ts +4 -84
- package/i18n/de.json +5 -66
- package/i18n/en.json +5 -66
- package/i18n/es.json +5 -66
- package/i18n/fr.json +5 -66
- package/i18n/it.json +5 -61
- package/i18n/nl.json +5 -61
- package/i18n/pl.json +5 -61
- package/i18n/pt.json +5 -61
- package/i18n/ru.json +5 -61
- package/i18n/uk.json +5 -61
- package/i18n/zh-cn.json +5 -61
- package/index.css +56 -56
- package/modulefederation.admin.config.d.ts +1 -0
- package/modulefederation.admin.config.js +1 -2
- package/package.json +49 -34
- package/tasksExample.ts +93 -0
- package/build/LegacyConnection.d.ts +0 -938
- package/build/LegacyConnection.js +0 -2849
- package/build/LegacyConnection.js.map +0 -1
- package/tasksExample.js +0 -91
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026, Denis Haev <dogafox@gmail.com>
|
|
3
3
|
*
|
|
4
4
|
* MIT License
|
|
5
5
|
*
|
|
6
6
|
*/
|
|
7
7
|
import React, { Component, createRef } from 'react';
|
|
8
|
-
import
|
|
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, Grid, 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,
|
|
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';
|
|
@@ -24,25 +23,21 @@ import { IconClearFilter } from '../icons/IconClearFilter';
|
|
|
24
23
|
import { Connection } from '../Connection';
|
|
25
24
|
import { Icon } from './Icon';
|
|
26
25
|
import { withWidth } from './withWidth';
|
|
27
|
-
import { Utils } from './Utils'; // @iobroker/
|
|
26
|
+
import { Utils } from './Utils'; // @iobroker/gui-components/Components/Utils
|
|
28
27
|
import { TabContainer } from './TabContainer';
|
|
29
28
|
import { TabContent } from './TabContent';
|
|
30
29
|
import { TabHeader } from './TabHeader';
|
|
31
|
-
|
|
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';
|
|
43
37
|
const COLOR_NAME_CONNECTED_LIGHT = '#098c04';
|
|
44
38
|
const COLOR_NAME_DISCONNECTED_DARK = '#f3ad11';
|
|
45
39
|
const COLOR_NAME_DISCONNECTED_LIGHT = '#6c5008';
|
|
40
|
+
const DEFAULT_DATE_FORMAT = 'YYYY.MM.DD';
|
|
46
41
|
const styles = {
|
|
47
42
|
toolbar: {
|
|
48
43
|
minHeight: 38, // Theme.toolbar.height,
|
|
@@ -232,18 +227,6 @@ const styles = {
|
|
|
232
227
|
height: SMALL_BUTTON_SIZE,
|
|
233
228
|
},
|
|
234
229
|
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
230
|
cellCopyButton: {
|
|
248
231
|
width: SMALL_BUTTON_SIZE,
|
|
249
232
|
height: SMALL_BUTTON_SIZE,
|
|
@@ -508,9 +491,6 @@ const styles = {
|
|
|
508
491
|
height: 24,
|
|
509
492
|
marginRight: 4,
|
|
510
493
|
},
|
|
511
|
-
selectNone: {
|
|
512
|
-
opacity: 0.5,
|
|
513
|
-
},
|
|
514
494
|
itemSelected: (theme) => ({
|
|
515
495
|
background: `${theme.palette.primary.main} !important`,
|
|
516
496
|
color: `${Utils.invertColor(theme.palette.primary.main, true)} !important`,
|
|
@@ -527,25 +507,6 @@ const styles = {
|
|
|
527
507
|
// paddingLeft: 5,
|
|
528
508
|
fontSize: 16,
|
|
529
509
|
},
|
|
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
510
|
visibleButtons: {
|
|
550
511
|
color: '#2196f3',
|
|
551
512
|
opacity: 0.7,
|
|
@@ -615,13 +576,6 @@ const styles = {
|
|
|
615
576
|
nonDraggable: {
|
|
616
577
|
cursor: 'no-drop',
|
|
617
578
|
},
|
|
618
|
-
selectClearButton: {
|
|
619
|
-
position: 'absolute',
|
|
620
|
-
top: 0,
|
|
621
|
-
right: 0,
|
|
622
|
-
borderRadius: 5,
|
|
623
|
-
backgroundColor: 'background.default',
|
|
624
|
-
},
|
|
625
579
|
iconDeviceConnected: (theme) => ({
|
|
626
580
|
color: theme.palette.mode === 'dark' ? COLOR_NAME_CONNECTED_DARK : COLOR_NAME_CONNECTED_LIGHT,
|
|
627
581
|
opacity: 0.8,
|
|
@@ -689,1077 +643,27 @@ const styles = {
|
|
|
689
643
|
color: theme.palette.mode === 'dark' ? '#42eaff' : '#01bbc2',
|
|
690
644
|
}),
|
|
691
645
|
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
|
-
|
|
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
|
-
export function filterRoles(roleArray, type, defaultRoles) {
|
|
793
|
-
const bigRoleArray = [];
|
|
794
|
-
roleArray.forEach(role => (role.type === 'mixed' || role.type) === type &&
|
|
795
|
-
!bigRoleArray.includes(role.role) &&
|
|
796
|
-
bigRoleArray.push(role.role));
|
|
797
|
-
defaultRoles.forEach(role => (role.type === 'mixed' || role.type) === type &&
|
|
798
|
-
!bigRoleArray.includes(role.role) &&
|
|
799
|
-
bigRoleArray.push(role.role));
|
|
800
|
-
bigRoleArray.sort();
|
|
801
|
-
return bigRoleArray;
|
|
802
|
-
}
|
|
803
|
-
/**
|
|
804
|
-
* Function to generate a json-file for an object and trigger download it
|
|
805
|
-
*/
|
|
806
|
-
function generateFile(
|
|
807
|
-
/** The desired filename */
|
|
808
|
-
fileName,
|
|
809
|
-
/** The objects which should be downloaded */
|
|
810
|
-
obj,
|
|
811
|
-
/** Options to filter/reduce the output */
|
|
812
|
-
options) {
|
|
813
|
-
const el = document.createElement('a');
|
|
814
|
-
const filterKeys = [];
|
|
815
|
-
if (options.excludeSystemRepositories) {
|
|
816
|
-
filterKeys.push('system.repositories');
|
|
817
|
-
}
|
|
818
|
-
const filteredObject = filterKeys.length > 0 || options.excludeTranslations
|
|
819
|
-
? filterObject(obj, filterKeys, options.excludeTranslations)
|
|
820
|
-
: obj;
|
|
821
|
-
const data = options.beautify ? JSON.stringify(filteredObject, null, 2) : JSON.stringify(filteredObject);
|
|
822
|
-
el.setAttribute('href', `data:application/json;charset=utf-8,${encodeURIComponent(data)}`);
|
|
823
|
-
el.setAttribute('download', fileName);
|
|
824
|
-
el.style.display = 'none';
|
|
825
|
-
document.body.appendChild(el);
|
|
826
|
-
el.click();
|
|
827
|
-
document.body.removeChild(el);
|
|
828
|
-
}
|
|
829
|
-
// d=data, t=target, s=start, e=end, m=middle
|
|
830
|
-
function binarySearch(list, find, _start, _end) {
|
|
831
|
-
_start ||= 0;
|
|
832
|
-
if (_end === undefined) {
|
|
833
|
-
_end = list.length - 1;
|
|
834
|
-
if (!_end) {
|
|
835
|
-
return list[0] === find;
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
const middle = Math.floor((_start + _end) / 2);
|
|
839
|
-
if (find === list[middle]) {
|
|
840
|
-
return true;
|
|
841
|
-
}
|
|
842
|
-
if (_end - 1 === _start) {
|
|
843
|
-
return list[_start] === find || list[_end] === find;
|
|
844
|
-
}
|
|
845
|
-
if (find > list[middle]) {
|
|
846
|
-
return binarySearch(list, find, middle, _end);
|
|
847
|
-
}
|
|
848
|
-
if (find < list[middle]) {
|
|
849
|
-
return binarySearch(list, find, _start, middle);
|
|
850
|
-
}
|
|
851
|
-
return false;
|
|
852
|
-
}
|
|
853
|
-
function getName(name, lang) {
|
|
854
|
-
if (typeof name === 'object') {
|
|
855
|
-
if (!name) {
|
|
856
|
-
return '';
|
|
857
|
-
}
|
|
858
|
-
return (name[lang] || name.en || '').toString();
|
|
859
|
-
}
|
|
860
|
-
return name ? name.toString() : '';
|
|
861
|
-
}
|
|
862
|
-
export function getSelectIdIconFromObjects(objects, id, lang, imagePrefix) {
|
|
863
|
-
// `admin` has prefix '.' and `web` has '../..'
|
|
864
|
-
imagePrefix ||= '.'; // http://localhost:8081';
|
|
865
|
-
let src = '';
|
|
866
|
-
const _id_ = `system.adapter.${id}`;
|
|
867
|
-
const aIcon = id && objects[_id_]?.common?.icon;
|
|
868
|
-
if (aIcon) {
|
|
869
|
-
// if not BASE64
|
|
870
|
-
if (!aIcon.startsWith('data:image/')) {
|
|
871
|
-
if (aIcon.includes('.')) {
|
|
872
|
-
const name = objects[_id_].common.name;
|
|
873
|
-
if (typeof name === 'object') {
|
|
874
|
-
src = `${imagePrefix}/adapter/${name[lang] || name.en}/${aIcon}`;
|
|
875
|
-
}
|
|
876
|
-
else {
|
|
877
|
-
src = `${imagePrefix}/adapter/${name}/${aIcon}`;
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
else if (aIcon && aIcon.length < 3) {
|
|
881
|
-
return aIcon; // utf-8
|
|
882
|
-
}
|
|
883
|
-
else {
|
|
884
|
-
return null; // '<i class="material-icons iob-list-icon">' + objects[_id_].common.icon + '</i>';
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
else if (aIcon.startsWith('data:image/svg')) {
|
|
888
|
-
src = (React.createElement(SVG, { className: "iconOwn", src: aIcon, width: 28, height: 28 }));
|
|
889
|
-
}
|
|
890
|
-
else {
|
|
891
|
-
src = aIcon;
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
else {
|
|
895
|
-
const common = objects[id] && objects[id].common;
|
|
896
|
-
if (common) {
|
|
897
|
-
const cIcon = common.icon;
|
|
898
|
-
if (cIcon) {
|
|
899
|
-
if (!cIcon.startsWith('data:image/')) {
|
|
900
|
-
if (cIcon.includes('.')) {
|
|
901
|
-
let instance;
|
|
902
|
-
if (objects[id].type === 'instance' || objects[id].type === 'adapter') {
|
|
903
|
-
if (typeof common.name === 'object') {
|
|
904
|
-
src = `${imagePrefix}/adapter/${common.name[lang] || common.name.en}/${cIcon}`;
|
|
905
|
-
}
|
|
906
|
-
else {
|
|
907
|
-
src = `${imagePrefix}/adapter/${common.name}/${cIcon}`;
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
else if (id && id.startsWith('system.adapter.')) {
|
|
911
|
-
instance = id.split('.', 3);
|
|
912
|
-
if (cIcon[0] === '/') {
|
|
913
|
-
instance[2] += cIcon;
|
|
914
|
-
}
|
|
915
|
-
else {
|
|
916
|
-
instance[2] += `/${cIcon}`;
|
|
917
|
-
}
|
|
918
|
-
src = `${imagePrefix}/adapter/${instance[2]}`;
|
|
919
|
-
}
|
|
920
|
-
else {
|
|
921
|
-
instance = id.split('.', 2);
|
|
922
|
-
if (cIcon[0] === '/') {
|
|
923
|
-
instance[0] += cIcon;
|
|
924
|
-
}
|
|
925
|
-
else {
|
|
926
|
-
instance[0] += `/${cIcon}`;
|
|
927
|
-
}
|
|
928
|
-
src = `${imagePrefix}/adapter/${instance[0]}`;
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
else if (aIcon && aIcon.length < 3) {
|
|
932
|
-
return aIcon; // utf-8
|
|
933
|
-
}
|
|
934
|
-
else {
|
|
935
|
-
return null;
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
else if (cIcon.startsWith('data:image/svg')) {
|
|
939
|
-
// if base 64 image
|
|
940
|
-
src = (React.createElement(SVG, { className: "iconOwn", src: cIcon, width: 28, height: 28 }));
|
|
941
|
-
}
|
|
942
|
-
else {
|
|
943
|
-
src = cIcon;
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
return src || null;
|
|
949
|
-
}
|
|
950
|
-
function applyFilter(item, filters, lang, objects, context, counter, customFilter, selectedTypes, _depth) {
|
|
951
|
-
_depth ||= 0;
|
|
952
|
-
let filteredOut = false;
|
|
953
|
-
if (!context) {
|
|
954
|
-
context = {};
|
|
955
|
-
if (filters.id) {
|
|
956
|
-
const id = filters.id.toLowerCase();
|
|
957
|
-
if (id.includes('*')) {
|
|
958
|
-
context.idRx = new RegExp(pattern2RegEx(filters.id), 'i');
|
|
959
|
-
}
|
|
960
|
-
else {
|
|
961
|
-
context.id = id;
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
if (filters.name) {
|
|
965
|
-
const name = filters.name.toLowerCase();
|
|
966
|
-
if (name.includes('*')) {
|
|
967
|
-
context.nameRx = new RegExp(pattern2RegEx(name), 'i');
|
|
968
|
-
}
|
|
969
|
-
else {
|
|
970
|
-
context.name = name;
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
if (filters.type?.length) {
|
|
974
|
-
context.type = filters.type.map(f => f.toLowerCase());
|
|
975
|
-
}
|
|
976
|
-
if (filters.custom?.length) {
|
|
977
|
-
context.custom = filters.custom.map(c => c.toLowerCase());
|
|
978
|
-
}
|
|
979
|
-
if (filters.role?.length) {
|
|
980
|
-
context.role = filters.role.map(r => r.toLowerCase());
|
|
981
|
-
}
|
|
982
|
-
if (filters.room?.length) {
|
|
983
|
-
context.room = [];
|
|
984
|
-
filters.room.forEach(room => {
|
|
985
|
-
context.room = context.room.concat(objects[room]?.common?.members || []);
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
if (filters.func?.length) {
|
|
989
|
-
context.func = [];
|
|
990
|
-
filters.func.forEach(func => {
|
|
991
|
-
context.func = context.func.concat(objects[func]?.common?.members || []);
|
|
992
|
-
});
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
const data = item.data;
|
|
996
|
-
if (data?.id) {
|
|
997
|
-
const common = data.obj?.common;
|
|
998
|
-
if (customFilter) {
|
|
999
|
-
if (customFilter.type) {
|
|
1000
|
-
if (typeof customFilter.type === 'string') {
|
|
1001
|
-
if (!data.obj || customFilter.type !== data.obj.type) {
|
|
1002
|
-
filteredOut = true;
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
else if (Array.isArray(customFilter.type)) {
|
|
1006
|
-
if (!data.obj || !customFilter.type.includes(data.obj.type)) {
|
|
1007
|
-
filteredOut = true;
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
if (!filteredOut && customFilter.common?.type) {
|
|
1012
|
-
if (!common?.type) {
|
|
1013
|
-
filteredOut = true;
|
|
1014
|
-
}
|
|
1015
|
-
else if (typeof customFilter.common.type === 'string') {
|
|
1016
|
-
if (customFilter.common.type !== common.type) {
|
|
1017
|
-
filteredOut = true;
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
else if (Array.isArray(customFilter.common.type)) {
|
|
1021
|
-
if (!customFilter.common.type.includes(common.type)) {
|
|
1022
|
-
filteredOut = true;
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
if (!filteredOut && customFilter.common?.role) {
|
|
1027
|
-
if (!common?.role) {
|
|
1028
|
-
filteredOut = true;
|
|
1029
|
-
}
|
|
1030
|
-
else if (typeof customFilter.common.role === 'string') {
|
|
1031
|
-
if (common.role.startsWith(customFilter.common.role)) {
|
|
1032
|
-
filteredOut = true;
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
else if (Array.isArray(customFilter.common.role)) {
|
|
1036
|
-
if (!customFilter.common.role.find(role => common.role.startsWith(role))) {
|
|
1037
|
-
filteredOut = true;
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
if (!filteredOut && customFilter.common?.custom === '_' && common?.custom) {
|
|
1042
|
-
filteredOut = true;
|
|
1043
|
-
}
|
|
1044
|
-
else if (!filteredOut && customFilter.common?.custom && customFilter.common?.custom !== '_') {
|
|
1045
|
-
const filterOfCustom = customFilter.common.custom;
|
|
1046
|
-
if (!common?.custom) {
|
|
1047
|
-
filteredOut = true;
|
|
1048
|
-
}
|
|
1049
|
-
else if (filterOfCustom === '_dataSources') {
|
|
1050
|
-
// TODO: make it configurable
|
|
1051
|
-
if (!Object.keys(common.custom).find(id => id.startsWith('history.') || id.startsWith('sql.') || id.startsWith('influxdb.'))) {
|
|
1052
|
-
filteredOut = true;
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
else if (Array.isArray(filterOfCustom)) {
|
|
1056
|
-
// here are ['influxdb.', 'telegram.']
|
|
1057
|
-
const customs = Object.keys(common.custom); // here are ['influxdb.0', 'telegram.2']
|
|
1058
|
-
if (filterOfCustom.find(cst => customs.find(id => id.startsWith(cst)))) {
|
|
1059
|
-
filteredOut = true;
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
else if (filterOfCustom !== true &&
|
|
1063
|
-
!Object.keys(common.custom).find(id => id.startsWith(filterOfCustom))) {
|
|
1064
|
-
filteredOut = true;
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
if (!filteredOut && !filters.expertMode) {
|
|
1069
|
-
filteredOut =
|
|
1070
|
-
data.id === 'system' ||
|
|
1071
|
-
data.id === 'enum' ||
|
|
1072
|
-
// (data.obj && data.obj.type === 'meta') ||
|
|
1073
|
-
data.id.startsWith('system.') ||
|
|
1074
|
-
data.id.startsWith('enum.') ||
|
|
1075
|
-
data.id.startsWith('_design/') ||
|
|
1076
|
-
data.id.endsWith('.admin') ||
|
|
1077
|
-
!!common?.expert;
|
|
1078
|
-
}
|
|
1079
|
-
if (!filteredOut && context.id) {
|
|
1080
|
-
if (data.fID === undefined) {
|
|
1081
|
-
data.fID = data.id.toLowerCase();
|
|
1082
|
-
}
|
|
1083
|
-
filteredOut = !data.fID.includes(context.id);
|
|
1084
|
-
}
|
|
1085
|
-
if (!filteredOut && context.idRx) {
|
|
1086
|
-
filteredOut = !context.idRx.test(data.id);
|
|
1087
|
-
}
|
|
1088
|
-
if (!filteredOut && context.name) {
|
|
1089
|
-
if (common) {
|
|
1090
|
-
if (data.fName === undefined) {
|
|
1091
|
-
data.fName = getName(common.name, lang) || '';
|
|
1092
|
-
data.fName = data.fName.toLowerCase();
|
|
1093
|
-
}
|
|
1094
|
-
filteredOut = !data.fName.includes(context.name);
|
|
1095
|
-
}
|
|
1096
|
-
else {
|
|
1097
|
-
filteredOut = true;
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
if (!filteredOut && context.nameRx) {
|
|
1101
|
-
if (common) {
|
|
1102
|
-
if (data.fName === undefined) {
|
|
1103
|
-
data.fName = getName(common.name, lang) || '';
|
|
1104
|
-
data.fName = data.fName.toLowerCase();
|
|
1105
|
-
}
|
|
1106
|
-
filteredOut = !context.nameRx.test(data.fName);
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
if (!filteredOut && filters.role?.length && common) {
|
|
1110
|
-
filteredOut = !(typeof common.role === 'string' && context.role.find(role => common.role.startsWith(role)));
|
|
1111
|
-
}
|
|
1112
|
-
if (!filteredOut && context.room?.length) {
|
|
1113
|
-
filteredOut = !context.room.find(id => id === data.id || data.id.startsWith(`${id}.`));
|
|
1114
|
-
}
|
|
1115
|
-
if (!filteredOut && context.func?.length) {
|
|
1116
|
-
filteredOut = !context.func.find(id => id === data.id || data.id.startsWith(`${id}.`));
|
|
1117
|
-
}
|
|
1118
|
-
if (!filteredOut && context.type?.length) {
|
|
1119
|
-
filteredOut = !(data.obj?.type && context.type.includes(data.obj.type));
|
|
1120
|
-
}
|
|
1121
|
-
if (!filteredOut && selectedTypes) {
|
|
1122
|
-
filteredOut = !(data.obj?.type && selectedTypes.includes(data.obj.type));
|
|
1123
|
-
}
|
|
1124
|
-
if (!filteredOut && context.custom?.length) {
|
|
1125
|
-
if (common) {
|
|
1126
|
-
if (context.custom.includes('_')) {
|
|
1127
|
-
filteredOut = !!common.custom;
|
|
1128
|
-
}
|
|
1129
|
-
else if (common.custom) {
|
|
1130
|
-
filteredOut = !context.custom.find(custom => common.custom[custom]);
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
else {
|
|
1134
|
-
filteredOut = true;
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
data.visible = !filteredOut;
|
|
1139
|
-
data.hasVisibleChildren = false;
|
|
1140
|
-
if (item.children && _depth < 20) {
|
|
1141
|
-
item.children.forEach(_item => {
|
|
1142
|
-
const visible = applyFilter(_item, filters, lang, objects, context, counter, customFilter, selectedTypes, _depth + 1);
|
|
1143
|
-
if (visible) {
|
|
1144
|
-
data.hasVisibleChildren = true;
|
|
1145
|
-
}
|
|
1146
|
-
});
|
|
1147
|
-
}
|
|
1148
|
-
// const visible = data.visible || data.hasVisibleChildren;
|
|
1149
|
-
data.sumVisibility = data.visible || data.hasVisibleChildren; // || data.hasVisibleParent;
|
|
1150
|
-
if (counter && data.sumVisibility) {
|
|
1151
|
-
counter.count++;
|
|
1152
|
-
}
|
|
1153
|
-
// show all children of visible object with opacity 0.5
|
|
1154
|
-
if (data.id && data.sumVisibility && item.children) {
|
|
1155
|
-
item.children.forEach(_item => (_item.data.hasVisibleParent = true));
|
|
1156
|
-
}
|
|
1157
|
-
return data.visible || data.hasVisibleChildren;
|
|
1158
|
-
}
|
|
1159
|
-
function getVisibleItems(item, type, objects, _result) {
|
|
1160
|
-
_result ||= [];
|
|
1161
|
-
const data = item.data;
|
|
1162
|
-
if (data.sumVisibility) {
|
|
1163
|
-
if (data.id && objects[data.id] && (!type || objects[data.id].type === type)) {
|
|
1164
|
-
_result.push(data.id);
|
|
1165
|
-
}
|
|
1166
|
-
item.children?.forEach(_item => getVisibleItems(_item, type, objects, _result));
|
|
1167
|
-
}
|
|
1168
|
-
return _result;
|
|
1169
|
-
}
|
|
1170
|
-
function getSystemIcon(objects, id, level, themeType, lang, imagePrefix) {
|
|
1171
|
-
let icon;
|
|
1172
|
-
// system or design has special icons
|
|
1173
|
-
if (id === 'alias' || id === 'alias.0') {
|
|
1174
|
-
icon = (React.createElement(IconLink, { className: "iconOwn", style: { color: COLOR_NAME_ALIAS(themeType) } }));
|
|
1175
|
-
}
|
|
1176
|
-
else if (id === '0_userdata' || id === '0_userdata.0') {
|
|
1177
|
-
icon = (React.createElement(IconData, { className: "iconOwn", style: { color: COLOR_NAME_USERDATA(themeType) } }));
|
|
1178
|
-
}
|
|
1179
|
-
else if (id.startsWith('_design/') || id === 'system') {
|
|
1180
|
-
icon = (React.createElement(IconSystem, { className: "iconOwn", style: { color: COLOR_NAME_SYSTEM(themeType) } }));
|
|
1181
|
-
}
|
|
1182
|
-
else if (id === 'system.adapter') {
|
|
1183
|
-
icon = (React.createElement(IconSystem, { className: "iconOwn", style: { color: COLOR_NAME_SYSTEM_ADAPTER(themeType) } }));
|
|
1184
|
-
}
|
|
1185
|
-
else if (id === 'system.group') {
|
|
1186
|
-
icon = React.createElement(IconGroup, { className: "iconOwn" });
|
|
1187
|
-
}
|
|
1188
|
-
else if (id === 'system.user') {
|
|
1189
|
-
icon = React.createElement(IconUser, { className: "iconOwn" });
|
|
1190
|
-
}
|
|
1191
|
-
else if (id === 'system.host') {
|
|
1192
|
-
icon = React.createElement(IconHost, { className: "iconOwn" });
|
|
1193
|
-
}
|
|
1194
|
-
else if (id.endsWith('.connection') || id.endsWith('.connected')) {
|
|
1195
|
-
icon = React.createElement(IconConnection, { className: "iconOwn" });
|
|
1196
|
-
}
|
|
1197
|
-
else if (id.endsWith('.info')) {
|
|
1198
|
-
icon = React.createElement(IconInfo, { className: "iconOwn" });
|
|
1199
|
-
}
|
|
1200
|
-
else if (objects[id] && objects[id].type === 'meta') {
|
|
1201
|
-
icon = React.createElement(IconMeta, { className: "iconOwn" });
|
|
1202
|
-
}
|
|
1203
|
-
else if (level < 2) {
|
|
1204
|
-
// detect "cloud.0"
|
|
1205
|
-
if (objects[`system.adapter.${id}`]) {
|
|
1206
|
-
icon = getSelectIdIconFromObjects(objects, `system.adapter.${id}`, lang, imagePrefix);
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
return icon || null;
|
|
1210
|
-
}
|
|
1211
|
-
function getObjectTooltip(data, lang) {
|
|
1212
|
-
if (data?.obj?.common?.desc) {
|
|
1213
|
-
return getName(data.obj.common.desc, lang) || null;
|
|
1214
|
-
}
|
|
1215
|
-
return null;
|
|
1216
|
-
}
|
|
1217
|
-
function getIdFieldTooltip(data, lang) {
|
|
1218
|
-
const tooltip = getObjectTooltip(data, lang);
|
|
1219
|
-
if (tooltip?.startsWith('http')) {
|
|
1220
|
-
return (React.createElement(Box, { component: "a", sx: styles.cellIdTooltipLink, href: tooltip, target: "_blank", rel: "noreferrer" }, tooltip));
|
|
1221
|
-
}
|
|
1222
|
-
return React.createElement("span", { style: styles.cellIdTooltip }, tooltip || data.id || '');
|
|
1223
|
-
}
|
|
1224
|
-
function buildTree(objects, options) {
|
|
1225
|
-
const imagePrefix = options.imagePrefix || '.';
|
|
1226
|
-
let ids = Object.keys(objects);
|
|
1227
|
-
ids.sort((a, b) => {
|
|
1228
|
-
if (a === b) {
|
|
1229
|
-
return 0;
|
|
1230
|
-
}
|
|
1231
|
-
a = a.replace(/\./g, '!!!');
|
|
1232
|
-
b = b.replace(/\./g, '!!!');
|
|
1233
|
-
if (a > b) {
|
|
1234
|
-
return 1;
|
|
1235
|
-
}
|
|
1236
|
-
return -1;
|
|
1237
|
-
});
|
|
1238
|
-
if (options.root) {
|
|
1239
|
-
ids = ids.filter(id => id === options.root || id.startsWith(`${options.root}.`));
|
|
1240
|
-
}
|
|
1241
|
-
// find empty nodes and create names for it
|
|
1242
|
-
let currentPathArr = [];
|
|
1243
|
-
let currentPath = '';
|
|
1244
|
-
let currentPathLen = 0;
|
|
1245
|
-
const root = {
|
|
1246
|
-
data: {
|
|
1247
|
-
name: '',
|
|
1248
|
-
id: '',
|
|
1249
|
-
},
|
|
1250
|
-
children: [],
|
|
1251
|
-
};
|
|
1252
|
-
const info = {
|
|
1253
|
-
funcEnums: [],
|
|
1254
|
-
roomEnums: [],
|
|
1255
|
-
roles: [],
|
|
1256
|
-
ids: [],
|
|
1257
|
-
types: [],
|
|
1258
|
-
objects,
|
|
1259
|
-
customs: ['_'],
|
|
1260
|
-
enums: [],
|
|
1261
|
-
hasSomeCustoms: false,
|
|
1262
|
-
aliasesMap: {},
|
|
1263
|
-
};
|
|
1264
|
-
let cRoot = root;
|
|
1265
|
-
for (let i = 0; i < ids.length; i++) {
|
|
1266
|
-
const id = ids[i];
|
|
1267
|
-
if (!id) {
|
|
1268
|
-
continue;
|
|
1269
|
-
}
|
|
1270
|
-
const obj = objects[id];
|
|
1271
|
-
const parts = id.split('.');
|
|
1272
|
-
if (obj.type && !info.types.includes(obj.type)) {
|
|
1273
|
-
info.types.push(obj.type);
|
|
1274
|
-
}
|
|
1275
|
-
if (obj) {
|
|
1276
|
-
const common = obj.common;
|
|
1277
|
-
const role = common?.role;
|
|
1278
|
-
if (role && !info.roles.find(it => it.role === role)) {
|
|
1279
|
-
if (typeof role !== 'string') {
|
|
1280
|
-
console.warn(`Invalid role type "${typeof role}" in "${obj._id}"`);
|
|
1281
|
-
}
|
|
1282
|
-
else {
|
|
1283
|
-
info.roles.push({ role, type: common.type });
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
else if (id.startsWith('enum.rooms.')) {
|
|
1287
|
-
info.roomEnums.push(id);
|
|
1288
|
-
info.enums.push(id);
|
|
1289
|
-
}
|
|
1290
|
-
else if (id.startsWith('enum.functions.')) {
|
|
1291
|
-
info.funcEnums.push(id);
|
|
1292
|
-
info.enums.push(id);
|
|
1293
|
-
}
|
|
1294
|
-
else if (obj.type === 'enum') {
|
|
1295
|
-
info.enums.push(id);
|
|
1296
|
-
}
|
|
1297
|
-
else if (obj.type === 'instance' && common && (common.supportCustoms || common.adminUI?.custom)) {
|
|
1298
|
-
info.hasSomeCustoms = true;
|
|
1299
|
-
info.customs.push(id.substring('system.adapter.'.length));
|
|
1300
|
-
}
|
|
1301
|
-
// Build a map of aliases
|
|
1302
|
-
if (id.startsWith('alias.') && obj.common.alias?.id) {
|
|
1303
|
-
if (typeof obj.common.alias.id === 'string') {
|
|
1304
|
-
const usedId = obj.common.alias.id;
|
|
1305
|
-
if (!info.aliasesMap[usedId]) {
|
|
1306
|
-
info.aliasesMap[usedId] = [id];
|
|
1307
|
-
}
|
|
1308
|
-
else if (!info.aliasesMap[usedId].includes(id)) {
|
|
1309
|
-
info.aliasesMap[usedId].push(id);
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
else {
|
|
1313
|
-
const readId = obj.common.alias.id.read;
|
|
1314
|
-
if (readId) {
|
|
1315
|
-
if (!info.aliasesMap[readId]) {
|
|
1316
|
-
info.aliasesMap[readId] = [id];
|
|
1317
|
-
}
|
|
1318
|
-
else if (!info.aliasesMap[readId].includes(id)) {
|
|
1319
|
-
info.aliasesMap[readId].push(id);
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
const writeId = obj.common.alias.id.write;
|
|
1323
|
-
if (writeId) {
|
|
1324
|
-
if (!info.aliasesMap[writeId]) {
|
|
1325
|
-
info.aliasesMap[writeId] = [id];
|
|
1326
|
-
}
|
|
1327
|
-
else if (!info.aliasesMap[writeId].includes(id)) {
|
|
1328
|
-
info.aliasesMap[writeId].push(id);
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
info.ids.push(id);
|
|
1335
|
-
let repeat;
|
|
1336
|
-
// if next level
|
|
1337
|
-
do {
|
|
1338
|
-
repeat = false;
|
|
1339
|
-
// If the current level is still OK, and we can add ID to children
|
|
1340
|
-
if (!currentPath || id.startsWith(`${currentPath}.`)) {
|
|
1341
|
-
// if more than one level added
|
|
1342
|
-
if (parts.length - currentPathLen > 1) {
|
|
1343
|
-
let curPath = currentPath;
|
|
1344
|
-
// generate missing levels
|
|
1345
|
-
for (let k = currentPathLen; k < parts.length - 1; k++) {
|
|
1346
|
-
curPath += (curPath ? '.' : '') + parts[k];
|
|
1347
|
-
// level does not exist
|
|
1348
|
-
if (!binarySearch(info.ids, curPath)) {
|
|
1349
|
-
const _cRoot = {
|
|
1350
|
-
data: {
|
|
1351
|
-
name: parts[k],
|
|
1352
|
-
parent: cRoot,
|
|
1353
|
-
id: curPath,
|
|
1354
|
-
obj: objects[curPath],
|
|
1355
|
-
level: k,
|
|
1356
|
-
icon: getSystemIcon(objects, curPath, k, options.themeType, options.lang, imagePrefix),
|
|
1357
|
-
generated: true,
|
|
1358
|
-
},
|
|
1359
|
-
};
|
|
1360
|
-
cRoot.children ||= [];
|
|
1361
|
-
cRoot.children.push(_cRoot);
|
|
1362
|
-
cRoot = _cRoot;
|
|
1363
|
-
info.ids.push(curPath); // IDs will be added by alphabet
|
|
1364
|
-
}
|
|
1365
|
-
else if (cRoot.children) {
|
|
1366
|
-
cRoot = cRoot.children.find(item => item.data.name === parts[k]);
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
const _cRoot = {
|
|
1371
|
-
data: {
|
|
1372
|
-
name: parts[parts.length - 1],
|
|
1373
|
-
title: getName(obj?.common?.name, options.lang),
|
|
1374
|
-
obj,
|
|
1375
|
-
parent: cRoot,
|
|
1376
|
-
icon: getSelectIdIconFromObjects(objects, id, options.lang, imagePrefix) ||
|
|
1377
|
-
getSystemIcon(objects, id, 0, options.themeType, options.lang, imagePrefix),
|
|
1378
|
-
id,
|
|
1379
|
-
hasCustoms: !!(obj.common?.custom && Object.keys(obj.common.custom).length),
|
|
1380
|
-
level: parts.length - 1,
|
|
1381
|
-
generated: false,
|
|
1382
|
-
button: obj.type === 'state' &&
|
|
1383
|
-
!!obj.common?.role &&
|
|
1384
|
-
typeof obj.common.role === 'string' &&
|
|
1385
|
-
obj.common.role.startsWith('button') &&
|
|
1386
|
-
obj.common?.write !== false,
|
|
1387
|
-
switch: obj.type === 'state' &&
|
|
1388
|
-
obj.common?.type === 'boolean' &&
|
|
1389
|
-
obj.common?.write !== false &&
|
|
1390
|
-
obj.common?.read !== false,
|
|
1391
|
-
url: !!obj.common?.role &&
|
|
1392
|
-
typeof obj.common.role === 'string' &&
|
|
1393
|
-
obj.common.role.startsWith('url'),
|
|
1394
|
-
},
|
|
1395
|
-
};
|
|
1396
|
-
cRoot.children ||= [];
|
|
1397
|
-
cRoot.children.push(_cRoot);
|
|
1398
|
-
cRoot = _cRoot;
|
|
1399
|
-
currentPathLen = parts.length;
|
|
1400
|
-
currentPathArr = parts;
|
|
1401
|
-
currentPath = id;
|
|
1402
|
-
}
|
|
1403
|
-
else {
|
|
1404
|
-
let u = 0;
|
|
1405
|
-
while (currentPathArr[u] === parts[u]) {
|
|
1406
|
-
u++;
|
|
1407
|
-
}
|
|
1408
|
-
if (u > 0) {
|
|
1409
|
-
let move = currentPathArr.length;
|
|
1410
|
-
currentPathArr = currentPathArr.splice(0, u);
|
|
1411
|
-
currentPathLen = u;
|
|
1412
|
-
currentPath = currentPathArr.join('.');
|
|
1413
|
-
while (move > u) {
|
|
1414
|
-
if (cRoot.data.parent) {
|
|
1415
|
-
cRoot = cRoot.data.parent;
|
|
1416
|
-
}
|
|
1417
|
-
else {
|
|
1418
|
-
console.error(`Parent is null for ${id} ${currentPath} ${currentPathArr.join('.')}`);
|
|
1419
|
-
}
|
|
1420
|
-
move--;
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
else {
|
|
1424
|
-
cRoot = root;
|
|
1425
|
-
currentPathArr = [];
|
|
1426
|
-
currentPath = '';
|
|
1427
|
-
currentPathLen = 0;
|
|
1428
|
-
}
|
|
1429
|
-
repeat = true;
|
|
1430
|
-
}
|
|
1431
|
-
} while (repeat);
|
|
1432
|
-
}
|
|
1433
|
-
info.roomEnums.sort((a, b) => {
|
|
1434
|
-
const aName = getName(objects[a]?.common?.name, options.lang) || a.split('.').pop();
|
|
1435
|
-
const bName = getName(objects[b]?.common?.name, options.lang) || b.split('.').pop();
|
|
1436
|
-
if (aName > bName) {
|
|
1437
|
-
return 1;
|
|
1438
|
-
}
|
|
1439
|
-
if (aName < bName) {
|
|
1440
|
-
return -1;
|
|
1441
|
-
}
|
|
1442
|
-
return 0;
|
|
1443
|
-
});
|
|
1444
|
-
info.funcEnums.sort((a, b) => {
|
|
1445
|
-
const aName = getName(objects[a]?.common?.name, options.lang) || a.split('.').pop();
|
|
1446
|
-
const bName = getName(objects[b]?.common?.name, options.lang) || b.split('.').pop();
|
|
1447
|
-
if (aName > bName) {
|
|
1448
|
-
return 1;
|
|
1449
|
-
}
|
|
1450
|
-
if (aName < bName) {
|
|
1451
|
-
return -1;
|
|
1452
|
-
}
|
|
1453
|
-
return 0;
|
|
1454
|
-
});
|
|
1455
|
-
info.roles.sort((a, b) => a.role.localeCompare(b.role));
|
|
1456
|
-
info.types.sort();
|
|
1457
|
-
return { info, root };
|
|
1458
|
-
}
|
|
1459
|
-
function findNode(root, id, _parts, _path, _level) {
|
|
1460
|
-
if (root.data.id === id) {
|
|
1461
|
-
return root;
|
|
1462
|
-
}
|
|
1463
|
-
if (!_parts) {
|
|
1464
|
-
_parts = id.split('.');
|
|
1465
|
-
_level = 0;
|
|
1466
|
-
_path = _parts[_level];
|
|
1467
|
-
}
|
|
1468
|
-
if (!root.children && root.data.id !== id) {
|
|
1469
|
-
return null;
|
|
1470
|
-
}
|
|
1471
|
-
let found;
|
|
1472
|
-
if (root.children) {
|
|
1473
|
-
for (let i = 0; i < root.children.length; i++) {
|
|
1474
|
-
const _id = root.children[i].data.id;
|
|
1475
|
-
if (_id === _path) {
|
|
1476
|
-
found = root.children[i];
|
|
1477
|
-
break;
|
|
1478
|
-
}
|
|
1479
|
-
else if (_id > _path) {
|
|
1480
|
-
break;
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
if (found) {
|
|
1485
|
-
_level ||= 0;
|
|
1486
|
-
return findNode(found, id, _parts, `${_path}.${_parts[_level + 1]}`, _level + 1);
|
|
1487
|
-
}
|
|
1488
|
-
return null;
|
|
1489
|
-
}
|
|
1490
|
-
function findRoomsForObject(info, id, lang, rooms) {
|
|
1491
|
-
if (!id) {
|
|
1492
|
-
return { rooms: [], per: false };
|
|
1493
|
-
}
|
|
1494
|
-
rooms ||= [];
|
|
1495
|
-
for (const room of info.roomEnums) {
|
|
1496
|
-
const common = info.objects[room]?.common;
|
|
1497
|
-
if (!common) {
|
|
1498
|
-
continue;
|
|
1499
|
-
}
|
|
1500
|
-
const name = getName(common.name, lang);
|
|
1501
|
-
if (common.members?.includes(id) && !rooms.includes(name)) {
|
|
1502
|
-
rooms.push(name);
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
let ownEnums;
|
|
1506
|
-
// Check parent
|
|
1507
|
-
const parts = id.split('.');
|
|
1508
|
-
parts.pop();
|
|
1509
|
-
id = parts.join('.');
|
|
1510
|
-
if (info.objects[id]) {
|
|
1511
|
-
ownEnums = rooms.length;
|
|
1512
|
-
findRoomsForObject(info, id, lang, rooms);
|
|
1513
|
-
}
|
|
1514
|
-
return { rooms, per: !ownEnums }; // per is if the enums are from parent
|
|
1515
|
-
}
|
|
1516
|
-
function findEnumsForObjectAsIds(info, id, enumName, funcs) {
|
|
1517
|
-
if (!id) {
|
|
1518
|
-
return [];
|
|
1519
|
-
}
|
|
1520
|
-
funcs ||= [];
|
|
1521
|
-
for (let i = 0; i < info[enumName].length; i++) {
|
|
1522
|
-
const common = info.objects[info[enumName][i]]?.common;
|
|
1523
|
-
if (common?.members?.includes(id) && !funcs.includes(info[enumName][i])) {
|
|
1524
|
-
funcs.push(info[enumName][i]);
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
funcs.sort();
|
|
1528
|
-
return funcs;
|
|
1529
|
-
}
|
|
1530
|
-
function findFunctionsForObject(info, id, lang, funcs) {
|
|
1531
|
-
if (!id) {
|
|
1532
|
-
return { funcs: [], pef: false };
|
|
1533
|
-
}
|
|
1534
|
-
funcs ||= [];
|
|
1535
|
-
for (let i = 0; i < info.funcEnums.length; i++) {
|
|
1536
|
-
const common = info.objects[info.funcEnums[i]]?.common;
|
|
1537
|
-
if (!common) {
|
|
1538
|
-
continue;
|
|
1539
|
-
}
|
|
1540
|
-
const name = getName(common.name, lang);
|
|
1541
|
-
if (common.members?.includes(id) && !funcs.includes(name)) {
|
|
1542
|
-
funcs.push(name);
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
let ownEnums;
|
|
1546
|
-
// Check parent
|
|
1547
|
-
const parts = id.split('.');
|
|
1548
|
-
parts.pop();
|
|
1549
|
-
id = parts.join('.');
|
|
1550
|
-
if (info.objects[id]) {
|
|
1551
|
-
ownEnums = funcs.length;
|
|
1552
|
-
findFunctionsForObject(info, id, lang, funcs);
|
|
1553
|
-
}
|
|
1554
|
-
return { funcs, pef: !ownEnums };
|
|
1555
|
-
}
|
|
1556
|
-
/*
|
|
1557
|
-
function quality2text(q) {
|
|
1558
|
-
if (!q) {
|
|
1559
|
-
return 'ok';
|
|
1560
|
-
}
|
|
1561
|
-
const custom = q & 0xFFFF0000;
|
|
1562
|
-
let text = '';
|
|
1563
|
-
if (q & 0x40) text += 'device';
|
|
1564
|
-
if (q & 0x80) text += 'sensor';
|
|
1565
|
-
if (q & 0x01) text += ' bad';
|
|
1566
|
-
if (q & 0x02) text += ' not connected';
|
|
1567
|
-
if (q & 0x04) text += ' error';
|
|
1568
|
-
|
|
1569
|
-
return text + (custom ? '|0x' + (custom >> 16).toString(16).toUpperCase() : '') + ' [0x' + q.toString(16).toUpperCase() + ']';
|
|
1570
|
-
}
|
|
1571
|
-
*/
|
|
1572
|
-
/**
|
|
1573
|
-
* Format a state value for visualization
|
|
1574
|
-
*/
|
|
1575
|
-
function formatValue(options) {
|
|
1576
|
-
const { dateFormat, state, isFloatComma, texts, obj } = options;
|
|
1577
|
-
const states = Utils.getStates(obj);
|
|
1578
|
-
const isCommon = obj.common;
|
|
1579
|
-
let fileViewer;
|
|
1580
|
-
let v =
|
|
1581
|
-
// @ts-expect-error deprecated from js-controller 6
|
|
1582
|
-
isCommon?.type === 'file'
|
|
1583
|
-
? '[file]'
|
|
1584
|
-
: !state || state.val === null
|
|
1585
|
-
? '(null)'
|
|
1586
|
-
: state.val === undefined
|
|
1587
|
-
? '[undef]'
|
|
1588
|
-
: state.val;
|
|
1589
|
-
const type = typeof v;
|
|
1590
|
-
if (isCommon?.role && typeof isCommon.role === 'string' && isCommon.role.match(/^value\.time|^date/)) {
|
|
1591
|
-
if (v && typeof v === 'string') {
|
|
1592
|
-
if (Utils.isStringInteger(v)) {
|
|
1593
|
-
// we assume a unix ts
|
|
1594
|
-
v = new Date(parseInt(v, 10)).toString();
|
|
1595
|
-
}
|
|
1596
|
-
else {
|
|
1597
|
-
// check if parsable by new date
|
|
1598
|
-
try {
|
|
1599
|
-
const parsedDate = new Date(v);
|
|
1600
|
-
if (Utils.isValidDate(parsedDate)) {
|
|
1601
|
-
v = parsedDate.toString();
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
catch {
|
|
1605
|
-
// ignore
|
|
1606
|
-
}
|
|
1607
|
-
}
|
|
1608
|
-
}
|
|
1609
|
-
else {
|
|
1610
|
-
if (v > 946681200 && v < 946681200000) {
|
|
1611
|
-
// '2000-01-01T00:00:00' => 946681200000
|
|
1612
|
-
v *= 1_000; // maybe the time is in seconds (UNIX time)
|
|
1613
|
-
}
|
|
1614
|
-
// "null" and undefined could not be here. See `let v = (isCommon && isCommon.type === 'file') ....` above
|
|
1615
|
-
v = v ? new Date(v).toString() : v;
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
else if (isCommon?.role && typeof isCommon.role === 'string' && isCommon.role.match(/^value\.duration/)) {
|
|
1619
|
-
// Format duration values in HH:mm:ss format
|
|
1620
|
-
if (typeof v === 'number' && v >= 0) {
|
|
1621
|
-
const hours = Math.floor(v / 3600);
|
|
1622
|
-
const minutes = Math.floor((v % 3600) / 60);
|
|
1623
|
-
const seconds = Math.floor(v % 60);
|
|
1624
|
-
v = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
|
1625
|
-
}
|
|
1626
|
-
else if (typeof v === 'string' && Utils.isStringInteger(v)) {
|
|
1627
|
-
const numValue = parseInt(v, 10);
|
|
1628
|
-
if (numValue >= 0) {
|
|
1629
|
-
const hours = Math.floor(numValue / 3600);
|
|
1630
|
-
const minutes = Math.floor((numValue % 3600) / 60);
|
|
1631
|
-
const seconds = Math.floor(numValue % 60);
|
|
1632
|
-
v = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1636
|
-
else {
|
|
1637
|
-
if (type === 'number') {
|
|
1638
|
-
if (!Number.isInteger(v)) {
|
|
1639
|
-
v = Math.round(v * 100_000_000) / 100_000_000; // remove 4.00000000000000001
|
|
1640
|
-
if (isFloatComma) {
|
|
1641
|
-
v = v.toString().replace('.', ',');
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
else if (type === 'object') {
|
|
1646
|
-
v = JSON.stringify(v);
|
|
1647
|
-
}
|
|
1648
|
-
else if (type !== 'string') {
|
|
1649
|
-
v = v.toString();
|
|
1650
|
-
}
|
|
1651
|
-
else if (v.startsWith('data:image/')) {
|
|
1652
|
-
fileViewer = 'image';
|
|
1653
|
-
}
|
|
1654
|
-
if (typeof v !== 'string') {
|
|
1655
|
-
v = v.toString();
|
|
1656
|
-
}
|
|
1657
|
-
}
|
|
1658
|
-
const valText = { v: v };
|
|
1659
|
-
// try to replace number with "common.states"
|
|
1660
|
-
if (states && states[v] !== undefined) {
|
|
1661
|
-
if (v !== states[v]) {
|
|
1662
|
-
valText.s = v;
|
|
1663
|
-
v = states[v];
|
|
1664
|
-
valText.v = v;
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
|
-
if (valText.v?.length > 40) {
|
|
1668
|
-
valText.c = valText.v;
|
|
1669
|
-
valText.v = `${valText.v.substring(0, 40)}...`;
|
|
1670
|
-
}
|
|
1671
|
-
if (isCommon?.unit) {
|
|
1672
|
-
valText.u = isCommon.unit;
|
|
1673
|
-
}
|
|
1674
|
-
let valFull;
|
|
1675
|
-
if (options.full) {
|
|
1676
|
-
if (typeof v === 'string' && v.length > 100) {
|
|
1677
|
-
valFull = [{ t: texts.value, v: `${v.substring(0, 100)}...` }];
|
|
1678
|
-
}
|
|
1679
|
-
else {
|
|
1680
|
-
valFull = [{ t: texts.value, v }];
|
|
1681
|
-
}
|
|
1682
|
-
if (state) {
|
|
1683
|
-
if (state.ack !== undefined && state.ack !== null) {
|
|
1684
|
-
valFull.push({ t: texts.ack, v: state.ack.toString() });
|
|
1685
|
-
}
|
|
1686
|
-
if (state.ts) {
|
|
1687
|
-
valFull.push({ t: texts.ts, v: state.ts ? Utils.formatDate(new Date(state.ts), dateFormat) : '' });
|
|
1688
|
-
}
|
|
1689
|
-
if (state.lc) {
|
|
1690
|
-
valFull.push({ t: texts.lc, v: state.lc ? Utils.formatDate(new Date(state.lc), dateFormat) : '' });
|
|
1691
|
-
}
|
|
1692
|
-
if (state.from) {
|
|
1693
|
-
let from = state.from.toString();
|
|
1694
|
-
if (from.startsWith('system.adapter.')) {
|
|
1695
|
-
from = from.substring(15);
|
|
1696
|
-
}
|
|
1697
|
-
valFull.push({ t: texts.from, v: from });
|
|
1698
|
-
}
|
|
1699
|
-
if (state.user) {
|
|
1700
|
-
let user = state.user.toString();
|
|
1701
|
-
if (user.startsWith('system.user.')) {
|
|
1702
|
-
user = user.substring(12);
|
|
1703
|
-
}
|
|
1704
|
-
valFull.push({ t: texts.user, v: user });
|
|
1705
|
-
}
|
|
1706
|
-
if (state.c) {
|
|
1707
|
-
valFull.push({ t: texts.c, v: state.c });
|
|
1708
|
-
}
|
|
1709
|
-
valFull.push({ t: texts.quality, v: Utils.quality2text(state.q || 0).join(', '), nbr: true });
|
|
1710
|
-
}
|
|
1711
|
-
}
|
|
1712
|
-
return {
|
|
1713
|
-
valText,
|
|
1714
|
-
valFull,
|
|
1715
|
-
fileViewer,
|
|
1716
|
-
};
|
|
1717
|
-
}
|
|
1718
|
-
/**
|
|
1719
|
-
* Get CSS style for given state value
|
|
1720
|
-
*/
|
|
1721
|
-
function getValueStyle(options) {
|
|
1722
|
-
const { state /* , isExpertMode, isButton */ } = options;
|
|
1723
|
-
const color = state?.ack ? (state.q ? '#ffa500' : '') : '#ff2222c9';
|
|
1724
|
-
// do not show the color of the button in non-expert mode
|
|
1725
|
-
// if (!isExpertMode && isButton) {
|
|
1726
|
-
// color = '';
|
|
1727
|
-
// }
|
|
1728
|
-
return { color };
|
|
1729
|
-
}
|
|
1730
|
-
function prepareSparkData(values, from) {
|
|
1731
|
-
// set one point every hour
|
|
1732
|
-
let time = from;
|
|
1733
|
-
let i = 1;
|
|
1734
|
-
const v = [];
|
|
1735
|
-
while (i < values.length && time < from + 25 * 3600000) {
|
|
1736
|
-
// find the interval
|
|
1737
|
-
while (values[i - 1].ts < time && time <= values[i].ts && i < values.length) {
|
|
1738
|
-
i++;
|
|
1739
|
-
}
|
|
1740
|
-
if (i === 1 && values[i - 1].ts >= time) {
|
|
1741
|
-
// assume the value was always null
|
|
1742
|
-
v.push(0);
|
|
1743
|
-
}
|
|
1744
|
-
else if (i < values.length) {
|
|
1745
|
-
if (typeof values[i].val === 'boolean' || typeof values[i - 1].val === 'boolean') {
|
|
1746
|
-
v.push(values[i].val ? 1 : 0);
|
|
1747
|
-
}
|
|
1748
|
-
else {
|
|
1749
|
-
// remove nulls
|
|
1750
|
-
values[i - 1].val ||= 0;
|
|
1751
|
-
values[i].val ||= 0;
|
|
1752
|
-
// interpolate
|
|
1753
|
-
const nm1 = values[i - 1].val;
|
|
1754
|
-
const n = values[i].val;
|
|
1755
|
-
const val = nm1 + ((n - nm1) * (time - values[i - 1].ts)) / (values[i].ts - values[i - 1].ts);
|
|
1756
|
-
v.push(val);
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
time += 3600000;
|
|
1760
|
-
}
|
|
1761
|
-
return v;
|
|
1762
|
-
}
|
|
646
|
+
color: theme.palette.mode === 'dark' ? '#e079ff' : '#500070',
|
|
647
|
+
}),
|
|
648
|
+
contextMenuRoom: (theme) => ({
|
|
649
|
+
color: theme.palette.mode === 'dark' ? '#ff9a33' : '#642a00',
|
|
650
|
+
}),
|
|
651
|
+
contextMenuRole: (theme) => ({
|
|
652
|
+
color: theme.palette.mode === 'dark' ? '#ffdb43' : '#562d00',
|
|
653
|
+
}),
|
|
654
|
+
contextMenuDelete: (theme) => ({
|
|
655
|
+
color: theme.palette.mode === 'dark' ? '#ff4f4f' : '#cf0000',
|
|
656
|
+
}),
|
|
657
|
+
contextMenuKeys: {
|
|
658
|
+
marginLeft: 8,
|
|
659
|
+
opacity: 0.7,
|
|
660
|
+
fontSize: 'smaller',
|
|
661
|
+
},
|
|
662
|
+
contextMenuWithSubMenu: {
|
|
663
|
+
display: 'flex',
|
|
664
|
+
},
|
|
665
|
+
...utilStyles,
|
|
666
|
+
};
|
|
1763
667
|
export const ITEM_IMAGES = {
|
|
1764
668
|
state: (React.createElement(IconState, { className: "itemIcon", style: { verticalAlign: 'middle' } })),
|
|
1765
669
|
channel: (React.createElement(IconChannel, { className: "itemIcon", style: { verticalAlign: 'middle' } })),
|
|
@@ -1882,18 +786,19 @@ export class ObjectBrowserClass extends Component {
|
|
|
1882
786
|
aliasesMap: {},
|
|
1883
787
|
};
|
|
1884
788
|
localStorage = window._localStorage || window.localStorage;
|
|
1885
|
-
lastAppliedFilter = null;
|
|
1886
789
|
tableRef;
|
|
1887
|
-
filterRefs;
|
|
1888
790
|
pausedSubscribes = false;
|
|
1889
791
|
selectFirst;
|
|
792
|
+
/** Last navigation that was applied from `navigateTo` or reported via `onNavigateTo` (loop guard). */
|
|
793
|
+
lastNav = null;
|
|
794
|
+
/** True while applying `navigateTo`, so the derived-state watcher does not echo it back. */
|
|
795
|
+
applyingNav = false;
|
|
1890
796
|
root = null;
|
|
1891
797
|
states = {};
|
|
1892
798
|
subscribes = [];
|
|
1893
799
|
unsubscribeTimer = null;
|
|
1894
800
|
statesUpdateTimer = null;
|
|
1895
801
|
objectsUpdateTimer = null;
|
|
1896
|
-
filterTimer = null;
|
|
1897
802
|
visibleCols;
|
|
1898
803
|
texts;
|
|
1899
804
|
possibleCols;
|
|
@@ -1921,22 +826,22 @@ export class ObjectBrowserClass extends Component {
|
|
|
1921
826
|
resizerActiveDiv = null;
|
|
1922
827
|
resizerNextDiv = null;
|
|
1923
828
|
storedWidths = null;
|
|
1924
|
-
systemConfig;
|
|
829
|
+
systemConfig = null;
|
|
1925
830
|
objects;
|
|
1926
831
|
defaultHistory = '';
|
|
1927
|
-
|
|
832
|
+
ctrlPressed = false;
|
|
1928
833
|
columnsVisibility = {};
|
|
1929
834
|
changedIds = null;
|
|
1930
835
|
contextMenu = null;
|
|
1931
836
|
recordStates = [];
|
|
1932
837
|
styles = {};
|
|
838
|
+
expertMode = false;
|
|
1933
839
|
customColumnDialog = null;
|
|
1934
|
-
/** Namespaces which are allowed to be edited by non-expert users */
|
|
1935
|
-
static NON_EXPERT_NAMESPACES = ['0_userdata.0.', 'alias.0.'];
|
|
1936
840
|
constructor(props) {
|
|
1937
841
|
super(props);
|
|
1938
842
|
const lastSelectedItemStr = this.localStorage.getItem(`${props.dialogName || 'App'}.objectSelected`) || '';
|
|
1939
843
|
this.selectFirst = '';
|
|
844
|
+
this.expertMode = !!this.props.expertMode;
|
|
1940
845
|
if (lastSelectedItemStr.startsWith('[')) {
|
|
1941
846
|
try {
|
|
1942
847
|
const lastSelectedItems = JSON.parse(lastSelectedItemStr);
|
|
@@ -1976,20 +881,35 @@ export class ObjectBrowserClass extends Component {
|
|
|
1976
881
|
filter = { ...DEFAULT_FILTER };
|
|
1977
882
|
}
|
|
1978
883
|
// Migrate old filters to new one
|
|
1979
|
-
if (typeof filter.room === 'string') {
|
|
1980
|
-
filter.room = [filter.room];
|
|
884
|
+
if (typeof filter.room === 'string' && filter.room) {
|
|
885
|
+
filter.room = [filter.room].filter(s => s);
|
|
886
|
+
if (!filter.room.length) {
|
|
887
|
+
delete filter.room;
|
|
888
|
+
}
|
|
1981
889
|
}
|
|
1982
|
-
if (typeof filter.func === 'string') {
|
|
1983
|
-
filter.func = [filter.func];
|
|
890
|
+
if (typeof filter.func === 'string' && filter.func) {
|
|
891
|
+
filter.func = [filter.func].filter(s => s);
|
|
892
|
+
if (!filter.func.length) {
|
|
893
|
+
delete filter.func;
|
|
894
|
+
}
|
|
1984
895
|
}
|
|
1985
|
-
if (typeof filter.role === 'string') {
|
|
1986
|
-
filter.role = [filter.role];
|
|
896
|
+
if (typeof filter.role === 'string' && filter.role) {
|
|
897
|
+
filter.role = [filter.role].filter(s => s);
|
|
898
|
+
if (!filter.role.length) {
|
|
899
|
+
delete filter.role;
|
|
900
|
+
}
|
|
1987
901
|
}
|
|
1988
902
|
if (typeof filter.type === 'string') {
|
|
1989
|
-
filter.type = [filter.type];
|
|
903
|
+
filter.type = [filter.type].filter(s => s);
|
|
904
|
+
if (!filter.type.length) {
|
|
905
|
+
delete filter.type;
|
|
906
|
+
}
|
|
1990
907
|
}
|
|
1991
908
|
if (typeof filter.custom === 'string') {
|
|
1992
|
-
filter.custom = [filter.custom];
|
|
909
|
+
filter.custom = [filter.custom].filter(s => s);
|
|
910
|
+
if (!filter.custom.length) {
|
|
911
|
+
delete filter.custom;
|
|
912
|
+
}
|
|
1993
913
|
}
|
|
1994
914
|
filter.expertMode =
|
|
1995
915
|
props.expertMode !== undefined
|
|
@@ -1997,8 +917,6 @@ export class ObjectBrowserClass extends Component {
|
|
|
1997
917
|
: (window._sessionStorage || window.sessionStorage).getItem('App.expertMode') ===
|
|
1998
918
|
'true';
|
|
1999
919
|
this.tableRef = createRef();
|
|
2000
|
-
this.filterRefs = {};
|
|
2001
|
-
Object.keys(DEFAULT_FILTER).forEach(name => (this.filterRefs[name] = createRef()));
|
|
2002
920
|
this.visibleCols = props.columns || SCREEN_WIDTHS[props.width || 'lg'].fields;
|
|
2003
921
|
// remove type column if only one type must be selected
|
|
2004
922
|
if (props.types && props.types.length === 1) {
|
|
@@ -2062,46 +980,48 @@ export class ObjectBrowserClass extends Component {
|
|
|
2062
980
|
// ignore
|
|
2063
981
|
}
|
|
2064
982
|
this.state = {
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
selected,
|
|
2068
|
-
selectedNonObject: this.localStorage.getItem(`${props.dialogName || 'App'}.selectedNonObject`) || '',
|
|
2069
|
-
filter,
|
|
2070
|
-
filterKey: 0,
|
|
2071
|
-
focused: this.localStorage.getItem(`${props.dialogName || 'App'}.focused`) || '',
|
|
2072
|
-
depth: 0,
|
|
2073
|
-
expandAllVisible: false,
|
|
2074
|
-
expanded,
|
|
2075
|
-
toast: '',
|
|
2076
|
-
scrollBarWidth: 16,
|
|
2077
|
-
customDialog,
|
|
2078
|
-
editObjectDialog: '',
|
|
2079
|
-
editObjectAlias: false, // open the edit object dialog on alias tab
|
|
2080
|
-
viewFileDialog: '',
|
|
2081
|
-
showAliasEditor: '',
|
|
2082
|
-
enumDialog: null,
|
|
2083
|
-
roleDialog: null,
|
|
2084
|
-
statesView,
|
|
983
|
+
aliasMenu: '',
|
|
984
|
+
beautifyJsonExport: true,
|
|
2085
985
|
columns,
|
|
2086
|
-
columnsForAdmin: null,
|
|
2087
|
-
columnsSelectorShow: false,
|
|
2088
986
|
columnsAuto: this.localStorage.getItem(`${props.dialogName || 'App'}.columnsAuto`) !== 'false',
|
|
2089
|
-
columnsWidths,
|
|
2090
987
|
columnsDialogTransparent: 100,
|
|
2091
988
|
columnsEditCustomDialog: null,
|
|
989
|
+
columnsForAdmin: null,
|
|
990
|
+
columnsSelectorShow: false,
|
|
991
|
+
columnsWidths,
|
|
2092
992
|
customColumnDialogValueChanged: false,
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
noStatesByExportImport: false,
|
|
2099
|
-
beautifyJsonExport: true,
|
|
993
|
+
customDialog,
|
|
994
|
+
depth: 0,
|
|
995
|
+
editObjectAlias: false, // open the edit object dialog on alias tab
|
|
996
|
+
editObjectDialog: '',
|
|
997
|
+
enumDialog: null,
|
|
2100
998
|
excludeSystemRepositoriesFromExport: true,
|
|
2101
999
|
excludeTranslations: false,
|
|
2102
|
-
|
|
2103
|
-
|
|
1000
|
+
expandAllVisible: false,
|
|
1001
|
+
expanded,
|
|
1002
|
+
filter,
|
|
1003
|
+
filterKey: 0,
|
|
1004
|
+
focused: this.localStorage.getItem(`${props.dialogName || 'App'}.focused`) || '',
|
|
1005
|
+
foldersFirst,
|
|
1006
|
+
linesEnabled: this.localStorage.getItem(`${props.dialogName || 'App'}.lines`) === 'true',
|
|
1007
|
+
loaded: false,
|
|
1008
|
+
noStatesByExportImport: false,
|
|
1009
|
+
roleDialog: null,
|
|
1010
|
+
scrollBarWidth: 16,
|
|
1011
|
+
selected,
|
|
1012
|
+
selectedNonObject: this.localStorage.getItem(`${props.dialogName || 'App'}.selectedNonObject`) || '',
|
|
1013
|
+
showAliasEditor: '',
|
|
1014
|
+
showAllExportOptions: false,
|
|
1015
|
+
showContextMenu: null,
|
|
1016
|
+
showDescription: this.localStorage.getItem(`${props.dialogName || 'App'}.desc`) !== 'false',
|
|
1017
|
+
showExportDialog: false,
|
|
1018
|
+
showImportDialog: false,
|
|
1019
|
+
showImportMenu: null,
|
|
2104
1020
|
showRenameDialog: null,
|
|
1021
|
+
statesView,
|
|
1022
|
+
toast: '',
|
|
1023
|
+
tooltipInfo: null,
|
|
1024
|
+
viewFileDialog: '',
|
|
2105
1025
|
};
|
|
2106
1026
|
this.texts = {
|
|
2107
1027
|
name: props.t('ra_Name'),
|
|
@@ -2210,22 +1130,19 @@ export class ObjectBrowserClass extends Component {
|
|
|
2210
1130
|
Object.assign(objects || {}, moreObjects);
|
|
2211
1131
|
}
|
|
2212
1132
|
}
|
|
2213
|
-
this.systemConfig
|
|
2214
|
-
|
|
2215
|
-
objects?.['system.config'] ||
|
|
1133
|
+
this.systemConfig ||=
|
|
1134
|
+
objects?.['system.config'] ||
|
|
2216
1135
|
(await props.socket.getObject('system.config'));
|
|
2217
|
-
this.systemConfig.common
|
|
2218
|
-
this.systemConfig.common.defaultNewAcl
|
|
1136
|
+
this.systemConfig.common ||= {};
|
|
1137
|
+
this.systemConfig.common.defaultNewAcl ||= {
|
|
2219
1138
|
object: 0,
|
|
2220
1139
|
state: 0,
|
|
2221
1140
|
file: 0,
|
|
2222
1141
|
owner: 'system.user.admin',
|
|
2223
1142
|
ownerGroup: 'system.group.administrator',
|
|
2224
1143
|
};
|
|
2225
|
-
this.systemConfig.common.defaultNewAcl.owner
|
|
2226
|
-
|
|
2227
|
-
this.systemConfig.common.defaultNewAcl.ownerGroup =
|
|
2228
|
-
this.systemConfig.common.defaultNewAcl.ownerGroup || 'system.group.administrator';
|
|
1144
|
+
this.systemConfig.common.defaultNewAcl.owner ||= 'system.user.admin';
|
|
1145
|
+
this.systemConfig.common.defaultNewAcl.ownerGroup ||= 'system.group.administrator';
|
|
2229
1146
|
if (typeof this.systemConfig.common.defaultNewAcl.state !== 'number') {
|
|
2230
1147
|
// TODO: may be convert here from string
|
|
2231
1148
|
this.systemConfig.common.defaultNewAcl.state = 0x664;
|
|
@@ -2308,31 +1225,30 @@ export class ObjectBrowserClass extends Component {
|
|
|
2308
1225
|
this.info = info;
|
|
2309
1226
|
// Show first selected item
|
|
2310
1227
|
const node = this.state.selected?.length && findNode(this.root, this.state.selected[0]);
|
|
2311
|
-
this.lastAppliedFilter = null;
|
|
2312
1228
|
// If the selected ID is not visible, reset filter
|
|
2313
1229
|
if (node &&
|
|
2314
1230
|
!applyFilter(node, this.state.filter, props.lang, this.objects, undefined, undefined, props.customFilter, props.types)) {
|
|
2315
1231
|
// reset filter
|
|
2316
1232
|
this.setState({ filter: { ...DEFAULT_FILTER }, columnsForAdmin }, () => {
|
|
2317
|
-
this.
|
|
1233
|
+
this.doFilter();
|
|
1234
|
+
this.setState({ loaded: true, updating: false }, () => this.expandAllSelected(() => {
|
|
1235
|
+
this.onAfterSelect();
|
|
1236
|
+
this.applyInitialNavigateTo();
|
|
1237
|
+
}));
|
|
2318
1238
|
});
|
|
2319
1239
|
}
|
|
2320
1240
|
else {
|
|
2321
|
-
this.
|
|
1241
|
+
this.doFilter();
|
|
1242
|
+
this.setState({ loaded: true, updating: false, columnsForAdmin }, () => this.expandAllSelected(() => {
|
|
1243
|
+
this.onAfterSelect();
|
|
1244
|
+
this.applyInitialNavigateTo();
|
|
1245
|
+
}));
|
|
2322
1246
|
}
|
|
2323
1247
|
}
|
|
2324
1248
|
catch (error) {
|
|
2325
1249
|
this.showError(error);
|
|
2326
1250
|
}
|
|
2327
1251
|
}
|
|
2328
|
-
/**
|
|
2329
|
-
* Check if it is a non-expert id
|
|
2330
|
-
*/
|
|
2331
|
-
static isNonExpertId(
|
|
2332
|
-
/** id to test */
|
|
2333
|
-
id) {
|
|
2334
|
-
return !!ObjectBrowserClass.NON_EXPERT_NAMESPACES.find(saveNamespace => id.startsWith(saveNamespace));
|
|
2335
|
-
}
|
|
2336
1252
|
expandAllSelected(cb) {
|
|
2337
1253
|
const expanded = [...this.state.expanded];
|
|
2338
1254
|
let changed = false;
|
|
@@ -2421,6 +1337,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
2421
1337
|
}
|
|
2422
1338
|
}
|
|
2423
1339
|
}
|
|
1340
|
+
// This function is used
|
|
2424
1341
|
static getDerivedStateFromProps(props, state) {
|
|
2425
1342
|
const newState = {};
|
|
2426
1343
|
let changed = false;
|
|
@@ -2454,14 +1371,14 @@ export class ObjectBrowserClass extends Component {
|
|
|
2454
1371
|
}
|
|
2455
1372
|
}
|
|
2456
1373
|
onKeyPress = (event) => {
|
|
2457
|
-
if (event.type === 'keydown' && event.ctrlKey && !this.
|
|
2458
|
-
this.
|
|
1374
|
+
if (event.type === 'keydown' && event.ctrlKey && !this.ctrlPressed) {
|
|
1375
|
+
this.ctrlPressed = true;
|
|
2459
1376
|
if (this.tableRef.current) {
|
|
2460
1377
|
this.tableRef.current.className = 'highlight-link';
|
|
2461
1378
|
}
|
|
2462
1379
|
}
|
|
2463
|
-
else if (event.type === 'keyup' && !event.ctrlKey && this.
|
|
2464
|
-
this.
|
|
1380
|
+
else if (event.type === 'keyup' && !event.ctrlKey && this.ctrlPressed) {
|
|
1381
|
+
this.ctrlPressed = false;
|
|
2465
1382
|
if (this.tableRef.current) {
|
|
2466
1383
|
this.tableRef.current.className = '';
|
|
2467
1384
|
}
|
|
@@ -2471,10 +1388,6 @@ export class ObjectBrowserClass extends Component {
|
|
|
2471
1388
|
* Called when component is unmounted.
|
|
2472
1389
|
*/
|
|
2473
1390
|
componentWillUnmount() {
|
|
2474
|
-
if (this.filterTimer) {
|
|
2475
|
-
clearTimeout(this.filterTimer);
|
|
2476
|
-
this.filterTimer = null;
|
|
2477
|
-
}
|
|
2478
1391
|
window.removeEventListener('contextmenu', this.onContextMenu, true);
|
|
2479
1392
|
window.removeEventListener('keydown', this.onKeyPress, true);
|
|
2480
1393
|
window.removeEventListener('keyup', this.onKeyPress, true);
|
|
@@ -2813,7 +1726,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
2813
1726
|
let cColumns;
|
|
2814
1727
|
if (columns) {
|
|
2815
1728
|
cColumns = aColumns
|
|
2816
|
-
|
|
1729
|
+
?.map((_item) => {
|
|
2817
1730
|
if (typeof _item !== 'object') {
|
|
2818
1731
|
return { path: _item, name: _item.split('.').pop() };
|
|
2819
1732
|
}
|
|
@@ -2853,7 +1766,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
2853
1766
|
else {
|
|
2854
1767
|
cColumns = null;
|
|
2855
1768
|
}
|
|
2856
|
-
if (cColumns
|
|
1769
|
+
if (cColumns && cColumns.length) {
|
|
2857
1770
|
columnsForAdmin ||= {};
|
|
2858
1771
|
columnsForAdmin[obj.common.name] = cColumns.sort((a, b) => a.path > b.path ? -1 : a.path < b.path ? 1 : 0);
|
|
2859
1772
|
}
|
|
@@ -2906,9 +1819,8 @@ export class ObjectBrowserClass extends Component {
|
|
|
2906
1819
|
});
|
|
2907
1820
|
this.root = root;
|
|
2908
1821
|
this.info = info;
|
|
2909
|
-
this.lastAppliedFilter = null; // apply filter anew
|
|
2910
1822
|
if (!this.pausedSubscribes) {
|
|
2911
|
-
this.
|
|
1823
|
+
this.doFilter();
|
|
2912
1824
|
}
|
|
2913
1825
|
// else it will be re-rendered when the dialog will be closed
|
|
2914
1826
|
}, 500);
|
|
@@ -3032,129 +1944,52 @@ export class ObjectBrowserClass extends Component {
|
|
|
3032
1944
|
this.subscribes.forEach(id => this.props.socket.subscribeState(id, this.onStateChange));
|
|
3033
1945
|
}
|
|
3034
1946
|
}
|
|
3035
|
-
onFilter(name, value) {
|
|
3036
|
-
this.filterTimer = null;
|
|
3037
|
-
const filter = { ...this.state.filter };
|
|
3038
|
-
Object.keys(this.filterRefs).forEach((_name) => {
|
|
3039
|
-
if (this.filterRefs[_name]?.current) {
|
|
3040
|
-
const filterRef = this.filterRefs[_name].current;
|
|
3041
|
-
for (let i = 0; i < filterRef.children.length; i++) {
|
|
3042
|
-
if (filterRef.children[i].tagName === 'INPUT') {
|
|
3043
|
-
if (_name === 'role' ||
|
|
3044
|
-
_name === 'type' ||
|
|
3045
|
-
_name === 'func' ||
|
|
3046
|
-
_name === 'custom' ||
|
|
3047
|
-
_name === 'room') {
|
|
3048
|
-
const value = filterRef.children[i].value;
|
|
3049
|
-
if (value) {
|
|
3050
|
-
filter[_name] = value.split(',');
|
|
3051
|
-
}
|
|
3052
|
-
else {
|
|
3053
|
-
filter[_name] = undefined;
|
|
3054
|
-
}
|
|
3055
|
-
}
|
|
3056
|
-
else {
|
|
3057
|
-
filter[_name] = filterRef.children[i].value;
|
|
3058
|
-
}
|
|
3059
|
-
break;
|
|
3060
|
-
}
|
|
3061
|
-
}
|
|
3062
|
-
}
|
|
3063
|
-
});
|
|
3064
|
-
if (name) {
|
|
3065
|
-
filter[name] = value;
|
|
3066
|
-
if (name === 'expertMode') {
|
|
3067
|
-
(window._sessionStorage || window.sessionStorage).setItem('App.expertMode', value ? 'true' : 'false');
|
|
3068
|
-
}
|
|
3069
|
-
}
|
|
3070
|
-
if (JSON.stringify(this.state.filter) !== JSON.stringify(filter)) {
|
|
3071
|
-
this.localStorage.setItem(`${this.props.dialogName || 'App'}.objectFilter`, JSON.stringify(filter));
|
|
3072
|
-
this.setState({ filter }, () => this.props.onFilterChanged && this.props.onFilterChanged(filter));
|
|
3073
|
-
}
|
|
3074
|
-
}
|
|
3075
1947
|
clearFilter() {
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
const item = filterRef.childNodes[i];
|
|
3082
|
-
if (item.tagName === 'INPUT') {
|
|
3083
|
-
filter[name] = '';
|
|
3084
|
-
item.value = '';
|
|
3085
|
-
break;
|
|
3086
|
-
}
|
|
3087
|
-
}
|
|
3088
|
-
}
|
|
3089
|
-
});
|
|
3090
|
-
if (JSON.stringify(this.state.filter) !== JSON.stringify(filter)) {
|
|
3091
|
-
this.localStorage.setItem(`${this.props.dialogName || 'App'}.objectFilter`, JSON.stringify(filter));
|
|
3092
|
-
this.setState({ filter, filterKey: this.state.filterKey + 1 }, () => this.props.onFilterChanged?.(filter));
|
|
1948
|
+
if (JSON.stringify(this.state.filter) !== JSON.stringify(DEFAULT_FILTER)) {
|
|
1949
|
+
this.setState({ filter: { ...DEFAULT_FILTER }, filterKey: this.state.filterKey + 1 }, () => {
|
|
1950
|
+
this.doFilter();
|
|
1951
|
+
this.props.onFilterChanged?.({ ...DEFAULT_FILTER });
|
|
1952
|
+
});
|
|
3093
1953
|
}
|
|
3094
1954
|
}
|
|
3095
1955
|
isFilterEmpty() {
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
} },
|
|
3119
|
-
React.createElement(IconClose, null)))) : null));
|
|
1956
|
+
return (!!this.state.filter.id ||
|
|
1957
|
+
!!this.state.filter.name ||
|
|
1958
|
+
!!this.state.filter.room?.length ||
|
|
1959
|
+
!!this.state.filter.func?.length ||
|
|
1960
|
+
!!this.state.filter.role?.length ||
|
|
1961
|
+
!!this.state.filter.type?.length ||
|
|
1962
|
+
!!this.state.filter.custom?.length);
|
|
1963
|
+
}
|
|
1964
|
+
getFilterInput(name) {
|
|
1965
|
+
return (React.createElement(CustomFilterInput, { key: `${name}_${this.state.filterKey}`, styles: this.styles.filterInput, name: name, texts: this.texts, t: this.props.t, initialValue: this.state.filter[name], onChange: (name, value) => {
|
|
1966
|
+
const filter = { ...this.state.filter };
|
|
1967
|
+
if (value === undefined) {
|
|
1968
|
+
delete filter[name];
|
|
1969
|
+
}
|
|
1970
|
+
else {
|
|
1971
|
+
filter[name] = value;
|
|
1972
|
+
}
|
|
1973
|
+
this.setState({ filter }, () => {
|
|
1974
|
+
this.doFilter();
|
|
1975
|
+
this.props.onFilterChanged?.(filter);
|
|
1976
|
+
});
|
|
1977
|
+
} }));
|
|
3120
1978
|
}
|
|
3121
1979
|
getFilterSelect(name, values) {
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
}
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
let icon;
|
|
3136
|
-
if (typeof item === 'object') {
|
|
3137
|
-
id = item.value;
|
|
3138
|
-
_name = item.name;
|
|
3139
|
-
icon = item.icon;
|
|
3140
|
-
}
|
|
3141
|
-
else {
|
|
3142
|
-
id = item;
|
|
3143
|
-
_name = item;
|
|
3144
|
-
}
|
|
3145
|
-
return (React.createElement(MenuItem, { sx: styles.headerCellSelectItem, key: id, value: id },
|
|
3146
|
-
icon || (hasIcons ? React.createElement("div", { className: "itemIcon" }) : null),
|
|
3147
|
-
_name));
|
|
3148
|
-
})),
|
|
3149
|
-
this.filterRefs[name]?.current?.childNodes[1]?.value ? (React.createElement(Box, { component: "div", sx: styles.selectClearButton },
|
|
3150
|
-
React.createElement(IconButton, { size: "small", onClick: () => {
|
|
3151
|
-
const newFilter = { ...this.state.filter };
|
|
3152
|
-
delete newFilter[name];
|
|
3153
|
-
(this.filterRefs[name].current?.childNodes[1]).value = '';
|
|
3154
|
-
this.localStorage.setItem(`${this.props.dialogName || 'App'}.objectFilter`, JSON.stringify(newFilter));
|
|
3155
|
-
this.setState({ filter: newFilter, filterKey: this.state.filterKey + 1 }, () => this.props.onFilterChanged && this.props.onFilterChanged(newFilter));
|
|
3156
|
-
} },
|
|
3157
|
-
React.createElement(IconClose, null)))) : null));
|
|
1980
|
+
return (React.createElement(CustomFilterSelect, { key: `${name}_${this.state.filterKey}`, name: name, texts: this.texts, initialValue: this.state.filter[name] || [], values: values || [], onChange: (name, value) => {
|
|
1981
|
+
const filter = { ...this.state.filter };
|
|
1982
|
+
if (value === undefined) {
|
|
1983
|
+
delete filter[name];
|
|
1984
|
+
}
|
|
1985
|
+
else {
|
|
1986
|
+
filter[name] = value;
|
|
1987
|
+
}
|
|
1988
|
+
this.setState({ filter }, () => {
|
|
1989
|
+
this.doFilter();
|
|
1990
|
+
this.props.onFilterChanged?.(filter);
|
|
1991
|
+
});
|
|
1992
|
+
} }));
|
|
3158
1993
|
}
|
|
3159
1994
|
getFilterSelectRole() {
|
|
3160
1995
|
return this.getFilterSelect('role', this.info.roles.map(it => it.role));
|
|
@@ -3215,6 +2050,9 @@ export class ObjectBrowserClass extends Component {
|
|
|
3215
2050
|
this.setState({ expanded: [], depth: 0, selected: [] }, () => this.onAfterSelect());
|
|
3216
2051
|
}
|
|
3217
2052
|
expandDepth(root, depth, expanded) {
|
|
2053
|
+
if (!this.root) {
|
|
2054
|
+
throw new Error('No root');
|
|
2055
|
+
}
|
|
3218
2056
|
root ||= this.root;
|
|
3219
2057
|
if (depth > 0) {
|
|
3220
2058
|
root.children?.forEach(item => {
|
|
@@ -3492,79 +2330,80 @@ export class ObjectBrowserClass extends Component {
|
|
|
3492
2330
|
return null;
|
|
3493
2331
|
}
|
|
3494
2332
|
const ObjectMoveRenameDialog = this.props.objectMoveRenameDialog;
|
|
3495
|
-
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 }));
|
|
2333
|
+
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 }));
|
|
3496
2334
|
}
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
const
|
|
3502
|
-
|
|
3503
|
-
|
|
2335
|
+
async parseJsonFile(contents) {
|
|
2336
|
+
try {
|
|
2337
|
+
const json = JSON.parse(contents);
|
|
2338
|
+
const len = Object.keys(json).length;
|
|
2339
|
+
const id = json._id;
|
|
2340
|
+
// it could be a single object or many objects
|
|
2341
|
+
if (id === undefined && len) {
|
|
2342
|
+
// many objects
|
|
2343
|
+
await this.loadObjects(json);
|
|
2344
|
+
window.alert(this.props.t('ra_%s object(s) processed', len));
|
|
2345
|
+
}
|
|
2346
|
+
else {
|
|
2347
|
+
// it is only one object in form
|
|
2348
|
+
// {
|
|
2349
|
+
// "_id": "xxx",
|
|
2350
|
+
// "common": "yyy",
|
|
2351
|
+
// "native": "zzz"
|
|
2352
|
+
// "val": JSON.stringify(value)
|
|
2353
|
+
// "ack": true
|
|
2354
|
+
// }
|
|
2355
|
+
if (!id) {
|
|
2356
|
+
return window.alert(this.props.t('ra_Invalid structure'));
|
|
2357
|
+
}
|
|
3504
2358
|
try {
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
await this.loadObjects(json);
|
|
3512
|
-
window.alert(this.props.t('ra_%s object(s) processed', len));
|
|
2359
|
+
let enums;
|
|
2360
|
+
let val;
|
|
2361
|
+
let ack;
|
|
2362
|
+
if (json.common.enums) {
|
|
2363
|
+
enums = json.common.enums;
|
|
2364
|
+
delete json.common.enums;
|
|
3513
2365
|
}
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
2366
|
+
if (json.val) {
|
|
2367
|
+
val = json.val;
|
|
2368
|
+
delete json.val;
|
|
2369
|
+
}
|
|
2370
|
+
if (json.ack !== undefined) {
|
|
2371
|
+
ack = json.ack;
|
|
2372
|
+
delete json.ack;
|
|
2373
|
+
}
|
|
2374
|
+
await this.props.socket.setObject(json._id, json);
|
|
2375
|
+
if (json.type === 'state') {
|
|
2376
|
+
if (val !== undefined && val !== null) {
|
|
2377
|
+
await this.props.socket.setState(json._id, val, ack === undefined ? true : ack);
|
|
3525
2378
|
}
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
if (json.common.enums) {
|
|
3531
|
-
enums = json.common.enums;
|
|
3532
|
-
delete json.common.enums;
|
|
3533
|
-
}
|
|
3534
|
-
if (json.val) {
|
|
3535
|
-
val = json.val;
|
|
3536
|
-
delete json.val;
|
|
3537
|
-
}
|
|
3538
|
-
if (json.ack !== undefined) {
|
|
3539
|
-
ack = json.ack;
|
|
3540
|
-
delete json.ack;
|
|
3541
|
-
}
|
|
3542
|
-
await this.props.socket.setObject(json._id, json);
|
|
3543
|
-
if (json.type === 'state') {
|
|
3544
|
-
if (val !== undefined && val !== null) {
|
|
3545
|
-
await this.props.socket.setState(json._id, val, ack === undefined ? true : ack);
|
|
3546
|
-
}
|
|
3547
|
-
else {
|
|
3548
|
-
const state = await this.props.socket.getState(json._id);
|
|
3549
|
-
if (!state || state.val === null || state.val === undefined) {
|
|
3550
|
-
await this.props.socket.setState(json._id, json.common.def === undefined ? null : json.common.def, true);
|
|
3551
|
-
}
|
|
3552
|
-
}
|
|
3553
|
-
}
|
|
3554
|
-
if (enums) {
|
|
3555
|
-
await this._createAllEnums(enums, json._id);
|
|
2379
|
+
else {
|
|
2380
|
+
const state = await this.props.socket.getState(json._id);
|
|
2381
|
+
if (!state || state.val === null || state.val === undefined) {
|
|
2382
|
+
await this.props.socket.setState(json._id, json.common.def === undefined ? null : json.common.def, true);
|
|
3556
2383
|
}
|
|
3557
|
-
window.alert(this.props.t('ra_%s was imported', json._id));
|
|
3558
|
-
}
|
|
3559
|
-
catch (err) {
|
|
3560
|
-
window.alert(err);
|
|
3561
2384
|
}
|
|
3562
2385
|
}
|
|
2386
|
+
if (enums) {
|
|
2387
|
+
await this._createAllEnums(enums, json._id);
|
|
2388
|
+
}
|
|
2389
|
+
window.alert(this.props.t('ra_%s was imported', json._id));
|
|
3563
2390
|
}
|
|
3564
2391
|
catch (err) {
|
|
3565
2392
|
window.alert(err);
|
|
3566
2393
|
}
|
|
3567
|
-
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
catch (err) {
|
|
2397
|
+
window.alert(err);
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
handleJsonUpload(evt) {
|
|
2401
|
+
const target = evt.target;
|
|
2402
|
+
const f = target.files?.length && target.files[0];
|
|
2403
|
+
if (f) {
|
|
2404
|
+
const r = new FileReader();
|
|
2405
|
+
r.onload = (e) => {
|
|
2406
|
+
this.parseJsonFile(e.target?.result).catch(e => console.log(`Cannot parse file: ${e}`));
|
|
3568
2407
|
};
|
|
3569
2408
|
r.readAsText(f);
|
|
3570
2409
|
}
|
|
@@ -3641,6 +2480,26 @@ export class ObjectBrowserClass extends Component {
|
|
|
3641
2480
|
}
|
|
3642
2481
|
return value.length ? value : t('ra_Add new child object to selected parent');
|
|
3643
2482
|
};
|
|
2483
|
+
onOpenFile() {
|
|
2484
|
+
const input = document.createElement('input');
|
|
2485
|
+
input.setAttribute('type', 'file');
|
|
2486
|
+
input.setAttribute('id', 'files');
|
|
2487
|
+
input.setAttribute('opacity', '0');
|
|
2488
|
+
input.addEventListener('change', (e) => this.handleJsonUpload(e), false);
|
|
2489
|
+
input.click();
|
|
2490
|
+
}
|
|
2491
|
+
renderInputJsonDialog() {
|
|
2492
|
+
if (!this.state.showImportDialog) {
|
|
2493
|
+
return null;
|
|
2494
|
+
}
|
|
2495
|
+
const ObjectBrowserInsertJsonObjects = this.props.objectBrowserInsertJsonObjects;
|
|
2496
|
+
return (React.createElement(ObjectBrowserInsertJsonObjects, { onClose: (text) => {
|
|
2497
|
+
this.setState({ showImportDialog: false });
|
|
2498
|
+
if (text) {
|
|
2499
|
+
void this.parseJsonFile(text);
|
|
2500
|
+
}
|
|
2501
|
+
}, themeName: this.props.themeName, themeType: this.props.themeType, t: this.props.t }));
|
|
2502
|
+
}
|
|
3644
2503
|
/**
|
|
3645
2504
|
* Renders the toolbar.
|
|
3646
2505
|
*/
|
|
@@ -3674,7 +2533,14 @@ export class ObjectBrowserClass extends Component {
|
|
|
3674
2533
|
React.createElement(IconButton, { onClick: () => this.refreshComponent(), disabled: this.state.updating, size: "large" },
|
|
3675
2534
|
React.createElement(RefreshIcon, null)))),
|
|
3676
2535
|
this.props.showExpertButton && !this.props.expertMode && (React.createElement(Tooltip, { title: this.props.t('ra_expertMode'), slotProps: { popper: { sx: styles.tooltip } } },
|
|
3677
|
-
React.createElement(IconButton, { key: "expertMode", color: this.state.filter.expertMode ? 'secondary' : 'default', onClick: () =>
|
|
2536
|
+
React.createElement(IconButton, { key: "expertMode", color: this.state.filter.expertMode ? 'secondary' : 'default', onClick: () => {
|
|
2537
|
+
const filter = { ...this.state.filter };
|
|
2538
|
+
filter.expertMode = !filter.expertMode;
|
|
2539
|
+
this.localStorage.setItem(`${this.props.dialogName || 'App'}.objectFilter`, JSON.stringify(filter));
|
|
2540
|
+
this.setState({
|
|
2541
|
+
filter,
|
|
2542
|
+
});
|
|
2543
|
+
}, size: "large" },
|
|
3678
2544
|
React.createElement(IconExpert, null)))),
|
|
3679
2545
|
!this.props.disableColumnSelector && this.props.width !== 'xs' && (React.createElement(Tooltip, { title: this.props.t('ra_Configure'), slotProps: { popper: { sx: styles.tooltip } } },
|
|
3680
2546
|
React.createElement(IconButton, { key: "columnSelector", color: this.state.columnsAuto ? 'primary' : 'default', onClick: () => this.setState({ columnsSelectorShow: true }), size: "large" },
|
|
@@ -3725,15 +2591,24 @@ export class ObjectBrowserClass extends Component {
|
|
|
3725
2591
|
}), size: "large" },
|
|
3726
2592
|
React.createElement(AddIcon, null))))) : null,
|
|
3727
2593
|
this.props.objectImportExport && (React.createElement(Tooltip, { title: this.props.t('ra_Add objects tree from JSON file'), slotProps: { popper: { sx: styles.tooltip } } },
|
|
3728
|
-
React.createElement(IconButton, { onClick:
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
2594
|
+
React.createElement(IconButton, { onClick: e => {
|
|
2595
|
+
if (this.props.objectBrowserInsertJsonObjects) {
|
|
2596
|
+
this.setState({ showImportMenu: e.currentTarget });
|
|
2597
|
+
}
|
|
2598
|
+
else {
|
|
2599
|
+
this.onOpenFile();
|
|
2600
|
+
}
|
|
3735
2601
|
}, size: "large" },
|
|
3736
2602
|
React.createElement(PublishIcon, null)))),
|
|
2603
|
+
this.props.objectBrowserInsertJsonObjects ? (React.createElement(Menu, { anchorEl: this.state.showImportMenu, open: !!this.state.showImportMenu, onClose: () => this.setState({ showImportMenu: null }) },
|
|
2604
|
+
React.createElement(MenuItem, { onClick: () => this.setState({ showImportMenu: null }, () => this.onOpenFile()) },
|
|
2605
|
+
React.createElement(ListItemIcon, null,
|
|
2606
|
+
React.createElement(UploadFile, null)),
|
|
2607
|
+
React.createElement(ListItemText, null, this.props.t('ra_From file'))),
|
|
2608
|
+
React.createElement(MenuItem, { onClick: () => this.setState({ showImportMenu: null, showImportDialog: true }) },
|
|
2609
|
+
React.createElement(ListItemIcon, null,
|
|
2610
|
+
React.createElement(ContentPaste, null)),
|
|
2611
|
+
React.createElement(ListItemText, null, this.props.t('ra_From text'))))) : null,
|
|
3737
2612
|
this.props.objectImportExport &&
|
|
3738
2613
|
(!!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 } } },
|
|
3739
2614
|
React.createElement(IconButton, { onClick: () => this.setState({ showExportDialog: this._getSelectedIdsForExport().length }), size: "large" },
|
|
@@ -3887,9 +2762,9 @@ export class ObjectBrowserClass extends Component {
|
|
|
3887
2762
|
: 0;
|
|
3888
2763
|
const aclSystemConfig = item.data.obj.acl &&
|
|
3889
2764
|
(item.data.obj.type === 'state'
|
|
3890
|
-
? this.systemConfig
|
|
3891
|
-
: this.systemConfig
|
|
3892
|
-
const showEdit = this.state.filter.expertMode ||
|
|
2765
|
+
? this.systemConfig?.common.defaultNewAcl.state
|
|
2766
|
+
: this.systemConfig?.common.defaultNewAcl.object);
|
|
2767
|
+
const showEdit = !!item.data.obj && (this.state.filter.expertMode || isNonExpertId(item.data.id));
|
|
3893
2768
|
return [
|
|
3894
2769
|
this.state.filter.expertMode && this.props.objectEditOfAccessControl ? (React.createElement(Tooltip, { key: "acl", title: item.data.aclTooltip, slotProps: { popper: { sx: styles.tooltip } } },
|
|
3895
2770
|
React.createElement(IconButton, { sx: {
|
|
@@ -4002,8 +2877,10 @@ export class ObjectBrowserClass extends Component {
|
|
|
4002
2877
|
state,
|
|
4003
2878
|
obj: obj,
|
|
4004
2879
|
texts: this.texts,
|
|
4005
|
-
dateFormat: this.props.dateFormat || this.systemConfig
|
|
4006
|
-
isFloatComma: this.props.isFloatComma === undefined
|
|
2880
|
+
dateFormat: this.props.dateFormat || this.systemConfig?.common.dateFormat || DEFAULT_DATE_FORMAT,
|
|
2881
|
+
isFloatComma: this.props.isFloatComma === undefined
|
|
2882
|
+
? (this.systemConfig?.common.isFloatComma ?? true)
|
|
2883
|
+
: this.props.isFloatComma,
|
|
4007
2884
|
full: true,
|
|
4008
2885
|
});
|
|
4009
2886
|
const valFullRx = [];
|
|
@@ -4032,7 +2909,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
4032
2909
|
if (fileViewer === 'image') {
|
|
4033
2910
|
valFullRx.push(React.createElement("img", { style: styles.cellValueTooltipImage, src: state.val, alt: id }));
|
|
4034
2911
|
}
|
|
4035
|
-
else if (role === 'url' ||
|
|
2912
|
+
else if (role === 'url' || role === 'url.self' || role === 'url.blank') {
|
|
4036
2913
|
// Show comment about "Hold Ctrl/⌘ key to open the link"
|
|
4037
2914
|
valFullRx.unshift(React.createElement("div", { key: "ctrl", style: { textDecoration: 'underline', fontWeight: 'bold' } }, this.texts.ctrlForLink));
|
|
4038
2915
|
}
|
|
@@ -4077,9 +2954,9 @@ export class ObjectBrowserClass extends Component {
|
|
|
4077
2954
|
state,
|
|
4078
2955
|
obj: obj,
|
|
4079
2956
|
texts: this.texts,
|
|
4080
|
-
dateFormat: this.props.dateFormat || this.systemConfig
|
|
2957
|
+
dateFormat: this.props.dateFormat || this.systemConfig?.common.dateFormat || DEFAULT_DATE_FORMAT,
|
|
4081
2958
|
isFloatComma: this.props.isFloatComma === undefined
|
|
4082
|
-
? this.systemConfig
|
|
2959
|
+
? (this.systemConfig?.common.isFloatComma ?? true)
|
|
4083
2960
|
: this.props.isFloatComma,
|
|
4084
2961
|
});
|
|
4085
2962
|
const valTextRx = [];
|
|
@@ -4106,7 +2983,12 @@ export class ObjectBrowserClass extends Component {
|
|
|
4106
2983
|
// <IconEdit className="copyButton" style={{{ ...styles.cellButtonsValueButton, styles.cellButtonsValueButtonEdit)} key="ce" />
|
|
4107
2984
|
info = item.data.state;
|
|
4108
2985
|
}
|
|
4109
|
-
info.style = getValueStyle({
|
|
2986
|
+
info.style = getValueStyle({
|
|
2987
|
+
state,
|
|
2988
|
+
isExpertMode: this.state.filter.expertMode,
|
|
2989
|
+
isButton: item.data.button,
|
|
2990
|
+
nonAckColor: this.props.theme.palette.nonAck,
|
|
2991
|
+
});
|
|
4110
2992
|
let val = info.valTextRx;
|
|
4111
2993
|
if (!this.state.filter.expertMode) {
|
|
4112
2994
|
if (item.data.button) {
|
|
@@ -4250,7 +3132,9 @@ export class ObjectBrowserClass extends Component {
|
|
|
4250
3132
|
this.setState({ enumDialogEnums });
|
|
4251
3133
|
}, secondaryAction: icon },
|
|
4252
3134
|
React.createElement(ListItemIcon, { sx: { '&.MuiListItemIcon-root': styles.enumCheckbox } },
|
|
4253
|
-
React.createElement(Checkbox, { edge: "start", checked: itemEnums.includes(id), tabIndex: -1, disableRipple: true,
|
|
3135
|
+
React.createElement(Checkbox, { edge: "start", checked: itemEnums.includes(id), tabIndex: -1, disableRipple: true, slotProps: {
|
|
3136
|
+
input: { 'aria-labelledby': labelId },
|
|
3137
|
+
} })),
|
|
4254
3138
|
React.createElement(ListItemText, { id: labelId }, name)));
|
|
4255
3139
|
}))));
|
|
4256
3140
|
}
|
|
@@ -4261,7 +3145,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
4261
3145
|
if (this.state.roleDialog && this.props.objectBrowserEditRole) {
|
|
4262
3146
|
const ObjectBrowserEditRole = this.props.objectBrowserEditRole;
|
|
4263
3147
|
return (React.createElement(ObjectBrowserEditRole, { key: "objectBrowserEditRole", id: this.state.roleDialog, socket: this.props.socket, t: this.props.t, roleArray: this.info.roles, commonType: this.info.objects[this.state.roleDialog]?.common?.type, onClose: (obj) => {
|
|
4264
|
-
if (obj) {
|
|
3148
|
+
if (obj && this.state.roleDialog) {
|
|
4265
3149
|
this.info.objects[this.state.roleDialog] = obj;
|
|
4266
3150
|
}
|
|
4267
3151
|
this.setState({ roleDialog: null });
|
|
@@ -4284,7 +3168,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
4284
3168
|
this.props.socket
|
|
4285
3169
|
.getObject(this.state.columnsEditCustomDialog?.obj?._id || '')
|
|
4286
3170
|
.then(obj => {
|
|
4287
|
-
if (obj &&
|
|
3171
|
+
if (obj && setCustomValue(obj, this.state.columnsEditCustomDialog?.it, value)) {
|
|
4288
3172
|
return this.props.socket.setObject(obj._id, obj);
|
|
4289
3173
|
}
|
|
4290
3174
|
throw new Error(this.props.t('ra_Cannot update attribute, because not found in the object'));
|
|
@@ -4302,7 +3186,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
4302
3186
|
return null;
|
|
4303
3187
|
}
|
|
4304
3188
|
if (!this.customColumnDialog) {
|
|
4305
|
-
const value =
|
|
3189
|
+
const value = getCustomValue(this.state.columnsEditCustomDialog.obj, this.state.columnsEditCustomDialog.it);
|
|
4306
3190
|
this.customColumnDialog = {
|
|
4307
3191
|
type: (this.state.columnsEditCustomDialog.it.type || typeof value),
|
|
4308
3192
|
initValue: (value === null || value === undefined ? '' : value).toString(),
|
|
@@ -4331,111 +3215,11 @@ export class ObjectBrowserClass extends Component {
|
|
|
4331
3215
|
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')),
|
|
4332
3216
|
React.createElement(Button, { color: "grey", variant: "contained", onClick: () => this.onColumnsEditCustomDialogClose(), startIcon: React.createElement(IconClose, null) }, this.props.t('ra_Cancel')))));
|
|
4333
3217
|
}
|
|
4334
|
-
static getCustomValue(obj, it) {
|
|
4335
|
-
if (obj?._id?.startsWith(`${it.adapter}.`) && it.path.length > 1) {
|
|
4336
|
-
const p = it.path;
|
|
4337
|
-
let value;
|
|
4338
|
-
const anyObj = obj;
|
|
4339
|
-
if (anyObj[p[0]] && typeof anyObj[p[0]] === 'object') {
|
|
4340
|
-
if (p.length === 2) {
|
|
4341
|
-
// most common case
|
|
4342
|
-
value = anyObj[p[0]][p[1]];
|
|
4343
|
-
}
|
|
4344
|
-
else if (p.length === 3) {
|
|
4345
|
-
value =
|
|
4346
|
-
anyObj[p[0]][p[1]] && typeof anyObj[p[0]][p[1]] === 'object' ? anyObj[p[0]][p[1]][p[2]] : null;
|
|
4347
|
-
}
|
|
4348
|
-
else if (p.length === 4) {
|
|
4349
|
-
value =
|
|
4350
|
-
anyObj[p[0]][p[1]] && typeof anyObj[p[0]][p[1]] === 'object' && anyObj[p[0]][p[1]][p[2]]
|
|
4351
|
-
? anyObj[p[0]][p[1]][p[2]][p[3]]
|
|
4352
|
-
: null;
|
|
4353
|
-
}
|
|
4354
|
-
else if (p.length === 5) {
|
|
4355
|
-
value =
|
|
4356
|
-
anyObj[p[0]][p[1]] &&
|
|
4357
|
-
typeof anyObj[p[0]][p[1]] === 'object' &&
|
|
4358
|
-
anyObj[p[0]][p[1]][p[2]] &&
|
|
4359
|
-
anyObj[p[0]][p[1]][p[2]][p[3]]
|
|
4360
|
-
? anyObj[p[0]][p[1]][p[2]][p[3]][p[4]]
|
|
4361
|
-
: null;
|
|
4362
|
-
}
|
|
4363
|
-
else if (p.length === 6) {
|
|
4364
|
-
value =
|
|
4365
|
-
anyObj[p[0]][p[1]] &&
|
|
4366
|
-
typeof anyObj[p[0]][p[1]] === 'object' &&
|
|
4367
|
-
anyObj[p[0]][p[1]][p[2]] &&
|
|
4368
|
-
anyObj[p[0]][p[1]][p[2]][p[3]] &&
|
|
4369
|
-
anyObj[p[0]][p[1]][p[2]][p[3]][p[4]]
|
|
4370
|
-
? anyObj[p[0]][p[1]][p[2]][p[3]][p[4]][p[5]]
|
|
4371
|
-
: null;
|
|
4372
|
-
}
|
|
4373
|
-
if (value === undefined || value === null) {
|
|
4374
|
-
return null;
|
|
4375
|
-
}
|
|
4376
|
-
return value;
|
|
4377
|
-
}
|
|
4378
|
-
}
|
|
4379
|
-
return null;
|
|
4380
|
-
}
|
|
4381
|
-
static setCustomValue(obj, it, value) {
|
|
4382
|
-
if (obj?._id?.startsWith(`${it.adapter}.`) && it.path.length > 1) {
|
|
4383
|
-
const p = it.path;
|
|
4384
|
-
const anyObj = obj;
|
|
4385
|
-
if (anyObj[p[0]] && typeof anyObj[p[0]] === 'object') {
|
|
4386
|
-
if (p.length === 2) {
|
|
4387
|
-
// most common case
|
|
4388
|
-
anyObj[p[0]][p[1]] = value;
|
|
4389
|
-
return true;
|
|
4390
|
-
}
|
|
4391
|
-
if (p.length === 3) {
|
|
4392
|
-
if (anyObj[p[0]][p[1]] && typeof anyObj[p[0]][p[1]] === 'object') {
|
|
4393
|
-
anyObj[p[0]][p[1]][p[2]] = value;
|
|
4394
|
-
return true;
|
|
4395
|
-
}
|
|
4396
|
-
}
|
|
4397
|
-
else if (p.length === 4) {
|
|
4398
|
-
if (anyObj[p[0]][p[1]] &&
|
|
4399
|
-
typeof anyObj[p[0]][p[1]] === 'object' &&
|
|
4400
|
-
anyObj[p[0]][p[1]][p[2]] &&
|
|
4401
|
-
typeof anyObj[p[0]][p[1]][p[2]] === 'object') {
|
|
4402
|
-
anyObj[p[0]][p[1]][p[2]][p[3]] = value;
|
|
4403
|
-
return true;
|
|
4404
|
-
}
|
|
4405
|
-
}
|
|
4406
|
-
else if (p.length === 5) {
|
|
4407
|
-
if (anyObj[p[0]][p[1]] &&
|
|
4408
|
-
typeof anyObj[p[0]][p[1]] === 'object' &&
|
|
4409
|
-
anyObj[p[0]][p[1]][p[2]] &&
|
|
4410
|
-
typeof anyObj[p[0]][p[1]][p[2]] === 'object' &&
|
|
4411
|
-
anyObj[p[0]][p[1]][p[2]][p[3]] &&
|
|
4412
|
-
typeof anyObj[p[0]][p[1]][p[2]][p[3]] === 'object') {
|
|
4413
|
-
anyObj[p[0]][p[1]][p[2]][p[3]][p[4]] = value;
|
|
4414
|
-
return true;
|
|
4415
|
-
}
|
|
4416
|
-
}
|
|
4417
|
-
else if (p.length === 6) {
|
|
4418
|
-
if (anyObj[p[0]][p[1]] &&
|
|
4419
|
-
typeof anyObj[p[0]][p[1]] === 'object' &&
|
|
4420
|
-
anyObj[p[0]][p[1]][p[2]] &&
|
|
4421
|
-
typeof anyObj[p[0]][p[1]][p[2]] === 'object' &&
|
|
4422
|
-
anyObj[p[0]][p[1]][p[2]][p[3]] &&
|
|
4423
|
-
typeof anyObj[p[0]][p[1]][p[2]][p[3]] === 'object' &&
|
|
4424
|
-
anyObj[p[0]][p[1]][p[2]][p[3]][p[4]] &&
|
|
4425
|
-
typeof anyObj[p[0]][p[1]][p[2]][p[3]][p[4]] === 'object') {
|
|
4426
|
-
anyObj[p[0]][p[1]][p[2]][p[3]][p[4]][p[5]] = value;
|
|
4427
|
-
return true;
|
|
4428
|
-
}
|
|
4429
|
-
}
|
|
4430
|
-
}
|
|
4431
|
-
}
|
|
4432
|
-
return false;
|
|
4433
|
-
}
|
|
4434
3218
|
/**
|
|
4435
3219
|
* Renders a custom value.
|
|
4436
3220
|
*/
|
|
4437
3221
|
renderCustomValue(obj, it, item) {
|
|
4438
|
-
const text =
|
|
3222
|
+
const text = getCustomValue(obj, it);
|
|
4439
3223
|
if (text !== null && text !== undefined) {
|
|
4440
3224
|
if (it.edit && !this.props.notEditable && (!it.objTypes || it.objTypes.includes(obj.type))) {
|
|
4441
3225
|
return (React.createElement(Box, { component: "div", style: {
|
|
@@ -4568,7 +3352,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
4568
3352
|
newValueTitle.push(`${this.texts.objectChangedBy} ${obj.user.replace(/^system\.user\./, '')}`);
|
|
4569
3353
|
}
|
|
4570
3354
|
if (obj.ts) {
|
|
4571
|
-
newValueTitle.push(`${this.texts.objectChangedByUser} ${Utils.formatDate(new Date(obj.ts), this.props.dateFormat || this.systemConfig
|
|
3355
|
+
newValueTitle.push(`${this.texts.objectChangedByUser} ${Utils.formatDate(new Date(obj.ts), this.props.dateFormat || this.systemConfig?.common.dateFormat || DEFAULT_DATE_FORMAT)}`);
|
|
4572
3356
|
}
|
|
4573
3357
|
}
|
|
4574
3358
|
let readWriteAlias = false;
|
|
@@ -4634,24 +3418,30 @@ export class ObjectBrowserClass extends Component {
|
|
|
4634
3418
|
if (id === '0_userdata') {
|
|
4635
3419
|
checkColor = COLOR_NAME_USERDATA(this.props.themeType);
|
|
4636
3420
|
bold = true;
|
|
3421
|
+
invertBackground = false;
|
|
4637
3422
|
}
|
|
4638
3423
|
else if (id === 'alias') {
|
|
4639
3424
|
checkColor = COLOR_NAME_ALIAS(this.props.themeType);
|
|
4640
3425
|
bold = true;
|
|
3426
|
+
invertBackground = false;
|
|
4641
3427
|
}
|
|
4642
3428
|
else if (id === 'javascript') {
|
|
4643
3429
|
checkColor = COLOR_NAME_JAVASCRIPT(this.props.themeType);
|
|
4644
3430
|
bold = true;
|
|
3431
|
+
invertBackground = false;
|
|
4645
3432
|
}
|
|
4646
3433
|
else if (id === 'system') {
|
|
4647
3434
|
checkColor = COLOR_NAME_SYSTEM(this.props.themeType);
|
|
4648
3435
|
bold = true;
|
|
3436
|
+
invertBackground = false;
|
|
4649
3437
|
}
|
|
4650
3438
|
else if (id === 'system.adapter') {
|
|
4651
3439
|
checkColor = COLOR_NAME_SYSTEM_ADAPTER(this.props.themeType);
|
|
3440
|
+
invertBackground = false;
|
|
4652
3441
|
}
|
|
4653
3442
|
else if (!checkColor || this.state.selected.includes(id)) {
|
|
4654
3443
|
checkColor = 'inherit';
|
|
3444
|
+
invertBackground = false;
|
|
4655
3445
|
}
|
|
4656
3446
|
const icons = [];
|
|
4657
3447
|
if (common?.statusStates) {
|
|
@@ -4680,6 +3470,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
4680
3470
|
let colorSet = false;
|
|
4681
3471
|
if (common.statusStates.errorId && this.states[ids.errorId] && this.states[ids.errorId].val) {
|
|
4682
3472
|
checkColor = this.props.themeType === 'dark' ? COLOR_NAME_ERROR_DARK : COLOR_NAME_ERROR_LIGHT;
|
|
3473
|
+
invertBackground = false;
|
|
4683
3474
|
colorSet = true;
|
|
4684
3475
|
icons.push(React.createElement(IconError, { key: "error",
|
|
4685
3476
|
// title={this.texts.deviceError}
|
|
@@ -4690,6 +3481,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
4690
3481
|
if (this.states[ids.onlineId].val) {
|
|
4691
3482
|
checkColor =
|
|
4692
3483
|
this.props.themeType === 'dark' ? COLOR_NAME_CONNECTED_DARK : COLOR_NAME_CONNECTED_LIGHT;
|
|
3484
|
+
invertBackground = false;
|
|
4693
3485
|
icons.push(React.createElement(IconConnection, { key: "conn",
|
|
4694
3486
|
// title={this.texts.deviceError}
|
|
4695
3487
|
style: this.styles.iconDeviceConnected }));
|
|
@@ -4699,6 +3491,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
4699
3491
|
this.props.themeType === 'dark'
|
|
4700
3492
|
? COLOR_NAME_DISCONNECTED_DARK
|
|
4701
3493
|
: COLOR_NAME_DISCONNECTED_LIGHT;
|
|
3494
|
+
invertBackground = false;
|
|
4702
3495
|
icons.push(React.createElement(IconDisconnected, { key: "disc",
|
|
4703
3496
|
// title={this.texts.deviceError}
|
|
4704
3497
|
style: this.styles.iconDeviceDisconnected }));
|
|
@@ -4722,6 +3515,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
4722
3515
|
this.props.themeType === 'dark'
|
|
4723
3516
|
? COLOR_NAME_DISCONNECTED_DARK
|
|
4724
3517
|
: COLOR_NAME_DISCONNECTED_LIGHT;
|
|
3518
|
+
invertBackground = false;
|
|
4725
3519
|
icons.push(React.createElement(IconDisconnected, { key: "disc",
|
|
4726
3520
|
// title={this.texts.deviceError}
|
|
4727
3521
|
style: this.styles.iconDeviceDisconnected }));
|
|
@@ -4729,6 +3523,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
4729
3523
|
else {
|
|
4730
3524
|
checkColor =
|
|
4731
3525
|
this.props.themeType === 'dark' ? COLOR_NAME_CONNECTED_DARK : COLOR_NAME_CONNECTED_LIGHT;
|
|
3526
|
+
invertBackground = false;
|
|
4732
3527
|
icons.push(React.createElement(IconConnection, { key: "conn",
|
|
4733
3528
|
// title={this.texts.deviceError}
|
|
4734
3529
|
style: this.styles.iconDeviceConnected }));
|
|
@@ -4760,11 +3555,11 @@ export class ObjectBrowserClass extends Component {
|
|
|
4760
3555
|
}
|
|
4761
3556
|
}
|
|
4762
3557
|
const narrowStyleWithDetails = this.props.width === 'xs' && this.state.focused === id;
|
|
4763
|
-
const colID = (React.createElement(
|
|
4764
|
-
React.createElement(
|
|
3558
|
+
const colID = (React.createElement(Grid, { container: true, wrap: "nowrap", direction: "row", sx: styles.cellId, style: { width: this.columnsVisibility.id, paddingLeft } },
|
|
3559
|
+
React.createElement(Grid, { container: true, sx: { alignItems: 'center' } },
|
|
4765
3560
|
checkbox,
|
|
4766
3561
|
iconFolder),
|
|
4767
|
-
React.createElement(
|
|
3562
|
+
React.createElement(Grid, { style: {
|
|
4768
3563
|
...styles.cellIdSpan,
|
|
4769
3564
|
...(invertBackground ? this.styles.invertedBackground : undefined),
|
|
4770
3565
|
color: checkColor,
|
|
@@ -4775,7 +3570,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
4775
3570
|
alias,
|
|
4776
3571
|
icons),
|
|
4777
3572
|
React.createElement("div", { style: { ...styles.grow, ...(invertBackground ? this.styles.invertedBackgroundFlex : {}) } }),
|
|
4778
|
-
React.createElement(
|
|
3573
|
+
React.createElement(Grid, { container: true, sx: { alignItems: 'center' } }, iconItem),
|
|
4779
3574
|
this.props.width !== 'xs' ? (React.createElement("div", null,
|
|
4780
3575
|
React.createElement(IconCopy, { className: narrowStyleWithDetails ? '' : 'copyButton', style: styles.cellCopyButton, onClick: e => this.onCopy(e, id) }))) : null));
|
|
4781
3576
|
let colName = (narrowStyleWithDetails && name) || this.columnsVisibility.name ? (React.createElement(Box, { component: "div", sx: {
|
|
@@ -4929,7 +3724,9 @@ export class ObjectBrowserClass extends Component {
|
|
|
4929
3724
|
...styles.cellRole,
|
|
4930
3725
|
width: this.props.width !== 'xs' ? this.columnsVisibility.timestamp : undefined,
|
|
4931
3726
|
} }, checkVisibleObjectType && this.states[id]?.ts
|
|
4932
|
-
? Utils.formatDate(new Date(this.states[id].ts), this.props.dateFormat ||
|
|
3727
|
+
? Utils.formatDate(new Date(this.states[id].ts), this.props.dateFormat ||
|
|
3728
|
+
this.systemConfig?.common.dateFormat ||
|
|
3729
|
+
DEFAULT_DATE_FORMAT)
|
|
4933
3730
|
: null)),
|
|
4934
3731
|
type: 'ts',
|
|
4935
3732
|
}
|
|
@@ -4941,7 +3738,9 @@ export class ObjectBrowserClass extends Component {
|
|
|
4941
3738
|
...styles.cellRole,
|
|
4942
3739
|
width: this.props.width !== 'xs' ? this.columnsVisibility.lastChange : undefined,
|
|
4943
3740
|
} }, checkVisibleObjectType && this.states[id]?.lc
|
|
4944
|
-
? Utils.formatDate(new Date(this.states[id].lc), this.props.dateFormat ||
|
|
3741
|
+
? Utils.formatDate(new Date(this.states[id].lc), this.props.dateFormat ||
|
|
3742
|
+
this.systemConfig?.common.dateFormat ||
|
|
3743
|
+
DEFAULT_DATE_FORMAT)
|
|
4945
3744
|
: null)),
|
|
4946
3745
|
type: 'lc',
|
|
4947
3746
|
}
|
|
@@ -5081,9 +3880,11 @@ export class ObjectBrowserClass extends Component {
|
|
|
5081
3880
|
state: this.states[id],
|
|
5082
3881
|
obj: this.objects[id],
|
|
5083
3882
|
texts: this.texts,
|
|
5084
|
-
dateFormat: this.props.dateFormat ||
|
|
3883
|
+
dateFormat: this.props.dateFormat ||
|
|
3884
|
+
this.systemConfig?.common.dateFormat ||
|
|
3885
|
+
DEFAULT_DATE_FORMAT,
|
|
5085
3886
|
isFloatComma: this.props.isFloatComma === undefined
|
|
5086
|
-
? this.systemConfig
|
|
3887
|
+
? (this.systemConfig?.common.isFloatComma ?? true)
|
|
5087
3888
|
: this.props.isFloatComma,
|
|
5088
3889
|
});
|
|
5089
3890
|
this.onCopy(e, valText.c !== undefined ? valText.c : valText.v.toString());
|
|
@@ -5095,7 +3896,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
5095
3896
|
colValue = null;
|
|
5096
3897
|
colButtons = null;
|
|
5097
3898
|
}
|
|
5098
|
-
const row = (React.createElement(
|
|
3899
|
+
const row = (React.createElement(Grid, { container: true, direction: "row", wrap: "nowrap", sx: Utils.getStyle(this.props.theme, styles.tableRow, this.state.linesEnabled && styles.tableRowLines, !this.props.dragEnabled && styles.tableRowNoDragging, alias && styles.tableRowAlias, readWriteAlias && styles.tableRowAliasReadWrite, this.state.focused === id && this.props.multiSelect && styles.tableRowFocused, !item.data.visible && styles.filteredOut, item.data.hasVisibleParent &&
|
|
5099
3900
|
!item.data.visible &&
|
|
5100
3901
|
!item.data.hasVisibleChildren &&
|
|
5101
3902
|
styles.filteredParentOut, this.state.selected.includes(id) && styles.itemSelected, this.state.selectedNonObject === id && styles.itemSelected), key: id, id: id, onMouseDown: e => {
|
|
@@ -5635,10 +4436,109 @@ export class ObjectBrowserClass extends Component {
|
|
|
5635
4436
|
return (React.createElement(Snackbar, { open: !!this.state.toast, autoHideDuration: 3000, onClick: () => this.setState({ toast: '' }), onClose: () => this.setState({ toast: '' }), message: this.state.toast, action: React.createElement(IconButton, { size: "small", "aria-label": "close", color: "inherit", onClick: () => this.setState({ toast: '' }) },
|
|
5636
4437
|
React.createElement(IconClose, { fontSize: "small" })) }));
|
|
5637
4438
|
}
|
|
4439
|
+
// --- Routing (navigateTo / onNavigateTo) ---
|
|
4440
|
+
// The browser never reads the URL itself; the parent drives it via `navigateTo` and is informed
|
|
4441
|
+
// of user navigation via `onNavigateTo`. All URL parsing/writing lives in the parent component.
|
|
4442
|
+
/** Derive the current navigation target from the dialog/selection state. */
|
|
4443
|
+
getStateNav() {
|
|
4444
|
+
if (this.state.editObjectDialog) {
|
|
4445
|
+
return { mode: 'edit', id: this.state.editObjectDialog };
|
|
4446
|
+
}
|
|
4447
|
+
if (this.state.customDialog && this.state.customDialog.length === 1 && !this.state.customDialogAll) {
|
|
4448
|
+
return { mode: 'settings', id: this.state.customDialog[0] };
|
|
4449
|
+
}
|
|
4450
|
+
if (this.state.viewFileDialog) {
|
|
4451
|
+
return { mode: 'viewFile', id: this.state.viewFileDialog };
|
|
4452
|
+
}
|
|
4453
|
+
if (this.state.selected.length === 1 && this.state.selected[0]) {
|
|
4454
|
+
return { mode: 'select', id: this.state.selected[0] };
|
|
4455
|
+
}
|
|
4456
|
+
return null;
|
|
4457
|
+
}
|
|
4458
|
+
static navEqual(a, b) {
|
|
4459
|
+
if (!a || !b) {
|
|
4460
|
+
return !a && !b;
|
|
4461
|
+
}
|
|
4462
|
+
return a.mode === b.mode && a.id === b.id;
|
|
4463
|
+
}
|
|
4464
|
+
/** Apply a navigation target coming from the parent (`navigateTo`): select + open the dialog. */
|
|
4465
|
+
applyNavigateTo(nav) {
|
|
4466
|
+
this.applyingNav = true;
|
|
4467
|
+
const done = () => {
|
|
4468
|
+
this.applyingNav = false;
|
|
4469
|
+
};
|
|
4470
|
+
if (!nav?.id) {
|
|
4471
|
+
// No target: just close any open dialog (keep the current selection).
|
|
4472
|
+
this.setState({ editObjectDialog: '', customDialog: null, viewFileDialog: '' }, done);
|
|
4473
|
+
return;
|
|
4474
|
+
}
|
|
4475
|
+
const { id } = nav;
|
|
4476
|
+
const open = () => {
|
|
4477
|
+
if (nav.mode === 'edit') {
|
|
4478
|
+
this.setState({ editObjectDialog: id, editObjectAlias: false, customDialog: null, viewFileDialog: '' }, done);
|
|
4479
|
+
}
|
|
4480
|
+
else if (nav.mode === 'settings') {
|
|
4481
|
+
this.setState({ customDialog: [id], customDialogAll: false, editObjectDialog: '', viewFileDialog: '' }, done);
|
|
4482
|
+
}
|
|
4483
|
+
else if (nav.mode === 'viewFile') {
|
|
4484
|
+
this.setState({ viewFileDialog: id, editObjectDialog: '', customDialog: null }, done);
|
|
4485
|
+
}
|
|
4486
|
+
else {
|
|
4487
|
+
this.setState({ editObjectDialog: '', customDialog: null, viewFileDialog: '' }, done);
|
|
4488
|
+
}
|
|
4489
|
+
};
|
|
4490
|
+
// Select the target (if needed), expand to it and scroll into view, then open the dialog.
|
|
4491
|
+
if (this.state.selected.length === 1 && this.state.selected[0] === id) {
|
|
4492
|
+
open();
|
|
4493
|
+
}
|
|
4494
|
+
else {
|
|
4495
|
+
this.onSelect(id, false, () => this.expandAllSelected(() => {
|
|
4496
|
+
this.scrollToItem(id);
|
|
4497
|
+
open();
|
|
4498
|
+
}));
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
/** Apply the initial `navigateTo` after the tree has loaded (called from componentDidMount). */
|
|
4502
|
+
applyInitialNavigateTo() {
|
|
4503
|
+
const nav = this.props.navigateTo ?? null;
|
|
4504
|
+
if (nav?.id) {
|
|
4505
|
+
this.lastNav = nav;
|
|
4506
|
+
this.applyNavigateTo(nav);
|
|
4507
|
+
}
|
|
4508
|
+
else {
|
|
4509
|
+
// Don't push the restored-from-localStorage selection into the URL on load.
|
|
4510
|
+
this.lastNav = this.getStateNav();
|
|
4511
|
+
}
|
|
4512
|
+
}
|
|
4513
|
+
/** Reconcile `navigateTo` (parent/URL) with the browser's selection/dialog state. */
|
|
4514
|
+
reconcileNavigation(prevProps) {
|
|
4515
|
+
if (this.props.navigateTo === undefined && !this.props.onNavigateTo) {
|
|
4516
|
+
return; // routing not used by this consumer
|
|
4517
|
+
}
|
|
4518
|
+
if (this.applyingNav) {
|
|
4519
|
+
return; // currently applying a target; ignore the intermediate state
|
|
4520
|
+
}
|
|
4521
|
+
const propNav = this.props.navigateTo ?? null;
|
|
4522
|
+
const stateNav = this.getStateNav();
|
|
4523
|
+
if (ObjectBrowserClass.navEqual(propNav, stateNav)) {
|
|
4524
|
+
this.lastNav = stateNav;
|
|
4525
|
+
return;
|
|
4526
|
+
}
|
|
4527
|
+
if (!ObjectBrowserClass.navEqual(propNav, prevProps.navigateTo ?? null)) {
|
|
4528
|
+
// The parent (URL) drove a change → apply it to the browser.
|
|
4529
|
+
this.lastNav = propNav;
|
|
4530
|
+
this.applyNavigateTo(propNav);
|
|
4531
|
+
}
|
|
4532
|
+
else if (!ObjectBrowserClass.navEqual(stateNav, this.lastNav)) {
|
|
4533
|
+
// The user changed selection/dialog → report it so the parent can update the URL.
|
|
4534
|
+
this.lastNav = stateNav;
|
|
4535
|
+
this.props.onNavigateTo?.(stateNav);
|
|
4536
|
+
}
|
|
4537
|
+
}
|
|
5638
4538
|
/**
|
|
5639
4539
|
* Called when component is updated.
|
|
5640
4540
|
*/
|
|
5641
|
-
componentDidUpdate() {
|
|
4541
|
+
componentDidUpdate(prevProps) {
|
|
5642
4542
|
if (this.tableRef.current) {
|
|
5643
4543
|
const scrollBarWidth = this.tableRef.current.offsetWidth - this.tableRef.current.clientWidth;
|
|
5644
4544
|
if (this.state.scrollBarWidth !== scrollBarWidth) {
|
|
@@ -5648,6 +4548,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
5648
4548
|
this.scrollToItem(this.selectFirst);
|
|
5649
4549
|
}
|
|
5650
4550
|
}
|
|
4551
|
+
this.reconcileNavigation(prevProps);
|
|
5651
4552
|
}
|
|
5652
4553
|
scrollToItem(id) {
|
|
5653
4554
|
this.selectFirst = '';
|
|
@@ -5659,10 +4560,10 @@ export class ObjectBrowserClass extends Component {
|
|
|
5659
4560
|
});
|
|
5660
4561
|
}
|
|
5661
4562
|
renderCustomDialog() {
|
|
5662
|
-
if (this.state.customDialog && this.props.objectCustomDialog) {
|
|
4563
|
+
if (this.state.customDialog && this.props.objectCustomDialog && this.systemConfig) {
|
|
5663
4564
|
const ObjectCustomDialog = this.props.objectCustomDialog;
|
|
5664
4565
|
return (React.createElement(ObjectCustomDialog, { reportChangedIds: (changedIds) => (this.changedIds = [...changedIds]), objectIDs: this.state.customDialog, allVisibleObjects: !!this.state.customDialogAll, expertMode: this.state.filter.expertMode, isFloatComma: this.props.isFloatComma === undefined
|
|
5665
|
-
? this.systemConfig
|
|
4566
|
+
? (this.systemConfig?.common.isFloatComma ?? true)
|
|
5666
4567
|
: this.props.isFloatComma, t: this.props.t, lang: this.props.lang, socket: this.props.socket, themeName: this.props.themeName, themeType: this.props.themeType, theme: this.props.theme, objects: this.objects, customsInstances: this.info.customs, onClose: () => {
|
|
5667
4568
|
this.pauseSubscribe(false);
|
|
5668
4569
|
this.setState({ customDialog: null });
|
|
@@ -5690,9 +4591,14 @@ export class ObjectBrowserClass extends Component {
|
|
|
5690
4591
|
if (!this.state.editObjectDialog || !this.props.objectBrowserEditObject) {
|
|
5691
4592
|
return null;
|
|
5692
4593
|
}
|
|
4594
|
+
// Guard against opening the editor for a node without a real object (e.g. a virtual folder
|
|
4595
|
+
// that only exists because child states share an ID prefix). Its constructor reads obj._id.
|
|
4596
|
+
if (!this.objects[this.state.editObjectDialog]) {
|
|
4597
|
+
return null;
|
|
4598
|
+
}
|
|
5693
4599
|
const ObjectBrowserEditObject = this.props.objectBrowserEditObject;
|
|
5694
|
-
return (React.createElement(ObjectBrowserEditObject, { key: this.state.editObjectDialog, obj: this.objects[this.state.editObjectDialog], roleArray: this.info.roles, objects: this.objects, dateFormat: this.props.dateFormat || this.systemConfig
|
|
5695
|
-
? this.systemConfig
|
|
4600
|
+
return (React.createElement(ObjectBrowserEditObject, { key: this.state.editObjectDialog, obj: this.objects[this.state.editObjectDialog], roleArray: this.info.roles, objects: this.objects, dateFormat: this.props.dateFormat || this.systemConfig?.common.dateFormat || DEFAULT_DATE_FORMAT, isFloatComma: this.props.isFloatComma === undefined
|
|
4601
|
+
? (this.systemConfig?.common.isFloatComma ?? true)
|
|
5696
4602
|
: this.props.isFloatComma, themeType: this.props.themeType, theme: this.props.theme, socket: this.props.socket, dialogName: this.props.dialogName, aliasTab: this.state.editObjectAlias, t: this.props.t, expertMode: !!this.state.filter.expertMode, onNewObject: (obj) => this.props.socket
|
|
5697
4603
|
.setObject(obj._id, obj)
|
|
5698
4604
|
.then(() => this.setState({ editObjectDialog: obj._id, editObjectAlias: false }, () => this.onSelect(obj._id)))
|
|
@@ -5782,8 +4688,8 @@ export class ObjectBrowserClass extends Component {
|
|
|
5782
4688
|
const acl = obj.acl ? (obj.type === 'state' ? obj.acl.state : obj.acl.object) : 0;
|
|
5783
4689
|
const aclSystemConfig = obj.acl &&
|
|
5784
4690
|
(obj.type === 'state'
|
|
5785
|
-
? this.systemConfig
|
|
5786
|
-
: this.systemConfig
|
|
4691
|
+
? this.systemConfig?.common.defaultNewAcl.state
|
|
4692
|
+
: this.systemConfig?.common.defaultNewAcl.object);
|
|
5787
4693
|
showACL = Number.isNaN(Number(acl)) ? Number(aclSystemConfig).toString(16) : Number(acl).toString(16);
|
|
5788
4694
|
}
|
|
5789
4695
|
}
|
|
@@ -5814,7 +4720,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
5814
4720
|
key: '0',
|
|
5815
4721
|
visibility: !!(this.props.objectBrowserEditObject &&
|
|
5816
4722
|
obj &&
|
|
5817
|
-
(this.state.filter.expertMode ||
|
|
4723
|
+
(this.state.filter.expertMode || isNonExpertId(id))),
|
|
5818
4724
|
icon: (React.createElement(IconEdit, { fontSize: "small", style: this.styles.contextMenuEdit })),
|
|
5819
4725
|
label: this.texts.editObject,
|
|
5820
4726
|
onClick: () => this.setState({ editObjectDialog: item.data.id, showContextMenu: null, editObjectAlias: false }),
|
|
@@ -5825,7 +4731,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
5825
4731
|
!this.props.notEditable &&
|
|
5826
4732
|
obj &&
|
|
5827
4733
|
obj.type === 'state' &&
|
|
5828
|
-
//
|
|
4734
|
+
// deprecated from js-controller 6
|
|
5829
4735
|
obj.common?.type !== 'file' &&
|
|
5830
4736
|
(this.state.filter.expertMode || obj.common.write !== false)),
|
|
5831
4737
|
icon: (React.createElement(IconValueEdit, { fontSize: "small", style: this.styles.contextMenuEditValue })),
|
|
@@ -5843,7 +4749,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
5843
4749
|
VIEW: {
|
|
5844
4750
|
visibility: !!this.props.objectBrowserViewFile &&
|
|
5845
4751
|
obj?.type === 'state' &&
|
|
5846
|
-
//
|
|
4752
|
+
// deprecated from js-controller 6
|
|
5847
4753
|
obj.common?.type === 'file',
|
|
5848
4754
|
icon: (React.createElement(FindInPage, { fontSize: "small", style: this.styles.contextMenuView })),
|
|
5849
4755
|
label: this.props.t('ra_View file'),
|
|
@@ -5855,7 +4761,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
5855
4761
|
this.info.hasSomeCustoms &&
|
|
5856
4762
|
obj &&
|
|
5857
4763
|
obj.type === 'state' &&
|
|
5858
|
-
//
|
|
4764
|
+
// deprecated from js-controller 6
|
|
5859
4765
|
obj.common?.type !== 'file'),
|
|
5860
4766
|
icon: (React.createElement(IconConfig, { fontSize: "small", style: item.data.hasCustoms
|
|
5861
4767
|
? this.styles.cellButtonsButtonWithCustoms
|
|
@@ -5931,7 +4837,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
5931
4837
|
this.props.objectBrowserAliasEditor &&
|
|
5932
4838
|
this.props.objectBrowserEditObject &&
|
|
5933
4839
|
obj?.type === 'state' &&
|
|
5934
|
-
//
|
|
4840
|
+
// deprecated from js-controller 6
|
|
5935
4841
|
obj.common?.type !== 'file'),
|
|
5936
4842
|
icon: (React.createElement(IconLink, { style: obj?.common?.alias
|
|
5937
4843
|
? this.styles.cellButtonsButtonWithCustoms
|
|
@@ -6117,8 +5023,8 @@ export class ObjectBrowserClass extends Component {
|
|
|
6117
5023
|
: typeof this.edit.val;
|
|
6118
5024
|
const role = this.objects[this.edit.id].common.role;
|
|
6119
5025
|
const ObjectBrowserValue = this.props.objectBrowserValue;
|
|
6120
|
-
return (React.createElement(ObjectBrowserValue, { t: this.props.t, lang: this.props.lang, type: type, role: role || '', states: Utils.getStates(this.objects[this.edit.id]), themeType: this.props.themeType, theme: this.props.theme, expertMode: !!this.state.filter.expertMode, value: this.edit.val, socket: this.props.socket, object: this.objects[this.edit.id], defaultHistory: this.defaultHistory, dateFormat: this.props.dateFormat || this.systemConfig
|
|
6121
|
-
? this.systemConfig
|
|
5026
|
+
return (React.createElement(ObjectBrowserValue, { t: this.props.t, lang: this.props.lang, type: type, role: role || '', states: Utils.getStates(this.objects[this.edit.id]), themeType: this.props.themeType, theme: this.props.theme, expertMode: !!this.state.filter.expertMode, value: this.edit.val, socket: this.props.socket, object: this.objects[this.edit.id], defaultHistory: this.defaultHistory, dateFormat: this.props.dateFormat || this.systemConfig?.common.dateFormat || DEFAULT_DATE_FORMAT, isFloatComma: this.props.isFloatComma === undefined
|
|
5027
|
+
? (this.systemConfig?.common.isFloatComma ?? true)
|
|
6122
5028
|
: this.props.isFloatComma, onClose: (res) => {
|
|
6123
5029
|
this.setState({ updateOpened: false });
|
|
6124
5030
|
if (res) {
|
|
@@ -6126,6 +5032,25 @@ export class ObjectBrowserClass extends Component {
|
|
|
6126
5032
|
}
|
|
6127
5033
|
}, width: this.props.width }));
|
|
6128
5034
|
}
|
|
5035
|
+
doFilter(doNotStore) {
|
|
5036
|
+
if (!this.objects || !this.root) {
|
|
5037
|
+
return;
|
|
5038
|
+
}
|
|
5039
|
+
if (!doNotStore) {
|
|
5040
|
+
this.localStorage.setItem(`${this.props.dialogName || 'App'}.objectFilter`, JSON.stringify(this.state.filter));
|
|
5041
|
+
}
|
|
5042
|
+
const counter = { count: 0 };
|
|
5043
|
+
applyFilter(this.root, this.state.filter, this.props.lang, this.objects, undefined, counter, this.props.customFilter, this.props.types);
|
|
5044
|
+
if (counter.count < 500 && !this.state.expandAllVisible) {
|
|
5045
|
+
setTimeout(() => this.setState({ expandAllVisible: true }));
|
|
5046
|
+
}
|
|
5047
|
+
else if (counter.count >= 500 && this.state.expandAllVisible) {
|
|
5048
|
+
setTimeout(() => this.setState({ expandAllVisible: false }));
|
|
5049
|
+
}
|
|
5050
|
+
else {
|
|
5051
|
+
this.forceUpdate();
|
|
5052
|
+
}
|
|
5053
|
+
}
|
|
6129
5054
|
/**
|
|
6130
5055
|
* The rendering method of this component.
|
|
6131
5056
|
*/
|
|
@@ -6159,23 +5084,14 @@ export class ObjectBrowserClass extends Component {
|
|
|
6159
5084
|
};
|
|
6160
5085
|
this.styleTheme = this.props.themeType;
|
|
6161
5086
|
}
|
|
6162
|
-
// apply filter if changed
|
|
6163
|
-
const jsonFilter = JSON.stringify(this.state.filter);
|
|
6164
|
-
if (this.lastAppliedFilter !== jsonFilter && this.objects && this.root) {
|
|
6165
|
-
const counter = { count: 0 };
|
|
6166
|
-
applyFilter(this.root, this.state.filter, this.props.lang, this.objects, undefined, counter, this.props.customFilter, this.props.types);
|
|
6167
|
-
if (counter.count < 500 && !this.state.expandAllVisible) {
|
|
6168
|
-
setTimeout(() => this.setState({ expandAllVisible: true }));
|
|
6169
|
-
}
|
|
6170
|
-
else if (counter.count >= 500 && this.state.expandAllVisible) {
|
|
6171
|
-
setTimeout(() => this.setState({ expandAllVisible: false }));
|
|
6172
|
-
}
|
|
6173
|
-
this.lastAppliedFilter = jsonFilter;
|
|
6174
|
-
}
|
|
6175
5087
|
this.unsubscribeTimer = setTimeout(() => {
|
|
6176
5088
|
this.unsubscribeTimer = null;
|
|
6177
5089
|
this.checkUnsubscribes();
|
|
6178
5090
|
}, 200);
|
|
5091
|
+
if (this.expertMode !== !!this.state.filter.expertMode) {
|
|
5092
|
+
this.expertMode = !!this.state.filter.expertMode;
|
|
5093
|
+
this.doFilter(true);
|
|
5094
|
+
}
|
|
6179
5095
|
if (!this.state.loaded) {
|
|
6180
5096
|
return React.createElement(CircularProgress, { key: `${this.props.dialogName}_c` });
|
|
6181
5097
|
}
|
|
@@ -6234,6 +5150,7 @@ export class ObjectBrowserClass extends Component {
|
|
|
6234
5150
|
this.renderErrorDialog(),
|
|
6235
5151
|
this.renderExportDialog(),
|
|
6236
5152
|
this.renderRenameDialog(),
|
|
5153
|
+
this.renderInputJsonDialog(),
|
|
6237
5154
|
this.state.modalNewObj && this.props.modalNewObject && this.props.modalNewObject(this),
|
|
6238
5155
|
this.state.modalEditOfAccess &&
|
|
6239
5156
|
this.state.modalEditOfAccessObjData &&
|