Spaces:
Configuration error
Configuration error
| import { useState, useRef, useEffect } from 'react' | |
| import { useSelector } from 'react-redux' | |
| import PropTypes from 'prop-types' | |
| // material-ui | |
| 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' | |
| // third-party | |
| import PerfectScrollbar from 'react-perfect-scrollbar' | |
| // project imports | |
| import MainCard from 'ui-component/cards/MainCard' | |
| import Transitions from 'ui-component/extended/Transitions' | |
| import { StyledFab } from 'ui-component/button/StyledFab' | |
| // icons | |
| import { IconPlus, IconSearch, IconMinus } from '@tabler/icons' | |
| // const | |
| import { baseURL } from 'store/constant' | |
| // ==============================|| ADD NODES||============================== // | |
| 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 | |