diff --git a/src/App.tsx b/src/App.tsx index e3c3fc78..116a0aa4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -90,7 +90,12 @@ export default function App() { id="appMainContainer" maxWidth={false} disableGutters - style={{ paddingTop: theme.spacing(8), paddingLeft: isMobileWidth ? '' : theme.spacing(8) }} + style={{ + marginTop: theme.spacing(8), + marginLeft: isMobileWidth ? '' : theme.spacing(8), + width: 'auto', + overflow: 'auto', + }} > {/* General Routes */} diff --git a/src/components/EmptyView.tsx b/src/components/EmptyView.tsx new file mode 100644 index 00000000..9a7f1e71 --- /dev/null +++ b/src/components/EmptyView.tsx @@ -0,0 +1,59 @@ +/* + * 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/. */ +// adopted from: https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt + +import React from 'react'; +import Typography from '@mui/material/Typography'; +import { useTheme } from '@mui/material/styles'; +import { useMediaQuery } from '@mui/material'; + +const ERROR_FACES = [ + '(・o・;)', + 'Σ(ಠ_ಠ)', + 'ಥ_ಥ', + '(˘・_・˘)', + '(; ̄Д ̄)', + '(・Д・。', +]; + +function getRandomErrorFace() { + const randIndex = Math.floor(Math.random() * ERROR_FACES.length); + return ERROR_FACES[randIndex]; +} + +interface IProps { + message: string + messageExtra?: JSX.Element +} + +export default function EmptyView({ message, messageExtra }: IProps) { + const theme = useTheme(); + const isMobileWidth = useMediaQuery(theme.breakpoints.down('sm')); + + return ( +
+ + {getRandomErrorFace()} + + + {message} + + {messageExtra} +
+ ); +} + +EmptyView.defaultProps = { + messageExtra: undefined, +}; diff --git a/src/components/LoadingPlaceholder.tsx b/src/components/LoadingPlaceholder.tsx index 2de811fd..1a2412e9 100644 --- a/src/components/LoadingPlaceholder.tsx +++ b/src/components/LoadingPlaceholder.tsx @@ -20,7 +20,7 @@ const useStyles = makeStyles({ }); interface IProps { - shouldRender: boolean | (() => boolean) + shouldRender?: boolean | (() => boolean) children?: React.ReactNode component?: string | React.FunctionComponent | React.ComponentClass componentProps?: any @@ -32,7 +32,10 @@ export default function LoadingPlaceholder(props: IProps) { } = props; const classes = useStyles(); - const condition = shouldRender instanceof Function ? shouldRender() : shouldRender; + let condition = true; + if (shouldRender !== undefined) { + condition = shouldRender instanceof Function ? shouldRender() : shouldRender; + } if (condition) { if (component) { diff --git a/src/components/manga/MangaGrid.tsx b/src/components/manga/MangaGrid.tsx index efff498a..ed01bd92 100644 --- a/src/components/manga/MangaGrid.tsx +++ b/src/components/manga/MangaGrid.tsx @@ -7,10 +7,13 @@ import React, { useEffect, useRef } from 'react'; import Grid from '@mui/material/Grid'; +import EmptyView from 'components/EmptyView'; +import LoadingPlaceholder from 'components/LoadingPlaceholder'; import MangaCard from './MangaCard'; interface IProps{ mangas: IMangaCard[] + isLoading: boolean message?: string messageExtra?: JSX.Element hasNextPage: boolean @@ -20,7 +23,7 @@ interface IProps{ export default function MangaGrid(props: IProps) { const { - mangas, message, messageExtra, hasNextPage, lastPageNum, setLastPageNum, + mangas, isLoading, message, messageExtra, hasNextPage, lastPageNum, setLastPageNum, } = props; let mapped; const lastManga = useRef(null); @@ -41,12 +44,15 @@ export default function MangaGrid(props: IProps) { }, [hasNextPage, mangas]); if (mangas.length === 0) { - mapped = ( -
-

{message}

- {messageExtra} -
- ); + if (isLoading) { + mapped = ( + + ); + } else { + mapped = ( + + ); + } } else { mapped = mangas.map((it, idx) => { if (idx === mangas.length - 1) { @@ -75,6 +81,6 @@ export default function MangaGrid(props: IProps) { } MangaGrid.defaultProps = { - message: 'loading...', + message: '', messageExtra: undefined, }; diff --git a/src/components/navbar/NavBar.tsx b/src/components/navbar/NavBar.tsx index e8c8e6e1..f7737d26 100644 --- a/src/components/navbar/NavBar.tsx +++ b/src/components/navbar/NavBar.tsx @@ -20,10 +20,10 @@ import ExtensionIcon from '@mui/icons-material/Extension'; import ExploreIcon from '@mui/icons-material/Explore'; import GetAppIcon from '@mui/icons-material/GetApp'; import SettingsIcon from '@mui/icons-material/Settings'; -import TemporaryDrawer from 'components/TemporaryDrawer'; -import NavBarContext from '../../context/NavbarContext'; -import DarkTheme from '../../context/DarkTheme'; +import NavBarContext from 'context/NavbarContext'; +import DarkTheme from 'context/DarkTheme'; import PermanentSideBar from './PermanentSideBar'; +import TemporaryDrawer from './TemporaryDrawer'; const useStyles = makeStyles((theme) => ({ root: { diff --git a/src/components/TemporaryDrawer.tsx b/src/components/navbar/TemporaryDrawer.tsx similarity index 100% rename from src/components/TemporaryDrawer.tsx rename to src/components/navbar/TemporaryDrawer.tsx diff --git a/src/screens/manga/Extensions.tsx b/src/screens/manga/Extensions.tsx index 1189e38c..60a5b850 100644 --- a/src/screens/manga/Extensions.tsx +++ b/src/screens/manga/Extensions.tsx @@ -16,6 +16,7 @@ import useLocalStorage from 'util/useLocalStorage'; import LangSelect from 'components/manga/LangSelect'; import { extensionDefaultLangs, langCodeToName, langSortCmp } from 'util/language'; import { makeToaster } from 'components/Toast'; +import LoadingPlaceholder from 'components/LoadingPlaceholder'; const allLangs: string[] = []; @@ -145,7 +146,7 @@ export default function MangaExtensions() { }, [extensions]); // useEffect only after renders if (Object.entries(extensions).length === 0) { - return

loading...

; + return ; } const groupsToShow = ['updates pending', 'installed', ...shownLangs]; return ( diff --git a/src/screens/manga/Library.tsx b/src/screens/manga/Library.tsx index bba70c4a..aba4b44a 100644 --- a/src/screens/manga/Library.tsx +++ b/src/screens/manga/Library.tsx @@ -11,6 +11,8 @@ import MangaGrid from 'components/manga/MangaGrid'; import NavbarContext from 'context/NavbarContext'; import client from 'util/client'; import cloneObject from 'util/cloneObject'; +import EmptyView from 'components/EmptyView'; +import LoadingPlaceholder from 'components/LoadingPlaceholder'; interface IMangaCategory { category: ICategory @@ -93,11 +95,11 @@ export default function Library() { }, [tabs?.length, tabNum]); if (tabs === undefined) { - return

Loading...

; + return ; } if (tabs.length === 0) { - return

Library is empty

; + return ; } let toRender; @@ -112,7 +114,8 @@ export default function Library() { hasNextPage={false} lastPageNum={lastPageNum} setLastPageNum={setLastPageNum} - message={tab.isFetched ? 'Category is Empty' : 'Loading...'} + message="Category is Empty" + isLoading={!tab.isFetched} /> )); @@ -145,7 +148,8 @@ export default function Library() { hasNextPage={false} lastPageNum={lastPageNum} setLastPageNum={setLastPageNum} - message={tabs.length > 0 ? 'Library is Empty' : undefined} + message="Your Library is empty" + isLoading={!tabs[0].isFetched} /> ); } diff --git a/src/screens/manga/SearchSingle.tsx b/src/screens/manga/SearchSingle.tsx index c35585f6..e415c02b 100644 --- a/src/screens/manga/SearchSingle.tsx +++ b/src/screens/manga/SearchSingle.tsx @@ -39,6 +39,7 @@ export default function SearchSingle() { const [searchTerm, setSearchTerm] = useState(''); const [hasNextPage, setHasNextPage] = useState(false); const [lastPageNum, setLastPageNum] = useState(1); + const [isLoading, setIsLoading] = useState(false); const textInput = React.createRef(); @@ -53,22 +54,23 @@ export default function SearchSingle() { const { value } = textInput.current; if (value === '') { setError(true); - setMessage('Type something to search'); - } else { + } else if (value !== searchTerm) { setError(false); setSearchTerm(value); setMangas([]); - setMessage('loading...'); } } } useEffect(() => { if (searchTerm.length > 0) { + setIsLoading(true); + client.get(`/api/v1/source/${sourceId}/search/${searchTerm}/${lastPageNum}`) .then((response) => response.data) .then((data: { mangaList: IManga[], hasNextPage: boolean }) => { setMessage(''); + if (data.mangaList.length > 0) { setMangas([ ...mangas, @@ -77,22 +79,14 @@ export default function SearchSingle() { }))]); setHasNextPage(data.hasNextPage); } else { - setMessage('search query returned nothing.'); + setMessage('Search query returned nothing.'); } + + setIsLoading(false); }); } }, [searchTerm]); - const mangaGrid = ( - - ); - return ( <>
@@ -109,7 +103,17 @@ export default function SearchSingle() { Search
- {mangaGrid} + {searchTerm.length > 0 + && ( + + )} ); } diff --git a/src/screens/manga/SourceMangas.tsx b/src/screens/manga/SourceMangas.tsx index 52653715..935dfcfe 100644 --- a/src/screens/manga/SourceMangas.tsx +++ b/src/screens/manga/SourceMangas.tsx @@ -93,6 +93,7 @@ export default function SourceMangas(props: { popular: boolean }) { setLastPageNum={setLastPageNum} message={message} messageExtra={messageExtra} + isLoading={!fetched} /> ); } diff --git a/src/screens/manga/Sources.tsx b/src/screens/manga/Sources.tsx index 1718e940..2f57d3f6 100644 --- a/src/screens/manga/Sources.tsx +++ b/src/screens/manga/Sources.tsx @@ -14,6 +14,7 @@ import { sourceDefualtLangs, sourceForcedDefaultLangs, langCodeToName, langSortCmp, } from 'util/language'; import useLocalStorage from 'util/useLocalStorage'; +import LoadingPlaceholder from 'components/LoadingPlaceholder'; function sourceToLangList(sources: ISource[]) { const result: string[] = []; @@ -81,7 +82,7 @@ export default function MangaSources() { if (sources.length === 0) { if (fetched) return (

No sources found. Install Some Extensions first.

); - return (

loading...

); + return ; } return ( <>