????
Current Path : C:/opt/pgsql/pgAdmin 4/web/pgadmin/static/js/PgTreeView/ |
Current File : C:/opt/pgsql/pgAdmin 4/web/pgadmin/static/js/PgTreeView/index.jsx |
import { Checkbox } from '@mui/material'; import { makeStyles } from '@mui/styles'; import clsx from 'clsx'; import gettext from 'sources/gettext'; import React, { useEffect, useRef } from 'react'; import { Tree } from 'react-arborist'; import AutoSizer from 'react-virtualized-auto-sizer'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; import PropTypes from 'prop-types'; import IndeterminateCheckBoxIcon from '@mui/icons-material/IndeterminateCheckBox'; import EmptyPanelMessage from '../components/EmptyPanelMessage'; import CheckBoxIcon from '@mui/icons-material/CheckBox'; const useStyles = makeStyles((theme) => ({ node: { display: 'inline-block', paddingLeft: '1.5rem', height: '100%', }, checkboxStyle: { fill: theme.palette.primary.main }, tree: { background: theme.palette.background.default, height: '100%', width: '100%', display: 'flex', flexDirection: 'column', flex: 1, }, focusedNode: { background: theme.palette.primary.light, }, leafNode: { marginLeft: '1.5rem' }, })); export const PgTreeSelectionContext = React.createContext(); export default function PgTreeView({ data = [], hasCheckbox = false, selectionChange = null}) { let classes = useStyles(); let treeData = data; const treeObj = useRef(); const treeContainerRef = useRef(); const [selectedCheckBoxNodes, setSelectedCheckBoxNodes] = React.useState([]); const onSelectionChange = () => { let selectedChNodes = treeObj.current.selectedNodes; if (hasCheckbox) { let selectedChildNodes = []; treeObj.current.selectedNodes.forEach((node) => { if(node.isInternal && !node.isOpen) { node.children.forEach((ch)=>{ if(ch.data.isSelected && ch.isLeaf && !selectedChildNodes.includes(ch.id)) { selectedChildNodes.push(ch.id); selectedChNodes.push(ch); } }); } selectedChildNodes.push(node.id); }); setSelectedCheckBoxNodes(selectedChildNodes); } selectionChange?.(selectedChNodes); }; return (<> { treeData.length > 0 ? <PgTreeSelectionContext.Provider value={selectedCheckBoxNodes}> <div ref={(containerRef) => treeContainerRef.current = containerRef} className={clsx(classes.tree)}> <AutoSizer> {({ width, height }) => ( <Tree ref={(obj) => { treeObj.current = obj; }} width={isNaN(width) ? 100 : width} height={isNaN(height) ? 100 : height} data={treeData} disableDrag={true} disableDrop={true} dndRootElement={treeContainerRef.current} > { (props) => <Node onNodeSelectionChange={onSelectionChange} hasCheckbox={hasCheckbox} {...props}></Node> } </Tree> )} </AutoSizer> </div> </PgTreeSelectionContext.Provider> : <EmptyPanelMessage text={gettext('No objects are found to display')}/> } </> ); } PgTreeView.propTypes = { data: PropTypes.array, selectionChange: PropTypes.func, hasCheckbox: PropTypes.bool, }; function Node({ node, style, tree, hasCheckbox, onNodeSelectionChange}) { const classes = useStyles(); const pgTreeSelCtx = React.useContext(PgTreeSelectionContext); const [isSelected, setIsSelected] = React.useState(pgTreeSelCtx.includes(node.id) || node.data?.isSelected); const [isIndeterminate, setIsIndeterminate] = React.useState(node?.parent.level==0); useEffect(()=>{ setIsIndeterminate(node.data.isIndeterminate); }, [node?.data?.isIndeterminate]); useEffect(()=>{ if(isSelected){ if(!pgTreeSelCtx.includes(node.id)){ tree.selectMulti(node.id); onNodeSelectionChange(); } } }, [isSelected]); const onCheckboxSelection = (e) => { if (hasCheckbox) { setIsSelected(e.currentTarget.checked); node.data.isSelected = e.currentTarget.checked; if (e.currentTarget.checked) { node.selectMulti(node.id); if (!node.isLeaf) { node.data.isIndeterminate = false; selectAllChild(node, tree, 'checkbox', pgTreeSelCtx); } else if (node?.parent) { checkAndSelectParent(node); } if(node?.level == 0) { node.data.isIndeterminate = false; } node.focus(); } else { node.deselect(node); if (!node.isLeaf) { deselectAllChild(node); } if(node?.parent){ node.parent.data.isIndeterminate = false; delectPrentNode(node.parent); } } } tree.scrollTo(node.id, 'center'); onNodeSelectionChange(); }; const onSelect = (e) => { node.focus(); e.stopPropagation(); }; const onKeyDown = (e) => { if(e.code == 'Enter') { onSelect(e); } }; return ( <div style={style} className={clsx(node.isFocused ? classes.focusedNode : '')} onClick={onSelect} onKeyDown={onKeyDown}> <CollectionArrow node={node} tree={tree} selectedNodeIds={pgTreeSelCtx} /> { hasCheckbox ? <Checkbox style={{ padding: 0 }} color="primary" className={clsx(!node.isInternal ? classes.leafNode: null)} checked={isSelected} checkedIcon={isIndeterminate ? <IndeterminateCheckBoxIcon style={{height: '1.4rem'}} />: <CheckBoxIcon style={{height: '1.4rem'}} />} onChange={onCheckboxSelection}/> : <span className={clsx(node.data.icon)}></span> } <div className={clsx(node.data.icon, classes.node)}>{node.data.name}</div> </div> ); } Node.propTypes = { node: PropTypes.object, style: PropTypes.any, tree: PropTypes.object, hasCheckbox: PropTypes.bool, onNodeSelectionChange: PropTypes.func }; function CollectionArrow({ node, tree, selectedNodeIds }) { const toggleNode = () => { node.isInternal && node.toggle(); if (node.isSelected && node.isOpen) { node.data.isSelected = true; selectAllChild(node, tree, 'expand', selectedNodeIds); } }; return ( <span onClick={toggleNode} onKeyDown={()=>{/* handled by parent */}}> {node.isInternal && node?.children.length > 0 ? <ToggleArrowIcon node={node} /> : null} </span> ); } CollectionArrow.propTypes = { node: PropTypes.object, tree: PropTypes.object, selectedNodeIds: PropTypes.array }; function ToggleArrowIcon({node}){ return (<>{node.isOpen ? <ExpandMoreIcon /> : <ChevronRightIcon />}</>); } ToggleArrowIcon.propTypes = { node: PropTypes.object, }; function checkAndSelectParent(chNode){ let isAllChildSelected = true; chNode?.parent?.children?.forEach((child) => { if (!child.isSelected) { isAllChildSelected = false; } }); if (chNode?.parent) { if (isAllChildSelected) { if (chNode.parent?.level == 0) { chNode.parent.data.isIndeterminate = true; } else { chNode.parent.data.isIndeterminate = false; } chNode.parent.selectMulti(chNode.parent.id); } else { chNode.parent.data.isIndeterminate = true; chNode.parent.selectMulti(chNode.parent.id); } chNode.parent.data.isSelected = true; checkAndSelectParent(chNode.parent); } } checkAndSelectParent.propTypes = { chNode: PropTypes.object }; function delectPrentNode(chNode){ if (chNode) { let isAnyChildSelected = false; chNode.children.forEach((childNode)=>{ if(childNode.isSelected && !isAnyChildSelected){ isAnyChildSelected = true; } }); if(isAnyChildSelected){ chNode.data.isSelected = true; chNode.data.isIndeterminate = true; } else { chNode.deselect(chNode); chNode.data.isSelected = false; } } if (chNode?.parent) { delectPrentNode(chNode.parent); } } function selectAllChild(chNode, tree, source, selectedNodeIds){ let selectedChild = 0; chNode?.children?.forEach(child => { if(!child.isLeaf) { child.data.isIndeterminate = false; } if((source == 'expand' && selectedNodeIds.includes(child.id)) || source == 'checkbox') { child.data.isSelected = true; selectedChild += 1; } child.selectMulti(child.id); if (child?.children) { selectAllChild(child, tree, source, selectedNodeIds); } }); if(selectedChild < chNode?.children.length ){ chNode.data.isIndeterminate = true; } else { chNode.data.isIndeterminate = false; } if (chNode?.parent) { checkAndSelectParent(chNode); } } function deselectAllChild(chNode){ chNode?.children.forEach(child => { child.deselect(child); child.data.isSelected = false; if (child?.children) { deselectAllChild(child); } }); }