Untitled

mail@pastecode.io avatar
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>
  );
};