From 0e753c56da57cb78def124dc24c2e36b893caf50 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Wed, 26 May 2021 14:02:18 +0300 Subject: [PATCH 01/40] refactor. memory leak fix --- web/src/components/DialogCacheInfo.jsx | 104 +++++++++++++------------ web/src/components/Torrent/index.jsx | 6 +- 2 files changed, 58 insertions(+), 52 deletions(-) diff --git a/web/src/components/DialogCacheInfo.jsx b/web/src/components/DialogCacheInfo.jsx index ffdef9f..24886a5 100644 --- a/web/src/components/DialogCacheInfo.jsx +++ b/web/src/components/DialogCacheInfo.jsx @@ -5,54 +5,63 @@ import DialogContent from '@material-ui/core/DialogContent' import { getPeerString, humanizeSize } from 'utils/Utils' import { cacheHost } from 'utils/Hosts' -export default function DialogCacheInfo({ hash, open }) { +export default function DialogCacheInfo({ hash }) { const [cache, setCache] = useState({}) - const timerID = useRef(-1) const [pMap, setPMap] = useState([]) + const timerID = useRef(null) + const componentIsMounted = useRef(true) + + useEffect( + () => () => { + componentIsMounted.current = false + }, + [], + ) useEffect(() => { - if (hash) + if (hash) { timerID.current = setInterval(() => { - getCache(hash, cache => { - setCache(cache) + getCache(hash, value => { + // this is needed to avoid memory leak + if (componentIsMounted.current) setCache(value) }) }, 100) - else clearInterval(timerID.current) + } else clearInterval(timerID.current) return () => { clearInterval(timerID.current) } - }, [hash, open]) + }, [hash]) useEffect(() => { - if (cache && cache.PiecesCount && cache.Pieces) { - const map = [] - for (let i = 0; i < cache.PiecesCount; i++) { - const reader = 0 - let cls = 'piece' - let prc = 0 - if (cache.Pieces[i]) { - if (cache.Pieces[i].Completed && cache.Pieces[i].Size >= cache.Pieces[i].Length) cls += ' piece-complete' - else cls += ' piece-loading' - prc = ((cache.Pieces[i].Size / cache.Pieces[i].Length) * 100).toFixed(2) - } + if (!cache?.PiecesCount || !cache?.Pieces) return - cache.Readers.forEach(r => { - if (i >= r.Start && i <= r.End && i !== r.Reader) cls += ' reader-range' - if (i === r.Reader) { - cls += ' piece-reader' - } - }) - map.push({ - prc, - class: cls, - info: i, - reader, - }) + const { Pieces, PiecesCount, Readers } = cache + + const map = [] + + for (let i = 0; i < PiecesCount; i++) { + const cls = ['piece'] + let prc = 0 + + const currentPiece = Pieces[i] + if (currentPiece) { + if (currentPiece.Completed && currentPiece.Size === currentPiece.Length) cls.push('piece-complete') + else cls.push('piece-loading') + + prc = (currentPiece.Size / currentPiece.Length).toFixed(2) } - setPMap(map) + + Readers.forEach(r => { + if (i === r.Reader) return cls.push('piece-reader') + if (i >= r.Start && i <= r.End) cls.push('reader-range') + }) + + map.push({ prc, className: cls.join(' '), id: i }) } - }, [cache.Pieces]) + + setPMap(map) + }, [cache]) return (
@@ -84,20 +93,22 @@ export default function DialogCacheInfo({ hash, open }) {
- {pMap.map(itm => ( - - {itm.prc > 0 && itm.prc < 100 && ( -
- )} - - ))} + {pMap.map(({ prc, className: currentPieceCalss, id }) => { + const boxHeight = 12 + + return ( + + {prc > 0 && prc < 1 &&
} + + ) + })}
) } -function getCache(hash, callback) { +const getCache = (hash, callback) => { try { fetch(cacheHost(), { method: 'post', @@ -108,15 +119,10 @@ function getCache(hash, callback) { }, }) .then(res => res.json()) - .then( - json => { - callback(json) - }, - error => { - callback({}) - console.error(error) - }, - ) + .then(callback, error => { + callback({}) + console.error(error) + }) } catch (e) { console.error(e) callback({}) diff --git a/web/src/components/Torrent/index.jsx b/web/src/components/Torrent/index.jsx index f236d0f..cca4dd1 100644 --- a/web/src/components/Torrent/index.jsx +++ b/web/src/components/Torrent/index.jsx @@ -123,10 +123,10 @@ export default function Torrent({ torrent }) { setOpen(false)} aria-labelledby='form-dialog-title' fullWidth maxWidth='lg'> - {!showCache ? ( - + {showCache ? ( + ) : ( - + )} + {isLoading ? ( 'loading' ) : ( {pMap.map(({ id, percentage, isComplete, inProgress, isActive, isReaderRange }) => { - const currentRow = Math.floor(id / piecesInOneRow) + 1 + const currentRow = Math.floor((isShortView ? id - activeId : id) / piecesInOneRow) - return ( + // -------- related only for short view ------- + if (isActive) activeId = id + const shouldBeRendered = + isActive || (id - activeId <= amountOfBlocksToRenderInShortView && id - activeId >= 0) + // -------------------------------------------- + + return isShortView ? ( + shouldBeRendered && ( + + ) + ) : ( ) })} diff --git a/web/src/components/Settings.jsx b/web/src/components/Settings.jsx index ceb623a..2b94e66 100644 --- a/web/src/components/Settings.jsx +++ b/web/src/components/Settings.jsx @@ -135,13 +135,7 @@ export default function SettingsDialog() {

Retracker mode - From feb9b11993d68dd507c540db5303a25c7b7b4c5a Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Thu, 27 May 2021 13:52:27 +0300 Subject: [PATCH 06/40] refactor --- web/src/components/DialogCacheInfo/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/DialogCacheInfo/index.jsx b/web/src/components/DialogCacheInfo/index.jsx index a80a3c2..f78ac5e 100644 --- a/web/src/components/DialogCacheInfo/index.jsx +++ b/web/src/components/DialogCacheInfo/index.jsx @@ -62,7 +62,7 @@ export default function DialogCacheInfo({ hash }) { }, [hash]) useEffect(() => { - if (!cache?.PiecesCount || !cache?.Pieces) return + if (!cache.PiecesCount || !cache.Pieces) return const { Pieces, PiecesCount, Readers } = cache From e3b276f02b9902cc9136a7fa99d2c784dcb4a1ee Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Thu, 27 May 2021 13:57:16 +0300 Subject: [PATCH 07/40] refactor --- web/src/components/DialogCacheInfo/index.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/src/components/DialogCacheInfo/index.jsx b/web/src/components/DialogCacheInfo/index.jsx index f78ac5e..79c6a29 100644 --- a/web/src/components/DialogCacheInfo/index.jsx +++ b/web/src/components/DialogCacheInfo/index.jsx @@ -15,14 +15,14 @@ export default function DialogCacheInfo({ hash }) { const timerID = useRef(null) const componentIsMounted = useRef(true) const [dimensions, setDimensions] = useState({ width: -1, height: -1 }) + const [isShortView, setIsShortView] = useState(true) + const [isLoading, setIsLoading] = useState(true) const [stageSettings, setStageSettings] = useState({ boxHeight: null, strokeWidth: null, marginBetweenBlocks: null, stageOffset: null, }) - const [isShortView, setIsShortView] = useState(true) - const [isLoading, setIsLoading] = useState(true) const updateStageSettings = (boxHeight, strokeWidth) => { setStageSettings({ @@ -33,9 +33,6 @@ export default function DialogCacheInfo({ hash }) { }) } - const { boxHeight, strokeWidth, marginBetweenBlocks, stageOffset } = stageSettings - let activeId = null - useEffect(() => { // initializing stageSettings updateStageSettings(24, 4) @@ -92,6 +89,8 @@ export default function DialogCacheInfo({ hash }) { setIsLoading(false) }, [cache]) + const { boxHeight, strokeWidth, marginBetweenBlocks, stageOffset } = stageSettings + const preloadPiecesAmount = Math.round(cache.Capacity / cache.PiecesLength - 1) const blockSizeWithMargin = boxHeight + strokeWidth + marginBetweenBlocks const piecesInOneRow = Math.floor((dimensions.width * 0.9) / blockSizeWithMargin) @@ -100,6 +99,7 @@ export default function DialogCacheInfo({ hash }) { ? preloadPiecesAmount - 1 : preloadPiecesAmount + piecesInOneRow - (preloadPiecesAmount % piecesInOneRow) - 1 const amountOfRows = Math.ceil((isShortView ? amountOfBlocksToRenderInShortView : pMap.length) / piecesInOneRow) + let activeId = null return ( setDimensions(contentRect.bounds)}> From 6b5a572918ec0a74883411f3118dafb198b26c09 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Thu, 27 May 2021 17:21:39 +0300 Subject: [PATCH 08/40] added full width dialog for cache and info --- web/package.json | 1 + web/src/components/DialogCacheInfo/index.jsx | 86 +----- .../customHooks.jsx | 74 +++++ .../DialogTorrentDetailsContent/index.jsx | 275 ++++++++++++++++++ web/src/components/Torrent/index.jsx | 25 +- web/yarn.lock | 9 +- 6 files changed, 382 insertions(+), 88 deletions(-) create mode 100644 web/src/components/DialogTorrentDetailsContent/customHooks.jsx create mode 100644 web/src/components/DialogTorrentDetailsContent/index.jsx diff --git a/web/package.json b/web/package.json index 5c00cee..99ee976 100644 --- a/web/package.json +++ b/web/package.json @@ -5,6 +5,7 @@ "dependencies": { "@material-ui/core": "^4.11.4", "@material-ui/icons": "^4.11.2", + "axios": "^0.21.1", "clsx": "^1.1.1", "fontsource-roboto": "^4.0.0", "konva": "^8.0.1", diff --git a/web/src/components/DialogCacheInfo/index.jsx b/web/src/components/DialogCacheInfo/index.jsx index 79c6a29..1e6b5b3 100644 --- a/web/src/components/DialogCacheInfo/index.jsx +++ b/web/src/components/DialogCacheInfo/index.jsx @@ -1,19 +1,15 @@ -import { useEffect, useRef, useState } from 'react' +import { useEffect, useState } from 'react' import Typography from '@material-ui/core/Typography' import DialogTitle from '@material-ui/core/DialogTitle' import DialogContent from '@material-ui/core/DialogContent' import { getPeerString, humanizeSize } from 'utils/Utils' -import { cacheHost } from 'utils/Hosts' import { Stage, Layer } from 'react-konva' import Measure from 'react-measure' +import { useUpdateCache, useCreateCacheMap } from 'components/DialogTorrentDetailsContent/customHooks' import SingleBlock from './SingleBlock' export default function DialogCacheInfo({ hash }) { - const [cache, setCache] = useState({}) - const [pMap, setPMap] = useState([]) - const timerID = useRef(null) - const componentIsMounted = useRef(true) const [dimensions, setDimensions] = useState({ width: -1, height: -1 }) const [isShortView, setIsShortView] = useState(true) const [isLoading, setIsLoading] = useState(true) @@ -24,6 +20,9 @@ export default function DialogCacheInfo({ hash }) { stageOffset: null, }) + const cache = useUpdateCache(hash) + const cacheMap = useCreateCacheMap(cache, () => setIsLoading(false)) + const updateStageSettings = (boxHeight, strokeWidth) => { setStageSettings({ boxHeight, @@ -36,59 +35,8 @@ export default function DialogCacheInfo({ hash }) { useEffect(() => { // initializing stageSettings updateStageSettings(24, 4) - - return () => { - // this function is required to notify "getCache" when NOT to make state update - componentIsMounted.current = false - } }, []) - useEffect(() => { - if (hash) { - timerID.current = setInterval(() => { - getCache(hash, value => { - // this is required to avoid memory leak - if (componentIsMounted.current) setCache(value) - }) - }, 100) - } else clearInterval(timerID.current) - - return () => { - clearInterval(timerID.current) - } - }, [hash]) - - useEffect(() => { - if (!cache.PiecesCount || !cache.Pieces) return - - const { Pieces, PiecesCount, Readers } = cache - - const map = [] - - for (let i = 0; i < PiecesCount; i++) { - const newPiece = { id: i } - - const currentPiece = Pieces[i] - if (currentPiece) { - if (currentPiece.Completed && currentPiece.Size === currentPiece.Length) newPiece.isComplete = true - else { - newPiece.inProgress = true - newPiece.percentage = (currentPiece.Size / currentPiece.Length).toFixed(2) - } - } - - Readers.forEach(r => { - if (i === r.Reader) newPiece.isActive = true - if (i >= r.Start && i <= r.End) newPiece.isReaderRange = true - }) - - map.push(newPiece) - } - - setPMap(map) - setIsLoading(false) - }, [cache]) - const { boxHeight, strokeWidth, marginBetweenBlocks, stageOffset } = stageSettings const preloadPiecesAmount = Math.round(cache.Capacity / cache.PiecesLength - 1) @@ -98,7 +46,7 @@ export default function DialogCacheInfo({ hash }) { preloadPiecesAmount === piecesInOneRow ? preloadPiecesAmount - 1 : preloadPiecesAmount + piecesInOneRow - (preloadPiecesAmount % piecesInOneRow) - 1 - const amountOfRows = Math.ceil((isShortView ? amountOfBlocksToRenderInShortView : pMap.length) / piecesInOneRow) + const amountOfRows = Math.ceil((isShortView ? amountOfBlocksToRenderInShortView : cacheMap.length) / piecesInOneRow) let activeId = null return ( @@ -158,7 +106,7 @@ export default function DialogCacheInfo({ hash }) { height={stageOffset + blockSizeWithMargin * amountOfRows} > - {pMap.map(({ id, percentage, isComplete, inProgress, isActive, isReaderRange }) => { + {cacheMap.map(({ id, percentage, isComplete, inProgress, isActive, isReaderRange }) => { const currentRow = Math.floor((isShortView ? id - activeId : id) / piecesInOneRow) // -------- related only for short view ------- @@ -207,26 +155,6 @@ export default function DialogCacheInfo({ hash }) { ) } -const getCache = (hash, callback) => { - try { - fetch(cacheHost(), { - method: 'post', - body: JSON.stringify({ action: 'get', hash }), - headers: { - Accept: 'application/json, text/plain, */*', - 'Content-Type': 'application/json', - }, - }) - .then(res => res.json()) - .then(callback, error => { - callback({}) - console.error(error) - }) - } catch (e) { - console.error(e) - callback({}) - } -} /* { "Hash": "41e36c8de915d80db83fc134bee4e7e2d292657e", diff --git a/web/src/components/DialogTorrentDetailsContent/customHooks.jsx b/web/src/components/DialogTorrentDetailsContent/customHooks.jsx new file mode 100644 index 0000000..550ac0a --- /dev/null +++ b/web/src/components/DialogTorrentDetailsContent/customHooks.jsx @@ -0,0 +1,74 @@ +import { useEffect, useRef, useState } from 'react' +import { cacheHost } from 'utils/Hosts' +import axios from 'axios' + +export const useUpdateCache = hash => { + const [cache, setCache] = useState({}) + const componentIsMounted = useRef(true) + const timerID = useRef(null) + + useEffect( + () => () => { + // this function is required to notify "updateCache" when NOT to make state update + componentIsMounted.current = false + }, + [], + ) + + useEffect(() => { + if (hash) { + timerID.current = setInterval(() => { + const updateCache = newCache => componentIsMounted.current && setCache(newCache) + + axios + .post(cacheHost(), { action: 'get', hash }) + .then(({ data }) => updateCache(data)) + // empty cache if error + .catch(() => updateCache({})) + }, 100) + } else clearInterval(timerID.current) + + return () => { + clearInterval(timerID.current) + } + }, [hash]) + + return cache +} + +export const useCreateCacheMap = (cache, callback) => { + const [cacheMap, setCacheMap] = useState([]) + + useEffect(() => { + if (!cache.PiecesCount || !cache.Pieces) return + + const { Pieces, PiecesCount, Readers } = cache + + const map = [] + + for (let i = 0; i < PiecesCount; i++) { + const newPiece = { id: i } + + const currentPiece = Pieces[i] + if (currentPiece) { + if (currentPiece.Completed && currentPiece.Size === currentPiece.Length) newPiece.isComplete = true + else { + newPiece.inProgress = true + newPiece.percentage = (currentPiece.Size / currentPiece.Length).toFixed(2) + } + } + + Readers.forEach(r => { + if (i === r.Reader) newPiece.isActive = true + if (i >= r.Start && i <= r.End) newPiece.isReaderRange = true + }) + + map.push(newPiece) + } + + setCacheMap(map) + callback && callback() + }, [cache, callback]) + + return cacheMap +} diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx new file mode 100644 index 0000000..3de1eb1 --- /dev/null +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -0,0 +1,275 @@ +import Button from '@material-ui/core/Button' +import { AppBar, IconButton, makeStyles, Toolbar, Typography } from '@material-ui/core' +import CloseIcon from '@material-ui/icons/Close' +import styled, { css } from 'styled-components' +import { NoImageIcon } from 'icons' +import { getPeerString, humanizeSize } from 'utils/Utils' +import { viewedHost } from 'utils/Hosts' + +import { useUpdateCache, useCreateCacheMap } from './customHooks' + +const useStyles = makeStyles(theme => ({ + appBar: { position: 'relative' }, + title: { marginLeft: theme.spacing(2), flex: 1 }, +})) + +const DialogContent = styled.div` + display: grid; + grid-template-rows: min-content 200px 80px 70px; +` +const Poster = styled.div` + ${({ poster }) => css` + height: 400px; + border-radius: 5px; + overflow: hidden; + + ${poster + ? css` + img { + border-radius: 5px; + height: 100%; + } + ` + : css` + width: 300px; + display: grid; + place-items: center; + background: #74c39c; + + svg { + transform: scale(2.5) translateY(-3px); + } + `} + `} +` +const HeaderSection = styled.section` + padding: 40px; + display: grid; + grid-template-columns: min-content 1fr; + gap: 30px; +` + +const TorrentData = styled.div`` + +const CacheSection = styled.section` + padding: 40px; + background: lightgray; +` + +const ButtonSection = styled.section` + box-shadow: 0px 4px 4px -1px rgb(0 0 0 / 30%); + display: flex; + justify-content: space-evenly; + align-items: center; + text-transform: uppercase; +` + +const ButtonSectionButton = styled.div` + background: lightblue; + height: 100%; + flex: 1; + display: grid; + place-items: center; + cursor: pointer; + font-size: 15px; + + :not(:last-child) { + border-right: 1px solid blue; + } + + :hover { + background: red; + } +` + +const TorrentFilesSection = styled.div`` + +export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { + const classes = useStyles() + const { + poster, + hash, + title, + name, + download_speed: downloadSpeed, + upload_speed: uploadSpeed, + stat_string: statString, + torrent_size: torrentSize, + } = torrent + + const cache = useUpdateCache(hash) + const cacheMap = useCreateCacheMap(cache) + + const { Capacity, PiecesCount, PiecesLength } = cache + + return ( + <> + + + + + + + Torrent Details + + + + + + + + {poster ? poster : } + + +
hash: {hash}
+
title: {title}
+
name: {name}
+
peers: {getPeerString(torrent)}
+
loaded: {getPreload(torrent)}
+
download speed: {humanizeSize(downloadSpeed)}
+
upload speed: {humanizeSize(uploadSpeed)}
+
status: {statString}
+
torrent size: {humanizeSize(torrentSize)}
+ +
Capacity: {humanizeSize(Capacity)}
+
PiecesCount: {PiecesCount}
+
PiecesLength: {humanizeSize(PiecesLength)}
+
+
+ + + + + copy hash + + remove views + + drop torrent + + download playlist + + download playlist after last view + + + +
+ + ) +} + +function getPreload(torrent) { + if (torrent.preloaded_bytes > 0 && torrent.preload_size > 0 && torrent.preloaded_bytes < torrent.preload_size) { + const progress = ((torrent.preloaded_bytes * 100) / torrent.preload_size).toFixed(2) + return `${humanizeSize(torrent.preloaded_bytes)} / ${humanizeSize(torrent.preload_size)} ${progress}%` + } + + if (!torrent.preloaded_bytes) return humanizeSize(0) + + return humanizeSize(torrent.preloaded_bytes) +} + +function remViews(hash) { + try { + if (hash) + fetch(viewedHost(), { + method: 'post', + body: JSON.stringify({ action: 'rem', hash, file_index: -1 }), + headers: { + Accept: 'application/json, text/plain, */*', + 'Content-Type': 'application/json', + }, + }) + } catch (e) { + console.error(e) + } +} + +function getViewed(hash, callback) { + try { + fetch(viewedHost(), { + method: 'post', + body: JSON.stringify({ action: 'list', hash }), + headers: { + Accept: 'application/json, text/plain, */*', + 'Content-Type': 'application/json', + }, + }) + .then(res => res.json()) + .then(callback) + } catch (e) { + console.error(e) + } +} + +function getPlayableFile(torrent) { + if (!torrent || !torrent.file_stats) return null + return torrent.file_stats.filter(file => extPlayable.includes(getExt(file.path))) +} + +function getExt(filename) { + const ext = filename.split('.').pop() + if (ext === filename) return '' + return ext.toLowerCase() +} +const extPlayable = [ + // video + '3g2', + '3gp', + 'aaf', + 'asf', + 'avchd', + 'avi', + 'drc', + 'flv', + 'iso', + 'm2v', + 'm2ts', + 'm4p', + 'm4v', + 'mkv', + 'mng', + 'mov', + 'mp2', + 'mp4', + 'mpe', + 'mpeg', + 'mpg', + 'mpv', + 'mxf', + 'nsv', + 'ogg', + 'ogv', + 'ts', + 'qt', + 'rm', + 'rmvb', + 'roq', + 'svi', + 'vob', + 'webm', + 'wmv', + 'yuv', + // audio + 'aac', + 'aiff', + 'ape', + 'au', + 'flac', + 'gsm', + 'it', + 'm3u', + 'm4a', + 'mid', + 'mod', + 'mp3', + 'mpa', + 'pls', + 'ra', + 's3m', + 'sid', + 'wav', + 'wma', + 'xm', +] diff --git a/web/src/components/Torrent/index.jsx b/web/src/components/Torrent/index.jsx index cca4dd1..bf19069 100644 --- a/web/src/components/Torrent/index.jsx +++ b/web/src/components/Torrent/index.jsx @@ -1,18 +1,20 @@ /* eslint-disable camelcase */ import 'fontsource-roboto' -import { useEffect, useRef, useState } from 'react' -import Button from '@material-ui/core/Button' +import { forwardRef, useEffect, useRef, useState } from 'react' +import DialogActions from '@material-ui/core/DialogActions' +import DialogTorrentInfo from 'components/DialogTorrentInfo' +import DialogCacheInfo from 'components/DialogCacheInfo' import HeightIcon from '@material-ui/icons/Height' import CloseIcon from '@material-ui/icons/Close' import DeleteIcon from '@material-ui/icons/Delete' -import DialogActions from '@material-ui/core/DialogActions' -import Dialog from '@material-ui/core/Dialog' import DataUsageIcon from '@material-ui/icons/DataUsage' import { getPeerString, humanizeSize } from 'utils/Utils' import { torrentsHost } from 'utils/Hosts' import { NoImageIcon } from 'icons' -import DialogTorrentInfo from 'components/DialogTorrentInfo' -import DialogCacheInfo from 'components/DialogCacheInfo' +import DialogTorrentDetailsContent from 'components/DialogTorrentDetailsContent' +import Dialog from '@material-ui/core/Dialog' +import Slide from '@material-ui/core/Slide' +import { Button } from '@material-ui/core' import { StyledButton, @@ -25,6 +27,9 @@ import { TorrentCardDetails, } from './style' +// eslint-disable-next-line react/jsx-props-no-spreading +const Transition = forwardRef((props, ref) => ) + export default function Torrent({ torrent }) { const [open, setOpen] = useState(false) const [showCache, setShowCache] = useState(false) @@ -122,7 +127,11 @@ export default function Torrent({ torrent }) { - setOpen(false)} aria-labelledby='form-dialog-title' fullWidth maxWidth='lg'> + + setOpen(false)} torrent={torrentLocalComponentValue} /> + + + {/* {showCache ? ( ) : ( @@ -133,7 +142,7 @@ export default function Torrent({ torrent }) { OK
-
+ */} ) } diff --git a/web/yarn.lock b/web/yarn.lock index 162396c..ec2581b 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -2748,6 +2748,13 @@ axe-core@^4.0.2: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.2.1.tgz#2e50bcf10ee5b819014f6e342e41e45096239e34" integrity sha512-evY7DN8qSIbsW2H/TWQ1bX3sXN1d4MNb5Vb4n7BzPuCwRHdkZ1H2eNLuSh73EoQqkGKUtju2G2HCcjCfhvZIAA== +axios@^0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== + dependencies: + follow-redirects "^1.10.0" + axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" @@ -5727,7 +5734,7 @@ flush-write-stream@^1.0.0, flush-write-stream@^1.0.2: inherits "^2.0.3" readable-stream "^2.3.6" -follow-redirects@^1.0.0: +follow-redirects@^1.0.0, follow-redirects@^1.10.0: version "1.14.1" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43" integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg== From bd492b4bf8c56ba4f008ff83350406b948e61eac Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Thu, 27 May 2021 17:57:08 +0300 Subject: [PATCH 09/40] more description added --- web/package.json | 1 + .../DialogTorrentDetailsContent/index.jsx | 92 +++++++++++++------ web/yarn.lock | 20 ++++ 3 files changed, 83 insertions(+), 30 deletions(-) diff --git a/web/package.json b/web/package.json index 99ee976..7a7908b 100644 --- a/web/package.json +++ b/web/package.json @@ -11,6 +11,7 @@ "konva": "^8.0.1", "material-ui-image": "^3.3.2", "react": "^17.0.2", + "react-copy-to-clipboard": "^5.0.3", "react-dom": "^17.0.2", "react-konva": "^17.0.2-4", "react-measure": "^2.5.2", diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index 3de1eb1..fa9843e 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -5,6 +5,8 @@ import styled, { css } from 'styled-components' import { NoImageIcon } from 'icons' import { getPeerString, humanizeSize } from 'utils/Utils' import { viewedHost } from 'utils/Hosts' +import { CopyToClipboard } from 'react-copy-to-clipboard' +import { useState } from 'react' import { useUpdateCache, useCreateCacheMap } from './customHooks' @@ -49,7 +51,11 @@ const HeaderSection = styled.section` gap: 30px; ` -const TorrentData = styled.div`` +const TorrentData = styled.div` + > :not(:last-child) { + margin-bottom: 20px; + } +` const CacheSection = styled.section` padding: 40px; @@ -84,8 +90,20 @@ const ButtonSectionButton = styled.div` const TorrentFilesSection = styled.div`` +const TorrentName = styled.div` + font-size: 50px; + font-weight: 200; + line-height: 1; +` +const TorrentSubName = styled.div` + color: #7c7b7c; +` + +const shortenText = (text, count) => text.slice(0, count) + (text.length > count ? '...' : '') + export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { const classes = useStyles() + const [isLoading, setIsLoading] = useState(true) const { poster, hash, @@ -98,7 +116,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { } = torrent const cache = useUpdateCache(hash) - const cacheMap = useCreateCacheMap(cache) + const cacheMap = useCreateCacheMap(cache, () => setIsLoading(false)) const { Capacity, PiecesCount, PiecesLength } = cache @@ -118,43 +136,57 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { - - - {poster ? poster : } + {isLoading ? ( + 'loading' + ) : ( + + + {poster ? poster : } - -
hash: {hash}
-
title: {title}
-
name: {name}
-
peers: {getPeerString(torrent)}
-
loaded: {getPreload(torrent)}
-
download speed: {humanizeSize(downloadSpeed)}
-
upload speed: {humanizeSize(uploadSpeed)}
-
status: {statString}
-
torrent size: {humanizeSize(torrentSize)}
+ +
+ {name && name !== title ? ( + <> + {shortenText(name, 50)} + {shortenText(title, 160)} + + ) : ( + {shortenText(title, 50)} + )} +
-
Capacity: {humanizeSize(Capacity)}
-
PiecesCount: {PiecesCount}
-
PiecesLength: {humanizeSize(PiecesLength)}
-
-
+
peers: {getPeerString(torrent)}
+
loaded: {getPreload(torrent)}
+
download speed: {humanizeSize(downloadSpeed)}
+
upload speed: {humanizeSize(uploadSpeed)}
+
status: {statString}
+
torrent size: {humanizeSize(torrentSize)}
- +
Capacity: {humanizeSize(Capacity)}
+
PiecesCount: {PiecesCount}
+
PiecesLength: {humanizeSize(PiecesLength)}
+ +
- - copy hash + - remove views + + + copy hash + - drop torrent + remove views - download playlist + drop torrent - download playlist after last view - + download playlist - -
+ download playlist after last view + + + + + )} ) } diff --git a/web/yarn.lock b/web/yarn.lock index ec2581b..f21794e 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -4054,6 +4054,13 @@ copy-props@^2.0.1: each-props "^1.3.2" is-plain-object "^5.0.0" +copy-to-clipboard@^3: + version "3.3.1" + resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae" + integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw== + dependencies: + toggle-selection "^1.0.6" + core-js-compat@^3.6.2, core-js-compat@^3.9.0, core-js-compat@^3.9.1: version "3.13.0" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.13.0.tgz#a88f5fa81d8e9b15d7f98abc4447a4dfca2a358f" @@ -10232,6 +10239,14 @@ react-app-polyfill@^2.0.0: regenerator-runtime "^0.13.7" whatwg-fetch "^3.4.1" +react-copy-to-clipboard@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.3.tgz#2a0623b1115a1d8c84144e9434d3342b5af41ab4" + integrity sha512-9S3j+m+UxDZOM0Qb8mhnT/rMR0NGSrj9A/073yz2DSxPMYhmYFBMYIdI2X4o8AjOjyFsSNxDRnCX6s/gRxpriw== + dependencies: + copy-to-clipboard "^3" + prop-types "^15.5.8" + react-dev-utils@^11.0.3: version "11.0.4" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-11.0.4.tgz#a7ccb60257a1ca2e0efe7a83e38e6700d17aa37a" @@ -12015,6 +12030,11 @@ to-through@^2.0.0: dependencies: through2 "^2.0.3" +toggle-selection@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" + integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI= + toidentifier@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" From 981f668da50616f5a6f0bfdc877bcb15ec7ead0a Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Thu, 27 May 2021 18:47:39 +0300 Subject: [PATCH 10/40] fixed memory leak --- .../components/DialogTorrentDetailsContent/customHooks.jsx | 5 ++--- web/src/components/DialogTorrentDetailsContent/index.jsx | 6 ++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/web/src/components/DialogTorrentDetailsContent/customHooks.jsx b/web/src/components/DialogTorrentDetailsContent/customHooks.jsx index 550ac0a..aaaeead 100644 --- a/web/src/components/DialogTorrentDetailsContent/customHooks.jsx +++ b/web/src/components/DialogTorrentDetailsContent/customHooks.jsx @@ -36,7 +36,7 @@ export const useUpdateCache = hash => { return cache } -export const useCreateCacheMap = (cache, callback) => { +export const useCreateCacheMap = cache => { const [cacheMap, setCacheMap] = useState([]) useEffect(() => { @@ -67,8 +67,7 @@ export const useCreateCacheMap = (cache, callback) => { } setCacheMap(map) - callback && callback() - }, [cache, callback]) + }, [cache]) return cacheMap } diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index fa9843e..b303e9f 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -6,7 +6,7 @@ import { NoImageIcon } from 'icons' import { getPeerString, humanizeSize } from 'utils/Utils' import { viewedHost } from 'utils/Hosts' import { CopyToClipboard } from 'react-copy-to-clipboard' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { useUpdateCache, useCreateCacheMap } from './customHooks' @@ -116,7 +116,9 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { } = torrent const cache = useUpdateCache(hash) - const cacheMap = useCreateCacheMap(cache, () => setIsLoading(false)) + const cacheMap = useCreateCacheMap(cache) + + useEffect(() => setIsLoading(false), [cacheMap]) const { Capacity, PiecesCount, PiecesLength } = cache From 461fc9014bcaa3702cb9bf9160739b1e653c0c16 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Fri, 28 May 2021 10:53:37 +0300 Subject: [PATCH 11/40] added different page for detailed cache view --- .../DialogHeader.jsx | 33 ++++++ .../SingleBlock.jsx | 54 +++++++++ .../TorrentCache.jsx | 100 +++++++++++++++++ .../DialogTorrentDetailsContent/index.jsx | 103 ++++++++++++------ 4 files changed, 257 insertions(+), 33 deletions(-) create mode 100644 web/src/components/DialogTorrentDetailsContent/DialogHeader.jsx create mode 100644 web/src/components/DialogTorrentDetailsContent/SingleBlock.jsx create mode 100644 web/src/components/DialogTorrentDetailsContent/TorrentCache.jsx diff --git a/web/src/components/DialogTorrentDetailsContent/DialogHeader.jsx b/web/src/components/DialogTorrentDetailsContent/DialogHeader.jsx new file mode 100644 index 0000000..520ed60 --- /dev/null +++ b/web/src/components/DialogTorrentDetailsContent/DialogHeader.jsx @@ -0,0 +1,33 @@ +import Button from '@material-ui/core/Button' +import { AppBar, IconButton, makeStyles, Toolbar, Typography } from '@material-ui/core' +import CloseIcon from '@material-ui/icons/Close' +import { ArrowBack } from '@material-ui/icons' + +const useStyles = makeStyles(theme => ({ + appBar: { position: 'relative' }, + title: { marginLeft: theme.spacing(2), flex: 1 }, +})) + +export default function DialogHeader({ title, onClose, onBack }) { + const classes = useStyles() + + return ( + + + + {onBack ? : } + + + + {title} + + + {onBack && ( + + )} + + + ) +} diff --git a/web/src/components/DialogTorrentDetailsContent/SingleBlock.jsx b/web/src/components/DialogTorrentDetailsContent/SingleBlock.jsx new file mode 100644 index 0000000..07c75b4 --- /dev/null +++ b/web/src/components/DialogTorrentDetailsContent/SingleBlock.jsx @@ -0,0 +1,54 @@ +import { Rect } from 'react-konva' + +export default function SingleBlock({ + x, + y, + percentage, + isActive = false, + inProgress = false, + isReaderRange = false, + isComplete = false, + boxHeight, + strokeWidth, +}) { + const strokeColor = isActive + ? '#000' + : isComplete + ? '#3fb57a' + : inProgress + ? '#00d0d0' + : isReaderRange + ? '#9a9aff' + : '#eef2f4' + const backgroundColor = inProgress ? '#00d0d0' : '#eef2f4' + const percentageProgressColor = '#3fb57a' + const processCompletedColor = '#3fb57a' + + return ( + + ) +} diff --git a/web/src/components/DialogTorrentDetailsContent/TorrentCache.jsx b/web/src/components/DialogTorrentDetailsContent/TorrentCache.jsx new file mode 100644 index 0000000..d46d259 --- /dev/null +++ b/web/src/components/DialogTorrentDetailsContent/TorrentCache.jsx @@ -0,0 +1,100 @@ +import { useEffect, useState } from 'react' +import DialogContent from '@material-ui/core/DialogContent' +import { Stage, Layer } from 'react-konva' +import Measure from 'react-measure' + +import SingleBlock from './SingleBlock' + +export default function TorrentCache({ cache, cacheMap, isMini }) { + const [dimensions, setDimensions] = useState({ width: -1, height: -1 }) + const [stageSettings, setStageSettings] = useState({ + boxHeight: null, + strokeWidth: null, + marginBetweenBlocks: null, + stageOffset: null, + }) + + const updateStageSettings = (boxHeight, strokeWidth) => { + setStageSettings({ + boxHeight, + strokeWidth, + marginBetweenBlocks: strokeWidth, + stageOffset: strokeWidth * 2, + }) + } + + useEffect(() => { + // initializing stageSettings + isMini ? updateStageSettings(24, 4) : updateStageSettings(12, 2) + }, [isMini]) + + const { boxHeight, strokeWidth, marginBetweenBlocks, stageOffset } = stageSettings + const preloadPiecesAmount = Math.round(cache.Capacity / cache.PiecesLength - 1) + const blockSizeWithMargin = boxHeight + strokeWidth + marginBetweenBlocks + const piecesInOneRow = Math.floor((dimensions.width * 0.9) / blockSizeWithMargin) + const amountOfBlocksToRenderInShortView = + preloadPiecesAmount === piecesInOneRow + ? preloadPiecesAmount - 1 + : preloadPiecesAmount + piecesInOneRow - (preloadPiecesAmount % piecesInOneRow) - 1 + const amountOfRows = Math.ceil((isMini ? amountOfBlocksToRenderInShortView : cacheMap.length) / piecesInOneRow) + let activeId = null + + return ( + setDimensions(contentRect.bounds)}> + {({ measureRef }) => ( +
+ + + + {cacheMap.map(({ id, percentage, isComplete, inProgress, isActive, isReaderRange }) => { + const currentRow = Math.floor((isMini ? id - activeId : id) / piecesInOneRow) + + // -------- related only for short view ------- + if (isActive) activeId = id + const shouldBeRendered = + isActive || (id - activeId <= amountOfBlocksToRenderInShortView && id - activeId >= 0) + // -------------------------------------------- + + return isMini ? ( + shouldBeRendered && ( + + ) + ) : ( + + ) + })} + + + +
+ )} +
+ ) +} diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index b303e9f..c0ef80a 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -1,23 +1,18 @@ -import Button from '@material-ui/core/Button' -import { AppBar, IconButton, makeStyles, Toolbar, Typography } from '@material-ui/core' -import CloseIcon from '@material-ui/icons/Close' import styled, { css } from 'styled-components' import { NoImageIcon } from 'icons' import { getPeerString, humanizeSize } from 'utils/Utils' import { viewedHost } from 'utils/Hosts' import { CopyToClipboard } from 'react-copy-to-clipboard' import { useEffect, useState } from 'react' +import { Button } from '@material-ui/core' import { useUpdateCache, useCreateCacheMap } from './customHooks' +import DialogHeader from './DialogHeader' +import TorrentCache from './TorrentCache' -const useStyles = makeStyles(theme => ({ - appBar: { position: 'relative' }, - title: { marginLeft: theme.spacing(2), flex: 1 }, -})) - -const DialogContent = styled.div` +const DialogContentGrid = styled.div` display: grid; - grid-template-rows: min-content 200px 80px 70px; + grid-template-rows: min-content min-content 80px min-content; ` const Poster = styled.div` ${({ poster }) => css` @@ -44,11 +39,12 @@ const Poster = styled.div` `} `} ` -const HeaderSection = styled.section` +const TorrentMainSection = styled.section` padding: 40px; display: grid; grid-template-columns: min-content 1fr; gap: 30px; + background: lightgray; ` const TorrentData = styled.div` @@ -59,7 +55,8 @@ const TorrentData = styled.div` const CacheSection = styled.section` padding: 40px; - background: lightgray; + display: flex; + flex-direction: column; ` const ButtonSection = styled.section` @@ -86,6 +83,16 @@ const ButtonSectionButton = styled.div` :hover { background: red; } + + .hash-group { + display: grid; + place-items: center; + } + + .hash-text { + font-size: 10px; + color: #7c7b7c; + } ` const TorrentFilesSection = styled.div`` @@ -99,11 +106,22 @@ const TorrentSubName = styled.div` color: #7c7b7c; ` +const SectionTitle = styled.div` + font-size: 35px; + font-weight: 200; + line-height: 1; + margin-bottom: 20px; +` + +const DetailedTorrentCacheViewWrapper = styled.div` + padding-top: 50px; +` + const shortenText = (text, count) => text.slice(0, count) + (text.length > count ? '...' : '') export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { - const classes = useStyles() const [isLoading, setIsLoading] = useState(true) + const [isDetailedCacheView, setIsDetailedCacheView] = useState(false) const { poster, hash, @@ -118,31 +136,31 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { const cache = useUpdateCache(hash) const cacheMap = useCreateCacheMap(cache) - useEffect(() => setIsLoading(false), [cacheMap]) + useEffect(() => { + const torrentLoaded = torrent.stat_string !== 'Torrent in db' && torrent.stat_string !== 'Torrent getting info' + torrentLoaded && isLoading && setIsLoading(false) + }, [torrent, isLoading]) const { Capacity, PiecesCount, PiecesLength } = cache return ( <> - - - - - - - Torrent Details - - - - + setIsDetailedCacheView(false) })} + /> {isLoading ? ( 'loading' + ) : isDetailedCacheView ? ( + + + ) : ( - - + + {poster ? poster : } @@ -168,13 +186,32 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
PiecesCount: {PiecesCount}
PiecesLength: {humanizeSize(PiecesLength)}
-
+ - + + Cache + + + + + - copy hash + +
+
copy hash
+
{hash}
+
+
remove views @@ -187,7 +224,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
-
+ )} ) From 8de8139f87a1093d54722ec7f983be04a9c436d0 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Fri, 28 May 2021 17:33:21 +0300 Subject: [PATCH 12/40] added few sections --- .../StatisticsField.jsx | 14 + .../customHooks.jsx | 11 +- .../DialogTorrentDetailsContent/index.jsx | 431 ++++++++---------- .../DialogTorrentDetailsContent/style.js | 185 ++++++++ web/src/components/Settings.jsx | 3 - 5 files changed, 402 insertions(+), 242 deletions(-) create mode 100644 web/src/components/DialogTorrentDetailsContent/StatisticsField.jsx create mode 100644 web/src/components/DialogTorrentDetailsContent/style.js diff --git a/web/src/components/DialogTorrentDetailsContent/StatisticsField.jsx b/web/src/components/DialogTorrentDetailsContent/StatisticsField.jsx new file mode 100644 index 0000000..7ddf8fa --- /dev/null +++ b/web/src/components/DialogTorrentDetailsContent/StatisticsField.jsx @@ -0,0 +1,14 @@ +import { StatisticsFieldWrapper, StatisticsFieldIcon, StatisticsFieldValue, StatisticsFieldTitle } from './style' + +export default function StatisticsField({ icon: Icon, title, value, iconBg, valueBg }) { + return ( + + {title} + + + + + {value} + + ) +} diff --git a/web/src/components/DialogTorrentDetailsContent/customHooks.jsx b/web/src/components/DialogTorrentDetailsContent/customHooks.jsx index aaaeead..41c0008 100644 --- a/web/src/components/DialogTorrentDetailsContent/customHooks.jsx +++ b/web/src/components/DialogTorrentDetailsContent/customHooks.jsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from 'react' -import { cacheHost } from 'utils/Hosts' +import { cacheHost, settingsHost } from 'utils/Hosts' import axios from 'axios' export const useUpdateCache = hash => { @@ -71,3 +71,12 @@ export const useCreateCacheMap = cache => { return cacheMap } + +export const useGetSettings = cache => { + const [settings, setSettings] = useState() + useEffect(() => { + axios.post(settingsHost(), { action: 'get' }).then(({ data }) => setSettings(data)) + }, [cache]) + + return settings +} diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index c0ef80a..b7c2c91 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -1,121 +1,34 @@ import styled, { css } from 'styled-components' import { NoImageIcon } from 'icons' import { getPeerString, humanizeSize } from 'utils/Utils' -import { viewedHost } from 'utils/Hosts' +// import { viewedHost } from 'utils/Hosts' import { CopyToClipboard } from 'react-copy-to-clipboard' import { useEffect, useState } from 'react' import { Button } from '@material-ui/core' +import { ArrowDownward, ArrowUpward, SwapVerticalCircle, ViewAgenda } from '@material-ui/icons' +import axios from 'axios' +import { torrentsHost } from 'utils/Hosts' -import { useUpdateCache, useCreateCacheMap } from './customHooks' +import { useUpdateCache, useCreateCacheMap, useGetSettings } from './customHooks' import DialogHeader from './DialogHeader' import TorrentCache from './TorrentCache' - -const DialogContentGrid = styled.div` - display: grid; - grid-template-rows: min-content min-content 80px min-content; -` -const Poster = styled.div` - ${({ poster }) => css` - height: 400px; - border-radius: 5px; - overflow: hidden; - - ${poster - ? css` - img { - border-radius: 5px; - height: 100%; - } - ` - : css` - width: 300px; - display: grid; - place-items: center; - background: #74c39c; - - svg { - transform: scale(2.5) translateY(-3px); - } - `} - `} -` -const TorrentMainSection = styled.section` - padding: 40px; - display: grid; - grid-template-columns: min-content 1fr; - gap: 30px; - background: lightgray; -` - -const TorrentData = styled.div` - > :not(:last-child) { - margin-bottom: 20px; - } -` - -const CacheSection = styled.section` - padding: 40px; - display: flex; - flex-direction: column; -` - -const ButtonSection = styled.section` - box-shadow: 0px 4px 4px -1px rgb(0 0 0 / 30%); - display: flex; - justify-content: space-evenly; - align-items: center; - text-transform: uppercase; -` - -const ButtonSectionButton = styled.div` - background: lightblue; - height: 100%; - flex: 1; - display: grid; - place-items: center; - cursor: pointer; - font-size: 15px; - - :not(:last-child) { - border-right: 1px solid blue; - } - - :hover { - background: red; - } - - .hash-group { - display: grid; - place-items: center; - } - - .hash-text { - font-size: 10px; - color: #7c7b7c; - } -` - -const TorrentFilesSection = styled.div`` - -const TorrentName = styled.div` - font-size: 50px; - font-weight: 200; - line-height: 1; -` -const TorrentSubName = styled.div` - color: #7c7b7c; -` - -const SectionTitle = styled.div` - font-size: 35px; - font-weight: 200; - line-height: 1; - margin-bottom: 20px; -` - -const DetailedTorrentCacheViewWrapper = styled.div` - padding-top: 50px; -` +import { + DetailedTorrentCacheViewWrapper, + DialogContentGrid, + TorrentMainSection, + Poster, + TorrentData, + SectionTitle, + SectionSubName, + StatisticsWrapper, + ButtonSection, + LoadingProgress, + SectionHeader, + CacheSection, + ButtonSectionButton, + TorrentFilesSection, +} from './style' +import StatisticsField from './StatisticsField' const shortenText = (text, count) => text.slice(0, count) + (text.length > count ? '...' : '') @@ -135,13 +48,22 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { const cache = useUpdateCache(hash) const cacheMap = useCreateCacheMap(cache) + const settings = useGetSettings(cache) + + const dropTorrent = hash => { + axios.post(torrentsHost(), { action: 'drop', hash }).then(() => console.log('torrent dropped')) + } + + const { Capacity, PiecesCount, PiecesLength, Filled } = cache useEffect(() => { - const torrentLoaded = torrent.stat_string !== 'Torrent in db' && torrent.stat_string !== 'Torrent getting info' - torrentLoaded && isLoading && setIsLoading(false) - }, [torrent, isLoading]) + const cacheIsLoading = !Object.entries(cache).length - const { Capacity, PiecesCount, PiecesLength } = cache + if (cacheIsLoading && !isLoading) setIsLoading(true) + if (!cacheIsLoading && isLoading) setIsLoading(false) + }, [cache, isLoading]) + + const bufferSize = settings?.PreloadBuffer ? Capacity : 33554432 // Default is 32mb if PreloadBuffer is false return ( <> @@ -156,6 +78,9 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { 'loading' ) : isDetailedCacheView ? ( +
PiecesCount: {PiecesCount}
+
PiecesLength: {humanizeSize(PiecesLength)}
+
status: {statString}
) : ( @@ -167,34 +92,62 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
{name && name !== title ? ( <> - {shortenText(name, 50)} - {shortenText(title, 160)} + {shortenText(name, 50)} + {shortenText(title, 160)} ) : ( - {shortenText(title, 50)} + {shortenText(title, 50)} )}
-
peers: {getPeerString(torrent)}
-
loaded: {getPreload(torrent)}
-
download speed: {humanizeSize(downloadSpeed)}
-
upload speed: {humanizeSize(uploadSpeed)}
-
status: {statString}
-
torrent size: {humanizeSize(torrentSize)}
+ + -
Capacity: {humanizeSize(Capacity)}
-
PiecesCount: {PiecesCount}
-
PiecesLength: {humanizeSize(PiecesLength)}
+ + + + + +
- Cache + + Buffer + {!settings?.PreloadBuffer && ( + Enable "Preload Buffer" in settings to change buffer size + )} + + - + + + + ))}
)} @@ -184,117 +237,68 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { ) } -// function getPreload(torrent) { -// if (torrent.preloaded_bytes > 0 && torrent.preload_size > 0 && torrent.preloaded_bytes < torrent.preload_size) { -// const progress = ((torrent.preloaded_bytes * 100) / torrent.preload_size).toFixed(2) -// return `${humanizeSize(torrent.preloaded_bytes)} / ${humanizeSize(torrent.preload_size)} ${progress}%` -// } - -// if (!torrent.preloaded_bytes) return humanizeSize(0) - -// return humanizeSize(torrent.preloaded_bytes) -// } - -// function remViews(hash) { -// try { -// if (hash) -// fetch(viewedHost(), { -// method: 'post', -// body: JSON.stringify({ action: 'rem', hash, file_index: -1 }), -// headers: { -// Accept: 'application/json, text/plain, */*', -// 'Content-Type': 'application/json', -// }, -// }) -// } catch (e) { -// console.error(e) -// } -// } - -// function getViewed(hash, callback) { -// try { -// fetch(viewedHost(), { -// method: 'post', -// body: JSON.stringify({ action: 'list', hash }), -// headers: { -// Accept: 'application/json, text/plain, */*', -// 'Content-Type': 'application/json', -// }, -// }) -// .then(res => res.json()) -// .then(callback) -// } catch (e) { -// console.error(e) -// } -// } - -// function getPlayableFile(torrent) { -// if (!torrent || !torrent.file_stats) return null -// return torrent.file_stats.filter(file => extPlayable.includes(getExt(file.path))) -// } - -// function getExt(filename) { -// const ext = filename.split('.').pop() -// if (ext === filename) return '' -// return ext.toLowerCase() -// } -// const extPlayable = [ -// // video -// '3g2', -// '3gp', -// 'aaf', -// 'asf', -// 'avchd', -// 'avi', -// 'drc', -// 'flv', -// 'iso', -// 'm2v', -// 'm2ts', -// 'm4p', -// 'm4v', -// 'mkv', -// 'mng', -// 'mov', -// 'mp2', -// 'mp4', -// 'mpe', -// 'mpeg', -// 'mpg', -// 'mpv', -// 'mxf', -// 'nsv', -// 'ogg', -// 'ogv', -// 'ts', -// 'qt', -// 'rm', -// 'rmvb', -// 'roq', -// 'svi', -// 'vob', -// 'webm', -// 'wmv', -// 'yuv', -// // audio -// 'aac', -// 'aiff', -// 'ape', -// 'au', -// 'flac', -// 'gsm', -// 'it', -// 'm3u', -// 'm4a', -// 'mid', -// 'mod', -// 'mp3', -// 'mpa', -// 'pls', -// 'ra', -// 's3m', -// 'sid', -// 'wav', -// 'wma', -// 'xm', -// ] +function getExt(filename) { + const ext = filename.split('.').pop() + if (ext === filename) return '' + return ext.toLowerCase() +} +const playableExtList = [ + // video + '3g2', + '3gp', + 'aaf', + 'asf', + 'avchd', + 'avi', + 'drc', + 'flv', + 'iso', + 'm2v', + 'm2ts', + 'm4p', + 'm4v', + 'mkv', + 'mng', + 'mov', + 'mp2', + 'mp4', + 'mpe', + 'mpeg', + 'mpg', + 'mpv', + 'mxf', + 'nsv', + 'ogg', + 'ogv', + 'ts', + 'qt', + 'rm', + 'rmvb', + 'roq', + 'svi', + 'vob', + 'webm', + 'wmv', + 'yuv', + // audio + 'aac', + 'aiff', + 'ape', + 'au', + 'flac', + 'gsm', + 'it', + 'm3u', + 'm4a', + 'mid', + 'mod', + 'mp3', + 'mpa', + 'pls', + 'ra', + 's3m', + 'sid', + 'wav', + 'wma', + 'xm', +] diff --git a/web/src/components/DialogTorrentDetailsContent/style.js b/web/src/components/DialogTorrentDetailsContent/style.js index 4716897..bdb94b0 100644 --- a/web/src/components/DialogTorrentDetailsContent/style.js +++ b/web/src/components/DialogTorrentDetailsContent/style.js @@ -166,14 +166,22 @@ export const StatisticsFieldValue = styled.div` `} ` -export const LoadingProgress = styled.div.attrs(({ value, fullAmount }) => ({ - percentage: Math.min(100, (value * 100) / fullAmount), -}))` - ${({ percentage, label }) => css` +export const LoadingProgress = styled.div.attrs(({ value, fullAmount }) => { + const percentage = (value * 100) / fullAmount + const percentageMinmax = Math.min(100, percentage) + // console.log(percentage) + const stylePercentage = percentageMinmax === 100 ? 100 : percentageMinmax % 100 + + return { + style: { + background: `linear-gradient(to right, #b5dec9 0%, #b5dec9 ${stylePercentage}%, #fff ${stylePercentage}%, #fff 100%)`, + }, + } +})` + ${({ label }) => css` border: 1px solid; padding: 10px 20px; border-radius: 5px; - background: linear-gradient(to right, #b5dec9 0%, #b5dec9 ${percentage}%, #fff ${percentage}%, #fff 100%); :before { content: '${label}'; diff --git a/web/src/torrentStates.js b/web/src/torrentStates.js new file mode 100644 index 0000000..a2cef45 --- /dev/null +++ b/web/src/torrentStates.js @@ -0,0 +1 @@ +export const [GETTING_INFO, PRELOAD, WORKING, CLOSED, IN_DB] = [1, 2, 3, 4, 5] From b636099fb74da404db86e9bbebe7d5ae947be1b5 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Fri, 28 May 2021 23:34:24 +0300 Subject: [PATCH 15/40] refactor --- web/src/components/DialogTorrentDetailsContent/style.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/web/src/components/DialogTorrentDetailsContent/style.js b/web/src/components/DialogTorrentDetailsContent/style.js index bdb94b0..c051feb 100644 --- a/web/src/components/DialogTorrentDetailsContent/style.js +++ b/web/src/components/DialogTorrentDetailsContent/style.js @@ -167,14 +167,12 @@ export const StatisticsFieldValue = styled.div` ` export const LoadingProgress = styled.div.attrs(({ value, fullAmount }) => { - const percentage = (value * 100) / fullAmount - const percentageMinmax = Math.min(100, percentage) - // console.log(percentage) - const stylePercentage = percentageMinmax === 100 ? 100 : percentageMinmax % 100 + const percentage = Math.min(100, (value * 100) / fullAmount) return { + // this block is here according to styled-components recomendation about fast changable components style: { - background: `linear-gradient(to right, #b5dec9 0%, #b5dec9 ${stylePercentage}%, #fff ${stylePercentage}%, #fff 100%)`, + background: `linear-gradient(to right, #b5dec9 0%, #b5dec9 ${percentage}%, #fff ${percentage}%, #fff 100%)`, }, } })` From 64c13ec299834c89b0a2b425ea2067626b0161e0 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Fri, 28 May 2021 23:41:10 +0300 Subject: [PATCH 16/40] refactor --- .../DialogTorrentDetailsContent/index.jsx | 27 +++++++++---------- .../DialogTorrentDetailsContent/style.js | 22 +++++++-------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index 808cac7..62475ff 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -22,7 +22,6 @@ import { DialogContentGrid, TorrentMainSection, Poster, - TorrentData, SectionTitle, SectionSubName, StatisticsWrapper, @@ -116,17 +115,15 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { {poster ? poster : } - -
- {name && name !== title ? ( - <> - {shortenText(name, 50)} - {shortenText(title, 160)} - - ) : ( - {shortenText(title, 50)} - )} -
+
+ {name && name !== title ? ( + <> + {shortenText(name, 50)} + {shortenText(title, 160)} + + ) : ( + {shortenText(title, 50)} + )} - +
- Buffer + Buffer {!settings?.PreloadBuffer && ( Enable "Preload Buffer" in settings to change buffer size )} @@ -209,7 +206,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { - Torrent Content + Torrent Content {!playableFileList?.length ? 'No playable files in this torrent' diff --git a/web/src/components/DialogTorrentDetailsContent/style.js b/web/src/components/DialogTorrentDetailsContent/style.js index c051feb..8304d33 100644 --- a/web/src/components/DialogTorrentDetailsContent/style.js +++ b/web/src/components/DialogTorrentDetailsContent/style.js @@ -43,12 +43,6 @@ export const TorrentMainSection = styled.section` background: linear-gradient(145deg, #e4f6ed, #b5dec9); ` -export const TorrentData = styled.div` - > :not(:last-child) { - margin-bottom: 20px; - } -` - export const CacheSection = styled.section` grid-area: cache; padding: 40px; @@ -100,14 +94,20 @@ export const TorrentFilesSection = styled.div` ` export const SectionSubName = styled.div` - color: #7c7b7c; + ${({ mb }) => css` + ${mb && `margin-bottom: ${mb}px`}; + color: #7c7b7c; + `} ` export const SectionTitle = styled.div` - font-size: 35px; - font-weight: 200; - line-height: 1; - word-break: break-word; + ${({ mb }) => css` + ${mb && `margin-bottom: ${mb}px`}; + font-size: 35px; + font-weight: 200; + line-height: 1; + word-break: break-word; + `} ` export const SectionHeader = styled.div` From 8c49647aa21cdc934300597c5d88276b4d9e6e7a Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Sat, 29 May 2021 00:57:56 +0300 Subject: [PATCH 17/40] divider added --- .../DialogTorrentDetailsContent/index.jsx | 54 +++++++++++++++++-- .../DialogTorrentDetailsContent/style.js | 30 +++++++++-- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index 62475ff..f53c521 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -20,7 +20,8 @@ import TorrentCache from './TorrentCache' import { DetailedTorrentCacheViewWrapper, DialogContentGrid, - TorrentMainSection, + MainSection, + MainSectionButtonGroup, Poster, SectionTitle, SectionSubName, @@ -31,6 +32,8 @@ import { CacheSection, ButtonSectionButton, TorrentFilesSection, + Divider, + SmallLabel, } from './style' import StatisticsField from './StatisticsField' @@ -112,7 +115,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { ) : ( - + {poster ? poster : }
@@ -158,8 +161,51 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { icon={ViewAgendaIcon} /> + + + + Download Playlist + + + + + + More + + + + + + + {/* + + + + + + */}
-
+ @@ -200,7 +246,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { dropTorrent()}>drop torrent - download playlist + download full playlist download playlist after last view diff --git a/web/src/components/DialogTorrentDetailsContent/style.js b/web/src/components/DialogTorrentDetailsContent/style.js index 8304d33..d5c17f4 100644 --- a/web/src/components/DialogTorrentDetailsContent/style.js +++ b/web/src/components/DialogTorrentDetailsContent/style.js @@ -14,6 +14,7 @@ export const Poster = styled.div` height: 400px; border-radius: 5px; overflow: hidden; + align-self: center; ${poster ? css` @@ -34,7 +35,7 @@ export const Poster = styled.div` `} `} ` -export const TorrentMainSection = styled.section` +export const MainSection = styled.section` grid-area: main; padding: 40px; display: grid; @@ -43,6 +44,16 @@ export const TorrentMainSection = styled.section` background: linear-gradient(145deg, #e4f6ed, #b5dec9); ` +export const MainSectionButtonGroup = styled.div` + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 20px; + + :not(:last-child) { + margin-bottom: 30px; + } +` + export const CacheSection = styled.section` grid-area: cache; padding: 40px; @@ -120,7 +131,7 @@ export const DetailedTorrentCacheViewWrapper = styled.div` export const StatisticsWrapper = styled.div` display: grid; - grid-template-columns: repeat(auto-fit, minmax(190px, min-content)); + grid-template-columns: repeat(auto-fit, minmax(210px, min-content)); gap: 20px; ` @@ -157,7 +168,7 @@ export const StatisticsFieldIcon = styled.div` export const StatisticsFieldValue = styled.div` ${({ bgColor }) => css` grid-area: value; - min-width: 150px; + min-width: 170px; padding: 0 20px; color: #fff; font-size: 25px; @@ -189,3 +200,16 @@ export const LoadingProgress = styled.div.attrs(({ value, fullAmount }) => { } `} ` + +export const Divider = styled.div` + height: 1px; + background-color: rgba(0, 0, 0, 0.12); + margin: 30px 0; +` + +export const SmallLabel = styled.div` + font-size: 20px; + margin-bottom: 10px; + font-weight: 300; + line-height: 1; +` From 35d2f0ddddde07cbed6ccb9ed1cfdfcc9e065455 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Mon, 31 May 2021 08:13:59 +0300 Subject: [PATCH 18/40] fixed scroll on small devices --- web/src/components/Torrent/style.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/web/src/components/Torrent/style.js b/web/src/components/Torrent/style.js index 221a6a0..c0c2b20 100644 --- a/web/src/components/Torrent/style.js +++ b/web/src/components/Torrent/style.js @@ -88,6 +88,7 @@ export const TorrentCardDescriptionLabel = styled.div` export const TorrentCardDescriptionContent = styled.div` margin-left: 5px; margin-bottom: 10px; + word-break: break-all; @media (max-width: 600px), (max-height: 500px) { font-size: 11px; @@ -103,7 +104,7 @@ export const TorrentCardDescriptionContent = styled.div` } @media (max-width: 410px) { - height: 100%; + font-size: 10px; } ` @@ -158,7 +159,7 @@ export const TorrentCardDetails = styled.div` grid-template-columns: repeat(3, 1fr); } - @media (max-width: 410px) { + /* @media (max-width: 410px) { display: none; - } + } */ ` From 618062d8195cd9bfd087c3bebe20e62cbeed5635 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Mon, 31 May 2021 08:15:18 +0300 Subject: [PATCH 19/40] refactor --- web/src/components/Torrent/style.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/web/src/components/Torrent/style.js b/web/src/components/Torrent/style.js index c0c2b20..a8c242c 100644 --- a/web/src/components/Torrent/style.js +++ b/web/src/components/Torrent/style.js @@ -158,8 +158,4 @@ export const TorrentCardDetails = styled.div` display: grid; grid-template-columns: repeat(3, 1fr); } - - /* @media (max-width: 410px) { - display: none; - } */ ` From 1f7554489c68299645a6f09a729b81619ec39ad1 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Mon, 31 May 2021 12:44:13 +0300 Subject: [PATCH 20/40] teble added for file list --- web/package.json | 1 + .../DialogTorrentDetailsContent/index.jsx | 217 +++++++++++++----- .../DialogTorrentDetailsContent/style.js | 116 ++++++---- web/yarn.lock | 5 + 4 files changed, 238 insertions(+), 101 deletions(-) diff --git a/web/package.json b/web/package.json index 7a7908b..282af49 100644 --- a/web/package.json +++ b/web/package.json @@ -10,6 +10,7 @@ "fontsource-roboto": "^4.0.0", "konva": "^8.0.1", "material-ui-image": "^3.3.2", + "parse-torrent-title": "^1.3.0", "react": "^17.0.2", "react-copy-to-clipboard": "^5.0.3", "react-dom": "^17.0.2", diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index f53c521..16648d3 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -3,6 +3,7 @@ import { getPeerString, humanizeSize } from 'utils/Utils' import { CopyToClipboard } from 'react-copy-to-clipboard' import { useEffect, useState } from 'react' import { Button, ButtonGroup, Typography } from '@material-ui/core' +import ptt from 'parse-torrent-title' import { ArrowDownward as ArrowDownwardIcon, ArrowUpward as ArrowUpwardIcon, @@ -26,17 +27,18 @@ import { SectionTitle, SectionSubName, StatisticsWrapper, - ButtonSection, LoadingProgress, SectionHeader, CacheSection, - ButtonSectionButton, TorrentFilesSection, Divider, SmallLabel, + Table, } from './style' import StatisticsField from './StatisticsField' +ptt.addHandler('part', /Part[. ]([0-9])/i, { type: 'integer' }) + const shortenText = (text, count) => text.slice(0, count) + (text.length > count ? '...' : '') export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { @@ -45,6 +47,11 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { const [viewedFileList, setViewedFileList] = useState() const [playableFileList, setPlayableFileList] = useState() + const isOnlyOnePlayableFile = playableFileList?.length === 1 + const latestViewedFileId = viewedFileList?.[viewedFileList?.length - 1] + const latestViewedFile = playableFileList?.find(({ id }) => id === latestViewedFileId)?.path + const latestViewedFileData = latestViewedFile && ptt.parse(latestViewedFile) + const { poster, hash, @@ -87,7 +94,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { // getting viewed file list axios.post(viewedHost(), { action: 'list', hash }).then(({ data }) => { if (data) { - const lst = data.map(itm => itm.file_index) + const lst = data.map(itm => itm.file_index).sort((a, b) => a - b) setViewedFileList(lst) } else setViewedFileList() }) @@ -148,43 +155,70 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { - Download Playlist + {!isOnlyOnePlayableFile && !!viewedFileList?.length && ( + <> + Download Playlist + + Latest file played: {latestViewedFileData.title}. + {latestViewedFileData.season && ( + <> + {' '} + Season: {latestViewedFileData.season}. Episode: {latestViewedFileData.episode}. + + )} + + + + + + + + )} + + Torrent State + - - - More + Info + - - - + {(isOnlyOnePlayableFile || !viewedFileList?.length) && ( + + )} + + + {/* @@ -232,47 +266,116 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { - - - -
-
copy hash
-
{hash}
-
-
-
- - removeTorrentViews()}>remove views - - dropTorrent()}>drop torrent - - download full playlist - - download playlist after last view -
- Torrent Content + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
viewednameseasonepisoderesolutionsizeactions
+ Jupiters Legacy311080p945,41 MB + + + +
+ Jupiters Legacy321080p712,47 MB + + + +
+ Jupiters Legacy331080p687,44 MB + + + +
+ {!playableFileList?.length ? 'No playable files in this torrent' - : playableFileList.map(({ id, path, length }) => ( - - + : playableFileList.map(({ id, path, length }) => { + { + /* console.log(ptt.parse(path)) */ + } + { + /* console.log({ title: ptt.parse(path).title }) + console.log({ resolution: ptt.parse(path).resolution }) + console.log({ episode: ptt.parse(path).episode }) + console.log({ season: ptt.parse(path).season }) */ + } - - - ))} + return ( + + + + + + ) + })}
)} diff --git a/web/src/components/DialogTorrentDetailsContent/style.js b/web/src/components/DialogTorrentDetailsContent/style.js index d5c17f4..0cd7a50 100644 --- a/web/src/components/DialogTorrentDetailsContent/style.js +++ b/web/src/components/DialogTorrentDetailsContent/style.js @@ -3,10 +3,9 @@ import styled, { css } from 'styled-components' export const DialogContentGrid = styled.div` display: grid; grid-template-columns: 70% 1fr; - grid-template-rows: min-content 80px min-content; + grid-template-rows: repeat(2, min-content); grid-template-areas: 'main cache' - 'buttons buttons' 'file-list file-list'; ` export const Poster = styled.div` @@ -60,48 +59,13 @@ export const CacheSection = styled.section` display: grid; align-content: start; grid-template-rows: min-content 1fr min-content; + background: #88cdaa; ` -export const ButtonSection = styled.section` - grid-area: buttons; - box-shadow: 0px 4px 4px -1px rgb(0 0 0 / 30%); - display: flex; - justify-content: space-evenly; - align-items: center; - text-transform: uppercase; -` - -export const ButtonSectionButton = styled.div` - background: lightblue; - height: 100%; - flex: 1; - display: grid; - place-items: center; - cursor: pointer; - font-size: 15px; - - :not(:last-child) { - border-right: 1px solid blue; - } - - :hover { - background: red; - } - - .hash-group { - display: grid; - place-items: center; - } - - .hash-text { - font-size: 10px; - color: #7c7b7c; - } -` - -export const TorrentFilesSection = styled.div` +export const TorrentFilesSection = styled.section` grid-area: file-list; padding: 40px; + box-shadow: inset 3px 25px 8px -25px rgba(0, 0, 0, 0.5); ` export const SectionSubName = styled.div` @@ -208,8 +172,72 @@ export const Divider = styled.div` ` export const SmallLabel = styled.div` - font-size: 20px; - margin-bottom: 10px; - font-weight: 300; - line-height: 1; + ${({ mb }) => css` + ${mb && `margin-bottom: ${mb}px`}; + font-size: 20px; + font-weight: 300; + line-height: 1; + `} +` + +export const Table = styled.table` + border-collapse: collapse; + margin: 25px 0; + font-size: 0.9em; + width: 100%; + border-radius: 5px 5px 0 0; + overflow: hidden; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); + + thead tr { + background: #009879; + color: #fff; + text-align: left; + text-transform: uppercase; + } + + th, + td { + padding: 12px 15px; + } + + tbody tr { + border-bottom: 1px solid #ddd; + + /* :nth-of-type(even) { + background: #f3f3f3; + } */ + + :last-of-type { + border-bottom: 2px solid #009879; + } + + &.viewed-file-row { + color: lightgray; + background: #f3f3f3; + } + } + + td { + &.viewed-file-indicator { + position: relative; + :before { + content: ''; + width: 10px; + height: 10px; + background: #15d5af; + border-radius: 50%; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + } + + &.button-cell { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 10px; + } + } ` diff --git a/web/yarn.lock b/web/yarn.lock index f21794e..a0bf6c7 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -9057,6 +9057,11 @@ parse-passwd@^1.0.0: resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= +parse-torrent-title@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/parse-torrent-title/-/parse-torrent-title-1.3.0.tgz#3dedea10277b17998b124a4fd67d9e190b0306b8" + integrity sha512-R5wya73/Ef0qUhb9177Ko8nRQyN1ziWD5DPnlrDrrgcchUnmIrG//cPENunvFYRZCLDZosXTKTo7TpQ2Pgbryg== + parse5@6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" From 695a7c53b6d426948da074e3519ab6e2373610b7 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Mon, 31 May 2021 13:38:46 +0300 Subject: [PATCH 21/40] table now has real data --- .../DialogTorrentDetailsContent/index.jsx | 178 ++++++------------ .../DialogTorrentDetailsContent/style.js | 1 - 2 files changed, 55 insertions(+), 124 deletions(-) diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index 16648d3..dea5b57 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -37,8 +37,6 @@ import { } from './style' import StatisticsField from './StatisticsField' -ptt.addHandler('part', /Part[. ]([0-9])/i, { type: 'integer' }) - const shortenText = (text, count) => text.slice(0, count) + (text.length > count ? '...' : '') export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { @@ -76,6 +74,10 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { const getFileLink = (path, id) => `${streamHost()}/${encodeURIComponent(path.split('\\').pop().split('/').pop())}?link=${hash}&index=${id}&play` + const fileHasEpisodeText = !!playableFileList?.find(({ path }) => ptt.parse(path).episode) + const fileHasSeasonText = !!playableFileList?.find(({ path }) => ptt.parse(path).season) + const fileHasResolutionText = !!playableFileList?.find(({ path }) => ptt.parse(path).resolution) + const { Capacity, PiecesCount, PiecesLength, Filled } = cache useEffect(() => { @@ -220,24 +222,6 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { - - {/* - - - - - - */}
@@ -269,113 +253,61 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { Torrent Content - - - - - - - - - - - - + {!playableFileList?.length ? ( + 'No playable files in this torrent' + ) : ( + <> +
viewednameseasonepisoderesolutionsizeactions
+ + + + + {fileHasSeasonText && } + {fileHasEpisodeText && } + {fileHasResolutionText && } + + + + - - - - - - - - - + + {playableFileList.map(({ id, path, length }) => { + const { title, resolution, episode, season } = ptt.parse(path) + const isViewed = viewedFileList?.includes(id) + const link = getFileLink(path, id) - - - - - - - - + return ( + + + {fileHasSeasonText && } + {fileHasEpisodeText && } + {fileHasResolutionText && } + + - - - - - - - - -
viewednameseasonepisoderesolutionsizeactions
- Jupiters Legacy311080p945,41 MB - - - -
- Jupiters Legacy321080p712,47 MB - - - -
+ {title}{season}{episode}{resolution}{humanizeSize(length)} + -
- Jupiters Legacy331080p687,44 MB - - - -
+ + + - {!playableFileList?.length - ? 'No playable files in this torrent' - : playableFileList.map(({ id, path, length }) => { - { - /* console.log(ptt.parse(path)) */ - } - { - /* console.log({ title: ptt.parse(path).title }) - console.log({ resolution: ptt.parse(path).resolution }) - console.log({ episode: ptt.parse(path).episode }) - console.log({ season: ptt.parse(path).season }) */ - } - - return ( - - - - - - ) - })} + + + + + + ) + })} + + + + )}
)} diff --git a/web/src/components/DialogTorrentDetailsContent/style.js b/web/src/components/DialogTorrentDetailsContent/style.js index 0cd7a50..60af08e 100644 --- a/web/src/components/DialogTorrentDetailsContent/style.js +++ b/web/src/components/DialogTorrentDetailsContent/style.js @@ -213,7 +213,6 @@ export const Table = styled.table` } &.viewed-file-row { - color: lightgray; background: #f3f3f3; } } From aab85cc366c36aac14dee4e257704762d880003d Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Mon, 31 May 2021 15:21:56 +0300 Subject: [PATCH 22/40] refactor --- web/.eslintrc | 3 +- web/src/components/Add/AddDialog.jsx | 2 +- .../DialogCacheInfo/SingleBlock.jsx | 1 - .../SingleBlock.jsx | 1 - .../customHooks.jsx | 4 +- .../DialogTorrentDetailsContent/index.jsx | 4 +- .../DialogTorrentDetailsContent/style.js | 4 - web/src/components/DialogTorrentInfo.jsx | 2 +- web/src/components/RemoveAll.jsx | 43 +++- web/src/components/Torrent/index.jsx | 208 ------------------ web/src/components/TorrentCard/index.jsx | 132 +++++++++++ .../{Torrent => TorrentCard}/style.js | 0 web/src/components/TorrentList.jsx | 47 ++-- 13 files changed, 191 insertions(+), 260 deletions(-) delete mode 100644 web/src/components/Torrent/index.jsx create mode 100644 web/src/components/TorrentCard/index.jsx rename web/src/components/{Torrent => TorrentCard}/style.js (100%) diff --git a/web/.eslintrc b/web/.eslintrc index 929c132..02f9504 100644 --- a/web/.eslintrc +++ b/web/.eslintrc @@ -36,6 +36,7 @@ "react/prop-types": 0, "react/react-in-jsx-scope": 0, "react/jsx-uses-react": 0, - "import/no-unresolved": 0 // used to allow relative paths from "src" folder + "import/no-unresolved": 0, // used to allow relative paths from "src" folder + "react/jsx-props-no-spreading": 0 } } \ No newline at end of file diff --git a/web/src/components/Add/AddDialog.jsx b/web/src/components/Add/AddDialog.jsx index dbbbe72..4c7225f 100644 --- a/web/src/components/Add/AddDialog.jsx +++ b/web/src/components/Add/AddDialog.jsx @@ -63,7 +63,7 @@ export default function AddDialog({ handleClose }) { Cancel - diff --git a/web/src/components/DialogCacheInfo/SingleBlock.jsx b/web/src/components/DialogCacheInfo/SingleBlock.jsx index 07c75b4..96268f6 100644 --- a/web/src/components/DialogCacheInfo/SingleBlock.jsx +++ b/web/src/components/DialogCacheInfo/SingleBlock.jsx @@ -34,7 +34,6 @@ export default function SingleBlock({ width={boxHeight} fillAfterStrokeEnabled preventDefault={false} - // eslint-disable-next-line react/jsx-props-no-spreading {...(isComplete ? { fill: processCompletedColor } : inProgress && { diff --git a/web/src/components/DialogTorrentDetailsContent/SingleBlock.jsx b/web/src/components/DialogTorrentDetailsContent/SingleBlock.jsx index 07c75b4..96268f6 100644 --- a/web/src/components/DialogTorrentDetailsContent/SingleBlock.jsx +++ b/web/src/components/DialogTorrentDetailsContent/SingleBlock.jsx @@ -34,7 +34,6 @@ export default function SingleBlock({ width={boxHeight} fillAfterStrokeEnabled preventDefault={false} - // eslint-disable-next-line react/jsx-props-no-spreading {...(isComplete ? { fill: processCompletedColor } : inProgress && { diff --git a/web/src/components/DialogTorrentDetailsContent/customHooks.jsx b/web/src/components/DialogTorrentDetailsContent/customHooks.jsx index 41c0008..5eeed67 100644 --- a/web/src/components/DialogTorrentDetailsContent/customHooks.jsx +++ b/web/src/components/DialogTorrentDetailsContent/customHooks.jsx @@ -28,9 +28,7 @@ export const useUpdateCache = hash => { }, 100) } else clearInterval(timerID.current) - return () => { - clearInterval(timerID.current) - } + return () => clearInterval(timerID.current) }, [hash]) return cache diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index dea5b57..b9c0852 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -2,14 +2,13 @@ import { NoImageIcon } from 'icons' import { getPeerString, humanizeSize } from 'utils/Utils' import { CopyToClipboard } from 'react-copy-to-clipboard' import { useEffect, useState } from 'react' -import { Button, ButtonGroup, Typography } from '@material-ui/core' +import { Button } from '@material-ui/core' import ptt from 'parse-torrent-title' import { ArrowDownward as ArrowDownwardIcon, ArrowUpward as ArrowUpwardIcon, SwapVerticalCircle as SwapVerticalCircleIcon, ViewAgenda as ViewAgendaIcon, - Cached as CachedIcon, } from '@material-ui/icons' import axios from 'axios' import { streamHost, torrentsHost, viewedHost } from 'utils/Hosts' @@ -109,7 +108,6 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { setIsDetailedCacheView(false) })} /> diff --git a/web/src/components/DialogTorrentDetailsContent/style.js b/web/src/components/DialogTorrentDetailsContent/style.js index 60af08e..7fc3be8 100644 --- a/web/src/components/DialogTorrentDetailsContent/style.js +++ b/web/src/components/DialogTorrentDetailsContent/style.js @@ -204,10 +204,6 @@ export const Table = styled.table` tbody tr { border-bottom: 1px solid #ddd; - /* :nth-of-type(even) { - background: #f3f3f3; - } */ - :last-of-type { border-bottom: 2px solid #009879; } diff --git a/web/src/components/DialogTorrentInfo.jsx b/web/src/components/DialogTorrentInfo.jsx index 5352519..a70ca79 100644 --- a/web/src/components/DialogTorrentInfo.jsx +++ b/web/src/components/DialogTorrentInfo.jsx @@ -126,7 +126,7 @@ export default function DialogTorrentInfo({ torrent, open }) { fetch(`${streamHost()}?link=${torrentLocalComponentValue.hash}&index=${file.id}&preload`) } > - + {/* */} Preload diff --git a/web/src/components/RemoveAll.jsx b/web/src/components/RemoveAll.jsx index fd81770..aac5c0f 100644 --- a/web/src/components/RemoveAll.jsx +++ b/web/src/components/RemoveAll.jsx @@ -1,7 +1,9 @@ +import { Button, Dialog, DialogActions, DialogTitle } from '@material-ui/core' import ListItem from '@material-ui/core/ListItem' import ListItemIcon from '@material-ui/core/ListItemIcon' import ListItemText from '@material-ui/core/ListItemText' import DeleteIcon from '@material-ui/icons/Delete' +import { useState } from 'react' import { torrentsHost } from 'utils/Hosts' const fnRemoveAll = () => { @@ -29,13 +31,40 @@ const fnRemoveAll = () => { } export default function RemoveAll() { - return ( - - - - + const [open, setOpen] = useState(false) + const closeDialog = () => setOpen(false) + const openDialog = () => setOpen(true) - - + return ( + <> + + + + + + + + + + Delete Torrent? + + + + + + + ) } diff --git a/web/src/components/Torrent/index.jsx b/web/src/components/Torrent/index.jsx deleted file mode 100644 index bf19069..0000000 --- a/web/src/components/Torrent/index.jsx +++ /dev/null @@ -1,208 +0,0 @@ -/* eslint-disable camelcase */ -import 'fontsource-roboto' -import { forwardRef, useEffect, useRef, useState } from 'react' -import DialogActions from '@material-ui/core/DialogActions' -import DialogTorrentInfo from 'components/DialogTorrentInfo' -import DialogCacheInfo from 'components/DialogCacheInfo' -import HeightIcon from '@material-ui/icons/Height' -import CloseIcon from '@material-ui/icons/Close' -import DeleteIcon from '@material-ui/icons/Delete' -import DataUsageIcon from '@material-ui/icons/DataUsage' -import { getPeerString, humanizeSize } from 'utils/Utils' -import { torrentsHost } from 'utils/Hosts' -import { NoImageIcon } from 'icons' -import DialogTorrentDetailsContent from 'components/DialogTorrentDetailsContent' -import Dialog from '@material-ui/core/Dialog' -import Slide from '@material-ui/core/Slide' -import { Button } from '@material-ui/core' - -import { - StyledButton, - TorrentCard, - TorrentCardButtons, - TorrentCardDescription, - TorrentCardDescriptionContent, - TorrentCardDescriptionLabel, - TorrentCardPoster, - TorrentCardDetails, -} from './style' - -// eslint-disable-next-line react/jsx-props-no-spreading -const Transition = forwardRef((props, ref) => ) - -export default function Torrent({ torrent }) { - const [open, setOpen] = useState(false) - const [showCache, setShowCache] = useState(false) - const [torrentLocalComponentValue, setTorrentLocalComponentValue] = useState(torrent) - const timerID = useRef(-1) - - useEffect(() => { - setTorrentLocalComponentValue(torrent) - }, [torrent]) - - useEffect(() => { - if (open) - timerID.current = setInterval(() => { - getTorrent(torrentLocalComponentValue.hash, (torr, error) => { - if (error) console.error(error) - else if (torr) setTorrentLocalComponentValue(torr) - }) - }, 1000) - else clearInterval(timerID.current) - - return () => { - clearInterval(timerID.current) - } - }, [torrentLocalComponentValue.hash, open]) - - const { title, name, poster, torrent_size, download_speed } = torrentLocalComponentValue - - return ( - <> - - - {poster ? poster : } - - - - { - setShowCache(true) - setOpen(true) - }} - > - - Cache - - - dropTorrent(torrentLocalComponentValue)}> - - Drop - - - { - setShowCache(false) - setOpen(true) - }} - > - - Details - - - deleteTorrent(torrentLocalComponentValue)}> - - Delete - - - - - - Name - {title || name} - - - - - Size - - {torrent_size > 0 && humanizeSize(torrent_size)} - - - - - Speed - - {download_speed > 0 ? humanizeSize(download_speed) : '---'} - - - - - Peers - - {getPeerString(torrentLocalComponentValue) || '---'} - - - - - - - - setOpen(false)} torrent={torrentLocalComponentValue} /> - - - {/* - {showCache ? ( - - ) : ( - - )} - - - - */} - - ) -} - -function getTorrent(hash, callback) { - try { - fetch(torrentsHost(), { - method: 'post', - body: JSON.stringify({ action: 'get', hash }), - headers: { - Accept: 'application/json, text/plain, */*', - 'Content-Type': 'application/json', - }, - }) - .then(res => res.json()) - .then( - json => { - callback(json, null) - }, - error => { - callback(null, error) - }, - ) - } catch (e) { - console.error(e) - } -} - -function deleteTorrent(torrent) { - try { - fetch(torrentsHost(), { - method: 'post', - body: JSON.stringify({ - action: 'rem', - hash: torrent.hash, - }), - headers: { - Accept: 'application/json, text/plain, */*', - 'Content-Type': 'application/json', - }, - }) - } catch (e) { - console.error(e) - } -} - -function dropTorrent(torrent) { - try { - fetch(torrentsHost(), { - method: 'post', - body: JSON.stringify({ - action: 'drop', - hash: torrent.hash, - }), - headers: { - Accept: 'application/json, text/plain, */*', - 'Content-Type': 'application/json', - }, - }) - } catch (e) { - console.error(e) - } -} diff --git a/web/src/components/TorrentCard/index.jsx b/web/src/components/TorrentCard/index.jsx new file mode 100644 index 0000000..30f7947 --- /dev/null +++ b/web/src/components/TorrentCard/index.jsx @@ -0,0 +1,132 @@ +/* eslint-disable camelcase */ +import 'fontsource-roboto' +import { forwardRef, useState } from 'react' +import HeightIcon from '@material-ui/icons/Height' +import CloseIcon from '@material-ui/icons/Close' +import DeleteIcon from '@material-ui/icons/Delete' +import { getPeerString, humanizeSize } from 'utils/Utils' +import { torrentsHost } from 'utils/Hosts' +import { NoImageIcon } from 'icons' +import DialogTorrentDetailsContent from 'components/DialogTorrentDetailsContent' +import Dialog from '@material-ui/core/Dialog' +import Slide from '@material-ui/core/Slide' +import { Button, DialogActions, DialogTitle, useMediaQuery, useTheme } from '@material-ui/core' +import axios from 'axios' + +import { + StyledButton, + TorrentCard, + TorrentCardButtons, + TorrentCardDescription, + TorrentCardDescriptionContent, + TorrentCardDescriptionLabel, + TorrentCardPoster, + TorrentCardDetails, +} from './style' + +const Transition = forwardRef((props, ref) => ) + +export default function Torrent({ torrent }) { + const [isDetailedInfoOpened, setIsDetailedInfoOpened] = useState(false) + const [isDeleteTorrentOpened, setIsDeleteTorrentOpened] = useState(false) + + const theme = useTheme() + const fullScreen = useMediaQuery(theme.breakpoints.down('md')) + + const openDetailedInfo = () => setIsDetailedInfoOpened(true) + const closeDetailedInfo = () => setIsDetailedInfoOpened(false) + const openDeleteTorrentAlert = () => setIsDeleteTorrentOpened(true) + const closeDeleteTorrentAlert = () => setIsDeleteTorrentOpened(false) + + const { title, name, poster, torrent_size, download_speed, hash } = torrent + + const dropTorrent = () => axios.post(torrentsHost(), { action: 'drop', hash }) + const deleteTorrent = () => axios.post(torrentsHost(), { action: 'rem', hash }) + + return ( + <> + + + {poster ? poster : } + + + + + + Details + + + dropTorrent(torrent)}> + + Drop + + + + + Delete + + + + + + Name + {title || name} + + + + + Size + + {torrent_size > 0 && humanizeSize(torrent_size)} + + + + + Speed + + {download_speed > 0 ? humanizeSize(download_speed) : '---'} + + + + + Peers + {getPeerString(torrent) || '---'} + + + + + + + + + + + Delete Torrent? + + + + + + + + ) +} diff --git a/web/src/components/Torrent/style.js b/web/src/components/TorrentCard/style.js similarity index 100% rename from web/src/components/Torrent/style.js rename to web/src/components/TorrentCard/style.js diff --git a/web/src/components/TorrentList.jsx b/web/src/components/TorrentList.jsx index 4fbaaa7..7b33671 100644 --- a/web/src/components/TorrentList.jsx +++ b/web/src/components/TorrentList.jsx @@ -2,7 +2,8 @@ import styled from 'styled-components' import { useEffect, useRef, useState } from 'react' import { Typography } from '@material-ui/core' import { torrentsHost } from 'utils/Hosts' -import Torrent from 'components/Torrent' +import TorrentCard from 'components/TorrentCard' +import axios from 'axios' const TorrentListWrapper = styled.div` display: grid; @@ -19,43 +20,29 @@ const TorrentListWrapper = styled.div` } ` -const getTorrentList = (callback, errorCallback) => { - fetch(torrentsHost(), { - method: 'post', - body: JSON.stringify({ action: 'list' }), - headers: { - Accept: 'application/json, text/plain, */*', - 'Content-Type': 'application/json', - }, - }) - .then(res => res.json()) - .then(callback) - .catch(() => errorCallback()) -} - export default function TorrentList() { const [torrents, setTorrents] = useState([]) const [offline, setOffline] = useState(true) const timerID = useRef(-1) - const updateTorrentList = torrs => { - setTorrents(torrs) - setOffline(false) - } - - const resetTorrentList = () => { - setTorrents([]) - setOffline(true) - } - useEffect(() => { timerID.current = setInterval(() => { - getTorrentList(updateTorrentList, resetTorrentList) + // getting torrent list + axios + .post(torrentsHost(), { action: 'list' }) + .then(({ data }) => { + // updating torrent list + setTorrents(data) + setOffline(false) + }) + .catch(() => { + // resetting torrent list + setTorrents([]) + setOffline(true) + }) }, 1000) - return () => { - clearInterval(timerID.current) - } + return () => clearInterval(timerID.current) }, []) return ( @@ -65,7 +52,7 @@ export default function TorrentList() { ) : !torrents.length ? ( No torrents added ) : ( - torrents && torrents.map(torrent => ) + torrents.map(torrent => ) )} ) From 92bc874e7f10d2a11a039de917482b992fbe1a4f Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Mon, 31 May 2021 15:33:12 +0300 Subject: [PATCH 23/40] refactor --- .../DialogTorrentDetailsContent/index.jsx | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index b9c0852..fc47754 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -11,7 +11,7 @@ import { ViewAgenda as ViewAgendaIcon, } from '@material-ui/icons' import axios from 'axios' -import { streamHost, torrentsHost, viewedHost } from 'utils/Hosts' +import { playlistTorrHost, streamHost, torrentsHost, viewedHost } from 'utils/Hosts' import { GETTING_INFO, IN_DB } from 'torrentStates' import { useUpdateCache, useCreateCacheMap, useGetSettings } from './customHooks' @@ -72,6 +72,8 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { const preloadBuffer = fileId => fetch(`${streamHost()}?link=${hash}&index=${fileId}&preload`) const getFileLink = (path, id) => `${streamHost()}/${encodeURIComponent(path.split('\\').pop().split('/').pop())}?link=${hash}&index=${id}&play` + const fullPlaylistLink = `${playlistTorrHost()}/${encodeURIComponent(name || title || 'file')}.m3u?link=${hash}&m3u` + const partialPlaylistLink = `${fullPlaylistLink}&fromlast` const fileHasEpisodeText = !!playableFileList?.find(({ path }) => ptt.parse(path).episode) const fileHasSeasonText = !!playableFileList?.find(({ path }) => ptt.parse(path).season) @@ -185,12 +187,17 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { - - + + + + + + + )} @@ -210,9 +217,11 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { {(isOnlyOnePlayableFile || !viewedFileList?.length) && ( - + + + )} - - - - - - {getPlayableFile(torrentLocalComponentValue) && - getPlayableFile(torrentLocalComponentValue).map(file => ( - - - - - ))} - -
-
- ) -} - -function remViews(hash) { - try { - if (hash) - fetch(viewedHost(), { - method: 'post', - body: JSON.stringify({ action: 'rem', hash, file_index: -1 }), - headers: { - Accept: 'application/json, text/plain, */*', - 'Content-Type': 'application/json', - }, - }) - } catch (e) { - console.error(e) - } -} - -function getViewed(hash, callback) { - try { - fetch(viewedHost(), { - method: 'post', - body: JSON.stringify({ action: 'list', hash }), - headers: { - Accept: 'application/json, text/plain, */*', - 'Content-Type': 'application/json', - }, - }) - .then(res => res.json()) - .then(callback) - } catch (e) { - console.error(e) - } -} - -function getPlayableFile(torrent) { - if (!torrent || !torrent.file_stats) return null - return torrent.file_stats.filter(file => extPlayable.includes(getExt(file.path))) -} - -function getExt(filename) { - const ext = filename.split('.').pop() - if (ext === filename) return '' - return ext.toLowerCase() -} - -function getPreload(torrent) { - if (torrent.preloaded_bytes > 0 && torrent.preload_size > 0 && torrent.preloaded_bytes < torrent.preload_size) { - const progress = ((torrent.preloaded_bytes * 100) / torrent.preload_size).toFixed(2) - return `${humanizeSize(torrent.preloaded_bytes)} / ${humanizeSize(torrent.preload_size)} ${progress}%` - } - - if (!torrent.preloaded_bytes) return humanizeSize(0) - - return humanizeSize(torrent.preloaded_bytes) -} - -const extPlayable = [ - // video - '3g2', - '3gp', - 'aaf', - 'asf', - 'avchd', - 'avi', - 'drc', - 'flv', - 'iso', - 'm2v', - 'm2ts', - 'm4p', - 'm4v', - 'mkv', - 'mng', - 'mov', - 'mp2', - 'mp4', - 'mpe', - 'mpeg', - 'mpg', - 'mpv', - 'mxf', - 'nsv', - 'ogg', - 'ogv', - 'ts', - 'qt', - 'rm', - 'rmvb', - 'roq', - 'svi', - 'vob', - 'webm', - 'wmv', - 'yuv', - // audio - 'aac', - 'aiff', - 'ape', - 'au', - 'flac', - 'gsm', - 'it', - 'm3u', - 'm4a', - 'mid', - 'mod', - 'mp3', - 'mpa', - 'pls', - 'ra', - 's3m', - 'sid', - 'wav', - 'wma', - 'xm', -] From 34ff7c5d53d6197d3ed58dd4240ebe35030b4b8b Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Mon, 31 May 2021 17:03:57 +0300 Subject: [PATCH 25/40] added more widgets --- .../DialogCacheInfo/SingleBlock.jsx | 53 ------ web/src/components/DialogCacheInfo/index.jsx | 175 ------------------ .../DialogTorrentDetailsContent/index.jsx | 63 ++++++- .../DialogTorrentDetailsContent/style.js | 6 +- web/src/components/TorrentCard/index.jsx | 7 +- 5 files changed, 66 insertions(+), 238 deletions(-) delete mode 100644 web/src/components/DialogCacheInfo/SingleBlock.jsx delete mode 100644 web/src/components/DialogCacheInfo/index.jsx diff --git a/web/src/components/DialogCacheInfo/SingleBlock.jsx b/web/src/components/DialogCacheInfo/SingleBlock.jsx deleted file mode 100644 index 96268f6..0000000 --- a/web/src/components/DialogCacheInfo/SingleBlock.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Rect } from 'react-konva' - -export default function SingleBlock({ - x, - y, - percentage, - isActive = false, - inProgress = false, - isReaderRange = false, - isComplete = false, - boxHeight, - strokeWidth, -}) { - const strokeColor = isActive - ? '#000' - : isComplete - ? '#3fb57a' - : inProgress - ? '#00d0d0' - : isReaderRange - ? '#9a9aff' - : '#eef2f4' - const backgroundColor = inProgress ? '#00d0d0' : '#eef2f4' - const percentageProgressColor = '#3fb57a' - const processCompletedColor = '#3fb57a' - - return ( - - ) -} diff --git a/web/src/components/DialogCacheInfo/index.jsx b/web/src/components/DialogCacheInfo/index.jsx deleted file mode 100644 index 1e6b5b3..0000000 --- a/web/src/components/DialogCacheInfo/index.jsx +++ /dev/null @@ -1,175 +0,0 @@ -import { useEffect, useState } from 'react' -import Typography from '@material-ui/core/Typography' -import DialogTitle from '@material-ui/core/DialogTitle' -import DialogContent from '@material-ui/core/DialogContent' -import { getPeerString, humanizeSize } from 'utils/Utils' -import { Stage, Layer } from 'react-konva' -import Measure from 'react-measure' -import { useUpdateCache, useCreateCacheMap } from 'components/DialogTorrentDetailsContent/customHooks' - -import SingleBlock from './SingleBlock' - -export default function DialogCacheInfo({ hash }) { - const [dimensions, setDimensions] = useState({ width: -1, height: -1 }) - const [isShortView, setIsShortView] = useState(true) - const [isLoading, setIsLoading] = useState(true) - const [stageSettings, setStageSettings] = useState({ - boxHeight: null, - strokeWidth: null, - marginBetweenBlocks: null, - stageOffset: null, - }) - - const cache = useUpdateCache(hash) - const cacheMap = useCreateCacheMap(cache, () => setIsLoading(false)) - - const updateStageSettings = (boxHeight, strokeWidth) => { - setStageSettings({ - boxHeight, - strokeWidth, - marginBetweenBlocks: strokeWidth, - stageOffset: strokeWidth * 2, - }) - } - - useEffect(() => { - // initializing stageSettings - updateStageSettings(24, 4) - }, []) - - const { boxHeight, strokeWidth, marginBetweenBlocks, stageOffset } = stageSettings - - const preloadPiecesAmount = Math.round(cache.Capacity / cache.PiecesLength - 1) - const blockSizeWithMargin = boxHeight + strokeWidth + marginBetweenBlocks - const piecesInOneRow = Math.floor((dimensions.width * 0.9) / blockSizeWithMargin) - const amountOfBlocksToRenderInShortView = - preloadPiecesAmount === piecesInOneRow - ? preloadPiecesAmount - 1 - : preloadPiecesAmount + piecesInOneRow - (preloadPiecesAmount % piecesInOneRow) - 1 - const amountOfRows = Math.ceil((isShortView ? amountOfBlocksToRenderInShortView : cacheMap.length) / piecesInOneRow) - let activeId = null - - return ( - setDimensions(contentRect.bounds)}> - {({ measureRef }) => ( -
- - - Hash {cache.Hash} -
- Capacity {humanizeSize(cache.Capacity)} -
- Filled {humanizeSize(cache.Filled)} -
- Torrent size {' '} - {cache.Torrent && cache.Torrent.torrent_size && humanizeSize(cache.Torrent.torrent_size)} -
- Pieces length {humanizeSize(cache.PiecesLength)} -
- Pieces count {cache.PiecesCount} -
- Peers: {getPeerString(cache.Torrent)} -
- Download speed {' '} - {cache.Torrent && cache.Torrent.download_speed ? `${humanizeSize(cache.Torrent.download_speed)}/sec` : ''} -
- Upload speed {' '} - {cache.Torrent && cache.Torrent.upload_speed ? `${humanizeSize(cache.Torrent.upload_speed)}/sec` : ''} -
- Status {cache.Torrent && cache.Torrent.stat_string && cache.Torrent.stat_string} -
-
- - - - {isLoading ? ( - 'loading' - ) : ( - - - {cacheMap.map(({ id, percentage, isComplete, inProgress, isActive, isReaderRange }) => { - const currentRow = Math.floor((isShortView ? id - activeId : id) / piecesInOneRow) - - // -------- related only for short view ------- - if (isActive) activeId = id - const shouldBeRendered = - isActive || (id - activeId <= amountOfBlocksToRenderInShortView && id - activeId >= 0) - // -------------------------------------------- - - return isShortView ? ( - shouldBeRendered && ( - - ) - ) : ( - - ) - })} - - - )} - -
- )} -
- ) -} - -/* -{ - "Hash": "41e36c8de915d80db83fc134bee4e7e2d292657e", - "Capacity": 209715200, - "Filled": 2914808, - "PiecesLength": 4194304, - "PiecesCount": 2065, - "DownloadSpeed": 32770.860273455524, - "Pieces": { - "2064": { - "Id": 2064, - "Length": 2914808, - "Size": 162296, - "Completed": false - } - } -} - */ diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index fc47754..93bb969 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -9,6 +9,9 @@ import { ArrowUpward as ArrowUpwardIcon, SwapVerticalCircle as SwapVerticalCircleIcon, ViewAgenda as ViewAgendaIcon, + Widgets as WidgetsIcon, + PhotoSizeSelectSmall as PhotoSizeSelectSmallIcon, + Build as BuildIcon, } from '@material-ui/icons' import axios from 'axios' import { playlistTorrHost, streamHost, torrentsHost, viewedHost } from 'utils/Hosts' @@ -117,9 +120,63 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { 'loading' ) : isDetailedCacheView ? ( -
PiecesCount: {PiecesCount}
-
PiecesLength: {humanizeSize(PiecesLength)}
-
status: {statString}
+ + + + + + + + + + + + + + +
) : ( diff --git a/web/src/components/DialogTorrentDetailsContent/style.js b/web/src/components/DialogTorrentDetailsContent/style.js index c524315..3ae704f 100644 --- a/web/src/components/DialogTorrentDetailsContent/style.js +++ b/web/src/components/DialogTorrentDetailsContent/style.js @@ -91,20 +91,20 @@ export const SectionHeader = styled.div` ` export const DetailedTorrentCacheViewWrapper = styled.div` - padding-top: 50px; + padding: 40px; overflow: auto; min-height: 80vh; ` export const StatisticsWrapper = styled.div` display: grid; - grid-template-columns: repeat(auto-fit, minmax(210px, min-content)); + grid-template-columns: repeat(auto-fit, minmax(max-content, 220px)); gap: 20px; ` export const StatisticsFieldWrapper = styled.div` display: grid; - grid-template-columns: 40px max-content; + grid-template-columns: 40px 1fr; grid-template-rows: min-content 50px; grid-template-areas: 'title title' diff --git a/web/src/components/TorrentCard/index.jsx b/web/src/components/TorrentCard/index.jsx index 30f7947..331a87f 100644 --- a/web/src/components/TorrentCard/index.jsx +++ b/web/src/components/TorrentCard/index.jsx @@ -1,4 +1,3 @@ -/* eslint-disable camelcase */ import 'fontsource-roboto' import { forwardRef, useState } from 'react' import HeightIcon from '@material-ui/icons/Height' @@ -38,7 +37,7 @@ export default function Torrent({ torrent }) { const openDeleteTorrentAlert = () => setIsDeleteTorrentOpened(true) const closeDeleteTorrentAlert = () => setIsDeleteTorrentOpened(false) - const { title, name, poster, torrent_size, download_speed, hash } = torrent + const { title, name, poster, torrent_size: torrentSize, download_speed: downloadSpeed, hash } = torrent const dropTorrent = () => axios.post(torrentsHost(), { action: 'drop', hash }) const deleteTorrent = () => axios.post(torrentsHost(), { action: 'rem', hash }) @@ -77,14 +76,14 @@ export default function Torrent({ torrent }) { Size - {torrent_size > 0 && humanizeSize(torrent_size)} + {torrentSize > 0 && humanizeSize(torrentSize)} Speed - {download_speed > 0 ? humanizeSize(download_speed) : '---'} + {downloadSpeed > 0 ? humanizeSize(downloadSpeed) : '---'} From 2432ebc9fbf4a4cdb08a7261c66a6402bc08ff7d Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Mon, 31 May 2021 17:33:17 +0300 Subject: [PATCH 26/40] refactor --- .../DialogTorrentDetailsContent/index.jsx | 135 +++++------------- .../DialogTorrentDetailsContent/style.js | 7 +- .../DialogTorrentDetailsContent/widgets.jsx | 68 +++++++++ 3 files changed, 106 insertions(+), 104 deletions(-) create mode 100644 web/src/components/DialogTorrentDetailsContent/widgets.jsx diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index 93bb969..09f5207 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -1,18 +1,9 @@ import { NoImageIcon } from 'icons' -import { getPeerString, humanizeSize } from 'utils/Utils' +import { humanizeSize } from 'utils/Utils' import { CopyToClipboard } from 'react-copy-to-clipboard' import { useEffect, useState } from 'react' import { Button } from '@material-ui/core' import ptt from 'parse-torrent-title' -import { - ArrowDownward as ArrowDownwardIcon, - ArrowUpward as ArrowUpwardIcon, - SwapVerticalCircle as SwapVerticalCircleIcon, - ViewAgenda as ViewAgendaIcon, - Widgets as WidgetsIcon, - PhotoSizeSelectSmall as PhotoSizeSelectSmallIcon, - Build as BuildIcon, -} from '@material-ui/icons' import axios from 'axios' import { playlistTorrHost, streamHost, torrentsHost, viewedHost } from 'utils/Hosts' import { GETTING_INFO, IN_DB } from 'torrentStates' @@ -28,7 +19,7 @@ import { Poster, SectionTitle, SectionSubName, - StatisticsWrapper, + WidgetWrapper, LoadingProgress, SectionHeader, CacheSection, @@ -37,7 +28,15 @@ import { SmallLabel, Table, } from './style' -import StatisticsField from './StatisticsField' +import { + DownlodSpeedWidget, + UploadSpeedWidget, + PeersWidget, + SizeWidget, + PiecesCountWidget, + PiecesLengthWidget, + StatusWidget, +} from './widgets' const shortenText = (text, count) => text.slice(0, count) + (text.length > count ? '...' : '') @@ -120,64 +119,23 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { 'loading' ) : isDetailedCacheView ? ( - - +
+ Data + + + + + + + + + +
- - - - - - - - - - - -
- +
+ Cache + +
) : ( @@ -194,39 +152,12 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { {shortenText(title, 50)} )} - - - - - - - - - + + + + + + diff --git a/web/src/components/DialogTorrentDetailsContent/style.js b/web/src/components/DialogTorrentDetailsContent/style.js index 3ae704f..fe62ab2 100644 --- a/web/src/components/DialogTorrentDetailsContent/style.js +++ b/web/src/components/DialogTorrentDetailsContent/style.js @@ -91,12 +91,15 @@ export const SectionHeader = styled.div` ` export const DetailedTorrentCacheViewWrapper = styled.div` - padding: 40px; overflow: auto; min-height: 80vh; + padding: 40px; + > :not(:last-child) { + padding-bottom: 50px; + } ` -export const StatisticsWrapper = styled.div` +export const WidgetWrapper = styled.div` display: grid; grid-template-columns: repeat(auto-fit, minmax(max-content, 220px)); gap: 20px; diff --git a/web/src/components/DialogTorrentDetailsContent/widgets.jsx b/web/src/components/DialogTorrentDetailsContent/widgets.jsx new file mode 100644 index 0000000..6a59f0c --- /dev/null +++ b/web/src/components/DialogTorrentDetailsContent/widgets.jsx @@ -0,0 +1,68 @@ +import { + ArrowDownward as ArrowDownwardIcon, + ArrowUpward as ArrowUpwardIcon, + SwapVerticalCircle as SwapVerticalCircleIcon, + ViewAgenda as ViewAgendaIcon, + Widgets as WidgetsIcon, + PhotoSizeSelectSmall as PhotoSizeSelectSmallIcon, + Build as BuildIcon, +} from '@material-ui/icons' +import { getPeerString, humanizeSize } from 'utils/Utils' + +import StatisticsField from './StatisticsField' + +export const DownlodSpeedWidget = ({ data }) => ( + +) + +export const UploadSpeedWidget = ({ data }) => ( + +) + +export const PeersWidget = ({ data }) => ( + +) + +export const PiecesCountWidget = ({ data }) => ( + +) +export const PiecesLengthWidget = ({ data }) => ( + +) +export const StatusWidget = ({ data }) => ( + +) + +export const SizeWidget = ({ data }) => ( + +) From fcd2e9b74e4363a23f7d60d65ea81a4599543709 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Mon, 31 May 2021 18:02:18 +0300 Subject: [PATCH 27/40] loaders added --- .../DialogTorrentDetailsContent/index.jsx | 351 +++++++++--------- .../DialogTorrentDetailsContent/style.js | 1 - web/src/components/TorrentList.jsx | 37 +- 3 files changed, 204 insertions(+), 185 deletions(-) diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index 09f5207..b3b8a32 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -7,6 +7,7 @@ import ptt from 'parse-torrent-title' import axios from 'axios' import { playlistTorrHost, streamHost, torrentsHost, viewedHost } from 'utils/Hosts' import { GETTING_INFO, IN_DB } from 'torrentStates' +import CircularProgress from '@material-ui/core/CircularProgress' import { useUpdateCache, useCreateCacheMap, useGetSettings } from './customHooks' import DialogHeader from './DialogHeader' @@ -115,197 +116,201 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { {...(isDetailedCacheView && { onBack: () => setIsDetailedCacheView(false) })} /> - {isLoading ? ( - 'loading' - ) : isDetailedCacheView ? ( - -
- Data - - - - - - - - - +
+ {isLoading ? ( +
+
- -
- Cache - -
- - ) : ( - - - {poster ? poster : } - + ) : isDetailedCacheView ? ( +
- {name && name !== title ? ( - <> - {shortenText(name, 50)} - {shortenText(title, 160)} - - ) : ( - {shortenText(title, 50)} - )} - + Data + + + +
- +
+ Cache + +
+
+ ) : ( + + + {poster ? poster : } - {!isOnlyOnePlayableFile && !!viewedFileList?.length && ( - <> - Download Playlist - - Latest file played: {latestViewedFileData.title}. - {latestViewedFileData.season && ( - <> - {' '} - Season: {latestViewedFileData.season}. Episode: {latestViewedFileData.episode}. - - )} - +
+ {name && name !== title ? ( + <> + {shortenText(name, 50)} + {shortenText(title, 160)} + + ) : ( + {shortenText(title, 50)} + )} - + + + + + + + + + + {!isOnlyOnePlayableFile && !!viewedFileList?.length && ( + <> + Download Playlist + + Latest file played: {latestViewedFileData.title}. + {latestViewedFileData.season && ( + <> + {' '} + Season: {latestViewedFileData.season}. Episode: {latestViewedFileData.episode}. + + )} + + + + + + + + + + + + + )} + + Torrent State + + + + + + + Info + + + {(isOnlyOnePlayableFile || !viewedFileList?.length) && ( + )} + + + + +
+
- - - - + + + Buffer + {!settings?.PreloadBuffer && ( + Enable "Preload Buffer" in settings to change buffer size + )} + + + + + + + + + Torrent Content + + {!playableFileList?.length ? ( + 'No playable files in this torrent' + ) : ( + <> + + + + + + {fileHasSeasonText && } + {fileHasEpisodeText && } + {fileHasResolutionText && } + + + + + + + {playableFileList.map(({ id, path, length }) => { + const { title, resolution, episode, season } = ptt.parse(path) + const isViewed = viewedFileList?.includes(id) + const link = getFileLink(path, id) + + return ( + + + {fileHasSeasonText && } + {fileHasEpisodeText && } + {fileHasResolutionText && } + + + + ) + })} + +
viewednameseasonepisoderesolutionsizeactions
+ {title}{season}{episode}{resolution}{humanizeSize(length)} + + + + + + + + + +
)} - - Torrent State - - - - - - - Info - - - {(isOnlyOnePlayableFile || !viewedFileList?.length) && ( - - - - )} - - - - -
- - - - - Buffer - {!settings?.PreloadBuffer && ( - Enable "Preload Buffer" in settings to change buffer size - )} - - - - - - - - - Torrent Content - - {!playableFileList?.length ? ( - 'No playable files in this torrent' - ) : ( - <> - - - - - - {fileHasSeasonText && } - {fileHasEpisodeText && } - {fileHasResolutionText && } - - - - - - - {playableFileList.map(({ id, path, length }) => { - const { title, resolution, episode, season } = ptt.parse(path) - const isViewed = viewedFileList?.includes(id) - const link = getFileLink(path, id) - - return ( - - - {fileHasSeasonText && } - {fileHasEpisodeText && } - {fileHasResolutionText && } - - - - ) - })} - -
viewednameseasonepisoderesolutionsizeactions
- {title}{season}{episode}{resolution}{humanizeSize(length)} - - - - - - - - - -
- - )} -
- - )} + + + )} +
) } diff --git a/web/src/components/DialogTorrentDetailsContent/style.js b/web/src/components/DialogTorrentDetailsContent/style.js index fe62ab2..f83b93b 100644 --- a/web/src/components/DialogTorrentDetailsContent/style.js +++ b/web/src/components/DialogTorrentDetailsContent/style.js @@ -92,7 +92,6 @@ export const SectionHeader = styled.div` export const DetailedTorrentCacheViewWrapper = styled.div` overflow: auto; - min-height: 80vh; padding: 40px; > :not(:last-child) { padding-bottom: 50px; diff --git a/web/src/components/TorrentList.jsx b/web/src/components/TorrentList.jsx index 7b33671..35fd993 100644 --- a/web/src/components/TorrentList.jsx +++ b/web/src/components/TorrentList.jsx @@ -4,6 +4,7 @@ import { Typography } from '@material-ui/core' import { torrentsHost } from 'utils/Hosts' import TorrentCard from 'components/TorrentCard' import axios from 'axios' +import CircularProgress from '@material-ui/core/CircularProgress' const TorrentListWrapper = styled.div` display: grid; @@ -20,9 +21,16 @@ const TorrentListWrapper = styled.div` } ` +const CenteredGrid = styled.div` + height: 75vh; + display: grid; + place-items: center; +` + export default function TorrentList() { const [torrents, setTorrents] = useState([]) - const [offline, setOffline] = useState(true) + const [isLoading, setIsLoading] = useState(true) + const [isOffline, setIsOffline] = useState(true) const timerID = useRef(-1) useEffect(() => { @@ -33,27 +41,34 @@ export default function TorrentList() { .then(({ data }) => { // updating torrent list setTorrents(data) - setOffline(false) + setIsOffline(false) }) .catch(() => { // resetting torrent list setTorrents([]) - setOffline(true) + setIsOffline(true) }) + .finally(() => setIsLoading(false)) }, 1000) return () => clearInterval(timerID.current) }, []) - return ( + return isLoading ? ( + + + + ) : isOffline ? ( + + Offline + + ) : !torrents.length ? ( + No torrents added + ) : ( - {offline ? ( - Offline - ) : !torrents.length ? ( - No torrents added - ) : ( - torrents.map(torrent => ) - )} + {torrents.map(torrent => ( + + ))} ) } From 6f728d2feef5fb8bcf2a032a7cf0bd92a24c67f3 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Mon, 31 May 2021 18:14:45 +0300 Subject: [PATCH 28/40] background added --- .../DialogTorrentDetailsContent/index.jsx | 16 +++++++++------- .../DialogTorrentDetailsContent/style.js | 16 +++++++++++----- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index b3b8a32..8537acc 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -13,7 +13,9 @@ import { useUpdateCache, useCreateCacheMap, useGetSettings } from './customHooks import DialogHeader from './DialogHeader' import TorrentCache from './TorrentCache' import { - DetailedTorrentCacheViewWrapper, + DetailedViewWrapper, + DetailedViewWidgetSection, + DetailedViewCacheSection, DialogContentGrid, MainSection, MainSectionButtonGroup, @@ -122,8 +124,8 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { ) : isDetailedCacheView ? ( - -
+ + Data @@ -134,13 +136,13 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { -
+ -
+ Cache -
-
+ + ) : ( diff --git a/web/src/components/DialogTorrentDetailsContent/style.js b/web/src/components/DialogTorrentDetailsContent/style.js index f83b93b..67a5591 100644 --- a/web/src/components/DialogTorrentDetailsContent/style.js +++ b/web/src/components/DialogTorrentDetailsContent/style.js @@ -90,12 +90,8 @@ export const SectionHeader = styled.div` margin-bottom: 20px; ` -export const DetailedTorrentCacheViewWrapper = styled.div` +export const DetailedViewWrapper = styled.div` overflow: auto; - padding: 40px; - > :not(:last-child) { - padding-bottom: 50px; - } ` export const WidgetWrapper = styled.div` @@ -241,3 +237,13 @@ export const Table = styled.table` } } ` + +export const DetailedViewWidgetSection = styled.section` + padding: 40px; + background: linear-gradient(145deg, #e4f6ed, #b5dec9); +` + +export const DetailedViewCacheSection = styled.section` + padding: 40px; + box-shadow: inset 3px 25px 8px -25px rgba(0, 0, 0, 0.5); +` From 78d02c00b613f8fc10aafee176b5ffb98814aa57 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Mon, 31 May 2021 18:42:37 +0300 Subject: [PATCH 29/40] adaptive design in cache view --- .../StatisticsField.jsx | 14 +++--- .../DialogTorrentDetailsContent/index.jsx | 2 +- .../DialogTorrentDetailsContent/style.js | 46 +++++++++++++++++-- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/web/src/components/DialogTorrentDetailsContent/StatisticsField.jsx b/web/src/components/DialogTorrentDetailsContent/StatisticsField.jsx index 7ddf8fa..c6b0e65 100644 --- a/web/src/components/DialogTorrentDetailsContent/StatisticsField.jsx +++ b/web/src/components/DialogTorrentDetailsContent/StatisticsField.jsx @@ -1,14 +1,14 @@ -import { StatisticsFieldWrapper, StatisticsFieldIcon, StatisticsFieldValue, StatisticsFieldTitle } from './style' +import { WidgetFieldWrapper, WidgetFieldIcon, WidgetFieldValue, WidgetFieldTitle } from './style' export default function StatisticsField({ icon: Icon, title, value, iconBg, valueBg }) { return ( - - {title} - + + {title} + - + - {value} - + {value} + ) } diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index 8537acc..1310578 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -127,7 +127,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { Data - + diff --git a/web/src/components/DialogTorrentDetailsContent/style.js b/web/src/components/DialogTorrentDetailsContent/style.js index 67a5591..31b0374 100644 --- a/web/src/components/DialogTorrentDetailsContent/style.js +++ b/web/src/components/DialogTorrentDetailsContent/style.js @@ -98,9 +98,22 @@ export const WidgetWrapper = styled.div` display: grid; grid-template-columns: repeat(auto-fit, minmax(max-content, 220px)); gap: 20px; + + ${({ detailedView }) => + detailedView && + css` + @media (max-width: 800px) { + gap: 15px; + grid-template-columns: repeat(2, 1fr); + } + @media (max-width: 410px) { + gap: 10px; + grid-template-columns: 1fr; + } + `} ` -export const StatisticsFieldWrapper = styled.div` +export const WidgetFieldWrapper = styled.div` display: grid; grid-template-columns: 40px 1fr; grid-template-rows: min-content 50px; @@ -112,8 +125,13 @@ export const StatisticsFieldWrapper = styled.div` display: grid; place-items: center; } + + @media (max-width: 800px) { + grid-template-columns: 30px 1fr; + grid-template-rows: min-content 40px; + } ` -export const StatisticsFieldTitle = styled.div` +export const WidgetFieldTitle = styled.div` grid-area: title; justify-self: start; text-transform: uppercase; @@ -122,23 +140,33 @@ export const StatisticsFieldTitle = styled.div` font-weight: 500; ` -export const StatisticsFieldIcon = styled.div` +export const WidgetFieldIcon = styled.div` ${({ bgColor }) => css` grid-area: icon; color: rgba(255, 255, 255, 0.8); background: ${bgColor}; border-radius: 5px 0 0 5px; + + @media (max-width: 800px) { + > svg { + width: 50%; + } + } `} ` -export const StatisticsFieldValue = styled.div` +export const WidgetFieldValue = styled.div` ${({ bgColor }) => css` grid-area: value; - min-width: 170px; padding: 0 20px; color: #fff; font-size: 25px; background: ${bgColor}; border-radius: 0 5px 5px 0; + + @media (max-width: 800px) { + font-size: 18px; + padding: 0 4px; + } `} ` @@ -241,9 +269,17 @@ export const Table = styled.table` export const DetailedViewWidgetSection = styled.section` padding: 40px; background: linear-gradient(145deg, #e4f6ed, #b5dec9); + + @media (max-width: 800px) { + padding: 20px; + } ` export const DetailedViewCacheSection = styled.section` padding: 40px; box-shadow: inset 3px 25px 8px -25px rgba(0, 0, 0, 0.5); + + @media (max-width: 800px) { + padding: 20px; + } ` From c2793438ad4bafdbc57d34a845448a13cb37ef73 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Mon, 31 May 2021 19:26:56 +0300 Subject: [PATCH 30/40] adaptive design added --- .../TorrentCache.jsx | 7 +- .../DialogTorrentDetailsContent/index.jsx | 4 +- .../DialogTorrentDetailsContent/style.js | 84 ++++++++++++++++--- 3 files changed, 79 insertions(+), 16 deletions(-) diff --git a/web/src/components/DialogTorrentDetailsContent/TorrentCache.jsx b/web/src/components/DialogTorrentDetailsContent/TorrentCache.jsx index d46d259..b739036 100644 --- a/web/src/components/DialogTorrentDetailsContent/TorrentCache.jsx +++ b/web/src/components/DialogTorrentDetailsContent/TorrentCache.jsx @@ -25,8 +25,9 @@ export default function TorrentCache({ cache, cacheMap, isMini }) { useEffect(() => { // initializing stageSettings - isMini ? updateStageSettings(24, 4) : updateStageSettings(12, 2) - }, [isMini]) + if (isMini) return dimensions.width < 500 ? updateStageSettings(20, 3) : updateStageSettings(24, 4) + updateStageSettings(12, 2) + }, [isMini, dimensions.width]) const { boxHeight, strokeWidth, marginBetweenBlocks, stageOffset } = stageSettings const preloadPiecesAmount = Math.round(cache.Capacity / cache.PiecesLength - 1) @@ -40,7 +41,7 @@ export default function TorrentCache({ cache, cacheMap, isMini }) { let activeId = null return ( - setDimensions(contentRect.bounds)}> + setDimensions(bounds)}> {({ measureRef }) => (
diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index 1310578..27aeefb 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -251,7 +251,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { - + {/* Torrent Content {!playableFileList?.length ? ( @@ -309,7 +309,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { )} - + */} )}
diff --git a/web/src/components/DialogTorrentDetailsContent/style.js b/web/src/components/DialogTorrentDetailsContent/style.js index 31b0374..f7b056e 100644 --- a/web/src/components/DialogTorrentDetailsContent/style.js +++ b/web/src/components/DialogTorrentDetailsContent/style.js @@ -8,6 +8,15 @@ export const DialogContentGrid = styled.div` grid-template-areas: 'main cache' 'file-list file-list'; + + @media (max-width: 1450px) { + grid-template-columns: 1fr; + grid-template-rows: repeat(3, min-content); + grid-template-areas: + 'main' + 'cache' + 'file-list'; + } ` export const Poster = styled.div` ${({ poster }) => css` @@ -33,6 +42,22 @@ export const Poster = styled.div` transform: scale(2.5) translateY(-3px); } `} + + @media (max-width: 1280px) { + align-self: start; + } + + @media (max-width: 840px) { + height: 200px; + + ${!poster && + css` + width: 150px; + svg { + transform: translateY(-3px); + } + `} + } `} ` export const MainSection = styled.section` @@ -42,6 +67,10 @@ export const MainSection = styled.section` grid-template-columns: min-content 1fr; gap: 30px; background: linear-gradient(145deg, #e4f6ed, #b5dec9); + + @media (max-width: 840px) { + grid-template-columns: 1fr; + } ` export const MainSectionButtonGroup = styled.div` @@ -52,6 +81,14 @@ export const MainSectionButtonGroup = styled.div` :not(:last-child) { margin-bottom: 30px; } + + @media (max-width: 1045px) { + grid-template-columns: repeat(2, 1fr); + } + + @media (max-width: 880px) { + grid-template-columns: 1fr; + } ` export const CacheSection = styled.section` @@ -73,6 +110,11 @@ export const SectionSubName = styled.div` ${({ mb }) => css` ${mb && `margin-bottom: ${mb}px`}; color: #7c7b7c; + + @media (max-width: 800px) { + ${mb && `margin-bottom: ${mb / 2}px`}; + font-size: 11px; + } `} ` @@ -83,6 +125,11 @@ export const SectionTitle = styled.div` font-weight: 200; line-height: 1; word-break: break-word; + + @media (max-width: 800px) { + font-size: 25px; + ${mb && `margin-bottom: ${mb / 2}px`}; + } `} ` @@ -99,18 +146,28 @@ export const WidgetWrapper = styled.div` grid-template-columns: repeat(auto-fit, minmax(max-content, 220px)); gap: 20px; + @media (max-width: 800px) { + gap: 15px; + } + @media (max-width: 410px) { + gap: 10px; + } + ${({ detailedView }) => - detailedView && - css` - @media (max-width: 800px) { - gap: 15px; - grid-template-columns: repeat(2, 1fr); - } - @media (max-width: 410px) { - gap: 10px; - grid-template-columns: 1fr; - } - `} + detailedView + ? css` + @media (max-width: 800px) { + grid-template-columns: repeat(2, 1fr); + } + @media (max-width: 410px) { + grid-template-columns: 1fr; + } + ` + : css` + @media (max-width: 840px) { + grid-template-columns: 1fr; + } + `} ` export const WidgetFieldWrapper = styled.div` @@ -206,6 +263,11 @@ export const SmallLabel = styled.div` font-size: 20px; font-weight: 300; line-height: 1; + + @media (max-width: 800px) { + font-size: 18px; + ${mb && `margin-bottom: ${mb / 1.5}px`}; + } `} ` From 191b45fe7fc9046f7a05a2eab41ac63735191493 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Mon, 31 May 2021 19:33:27 +0300 Subject: [PATCH 31/40] refactor --- web/src/components/DialogTorrentDetailsContent/index.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index 27aeefb..58b461f 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -118,7 +118,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { {...(isDetailedCacheView && { onBack: () => setIsDetailedCacheView(false) })} /> -
+
{isLoading ? (
@@ -251,7 +251,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { - {/* + Torrent Content {!playableFileList?.length ? ( @@ -309,7 +309,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { )} - */} + )}
From 9109f0f694a71e48b223784a8a244842ef304447 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Mon, 31 May 2021 19:54:09 +0300 Subject: [PATCH 32/40] refactor --- web/src/components/DialogTorrentDetailsContent/index.jsx | 9 ++++----- web/src/components/DialogTorrentDetailsContent/style.js | 5 ----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index 58b461f..fb21c1f 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -13,7 +13,6 @@ import { useUpdateCache, useCreateCacheMap, useGetSettings } from './customHooks import DialogHeader from './DialogHeader' import TorrentCache from './TorrentCache' import { - DetailedViewWrapper, DetailedViewWidgetSection, DetailedViewCacheSection, DialogContentGrid, @@ -124,7 +123,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
) : isDetailedCacheView ? ( - + <> Data @@ -142,7 +141,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { Cache - + ) : ( @@ -251,7 +250,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { - + {/* Torrent Content {!playableFileList?.length ? ( @@ -309,7 +308,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { )} - + */} )}
diff --git a/web/src/components/DialogTorrentDetailsContent/style.js b/web/src/components/DialogTorrentDetailsContent/style.js index f7b056e..6d0912a 100644 --- a/web/src/components/DialogTorrentDetailsContent/style.js +++ b/web/src/components/DialogTorrentDetailsContent/style.js @@ -2,7 +2,6 @@ import styled, { css } from 'styled-components' export const DialogContentGrid = styled.div` display: grid; - overflow: auto; grid-template-columns: 70% 1fr; grid-template-rows: repeat(2, min-content); grid-template-areas: @@ -137,10 +136,6 @@ export const SectionHeader = styled.div` margin-bottom: 20px; ` -export const DetailedViewWrapper = styled.div` - overflow: auto; -` - export const WidgetWrapper = styled.div` display: grid; grid-template-columns: repeat(auto-fit, minmax(max-content, 220px)); From 950b84e34e5ab58c38c752369d9a9ee04d539abb Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Tue, 1 Jun 2021 18:55:31 +0300 Subject: [PATCH 33/40] refactor --- web/package.json | 1 + .../Table/index.jsx | 144 ++++++++++ .../Table/style.js | 171 ++++++++++++ .../TorrentCache.jsx | 163 +++++------ .../TorrentFunctions/index.jsx | 83 ++++++ .../TorrentFunctions/style.js | 33 +++ .../DialogTorrentDetailsContent/helpers.js | 68 +++++ .../DialogTorrentDetailsContent/index.jsx | 261 ++++-------------- .../DialogTorrentDetailsContent/style.js | 105 ++----- web/yarn.lock | 2 +- 10 files changed, 660 insertions(+), 371 deletions(-) create mode 100644 web/src/components/DialogTorrentDetailsContent/Table/index.jsx create mode 100644 web/src/components/DialogTorrentDetailsContent/Table/style.js create mode 100644 web/src/components/DialogTorrentDetailsContent/TorrentFunctions/index.jsx create mode 100644 web/src/components/DialogTorrentDetailsContent/TorrentFunctions/style.js create mode 100644 web/src/components/DialogTorrentDetailsContent/helpers.js diff --git a/web/package.json b/web/package.json index 282af49..0aa4c5e 100644 --- a/web/package.json +++ b/web/package.json @@ -9,6 +9,7 @@ "clsx": "^1.1.1", "fontsource-roboto": "^4.0.0", "konva": "^8.0.1", + "lodash": "^4.17.21", "material-ui-image": "^3.3.2", "parse-torrent-title": "^1.3.0", "react": "^17.0.2", diff --git a/web/src/components/DialogTorrentDetailsContent/Table/index.jsx b/web/src/components/DialogTorrentDetailsContent/Table/index.jsx new file mode 100644 index 0000000..ce2121e --- /dev/null +++ b/web/src/components/DialogTorrentDetailsContent/Table/index.jsx @@ -0,0 +1,144 @@ +import { streamHost } from 'utils/Hosts' +import { isEqual } from 'lodash' +import { humanizeSize } from 'utils/Utils' +import ptt from 'parse-torrent-title' +import { Button } from '@material-ui/core' +import CopyToClipboard from 'react-copy-to-clipboard' + +import { TableStyle, ShortTableWrapper, ShortTable } from './style' + +const { memo } = require('react') + +const Table = memo( + ({ playableFileList, viewedFileList, selectedSeason, seasonAmount, hash }) => { + const preloadBuffer = fileId => fetch(`${streamHost()}?link=${hash}&index=${fileId}&preload`) + const getFileLink = (path, id) => + `${streamHost()}/${encodeURIComponent(path.split('\\').pop().split('/').pop())}?link=${hash}&index=${id}&play` + const fileHasEpisodeText = !!playableFileList?.find(({ path }) => ptt.parse(path).episode) + const fileHasSeasonText = !!playableFileList?.find(({ path }) => ptt.parse(path).season) + const fileHasResolutionText = !!playableFileList?.find(({ path }) => ptt.parse(path).resolution) + + return !playableFileList?.length ? ( + 'No playable files in this torrent' + ) : ( + <> + + + + viewed + name + {fileHasSeasonText && seasonAmount?.length === 1 && season} + {fileHasEpisodeText && episode} + {fileHasResolutionText && resolution} + size + actions + + + + + {playableFileList.map(({ id, path, length }) => { + const { title, resolution, episode, season } = ptt.parse(path) + const isViewed = viewedFileList?.includes(id) + const link = getFileLink(path, id) + + return ( + (season === selectedSeason || !seasonAmount?.length) && ( + + + {title} + {fileHasSeasonText && seasonAmount?.length === 1 && {season}} + {fileHasEpisodeText && {episode}} + {fileHasResolutionText && {resolution}} + {humanizeSize(length)} + + + + + + + + + + + + + ) + ) + })} + + + + {playableFileList.map(({ id, path, length }) => { + const { title, resolution, episode, season } = ptt.parse(path) + const isViewed = viewedFileList?.includes(id) + const link = getFileLink(path, id) + + return ( + (season === selectedSeason || !seasonAmount?.length) && ( + +
{title}
+
+
+
viewed
+
+
+
+
+ {fileHasSeasonText && seasonAmount?.length === 1 && ( +
+
season
+
{season}
+
+ )} + {fileHasEpisodeText && ( +
+
epoisode
+
{episode}
+
+ )} + {fileHasResolutionText && ( +
+
resolution
+
{resolution}
+
+ )} +
+
size
+
{humanizeSize(length)}
+
+
+
+ + + + + + + + + +
+ + ) + ) + })} + + + ) + }, + (prev, next) => isEqual(prev, next), +) + +export default Table diff --git a/web/src/components/DialogTorrentDetailsContent/Table/style.js b/web/src/components/DialogTorrentDetailsContent/Table/style.js new file mode 100644 index 0000000..f55197c --- /dev/null +++ b/web/src/components/DialogTorrentDetailsContent/Table/style.js @@ -0,0 +1,171 @@ +import styled, { css } from 'styled-components' + +const viewedIndicator = css` + :before { + content: ''; + width: 10px; + height: 10px; + background: #15d5af; + border-radius: 50%; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } +` +export const TableStyle = styled.table` + border-collapse: collapse; + margin: 25px 0; + font-size: 0.9em; + width: 100%; + border-radius: 5px 5px 0 0; + overflow: hidden; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); + + thead tr { + background: #009879; + color: #fff; + text-align: left; + text-transform: uppercase; + } + + th, + td { + padding: 12px 15px; + } + + tbody tr { + border-bottom: 1px solid #ddd; + + :last-of-type { + border-bottom: 2px solid #009879; + } + + &.viewed-file-row { + background: #f3f3f3; + } + } + + td { + &.viewed-file-indicator { + position: relative; + + ${viewedIndicator} + } + + &.button-cell { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 10px; + } + } + + @media (max-width: 970px) { + display: none; + } +` + +export const ShortTableWrapper = styled.div` + display: grid; + gap: 20px; + grid-template-columns: repeat(2, 1fr); + display: none; + + @media (max-width: 970px) { + display: grid; + } + + @media (max-width: 820px) { + gap: 15px; + grid-template-columns: 1fr; + } +` + +export const ShortTable = styled.div` + ${({ isViewed }) => css` + width: 100%; + grid-template-rows: repeat(3, max-content); + border-radius: 5px; + overflow: hidden; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); + + .short-table { + &-name { + background: ${isViewed ? '#bdbdbd' : '#009879'}; + display: grid; + place-items: center; + padding: 15px; + color: #fff; + text-transform: uppercase; + font-size: 15px; + font-weight: bold; + + @media (max-width: 880px) { + font-size: 13px; + padding: 10px; + } + } + &-data { + display: grid; + grid-auto-flow: column; + grid-template-columns: max-content; + } + &-field { + display: grid; + grid-template-rows: 30px 1fr; + background: black; + :not(:last-child) { + border-right: 1px solid ${isViewed ? '#bdbdbd' : '#019376'}; + } + + &-name { + background: ${isViewed ? '#c4c4c4' : '#00a383'}; + color: #fff; + text-transform: uppercase; + font-size: 12px; + font-weight: 500; + display: grid; + place-items: center; + padding: 0 10px; + + @media (max-width: 880px) { + font-size: 11px; + } + } + + &-value { + background: ${isViewed ? '#c9c9c9' : '#03aa89'}; + display: grid; + place-items: center; + color: #fff; + font-size: 15px; + padding: 15px 10px; + position: relative; + + @media (max-width: 880px) { + font-size: 13px; + padding: 12px 8px; + } + } + } + + &-viewed-indicator { + ${isViewed && viewedIndicator} + } + + &-buttons { + padding: 20px; + border-bottom: 2px solid ${isViewed ? '#bdbdbd' : '#009879'}; + display: grid; + grid-template-columns: repeat(3, 1fr); + align-items: center; + gap: 20px; + + @media (max-width: 410px) { + gap: 10px; + grid-template-columns: 1fr; + } + } + } + `} +` diff --git a/web/src/components/DialogTorrentDetailsContent/TorrentCache.jsx b/web/src/components/DialogTorrentDetailsContent/TorrentCache.jsx index b739036..de600e7 100644 --- a/web/src/components/DialogTorrentDetailsContent/TorrentCache.jsx +++ b/web/src/components/DialogTorrentDetailsContent/TorrentCache.jsx @@ -1,71 +1,90 @@ -import { useEffect, useState } from 'react' +import { memo, useEffect, useState } from 'react' import DialogContent from '@material-ui/core/DialogContent' import { Stage, Layer } from 'react-konva' import Measure from 'react-measure' +import { isEqual } from 'lodash' import SingleBlock from './SingleBlock' +import { useCreateCacheMap } from './customHooks' -export default function TorrentCache({ cache, cacheMap, isMini }) { - const [dimensions, setDimensions] = useState({ width: -1, height: -1 }) - const [stageSettings, setStageSettings] = useState({ - boxHeight: null, - strokeWidth: null, - marginBetweenBlocks: null, - stageOffset: null, - }) - - const updateStageSettings = (boxHeight, strokeWidth) => { - setStageSettings({ - boxHeight, - strokeWidth, - marginBetweenBlocks: strokeWidth, - stageOffset: strokeWidth * 2, +const TorrentCache = memo( + ({ cache, isMini }) => { + const [dimensions, setDimensions] = useState({ width: -1, height: -1 }) + const [stageSettings, setStageSettings] = useState({ + boxHeight: null, + strokeWidth: null, + marginBetweenBlocks: null, + stageOffset: null, }) - } - useEffect(() => { - // initializing stageSettings - if (isMini) return dimensions.width < 500 ? updateStageSettings(20, 3) : updateStageSettings(24, 4) - updateStageSettings(12, 2) - }, [isMini, dimensions.width]) + const cacheMap = useCreateCacheMap(cache) - const { boxHeight, strokeWidth, marginBetweenBlocks, stageOffset } = stageSettings - const preloadPiecesAmount = Math.round(cache.Capacity / cache.PiecesLength - 1) - const blockSizeWithMargin = boxHeight + strokeWidth + marginBetweenBlocks - const piecesInOneRow = Math.floor((dimensions.width * 0.9) / blockSizeWithMargin) - const amountOfBlocksToRenderInShortView = - preloadPiecesAmount === piecesInOneRow - ? preloadPiecesAmount - 1 - : preloadPiecesAmount + piecesInOneRow - (preloadPiecesAmount % piecesInOneRow) - 1 - const amountOfRows = Math.ceil((isMini ? amountOfBlocksToRenderInShortView : cacheMap.length) / piecesInOneRow) - let activeId = null + const updateStageSettings = (boxHeight, strokeWidth) => { + setStageSettings({ + boxHeight, + strokeWidth, + marginBetweenBlocks: strokeWidth, + stageOffset: strokeWidth * 2, + }) + } - return ( - setDimensions(bounds)}> - {({ measureRef }) => ( -
- - - - {cacheMap.map(({ id, percentage, isComplete, inProgress, isActive, isReaderRange }) => { - const currentRow = Math.floor((isMini ? id - activeId : id) / piecesInOneRow) + useEffect(() => { + // initializing stageSettings + if (isMini) return dimensions.width < 500 ? updateStageSettings(20, 3) : updateStageSettings(24, 4) + updateStageSettings(12, 2) + }, [isMini, dimensions.width]) - // -------- related only for short view ------- - if (isActive) activeId = id - const shouldBeRendered = - isActive || (id - activeId <= amountOfBlocksToRenderInShortView && id - activeId >= 0) - // -------------------------------------------- + const { boxHeight, strokeWidth, marginBetweenBlocks, stageOffset } = stageSettings + const preloadPiecesAmount = Math.round(cache.Capacity / cache.PiecesLength - 1) + const blockSizeWithMargin = boxHeight + strokeWidth + marginBetweenBlocks + const piecesInOneRow = Math.floor((dimensions.width * 0.9) / blockSizeWithMargin) + const amountOfBlocksToRenderInShortView = + preloadPiecesAmount === piecesInOneRow + ? preloadPiecesAmount - 1 + : preloadPiecesAmount + piecesInOneRow - (preloadPiecesAmount % piecesInOneRow) - 1 + const amountOfRows = Math.ceil((isMini ? amountOfBlocksToRenderInShortView : cacheMap.length) / piecesInOneRow) + let activeId = null - return isMini ? ( - shouldBeRendered && ( + return ( + setDimensions(bounds)}> + {({ measureRef }) => ( +
+ + + + {cacheMap.map(({ id, percentage, isComplete, inProgress, isActive, isReaderRange }) => { + const currentRow = Math.floor((isMini ? id - activeId : id) / piecesInOneRow) + + // -------- related only for short view ------- + if (isActive) activeId = id + const shouldBeRendered = + isActive || (id - activeId <= amountOfBlocksToRenderInShortView && id - activeId >= 0) + // -------------------------------------------- + + return isMini ? ( + shouldBeRendered && ( + + ) + ) : ( ) - ) : ( - - ) - })} - - - -
- )} -
- ) -} + })} +
+
+
+
+ )} +
+ ) + }, + (prev, next) => isEqual(prev.cache.Pieces, next.cache.Pieces) && isEqual(prev.cache.Readers, next.cache.Readers), +) + +export default TorrentCache diff --git a/web/src/components/DialogTorrentDetailsContent/TorrentFunctions/index.jsx b/web/src/components/DialogTorrentDetailsContent/TorrentFunctions/index.jsx new file mode 100644 index 0000000..e460a87 --- /dev/null +++ b/web/src/components/DialogTorrentDetailsContent/TorrentFunctions/index.jsx @@ -0,0 +1,83 @@ +import axios from 'axios' +import { memo } from 'react' +import { playlistTorrHost, torrentsHost, viewedHost } from 'utils/Hosts' +import { CopyToClipboard } from 'react-copy-to-clipboard' +import { Button } from '@material-ui/core' +import ptt from 'parse-torrent-title' + +import { SmallLabel, MainSectionButtonGroup } from './style' +import { SectionSubName } from '../style' + +const TorrentFunctions = memo( + ({ hash, viewedFileList, playableFileList, name, title, setViewedFileList }) => { + const latestViewedFileId = viewedFileList?.[viewedFileList?.length - 1] + const latestViewedFile = playableFileList?.find(({ id }) => id === latestViewedFileId)?.path + const isOnlyOnePlayableFile = playableFileList?.length === 1 + const latestViewedFileData = latestViewedFile && ptt.parse(latestViewedFile) + const dropTorrent = () => axios.post(torrentsHost(), { action: 'drop', hash }) + const removeTorrentViews = () => + axios.post(viewedHost(), { action: 'rem', hash, file_index: -1 }).then(() => setViewedFileList()) + const fullPlaylistLink = `${playlistTorrHost()}/${encodeURIComponent(name || title || 'file')}.m3u?link=${hash}&m3u` + const partialPlaylistLink = `${fullPlaylistLink}&fromlast` + + return ( + <> + {!isOnlyOnePlayableFile && !!viewedFileList?.length && ( + <> + Download Playlist + + Latest file played: {latestViewedFileData?.title}. + {latestViewedFileData?.season && ( + <> + {' '} + Season: {latestViewedFileData?.season}. Episode: {latestViewedFileData?.episode}. + + )} + + + + + + + + + + + + + )} + Torrent State + + + + + Info + + {(isOnlyOnePlayableFile || !viewedFileList?.length) && ( + + + + )} + + + + + + ) + }, + () => true, +) + +export default TorrentFunctions diff --git a/web/src/components/DialogTorrentDetailsContent/TorrentFunctions/style.js b/web/src/components/DialogTorrentDetailsContent/TorrentFunctions/style.js new file mode 100644 index 0000000..0955c5e --- /dev/null +++ b/web/src/components/DialogTorrentDetailsContent/TorrentFunctions/style.js @@ -0,0 +1,33 @@ +import styled, { css } from 'styled-components' + +export const MainSectionButtonGroup = styled.div` + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 20px; + + :not(:last-child) { + margin-bottom: 30px; + } + + @media (max-width: 1580px) { + grid-template-columns: repeat(2, 1fr); + } + + @media (max-width: 880px) { + grid-template-columns: 1fr; + } +` + +export const SmallLabel = styled.div` + ${({ mb }) => css` + ${mb && `margin-bottom: ${mb}px`}; + font-size: 20px; + font-weight: 300; + line-height: 1; + + @media (max-width: 800px) { + font-size: 18px; + ${mb && `margin-bottom: ${mb / 1.5}px`}; + } + `} +` diff --git a/web/src/components/DialogTorrentDetailsContent/helpers.js b/web/src/components/DialogTorrentDetailsContent/helpers.js new file mode 100644 index 0000000..cb164e5 --- /dev/null +++ b/web/src/components/DialogTorrentDetailsContent/helpers.js @@ -0,0 +1,68 @@ +const getExt = filename => { + const ext = filename.split('.').pop() + if (ext === filename) return '' + return ext.toLowerCase() +} +const playableExtList = [ + // video + '3g2', + '3gp', + 'aaf', + 'asf', + 'avchd', + 'avi', + 'drc', + 'flv', + 'iso', + 'm2v', + 'm2ts', + 'm4p', + 'm4v', + 'mkv', + 'mng', + 'mov', + 'mp2', + 'mp4', + 'mpe', + 'mpeg', + 'mpg', + 'mpv', + 'mxf', + 'nsv', + 'ogg', + 'ogv', + 'ts', + 'qt', + 'rm', + 'rmvb', + 'roq', + 'svi', + 'vob', + 'webm', + 'wmv', + 'yuv', + // audio + 'aac', + 'aiff', + 'ape', + 'au', + 'flac', + 'gsm', + 'it', + 'm3u', + 'm4a', + 'mid', + 'mod', + 'mp3', + 'mpa', + 'pls', + 'ra', + 's3m', + 'sid', + 'wav', + 'wma', + 'xm', +] + +// eslint-disable-next-line import/prefer-default-export +export const isFilePlayable = fileName => playableExtList.includes(getExt(fileName)) diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index fb21c1f..ac4e15a 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -1,23 +1,22 @@ import { NoImageIcon } from 'icons' import { humanizeSize } from 'utils/Utils' -import { CopyToClipboard } from 'react-copy-to-clipboard' import { useEffect, useState } from 'react' -import { Button } from '@material-ui/core' +import { Button, ButtonGroup } from '@material-ui/core' import ptt from 'parse-torrent-title' import axios from 'axios' -import { playlistTorrHost, streamHost, torrentsHost, viewedHost } from 'utils/Hosts' +import { viewedHost } from 'utils/Hosts' import { GETTING_INFO, IN_DB } from 'torrentStates' import CircularProgress from '@material-ui/core/CircularProgress' -import { useUpdateCache, useCreateCacheMap, useGetSettings } from './customHooks' +import { useUpdateCache, useGetSettings } from './customHooks' import DialogHeader from './DialogHeader' import TorrentCache from './TorrentCache' +import Table from './Table' import { DetailedViewWidgetSection, DetailedViewCacheSection, DialogContentGrid, MainSection, - MainSectionButtonGroup, Poster, SectionTitle, SectionSubName, @@ -27,8 +26,6 @@ import { CacheSection, TorrentFilesSection, Divider, - SmallLabel, - Table, } from './style' import { DownlodSpeedWidget, @@ -39,6 +36,8 @@ import { PiecesLengthWidget, StatusWidget, } from './widgets' +import TorrentFunctions from './TorrentFunctions' +import { isFilePlayable } from './helpers' const shortenText = (text, count) => text.slice(0, count) + (text.length > count ? '...' : '') @@ -47,11 +46,8 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { const [isDetailedCacheView, setIsDetailedCacheView] = useState(false) const [viewedFileList, setViewedFileList] = useState() const [playableFileList, setPlayableFileList] = useState() - - const isOnlyOnePlayableFile = playableFileList?.length === 1 - const latestViewedFileId = viewedFileList?.[viewedFileList?.length - 1] - const latestViewedFile = playableFileList?.find(({ id }) => id === latestViewedFileId)?.path - const latestViewedFileData = latestViewedFile && ptt.parse(latestViewedFile) + const [seasonAmount, setSeasonAmount] = useState(null) + const [selectedSeason, setSelectedSeason] = useState() const { poster, @@ -67,26 +63,26 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { } = torrent const cache = useUpdateCache(hash) - const cacheMap = useCreateCacheMap(cache) const settings = useGetSettings(cache) - const dropTorrent = () => axios.post(torrentsHost(), { action: 'drop', hash }) - const removeTorrentViews = () => - axios.post(viewedHost(), { action: 'rem', hash, file_index: -1 }).then(() => setViewedFileList()) - const preloadBuffer = fileId => fetch(`${streamHost()}?link=${hash}&index=${fileId}&preload`) - const getFileLink = (path, id) => - `${streamHost()}/${encodeURIComponent(path.split('\\').pop().split('/').pop())}?link=${hash}&index=${id}&play` - const fullPlaylistLink = `${playlistTorrHost()}/${encodeURIComponent(name || title || 'file')}.m3u?link=${hash}&m3u` - const partialPlaylistLink = `${fullPlaylistLink}&fromlast` - - const fileHasEpisodeText = !!playableFileList?.find(({ path }) => ptt.parse(path).episode) - const fileHasSeasonText = !!playableFileList?.find(({ path }) => ptt.parse(path).season) - const fileHasResolutionText = !!playableFileList?.find(({ path }) => ptt.parse(path).resolution) - const { Capacity, PiecesCount, PiecesLength, Filled } = cache useEffect(() => { - setPlayableFileList(torrentFileList?.filter(file => playableExtList.includes(getExt(file.path)))) + if (playableFileList && seasonAmount === null) { + const seasons = [] + playableFileList.forEach(({ path }) => { + const currentSeason = ptt.parse(path).season + if (currentSeason) { + !seasons.includes(currentSeason) && seasons.push(currentSeason) + } + }) + seasons.length && setSelectedSeason(seasons[0]) + setSeasonAmount(seasons.sort((a, b) => a - b)) + } + }, [playableFileList, seasonAmount]) + + useEffect(() => { + setPlayableFileList(torrentFileList?.filter(({ path }) => isFilePlayable(path))) }, [torrentFileList]) useEffect(() => { @@ -139,7 +135,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { Cache - + ) : ( @@ -166,62 +162,14 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { - {!isOnlyOnePlayableFile && !!viewedFileList?.length && ( - <> - Download Playlist - - Latest file played: {latestViewedFileData.title}. - {latestViewedFileData.season && ( - <> - {' '} - Season: {latestViewedFileData.season}. Episode: {latestViewedFileData.episode}. - - )} - - - - - - - - - - - - - )} - - Torrent State - - - - - - - Info - - - {(isOnlyOnePlayableFile || !viewedFileList?.length) && ( - - - - )} - - - - +
@@ -238,7 +186,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { /> - + + ))} + )} - */} + + + )} ) } - -function getExt(filename) { - const ext = filename.split('.').pop() - if (ext === filename) return '' - return ext.toLowerCase() -} -const playableExtList = [ - // video - '3g2', - '3gp', - 'aaf', - 'asf', - 'avchd', - 'avi', - 'drc', - 'flv', - 'iso', - 'm2v', - 'm2ts', - 'm4p', - 'm4v', - 'mkv', - 'mng', - 'mov', - 'mp2', - 'mp4', - 'mpe', - 'mpeg', - 'mpg', - 'mpv', - 'mxf', - 'nsv', - 'ogg', - 'ogv', - 'ts', - 'qt', - 'rm', - 'rmvb', - 'roq', - 'svi', - 'vob', - 'webm', - 'wmv', - 'yuv', - // audio - 'aac', - 'aiff', - 'ape', - 'au', - 'flac', - 'gsm', - 'it', - 'm3u', - 'm4a', - 'mid', - 'mod', - 'mp3', - 'mpa', - 'pls', - 'ra', - 's3m', - 'sid', - 'wav', - 'wma', - 'xm', -] diff --git a/web/src/components/DialogTorrentDetailsContent/style.js b/web/src/components/DialogTorrentDetailsContent/style.js index 6d0912a..8b83cd0 100644 --- a/web/src/components/DialogTorrentDetailsContent/style.js +++ b/web/src/components/DialogTorrentDetailsContent/style.js @@ -70,23 +70,9 @@ export const MainSection = styled.section` @media (max-width: 840px) { grid-template-columns: 1fr; } -` -export const MainSectionButtonGroup = styled.div` - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 20px; - - :not(:last-child) { - margin-bottom: 30px; - } - - @media (max-width: 1045px) { - grid-template-columns: repeat(2, 1fr); - } - - @media (max-width: 880px) { - grid-template-columns: 1fr; + @media (max-width: 800px) { + padding: 20px; } ` @@ -97,12 +83,20 @@ export const CacheSection = styled.section` align-content: start; grid-template-rows: min-content 1fr min-content; background: #88cdaa; + + @media (max-width: 800px) { + padding: 20px; + } ` export const TorrentFilesSection = styled.section` grid-area: file-list; padding: 40px; box-shadow: inset 3px 25px 8px -25px rgba(0, 0, 0, 0.5); + + @media (max-width: 800px) { + padding: 20px; + } ` export const SectionSubName = styled.div` @@ -159,7 +153,13 @@ export const WidgetWrapper = styled.div` } ` : css` - @media (max-width: 840px) { + @media (max-width: 800px) { + grid-template-columns: repeat(auto-fit, minmax(max-content, 185px)); + } + @media (max-width: 480px) { + grid-template-columns: 1fr 1fr; + } + @media (max-width: 390px) { grid-template-columns: 1fr; } `} @@ -252,77 +252,6 @@ export const Divider = styled.div` margin: 30px 0; ` -export const SmallLabel = styled.div` - ${({ mb }) => css` - ${mb && `margin-bottom: ${mb}px`}; - font-size: 20px; - font-weight: 300; - line-height: 1; - - @media (max-width: 800px) { - font-size: 18px; - ${mb && `margin-bottom: ${mb / 1.5}px`}; - } - `} -` - -export const Table = styled.table` - border-collapse: collapse; - margin: 25px 0; - font-size: 0.9em; - width: 100%; - border-radius: 5px 5px 0 0; - overflow: hidden; - box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); - - thead tr { - background: #009879; - color: #fff; - text-align: left; - text-transform: uppercase; - } - - th, - td { - padding: 12px 15px; - } - - tbody tr { - border-bottom: 1px solid #ddd; - - :last-of-type { - border-bottom: 2px solid #009879; - } - - &.viewed-file-row { - background: #f3f3f3; - } - } - - td { - &.viewed-file-indicator { - position: relative; - :before { - content: ''; - width: 10px; - height: 10px; - background: #15d5af; - border-radius: 50%; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } - } - - &.button-cell { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 10px; - } - } -` - export const DetailedViewWidgetSection = styled.section` padding: 40px; background: linear-gradient(145deg, #e4f6ed, #b5dec9); diff --git a/web/yarn.lock b/web/yarn.lock index a0bf6c7..4ab2be3 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -8065,7 +8065,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5, lodash@^4.7.0: +"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== From 0ccf0caf32baf243b70b4f19e16ed0c4df80a96c Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Tue, 1 Jun 2021 19:37:40 +0300 Subject: [PATCH 34/40] refactor --- .../DetailedView/index.jsx | 45 ++++++++++++++++ .../DetailedView/style.js | 19 +++++++ .../Table/index.jsx | 13 +++-- .../DialogTorrentDetailsContent/index.jsx | 52 +++++++------------ .../DialogTorrentDetailsContent/style.js | 18 ------- 5 files changed, 91 insertions(+), 56 deletions(-) create mode 100644 web/src/components/DialogTorrentDetailsContent/DetailedView/index.jsx create mode 100644 web/src/components/DialogTorrentDetailsContent/DetailedView/style.js diff --git a/web/src/components/DialogTorrentDetailsContent/DetailedView/index.jsx b/web/src/components/DialogTorrentDetailsContent/DetailedView/index.jsx new file mode 100644 index 0000000..a65a7e5 --- /dev/null +++ b/web/src/components/DialogTorrentDetailsContent/DetailedView/index.jsx @@ -0,0 +1,45 @@ +import { SectionTitle, WidgetWrapper } from '../style' +import { DetailedViewCacheSection, DetailedViewWidgetSection } from './style' +import TorrentCache from '../TorrentCache' +import { + SizeWidget, + PiecesLengthWidget, + StatusWidget, + PiecesCountWidget, + PeersWidget, + UploadSpeedWidget, + DownlodSpeedWidget, +} from '../widgets' + +export default function Test({ + downloadSpeed, + uploadSpeed, + torrent, + torrentSize, + PiecesCount, + PiecesLength, + statString, + cache, +}) { + return ( + <> + + Data + + + + + + + + + + + + + Cache + + + + ) +} diff --git a/web/src/components/DialogTorrentDetailsContent/DetailedView/style.js b/web/src/components/DialogTorrentDetailsContent/DetailedView/style.js new file mode 100644 index 0000000..3fd9431 --- /dev/null +++ b/web/src/components/DialogTorrentDetailsContent/DetailedView/style.js @@ -0,0 +1,19 @@ +import styled from 'styled-components' + +export const DetailedViewWidgetSection = styled.section` + padding: 40px; + background: linear-gradient(145deg, #e4f6ed, #b5dec9); + + @media (max-width: 800px) { + padding: 20px; + } +` + +export const DetailedViewCacheSection = styled.section` + padding: 40px; + box-shadow: inset 3px 25px 8px -25px rgba(0, 0, 0, 0.5); + + @media (max-width: 800px) { + padding: 20px; + } +` diff --git a/web/src/components/DialogTorrentDetailsContent/Table/index.jsx b/web/src/components/DialogTorrentDetailsContent/Table/index.jsx index ce2121e..87952d0 100644 --- a/web/src/components/DialogTorrentDetailsContent/Table/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/Table/index.jsx @@ -73,6 +73,7 @@ const Table = memo( })} + {playableFileList.map(({ id, path, length }) => { const { title, resolution, episode, season } = ptt.parse(path) @@ -84,12 +85,14 @@ const Table = memo(
{title}
-
-
viewed
-
-
+ {isViewed && ( +
+
viewed
+
+
+
-
+ )} {fileHasSeasonText && seasonAmount?.length === 1 && (
season
diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index ac4e15a..846dec5 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -12,9 +12,8 @@ import { useUpdateCache, useGetSettings } from './customHooks' import DialogHeader from './DialogHeader' import TorrentCache from './TorrentCache' import Table from './Table' +import DetailedView from './DetailedView' import { - DetailedViewWidgetSection, - DetailedViewCacheSection, DialogContentGrid, MainSection, Poster, @@ -27,20 +26,18 @@ import { TorrentFilesSection, Divider, } from './style' -import { - DownlodSpeedWidget, - UploadSpeedWidget, - PeersWidget, - SizeWidget, - PiecesCountWidget, - PiecesLengthWidget, - StatusWidget, -} from './widgets' +import { DownlodSpeedWidget, UploadSpeedWidget, PeersWidget, SizeWidget } from './widgets' import TorrentFunctions from './TorrentFunctions' import { isFilePlayable } from './helpers' const shortenText = (text, count) => text.slice(0, count) + (text.length > count ? '...' : '') +const Loader = () => ( +
+ +
+) + export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { const [isLoading, setIsLoading] = useState(true) const [isDetailedCacheView, setIsDetailedCacheView] = useState(false) @@ -115,29 +112,18 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
{isLoading ? ( -
- -
+ ) : isDetailedCacheView ? ( - <> - - Data - - - - - - - - - - - - - Cache - - - + ) : ( diff --git a/web/src/components/DialogTorrentDetailsContent/style.js b/web/src/components/DialogTorrentDetailsContent/style.js index 8b83cd0..69beb40 100644 --- a/web/src/components/DialogTorrentDetailsContent/style.js +++ b/web/src/components/DialogTorrentDetailsContent/style.js @@ -251,21 +251,3 @@ export const Divider = styled.div` background-color: rgba(0, 0, 0, 0.12); margin: 30px 0; ` - -export const DetailedViewWidgetSection = styled.section` - padding: 40px; - background: linear-gradient(145deg, #e4f6ed, #b5dec9); - - @media (max-width: 800px) { - padding: 20px; - } -` - -export const DetailedViewCacheSection = styled.section` - padding: 40px; - box-shadow: inset 3px 25px 8px -25px rgba(0, 0, 0, 0.5); - - @media (max-width: 800px) { - padding: 20px; - } -` From 8696f6cd0b218f9a8945058456e1d12295cf11b4 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Wed, 2 Jun 2021 08:47:30 +0300 Subject: [PATCH 35/40] torrent title and name are humanized --- .../DialogTorrentDetailsContent/index.jsx | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index 846dec5..2ba1871 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -102,6 +102,17 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { const bufferSize = settings?.PreloadBuffer ? Capacity : 33554432 // Default is 32mb if PreloadBuffer is false + const getTitle = value => { + const torrentParsedName = value && ptt.parse(value) + const newNameStrings = [] + + if (torrentParsedName?.title) newNameStrings.push(` ${torrentParsedName?.title}`) + if (torrentParsedName?.year) newNameStrings.push(`. ${torrentParsedName?.year}.`) + if (torrentParsedName?.resolution) newNameStrings.push(` (${torrentParsedName?.resolution})`) + + return newNameStrings.join(' ') + } + return ( <> {name && name !== title ? ( <> - {shortenText(name, 50)} + {shortenText(getTitle(name), 50)} {shortenText(title, 160)} ) : ( - {shortenText(title, 50)} + {shortenText(getTitle(title), 50)} )} @@ -189,8 +200,8 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { {seasonAmount?.length > 1 && ( <> - Select Season - + Select Season + {seasonAmount.map(season => (