|
import { useState, useRef, useEffect } from 'react' |
|
import { useSelector } from 'react-redux' |
|
import PropTypes from 'prop-types' |
|
|
|
|
|
import { useTheme } from '@mui/material/styles' |
|
import { |
|
Accordion, |
|
AccordionSummary, |
|
AccordionDetails, |
|
Box, |
|
ClickAwayListener, |
|
Divider, |
|
InputAdornment, |
|
List, |
|
ListItemButton, |
|
ListItem, |
|
ListItemAvatar, |
|
ListItemText, |
|
OutlinedInput, |
|
Paper, |
|
Popper, |
|
Stack, |
|
Typography |
|
} from '@mui/material' |
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore' |
|
|
|
|
|
import PerfectScrollbar from 'react-perfect-scrollbar' |
|
|
|
|
|
import MainCard from 'ui-component/cards/MainCard' |
|
import Transitions from 'ui-component/extended/Transitions' |
|
import { StyledFab } from 'ui-component/button/StyledFab' |
|
|
|
|
|
import { IconPlus, IconSearch, IconMinus } from '@tabler/icons' |
|
|
|
|
|
import { baseURL } from 'store/constant' |
|
|
|
|
|
|
|
const AddNodes = ({ nodesData, node }) => { |
|
const theme = useTheme() |
|
const customization = useSelector((state) => state.customization) |
|
|
|
const [searchValue, setSearchValue] = useState('') |
|
const [nodes, setNodes] = useState({}) |
|
const [open, setOpen] = useState(false) |
|
const [categoryExpanded, setCategoryExpanded] = useState({}) |
|
|
|
const anchorRef = useRef(null) |
|
const prevOpen = useRef(open) |
|
const ps = useRef() |
|
|
|
const scrollTop = () => { |
|
const curr = ps.current |
|
if (curr) { |
|
curr.scrollTop = 0 |
|
} |
|
} |
|
|
|
const filterSearch = (value) => { |
|
setSearchValue(value) |
|
setTimeout(() => { |
|
if (value) { |
|
const returnData = nodesData.filter((nd) => nd.name.toLowerCase().includes(value.toLowerCase())) |
|
groupByCategory(returnData, true) |
|
scrollTop() |
|
} else if (value === '') { |
|
groupByCategory(nodesData) |
|
scrollTop() |
|
} |
|
}, 500) |
|
} |
|
|
|
const groupByCategory = (nodes, isFilter) => { |
|
const accordianCategories = {} |
|
const result = nodes.reduce(function (r, a) { |
|
r[a.category] = r[a.category] || [] |
|
r[a.category].push(a) |
|
accordianCategories[a.category] = isFilter ? true : false |
|
return r |
|
}, Object.create(null)) |
|
setNodes(result) |
|
setCategoryExpanded(accordianCategories) |
|
} |
|
|
|
const handleAccordionChange = (category) => (event, isExpanded) => { |
|
const accordianCategories = { ...categoryExpanded } |
|
accordianCategories[category] = isExpanded |
|
setCategoryExpanded(accordianCategories) |
|
} |
|
|
|
const handleClose = (event) => { |
|
if (anchorRef.current && anchorRef.current.contains(event.target)) { |
|
return |
|
} |
|
setOpen(false) |
|
} |
|
|
|
const handleToggle = () => { |
|
setOpen((prevOpen) => !prevOpen) |
|
} |
|
|
|
const onDragStart = (event, node) => { |
|
event.dataTransfer.setData('application/reactflow', JSON.stringify(node)) |
|
event.dataTransfer.effectAllowed = 'move' |
|
} |
|
|
|
useEffect(() => { |
|
if (prevOpen.current === true && open === false) { |
|
anchorRef.current.focus() |
|
} |
|
|
|
prevOpen.current = open |
|
}, [open]) |
|
|
|
useEffect(() => { |
|
if (node) setOpen(false) |
|
}, [node]) |
|
|
|
useEffect(() => { |
|
if (nodesData) groupByCategory(nodesData) |
|
}, [nodesData]) |
|
|
|
return ( |
|
<> |
|
<StyledFab |
|
sx={{ left: 20, top: 20 }} |
|
ref={anchorRef} |
|
size='small' |
|
color='primary' |
|
aria-label='add' |
|
title='Add Node' |
|
onClick={handleToggle} |
|
> |
|
{open ? <IconMinus /> : <IconPlus />} |
|
</StyledFab> |
|
<Popper |
|
placement='bottom-end' |
|
open={open} |
|
anchorEl={anchorRef.current} |
|
role={undefined} |
|
transition |
|
disablePortal |
|
popperOptions={{ |
|
modifiers: [ |
|
{ |
|
name: 'offset', |
|
options: { |
|
offset: [-40, 14] |
|
} |
|
} |
|
] |
|
}} |
|
sx={{ zIndex: 1000 }} |
|
> |
|
{({ TransitionProps }) => ( |
|
<Transitions in={open} {...TransitionProps}> |
|
<Paper> |
|
<ClickAwayListener onClickAway={handleClose}> |
|
<MainCard border={false} elevation={16} content={false} boxShadow shadow={theme.shadows[16]}> |
|
<Box sx={{ p: 2 }}> |
|
<Stack> |
|
<Typography variant='h4'>Add Nodes</Typography> |
|
</Stack> |
|
<OutlinedInput |
|
sx={{ width: '100%', pr: 1, pl: 2, my: 2 }} |
|
id='input-search-node' |
|
value={searchValue} |
|
onChange={(e) => filterSearch(e.target.value)} |
|
placeholder='Search nodes' |
|
startAdornment={ |
|
<InputAdornment position='start'> |
|
<IconSearch stroke={1.5} size='1rem' color={theme.palette.grey[500]} /> |
|
</InputAdornment> |
|
} |
|
aria-describedby='search-helper-text' |
|
inputProps={{ |
|
'aria-label': 'weight' |
|
}} |
|
/> |
|
<Divider /> |
|
</Box> |
|
<PerfectScrollbar |
|
containerRef={(el) => { |
|
ps.current = el |
|
}} |
|
style={{ height: '100%', maxHeight: 'calc(100vh - 320px)', overflowX: 'hidden' }} |
|
> |
|
<Box sx={{ p: 2 }}> |
|
<List |
|
sx={{ |
|
width: '100%', |
|
maxWidth: 370, |
|
py: 0, |
|
borderRadius: '10px', |
|
[theme.breakpoints.down('md')]: { |
|
maxWidth: 370 |
|
}, |
|
'& .MuiListItemSecondaryAction-root': { |
|
top: 22 |
|
}, |
|
'& .MuiDivider-root': { |
|
my: 0 |
|
}, |
|
'& .list-container': { |
|
pl: 7 |
|
} |
|
}} |
|
> |
|
{Object.keys(nodes) |
|
.sort() |
|
.map((category) => ( |
|
<Accordion |
|
expanded={categoryExpanded[category] || false} |
|
onChange={handleAccordionChange(category)} |
|
key={category} |
|
disableGutters |
|
> |
|
<AccordionSummary |
|
expandIcon={<ExpandMoreIcon />} |
|
aria-controls={`nodes-accordian-${category}`} |
|
id={`nodes-accordian-header-${category}`} |
|
> |
|
<Typography variant='h5'>{category}</Typography> |
|
</AccordionSummary> |
|
<AccordionDetails> |
|
{nodes[category].map((node, index) => ( |
|
<div |
|
key={node.name} |
|
onDragStart={(event) => onDragStart(event, node)} |
|
draggable |
|
> |
|
<ListItemButton |
|
sx={{ |
|
p: 0, |
|
borderRadius: `${customization.borderRadius}px`, |
|
cursor: 'move' |
|
}} |
|
> |
|
<ListItem alignItems='center'> |
|
<ListItemAvatar> |
|
<div |
|
style={{ |
|
width: 50, |
|
height: 50, |
|
borderRadius: '50%', |
|
backgroundColor: 'white' |
|
}} |
|
> |
|
<img |
|
style={{ |
|
width: '100%', |
|
height: '100%', |
|
padding: 10, |
|
objectFit: 'contain' |
|
}} |
|
alt={node.name} |
|
src={`${baseURL}/api/v1/node-icon/${node.name}`} |
|
/> |
|
</div> |
|
</ListItemAvatar> |
|
<ListItemText |
|
sx={{ ml: 1 }} |
|
primary={node.label} |
|
secondary={node.description} |
|
/> |
|
</ListItem> |
|
</ListItemButton> |
|
{index === nodes[category].length - 1 ? null : <Divider />} |
|
</div> |
|
))} |
|
</AccordionDetails> |
|
</Accordion> |
|
))} |
|
</List> |
|
</Box> |
|
</PerfectScrollbar> |
|
</MainCard> |
|
</ClickAwayListener> |
|
</Paper> |
|
</Transitions> |
|
)} |
|
</Popper> |
|
</> |
|
) |
|
} |
|
|
|
AddNodes.propTypes = { |
|
nodesData: PropTypes.array, |
|
node: PropTypes.object |
|
} |
|
|
|
export default AddNodes |
|
|