diff --git a/src/App.tsx b/src/App.tsx index 8441e0a9..719607db 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -20,25 +20,19 @@ import NavBar from 'components/navbar/NavBar'; import NavbarContext from 'context/NavbarContext'; import DarkTheme from 'context/DarkTheme'; import useLocalStorage from 'util/useLocalStorage'; -import MangaSources from 'screens/manga/MangaSources'; -import AnimeSources from 'screens/anime/AnimeSources'; +import MangaSources from 'screens/manga/Sources'; import Settings from 'screens/Settings'; import About from 'screens/settings/About'; import Categories from 'screens/settings/Categories'; import Backup from 'screens/settings/Backup'; import Library from 'screens/manga/Library'; import SearchSingle from 'screens/manga/SearchSingle'; -import AnimeSearchSingle from 'screens/anime/AnimeSearchSingle'; import SourceConfigure from 'screens/manga/SourceConfigure'; import Manga from 'screens/manga/Manga'; -import Anime from 'screens/anime/Anime'; -import MangaExtensions from 'screens/manga/MangaExtensions'; +import MangaExtensions from 'screens/manga/Extensions'; import SourceMangas from 'screens/manga/SourceMangas'; -import SourceAnimes from 'screens/anime/SourceAnimes'; import Reader from 'screens/manga/Reader'; -import Player from 'screens/anime/Player'; import Updates from 'screens/manga/Updates'; -import AnimeExtensions from 'screens/anime/AnimeExtensions'; import DownloadQueue from 'screens/manga/DownloadQueue'; declare module '@mui/styles/defaultTheme' { @@ -97,7 +91,7 @@ export default function App() { style={{ paddingTop: '64px' }} > - {/* general routes */} + {/* General Routes */} - - {/* Anime Routes */} - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/components/anime/AnimeCard.tsx b/src/components/anime/AnimeCard.tsx deleted file mode 100644 index 453b5194..00000000 --- a/src/components/anime/AnimeCard.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) Contributors to the Suwayomi project - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -import React from 'react'; -import makeStyles from '@mui/styles/makeStyles'; -import Card from '@mui/material/Card'; -import CardActionArea from '@mui/material/CardActionArea'; -import CardMedia from '@mui/material/CardMedia'; -import Typography from '@mui/material/Typography'; -import { Link } from 'react-router-dom'; -import { Grid } from '@mui/material'; -import useLocalStorage from 'util/useLocalStorage'; - -const useStyles = makeStyles({ - root: { - height: '100%', - width: '100%', - display: 'flex', - }, - wrapper: { - position: 'relative', - height: '100%', - }, - gradient: { - position: 'absolute', - top: 0, - width: '100%', - height: '100%', - background: 'linear-gradient(to bottom, transparent, #000000)', - opacity: 0.5, - }, - title: { - position: 'absolute', - bottom: 0, - padding: '0.5em', - color: 'white', - }, - image: { - height: '100%', - width: '100%', - }, -}); - -interface IProps { - manga: IMangaCard -} -const AnimeCard = React.forwardRef((props: IProps, ref) => { - const { - manga: { - id, title, thumbnailUrl, - }, - } = props; - const classes = useStyles(); - const [serverAddress] = useLocalStorage('serverBaseURL', ''); - - return ( - - - - - - - - {title} - - - - - - ); -}); - -export default AnimeCard; diff --git a/src/components/anime/AnimeDetails.tsx b/src/components/anime/AnimeDetails.tsx deleted file mode 100644 index 76f4e2be..00000000 --- a/src/components/anime/AnimeDetails.tsx +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) Contributors to the Suwayomi project - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -import makeStyles from '@mui/styles/makeStyles'; -import IconButton from '@mui/material/IconButton'; -import { Theme } from '@mui/material/styles'; -import FavoriteIcon from '@mui/icons-material/Favorite'; -import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder'; -import FilterListIcon from '@mui/icons-material/FilterList'; -import PublicIcon from '@mui/icons-material/Public'; -import React, { useContext, useEffect, useState } from 'react'; -import NavbarContext from 'context/NavbarContext'; -import client from 'util/client'; -import useLocalStorage from 'util/useLocalStorage'; -import CategorySelect from 'components/manga/CategorySelect'; - -const useStyles = (inLibrary: string) => makeStyles((theme: Theme) => ({ - root: { - width: '100%', - [theme.breakpoints.up('md')]: { - position: 'sticky', - top: '64px', - left: '0px', - width: '50vw', - height: 'calc(100vh - 64px)', - alignSelf: 'flex-start', - overflowY: 'auto', - }, - }, - top: { - padding: '10px', - // [theme.breakpoints.up('md')]: { - // minWidth: '50%', - // }, - }, - leftRight: { - display: 'flex', - }, - leftSide: { - '& img': { - borderRadius: 4, - maxWidth: '100%', - minWidth: '100%', - height: 'auto', - }, - maxWidth: '50%', - // [theme.breakpoints.up('md')]: { - // minWidth: '100px', - // }, - }, - rightSide: { - marginLeft: 15, - maxWidth: '100%', - '& span': { - fontWeight: '400', - }, - [theme.breakpoints.up('lg')]: { - fontSize: '1.3em', - }, - }, - buttons: { - display: 'flex', - justifyContent: 'space-around', - '& button': { - color: inLibrary === 'In Library' ? '#2196f3' : 'inherit', - }, - '& span': { - display: 'block', - fontSize: '0.85em', - }, - '& a': { - textDecoration: 'none', - color: '#858585', - '& button': { - color: 'inherit', - }, - }, - }, - bottom: { - paddingLeft: '10px', - paddingRight: '10px', - [theme.breakpoints.up('md')]: { - fontSize: '1.2em', - // maxWidth: '50%', - }, - [theme.breakpoints.up('lg')]: { - fontSize: '1.3em', - }, - }, - description: { - '& h4': { - marginTop: '1em', - marginBottom: 0, - }, - '& p': { - textAlign: 'justify', - textJustify: 'inter-word', - }, - }, - genre: { - display: 'flex', - flexWrap: 'wrap', - '& h5': { - border: '2px solid #2196f3', - borderRadius: '1.13em', - marginRight: '1em', - marginTop: 0, - marginBottom: '10px', - padding: '0.3em', - color: '#2196f3', - }, - }, -})); - -interface IProps{ - manga: IAnime -} - -function getSourceName(source: ISource) { - if (source.name !== null) { - return `${source.name} (${source.lang.toLocaleUpperCase()})`; - } - return source.id; -} - -function getValueOrUnknown(val: string) { - return val || 'UNKNOWN'; -} - -export default function AnimeDetails(props: IProps) { - const { setAction } = useContext(NavbarContext); - - const { manga } = props; - if (manga.genre == null) { - manga.genre = ''; - } - const [inLibrary, setInLibrary] = useState( - manga.inLibrary ? 'In Library' : 'Add To Library', - ); - - const [categoryDialogOpen, setCategoryDialogOpen] = useState(false); - - useEffect(() => { - if (inLibrary === 'In Library') { - setAction( - <> - setCategoryDialogOpen(true)} - aria-label="display more actions" - edge="end" - color="inherit" - size="large" - > - - - - >, - - ); - } else { setAction(<>>); } - }, [inLibrary, categoryDialogOpen]); - - const [serverAddress] = useLocalStorage('serverBaseURL', ''); - - const classes = useStyles(inLibrary)(); - - function addToLibrary() { - // setInLibrary('adding'); - client.get(`/api/v1/anime/anime/${manga.id}/library/`).then(() => { - setInLibrary('In Library'); - }); - } - - function removeFromLibrary() { - // setInLibrary('removing'); - client.delete(`/api/v1/anime/anime/${manga.id}/library/`).then(() => { - setInLibrary('Add To Library'); - }); - } - - function handleButtonClick() { - if (inLibrary === 'Add To Library') { - addToLibrary(); - } else { - removeFromLibrary(); - } - } - - return ( - - - - - - - - - {manga.title} - - - Author: - {' '} - {getValueOrUnknown(manga.author)} - - - Artist: - {' '} - {getValueOrUnknown(manga.artist)} - - - Status: - {' '} - {manga.status} - - - Source: - {' '} - {getSourceName(manga.source)} - - - - - - handleButtonClick()} size="large"> - {inLibrary === 'In Library' && } - {inLibrary !== 'In Library' && } - {inLibrary} - - - { /* eslint-disable-next-line react/jsx-no-target-blank */ } - - - - Open Site - - - - - - - About - {manga.description} - - - {manga.genre.split(', ').map((g) => {g})} - - - - ); -} diff --git a/src/components/anime/AnimeGrid.tsx b/src/components/anime/AnimeGrid.tsx deleted file mode 100644 index 2bcb5a56..00000000 --- a/src/components/anime/AnimeGrid.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) Contributors to the Suwayomi project - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -import React, { useEffect, useRef } from 'react'; -import Grid from '@mui/material/Grid'; -import AnimeCard from './AnimeCard'; - -interface IProps{ - mangas: IMangaCard[] - message?: string - hasNextPage: boolean - lastPageNum: number - setLastPageNum: (lastPageNum: number) => void -} - -export default function AnimeGrid(props: IProps) { - const { - mangas, message, hasNextPage, lastPageNum, setLastPageNum, - } = props; - let mapped; - const lastManga = useRef(null); - - const scrollHandler = () => { - if (lastManga.current) { - const rect = lastManga.current.getBoundingClientRect(); - if (((rect.y + rect.height) / window.innerHeight < 2) && hasNextPage) { - setLastPageNum(lastPageNum + 1); - } - } - }; - useEffect(() => { - window.addEventListener('scroll', scrollHandler, true); - return () => { - window.removeEventListener('scroll', scrollHandler, true); - }; - }, [hasNextPage, mangas]); - - if (mangas.length === 0) { - mapped = {message}; - } else { - mapped = mangas.map((it, idx) => { - if (idx === mangas.length - 1) { - return ; - } - return ; - }); - } - - return ( - - {mapped} - - ); -} - -AnimeGrid.defaultProps = { - message: 'loading...', -}; diff --git a/src/components/anime/EpisodeCard.tsx b/src/components/anime/EpisodeCard.tsx deleted file mode 100644 index f61d265c..00000000 --- a/src/components/anime/EpisodeCard.tsx +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) Contributors to the Suwayomi project - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -import React from 'react'; -import { useTheme } from '@mui/material/styles'; -import makeStyles from '@mui/styles/makeStyles'; -import Card from '@mui/material/Card'; -import CardContent from '@mui/material/CardContent'; -import IconButton from '@mui/material/IconButton'; -import MoreVertIcon from '@mui/icons-material/MoreVert'; -import Typography from '@mui/material/Typography'; -import { Link } from 'react-router-dom'; -import Menu from '@mui/material/Menu'; -import MenuItem from '@mui/material/MenuItem'; -import BookmarkIcon from '@mui/icons-material/Bookmark'; -import client from 'util/client'; - -const useStyles = makeStyles((theme) => ({ - root: { - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - padding: 16, - }, - bullet: { - display: 'inline-block', - margin: '0 2px', - transform: 'scale(0.8)', - }, - title: { - fontSize: 14, - }, - pos: { - marginBottom: 12, - }, - icon: { - width: theme.spacing(7), - height: theme.spacing(7), - flex: '0 0 auto', - marginRight: 16, - }, - card: { - margin: '10px', - '&:hover': { - backgroundColor: theme.palette.action.hover, - transition: 'background-color 100ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', - }, - '&:active': { - backgroundColor: theme.palette.action.selected, - transition: 'background-color 100ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', - }, - }, -})); - -interface IProps{ - episode: IEpisode - triggerEpisodesUpdate: () => void -} - -export default function EpisodeCard(props: IProps) { - const classes = useStyles(); - const theme = useTheme(); - const { episode, triggerEpisodesUpdate } = props; - - const dateStr = episode.uploadDate && new Date(episode.uploadDate).toISOString().slice(0, 10); - - const [anchorEl, setAnchorEl] = React.useState(null); - - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const sendChange = (key: string, value: any) => { - handleClose(); - - const formData = new FormData(); - formData.append(key, value); - client.patch(`/api/v1/anime/anime/${episode.animeId}/episode/${episode.index}`, formData) - .then(() => triggerEpisodesUpdate()); - }; - - const readChapterColor = theme.palette.mode === 'dark' ? '#acacac' : '#b0b0b0'; - return ( - <> - - - - - - - - - {episode.bookmarked && } - - {episode.name} - {episode.episodeNumber > 0 && ` : ${episode.episodeNumber}`} - - - {episode.scanlator} - {episode.scanlator && ' '} - {dateStr} - - - - - - - - - - {/* Download */} - sendChange('bookmarked', !episode.bookmarked)}> - {episode.bookmarked && 'Remove bookmark'} - {!episode.bookmarked && 'Bookmark'} - - sendChange('read', !episode.read)}> - Mark as - {' '} - {episode.read && 'unread'} - {!episode.read && 'read'} - - sendChange('markPrevRead', true)}> - Mark previous as Read - - - - - - > - ); -} diff --git a/src/components/anime/ExtensionCard.tsx b/src/components/anime/ExtensionCard.tsx deleted file mode 100644 index 5f61528f..00000000 --- a/src/components/anime/ExtensionCard.tsx +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) Contributors to the Suwayomi project - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -import React, { useState } from 'react'; -import makeStyles from '@mui/styles/makeStyles'; -import Card from '@mui/material/Card'; -import CardContent from '@mui/material/CardContent'; -import Button from '@mui/material/Button'; -import Avatar from '@mui/material/Avatar'; -import Typography from '@mui/material/Typography'; -import client from 'util/client'; -import useLocalStorage from 'util/useLocalStorage'; - -const useStyles = makeStyles((theme) => ({ - root: { - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - padding: 16, - }, - bullet: { - display: 'inline-block', - margin: '0 2px', - transform: 'scale(0.8)', - }, - title: { - fontSize: 14, - }, - pos: { - marginBottom: 12, - }, - icon: { - width: theme.spacing(7), - height: theme.spacing(7), - flex: '0 0 auto', - marginRight: 16, - }, - card: { - margin: '10px', - }, -})); - -interface IProps { - extension: IExtension - notifyInstall: () => void -} - -export default function ExtensionCard(props: IProps) { - const { - extension: { - name, lang, versionName, installed, hasUpdate, obsolete, pkgName, iconUrl, - }, - notifyInstall, - } = props; - const [installedState, setInstalledState] = useState( - () => { - if (obsolete) { return 'obsolete'; } - if (hasUpdate) { return 'update'; } - return (installed ? 'uninstall' : 'install'); - }, - ); - - const [serverAddress] = useLocalStorage('serverBaseURL', ''); - - const classes = useStyles(); - const langPress = lang === 'all' ? 'All' : lang.toUpperCase(); - - function install() { - setInstalledState('installing'); - client.get(`/api/v1/anime/extension/install/${pkgName}`) - .then(() => { - setInstalledState('uninstall'); - notifyInstall(); - }); - } - - function update() { - setInstalledState('updating'); - client.get(`/api/v1/anime/extension/update/${pkgName}`) - .then(() => { - setInstalledState('uninstall'); - notifyInstall(); - }); - } - - function uninstall() { - setInstalledState('uninstalling'); - client.get(`/api/v1/anime/extension/uninstall/${pkgName}`) - .then(() => { - // setInstalledState('install'); - notifyInstall(); - }); - } - - function handleButtonClick() { - switch (installedState) { - case 'install': - install(); - break; - case 'update': - update(); - break; - case 'obsolete': - uninstall(); - setTimeout(() => window.location.reload(), 3000); - break; - case 'uninstall': - uninstall(); - break; - default: - break; - } - } - - return ( - - - - - - - {name} - - - {langPress} - {' '} - {versionName} - - - - - handleButtonClick()} - > - {installedState} - - - - - ); -} diff --git a/src/components/anime/SourceCard.tsx b/src/components/anime/SourceCard.tsx deleted file mode 100644 index 7dc61d2e..00000000 --- a/src/components/anime/SourceCard.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) Contributors to the Suwayomi project - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -import React from 'react'; -import makeStyles from '@mui/styles/makeStyles'; -import Card from '@mui/material/Card'; -import CardContent from '@mui/material/CardContent'; -import Button from '@mui/material/Button'; -import Avatar from '@mui/material/Avatar'; -import Typography from '@mui/material/Typography'; -import useLocalStorage from 'util/useLocalStorage'; -import { langCodeToName } from 'util/language'; - -const useStyles = makeStyles((theme) => ({ - root: { - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - padding: 16, - }, - bullet: { - display: 'inline-block', - margin: '0 2px', - transform: 'scale(0.8)', - }, - title: { - fontSize: 14, - }, - pos: { - marginBottom: 12, - }, - icon: { - width: theme.spacing(7), - height: theme.spacing(7), - flex: '0 0 auto', - marginRight: 16, - }, - card: { - margin: '10px', - '&:hover': { - backgroundColor: theme.palette.action.hover, - transition: 'background-color 100ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', - }, - '&:active': { - backgroundColor: theme.palette.action.selected, - transition: 'background-color 100ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', - }, - }, -})); - -interface IProps { - source: ISource -} - -export default function SourceCard(props: IProps) { - const { - source: { - id, name, lang, iconUrl, supportsLatest, - }, - } = props; - - const [serverAddress] = useLocalStorage('serverBaseURL', ''); - - const classes = useStyles(); - - return ( - - - - - - - {name} - - - {langCodeToName(lang)} - - - - - { window.location.href = `/anime/sources/${id}/search/`; }}>Search - {supportsLatest && { window.location.href = `/anime/sources/${id}/latest/`; }}>Latest} - { window.location.href = `/anime/sources/${id}/popular/`; }}>Browse - - - - ); -} diff --git a/src/components/manga/LangSelect.tsx b/src/components/manga/LangSelect.tsx index e90ed842..fae00b40 100644 --- a/src/components/manga/LangSelect.tsx +++ b/src/components/manga/LangSelect.tsx @@ -29,7 +29,6 @@ const useStyles = makeStyles(() => createStyles({ })); function removeAll(firstList: any[], secondList: any[]) { - console.log(secondList); secondList.forEach((item) => { const index = firstList.indexOf(item); if (index !== -1) { diff --git a/src/screens/anime/Anime.tsx b/src/screens/anime/Anime.tsx deleted file mode 100644 index 944aef6a..00000000 --- a/src/screens/anime/Anime.tsx +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) Contributors to the Suwayomi project - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -import React, { useEffect, useState, useContext } from 'react'; -import { Theme } from '@mui/material/styles'; -import makeStyles from '@mui/styles/makeStyles'; -import { useParams } from 'react-router-dom'; -import { Virtuoso } from 'react-virtuoso'; -import EpisodeCard from 'components/anime/EpisodeCard'; -import AnimeDetails from 'components/anime/AnimeDetails'; -import NavbarContext from 'context/NavbarContext'; -import client from 'util/client'; -import LoadingPlaceholder from 'components/LoadingPlaceholder'; -import makeToast from 'components/Toast'; - -const useStyles = makeStyles((theme: Theme) => ({ - root: { - [theme.breakpoints.up('md')]: { - display: 'flex', - }, - }, - - chapters: { - listStyle: 'none', - padding: 0, - minHeight: '200px', - [theme.breakpoints.up('md')]: { - width: '50vw', - height: 'calc(100vh - 64px)', - margin: 0, - }, - }, - - loading: { - margin: '10px 0', - display: 'flex', - justifyContent: 'center', - }, -})); - -export default function Anime() { - const classes = useStyles(); - - const { setTitle } = useContext(NavbarContext); - useEffect(() => { setTitle('Anime'); }, []); // delegate setting topbar action to MangaDetails - - const { id } = useParams<{ id: string }>(); - - const [manga, setManga] = useState(); - const [episodes, setEpisodes] = useState([]); - const [fetchedEpisodes, setFetchedEpisodes] = useState(false); - const [noEpisodesFound, setNoEpisodesFound] = useState(false); - const [episodeUpdateTriggerer, setEpisodeUpdateTriggerer] = useState(0); - - function triggerEpisodesUpdate() { - setEpisodeUpdateTriggerer(episodeUpdateTriggerer + 1); - } - - useEffect(() => { - if (manga === undefined || !manga.freshData) { - client.get(`/api/v1/anime/anime/${id}/?onlineFetch=${manga !== undefined}`) - .then((response) => response.data) - .then((data: IAnime) => { - setManga(data); - setTitle(data.title); - }); - } - }, [manga]); - - useEffect(() => { - const shouldFetchOnline = fetchedEpisodes && episodeUpdateTriggerer === 0; - client.get(`/api/v1/anime/anime/${id}/episodes?onlineFetch=${shouldFetchOnline}`) - .then((response) => response.data) - .then((data) => { - if (data.length === 0 && fetchedEpisodes) { - makeToast('No episodes found', 'warning'); - setNoEpisodesFound(true); - } - setEpisodes(data); - }) - .then(() => setFetchedEpisodes(true)); - }, [episodes.length, fetchedEpisodes, episodeUpdateTriggerer]); - - return ( - - - - 0 || noEpisodesFound} - > - ( - - )} - useWindowScroll={window.innerWidth < 960} - overscan={window.innerHeight * 0.5} - /> - - - - ); -} diff --git a/src/screens/anime/AnimeExtensions.tsx b/src/screens/anime/AnimeExtensions.tsx deleted file mode 100644 index c3437a55..00000000 --- a/src/screens/anime/AnimeExtensions.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) Contributors to the Suwayomi project - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -import React, { useContext, useEffect, useState } from 'react'; -import ExtensionCard from 'components/anime/ExtensionCard'; -import NavbarContext from 'context/NavbarContext'; -import client from 'util/client'; -import useLocalStorage from 'util/useLocalStorage'; -import LangSelect from 'components/manga/LangSelect'; -import { extensionDefaultLangs, langCodeToName, langSortCmp } from 'util/language'; - -const allLangs: string[] = []; - -function groupExtensions(extensions: IExtension[]) { - allLangs.length = 0; // empty the array - const result = { installed: [], 'updates pending': [] } as any; - extensions.sort((a, b) => ((a.apkName > b.apkName) ? 1 : -1)); - - extensions.forEach((extension) => { - if (result[extension.lang] === undefined) { - result[extension.lang] = []; - if (extension.lang !== 'all') { allLangs.push(extension.lang); } - } - if (extension.installed) { - if (extension.hasUpdate) { - result['updates pending'].push(extension); - } else { - result.installed.push(extension); - } - } else { - result[extension.lang].push(extension); - } - }); - - // put english first for convience - allLangs.sort(langSortCmp); - - return result; -} - -export default function AnimeExtensions() { - const { setTitle, setAction } = useContext(NavbarContext); - const [shownLangs, setShownLangs] = useLocalStorage('shownExtensionLangs', extensionDefaultLangs()); - - useEffect(() => { - setTitle('Extensions'); - setAction( - , - ); - }, [shownLangs]); - - const [extensionsRaw, setExtensionsRaw] = useState([]); - const [extensions, setExtensions] = useState({}); - - const [updateTriggerHolder, setUpdateTriggerHolder] = useState(0); // just a hack - const triggerUpdate = () => setUpdateTriggerHolder(updateTriggerHolder + 1); // just a hack - - useEffect(() => { - client.get('/api/v1/anime/extension/list') - .then((response) => response.data) - .then((data) => setExtensionsRaw(data)); - }, [updateTriggerHolder]); - - useEffect(() => { - if (extensionsRaw.length > 0) { - const groupedExtension = groupExtensions(extensionsRaw); - setExtensions(groupedExtension); - } - }, [extensionsRaw]); - - if (Object.entries(extensions).length === 0) { - return loading...; - } - const groupsToShow = ['updates pending', 'installed', ...shownLangs]; - return ( - <> - { - Object.entries(extensions).map(([lang, list]) => ( - ((groupsToShow.indexOf(lang) !== -1 && (list as []).length > 0) - && ( - - - {langCodeToName(lang)} - - {(list as IExtension[]).map((it) => ( - { - triggerUpdate(); - }} - /> - ))} - - )) - )) - } - > - ); -} diff --git a/src/screens/anime/AnimeSearchSingle.tsx b/src/screens/anime/AnimeSearchSingle.tsx deleted file mode 100644 index 0508751b..00000000 --- a/src/screens/anime/AnimeSearchSingle.tsx +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) Contributors to the Suwayomi project - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -import React, { useContext, useEffect, useState } from 'react'; -import makeStyles from '@mui/styles/makeStyles'; -import TextField from '@mui/material/TextField'; -import Button from '@mui/material/Button'; -import { useParams } from 'react-router-dom'; -import AnimeGrid from 'components/anime/AnimeGrid'; -import NavbarContext from 'context/NavbarContext'; -import client from 'util/client'; - -const useStyles = makeStyles((theme) => ({ - root: { - margin: '20px 10px', - display: 'flex', - justifyContent: 'space-around', - width: '300px', - TextField: { - margin: theme.spacing(1), - width: '25ch', - }, - }, -})); - -export default function AnimeSearchSingle() { - const { setTitle, setAction } = useContext(NavbarContext); - useEffect(() => { setTitle('Search'); setAction(<>>); }, []); - - const { sourceId } = useParams<{ sourceId: string }>(); - const classes = useStyles(); - const [error, setError] = useState(false); - const [mangas, setMangas] = useState([]); - const [message, setMessage] = useState(''); - const [searchTerm, setSearchTerm] = useState(''); - const [hasNextPage, setHasNextPage] = useState(false); - const [lastPageNum, setLastPageNum] = useState(1); - - const textInput = React.createRef(); - - useEffect(() => { - client.get(`/api/v1/anime/source/${sourceId}`) - .then((response) => response.data) - .then((data: { name: string }) => setTitle(`Search: ${data.name}`)); - }, []); - - function processInput() { - if (textInput.current) { - const { value } = textInput.current; - if (value === '') { - setError(true); - setMessage('Type something to search'); - } else { - setError(false); - setSearchTerm(value); - setMangas([]); - setMessage('loading...'); - } - } - } - - useEffect(() => { - if (searchTerm.length > 0) { - client.get(`/api/v1/anime/source/${sourceId}/search/${searchTerm}/${lastPageNum}`) - .then((response) => response.data) - .then((data: { mangaList: IAnime[], hasNextPage: boolean }) => { - setMessage(''); - if (data.mangaList.length > 0) { - setMangas([ - ...mangas, - ...data.mangaList.map((it) => ({ - title: it.title, thumbnailUrl: it.thumbnailUrl, id: it.id, - }))]); - setHasNextPage(data.hasNextPage); - } else { - setMessage('search query returned nothing.'); - } - }); - } - }, [searchTerm]); - - const mangaGrid = ( - - ); - - return ( - <> - - e.key === 'Enter' && processInput()} - /> - processInput()}> - Search - - - {mangaGrid} - > - ); -} diff --git a/src/screens/anime/AnimeSources.tsx b/src/screens/anime/AnimeSources.tsx deleted file mode 100644 index be857481..00000000 --- a/src/screens/anime/AnimeSources.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) Contributors to the Suwayomi project - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -import React, { useContext, useEffect, useState } from 'react'; -import LangSelect from 'components/manga/LangSelect'; -import SourceCard from 'components/anime/SourceCard'; -import NavbarContext from 'context/NavbarContext'; -import client from 'util/client'; -import { sourceDefualtLangs, langCodeToName, langSortCmp } from 'util/language'; -import useLocalStorage from 'util/useLocalStorage'; - -function sourceToLangList(sources: ISource[]) { - const result: string[] = []; - - sources.forEach((source) => { - if (result.indexOf(source.lang) === -1) { result.push(source.lang); } - }); - - result.sort(langSortCmp); - return result; -} - -function groupByLang(sources: ISource[]) { - const result = {} as any; - sources.forEach((source) => { - if (result[source.lang] === undefined) { result[source.lang] = [] as ISource[]; } - result[source.lang].push(source); - }); - - return result; -} - -export default function AnimeSources() { - const { setTitle, setAction } = useContext(NavbarContext); - - const [shownLangs, setShownLangs] = useLocalStorage('shownSourceLangs', sourceDefualtLangs()); - - const [sources, setSources] = useState([]); - const [fetched, setFetched] = useState(false); - - useEffect(() => { - setTitle('Sources'); - setAction( - , - ); - }, [shownLangs, sources]); - - useEffect(() => { - client.get('/api/v1/anime/source/list') - .then((response) => response.data) - .then((data) => { setSources(data); setFetched(true); }); - }, []); - - if (sources.length === 0) { - if (fetched) return (No sources found. Install Some Extensions first.); - return (loading...); - } - return ( - <> - {/* eslint-disable-next-line max-len */} - {Object.entries(groupByLang(sources)).sort((a, b) => langSortCmp(a[0], b[0])).map(([lang, list]) => ( - shownLangs.indexOf(lang) !== -1 && ( - - {langCodeToName(lang)} - {(list as ISource[]).map((source) => ( - - ))} - - ) - ))} - > - ); -} diff --git a/src/screens/anime/Player.tsx b/src/screens/anime/Player.tsx deleted file mode 100644 index f8ea7adf..00000000 --- a/src/screens/anime/Player.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) Contributors to the Suwayomi project - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -import CircularProgress from '@mui/material/CircularProgress'; -import makeStyles from '@mui/styles/makeStyles'; -import React, { useContext, useEffect, useState } from 'react'; -import { useParams } from 'react-router-dom'; -import NavbarContext from 'context/NavbarContext'; -import client from 'util/client'; - -const useStyles = makeStyles({ - root: { - width: 'calc(100vw - 10px)', - height: 'calc(100vh - 64px)', - }, - - loading: { - margin: '50px auto', - display: 'flex', - justifyContent: 'center', - }, - - video: { - maxWidth: '100%', - maxHeight: '100%', - }, -}); - -const initialEpisode = () => ({ - index: -1, episodeCount: 0, videos: [], -}); - -export default function Player() { - const classes = useStyles(); - - const { episodeIndex, animeId } = useParams<{ episodeIndex: string, animeId: string }>(); - const [episode, setEpisode] = useState(initialEpisode()); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [episodeLink, setEpisodeLink] = useState(''); - const { setTitle } = useContext(NavbarContext); - - useEffect(() => { - setTitle('Reader'); - client.get(`/api/v1/anime/anime/${animeId}/`) - .then((response) => response.data) - .then((data: IAnime) => { - setTitle(data.title); - }); - }, [episodeIndex]); - - useEffect(() => { - setEpisode(initialEpisode); - client.get(`/api/v1/anime/anime/${animeId}/episode/${episodeIndex}`) - .then((response) => response.data) - .then((data:IEpisode) => { - setEpisode(data); - }); - }, [episodeIndex]); - - // return spinner while chpater data is loading - if (episode.videos.length === 0) { - return ( - - - - ); - } - - return ( - - {/* eslint-disable-next-line jsx-a11y/media-has-caption */} - - {episode.videos.map((it) => ( - - ))} - - - ); -} diff --git a/src/screens/anime/SourceAnimes.tsx b/src/screens/anime/SourceAnimes.tsx deleted file mode 100644 index f7808ee5..00000000 --- a/src/screens/anime/SourceAnimes.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) Contributors to the Suwayomi project - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -import React, { useContext, useEffect, useState } from 'react'; -import { useParams } from 'react-router-dom'; -import AnimeGrid from 'components/anime/AnimeGrid'; -import NavbarContext from 'context/NavbarContext'; -import client from 'util/client'; - -export default function SourceAnimes(props: { popular: boolean }) { - const { setTitle, setAction } = useContext(NavbarContext); - useEffect(() => { setTitle('Source'); setAction(<>>); }, []); - - const { sourceId } = useParams<{ sourceId: string }>(); - const [mangas, setMangas] = useState([]); - const [hasNextPage, setHasNextPage] = useState(false); - const [lastPageNum, setLastPageNum] = useState(1); - - useEffect(() => { - client.get(`/api/v1/anime/source/${sourceId}`) - .then((response) => response.data) - .then((data: { name: string }) => setTitle(data.name)); - }, []); - - useEffect(() => { - const sourceType = props.popular ? 'popular' : 'latest'; - client.get(`/api/v1/anime/source/${sourceId}/${sourceType}/${lastPageNum}`) - .then((response) => response.data) - .then((data: { mangaList: IAnime[], hasNextPage: boolean }) => { - setMangas([ - ...mangas, - ...data.mangaList.map((it) => ({ - title: it.title, thumbnailUrl: it.thumbnailUrl, id: it.id, - }))]); - setHasNextPage(data.hasNextPage); - }); - }, [lastPageNum]); - - return ( - - ); -} diff --git a/src/screens/manga/MangaExtensions.tsx b/src/screens/manga/Extensions.tsx similarity index 100% rename from src/screens/manga/MangaExtensions.tsx rename to src/screens/manga/Extensions.tsx diff --git a/src/screens/manga/MangaSources.tsx b/src/screens/manga/Sources.tsx similarity index 100% rename from src/screens/manga/MangaSources.tsx rename to src/screens/manga/Sources.tsx diff --git a/src/typings.d.ts b/src/typings.d.ts index 390ddd11..0bf6111d 100644 --- a/src/typings.d.ts +++ b/src/typings.d.ts @@ -58,28 +58,6 @@ interface IManga { freshData: boolean } -interface IAnime { - id: number - sourceId: string - - url: string - title: string - thumbnailUrl: string - - artist: string - author: string - description: string - genre: string | null - status: string - - inLibrary: boolean - source: ISource - - realUrl: string - - freshData: boolean -} - interface IChapter { url: string name: string @@ -102,39 +80,12 @@ interface IMangaChapter { chapter: IChapter } -interface Video{ - url: string - quality: string - videoUrl: string -} - -interface IEpisode { - url: string - name: string - uploadDate: number - episodeNumber: number - scanlator: string - animeId: number - read: boolean - bookmarked: boolean - lastPageRead: number - index: number - episodeCount: number - videos: Video[] -} - interface IPartialChpter { pageCount: number index: number chapterCount: number } -interface IPartialEpisode { - index: number - episodeCount: number - videos: Video[] -} - interface ICategory { id: number order: number
{manga.description}