diff --git a/webUI/react/package.json b/webUI/react/package.json index 9810cc9..c5e198a 100644 --- a/webUI/react/package.json +++ b/webUI/react/package.json @@ -8,11 +8,13 @@ "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", + "@types/react-lazyload": "^3.1.0", "axios": "^0.21.1", "fontsource-roboto": "^4.0.0", "react": "^17.0.1", "react-beautiful-dnd": "^13.0.0", "react-dom": "^17.0.1", + "react-lazyload": "^3.2.0", "react-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/webUI/react/src/App.tsx b/webUI/react/src/App.tsx index 9fe244b..0f5a752 100644 --- a/webUI/react/src/App.tsx +++ b/webUI/react/src/App.tsx @@ -27,9 +27,12 @@ import useLocalStorage from './util/useLocalStorage'; export default function App() { const [title, setTitle] = useState('Tachidesk'); const [action, setAction] = useState(
); + const [override, setOverride] = useState({ status: false, value:
}); + const [darkTheme, setDarkTheme] = useLocalStorage('darkTheme', true); + const navBarContext = { - title, setTitle, action, setAction, + title, setTitle, action, setAction, override, setOverride, }; const darkThemeContext = { darkTheme, setDarkTheme }; @@ -63,7 +66,7 @@ export default function App() { - + @@ -81,7 +84,7 @@ export default function App() { - + <> @@ -106,6 +109,11 @@ export default function App() { /> + + + + + diff --git a/webUI/react/src/components/ChapterCard.tsx b/webUI/react/src/components/ChapterCard.tsx index 19df017..8a6aa28 100644 --- a/webUI/react/src/components/ChapterCard.tsx +++ b/webUI/react/src/components/ChapterCard.tsx @@ -8,6 +8,7 @@ import Card from '@material-ui/core/Card'; import CardContent from '@material-ui/core/CardContent'; import Button from '@material-ui/core/Button'; import Typography from '@material-ui/core/Typography'; +import { useHistory } from 'react-router-dom'; const useStyles = makeStyles((theme) => ({ root: { @@ -41,6 +42,7 @@ interface IProps{ export default function ChapterCard(props: IProps) { const classes = useStyles(); + const history = useHistory(); const { chapter } = props; const dateStr = chapter.date_upload && new Date(chapter.date_upload).toISOString().slice(0, 10); @@ -64,7 +66,7 @@ export default function ChapterCard(props: IProps) {
- +
diff --git a/webUI/react/src/components/NavBar.tsx b/webUI/react/src/components/NavBar.tsx index bdbba2c..5bf8685 100644 --- a/webUI/react/src/components/NavBar.tsx +++ b/webUI/react/src/components/NavBar.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ // TODO: remove above! /* 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 @@ -6,18 +5,14 @@ import React, { useContext, useState } from 'react'; import { makeStyles } from '@material-ui/core/styles'; -import MoreIcon from '@material-ui/icons/MoreVert'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import Typography from '@material-ui/core/Typography'; import IconButton from '@material-ui/core/IconButton'; import MenuIcon from '@material-ui/icons/Menu'; -import MenuItem from '@material-ui/core/MenuItem'; -import Menu from '@material-ui/core/Menu'; - -import TemporaryDrawer from './TemporaryDrawer'; import NavBarContext from '../context/NavbarContext'; import DarkTheme from '../context/DarkTheme'; +import TemporaryDrawer from './TemporaryDrawer'; const useStyles = makeStyles((theme) => ({ root: { @@ -31,89 +26,40 @@ const useStyles = makeStyles((theme) => ({ }, })); -// const theme = createMuiTheme({ -// overrides: { -// MuiAppBar: { -// colorPrimary: { backgroundColor: '#FFC0CB' }, -// }, -// }, -// palette: { type: 'dark' }, -// }); - export default function NavBar() { const classes = useStyles(); const [drawerOpen, setDrawerOpen] = useState(false); - const [anchorEl, setAnchorEl] = React.useState(null); - const { title, action } = useContext(NavBarContext); - const open = Boolean(anchorEl); + const { title, action, override } = useContext(NavBarContext); const { darkTheme } = useContext(DarkTheme); - const handleMenu = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - return ( -
- - - setDrawerOpen(true)} - > - - - - {title} - - {action} - {/* - - */} - {/* - { setDarkTheme(true); handleClose(); }} + <> + {override.status && override.value} + {!override.status + && ( +
+ + + setDrawerOpen(true)} > - Dark Theme - - - { setDarkTheme(false); handleClose(); }} - > - Light Theme - - -
*/} -
-
- -
+ + + + {title} + + {action} + + + + + )} + ); } diff --git a/webUI/react/src/components/Page.tsx b/webUI/react/src/components/Page.tsx new file mode 100644 index 0000000..7048777 --- /dev/null +++ b/webUI/react/src/components/Page.tsx @@ -0,0 +1,74 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* 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 '@material-ui/core/CircularProgress'; +import { makeStyles } from '@material-ui/core/styles'; +import React, { useEffect, useState } from 'react'; +import LazyLoad from 'react-lazyload'; + +const useStyles = makeStyles({ + loading: { + margin: '100px auto', + height: '100vh', + }, + loadingImage: { + padding: 'calc(50vh - 40px) calc(50vw - 40px)', + height: '100vh', + width: '100vw', + backgroundColor: '#525252', + marginBottom: 10, + }, +}); + +interface IProps { + src: string + index: number +} + +function LazyImage(props: IProps) { + const classes = useStyles(); + const { src, index } = props; + const [imageSrc, setImagsrc] = useState(''); + + useEffect(() => { + const img = new Image(); + img.src = src; + + img.onload = () => setImagsrc(src); + }, []); + + if (imageSrc.length === 0) { + return ( +
+ +
+ ); + } + + return ( + {`Page + ); +} + +export default function Page(props: IProps) { + const { src, index } = props; + const classes = useStyles(); + + return ( +
+ + +
+ )} + > + + + + ); +} diff --git a/webUI/react/src/components/ReaderNavBar.tsx b/webUI/react/src/components/ReaderNavBar.tsx new file mode 100644 index 0000000..d70fe9d --- /dev/null +++ b/webUI/react/src/components/ReaderNavBar.tsx @@ -0,0 +1,186 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* 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 IconButton from '@material-ui/core/IconButton'; +import CloseIcon from '@material-ui/icons/Close'; +import KeyboardArrowLeftIcon from '@material-ui/icons/KeyboardArrowLeft'; +import KeyboardArrowRightIcon from '@material-ui/icons/KeyboardArrowRight'; +import { makeStyles, Theme, useTheme } from '@material-ui/core/styles'; +import React, { useContext, useEffect, useState } from 'react'; +import Typography from '@material-ui/core/Typography'; +import { useHistory } from 'react-router-dom'; +import Slide from '@material-ui/core/Slide'; +import Fade from '@material-ui/core/Fade'; +import Zoom from '@material-ui/core/Zoom'; +import NavBarContext from '../context/NavbarContext'; +import DarkTheme from '../context/DarkTheme'; + +const useStyles = makeStyles((theme: Theme) => ({ + // main container and root div need to change classes... + AppMainContainer: { + display: 'none', + }, + AppRootElment: { + display: 'flex', + }, + + root: { + position: 'fixed', + top: 0, + left: 0, + minWidth: '300px', + height: '100vh', + overflowY: 'auto', + backgroundColor: '#0a0b0b', + + '& header': { + backgroundColor: '#363b3d', + display: 'flex', + alignItems: 'center', + minHeight: '64px', + paddingLeft: '24px', + paddingRight: '24px', + + transition: 'left 2s ease', + + '& button': { + flexGrow: 0, + flexShrink: 0, + }, + + '& button:nth-child(1)': { + marginRight: '16px', + }, + + '& button:nth-child(3)': { + marginRight: '-12px', + }, + + '& h1': { + fontSize: '1.25rem', + flexGrow: 1, + }, + }, + }, + + openDrawerButton: { + position: 'fixed', + top: 0 + 20, + left: 10 + 20, + height: '40px', + width: '40px', + borderRadius: 5, + backgroundColor: 'black', + + '&:hover': { + backgroundColor: 'black', + }, + }, +})); + +export interface IReaderSettings{ + +} + +interface IProps { + settings: IReaderSettings + setSettings: React.Dispatch> + manga: IMangaCard | IManga +} + +export default function ReaderNavBar(props: IProps) { + const { title } = useContext(NavBarContext); + const { darkTheme } = useContext(DarkTheme); + + const history = useHistory(); + + const [drawerOpen, setDrawerOpen] = useState(false); + const [hideOpenButton, setHideOpenButton] = useState(false); + const [prevScrollPos, setPrevScrollPos] = useState(0); + + const theme = useTheme(); + const classes = useStyles(); + + const { settings, setSettings, manga } = props; + + const handleScroll = () => { + const currentScrollPos = window.pageYOffset; + + if (Math.abs(currentScrollPos - prevScrollPos) > 20) { + setHideOpenButton(currentScrollPos > prevScrollPos); + setPrevScrollPos(currentScrollPos); + } + }; + + useEffect(() => { + window.addEventListener('scroll', handleScroll); + + return () => { + window.removeEventListener('scroll', handleScroll); + }; + }, [handleScroll]); + + useEffect(() => { + window.addEventListener('scroll', handleScroll); + + const rootEl = document.querySelector('#root')!; + const mainContainer = document.querySelector('#appMainContainer')!; + + rootEl.classList.add(classes.AppRootElment); + mainContainer.classList.add(classes.AppMainContainer); + + return () => { + rootEl.classList.remove(classes.AppRootElment); + mainContainer.classList.remove(classes.AppMainContainer); + }; + }, []); + + return ( + <> + +
+
+ history.push(`/manga/${manga.id}`)} + > + + + + {title} + + setDrawerOpen(false)} + > + + +
+
+
+ + + setDrawerOpen(true)} + > + + + + + + ); +} diff --git a/webUI/react/src/components/TemporaryDrawer.tsx b/webUI/react/src/components/TemporaryDrawer.tsx index 89210c6..c486588 100644 --- a/webUI/react/src/components/TemporaryDrawer.tsx +++ b/webUI/react/src/components/TemporaryDrawer.tsx @@ -27,68 +27,54 @@ interface IProps { export default function TemporaryDrawer({ drawerOpen, setDrawerOpen }: IProps) { const classes = useStyles(); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const sideList = (side: 'left') => ( -
setDrawerOpen(false)} - onKeyDown={() => setDrawerOpen(false)} - > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {/* - - - - - - - */} - -
- ); - return (
setDrawerOpen(false)} > - {sideList('left')} +
setDrawerOpen(false)} + onKeyDown={() => setDrawerOpen(false)} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
); diff --git a/webUI/react/src/context/NavbarContext.tsx b/webUI/react/src/context/NavbarContext.tsx index b2b37e9..b4d80c2 100644 --- a/webUI/react/src/context/NavbarContext.tsx +++ b/webUI/react/src/context/NavbarContext.tsx @@ -9,6 +9,8 @@ type ContextType = { setTitle: React.Dispatch> action: any setAction: React.Dispatch> + override: INavbarOverride + setOverride: React.Dispatch> }; const NavBarContext = React.createContext({ @@ -16,6 +18,8 @@ const NavBarContext = React.createContext({ setTitle: ():void => {}, action:
, setAction: ():void => {}, + override: { status: false, value:
}, + setOverride: ():void => {}, }); export default NavBarContext; diff --git a/webUI/react/src/screens/Reader.tsx b/webUI/react/src/screens/Reader.tsx index 8f24146..7e55774 100644 --- a/webUI/react/src/screens/Reader.tsx +++ b/webUI/react/src/screens/Reader.tsx @@ -1,57 +1,91 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ /* 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 '@material-ui/core/CircularProgress'; +import { makeStyles } from '@material-ui/core/styles'; import React, { useContext, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; +import Page from '../components/Page'; +import ReaderNavBar, { IReaderSettings } from '../components/ReaderNavBar'; import NavbarContext from '../context/NavbarContext'; import client from '../util/client'; import useLocalStorage from '../util/useLocalStorage'; -const style = { - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - margin: '0 auto', - backgroundColor: '#343a40', -} as React.CSSProperties; +const useStyles = makeStyles({ + reader: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + margin: '0 auto', + }, + + loading: { + margin: '50px auto', + }, +}); const range = (n:number) => Array.from({ length: n }, (value, key) => key); export default function Reader() { - const { setTitle, setAction } = useContext(NavbarContext); - useEffect(() => { setTitle('Reader'); setAction(<>); }, []); + const classes = useStyles(); + + const [settings, setSettings] = useState({}); const [serverAddress] = useLocalStorage('serverBaseURL', ''); - const [pageCount, setPageCount] = useState(-1); const { chapterId, mangaId } = useParams<{chapterId: string, mangaId: string}>(); + const [manga, setManga] = useState({ id: +mangaId, title: '', thumbnailUrl: '' }); + const [pageCount, setPageCount] = useState(-1); + + const { setOverride, setTitle } = useContext(NavbarContext); + useEffect(() => { + setOverride( + { + status: true, + value: , + }, + ); + + // clean up for when we leave the reader + return () => setOverride({ status: false, value:
}); + }, [manga]); + + useEffect(() => { + setTitle('Reader'); + client.get(`/api/v1/manga/${mangaId}/`) + .then((response) => response.data) + .then((data: IManga) => { + setManga(data); + setTitle(data.title); + }); + }, []); useEffect(() => { client.get(`/api/v1/manga/${mangaId}/chapter/${chapterId}`) .then((response) => response.data) .then((data:IChapter) => { - setTitle(data.name); setPageCount(data.pageCount); }); }, []); if (pageCount === -1) { return ( -
-

wait

+
+
); } - - const mapped = range(pageCount).map((index) => ( -
- F -
- )); return ( -
- {mapped} +
+ {range(pageCount).map((index) => ( + + ))}
); } diff --git a/webUI/react/src/typings.d.ts b/webUI/react/src/typings.d.ts index 89e9dad..fab738b 100644 --- a/webUI/react/src/typings.d.ts +++ b/webUI/react/src/typings.d.ts @@ -62,3 +62,8 @@ interface ICategory { name: String isLanding: boolean } + +interface INavbarOverride { + status: boolean + value: any +} \ No newline at end of file diff --git a/webUI/react/yarn.lock b/webUI/react/yarn.lock index 844fc22..c0e01d0 100644 --- a/webUI/react/yarn.lock +++ b/webUI/react/yarn.lock @@ -1846,6 +1846,13 @@ dependencies: "@types/react" "*" +"@types/react-lazyload@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/react-lazyload/-/react-lazyload-3.1.0.tgz#97b167266afbc75f432eca01c50555adae21c5a4" + integrity sha512-JnVJb+6cUrIk4Fo/zc/4NuFtm0h3XeNlN4Gt++WEHGeUDtlhnF1lXRz0WoqNmh5gH3oyeYOJXIZ8MoPL9ehp0g== + dependencies: + "@types/react" "*" + "@types/react-router-dom@^5.1.6": version "5.1.6" resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.6.tgz#07b14e7ab1893a837c8565634960dc398564b1fb" @@ -9353,6 +9360,11 @@ react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== +react-lazyload@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/react-lazyload/-/react-lazyload-3.2.0.tgz#497bd06a6dbd7015e3376e1137a67dc47d2dd021" + integrity sha512-zJlrG8QyVZz4+xkYZH5v1w3YaP5wEFaYSUWC4CT9UXfK75IfRAIEdnyIUF+dXr3kX2MOtL1lUaZmaQZqrETwgw== + react-redux@^7.1.1: version "7.2.2" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.2.tgz#03862e803a30b6b9ef8582dadcc810947f74b736"