From 3777cc646e202d51e38b5224e1db7b21d5774a34 Mon Sep 17 00:00:00 2001 From: Manchewable <35658388+Manchewable@users.noreply.github.com> Date: Sat, 29 May 2021 08:11:59 -0700 Subject: [PATCH] Improve continuous horizontal reader (#110) * differentiate ContinuesHorizontalLTR and ContinuesHorizontalRTL * fix displaying pages in horizontal viewer * add scroll handler for horizontal mode * update curPage when images pass through center of the screen * add click events to navigate pages * remove console.log * fix click mapping for ContinuesHorizontalRTL * remove disable eslint inline comment * fix ContinuesHorizontalRTL not updating curPage on scroll * add ability to click to drag * add margin in between images --- .../src/components/manga/reader/Page.tsx | 41 ++++-- .../manga/reader/pager/HorizontalPager.tsx | 128 +++++++++++++++--- .../src/components/navbar/ReaderNavBar.tsx | 8 +- webUI/react/src/screens/manga/Reader.tsx | 3 +- webUI/react/src/typings.d.ts | 3 +- 5 files changed, 152 insertions(+), 31 deletions(-) diff --git a/webUI/react/src/components/manga/reader/Page.tsx b/webUI/react/src/components/manga/reader/Page.tsx index 88ac5b4..5c1d033 100644 --- a/webUI/react/src/components/manga/reader/Page.tsx +++ b/webUI/react/src/components/manga/reader/Page.tsx @@ -11,15 +11,20 @@ import React, { useEffect, useRef } from 'react'; import SpinnerImage from 'components/SpinnerImage'; function imageStyle(settings: IReaderSettings): CSSProperties { - if (settings.readerType === 'DoubleLTR' || settings.readerType === 'DoubleRTL' || settings.readerType === 'ContinuesHorizontal') { + if (settings.readerType === 'DoubleLTR' + || settings.readerType === 'DoubleRTL' + || settings.readerType === 'ContinuesHorizontalLTR' + || settings.readerType === 'ContinuesHorizontalRTL') { return { display: 'block', - marginBottom: 0, + marginLeft: '7px', + marginRight: '7px', width: 'auto', minHeight: '99vh', height: 'auto', maxHeight: '99vh', objectFit: 'contain', + pointerEvents: 'none', }; } @@ -64,7 +69,7 @@ const Page = React.forwardRef((props: IProps, ref: any) => { const classes = useStyles(settings)(); const imgRef = useRef(null); - const handleScroll = () => { + const handleVerticalScroll = () => { if (imgRef.current) { const rect = imgRef.current.getBoundingClientRect(); if (rect.y < 0 && rect.y + rect.height > 0) { @@ -73,15 +78,29 @@ const Page = React.forwardRef((props: IProps, ref: any) => { } }; - useEffect(() => { - if (settings.readerType === 'Webtoon' || settings.readerType === 'ContinuesVertical') { - window.addEventListener('scroll', handleScroll); + const handleHorizontalScroll = () => { + if (imgRef.current) { + const rect = imgRef.current.getBoundingClientRect(); + if (rect.left <= window.innerWidth / 2 && rect.right > window.innerWidth / 2) { + setCurPage(index); + } + } + }; - return () => { - window.removeEventListener('scroll', handleScroll); - }; - } return () => {}; - }, [handleScroll]); + useEffect(() => { + switch (settings.readerType) { + case 'Webtoon': + case 'ContinuesVertical': + window.addEventListener('scroll', handleVerticalScroll); + return () => window.removeEventListener('scroll', handleVerticalScroll); + case 'ContinuesHorizontalLTR': + case 'ContinuesHorizontalRTL': + window.addEventListener('scroll', handleHorizontalScroll); + return () => window.removeEventListener('scroll', handleHorizontalScroll); + default: + return () => {}; + } + }, [handleVerticalScroll]); return (
diff --git a/webUI/react/src/components/manga/reader/pager/HorizontalPager.tsx b/webUI/react/src/components/manga/reader/pager/HorizontalPager.tsx index b33a98d..4ee5c26 100644 --- a/webUI/react/src/components/manga/reader/pager/HorizontalPager.tsx +++ b/webUI/react/src/components/manga/reader/pager/HorizontalPager.tsx @@ -6,34 +6,129 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { makeStyles } from '@material-ui/core/styles'; -import React from 'react'; +import React, { useEffect, useRef } from 'react'; import Page from '../Page'; -const useStyles = makeStyles({ +const useStyles = (settings: IReaderSettings) => makeStyles({ reader: { display: 'flex', - flexDirection: 'row', - justifyContent: 'center', + flexDirection: (settings.readerType === 'ContinuesHorizontalLTR') ? 'row' : 'row-reverse', + justifyContent: (settings.readerType === 'ContinuesHorizontalLTR') ? 'flex-start' : 'flex-end', margin: '0 auto', - width: '100%', - height: '100vh', - overflowX: 'scroll', + width: 'auto', + height: 'auto', + overflowX: 'visible', + userSelect: 'none', }, }); -interface IProps { - pages: Array - setCurPage: React.Dispatch> - settings: IReaderSettings -} +export default function HorizontalPager(props: IReaderProps) { + const { + pages, curPage, settings, setCurPage, prevChapter, nextChapter, + } = props; -export default function HorizontalPager(props: IProps) { - const { pages, settings, setCurPage } = props; + const classes = useStyles(settings)(); - const classes = useStyles(); + const selfRef = useRef(null); + const pagesRef = useRef([]); + + function nextPage() { + if (curPage < pages.length - 1) { + pagesRef.current[curPage + 1]?.scrollIntoView({ inline: 'center' }); + setCurPage((page) => page + 1); + } else if (settings.loadNextonEnding) { + nextChapter(); + } + } + + function prevPage() { + if (curPage > 0) { + pagesRef.current[curPage - 1]?.scrollIntoView({ inline: 'center' }); + setCurPage(curPage - 1); + } else if (curPage === 0) { + prevChapter(); + } + } + + function goLeft() { + if (settings.readerType === 'ContinuesHorizontalLTR') { + prevPage(); + } else { + nextPage(); + } + } + + function goRight() { + if (settings.readerType === 'ContinuesHorizontalLTR') { + nextPage(); + } else { + prevPage(); + } + } + + const mouseXPos = useRef(0); + + function dragScreen(e: MouseEvent) { + window.scrollBy(mouseXPos.current - e.pageX, 0); + } + + function dragControl(e:MouseEvent) { + mouseXPos.current = e.pageX; + selfRef.current?.addEventListener('mousemove', dragScreen); + } + + function removeDragControl() { + selfRef.current?.removeEventListener('mousemove', dragScreen); + } + + function clickControl(e:MouseEvent) { + if (e.clientX >= window.innerWidth * 0.85) { + goRight(); + } else if (e.clientX <= window.innerWidth * 0.15) { + goLeft(); + } + } + + const handleLoadNextonEnding = () => { + if (settings.readerType === 'ContinuesHorizontalLTR') { + if (window.scrollX + window.innerWidth >= document.body.scrollWidth) { + nextChapter(); + } + } else if (settings.readerType === 'ContinuesHorizontalRTL') { + if (window.scrollX <= window.innerWidth) { + nextChapter(); + } + } + }; + + useEffect(() => { + pagesRef.current[curPage]?.scrollIntoView({ inline: 'center' }); + }, [settings.readerType]); + + useEffect(() => { + selfRef.current?.addEventListener('mousedown', dragControl); + selfRef.current?.addEventListener('mouseup', removeDragControl); + + return () => { + selfRef.current?.removeEventListener('mousedown', dragControl); + selfRef.current?.removeEventListener('mouseup', removeDragControl); + }; + }, [selfRef]); + + useEffect(() => { + if (settings.loadNextonEnding) { + document.addEventListener('scroll', handleLoadNextonEnding); + } + selfRef.current?.addEventListener('mousedown', clickControl); + + return () => { + document.removeEventListener('scroll', handleLoadNextonEnding); + selfRef.current?.removeEventListener('mousedown', clickControl); + }; + }, [selfRef, curPage]); return ( -
+
{ pages.map((page) => ( {}} setCurPage={setCurPage} settings={settings} + ref={(e:HTMLDivElement) => { pagesRef.current[page.index] = e; }} /> )) } diff --git a/webUI/react/src/components/navbar/ReaderNavBar.tsx b/webUI/react/src/components/navbar/ReaderNavBar.tsx index ac18e57..4d2f84e 100644 --- a/webUI/react/src/components/navbar/ReaderNavBar.tsx +++ b/webUI/react/src/components/navbar/ReaderNavBar.tsx @@ -313,8 +313,12 @@ export default function ReaderNavBar(props: IProps) { Continues Vertical - - Horizontal(WIP) + + Horizontal (LTR) + + + + Horizontal (RTL) diff --git a/webUI/react/src/screens/manga/Reader.tsx b/webUI/react/src/screens/manga/Reader.tsx index e468d67..05cf111 100644 --- a/webUI/react/src/screens/manga/Reader.tsx +++ b/webUI/react/src/screens/manga/Reader.tsx @@ -46,7 +46,8 @@ const getReaderComponent = (readerType: ReaderType) => { case 'DoubleLTR': return DoublePagedPager; break; - case 'ContinuesHorizontal': + case 'ContinuesHorizontalLTR': + case 'ContinuesHorizontalRTL': return HorizontalPager; default: return VerticalPager; diff --git a/webUI/react/src/typings.d.ts b/webUI/react/src/typings.d.ts index 99ec207..7fe92a1 100644 --- a/webUI/react/src/typings.d.ts +++ b/webUI/react/src/typings.d.ts @@ -117,7 +117,8 @@ type ReaderType = 'DoubleVertical' | 'DoubleRTL' | 'DoubleLTR' | -'ContinuesHorizontal'; +'ContinuesHorizontalLTR'| +'ContinuesHorizontalRTL'; interface IReaderSettings{ staticNav: boolean