Untitled
unknown
javascript
a year ago
13 kB
1
Indexable
Never
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> ); };