Compare commits

...

7 Commits

Author SHA1 Message Date
Aria Moradi bab25f9ad9 bump version
CI Publish / Validate Gradle Wrapper (push) Successful in 12s
CI Publish / Build FatJar (push) Failing after 16s
2021-05-15 23:24:25 +04:30
Aria Moradi a62ee8f8c3 handle reader types 2021-05-15 23:22:37 +04:30
Aria Moradi 5f23691e20 add toast lib 2021-05-15 20:37:07 +04:30
Aria Moradi 3de9ccc62f stop spinning if chapter list is empty 2021-05-15 18:51:38 +04:30
Aria Moradi 1896f7f37b remove lazy load 2021-05-15 18:26:40 +04:30
Aria Moradi 490643dc02 proof of concept readers 2021-05-15 18:17:12 +04:30
Aria Moradi 9808976088 restructure the reader 2021-05-15 17:18:57 +04:30
14 changed files with 468 additions and 159 deletions
+1 -1
View File
@@ -97,7 +97,7 @@ sourceSets {
} }
// should be bumped with each stable release // should be bumped with each stable release
val tachideskVersion = "v0.3.1" val tachideskVersion = "v0.3.2"
// counts commit count on master // counts commit count on master
val tachideskRevision = Runtime val tachideskRevision = Runtime
+1
View File
@@ -6,6 +6,7 @@
"@fontsource/roboto": "^4.3.0", "@fontsource/roboto": "^4.3.0",
"@material-ui/core": "^4.11.4", "@material-ui/core": "^4.11.4",
"@material-ui/icons": "^4.11.2", "@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.58",
"axios": "^0.21.1", "axios": "^0.21.1",
"file-selector": "^0.2.4", "file-selector": "^0.2.4",
"react": "^17.0.2", "react": "^17.0.2",
+16 -17
View File
@@ -23,6 +23,8 @@ import { Switch } from '@material-ui/core';
import List from '@material-ui/core/List'; import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem'; import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemIcon from '@material-ui/core/ListItemIcon';
import MenuItem from '@material-ui/core/MenuItem';
import Select from '@material-ui/core/Select';
import ListItemText from '@material-ui/core/ListItemText'; import ListItemText from '@material-ui/core/ListItemText';
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
import Collapse from '@material-ui/core/Collapse'; import Collapse from '@material-ui/core/Collapse';
@@ -137,16 +139,11 @@ const useStyles = (settings: IReaderSettings) => makeStyles((theme: Theme) => ({
}, },
})); }));
export interface IReaderSettings{
staticNav: boolean
showPageNumber: boolean
continuesPageGap: boolean
}
export const defaultReaderSettings = () => ({ export const defaultReaderSettings = () => ({
staticNav: false, staticNav: false,
showPageNumber: true, showPageNumber: true,
continuesPageGap: false, continuesPageGap: false,
readerType: 'ContinuesVertical',
} as IReaderSettings); } as IReaderSettings);
interface IProps { interface IProps {
@@ -171,7 +168,7 @@ export default function ReaderNavBar(props: IProps) {
const [drawerVisible, setDrawerVisible] = useState(false || settings.staticNav); const [drawerVisible, setDrawerVisible] = useState(false || settings.staticNav);
const [hideOpenButton, setHideOpenButton] = useState(false); const [hideOpenButton, setHideOpenButton] = useState(false);
const [prevScrollPos, setPrevScrollPos] = useState(0); const [prevScrollPos, setPrevScrollPos] = useState(0);
const [settingsCollapseOpen, setSettingsCollapseOpen] = useState(false); const [settingsCollapseOpen, setSettingsCollapseOpen] = useState(true);
const theme = useTheme(); const theme = useTheme();
const classes = useStyles(settings)(); const classes = useStyles(settings)();
@@ -205,7 +202,6 @@ export default function ReaderNavBar(props: IProps) {
return ( return (
<> <>
<ClickAwayListener onClickAway={() => (drawerVisible && setDrawerOpen(false))}>
<Slide <Slide
direction="right" direction="right"
in={drawerOpen} in={drawerOpen}
@@ -282,14 +278,18 @@ export default function ReaderNavBar(props: IProps) {
</ListItemSecondaryAction> </ListItemSecondaryAction>
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemText primary="Continues Page gap" /> <ListItemText primary="Reader Type" />
<ListItemSecondaryAction> <Select
<Switch value={settings.readerType}
edge="end" onChange={(e) => setSettingValue('readerType', e.target.value)}
checked={settings.continuesPageGap} >
onChange={(e) => setSettingValue('continuesPageGap', e.target.checked)} <MenuItem value="SingleLTR">Left to right</MenuItem>
/> <MenuItem value="SingleRTL">Right to left(WIP)</MenuItem>
</ListItemSecondaryAction> <MenuItem value="SingleVertical">Vertical(WIP)</MenuItem>
<MenuItem value="Webtoon">Webtoon</MenuItem>
<MenuItem value="ContinuesVertical">Continues Vertical</MenuItem>
<MenuItem value="ContinuesHorizontal">Horizontal(WIP)</MenuItem>
</Select>
</ListItem> </ListItem>
</List> </List>
</Collapse> </Collapse>
@@ -341,7 +341,6 @@ export default function ReaderNavBar(props: IProps) {
</div> </div>
</div> </div>
</Slide> </Slide>
</ClickAwayListener>
<Zoom in={!drawerOpen}> <Zoom in={!drawerOpen}>
<Fade in={!hideOpenButton}> <Fade in={!hideOpenButton}>
<IconButton <IconButton
@@ -24,7 +24,7 @@ const useStyles = makeStyles({
interface IProps { interface IProps {
drawerOpen: boolean drawerOpen: boolean
setDrawerOpen(state: boolean): void setDrawerOpen: React.Dispatch<React.SetStateAction<boolean>>
} }
export default function TemporaryDrawer({ drawerOpen, setDrawerOpen }: IProps) { export default function TemporaryDrawer({ drawerOpen, setDrawerOpen }: IProps) {
+62
View File
@@ -0,0 +1,62 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import ReactDOM from 'react-dom';
import React from 'react';
import Slide, { SlideProps } from '@material-ui/core/Slide';
import Snackbar from '@material-ui/core/Snackbar';
import MuiAlert, { AlertProps, Color as Severity } from '@material-ui/lab/Alert';
function removeToast(id: string) {
const container = document.querySelector(`#${id}`)!!;
ReactDOM.unmountComponentAtNode(container);
document.body.removeChild(container);
}
function Transition(props: SlideProps) {
// eslint-disable-next-line react/jsx-props-no-spreading
return <Slide {...props} direction="up" />;
}
function Alert(props: AlertProps) {
// eslint-disable-next-line react/jsx-props-no-spreading
return <MuiAlert elevation={6} variant="filled" {...props} />;
}
interface IToastProps{
message: string
severity: Severity
}
function Toast(props: IToastProps) {
const { message, severity } = props;
const [open, setOpen] = React.useState(true);
const handleClose = () => {
setOpen(false);
};
return (
<Snackbar
open={open}
onClose={handleClose}
autoHideDuration={3000}
TransitionComponent={Transition}
message="I love snacks"
>
<MuiAlert elevation={6} variant="filled" onClose={handleClose} severity={severity}>
{message}
</MuiAlert>
</Snackbar>
);
}
export default function makeToast(message: string, severity: Severity) {
const id = Math.floor(Math.random() * 1000);
const container = document.createElement('div');
container.id = `alert-${id}`;
document.body.appendChild(container);
ReactDOM.render(<Toast message={message} severity={severity} />, container);
setTimeout(() => removeToast(container.id), 3500);
}
@@ -0,0 +1,44 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { makeStyles } from '@material-ui/core/styles';
import React from 'react';
import Page from './Page';
const useStyles = makeStyles({
reader: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
margin: '0 auto',
width: '100%',
height: '100vh',
overflowX: 'scroll',
},
});
interface IProps {
pages: Array<IReaderPage>
setCurPage: React.Dispatch<React.SetStateAction<number>>
settings: IReaderSettings
}
export default function HorizontalReader(props: IProps) {
const { pages, settings, setCurPage } = props;
const classes = useStyles();
return (
<div className={classes.reader}>
{
pages.map((page) => (
<Page
key={page.index}
index={page.index}
src={page.src}
setCurPage={setCurPage}
settings={settings}
/>
))
}
</div>
);
}
@@ -11,23 +11,26 @@ import CircularProgress from '@material-ui/core/CircularProgress';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import LazyLoad from 'react-lazyload'; import LazyLoad from 'react-lazyload';
import { IReaderSettings } from './ReaderNavBar';
const useStyles = (settings: IReaderSettings) => makeStyles({ const useStyles = (settings: IReaderSettings) => makeStyles({
loading: { loading: {
margin: '100px auto', margin: '100px auto',
height: '100vh', height: '100vh',
width: '100vw',
}, },
loadingImage: { loadingImage: {
padding: settings.staticNav ? 'calc(50vh - 40px) calc(50vw - 340px)' : 'calc(50vh - 40px) calc(50vw - 40px)',
height: '100vh', height: '100vh',
width: '200px', width: '70vw',
padding: '50px calc(50% - 20px)',
backgroundColor: '#525252', backgroundColor: '#525252',
marginBottom: 10, marginBottom: 10,
}, },
image: { image: {
display: 'block', display: 'block',
marginBottom: settings.continuesPageGap ? '15px' : 0, marginBottom: settings.readerType === 'ContinuesVertical' ? '15px' : 0,
minWidth: '50vw',
width: '100%',
maxWidth: '100%',
}, },
}); });
@@ -73,7 +76,7 @@ function LazyImage(props: IProps) {
if (imageSrc.length === 0) { if (imageSrc.length === 0) {
return ( return (
<div className={classes.loadingImage}> <div className={`${classes.image} ${classes.loadingImage}`}>
<CircularProgress thickness={5} /> <CircularProgress thickness={5} />
</div> </div>
); );
@@ -85,7 +88,6 @@ function LazyImage(props: IProps) {
ref={ref} ref={ref}
src={imageSrc} src={imageSrc}
alt={`Page #${index}`} alt={`Page #${index}`}
style={{ width: '100%', maxWidth: '95vw' }}
/> />
); );
} }
@@ -98,22 +100,12 @@ export default function Page(props: IProps) {
return ( return (
<div style={{ margin: '0 auto' }}> <div style={{ margin: '0 auto' }}>
<LazyLoad
offset={window.innerHeight}
placeholder={(
<div className={classes.loading}>
<CircularProgress thickness={5} />
</div>
)}
once
>
<LazyImage <LazyImage
src={src} src={src}
index={index} index={index}
setCurPage={setCurPage} setCurPage={setCurPage}
settings={settings} settings={settings}
/> />
</LazyLoad>
</div> </div>
); );
} }
@@ -0,0 +1,32 @@
import { makeStyles } from '@material-ui/core/styles';
import React from 'react';
const useStyles = (settings: IReaderSettings) => makeStyles({
pageNumber: {
display: settings.showPageNumber ? 'block' : 'none',
position: 'fixed',
bottom: '50px',
right: settings.staticNav ? 'calc((100vw - 325px)/2)' : 'calc((100vw - 25px)/2)',
width: '50px',
textAlign: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.3)',
borderRadius: '10px',
},
});
interface IProps {
settings: IReaderSettings
curPage: number
pageCount: number
}
export default function PageNumber(props: IProps) {
const { settings, curPage, pageCount } = props;
const classes = useStyles(settings)();
return (
<div className={classes.pageNumber}>
{`${curPage + 1} / ${pageCount}`}
</div>
);
}
@@ -0,0 +1,74 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { makeStyles } from '@material-ui/core/styles';
import React, { useEffect } from 'react';
import Page from './Page';
const useStyles = makeStyles({
reader: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
margin: '0 auto',
width: '100%',
height: '100vh',
},
});
export default function PagedReader(props: IReaderProps) {
const {
pages, settings, setCurPage, curPage,
} = props;
const classes = useStyles();
function nextPage() {
if (curPage < pages.length - 1) { setCurPage(curPage + 1); }
}
function prevPage() {
if (curPage > 0) { setCurPage(curPage - 1); }
}
function keyboardControl(e:KeyboardEvent) {
switch (e.key) {
case 'ArrowRight':
nextPage();
break;
case 'ArrowLeft':
prevPage();
break;
default:
break;
}
}
function clickControl(e:MouseEvent) {
if (e.clientX > window.innerWidth / 2) {
nextPage();
} else {
prevPage();
}
}
useEffect(() => {
document.addEventListener('keyup', keyboardControl, false);
document.addEventListener('click', clickControl);
return () => {
document.removeEventListener('keyup', keyboardControl);
document.removeEventListener('click', clickControl);
};
}, [curPage]);
return (
<div className={classes.reader}>
<Page
key={curPage}
index={curPage}
src={pages[curPage].src}
setCurPage={setCurPage}
settings={settings}
/>
</div>
);
}
@@ -0,0 +1,36 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { makeStyles } from '@material-ui/core/styles';
import React from 'react';
import Page from './Page';
const useStyles = makeStyles({
reader: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
margin: '0 auto',
width: '100%',
},
});
export default function VerticalReader(props: IReaderProps) {
const { pages, settings, setCurPage } = props;
const classes = useStyles();
return (
<div className={classes.reader}>
{
pages.map((page) => (
<Page
key={page.index}
index={page.index}
src={page.src}
setCurPage={setCurPage}
settings={settings}
/>
))
}
</div>
);
}
+12 -4
View File
@@ -15,6 +15,7 @@ import MangaDetails from '../components/MangaDetails';
import NavbarContext from '../context/NavbarContext'; import NavbarContext from '../context/NavbarContext';
import client from '../util/client'; import client from '../util/client';
import LoadingPlaceholder from '../components/LoadingPlaceholder'; import LoadingPlaceholder from '../components/LoadingPlaceholder';
import makeToast from '../components/Toast';
const useStyles = makeStyles((theme: Theme) => ({ const useStyles = makeStyles((theme: Theme) => ({
root: { root: {
@@ -58,6 +59,7 @@ export default function Manga() {
const [manga, setManga] = useState<IManga>(); const [manga, setManga] = useState<IManga>();
const [chapters, setChapters] = useState<IChapter[]>([]); const [chapters, setChapters] = useState<IChapter[]>([]);
const [fetchedChapters, setFetchedChapters] = useState(false);
const [chapterUpdateTriggerer, setChapterUpdateTriggerer] = useState(0); const [chapterUpdateTriggerer, setChapterUpdateTriggerer] = useState(0);
function triggerChaptersUpdate() { function triggerChaptersUpdate() {
@@ -76,11 +78,17 @@ export default function Manga() {
}, [manga]); }, [manga]);
useEffect(() => { useEffect(() => {
const shouldFetchOnline = chapters.length > 0 && chapterUpdateTriggerer === 0; const shouldFetchOnline = fetchedChapters && chapterUpdateTriggerer === 0;
client.get(`/api/v1/manga/${id}/chapters?onlineFetch=${shouldFetchOnline}`) client.get(`/api/v1/manga/${id}/chapters?onlineFetch=${shouldFetchOnline}`)
.then((response) => response.data) .then((response) => response.data)
.then((data) => setChapters(data)); .then((data) => {
}, [chapters.length, chapterUpdateTriggerer]); if (data.length === 0 && fetchedChapters) {
makeToast('No chapters found', 'warning');
}
setChapters(data);
})
.then(() => setFetchedChapters(true));
}, [chapters.length, fetchedChapters, chapterUpdateTriggerer]);
// const itemContent = (index:any) => <InnerItem chapters={chapters} index={index} />; // const itemContent = (index:any) => <InnerItem chapters={chapters} index={index} />;
const itemContent = (index:any) => ( const itemContent = (index:any) => (
@@ -99,7 +107,7 @@ export default function Manga() {
/> />
<LoadingPlaceholder <LoadingPlaceholder
shouldRender={chapters.length > 0} shouldRender={chapters.length > 0 || fetchedChapters}
> >
<Virtuoso <Virtuoso
style={{ // override Virtuoso default values and set them with class style={{ // override Virtuoso default values and set them with class
+51 -28
View File
@@ -10,36 +10,51 @@ import CircularProgress from '@material-ui/core/CircularProgress';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import React, { useContext, useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import Page from '../components/Page'; import HorizontalReader from '../components/reader/HorizontalReader';
import ReaderNavBar, { defaultReaderSettings, IReaderSettings } from '../components/ReaderNavBar'; import Page from '../components/reader/Page';
import PageNumber from '../components/reader/PageNumber';
import PagedReader from '../components/reader/PagedReader';
import VerticalReader from '../components/reader/VerticalReader';
import ReaderNavBar, { defaultReaderSettings } from '../components/ReaderNavBar';
import NavbarContext from '../context/NavbarContext'; import NavbarContext from '../context/NavbarContext';
import client from '../util/client'; import client from '../util/client';
import useLocalStorage from '../util/useLocalStorage'; import useLocalStorage from '../util/useLocalStorage';
const useStyles = (settings: IReaderSettings) => makeStyles({ const useStyles = (settings: IReaderSettings) => makeStyles({
reader: { root: {
display: 'flex', width: settings.staticNav ? 'calc(100vw - 300px)' : '100vw',
flexDirection: 'column',
justifyContent: 'center',
margin: '0 auto',
}, },
loading: { loading: {
margin: '50px auto', margin: '50px auto',
}, },
pageNumber: {
display: settings.showPageNumber ? 'block' : 'none',
position: 'fixed',
bottom: '50px',
right: settings.staticNav ? 'calc((100vw - 325px)/2)' : 'calc((100vw - 25px)/2)',
width: '50px',
textAlign: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.3)',
borderRadius: '10px',
},
}); });
const getReaderComponent = (readerType: ReaderType) => {
switch (readerType) {
case 'ContinuesVertical':
return VerticalReader;
break;
case 'Webtoon':
return VerticalReader;
break;
case 'SingleVertical':
return PagedReader;
break;
case 'SingleRTL':
return PagedReader;
break;
case 'SingleLTR':
return PagedReader;
break;
case 'ContinuesHorizontal':
return HorizontalReader;
default:
return VerticalReader;
break;
}
};
const range = (n:number) => Array.from({ length: n }, (value, key) => key); const range = (n:number) => Array.from({ length: n }, (value, key) => key);
const initialChapter = () => ({ pageCount: -1, index: -1, chapterCount: 0 }); const initialChapter = () => ({ pageCount: -1, index: -1, chapterCount: 0 });
@@ -102,20 +117,28 @@ export default function Reader() {
</div> </div>
); );
} }
const pages = range(chapter.pageCount).map((index) => ({
index,
src: `${serverAddress}/api/v1/manga/${mangaId}/chapter/${chapterIndex}/page/${index}`,
}));
const ReaderComponent = getReaderComponent(settings.readerType);
return ( return (
<div className={classes.reader}> <div className={classes.root}>
<div className={classes.pageNumber}> <PageNumber
{`${curPage + 1} / ${chapter.pageCount}`} settings={settings}
</div> curPage={curPage}
{range(chapter.pageCount).map((index) => ( pageCount={chapter.pageCount}
<Page />
key={index} <ReaderComponent
index={index} pages={pages}
src={`${serverAddress}/api/v1/manga/${mangaId}/chapter/${chapterIndex}/page/${index}`} pageCount={chapter.pageCount}
setCurPage={setCurPage} setCurPage={setCurPage}
curPage={curPage}
settings={settings} settings={settings}
/> />
))}
</div> </div>
); );
} }
+27
View File
@@ -87,3 +87,30 @@ interface INavbarOverride {
status: boolean status: boolean
value: any value: any
} }
type ReaderType =
'ContinuesVertical'|
'Webtoon' |
'SingleVertical' |
'SingleRTL' |
'SingleLTR' |
'ContinuesHorizontal';
interface IReaderSettings{
staticNav: boolean
showPageNumber: boolean
readerType: ReaderType
}
interface IReaderPage {
index: number
src: string
}
interface IReaderProps {
pages: Array<IReaderPage>
pageCount: number
setCurPage: React.Dispatch<React.SetStateAction<number>>
curPage: number
settings: IReaderSettings
}
+11
View File
@@ -1487,6 +1487,17 @@
dependencies: dependencies:
"@babel/runtime" "^7.4.4" "@babel/runtime" "^7.4.4"
"@material-ui/lab@^4.0.0-alpha.58":
version "4.0.0-alpha.58"
resolved "https://registry.yarnpkg.com/@material-ui/lab/-/lab-4.0.0-alpha.58.tgz#c7ebb66f49863c5acbb20817163737caa299fafc"
integrity sha512-GKHlJqLxUeHH3L3dGQ48ZavYrqGOTXkFkiEiuYMAnAvXAZP4rhMIqeHOPXSUQan4Bd8QnafDcpovOSLnadDmKw==
dependencies:
"@babel/runtime" "^7.4.4"
"@material-ui/utils" "^4.11.2"
clsx "^1.0.4"
prop-types "^15.7.2"
react-is "^16.8.0 || ^17.0.0"
"@material-ui/styles@^4.11.4": "@material-ui/styles@^4.11.4":
version "4.11.4" version "4.11.4"
resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.4.tgz#eb9dfccfcc2d208243d986457dff025497afa00d" resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.4.tgz#eb9dfccfcc2d208243d986457dff025497afa00d"