244 lines
9.2 KiB
JavaScript
244 lines
9.2 KiB
JavaScript
import PropTypes from 'prop-types'
|
|
import { createPortal } from 'react-dom'
|
|
import { useDispatch } from 'react-redux'
|
|
import { useState, useEffect } from 'react'
|
|
|
|
import {
|
|
Box,
|
|
Button,
|
|
Dialog,
|
|
DialogActions,
|
|
DialogContent,
|
|
DialogTitle,
|
|
FormControl,
|
|
IconButton,
|
|
OutlinedInput,
|
|
Stack,
|
|
Typography
|
|
} from '@mui/material'
|
|
import { IconEraser, IconTrash, IconX } from '@tabler/icons'
|
|
import PerfectScrollbar from 'react-perfect-scrollbar'
|
|
|
|
import { BackdropLoader } from 'ui-component/loading/BackdropLoader'
|
|
import { StyledButton } from 'ui-component/button/StyledButton'
|
|
|
|
import scraperApi from 'api/scraper'
|
|
|
|
import useNotifier from 'utils/useNotifier'
|
|
|
|
import {
|
|
HIDE_CANVAS_DIALOG,
|
|
SHOW_CANVAS_DIALOG,
|
|
enqueueSnackbar as enqueueSnackbarAction,
|
|
closeSnackbar as closeSnackbarAction
|
|
} from 'store/actions'
|
|
|
|
const ManageScrapedLinksDialog = ({ show, dialogProps, onCancel, onSave }) => {
|
|
const portalElement = document.getElementById('portal')
|
|
const dispatch = useDispatch()
|
|
|
|
useNotifier()
|
|
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
|
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
|
|
|
const [loading, setLoading] = useState(false)
|
|
const [selectedLinks, setSelectedLinks] = useState([])
|
|
const [url, setUrl] = useState('')
|
|
|
|
useEffect(() => {
|
|
if (dialogProps.url) setUrl(dialogProps.url)
|
|
if (dialogProps.selectedLinks) setSelectedLinks(dialogProps.selectedLinks)
|
|
|
|
return () => {
|
|
setLoading(false)
|
|
setSelectedLinks([])
|
|
setUrl('')
|
|
}
|
|
}, [dialogProps])
|
|
|
|
useEffect(() => {
|
|
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
|
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
|
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
|
|
}, [show, dispatch])
|
|
|
|
const handleFetchLinks = async () => {
|
|
setLoading(true)
|
|
try {
|
|
const fetchLinksResp = await scraperApi.fetchLinks(url, dialogProps.relativeLinksMethod, dialogProps.limit)
|
|
if (fetchLinksResp.data) {
|
|
setSelectedLinks(fetchLinksResp.data.links)
|
|
enqueueSnackbar({
|
|
message: 'Successfully fetched links',
|
|
options: {
|
|
key: new Date().getTime() + Math.random(),
|
|
variant: 'success',
|
|
action: (key) => (
|
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
|
<IconX />
|
|
</Button>
|
|
)
|
|
}
|
|
})
|
|
}
|
|
} catch (error) {
|
|
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
|
enqueueSnackbar({
|
|
message: errorData,
|
|
options: {
|
|
key: new Date().getTime() + Math.random(),
|
|
variant: 'error',
|
|
persist: true,
|
|
action: (key) => (
|
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
|
<IconX />
|
|
</Button>
|
|
)
|
|
}
|
|
})
|
|
}
|
|
setLoading(false)
|
|
}
|
|
|
|
const handleChangeLink = (index, event) => {
|
|
const { value } = event.target
|
|
const links = [...selectedLinks]
|
|
links[index] = value
|
|
setSelectedLinks(links)
|
|
}
|
|
|
|
const handleRemoveLink = (index) => {
|
|
const links = [...selectedLinks]
|
|
links.splice(index, 1)
|
|
setSelectedLinks(links)
|
|
}
|
|
|
|
const handleRemoveAllLinks = () => {
|
|
setSelectedLinks([])
|
|
}
|
|
|
|
const handleSaveLinks = () => {
|
|
onSave(url, selectedLinks)
|
|
}
|
|
|
|
const component = show ? (
|
|
<Dialog
|
|
onClose={onCancel}
|
|
open={show}
|
|
fullWidth
|
|
maxWidth='sm'
|
|
aria-labelledby='manage-scraped-links-dialog-title'
|
|
aria-describedby='manage-scraped-links-dialog-description'
|
|
>
|
|
<DialogTitle sx={{ fontSize: '1rem' }} id='manage-scraped-links-dialog-title'>
|
|
{dialogProps.title || `Manage Scraped Links - ${url}`}
|
|
</DialogTitle>
|
|
<DialogContent>
|
|
<Box sx={{ mb: 4 }}>
|
|
<Stack flexDirection='row' gap={1} sx={{ width: '100%' }}>
|
|
<FormControl sx={{ mt: 1, width: '100%', display: 'flex', flexShrink: 1 }} size='small'>
|
|
<OutlinedInput
|
|
id='url'
|
|
size='small'
|
|
type='text'
|
|
value={url}
|
|
name='url'
|
|
onChange={(e) => {
|
|
setUrl(e.target.value)
|
|
}}
|
|
/>
|
|
</FormControl>
|
|
<Button
|
|
disabled={!url}
|
|
sx={{ borderRadius: '12px', mt: 1, display: 'flex', flexShrink: 0 }}
|
|
size='small'
|
|
variant='contained'
|
|
onClick={handleFetchLinks}
|
|
>
|
|
Fetch Links
|
|
</Button>
|
|
</Stack>
|
|
</Box>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1.5 }}>
|
|
<Typography sx={{ fontWeight: 500 }}>Scraped Links</Typography>
|
|
{selectedLinks.length > 0 ? (
|
|
<StyledButton
|
|
sx={{ height: 'max-content', width: 'max-content' }}
|
|
variant='outlined'
|
|
color='error'
|
|
title='Clear All Links'
|
|
onClick={handleRemoveAllLinks}
|
|
startIcon={<IconEraser />}
|
|
>
|
|
Clear All
|
|
</StyledButton>
|
|
) : null}
|
|
</Box>
|
|
<>
|
|
{loading && <BackdropLoader open={loading} />}
|
|
{selectedLinks.length > 0 ? (
|
|
<PerfectScrollbar
|
|
style={{
|
|
height: '100%',
|
|
maxHeight: '320px',
|
|
overflowX: 'hidden',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: 4
|
|
}}
|
|
>
|
|
{selectedLinks.map((link, index) => (
|
|
<div key={index} style={{ display: 'flex', width: '100%' }}>
|
|
<Box sx={{ display: 'flex', width: '100%' }}>
|
|
<OutlinedInput
|
|
sx={{ width: '100%' }}
|
|
key={index}
|
|
type='text'
|
|
onChange={(e) => handleChangeLink(index, e)}
|
|
size='small'
|
|
value={link}
|
|
name={`link_${index}`}
|
|
/>
|
|
</Box>
|
|
<Box sx={{ width: 'auto', flexGrow: 1 }}>
|
|
<IconButton
|
|
sx={{ height: 30, width: 30 }}
|
|
size='small'
|
|
color='error'
|
|
onClick={() => handleRemoveLink(index)}
|
|
edge='end'
|
|
>
|
|
<IconTrash />
|
|
</IconButton>
|
|
</Box>
|
|
</div>
|
|
))}
|
|
</PerfectScrollbar>
|
|
) : (
|
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
<Typography sx={{ my: 2 }}>Links scraped from the URL will appear here</Typography>
|
|
</div>
|
|
)}
|
|
</>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={onCancel}>Cancel</Button>
|
|
<StyledButton variant='contained' onClick={handleSaveLinks}>
|
|
Save
|
|
</StyledButton>
|
|
</DialogActions>
|
|
</Dialog>
|
|
) : null
|
|
|
|
return createPortal(component, portalElement)
|
|
}
|
|
|
|
ManageScrapedLinksDialog.propTypes = {
|
|
show: PropTypes.bool,
|
|
dialogProps: PropTypes.object,
|
|
onCancel: PropTypes.func,
|
|
onSave: PropTypes.func
|
|
}
|
|
|
|
export default ManageScrapedLinksDialog
|