diff --git a/web/README.md b/web/README.md index 7d97cac..e037846 100644 --- a/web/README.md +++ b/web/README.md @@ -8,7 +8,8 @@ > `http://192.168.78.4:8090` - correct > > `http://192.168.78.4:8090/` - wrong -3. `yarn start` +3. in `.env` file add TMDB api key +4. `yarn start` ### Eslint > Prettier will fix the code every time the code is saved diff --git a/web/package.json b/web/package.json index 1dbb14a..d166c66 100644 --- a/web/package.json +++ b/web/package.json @@ -7,7 +7,6 @@ "@material-ui/icons": "^4.11.2", "axios": "^0.21.1", "clsx": "^1.1.1", - "fontsource-roboto": "^4.0.0", "i18next": "^20.3.1", "i18next-browser-languagedetector": "^6.1.1", "lodash": "^4.17.21", diff --git a/web/public/android-chrome-192x192.png b/web/public/android-chrome-192x192.png new file mode 100644 index 0000000..d34d722 Binary files /dev/null and b/web/public/android-chrome-192x192.png differ diff --git a/web/public/android-chrome-512x512.png b/web/public/android-chrome-512x512.png new file mode 100644 index 0000000..18151cd Binary files /dev/null and b/web/public/android-chrome-512x512.png differ diff --git a/web/public/apple-touch-icon.png b/web/public/apple-touch-icon.png new file mode 100644 index 0000000..e36ede4 Binary files /dev/null and b/web/public/apple-touch-icon.png differ diff --git a/web/public/browserconfig.xml b/web/public/browserconfig.xml new file mode 100644 index 0000000..b3930d0 --- /dev/null +++ b/web/public/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/web/public/favicon-16x16.png b/web/public/favicon-16x16.png new file mode 100644 index 0000000..3e592b8 Binary files /dev/null and b/web/public/favicon-16x16.png differ diff --git a/web/public/favicon-32x32.png b/web/public/favicon-32x32.png new file mode 100644 index 0000000..4153179 Binary files /dev/null and b/web/public/favicon-32x32.png differ diff --git a/web/public/favicon.ico b/web/public/favicon.ico new file mode 100644 index 0000000..b234d84 Binary files /dev/null and b/web/public/favicon.ico differ diff --git a/web/public/index.html b/web/public/index.html index 41b4208..7e9388a 100644 --- a/web/public/index.html +++ b/web/public/index.html @@ -1,33 +1,43 @@ - - - - - - - TorrServer - - - -
+ + + + + + + + + + + + + - - - - - + TorrServer + + + + +
+ + + + + + + \ No newline at end of file diff --git a/web/public/mstile-150x150.png b/web/public/mstile-150x150.png new file mode 100644 index 0000000..ac32aeb Binary files /dev/null and b/web/public/mstile-150x150.png differ diff --git a/web/public/safari-pinned-tab.svg b/web/public/safari-pinned-tab.svg new file mode 100644 index 0000000..2ad030d --- /dev/null +++ b/web/public/safari-pinned-tab.svg @@ -0,0 +1,265 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + diff --git a/web/public/site.webmanifest b/web/public/site.webmanifest new file mode 100644 index 0000000..52b6bb1 --- /dev/null +++ b/web/public/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/web/src/components/Add/AddDialog.jsx b/web/src/components/Add/AddDialog.jsx index aa59b0b..9aac84d 100644 --- a/web/src/components/Add/AddDialog.jsx +++ b/web/src/components/Add/AddDialog.jsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import Button from '@material-ui/core/Button' import Dialog from '@material-ui/core/Dialog' import { torrentsHost, torrentUploadHost } from 'utils/Hosts' @@ -9,6 +9,9 @@ import useChangeLanguage from 'utils/useChangeLanguage' import { useMediaQuery } from '@material-ui/core' import CircularProgress from '@material-ui/core/CircularProgress' import usePreviousState from 'utils/usePreviousState' +import { useQuery } from 'react-query' +import { getTorrents } from 'utils/Utils' +import parseTorrent from 'parse-torrent' import { checkImageURL, getMoviePosters, chechTorrentSource, parseTorrentTitle } from './helpers' import { ButtonWrapper, Content, Header } from './style' @@ -23,31 +26,75 @@ export default function AddDialog({ poster: originalPoster, }) { const { t } = useTranslation() + const isEditMode = !!originalHash const [torrentSource, setTorrentSource] = useState(originalHash || '') const [title, setTitle] = useState(originalTitle || '') + const [originalTorrentTitle, setOriginalTorrentTitle] = useState('') + const [parsedTitle, setParsedTitle] = useState('') const [posterUrl, setPosterUrl] = useState(originalPoster || '') const [isPosterUrlCorrect, setIsPosterUrlCorrect] = useState(false) const [isTorrentSourceCorrect, setIsTorrentSourceCorrect] = useState(false) + const [isHashAlreadyExists, setIsHashAlreadyExists] = useState(false) const [posterList, setPosterList] = useState() - const [isUserInteractedWithPoster, setIsUserInteractedWithPoster] = useState(false) + const [isUserInteractedWithPoster, setIsUserInteractedWithPoster] = useState(isEditMode) 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 [isEditMode, setIsEditMode] = useState(false) + const [isCustomTitleEnabled, setIsCustomTitleEnabled] = useState(false) + + const { data: torrents } = useQuery('torrents', getTorrents, { + retry: 1, + refetchInterval: 1000, + }) + + useEffect(() => { + const allHashes = torrents.map(({ hash }) => hash) + + parseTorrent.remote(selectedFile || torrentSource, (err, { infoHash } = {}) => { + setIsHashAlreadyExists(allHashes.includes(infoHash)) + }) + }, [selectedFile, torrentSource, torrents]) const fullScreen = useMediaQuery('@media (max-width:930px)') + const updateTitleFromSource = useCallback(() => { + parseTorrentTitle(selectedFile || torrentSource, ({ parsedTitle, originalName }) => { + if (!originalName) return + + setSkipDebounce(true) + setTitle('') + setIsCustomTitleEnabled(false) + setOriginalTorrentTitle(originalName) + setParsedTitle(parsedTitle) + }) + }, [selectedFile, torrentSource]) + + useEffect(() => { + if (!selectedFile && !torrentSource) { + setTitle('') + setOriginalTorrentTitle('') + setParsedTitle('') + setIsCustomTitleEnabled(false) + setPosterList() + removePoster() + setIsUserInteractedWithPoster(false) + } + }, [selectedFile, torrentSource]) + + const removePoster = () => { + setIsPosterUrlCorrect(false) + setPosterUrl('') + } + useEffect(() => { if (originalHash) { - setIsEditMode(true) - checkImageURL(posterUrl).then(correctImage => { correctImage ? setIsPosterUrlCorrect(true) : removePoster() }) } - // This is needed only on mount + // This is needed only on mount. Do not remove line below // eslint-disable-next-line react-hooks/exhaustive-deps }, []) @@ -85,54 +132,51 @@ export default function AddDialog({ const delayedPosterSearch = useMemo(() => debounce(posterSearch, 700), [posterSearch]) - const prevTitleState = usePreviousState(title) const prevTorrentSourceState = usePreviousState(torrentSource) useEffect(() => { - // if torrentSource is updated then we are checking that source is valid and getting title from the source - const torrentSourceChanged = torrentSource !== prevTorrentSourceState - const isCorrectSource = chechTorrentSource(torrentSource) if (!isCorrectSource) return setIsTorrentSourceCorrect(false) setIsTorrentSourceCorrect(true) - if (torrentSourceChanged) { - parseTorrentTitle(selectedFile || torrentSource, newTitle => { - if (!newTitle) return + // if torrentSource is updated then we are getting title from the source + const torrentSourceChanged = torrentSource !== prevTorrentSourceState + if (!torrentSourceChanged) return - setSkipDebounce(true) - setTitle(newTitle) - }) - } - }, [prevTorrentSourceState, selectedFile, torrentSource]) + updateTitleFromSource() + }, [prevTorrentSourceState, selectedFile, torrentSource, updateTitleFromSource]) + + const prevTitleState = usePreviousState(title) useEffect(() => { // if title exists and title was changed then search poster. const titleChanged = title !== prevTitleState - if (!titleChanged) return + if (!titleChanged && !parsedTitle) return if (skipDebounce) { - posterSearch(title, posterSearchLanguage) + posterSearch(title || parsedTitle, posterSearchLanguage) setSkipDebounce(false) + } else if (!title) { + if (parsedTitle) { + posterSearch(parsedTitle, posterSearchLanguage) + } else { + delayedPosterSearch.cancel() + !isUserInteractedWithPoster && removePoster() + } } else { - title === '' ? removePoster() : delayedPosterSearch(title, posterSearchLanguage) + delayedPosterSearch(title, posterSearchLanguage) } - }, [title, prevTitleState, delayedPosterSearch, posterSearch, posterSearchLanguage, skipDebounce]) - - const removePoster = () => { - setIsPosterUrlCorrect(false) - setPosterUrl('') - } - - useEffect(() => { - if (!selectedFile && !torrentSource) { - setTitle('') - setPosterList() - removePoster() - setIsUserInteractedWithPoster(false) - } - }, [selectedFile, torrentSource]) + }, [ + title, + parsedTitle, + prevTitleState, + delayedPosterSearch, + posterSearch, + posterSearchLanguage, + skipDebounce, + isUserInteractedWithPoster, + ]) const handleSave = () => { setIsLoadingButton(true) @@ -142,7 +186,7 @@ export default function AddDialog({ .post(torrentsHost(), { action: 'set', hash: originalHash, - title: title === '' ? originalName : title, + title: title || originalName, poster: posterUrl, }) .finally(handleClose) @@ -185,13 +229,16 @@ export default function AddDialog({ )} @@ -212,7 +263,7 @@ export default function AddDialog({