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)} + + preloadBuffer(id)} variant='outlined' color='primary' size='small'> + Preload + + + + + Open link + + + + + + Copy link + + + + + ) + ) + })} + + + + {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)} + + + + preloadBuffer(id)} variant='outlined' color='primary' size='small'> + Preload + + + + + Open link + + + + + + Copy link + + + + + ) + ) + })} + + > + ) + }, + (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}. + > + )} + + + + + + full + + + + + + from latest file + + + + > + )} + Torrent State + + removeTorrentViews()} variant='contained' color='primary' size='large'> + remove views + + dropTorrent()} variant='contained' color='primary' size='large'> + drop torrent + + + Info + + {(isOnlyOnePlayableFile || !viewedFileList?.length) && ( + + + download playlist + + + )} + + + copy hash + + + + > + ) + }, + () => 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}. - > - )} - - - - - - full - - - - - - from latest file - - - - > - )} - - Torrent State - - - removeTorrentViews()} variant='contained' color='primary' size='large'> - remove views - - dropTorrent()} variant='contained' color='primary' size='large'> - drop torrent - - - - Info - - - {(isOnlyOnePlayableFile || !viewedFileList?.length) && ( - - - download playlist - - - )} - - - copy hash - - - + @@ -238,7 +186,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { /> - + - {/* + Torrent Content - {!playableFileList?.length ? ( - 'No playable files in this torrent' - ) : ( + {seasonAmount?.length > 1 && ( <> - - - - viewed - name - {fileHasSeasonText && 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 ( - - - {title} - {fileHasSeasonText && {season}} - {fileHasEpisodeText && {episode}} - {fileHasResolutionText && {resolution}} - {humanizeSize(length)} - - preloadBuffer(id)} variant='outlined' color='primary' size='small'> - Preload - - - - - Open link - - - - - - Copy link - - - - - ) - })} - - + Select Season + + {seasonAmount.map(season => ( + setSelectedSeason(season)} + > + {season} + + ))} + > )} - */} + + + )} > ) } - -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==