From f0a2ba83906d7dd7abd1ce2387d36ff89f940974 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Wed, 2 Jun 2021 19:19:41 +0300 Subject: [PATCH] torrent cards rewritten --- web/package.json | 1 + web/src/App.jsx | 34 ---- web/src/App/Sidebar.jsx | 60 ++++++ web/src/App/index.jsx | 66 +++++++ web/src/App/style.js | 57 ++++++ web/src/components/Appbar/index.jsx | 144 -------------- web/src/components/Appbar/useStyles.js | 65 ------ .../DialogTorrentDetailsContent/index.jsx | 4 +- web/src/components/TorrentCard/index.jsx | 59 +++--- web/src/components/TorrentCard/style.js | 187 ++++++++++++------ web/src/components/TorrentList.jsx | 22 +-- web/src/utils/Utils.js | 3 + web/yarn.lock | 5 + 13 files changed, 346 insertions(+), 361 deletions(-) delete mode 100644 web/src/App.jsx create mode 100644 web/src/App/Sidebar.jsx create mode 100644 web/src/App/index.jsx create mode 100644 web/src/App/style.js delete mode 100644 web/src/components/Appbar/index.jsx delete mode 100644 web/src/components/Appbar/useStyles.js diff --git a/web/package.json b/web/package.json index 0aa4c5e..7984c45 100644 --- a/web/package.json +++ b/web/package.json @@ -14,6 +14,7 @@ "parse-torrent-title": "^1.3.0", "react": "^17.0.2", "react-copy-to-clipboard": "^5.0.3", + "react-div-100vh": "^0.6.0", "react-dom": "^17.0.2", "react-konva": "^17.0.2-4", "react-measure": "^2.5.2", diff --git a/web/src/App.jsx b/web/src/App.jsx deleted file mode 100644 index 7286c36..0000000 --- a/web/src/App.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import CssBaseline from '@material-ui/core/CssBaseline' -import { createMuiTheme, MuiThemeProvider } from '@material-ui/core' - -import Appbar from './components/Appbar/index' - -const baseTheme = createMuiTheme({ - overrides: { - MuiCssBaseline: { - '@global': { - html: { - WebkitFontSmoothing: 'auto', - }, - }, - }, - }, - palette: { - primary: { - main: '#3fb57a', - }, - secondary: { - main: '#FFA724', - }, - tonalOffset: 0.2, - }, -}) - -export default function App() { - return ( - - - - - ) -} diff --git a/web/src/App/Sidebar.jsx b/web/src/App/Sidebar.jsx new file mode 100644 index 0000000..5d6edac --- /dev/null +++ b/web/src/App/Sidebar.jsx @@ -0,0 +1,60 @@ +import { playlistAllHost, shutdownHost } from 'utils/Hosts' +import Divider from '@material-ui/core/Divider' +import ListItem from '@material-ui/core/ListItem' +import ListItemIcon from '@material-ui/core/ListItemIcon' +import ListItemText from '@material-ui/core/ListItemText' +import AddDialogButton from 'components/Add' +import RemoveAll from 'components/RemoveAll' +import SettingsDialog from 'components/Settings' +import AboutDialog from 'components/About' +import UploadDialog from 'components/Upload' +import { + CreditCard as CreditCardIcon, + List as ListIcon, + PowerSettingsNew as PowerSettingsNewIcon, +} from '@material-ui/icons' +import List from '@material-ui/core/List' + +import { AppSidebarStyle } from './style' + +export default function Sidebar({ isDrawerOpen, setIsDonationDialogOpen }) { + return ( + + + + + + + + + + + + + + + + + + + fetch(shutdownHost())}> + + + + + + + + + + + setIsDonationDialogOpen(true)}> + + + + + + + + ) +} diff --git a/web/src/App/index.jsx b/web/src/App/index.jsx new file mode 100644 index 0000000..35bf94b --- /dev/null +++ b/web/src/App/index.jsx @@ -0,0 +1,66 @@ +import CssBaseline from '@material-ui/core/CssBaseline' +import { createMuiTheme, MuiThemeProvider } from '@material-ui/core' +import { useEffect, useState } from 'react' +import Typography from '@material-ui/core/Typography' +import IconButton from '@material-ui/core/IconButton' +import { Menu as MenuIcon, Close as CloseIcon } from '@material-ui/icons' +import { getTorrServerHost } from 'utils/Hosts' +import TorrentList from 'components/TorrentList' +import DonateSnackbar from 'components/Donate' +import DonateDialog from 'components/Donate/DonateDialog' +import Div100vh from 'react-div-100vh' + +import { AppWrapper, AppHeader } from './style' +import Sidebar from './Sidebar' + +const baseTheme = createMuiTheme({ + overrides: { MuiCssBaseline: { '@global': { html: { WebkitFontSmoothing: 'auto' } } } }, + palette: { primary: { main: '#3fb57a' }, secondary: { main: '#FFA724' }, tonalOffset: 0.2 }, +}) + +export default function App() { + const [isDrawerOpen, setIsDrawerOpen] = useState(false) + const [isDonationDialogOpen, setIsDonationDialogOpen] = useState(false) + const [tsVersion, setTSVersion] = useState('') + + useEffect(() => { + fetch(`${getTorrServerHost()}/echo`) + .then(resp => resp.text()) + .then(txt => { + if (!txt.startsWith('')) setTSVersion(txt) + }) + }, [isDrawerOpen]) + + return ( + + + + {/* Div100vh - iOS WebKit fix */} + + + + setIsDrawerOpen(!isDrawerOpen)} + edge='start' + > + {isDrawerOpen ? : } + + + + TorrServer {tsVersion} + + + + + + + + {isDonationDialogOpen && setIsDonationDialogOpen(false)} />} + {!JSON.parse(localStorage.getItem('snackbarIsClosed')) && } + + + + ) +} diff --git a/web/src/App/style.js b/web/src/App/style.js new file mode 100644 index 0000000..bf02027 --- /dev/null +++ b/web/src/App/style.js @@ -0,0 +1,57 @@ +import styled, { css } from 'styled-components' + +export const AppWrapper = styled.div` + height: 100%; + display: grid; + grid-template-columns: 60px 1fr; + grid-template-rows: 60px 1fr; + grid-template-areas: + 'head head' + 'side content'; +` +export const AppHeader = styled.div` + background: #3fb57a; + color: rgba(0, 0, 0, 0.87); + grid-area: head; + display: flex; + align-items: center; + box-shadow: 0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%); + padding: 0 24px; + z-index: 3; +` +export const AppSidebarStyle = styled.div` + ${({ isDrawerOpen }) => css` + grid-area: side; + width: ${isDrawerOpen ? '400%' : '100%'}; + z-index: 2; + overflow-x: hidden; + transition: width 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms; + border-right: 1px solid rgba(0, 0, 0, 0.12); + background: #fff; + white-space: nowrap; + `} +` +export const TorrentListWrapper = styled.div` + grid-area: content; + padding: 20px; + overflow: auto; + + display: grid; + place-content: start; + grid-template-columns: repeat(auto-fit, minmax(max-content, 570px)); + gap: 20px; + + @media (max-width: 1260px), (max-height: 500px) { + padding: 10px; + gap: 15px; + grid-template-columns: repeat(3, 1fr); + } + + @media (max-width: 1100px) { + grid-template-columns: repeat(2, 1fr); + } + + @media (max-width: 700px) { + grid-template-columns: 1fr; + } +` diff --git a/web/src/components/Appbar/index.jsx b/web/src/components/Appbar/index.jsx deleted file mode 100644 index a4f1eec..0000000 --- a/web/src/components/Appbar/index.jsx +++ /dev/null @@ -1,144 +0,0 @@ -import { useEffect, useState } from 'react' -import clsx from 'clsx' -import { useTheme } from '@material-ui/core/styles' -import Drawer from '@material-ui/core/Drawer' -import AppBar from '@material-ui/core/AppBar' -import Toolbar from '@material-ui/core/Toolbar' -import List from '@material-ui/core/List' -import Typography from '@material-ui/core/Typography' -import Divider from '@material-ui/core/Divider' -import IconButton from '@material-ui/core/IconButton' -import MenuIcon from '@material-ui/icons/Menu' -import ChevronLeftIcon from '@material-ui/icons/ChevronLeft' -import ChevronRightIcon from '@material-ui/icons/ChevronRight' -import ListItem from '@material-ui/core/ListItem' -import ListItemIcon from '@material-ui/core/ListItemIcon' -import ListItemText from '@material-ui/core/ListItemText' -import CreditCardIcon from '@material-ui/icons/CreditCard' -import ListIcon from '@material-ui/icons/List' -import PowerSettingsNewIcon from '@material-ui/icons/PowerSettingsNew' -import { playlistAllHost, shutdownHost, getTorrServerHost } from 'utils/Hosts' -import TorrentList from 'components/TorrentList' -import AddDialogButton from 'components/Add' -import RemoveAll from 'components/RemoveAll' -import SettingsDialog from 'components/Settings' -import AboutDialog from 'components/About' -import DonateSnackbar from 'components/Donate' -import DonateDialog from 'components/Donate/DonateDialog' -import UploadDialog from 'components/Upload' - -import useStyles from './useStyles' - -export default function MiniDrawer() { - const classes = useStyles() - const theme = useTheme() - const [isDrawerOpen, setIsDrawerOpen] = useState(false) - const [isDonationDialogOpen, setIsDonationDialogOpen] = useState(false) - const [tsVersion, setTSVersion] = useState('') - - const handleDrawerOpen = () => setIsDrawerOpen(true) - const handleDrawerClose = () => setIsDrawerOpen(false) - - useEffect(() => { - fetch(`${getTorrServerHost()}/echo`) - .then(resp => resp.text()) - .then(txt => { - if (!txt.startsWith('')) setTSVersion(txt) - }) - }, [isDrawerOpen]) - - return ( -
- - - - - - - TorrServer {tsVersion} - - - - - -
- - {theme.direction === 'rtl' ? : } - -
- - - - - - - - - - - - - - - - - - - - - fetch(shutdownHost())}> - - - - - - - - - - - setIsDonationDialogOpen(true)}> - - - - - - -
- -
-
- - -
- - {isDonationDialogOpen && setIsDonationDialogOpen(false)} />} - {!JSON.parse(localStorage.getItem('snackbarIsClosed')) && } -
- ) -} diff --git a/web/src/components/Appbar/useStyles.js b/web/src/components/Appbar/useStyles.js deleted file mode 100644 index d46b1d0..0000000 --- a/web/src/components/Appbar/useStyles.js +++ /dev/null @@ -1,65 +0,0 @@ -import { makeStyles } from '@material-ui/core/styles' - -const drawerWidth = 240 - -export default makeStyles(theme => ({ - root: { - display: 'flex', - }, - appBar: { - zIndex: theme.zIndex.drawer + 1, - transition: theme.transitions.create(['width', 'margin'], { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, - }), - }, - appBarShift: { - marginLeft: drawerWidth, - width: `calc(100% - ${drawerWidth}px)`, - transition: theme.transitions.create(['width', 'margin'], { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.enteringScreen, - }), - }, - menuButton: { - marginRight: 36, - }, - hide: { - display: 'none', - }, - drawer: { - width: drawerWidth, - flexShrink: 1, - whiteSpace: 'nowrap', - }, - drawerOpen: { - width: drawerWidth, - transition: theme.transitions.create('width', { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.enteringScreen, - }), - }, - drawerClose: { - transition: theme.transitions.create('width', { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, - }), - overflowX: 'hidden', - width: theme.spacing(7) + 1, - [theme.breakpoints.up('sm')]: { - width: theme.spacing(9) + 1, - }, - }, - toolbar: { - display: 'flex', - alignItems: 'center', - justifyContent: 'flex-end', - padding: theme.spacing(0, 1), - // necessary for content to be below app bar - ...theme.mixins.toolbar, - }, - content: { - flexGrow: 1, - padding: theme.spacing(3), - }, -})) diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index 2ba1871..4f6eb26 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -1,5 +1,5 @@ import { NoImageIcon } from 'icons' -import { humanizeSize } from 'utils/Utils' +import { humanizeSize, shortenText } from 'utils/Utils' import { useEffect, useState } from 'react' import { Button, ButtonGroup } from '@material-ui/core' import ptt from 'parse-torrent-title' @@ -30,8 +30,6 @@ import { DownlodSpeedWidget, UploadSpeedWidget, PeersWidget, SizeWidget } from ' import TorrentFunctions from './TorrentFunctions' import { isFilePlayable } from './helpers' -const shortenText = (text, count) => text.slice(0, count) + (text.length > count ? '...' : '') - const Loader = () => (
diff --git a/web/src/components/TorrentCard/index.jsx b/web/src/components/TorrentCard/index.jsx index 331a87f..022216c 100644 --- a/web/src/components/TorrentCard/index.jsx +++ b/web/src/components/TorrentCard/index.jsx @@ -1,9 +1,7 @@ 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 { UnfoldMore as UnfoldMoreIcon, Close as CloseIcon, Delete as DeleteIcon } from '@material-ui/icons' +import { getPeerString, humanizeSize, shortenText } from 'utils/Utils' import { torrentsHost } from 'utils/Hosts' import { NoImageIcon } from 'icons' import DialogTorrentDetailsContent from 'components/DialogTorrentDetailsContent' @@ -12,16 +10,7 @@ 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' +import { StyledButton, TorrentCard, TorrentCardButtons, TorrentCardDescription, TorrentCardPoster } from './style' const Transition = forwardRef((props, ref) => ) @@ -51,7 +40,7 @@ export default function Torrent({ torrent }) { - + Details @@ -67,31 +56,29 @@ export default function Torrent({ torrent }) { - - Name - {title || name} - +
+
Name
+
{shortenText(title || name, 100)}
+
- - - Size - - {torrentSize > 0 && humanizeSize(torrentSize)} - - +
+
+
Size
+
{torrentSize > 0 && humanizeSize(torrentSize)}
+
- - Speed - +
+
Speed
+
{downloadSpeed > 0 ? humanizeSize(downloadSpeed) : '---'} - - +
+
- - Peers - {getPeerString(torrent) || '---'} - - +
+
Peers
+
{getPeerString(torrent) || '---'}
+
+
diff --git a/web/src/components/TorrentCard/style.js b/web/src/components/TorrentCard/style.js index a8c242c..bd76160 100644 --- a/web/src/components/TorrentCard/style.js +++ b/web/src/components/TorrentCard/style.js @@ -4,22 +4,26 @@ export const TorrentCard = styled.div` border: 1px solid; border-radius: 5px; display: grid; - grid-template-columns: repeat(2, 1fr); - grid-template-rows: 175px minmax(min-content, 1fr); - grid-template-areas: - 'poster buttons' - 'description description'; + grid-template-columns: 120px 260px 1fr; + grid-template-rows: 180px; + grid-template-areas: 'poster description buttons'; gap: 10px; padding: 10px; background: #3fb57a; box-shadow: 0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%); - @media (max-width: 600px), (max-height: 500px) { + @media (max-width: 1260px), (max-height: 500px) { grid-template-areas: 'poster description' 'buttons buttons'; - grid-template-columns: 25% 1fr; - grid-template-rows: 100px min-content; + + grid-template-columns: 70px 1fr; + grid-template-rows: 110px max-content; + } + + @media (max-width: 770px) { + grid-template-columns: 60px 1fr; + grid-template-rows: 90px max-content; } ` @@ -33,7 +37,9 @@ export const TorrentCardPoster = styled.div` isPoster ? css` img { + width: 100%; height: 100%; + object-fit: cover; border-radius: 5px; } ` @@ -41,26 +47,27 @@ export const TorrentCardPoster = styled.div` display: grid; place-items: center; background: #74c39c; - border: 1px solid; + border: 1px solid #337a57; svg { transform: translateY(-3px); } `}; - @media (max-width: 600px), (max-height: 500px) { + @media (max-width: 1260px), (max-height: 500px) { svg { width: 50%; } } ` + export const TorrentCardButtons = styled.div` grid-area: buttons; display: grid; gap: 5px; - @media (max-width: 600px), (max-height: 500px) { - grid-template-columns: repeat(4, 1fr); + @media (max-width: 1260px), (max-height: 500px) { + grid-template-columns: repeat(3, 1fr); } ` export const TorrentCardDescription = styled.div` @@ -68,43 +75,101 @@ export const TorrentCardDescription = styled.div` background: #74c39c; border-radius: 5px; padding: 5px; - word-break: break-word; + display: grid; + grid-template-rows: 55% 1fr; + gap: 10px; - @media (max-width: 600px), (max-height: 500px) { + @media (max-width: 770px) { + grid-template-rows: 60% 1fr; + gap: 3px; + } + + @media (max-width: 770px) { + grid-template-rows: 56% 1fr; + } + + .description-title-wrapper { display: flex; flex-direction: column; - justify-content: space-between; - } -` - -export const TorrentCardDescriptionLabel = styled.div` - text-transform: uppercase; - font-size: 10px; - font-weight: 500; - letter-spacing: 0.4px; - color: #216e47; -` - -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; - margin-bottom: 3px; - margin-left: 0; - - ${({ isTitle }) => - isTitle && - css` - overflow: auto; - height: 45px; - `} } - @media (max-width: 410px) { + .description-section-name { + text-transform: uppercase; font-size: 10px; + font-weight: 500; + letter-spacing: 0.4px; + color: #216e47; + + @media (max-width: 770px) { + font-size: 0.4rem; + } + } + + .description-torrent-title { + overflow: auto; + word-break: break-all; + + @media (max-width: 770px) { + font-size: 0.6rem; + } + + @media (max-width: 600px), (max-height: 500px) { + font-size: 11px; + margin-bottom: 3px; + margin-left: 0; + } + + @media (max-width: 410px) { + font-size: 10px; + } + } + + .description-statistics-wrapper { + display: grid; + grid-template-columns: 80px 80px 1fr; + align-self: end; + + @media (max-width: 1260px), (max-height: 500px) { + grid-template-columns: 70px 70px 1fr; + } + + @media (max-width: 770px) { + grid-template-columns: 65px 65px 1fr; + } + + @media (max-width: 600px), (max-height: 500px) { + display: grid; + grid-template-columns: repeat(3, 1fr); + } + } + + .description-statistics-element-wrapper { + } + + .description-statistics-element-value { + margin-left: 5px; + margin-bottom: 10px; + word-break: break-all; + + @media (max-width: 1260px), (max-height: 500px) { + font-size: 0.7rem; + margin-bottom: 0; + margin-left: 0; + } + + @media (max-width: 770px) { + font-size: 0.6rem; + } + + @media (max-width: 600px), (max-height: 500px) { + font-size: 11px; + margin-bottom: 3px; + margin-left: 0; + } + + @media (max-width: 410px) { + font-size: 10px; + } } ` @@ -121,41 +186,39 @@ export const StyledButton = styled.button` font-size: 1rem; font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif; letter-spacing: 0.009em; + padding: 10px 20px; + + :hover { + background: #2a7e54; + } > :first-child { margin-right: 10px; } - @media (max-width: 600px), (max-height: 500px) { - padding: 5px 0; + @media (max-width: 1260px), (max-height: 500px) { + padding: 5px 10px; font-size: 0.8rem; - justify-content: center; - - span { - display: none; - } svg { width: 20px; } - - > :first-child { - margin-right: 0; - } } @media (max-width: 500px) { font-size: 0.7rem; + + svg { + width: 15px; + } } - :hover { - background: #2a7e54; - } -` + @media (max-width: 420px) { + padding: 7px 10px; + justify-content: center; -export const TorrentCardDetails = styled.div` - @media (max-width: 600px), (max-height: 500px) { - display: grid; - grid-template-columns: repeat(3, 1fr); + svg { + display: none; + } } ` diff --git a/web/src/components/TorrentList.jsx b/web/src/components/TorrentList.jsx index 35fd993..0209f4c 100644 --- a/web/src/components/TorrentList.jsx +++ b/web/src/components/TorrentList.jsx @@ -5,24 +5,10 @@ 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; - grid-template-columns: repeat(auto-fit, 350px); - gap: 30px; - - @media (max-width: 600px), (max-height: 500px) { - gap: 10px; - grid-template-columns: repeat(auto-fit, 310px); - } - - @media (max-width: 410px) { - grid-template-columns: minmax(min-content, 290px); - } -` +import { TorrentListWrapper } from 'App/style' const CenteredGrid = styled.div` - height: 75vh; + height: 100%; display: grid; place-items: center; ` @@ -63,7 +49,9 @@ export default function TorrentList() { Offline ) : !torrents.length ? ( - No torrents added + + No torrents added + ) : ( {torrents.map(torrent => ( diff --git a/web/src/utils/Utils.js b/web/src/utils/Utils.js index 766489f..a26095b 100644 --- a/web/src/utils/Utils.js +++ b/web/src/utils/Utils.js @@ -8,3 +8,6 @@ export function getPeerString(torrent) { if (!torrent || !torrent.connected_seeders) return '' return `[${torrent.connected_seeders}] ${torrent.active_peers} / ${torrent.total_peers}` } + +export const shortenText = (text, sympolAmount) => + text.slice(0, sympolAmount) + (text.length > sympolAmount ? '...' : '') diff --git a/web/yarn.lock b/web/yarn.lock index 4ab2be3..96ed686 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -10282,6 +10282,11 @@ react-dev-utils@^11.0.3: strip-ansi "6.0.0" text-table "0.2.0" +react-div-100vh@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/react-div-100vh/-/react-div-100vh-0.6.0.tgz#577972d8ac17693edcd44061c1a4b5a7578e49ec" + integrity sha512-ErV0VTNXUd8jZqofC0ExZr5u+XDD2kN2te4SbwtqsyTm0UOjVYu53kP+FalGQrTe+DoMG8VYR2dITcAFu7c/5w== + react-dom@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"