Demo page update

demo
NB 4 years ago
parent 2a0ca26c8c
commit 45d054571d
  1. 3
      package.json
  2. 9
      public/css/global.css
  3. 6
      src/containers/Reader.tsx
  4. 293
      src/modules/epubViewer/EpubViewer.tsx
  5. 15
      src/modules/epubViewer/style.ts
  6. 4
      src/modules/index.ts
  7. 292
      src/modules/reactViewer/ReactViewer.tsx
  8. 16
      src/modules/reactViewer/viewerStyle.ts
  9. 7
      yarn.lock

@ -40,7 +40,8 @@
"postcompile": "cp -r ./src/types ./lib/types" "postcompile": "cp -r ./src/types ./lib/types"
}, },
"dependencies": { "dependencies": {
"epubjs": "^0.3.88" "epubjs": "^0.3.88",
"react-epub-viewer": "^0.1.7"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.13.14", "@babel/cli": "^7.13.14",

@ -34,4 +34,13 @@ a {
#root { #root {
height : 100%; height : 100%;
}
.epub-container {
overflow-x: hidden !important;
}
.epub-container::-webkit-scrollbar {
width: 0px;
background: transparent;
} }

@ -1,8 +1,8 @@
import { useState, useRef } from 'react' import { useState, useRef } from 'react'
import { useSelector, useDispatch } from 'react-redux' import { useSelector, useDispatch } from 'react-redux'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import { ReactEpubViewer } from 'react-epub-viewer'
// containers // containers
import Viewer from 'modules/reactViewer/ReactViewer'
import Header from 'containers/Header' import Header from 'containers/Header'
import Footer from 'containers/Footer' import Footer from 'containers/Footer'
import Nav from 'containers/menu/Nav' import Nav from 'containers/menu/Nav'
@ -34,7 +34,7 @@ const Reader = ({ url, loadingView }: Props) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const currentLocation = useSelector<RootState, Page>(state => state.book.currentLocation); const currentLocation = useSelector<RootState, Page>(state => state.book.currentLocation);
const viewerRef = useRef<ViewerRef | null>(null); const viewerRef = useRef<ViewerRef | any>(null);
const navRef = useRef<HTMLDivElement | null>(null); const navRef = useRef<HTMLDivElement | null>(null);
const optionRef = useRef<HTMLDivElement | null>(null); const optionRef = useRef<HTMLDivElement | null>(null);
const learningRef = useRef<HTMLDivElement | null>(null); const learningRef = useRef<HTMLDivElement | null>(null);
@ -142,7 +142,7 @@ const Reader = ({ url, loadingView }: Props) => {
onLearningToggle={onLearningToggle} onLearningToggle={onLearningToggle}
/> />
<Viewer <ReactEpubViewer
url={url} url={url}
viewerLayout={viewerLayout} viewerLayout={viewerLayout}
viewerStyle={bookStyle} viewerStyle={bookStyle}

@ -1,293 +0,0 @@
import React, { useState, useEffect, useCallback, useRef } from 'react'
import { Book, Rendition } from "epubjs";
// style
import EpubViewerWrapper from 'modules/epubViewer/style'
// types
import { EpubViewerProps, ViewerRef } from 'types'
import Toc from 'types/toc'
import Loc from 'types/loc'
/**
* EpubViewer Module
* @class
* @param props
* @param props.url Epub path
* @param props.epubFileOptions Epub file option
* @param props.epubOptions Epub viewer option
* @param props.style Epub Wrapper style
* @param props.location Epub CFI or href
* @param props.bookChanged Run when book changed
* @param props.rendtionChanged Run when rendition changed
* @param props.pageChanged Run when page changed
* @param props.tocChanged Run when toc changed
* @param props.selectionChanged Run when selected
* @param props.loadingView Loading Component
* @param ref Viewer ref
*/
const EpubViewer = ({
url,
epubFileOptions,
epubOptions,
style,
location,
bookChanged,
rendtionChanged,
pageChanged,
tocChanged,
selectionChanged,
loadingView,
}: EpubViewerProps, ref: React.RefObject<ViewerRef> | any) => {
// TODO Fix the ref type correctly instead 'any' type.
const [isLoaded, setIsLoaded] = useState(false);
const [book, setBook] = useState<Book | null>(null);
const [rendition, setRendition] = useState<Rendition | null>(null);
const [toc, setToc] = useState<Toc[]>([]);
const currentCfi = useRef<string>('');
/**
* Init Book
* @method
* - Set book state
* - Set Epub path state
* - Set isLoaded state
* - Set toc state
*/
const initBook = () => {
if (book) {
book.destroy();
}
const book_ = new Book(url, epubFileOptions);
setBook(book_);
bookChanged && bookChanged(book_);
book_.loaded.navigation.then(({ toc }) => {
const toc_: Toc[] = toc.map(t => ({
label: t.label,
href: t.href
}));
setIsLoaded(true);
setToc(toc_);
tocChanged && tocChanged(toc_);
initRendition(book_);
});
// Init Location & Pagination
book_.ready.then(function() {
const stored = localStorage.getItem(book_.key() + '-locations');
if (stored) {
return book_.locations.load(stored);
} else {
return book_.locations.generate(1024);
}
}).then(() => {
localStorage.setItem(book_.key() + '-locations', book_.locations.save());
});
};
/**
* Init Rendition
* @method
* - Init iframe tag on DOM element
* - Set rendition
* - Set start page
*/
const initRendition = (book_: Book) => {
const node = ref.current;
if (!node || !book_) return;
const rendition_ = book_.renderTo(node, {
width: "100%",
height: "100%",
...epubOptions
});
setRendition(rendition_);
rendtionChanged && rendtionChanged(rendition_);
if(typeof location === "string") {
rendition_.display(location);
} else if(toc.length > 0 && toc[0].href) {
rendition_.display(toc[0].href);
} else {
rendition_.display();
}
}
/**
* Move page
* @method
* @param type direction
*/
const movePage = useCallback((type: "PREV" | "NEXT") => {
if (!rendition) return;
if (type === "PREV") rendition.prev();
else rendition.next();
}, [rendition]);
/**
* Move page by arrow key
* @method
* @param props Keyboard Event
* @param props.key
*/
const handleKeyPress = useCallback(({ key }: any) => {
key && key === "ArrowLeft" && movePage("PREV");
key && key === "ArrowRight" && movePage("NEXT");
}, [movePage]);
/**
* Run when location changed
* @method
* @param loc
* - Set location state
* - Run 'locationChanged()' through startCFI
*/
const onLocationChange = useCallback((loc: Loc) => {
const startCfi = loc && loc.start;
const endCfi = loc && loc.end;
const base = loc && loc.start.slice(8).split('!')[0];
if (!book) return;
const spineItem = book.spine.get(startCfi);
const navItem = book.navigation.get(spineItem.href);
const chapterName = navItem && navItem.label.trim();
const locations: any = book.locations;
const currentPage = locations.locationFromCfi(startCfi);
const totalPage = locations.total;
pageChanged && pageChanged({
chapterName,
currentPage,
totalPage,
startCfi,
endCfi,
base
});
currentCfi.current = startCfi;
}, [book, pageChanged]);
/**
* Highlight function
* @param cfiRange Selecton CFIRange
* @param color Highlight color
* @param callback Highlight callback function when click it
*/
const onHighlight = useCallback((
cfiRange: string,
callback: (e: any) => void,
color?: string
) => {
if (!rendition) return;
rendition.annotations.remove(cfiRange, 'highlight');
rendition.annotations.highlight(
cfiRange,
{},
callback,
"epub-highlight",
{ 'fill': color || '#fdf183' }
);
}, [rendition]);
/**
* Register viewer control function
* @method
* - REF.CURRENT.prevPage() : Move prev page
* - REF.CURRENT.nextPage() : Move next page
* - REF.CURRENT.getCurrentCfi() : Get current CFI
* - REF.CURRENT.onHighlight(): Set highlight
* - REF.CURRENT.offHighlight(): Remove specific highliht
* - REF.CURRENT.seLocation(): Move to specific cfi or href
*/
const registerGlobalFunc = useCallback(() => {
if (!ref.current) return;
if (movePage) {
ref.current.prevPage = () => movePage("PREV");
ref.current.nextPage = () => movePage("NEXT");
}
ref.current.getCurrentCfi = () => currentCfi.current;
if (onHighlight) {
ref.current.onHighlight = onHighlight;
}
if (rendition) {
ref.current.offHighlight = (cfiRange: string) => rendition.annotations.remove(cfiRange, 'highlight');
ref.current.setLocation = (location: string) => rendition.display(location);
}
}, [ref, rendition, movePage, onHighlight]);
/** Ref checker */
useEffect(() => {
if (!ref) {
throw new Error("[React-Epub-Viewer] Put a ref argument that has a ViewerRef type.");
}
}, [ref]);
/** Epub Init */
/* eslint-disable */
useEffect(() => {
if (!url) return;
initBook();
}, [url]);
/* eslint-enable */
/**
* Emit Viewer Event
* - Register move event
* - Register location changed event
* - Register selection event
*/
/* eslint-disable */
useEffect(() => {
if (!rendition) return;
// Emit global control function
registerGlobalFunc();
document.addEventListener('keyup', handleKeyPress, false);
rendition.on("keyup", handleKeyPress);
rendition.on("locationChanged", onLocationChange);
selectionChanged && rendition.on('selected', selectionChanged);
return (() => {
document.removeEventListener('keyup', handleKeyPress, false);
rendition.off("keyup", handleKeyPress);
rendition.off("locationChanged", onLocationChange);
selectionChanged && rendition.off('selected', selectionChanged);
});
}, [
rendition,
registerGlobalFunc,
handleKeyPress
]);
/* eslint-enable */
/** Display page by changed location */
useEffect(() => {
if (!location || !rendition) return;
rendition.display(location);
}, [location, rendition]);
return (<>
{!isLoaded && loadingView}
<EpubViewerWrapper ref={ref} style={style} />
</>);
}
export default React.forwardRef(EpubViewer)

@ -1,15 +0,0 @@
import styled from 'styled-components'
const EpubViewerWrapper = styled.div`
width: 100%;
box-sizing: border-box;
margin: 0 auto;
& > div {
&::-webkit-scrollbar {
display: none;
}
}
`;
export default EpubViewerWrapper

@ -1,4 +0,0 @@
import ReactEpubViewer from 'modules/reactViewer/ReactViewer'
import EpubViewer from 'modules/epubViewer/EpubViewer';
export { EpubViewer, ReactEpubViewer };

@ -1,292 +0,0 @@
import React, { useState, useEffect, useRef, useCallback, useMemo } from "react"
import { Book, Rendition, Contents } from "epubjs"
// modules
import EpubViewer from "modules/epubViewer/EpubViewer"
// components
import LoadingView from 'LoadingView'
// utils
import { debounce, timeFormatter } from 'lib/utils/commonUtil'
// styles
import viewerDefaultStyles from 'modules/reactViewer/viewerStyle'
// types
import { ReactViewerProps, ViewerRef } from 'types'
import BookType, { BookStyle, BookOption } from 'types/book'
/**
* Epub React Viewer Module
* @class
* @param props
* @param props.url Epub file path
* @param props.viewerLayout Viewer layout
* @param props.viewerStyle Viewer style
* @param props.viewerOption Viewer option
* @param props.onBookInfoChange Run when book information changed
* @param props.onPageChange Run when page changed
* @param props.onTocChange Run when toc changed
* @param props.onSelection Run when selected
* @param props.loadingView Loading component
* @param ref Viewer ref
*/
const ReactViewer = ({
url,
viewerLayout,
viewerStyle,
viewerOption,
onBookInfoChange,
onPageChange,
onTocChange,
onSelection,
loadingView
}: ReactViewerProps, ref: React.RefObject<ViewerRef> | any) => {
// TODO Fix the ref type correctly instead 'any' type.
const [book, setBook] = useState<Book | null>(null);
const [rendition, setRendition] = useState<Rendition | null>(null);
const [layoutStyle, setLayoutStyle] = useState<Object>({});
const [bookStyle, setBookStyle] = useState<BookStyle>({
fontFamily: 'Origin',
fontSize: 18,
lineHeight: 1.4,
marginHorizontal: 15,
marginVertical: 5
});
const [bookOption, setBookOption] = useState<BookOption>({
flow: "paginated",
resizeOnOrientationChange: true,
spread: "auto"
});
const currentSelection = useRef<{cfiRange:string, contents:Contents | null}>({
cfiRange: '',
contents: null
});
/**
* Run book changed
* @method
* @param book Epub Book
*/
const bookChanged = (book: Book) => setBook(book);
/**
* Run rendition changed
* @method
* @param rendition Epub Rendition
*/
const rendtionChanged = (rendition: Rendition) => setRendition(rendition);
/**
* Run selection changed [Debounce]
* @method
* @param cfiRange CFIRange
* @param contents Selection Epub Contents
*/
const selectionChanged = (cfiRange: string, contents: Contents) => {
currentSelection.current = { cfiRange, contents };
}
/**
* Viewer resizing function
* @method
*/
const onResize = useMemo(() => debounce(250, () => {
if (!rendition) return;
const viewerLayout_ = viewerLayout || {
MIN_VIEWER_WIDTH: 600,
MIN_VIEWER_HEIGHT: 300,
VIEWER_HEADER_HEIGHT: 0,
VIEWER_FOOTER_HEIGHT: 0,
VIEWER_SIDEMENU_WIDTH: 0
};
const { innerWidth: win_w, innerHeight: win_h } = window;
const componentHeight = viewerLayout_.VIEWER_HEADER_HEIGHT + viewerLayout_.VIEWER_FOOTER_HEIGHT;
const w = win_w - ~~((win_w - viewerLayout_.MIN_VIEWER_WIDTH) / 100 * bookStyle.marginHorizontal);
const h = bookOption.flow === "scrolled-doc"
? win_h - componentHeight
: win_h - componentHeight - ~~((win_h - componentHeight - viewerLayout_.MIN_VIEWER_HEIGHT) / 100 * bookStyle.marginVertical);
const marginTop = bookOption.flow === "scrolled-doc"
? ""
: `${~~((win_h - componentHeight - viewerLayout_.MIN_VIEWER_HEIGHT) / 100 * bookStyle.marginVertical) / 2}px`;
setLayoutStyle(l => ({
...l,
width: w,
height: h,
marginTop
}));
try {
rendition.resize(w, h);
} catch { }
}), [
rendition,
viewerLayout,
bookStyle.marginHorizontal,
bookStyle.marginVertical,
bookOption.flow
]);
/**
* Selection Event, run when run mouseup event
* @method <br/>
* - Fire after the Epubjs selected event. [about 300ms]
*/
const onSelected = useCallback(async () => {
if (!ref.current) return;
const iframe = ref.current.querySelector('iframe');
if (!iframe) return;
const iframeWin = iframe.contentWindow;
if (!iframeWin) return;
const selection_ = iframeWin.getSelection();
if (!selection_) return;
const selectionText = selection_.toString();
if (selectionText === "") return;
const cfiRange: string = await new Promise(resolve =>
setTimeout(() => resolve(currentSelection.current.cfiRange), 350)
);
if (!cfiRange) return;
const contents = currentSelection.current.contents;
if (!contents) return;
onSelection && onSelection(cfiRange, contents);
}, [ref, onSelection]);
/** Ref checker */
useEffect(() => {
if (!ref) {
throw new Error("[React-Epub-Viewer] Put a ref argument that has a ViewerRef type.");
}
}, [ref]);
/** Epub parsing */
// TODO Fix the infinite re-rendering issue, when inlcude `onBookInfoChange` to dependencies array.
/* eslint-disable */
useEffect(() => {
if (!book) return;
Promise.all([
book.loaded.metadata,
book.opened
])
.then(([metaData, bookData]: any[]) => {
const newBookData: BookType = {
coverURL: bookData.archive.urlCache[bookData.cover],
title: metaData.title,
description: metaData.description,
published_date: timeFormatter(new Date(metaData.pubdate)),
modified_date: timeFormatter(new Date(metaData.modified_date)),
author: metaData.creator,
publisher: metaData.publisher,
language: metaData.language
}
onBookInfoChange && onBookInfoChange(newBookData);
})
.catch(error => {
throw `${error.stack} \n\n Message : Epub parsing failed.`;
});
}, [book]);
/* eslint-enable */
/** Set viewer Styles/Options */
useEffect(() => {
viewerStyle && setBookStyle(viewerStyle);
viewerOption && setBookOption(viewerOption);
}, [viewerStyle, viewerOption]);
/** Apply viewer Styles/Options */
useEffect(() => {
if (!rendition) return;
onResize();
const newStyle = {
"body": {
"padding-top": "0px !important",
"padding-bottom": "0px !important"
},
"p": {
"font-size": `${bookStyle.fontSize}px !important`,
"line-height": `${bookStyle.lineHeight} !important`
},
};
rendition.flow(bookOption.flow);
rendition.spread(bookOption.spread);
if (bookStyle.fontFamily !== "Origin") {
Object.assign(newStyle.body, {
"font-family": `${bookStyle.fontFamily} !important`
});
}
if (bookOption.flow === "scrolled-doc") { // Scroll type
Object.assign(newStyle.body, {
"margin": "auto !important"
});
} else if (bookOption.spread === "auto") { // View 2 pages
Object.assign(newStyle.body, { });
} else { // View 1 page
Object.assign(newStyle.body, { });
}
rendition.themes.register("main", viewerDefaultStyles);
rendition.themes.register("default", newStyle);
rendition.themes.select("main");
}, [
rendition,
bookStyle.fontFamily,
bookStyle.fontSize,
bookStyle.lineHeight,
bookOption,
onResize
]);
/** Emit screen resizing event */
useEffect(() => {
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, [onResize]);
/** Emit selection event */
useEffect(() => {
if (!rendition) return;
rendition.on("mouseup", onSelected);
return () => rendition.off("mouseup", onSelected);
}, [rendition, onSelected]);
return (<>
<EpubViewer
url={url}
style={layoutStyle}
bookChanged={bookChanged}
rendtionChanged={rendtionChanged}
tocChanged={onTocChange}
pageChanged={onPageChange}
selectionChanged={selectionChanged}
loadingView={loadingView || <LoadingView />}
ref={ref}
/>
</>);
}
export default React.forwardRef(ReactViewer)

@ -1,16 +0,0 @@
const viewerStyle = {
"::selection": {
"background-color": "#d4e9ff"
},
"img": {
"-webkit-touch-callout": "none",
"-webkit-user-select": "none",
"-khtml-user-select": "none",
"-moz-user-select": "none",
"-ms-user-select": "none",
"user-select": "none"
}
}
export default viewerStyle

@ -9522,6 +9522,13 @@ react-dom@^17.0.2:
object-assign "^4.1.1" object-assign "^4.1.1"
scheduler "^0.20.2" scheduler "^0.20.2"
react-epub-viewer@^0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/react-epub-viewer/-/react-epub-viewer-0.1.7.tgz#4360bdb4ca3f7e0df22b813d4323ae1290be2f60"
integrity sha512-QWEi0GxDCGOCONC3GNsLP4K6KU23c9SuW55U/5C2rzWnJuTVWrx4d3qivqwTH+ypMhJ0UXG4+xxUSdz1dZbtqw==
dependencies:
epubjs "^0.3.88"
react-error-overlay@^6.0.9: react-error-overlay@^6.0.9:
version "6.0.9" version "6.0.9"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"

Loading…
Cancel
Save