diff --git a/web/package.json b/web/package.json index 67a8c88..83794af 100644 --- a/web/package.json +++ b/web/package.json @@ -23,6 +23,7 @@ "react-measure": "^2.5.2", "react-query": "^3.17.0", "react-scripts": "4.0.3", + "react-swipeable-views": "^0.14.0", "styled-components": "^5.3.0", "uuid": "^8.3.2" }, diff --git a/web/src/components/Add/AddDialog.jsx b/web/src/components/Add/AddDialog.jsx index 69431d9..fd67730 100644 --- a/web/src/components/Add/AddDialog.jsx +++ b/web/src/components/Add/AddDialog.jsx @@ -12,9 +12,10 @@ import usePreviousState from 'utils/usePreviousState' import { useQuery } from 'react-query' import { getTorrents } from 'utils/Utils' import parseTorrent from 'parse-torrent' +import { ButtonWrapper, Header } from 'style/DialogStyles' import { checkImageURL, getMoviePosters, chechTorrentSource, parseTorrentTitle } from './helpers' -import { ButtonWrapper, Content, Header } from './style' +import { Content } from './style' import RightSideComponent from './RightSideComponent' import LeftSideComponent from './LeftSideComponent' @@ -219,14 +220,7 @@ export default function AddDialog({ } return ( - +
{t(isEditMode ? 'EditTorrent' : 'AddNewTorrent')}
diff --git a/web/src/components/Add/style.js b/web/src/components/Add/style.js index d29809e..76fdbc1 100644 --- a/web/src/components/Add/style.js +++ b/web/src/components/Add/style.js @@ -1,19 +1,6 @@ import { Button } from '@material-ui/core' import styled, { css } from 'styled-components' -export const Header = styled.div` - ${({ theme: { primary } }) => css` - background: ${primary}; - color: rgba(0, 0, 0, 0.87); - font-size: 20px; - color: #fff; - font-weight: 600; - 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: 15px 24px; - position: relative; - `} -` - export const Content = styled.div` ${({ isEditMode, @@ -349,13 +336,3 @@ export const PosterLanguageSwitch = styled.div` } `} ` - -export const ButtonWrapper = styled.div` - padding: 20px; - display: flex; - justify-content: flex-end; - - > :not(:last-child) { - margin-right: 10px; - } -` diff --git a/web/src/components/Settings/SettingsDialog.jsx b/web/src/components/Settings/SettingsDialog.jsx index fc7e8d4..457d7d1 100644 --- a/web/src/components/Settings/SettingsDialog.jsx +++ b/web/src/components/Settings/SettingsDialog.jsx @@ -5,14 +5,200 @@ import DialogContent from '@material-ui/core/DialogContent' import TextField from '@material-ui/core/TextField' import DialogActions from '@material-ui/core/DialogActions' import Button from '@material-ui/core/Button' -import { FormControlLabel, InputLabel, Select, Switch } from '@material-ui/core' +import Checkbox from '@material-ui/core/Checkbox' +import { + ButtonGroup, + FormControlLabel, + Grid, + Input, + InputLabel, + Select, + Slider, + Switch, + useMediaQuery, + useTheme, +} from '@material-ui/core' import { settingsHost, setTorrServerHost, getTorrServerHost } from 'utils/Hosts' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' +import { Header } from 'style/DialogStyles' +import AppBar from '@material-ui/core/AppBar' +import Tabs from '@material-ui/core/Tabs' +import Tab from '@material-ui/core/Tab' +import SwipeableViews from 'react-swipeable-views' +import styled, { css } from 'styled-components' +import { USBIcon, RAMIcon } from 'icons' + +const FooterSection = styled.div` + padding: 20px; + display: flex; + align-items: center; + justify-content: space-between; + background: #e8e5eb; + + > :last-child > :not(:last-child) { + margin-right: 10px; + } +` +const Divider = styled.div` + height: 1px; + background-color: rgba(0, 0, 0, 0.12); + margin: 30px 0; +` + +const PreloadCachePercentage = styled.div.attrs( + ({ + value, + // theme: { + // dialogTorrentDetailsContent: { gradientEndColor }, + // }, + }) => { + const gradientStartColor = 'lightblue' + const gradientEndColor = 'orangered' + + return { + // this block is here according to styled-components recomendation about fast changable components + style: { + background: `linear-gradient(to right, ${gradientEndColor} 0%, ${gradientEndColor} ${value}%, ${gradientStartColor} ${value}%, ${gradientStartColor} 100%)`, + }, + } + }, +)` + ${({ label, isPreloadEnabled }) => css` + border: 1px solid; + padding: 10px 20px; + border-radius: 5px; + color: #000; + margin-bottom: 10px; + position: relative; + + :before { + content: '${label}'; + display: grid; + place-items: center; + font-size: 20px; + } + + ${isPreloadEnabled && + css` + :after { + content: ''; + width: 100%; + height: 3px; + background: green; + position: absolute; + bottom: 0; + left: 0; + } + `} + `} +` + +const PreloadCacheValue = styled.div` + ${({ color }) => css` + display: grid; + grid-template-columns: max-content 100px 1fr; + gap: 10px; + align-items: center; + + :not(:last-child) { + margin-bottom: 5px; + } + + :before { + content: ''; + background: ${color}; + width: 15px; + height: 15px; + border-radius: 50%; + } + `} +` + +const a11yProps = index => ({ + id: `full-width-tab-${index}`, + 'aria-controls': `full-width-tabpanel-${index}`, +}) + +const TabPanel = ({ children, value, index, ...other }) => ( + +) + +const MainSettingsContent = styled.div` + min-height: 500px; + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 40px; + padding: 20px; + + @media (max-width: 930px) { + grid-template-columns: 1fr; + } +` +const SecondarySettingsContent = styled.div` + min-height: 500px; + padding: 20px; +` + +const StorageButton = styled.div` + display: grid; + place-items: center; + gap: 10px; +` + +const StorageIconWrapper = styled.div` + ${({ selected }) => css` + width: 150px; + height: 150px; + border-radius: 50%; + background: ${selected ? 'blue' : 'lightgray'}; + transition: 0.2s; + + ${!selected && + css` + cursor: pointer; + + :hover { + background: orangered; + } + `} + + svg { + transform: rotate(-45deg) scale(0.75); + } + `} +` + +const CacheSizeSettings = styled.div`` +const CacheStorageSelector = styled.div` + display: grid; + grid-template-rows: max-content 1fr; + grid-template-columns: 1fr 1fr; + grid-template-areas: 'label label'; + place-items: center; + + @media (max-width: 930px) { + grid-template-columns: repeat(2, max-content); + column-gap: 30px; + } +` + +const CacheStorageSettings = styled.div`` + +const SettingSection = styled.section`` +const SettingLabel = styled.div`` +const SettingSectionLabel = styled.div` + font-size: 25px; + padding-bottom: 20px; +` export default function SettingsDialog({ handleClose }) { const { t } = useTranslation() + const fullScreen = useMediaQuery('@media (max-width:930px)') + const [settings, setSets] = useState({}) const [show, setShow] = useState(false) const [tsHost, setTSHost] = useState(getTorrServerHost()) @@ -83,9 +269,337 @@ export default function SettingsDialog({ handleClose }) { RemoveCacheOnDrop, } = settings + const { direction } = useTheme() + const [selectedTab, setSelectedTab] = useState(0) + + const handleChange = (_, newValue) => setSelectedTab(newValue) + + const handleChangeIndex = index => setSelectedTab(index) + + const [cacheSize, setCacheSize] = useState(96) + const [cachePercentage, setCachePercentage] = useState(95) + const [isProMode, setIsProMode] = useState(JSON.parse(localStorage.getItem('isProMode')) || false) + const [isRamSelected, setIsRamSelected] = useState(true) + + const handleSliderChange = (_, newValue) => { + setCacheSize(newValue) + } + + const handleBlur = ({ target: { value } }) => { + if (value < 32) return setCacheSize(32) + if (value > 20000) return setCacheSize(20000) + + setCacheSize(Math.round(value / 8) * 8) + } + + const handleInputChange = ({ target: { value } }) => { + setCacheSize(value === '' ? '' : Number(value)) + } + return ( - - {t('Settings')} + +
{t('Settings')}
+ + <> + + + + + + + + + + + + + Настройки кеша + + + + +
+ {100 - cachePercentage}% ({Math.round((cacheSize / 100) * (100 - cachePercentage))} МБ) +
+
От кеша будет оставаться позади воспроизводимого блока
+
+ + +
+ {cachePercentage}% ({Math.round((cacheSize / 100) * cachePercentage)} МБ) +
+
От кеша будет спереди от воспроизводимого блока
+
+ + + + + Размер кеша + + + + + + + {isProMode && ( + + + + )} + + + + + Кеш предзагрузки + + + + setCachePercentage(newValue)} + /> + + + {isProMode && ( + + setCachePercentage(value === '' ? '' : Number(value))} + onBlur={({ target: { value } }) => { + if (value < 0) return setCachePercentage(0) + if (value > 100) return setCachePercentage(100) + }} + style={{ width: '65px' }} + inputProps={{ + min: 0, + max: 100, + type: 'number', + }} + /> + + )} + + + + + } + label={t('PreloadBuffer')} + /> + +
+ + {isRamSelected ? ( + + + Место хранения кеша + + + + + + +
Оперативная память
+
+ + + setIsRamSelected(false)}> + + +
Диск
+
+
+ ) : ( + + Место хранения кеша + + + + + + + + + } + label={t('RemoveCacheOnDrop')} + /> + {t('RemoveCacheOnDropDesc')} + + + )} +
+
+ + + + Дополнительные настройки + + } + label={t('EnableIPv6')} + /> +
+ } + label={t('TCP')} + /> +
+ } + label={t('UTP')} + /> +
+ } + label={t('PEX')} + /> +
+ } + label={t('ForceEncrypt')} + /> +
+ +
+ +
+ } + label={t('DHT')} + /> +
+ +
+ +
+ } + label={t('Upload')} + /> +
+ +
+ +
+ } + label={t('UPNP')} + /> +
+ {t('RetrackersMode')} + +
+
+
+
+ + {/* {t('Settings')} {t('Save')} - + */} + + { + setIsProMode(checked) + localStorage.setItem('isProMode', checked) + if (!checked) setSelectedTab(0) + }} + color='primary' + /> + } + label='Pro mode' + /> + +
+ + + +
+
) } diff --git a/web/src/icons/index.jsx b/web/src/icons/index.jsx index 914e109..79caaea 100644 --- a/web/src/icons/index.jsx +++ b/web/src/icons/index.jsx @@ -52,6 +52,116 @@ export const AddItemIcon = () => { ) } +export const USBIcon = ({ color, width }) => { + const primary = useTheme().palette.primary.main + + return ( + + + + + + + ) +} + +export const RAMIcon = ({ color, width }) => { + const primary = useTheme().palette.primary.main + + return ( + + + + + + + + + + + + + + + ) +} + export const TorrentIcon = () => { const primary = useTheme().palette.primary.main const secondaryColor = primary === '#00a572' ? '#74c39c' : '#4a5255' diff --git a/web/src/style/DialogStyles.js b/web/src/style/DialogStyles.js new file mode 100644 index 0000000..6491812 --- /dev/null +++ b/web/src/style/DialogStyles.js @@ -0,0 +1,24 @@ +import styled, { css } from 'styled-components' + +export const Header = styled.div` + ${({ theme: { primary } }) => css` + background: ${primary}; + color: rgba(0, 0, 0, 0.87); + font-size: 20px; + color: #fff; + font-weight: 600; + 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: 15px 24px; + position: relative; + `} +` + +export const ButtonWrapper = styled.div` + padding: 20px; + display: flex; + justify-content: flex-end; + + > :not(:last-child) { + margin-right: 10px; + } +` diff --git a/web/yarn.lock b/web/yarn.lock index 0fa0ddd..12bc69a 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -1167,6 +1167,13 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" +"@babel/runtime@7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0.tgz#adeb78fedfc855aa05bc041640f3f6f98e85424c" + integrity sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA== + dependencies: + regenerator-runtime "^0.12.0" + "@babel/runtime@7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.1.tgz#b4116a6b6711d010b2dad3b7b6e43bf1b9954740" @@ -7894,6 +7901,11 @@ just-debounce@^1.0.0: resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.1.0.tgz#2f81a3ad4121a76bc7cb45dbf704c0d76a8e5ddf" integrity sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ== +keycode@^2.1.7: + version "2.2.0" + resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04" + integrity sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ= + killable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" @@ -8158,7 +8170,7 @@ loglevel@^1.6.8: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== -loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -10207,7 +10219,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -10451,6 +10463,15 @@ react-error-overlay@^6.0.9: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew== +react-event-listener@^0.6.0: + version "0.6.6" + resolved "https://registry.yarnpkg.com/react-event-listener/-/react-event-listener-0.6.6.tgz#758f7b991cad9086dd39fd29fad72127e1d8962a" + integrity sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw== + dependencies: + "@babel/runtime" "^7.2.0" + prop-types "^15.6.0" + warning "^4.0.1" + react-i18next@^11.10.0: version "11.10.0" resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.10.0.tgz#f34257447e18e710e36d8fd3f721dd7d37c7004f" @@ -10559,6 +10580,37 @@ react-scripts@4.0.3: optionalDependencies: fsevents "^2.1.3" +react-swipeable-views-core@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/react-swipeable-views-core/-/react-swipeable-views-core-0.14.0.tgz#6ac443a7cc7bc5ea022fbd549292bb5fff361cce" + integrity sha512-0W/e9uPweNEOSPjmYtuKSC/SvKKg1sfo+WtPdnxeLF3t2L82h7jjszuOHz9C23fzkvLfdgkaOmcbAxE9w2GEjA== + dependencies: + "@babel/runtime" "7.0.0" + warning "^4.0.1" + +react-swipeable-views-utils@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/react-swipeable-views-utils/-/react-swipeable-views-utils-0.14.0.tgz#6b76e251906747482730c22002fe47ab1014ba32" + integrity sha512-W+fXBOsDqgFK1/g7MzRMVcDurp3LqO3ksC8UgInh2P/tKgb5DusuuB1geKHFc6o1wKl+4oyER4Zh3Lxmr8xbXA== + dependencies: + "@babel/runtime" "7.0.0" + keycode "^2.1.7" + prop-types "^15.6.0" + react-event-listener "^0.6.0" + react-swipeable-views-core "^0.14.0" + shallow-equal "^1.2.1" + +react-swipeable-views@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/react-swipeable-views/-/react-swipeable-views-0.14.0.tgz#149c0df3d92220cc89e3f6d5c04a78dfe46f9b54" + integrity sha512-wrTT6bi2nC3JbmyNAsPXffUXLn0DVT9SbbcFr36gKpbaCgEp7rX/OFxsu5hPc/NBsUhHyoSRGvwqJNNrWTwCww== + dependencies: + "@babel/runtime" "7.0.0" + prop-types "^15.5.4" + react-swipeable-views-core "^0.14.0" + react-swipeable-views-utils "^0.14.0" + warning "^4.0.1" + react-transition-group@^4.4.0: version "4.4.1" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" @@ -10699,6 +10751,11 @@ regenerator-runtime@^0.11.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== +regenerator-runtime@^0.12.0: + version "0.12.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" + integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== + regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: version "0.13.7" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" @@ -11315,6 +11372,11 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" +shallow-equal@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da" + integrity sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA== + shallowequal@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" @@ -12751,6 +12813,13 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.x" +warning@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + watchpack-chokidar2@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957"