Untitled
unknown
javascript
3 years ago
13 kB
16
Indexable
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import FolderIcon from '@mui/icons-material/Folder';
import { TreeItem, treeItemClasses, TreeItemProps, TreeView } from '@mui/lab';
import { CircularProgress, SvgIconProps, Typography } from '@mui/material';
import { styled } from '@mui/material/styles';
import _ from 'lodash';
import React, { useEffect, useState } from 'react';
import Highlighter from 'react-highlight-words';
import { useSelector } from 'react-redux';
import { AccessPolicyCloudProvider } from '../../../../../access-policies/models/access-policies.enums';
import { AccessPolicy } from '../../../../../access-policies/models/AccessPolicy';
import account from '../../../../../assets/icons/account.svg';
import gcp_folder from '../../../../../assets/icons/gcp_folder.svg';
import gco_org from '../../../../../assets/icons/gcp_org.svg';
import gcp_project from '../../../../../assets/icons/gcp_project.svg';
import org from '../../../../../assets/icons/org.svg';
import { IWizardContext, WizardContext } from '../../../../wizard/Wizard';
import { ISelectedRolesContext, SelectedRolesContext } from '../../../RolesAndAttribute';
import { WorkspaceNode } from '../Entities/WorkspaceNode';
import '../RolesDialog.scss';
export const WorkspaceTree = (props: any) => {
const gcpIcons = {
gcp_organization: gco_org,
folder: gcp_folder,
project: gcp_project,
};
const policy = useSelector<AccessPolicy>((state: any) => state.policyReducer.policy) as any;
const [filteredNodes, setFilteredNodes] = useState<WorkspaceNode[]>([]);
const [selectedRoleContext, setSelectedRoleContext]: [ISelectedRolesContext, any] =
React.useContext(SelectedRolesContext);
const [wizardContext]: [IWizardContext, any] = React.useContext(WizardContext);
const [expanded, setExpanded] = React.useState<any[]>([]);
const handleToggle = (event: any, nodeIds: any) => {
event.persist();
const iconClicked = event.target.className === 'treeItem';
if (iconClicked) {
setExpanded(nodeIds);
}
};
type StyledTreeItemProps = TreeItemProps & {
bgColor?: string;
color?: string;
labelIcon: React.ElementType<SvgIconProps>;
labelInfo?: string;
idText: string;
labelText: string;
type: string;
searchWord: string;
numberOfChosen: string;
isNodeDisabled: boolean;
isNodeDisabled2: boolean;
parent: string;
nodeChildren: number;
childrenCount: number;
};
// see example - https://stackblitz.com/edit/react-rhv3gr?file=demo.tsx
console.log(treeItemClasses.content);
const StyledTreeItemRoot = styled(TreeItem, {
shouldForwardProp: (prop) => prop !== 'idText' && prop !== 'isNodeDisabled2',
})<{ isNodeDisabled2?: boolean; idText?: string }>(({ theme, isNodeDisabled2, idText }) => ({
color: 'black',
'& .MuiTreeItem-iconContainer': {},
// [`.${treeItemClasses.content}`]: {
// color: 'black',
// fontSize: 30,
// paddingRight: theme.spacing(1),
// fontWeight: theme.typography.fontWeightMedium,
// '&.Mui-expanded': {
// fontWeight: theme.typography.fontWeightRegular,
// },
// '&:hover': {
// backgroundColor: isNodeDisabled2 ? theme.palette.common.white : theme.palette.action.hover,
// },
// '&:focus': {
// backgroundColor: isNodeDisabled2 ? 'white' : 'gray',
// },
// '& .Mui-selected': {
// backgroundColor: isNodeDisabled2 ? 'white' : 'red',
// },
[`& .${treeItemClasses.label}`]: {
fontWeight: 'inherit',
color: '#1362CB',
},
'.MuiTreeItem-content': {
'&:hover': {
backgroundColor: isNodeDisabled2 ? 'white' : 'gray',
},
'&:focus': {
backgroundColor: isNodeDisabled2 ? 'white' : 'yellow',
},
'&.Mui-selected': {
backgroundColor: isNodeDisabled2 ? 'white' : 'red',
cursor: isNodeDisabled2 ? 'none' : 'pointer',
},
},
[`& .${treeItemClasses.group}`]: {
marginLeft: 0,
[`& .${treeItemClasses.content}`]: {
paddingLeft: theme.spacing(2),
fontSize: 30,
},
},
}));
const getIcon = (type: string) => {
if (policy.cloudProvider === AccessPolicyCloudProvider.GCP) {
const icon = gcpIcons[type];
if (icon != null) {
return icon;
} else {
return gcp_folder;
}
} else {
if (type === 'account') {
return account;
} else {
return org;
}
}
};
function StyledTreeItem(propsInner: StyledTreeItemProps) {
// @ts-ignore
// @ts-ignore
const {
bgColor,
color,
labelIcon: LabelIcon,
labelInfo,
labelText,
searchWord,
type,
idText,
numberOfChosen,
isNodeDisabled,
nodeChildren,
parent,
childrenCount,
isNodeDisabled2,
...other
} = propsInner;
const isNodeDisabled3 = !props.isPopupMode && !numberOfChosen && !nodeChildren;
return (
<StyledTreeItemRoot
key={idText}
idText={idText}
isNodeDisabled2={isNodeDisabled2}
sx={{
...((parent || !nodeChildren) &&
!numberOfChosen &&
!props.isPopupMode && {
touchAction: 'none',
// pointerEvents: 'none',
cursor: 'normal',
}),
'& > .MuiTreeItem-content': {
'& .MuiTreeItem-label': {
...(!numberOfChosen && !props.isPopupMode && !nodeChildren && { opacity: 0.5 }),
},
},
}}
label={
<div className="treeItem">
<div className="treeTitle">
<img src={getIcon(type)} style={{ marginRight: '5px' }} />
<Typography
variant="body2"
sx={{
fontWeight: 'inherit',
flexGrow: 1,
fontSize: 16,
width: '100%',
display: 'block',
}}
>
<Highlighter
highlightClassName="YourHighlightClass"
searchWords={[searchWord]}
autoEscape={true}
textToHighlight={idText}
/>
</Typography>
</div>
<span style={{ position: 'absolute', right: '11px', top: '10px' }}>
{props.isPopupMode && numberOfChosen}
</span>
<p
style={{
fontWeight: 'inherit',
flexGrow: 1,
fontSize: 12,
color: '#666666',
display: 'block',
width: '100%',
margin: 0,
}}
>
<Highlighter
highlightClassName="YourHighlightClass"
searchWords={[searchWord]}
autoEscape={true}
textToHighlight={labelText}
/>
</p>
</div>
}
style={{
color,
}}
{...other}
/>
);
}
const expandAll = (nodesFromServer: any[]) => {
let queue = _.cloneDeep(nodesFromServer);
const ids = [];
while (queue.length) {
const item = queue.shift();
if (item != null) {
ids.push(item.id);
if (item.nodes != null) {
queue = [...item.nodes, ...queue];
}
}
}
return ids;
};
useEffect(() => {
const nodesFromServer = wizardContext.rolesWizard.nodesFromServer;
if (nodesFromServer != null) {
if (nodesFromServer.length > 0) {
setFilteredNodes(nodesFromServer);
}
if (!props.isPopupMode && nodesFromServer !== -1) {
const ids = expandAll(nodesFromServer);
setExpanded(ids);
}
}
}, [wizardContext.rolesWizard.nodesFromServer]);
useEffect(() => {
if (props.isPopupMode) {
return;
}
const policies = policy.entities.map((po: any) => po.entitySourceId);
const selectedRoleContextCopy = JSON.parse(JSON.stringify(selectedRoleContext));
Object.keys(selectedRoleContextCopy.allSelectedRolesMap).forEach((entity) => {
if (policies.indexOf(entity) < 0) {
delete selectedRoleContextCopy.allSelectedRolesMap[entity];
}
});
if (Object.keys(selectedRoleContextCopy?.allSelectedRolesMap).length !== policies?.length) {
policy.entities
.filter((po: any) => po.entityType === 0)
.forEach((po: any) => {
selectedRoleContextCopy.allSelectedRolesMap[po.entitySourceId] = [
...(selectedRoleContextCopy?.allSelectedRolesMap[po.entitySourceId] || []),
po,
];
});
}
setSelectedRoleContext(selectedRoleContextCopy);
}, [policy.entities]);
const getNode = (id: string) => {
for (const node of filteredNodes) {
const nodeFound = searchNode(node, id);
if (nodeFound != null) {
return nodeFound;
}
}
return null;
};
const searchNode = (root: WorkspaceNode, id: string) => {
const queue = [root];
while (queue.length) {
const item = queue.shift();
if (item != null) {
if (item.id === id) {
return item;
}
item.nodes.forEach((child) => {
queue.push(child);
});
}
}
return null;
};
const getNumberOfChosen = (accountId: string) => {
if (selectedRoleContext != null) {
const value = selectedRoleContext[!props.isPopupMode ? 'allSelectedRolesMap' : 'selectedRolesMap'][accountId];
return value == null || value.length === 0 ? '' : '(' + value.length.toString() + ')';
} else {
return '';
}
};
const isDisabled = (accountId: string) => {
if (
props.isPopupMode ||
selectedRoleContext[!props.isPopupMode ? 'allSelectedRolesMap' : 'selectedRolesMap'] == null
) {
return false;
}
const keys = Object.keys(selectedRoleContext[!props.isPopupMode ? 'allSelectedRolesMap' : 'selectedRolesMap']);
return keys.find((x) => x === accountId) == null;
};
const count_children = (node: any) => {
let sum = +getNumberOfChosen(node.id).replace(/[^0-9]/g, '');
if (Array.isArray(node.nodes)) {
for (const row of node.nodes) {
sum += +count_children(row);
}
}
return sum;
};
const renderTree = (node: WorkspaceNode) => {
const numberOfChosen = getNumberOfChosen(node.id);
const childrenCount = count_children(node);
const isNodeDisabled2 = !props.isPopupMode && !numberOfChosen;
if (!childrenCount && !props.isPopupMode) {
return <div />;
}
return (
<StyledTreeItem
key={node.id}
style={{ marginBottom: 5, padding: 5 }}
nodeId={node.id}
labelText={node.name}
type={node.type}
labelIcon={FolderIcon}
labelInfo="90"
searchWord={props.accountFilter}
idText={node.id}
color="#1a73e8"
bgColor="#e8f0fe"
parent={node.parent_id}
nodeChildren={node.nodes.length}
childrenCount={childrenCount}
numberOfChosen={numberOfChosen}
isNodeDisabled={isDisabled(node.id)}
isNodeDisabled2={isNodeDisabled2}
onClick={(e) => {
if (!isNodeDisabled2) {
console.log('clicked is', node.id);
e.stopPropagation();
selectNode(node, numberOfChosen);
}
}}
>
{Array.isArray(node.nodes) ? node.nodes.map((innerNode) => renderTree(innerNode)) : null}
</StyledTreeItem>
);
};
const selectNode = (node: WorkspaceNode, numberOfChosen: string) => {
if (!props.isPopupMode && !numberOfChosen) {
return;
}
let breadCrumbs = '';
const parentsQueue = [];
if (node.parent_id != null) {
const parentNode = getNode(node.parent_id) as any;
if (parentNode != null) {
parentsQueue.push(parentNode.name);
}
}
parentsQueue.reverse().forEach((parentName) => {
breadCrumbs += parentName + ' / ';
});
breadCrumbs += node.name;
node.breadCrumbs = breadCrumbs;
props.selectNode(node);
};
return (
<div>
{wizardContext.rolesWizard.isWorkspaceTreeLoading ? (
<CircularProgress
size={48}
className="tree_loader"
style={{
color: 'blue',
}}
/>
) : (
<TreeView
onNodeToggle={handleToggle}
expanded={expanded}
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
aria-label="file system navigator"
sx={{ overflowY: 'auto' }}
>
{filteredNodes.map((node) => renderTree(node))}
</TreeView>
)}
</div>
);
};
Editor is loading...