Merge branch 'addDialog-refactor'

This commit is contained in:
Daniel Shleifman
2021-06-10 23:51:06 +03:00
5 changed files with 321 additions and 210 deletions

View File

@@ -1,65 +1,46 @@
import { useEffect, useMemo, useState } from 'react'
import Button from '@material-ui/core/Button'
import TextField from '@material-ui/core/TextField'
import Dialog from '@material-ui/core/Dialog'
import { torrentsHost, torrentUploadHost } from 'utils/Hosts'
import axios from 'axios'
import { useTranslation } from 'react-i18next'
import { NoImageIcon, AddItemIcon, TorrentIcon } from 'icons'
import debounce from 'lodash/debounce'
import { v4 as uuidv4 } from 'uuid'
import useChangeLanguage from 'utils/useChangeLanguage'
import { Cancel as CancelIcon } from '@material-ui/icons'
import { useDropzone } from 'react-dropzone'
import { useMediaQuery } from '@material-ui/core'
import parseTorrent from 'parse-torrent'
import ptt from 'parse-torrent-title'
import CircularProgress from '@material-ui/core/CircularProgress'
import usePreviousState from 'utils/usePreviousState'
import { checkImageURL, getMoviePosters, chechTorrentSource } from './helpers'
import {
ButtonWrapper,
CancelIconWrapper,
ClearPosterButton,
PosterLanguageSwitch,
Content,
Header,
IconWrapper,
RightSide,
Poster,
PosterSuggestions,
PosterSuggestionsItem,
PosterWrapper,
LeftSide,
LeftSideBottomSectionFileSelected,
LeftSideBottomSectionNoFile,
LeftSideTopSection,
TorrentIconWrapper,
RightSideContainer,
} from './style'
import { checkImageURL, getMoviePosters, chechTorrentSource, parseTorrentTitle } from './helpers'
import { ButtonWrapper, Content, Header } from './style'
import RightSideComponent from './RightSideComponent'
import LeftSideComponent from './LeftSideComponent'
export default function AddDialog({ handleClose }) {
const { t } = useTranslation()
const [torrentSource, setTorrentSource] = useState('')
const [isTorrentSourceActive, setIsTorrentSourceActive] = useState(false)
const [title, setTitle] = useState('')
const [posterUrl, setPosterUrl] = useState('')
const [isPosterUrlCorrect, setIsPosterUrlCorrect] = useState(false)
const [isTorrentSourceCorrect, setIsTorrentSourceCorrect] = useState(false)
const [posterList, setPosterList] = useState()
const [isUserInteractedWithPoster, setIsUserInteractedWithPoster] = useState(false)
const [isUserInteractedWithTitle, setIsUserInteractedWithTitle] = useState(false)
const [currentLang] = useChangeLanguage()
const [selectedFile, setSelectedFile] = useState()
const [posterSearchLanguage, setPosterSearchLanguage] = useState(currentLang === 'ru' ? 'ru' : 'en')
const [isLoadingButton, setIsLoadingButton] = useState(false)
const [skipDebounce, setSkipDebounce] = useState(false)
const fullScreen = useMediaQuery('@media (max-width:930px)')
const posterSearch = useMemo(
() =>
(movieName, language, settings = {}) => {
const { shouldRefreshMainPoster = false } = settings
(movieName, language, { shouldRefreshMainPoster = false } = {}) => {
if (!movieName) {
setPosterList()
removePoster()
return
}
getMoviePosters(movieName, language).then(urlList => {
if (urlList) {
setPosterList(urlList)
@@ -85,65 +66,54 @@ export default function AddDialog({ handleClose }) {
const delayedPosterSearch = useMemo(() => debounce(posterSearch, 700), [posterSearch])
const prevTitleState = usePreviousState(title)
const prevTorrentSourceState = usePreviousState(torrentSource)
useEffect(() => {
if (isUserInteractedWithTitle) return
// if torrentSource is updated then we are checking that source is valid and getting title from the source
const torrentSourceChanged = torrentSource !== prevTorrentSourceState
parseTorrent.remote(selectedFile || torrentSource, (err, parsedTorrent) => {
if (err) throw err
if (!parsedTorrent.name) return
const isCorrectSource = chechTorrentSource(torrentSource)
if (!isCorrectSource) return setIsTorrentSourceCorrect(false)
const torrentName = ptt.parse(parsedTorrent.name).title
const fileInsideTorrentName = parsedTorrent.files ? ptt.parse(parsedTorrent.files[0].name).title : null
setIsTorrentSourceCorrect(true)
let value = torrentName
if (fileInsideTorrentName) {
value = torrentName.length < fileInsideTorrentName.length ? torrentName : fileInsideTorrentName
}
if (torrentSourceChanged) {
parseTorrentTitle(selectedFile || torrentSource, newTitle => {
if (!newTitle) return
setTitle(value)
delayedPosterSearch(value, posterSearchLanguage)
setSkipDebounce(true)
setTitle(newTitle)
})
}, [selectedFile, delayedPosterSearch, torrentSource, posterSearchLanguage, isUserInteractedWithTitle])
}
}, [prevTorrentSourceState, selectedFile, torrentSource])
useEffect(() => {
setIsTorrentSourceCorrect(chechTorrentSource(torrentSource))
// if title exists and title was changed then search poster.
const titleChanged = title !== prevTitleState
if (!titleChanged) return
if (!torrentSource) {
setPosterUrl('')
setPosterList()
setIsPosterUrlCorrect(false)
if (skipDebounce) {
posterSearch(title, posterSearchLanguage)
setSkipDebounce(false)
} else {
delayedPosterSearch(title, posterSearchLanguage)
}
}, [torrentSource])
const handleCapture = files => {
const [file] = files
if (!file) return
setIsUserInteractedWithPoster(false)
setSelectedFile(file)
setTorrentSource(file.name)
}
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop: handleCapture, accept: '.torrent' })
}, [title, prevTitleState, delayedPosterSearch, posterSearch, posterSearchLanguage, skipDebounce])
const removePoster = () => {
setIsPosterUrlCorrect(false)
setPosterUrl('')
}
const handleTorrentSourceChange = ({ target: { value } }) => setTorrentSource(value)
const handleTitleChange = ({ target: { value } }) => {
setTitle(value)
delayedPosterSearch(value, posterSearchLanguage)
torrentSource && setIsUserInteractedWithTitle(true)
}
const handlePosterUrlChange = ({ target: { value } }) => {
setPosterUrl(value)
checkImageURL(value).then(setIsPosterUrlCorrect)
setIsUserInteractedWithPoster(!!value)
useEffect(() => {
if (!selectedFile && !torrentSource) {
setTitle('')
setPosterList()
removePoster()
setIsUserInteractedWithPoster(false)
}
}, [selectedFile, torrentSource])
const handleSave = () => {
setIsLoadingButton(true)
@@ -155,10 +125,7 @@ export default function AddDialog({ handleClose }) {
data.append('file', selectedFile)
title && data.append('title', title)
posterUrl && data.append('poster', posterUrl)
axios
.post(torrentUploadHost(), data)
// .then(res => console.log(res))
.finally(handleClose)
axios.post(torrentUploadHost(), data).finally(handleClose)
} else {
// link save
axios
@@ -167,18 +134,6 @@ export default function AddDialog({ handleClose }) {
}
}
const clearSelectedFile = () => {
setSelectedFile()
setTorrentSource('')
setIsUserInteractedWithTitle(false)
}
const userChangesPosterUrl = url => {
setPosterUrl(url)
checkImageURL(url).then(setIsPosterUrlCorrect)
setIsUserInteractedWithPoster(true)
}
return (
<Dialog
open
@@ -191,120 +146,32 @@ export default function AddDialog({ handleClose }) {
<Header>{t('AddNewTorrent')}</Header>
<Content>
<LeftSide>
<LeftSideTopSection active={isTorrentSourceActive}>
<TextField
onChange={handleTorrentSourceChange}
value={torrentSource}
margin='dense'
label={t('TorrentSourceLink')}
helperText={t('TorrentSourceOptions')}
type='text'
fullWidth
onFocus={() => setIsTorrentSourceActive(true)}
onBlur={() => setIsTorrentSourceActive(false)}
inputProps={{ autoComplete: 'off' }}
disabled={!!selectedFile}
/>
</LeftSideTopSection>
{selectedFile ? (
<LeftSideBottomSectionFileSelected>
<TorrentIconWrapper>
<TorrentIcon />
<CancelIconWrapper onClick={clearSelectedFile}>
<CancelIcon />
</CancelIconWrapper>
</TorrentIconWrapper>
</LeftSideBottomSectionFileSelected>
) : (
<LeftSideBottomSectionNoFile isDragActive={isDragActive} {...getRootProps()}>
<input {...getInputProps()} />
<div>{t('AppendFile.Or')}</div>
<IconWrapper>
<AddItemIcon color='primary' />
<div>{t('AppendFile.ClickOrDrag')}</div>
</IconWrapper>
</LeftSideBottomSectionNoFile>
)}
</LeftSide>
<RightSide>
<RightSideContainer isHidden={!isTorrentSourceCorrect}>
<TextField
onChange={handleTitleChange}
value={title}
margin='dense'
label={t('Title')}
type='text'
fullWidth
/>
<TextField
onChange={handlePosterUrlChange}
value={posterUrl}
margin='dense'
label={t('AddPosterLinkInput')}
type='url'
fullWidth
<LeftSideComponent
setIsUserInteractedWithPoster={setIsUserInteractedWithPoster}
setSelectedFile={setSelectedFile}
torrentSource={torrentSource}
setTorrentSource={setTorrentSource}
selectedFile={selectedFile}
/>
<PosterWrapper>
<Poster poster={+isPosterUrlCorrect}>
{isPosterUrlCorrect ? <img src={posterUrl} alt='poster' /> : <NoImageIcon />}
</Poster>
<PosterSuggestions>
{posterList
?.filter(url => url !== posterUrl)
.slice(0, 12)
.map(url => (
<PosterSuggestionsItem onClick={() => userChangesPosterUrl(url)} key={uuidv4()}>
<img src={url} alt='poster' />
</PosterSuggestionsItem>
))}
</PosterSuggestions>
{currentLang !== 'en' && (
<PosterLanguageSwitch
onClick={() => {
const newLanguage = posterSearchLanguage === 'en' ? 'ru' : 'en'
setPosterSearchLanguage(newLanguage)
posterSearch(title, newLanguage, { shouldRefreshMainPoster: true })
}}
showbutton={+isPosterUrlCorrect}
color='primary'
variant='contained'
size='small'
>
{posterSearchLanguage === 'en' ? 'EN' : 'RU'}
</PosterLanguageSwitch>
)}
<ClearPosterButton
showbutton={+isPosterUrlCorrect}
onClick={() => {
removePoster()
setIsUserInteractedWithPoster(true)
}}
color='primary'
variant='contained'
size='small'
>
{t('Clear')}
</ClearPosterButton>
</PosterWrapper>
</RightSideContainer>
<RightSideContainer
isError={torrentSource && !isTorrentSourceCorrect}
notificationMessage={
!torrentSource ? t('AddTorrentSourceNotification') : !isTorrentSourceCorrect && t('WrongTorrentSource')
}
isHidden={isTorrentSourceCorrect}
<RightSideComponent
setTitle={setTitle}
setPosterUrl={setPosterUrl}
setIsPosterUrlCorrect={setIsPosterUrlCorrect}
setIsUserInteractedWithPoster={setIsUserInteractedWithPoster}
setPosterList={setPosterList}
isTorrentSourceCorrect={isTorrentSourceCorrect}
title={title}
posterUrl={posterUrl}
isPosterUrlCorrect={isPosterUrlCorrect}
posterList={posterList}
currentLang={currentLang}
posterSearchLanguage={posterSearchLanguage}
setPosterSearchLanguage={setPosterSearchLanguage}
posterSearch={posterSearch}
removePoster={removePoster}
torrentSource={torrentSource}
/>
</RightSide>
</Content>
<ButtonWrapper>

View File

@@ -0,0 +1,87 @@
import { useTranslation } from 'react-i18next'
import { useDropzone } from 'react-dropzone'
import { AddItemIcon, TorrentIcon } from 'icons'
import TextField from '@material-ui/core/TextField'
import { Cancel as CancelIcon } from '@material-ui/icons'
import { useState } from 'react'
import {
CancelIconWrapper,
IconWrapper,
LeftSide,
LeftSideBottomSectionFileSelected,
LeftSideBottomSectionNoFile,
LeftSideTopSection,
TorrentIconWrapper,
} from './style'
export default function LeftSideComponent({
setIsUserInteractedWithPoster,
setSelectedFile,
torrentSource,
setTorrentSource,
selectedFile,
}) {
const { t } = useTranslation()
const handleCapture = files => {
const [file] = files
if (!file) return
setIsUserInteractedWithPoster(false)
setSelectedFile(file)
setTorrentSource(file.name)
}
const clearSelectedFile = () => {
setSelectedFile()
setTorrentSource('')
}
const [isTorrentSourceActive, setIsTorrentSourceActive] = useState(false)
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop: handleCapture, accept: '.torrent' })
const handleTorrentSourceChange = ({ target: { value } }) => setTorrentSource(value)
return (
<LeftSide>
<LeftSideTopSection active={isTorrentSourceActive}>
<TextField
onChange={handleTorrentSourceChange}
value={torrentSource}
margin='dense'
label={t('TorrentSourceLink')}
helperText={t('TorrentSourceOptions')}
type='text'
fullWidth
onFocus={() => setIsTorrentSourceActive(true)}
onBlur={() => setIsTorrentSourceActive(false)}
inputProps={{ autoComplete: 'off' }}
disabled={!!selectedFile}
/>
</LeftSideTopSection>
{selectedFile ? (
<LeftSideBottomSectionFileSelected>
<TorrentIconWrapper>
<TorrentIcon />
<CancelIconWrapper onClick={clearSelectedFile}>
<CancelIcon />
</CancelIconWrapper>
</TorrentIconWrapper>
</LeftSideBottomSectionFileSelected>
) : (
<LeftSideBottomSectionNoFile isDragActive={isDragActive} {...getRootProps()}>
<input {...getInputProps()} />
<div>{t('AppendFile.Or')}</div>
<IconWrapper>
<AddItemIcon color='primary' />
<div>{t('AppendFile.ClickOrDrag')}</div>
</IconWrapper>
</LeftSideBottomSectionNoFile>
)}
</LeftSide>
)
}

View File

@@ -0,0 +1,120 @@
import { useTranslation } from 'react-i18next'
import { v4 as uuidv4 } from 'uuid'
import { NoImageIcon } from 'icons'
import { TextField } from '@material-ui/core'
import {
ClearPosterButton,
PosterLanguageSwitch,
RightSide,
Poster,
PosterSuggestions,
PosterSuggestionsItem,
PosterWrapper,
RightSideContainer,
} from './style'
import { checkImageURL } from './helpers'
export default function RightSideComponent({
setTitle,
setPosterUrl,
setIsPosterUrlCorrect,
setIsUserInteractedWithPoster,
setPosterList,
isTorrentSourceCorrect,
title,
posterUrl,
isPosterUrlCorrect,
posterList,
currentLang,
posterSearchLanguage,
setPosterSearchLanguage,
posterSearch,
removePoster,
torrentSource,
}) {
const { t } = useTranslation()
const handleTitleChange = ({ target: { value } }) => setTitle(value)
const handlePosterUrlChange = ({ target: { value } }) => {
setPosterUrl(value)
checkImageURL(value).then(setIsPosterUrlCorrect)
setIsUserInteractedWithPoster(!!value)
setPosterList()
}
const userChangesPosterUrl = url => {
setPosterUrl(url)
checkImageURL(url).then(setIsPosterUrlCorrect)
setIsUserInteractedWithPoster(true)
}
return (
<RightSide>
<RightSideContainer isHidden={!isTorrentSourceCorrect}>
<TextField onChange={handleTitleChange} value={title} margin='dense' label={t('Title')} type='text' fullWidth />
<TextField
onChange={handlePosterUrlChange}
value={posterUrl}
margin='dense'
label={t('AddPosterLinkInput')}
type='url'
fullWidth
/>
<PosterWrapper>
<Poster poster={+isPosterUrlCorrect}>
{isPosterUrlCorrect ? <img src={posterUrl} alt='poster' /> : <NoImageIcon />}
</Poster>
<PosterSuggestions>
{posterList
?.filter(url => url !== posterUrl)
.slice(0, 12)
.map(url => (
<PosterSuggestionsItem onClick={() => userChangesPosterUrl(url)} key={uuidv4()}>
<img src={url} alt='poster' />
</PosterSuggestionsItem>
))}
</PosterSuggestions>
{currentLang !== 'en' && (
<PosterLanguageSwitch
onClick={() => {
const newLanguage = posterSearchLanguage === 'en' ? 'ru' : 'en'
setPosterSearchLanguage(newLanguage)
posterSearch(title, newLanguage, { shouldRefreshMainPoster: true })
}}
showbutton={+isPosterUrlCorrect}
color='primary'
variant='contained'
size='small'
>
{posterSearchLanguage === 'en' ? 'EN' : 'RU'}
</PosterLanguageSwitch>
)}
<ClearPosterButton
showbutton={+isPosterUrlCorrect}
onClick={() => {
removePoster()
setIsUserInteractedWithPoster(true)
}}
color='primary'
variant='contained'
size='small'
>
{t('Clear')}
</ClearPosterButton>
</PosterWrapper>
</RightSideContainer>
<RightSideContainer
isError={torrentSource && !isTorrentSourceCorrect}
notificationMessage={
!torrentSource ? t('AddTorrentSourceNotification') : !isTorrentSourceCorrect && t('WrongTorrentSource')
}
isHidden={isTorrentSourceCorrect}
/>
</RightSide>
)
}

View File

@@ -1,10 +1,19 @@
import axios from 'axios'
import parseTorrent from 'parse-torrent'
import ptt from 'parse-torrent-title'
export const getMoviePosters = (movieName, language = 'en') => {
const request = `${`http://api.themoviedb.org/3/search/multi?api_key=${process.env.REACT_APP_TMDB_API_KEY}`}&language=${language}&include_image_language=${language},null&query=${movieName}`
const url = 'http://api.themoviedb.org/3/search/multi'
return axios
.get(request)
.get(url, {
params: {
api_key: process.env.REACT_APP_TMDB_API_KEY,
language,
include_image_language: `${language},null`,
query: movieName,
},
})
.then(({ data: { results } }) =>
results.filter(el => el.poster_path).map(el => `https://image.tmdb.org/t/p/w300${el.poster_path}`),
)
@@ -27,3 +36,20 @@ const hashRegex = /^\b[0-9a-f]{32}\b$|^\b[0-9a-f]{40}\b$|^\b[0-9a-f]{64}\b$/i
const torrentRegex = /^.*\.(torrent)$/i
export const chechTorrentSource = source =>
source.match(hashRegex) !== null || source.match(magnetRegex) !== null || source.match(torrentRegex) !== null
export const parseTorrentTitle = (parsingSource, callback) => {
parseTorrent.remote(parsingSource, (err, { name, files } = {}) => {
if (!name || err) return callback(null)
const torrentName = ptt.parse(name).title
const nameOfFileInsideTorrent = files ? ptt.parse(files[0].name).title : null
let newTitle = torrentName
if (nameOfFileInsideTorrent) {
// taking shorter title because in most cases it is more accurate
newTitle = torrentName.length < nameOfFileInsideTorrent.length ? torrentName : nameOfFileInsideTorrent
}
callback(newTitle)
})
}

View File

@@ -0,0 +1,11 @@
import { useEffect, useRef } from 'react'
export default function usePreviousState(value) {
const ref = useRef(value)
useEffect(() => {
ref.current = value
}, [value])
return ref.current
}