Former-commit-id: 180a61da604f847884b08890d2df33122625691c
This commit is contained in:
troyeguo
2021-07-17 22:53:00 +08:00
parent 077101d163
commit 41fdff135e
13 changed files with 951 additions and 115 deletions

View File

@@ -1,6 +1,7 @@
const { app, BrowserWindow, nativeImage } = require("electron");
const { app, BrowserWindow, Menu, remote, ipcMain } = require("electron");
const { ebtMain } = require("electron-baidu-tongji");
const path = require("path");
const isDev = require("electron-is-dev");
let mainWin;
let readerWindow;
const singleInstance = app.requestSingleInstanceLock();
@@ -38,9 +39,8 @@ app.on("ready", () => {
};
mainWin = new BrowserWindow(option);
const isDev = require("electron-is-dev");
if (!isDev) {
const { Menu } = require("electron");
Menu.setApplicationMenu(null);
}
@@ -49,7 +49,7 @@ app.on("ready", () => {
: `file://${path.join(__dirname, "./build/index.html")}`;
mainWin.loadURL(urlLocation);
const { remote, ipcMain } = require("electron");
ebtMain(ipcMain, isDev);
mainWin.on("close", () => {
mainWin = null;

View File

@@ -0,0 +1,371 @@
import React from "react";
import EpubViewer from "../epubViewer";
import Background from "../background";
import SettingPanel from "../panels/settingPanel";
import NavigationPanel from "../panels/navigationPanel";
import OperationPanel from "../panels/operationPanel";
import MessageBox from "../messageBox";
import ProgressPanel from "../panels/progressPanel";
import { ReaderProps, ReaderState } from "./interface";
import { MouseEvent } from "../../utils/mouseEvent";
import OtherUtil from "../../utils/otherUtil";
import ReadingTime from "../../utils/readUtils/readingTime";
class Reader extends React.Component<ReaderProps, ReaderState> {
messageTimer!: NodeJS.Timeout;
tickTimer!: NodeJS.Timeout;
rendition: any;
constructor(props: ReaderProps) {
super(props);
this.state = {
isOpenRightPanel:
OtherUtil.getReaderConfig("isSettingLocked") === "yes" ? true : false,
isOpenTopPanel: false,
isOpenBottomPanel: false,
isOpenLeftPanel:
OtherUtil.getReaderConfig("isNavLocked") === "yes" ? true : false,
isMessage: false,
rendition: null,
scale: OtherUtil.getReaderConfig("scale") || 1,
margin: parseInt(OtherUtil.getReaderConfig("margin")) || 30,
time: ReadingTime.getTime(this.props.currentBook.key),
isTouch: OtherUtil.getReaderConfig("isTouch") === "yes",
readerMode: OtherUtil.getReaderConfig("readerMode") || "double",
};
}
componentWillMount() {
this.props.handleFetchBookmarks();
this.props.handleFetchPercentage(this.props.currentBook);
this.props.handleFetchNotes();
this.props.handleFetchBooks();
this.props.handleFetchChapters(this.props.currentEpub);
}
UNSAFE_componentWillReceiveProps(nextProps: ReaderProps) {
this.setState({
isMessage: nextProps.isMessage,
});
//控制消息提示两秒之后消失
if (nextProps.isMessage) {
this.messageTimer = global.setTimeout(() => {
this.props.handleMessageBox(false);
this.setState({ isMessage: false });
}, 2000);
}
}
componentDidMount() {
this.handleRenderBook();
this.props.handleRenderFunc(this.handleRenderBook);
window.addEventListener("resize", () => {
this.handleRenderBook();
});
this.tickTimer = global.setInterval(() => {
let time = this.state.time;
time += 1;
let page = document.querySelector("#page-area");
//解决快速翻页过程中图书消失的bug
let renderedBook = document.querySelector(".epub-view");
if (
renderedBook &&
!renderedBook.innerHTML &&
this.state.readerMode !== "continuous"
) {
this.handleRenderBook();
}
let ele = page!.getElementsByClassName("epub-container")[0];
if (page!.getElementsByClassName("epub-container").length > 1 && ele) {
ele.parentNode?.removeChild(ele);
}
this.setState({ time });
this.handleRecord();
}, 1000);
}
componentWillUnmount() {
clearInterval(this.tickTimer);
}
handleRenderBook = () => {
let page = document.querySelector("#page-area");
let epub = this.props.currentEpub;
if (page!.innerHTML) {
page!.innerHTML = "";
}
this.setState({ rendition: null }, () => {
(window as any).rangy.init(); // 初始化
this.rendition = epub.renderTo(page, {
manager:
this.state.readerMode === "continuous" ? "continuous" : "default",
flow: this.state.readerMode === "continuous" ? "scrolled" : "auto",
width: "100%",
height: "100%",
snap: true,
spread:
OtherUtil.getReaderConfig("readerMode") === "single" ? "none" : "",
});
this.setState({ rendition: this.rendition });
this.state.readerMode !== "continuous" && MouseEvent(this.rendition); // 绑定事件
});
};
handleRecord() {
OtherUtil.setReaderConfig("isFullScreen", "no");
OtherUtil.setReaderConfig("windowWidth", document.body.clientWidth + "");
OtherUtil.setReaderConfig("windowHeight", document.body.clientHeight + "");
OtherUtil.setReaderConfig("windowX", window.screenX + "");
OtherUtil.setReaderConfig("windowY", window.screenY + "");
}
//进入阅读器
handleEnterReader = (position: string) => {
//控制上下左右的菜单的显示
switch (position) {
case "right":
this.setState({
isOpenRightPanel: this.state.isOpenRightPanel ? false : true,
});
break;
case "left":
this.setState({
isOpenLeftPanel: this.state.isOpenLeftPanel ? false : true,
});
break;
case "top":
this.setState({
isOpenTopPanel: this.state.isOpenTopPanel ? false : true,
});
break;
case "bottom":
this.setState({
isOpenBottomPanel: this.state.isOpenBottomPanel ? false : true,
});
break;
default:
break;
}
};
//退出阅读器
handleLeaveReader = (position: string) => {
//控制上下左右的菜单的显示
switch (position) {
case "right":
if (OtherUtil.getReaderConfig("isSettingLocked") === "yes") {
break;
} else {
this.setState({ isOpenRightPanel: false });
break;
}
case "left":
if (OtherUtil.getReaderConfig("isNavLocked") === "yes") {
break;
} else {
this.setState({ isOpenLeftPanel: false });
break;
}
case "top":
this.setState({ isOpenTopPanel: false });
break;
case "bottom":
this.setState({ isOpenBottomPanel: false });
break;
default:
break;
}
};
nextPage = () => {
this.state.rendition.next();
};
prevPage = () => {
this.state.rendition.prev();
};
render() {
const renditionProps = {
rendition: this.state.rendition,
handleLeaveReader: this.handleLeaveReader,
handleEnterReader: this.handleEnterReader,
isShow:
this.state.isOpenLeftPanel ||
this.state.isOpenTopPanel ||
this.state.isOpenBottomPanel ||
this.state.isOpenRightPanel,
};
return (
<div className="viewer">
{OtherUtil.getReaderConfig("isHidePageButton") !== "yes" && (
<>
<div
className="previous-chapter-single-container"
onClick={() => {
this.prevPage();
}}
>
<span className="icon-dropdown previous-chapter-single"></span>
</div>
<div
className="next-chapter-single-container"
onClick={() => {
this.nextPage();
}}
>
<span className="icon-dropdown next-chapter-single"></span>
</div>
</>
)}
<div
className="reader-setting-icon-container"
onClick={() => {
this.handleEnterReader("left");
this.handleEnterReader("right");
this.handleEnterReader("bottom");
this.handleEnterReader("top");
}}
>
<span className="icon-grid reader-setting-icon"></span>
</div>
{this.state.isMessage ? <MessageBox /> : null}
<div
className="left-panel"
onMouseEnter={() => {
if (this.state.isTouch || this.state.isOpenLeftPanel) {
return;
}
this.handleEnterReader("left");
}}
onClick={() => {
this.handleEnterReader("left");
}}
></div>
<div
className="right-panel"
onMouseEnter={() => {
if (this.state.isTouch || this.state.isOpenRightPanel) {
return;
}
this.handleEnterReader("right");
}}
onClick={() => {
this.handleEnterReader("right");
}}
></div>
<div
className="top-panel"
onMouseEnter={() => {
if (this.state.isTouch || this.state.isOpenTopPanel) {
return;
}
this.handleEnterReader("top");
}}
onClick={() => {
this.handleEnterReader("top");
}}
></div>
<div
className="bottom-panel"
onMouseEnter={() => {
if (this.state.isTouch || this.state.isOpenBottomPanel) {
return;
}
this.handleEnterReader("bottom");
}}
onClick={() => {
this.handleEnterReader("bottom");
}}
></div>
{this.state.rendition && this.props.currentEpub.rendition && (
<EpubViewer {...renditionProps} />
)}
<div
className="setting-panel-container"
onMouseLeave={(event) => {
this.handleLeaveReader("right");
}}
style={
this.state.isOpenRightPanel
? {}
: {
transform: "translateX(309px)",
}
}
>
<SettingPanel />
</div>
<div
className="navigation-panel-container"
onMouseLeave={(event) => {
this.handleLeaveReader("left");
}}
style={
this.state.isOpenLeftPanel
? {}
: {
transform: "translateX(-309px)",
}
}
>
<NavigationPanel {...{ time: this.state.time }} />
</div>
<div
className="progress-panel-container"
onMouseLeave={(event) => {
this.handleLeaveReader("bottom");
}}
style={
this.state.isOpenBottomPanel
? {}
: {
transform: "translateY(110px)",
}
}
>
<ProgressPanel {...{ time: this.state.time }} />
</div>
<div
className="operation-panel-container"
onMouseLeave={(event) => {
this.handleLeaveReader("top");
}}
style={
this.state.isOpenTopPanel
? {}
: {
transform: "translateY(-110px)",
}
}
>
<OperationPanel {...{ time: this.state.time }} />
</div>
<div
className="view-area-page"
id="page-area"
style={
document.body.clientWidth < 570
? { left: 0, right: 0 }
: this.state.readerMode === "continuous"
? {
left: `calc(50vw - ${270 * parseFloat(this.state.scale)}px)`,
right: `calc(50vw - ${270 * parseFloat(this.state.scale)}px)`,
top: "75px",
bottom: "75px",
}
: this.state.readerMode === "single"
? {
left: `calc(50vw - ${270 * parseFloat(this.state.scale)}px)`,
right: `calc(50vw - ${270 * parseFloat(this.state.scale)}px)`,
}
: this.state.readerMode === "double"
? {
left: this.state.margin - 40 + "px",
right: this.state.margin - 40 + "px",
}
: {}
}
></div>
<Background {...{ time: this.state.time }} />
</div>
);
}
}
export default Reader;

View File

@@ -0,0 +1,220 @@
.viewer {
height: 100%;
}
.view-area {
height: 100%;
touch-action: none;
position: relative;
}
@keyframes fade-right {
0% {
transform: translateX(200px);
opacity: 0;
}
100% {
transform: translateX(0px);
opacity: 1;
}
}
@keyframes fade-left {
0% {
transform: translateX(-200px);
opacity: 0;
}
100% {
transform: translateX(0px);
opacity: 1;
}
}
.background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
z-index: -10;
display: flex;
justify-content: center;
align-items: center;
}
@keyframes fade-down {
0% {
transform: translateY(-60px);
opacity: 0;
}
100% {
transform: translateX(0px);
opacity: 1;
}
}
@keyframes fade-up {
0% {
transform: translateY(60px);
opacity: 0;
}
100% {
transform: translateX(0px);
opacity: 1;
}
}
.left-panel {
width: 50px;
height: 200px;
position: absolute;
left: 0px;
top: calc(50vh - 100px);
background-color: pink;
z-index: 10;
opacity: 0;
}
.right-panel {
width: 50px;
height: 200px;
position: absolute;
right: 0px;
top: calc(50vh - 100px);
background-color: pink;
z-index: 10;
opacity: 0;
}
.top-panel {
width: 200px;
height: 50px;
position: absolute;
right: calc(50vw - 100px);
top: 0px;
background-color: pink;
z-index: 10;
opacity: 0;
}
.bottom-panel {
width: 200px;
height: 50px;
position: absolute;
right: calc(50vw - 100px);
top: calc(100vh - 50px);
background-color: pink;
z-index: 10;
opacity: 0;
}
.operation-panel-container {
width: 412px;
height: 60px;
position: absolute;
top: 0px;
left: calc(50vw - 206px);
z-index: 15;
transition: transform 0.5s ease;
}
.progress-panel-container {
width: 412px;
height: 60px;
position: absolute;
top: calc(100vh - 60px);
left: calc(50vw - 206px);
z-index: 15;
transition: transform 0.5s ease;
}
.setting-panel-container {
width: 299px;
height: 100vh;
position: absolute;
top: 0px;
right: 0px;
transition: transform 0.5s ease;
z-index: 15;
}
.navigation-panel-container {
width: 299px;
height: 100vh;
position: absolute;
top: 0px;
left: 0px;
transition: transform 0.5s ease;
z-index: 15;
}
.view-area-page {
position: absolute;
left: 0px;
right: 0px;
top: 25px;
bottom: 20px;
/* width: calc(100% - 100px);
height: calc(100% - 100px); */
z-index: 0;
user-select: text;
}
.previous-chapter-single-container {
width: 50px !important;
height: 50px !important;
border-radius: 50%;
position: absolute;
bottom: 20px;
left: 20px;
font-size: 30px;
transform: rotate(90deg);
cursor: pointer;
opacity: 0.2;
display: flex;
justify-content: center;
align-items: center;
z-index: 5;
}
.next-chapter-single-container {
width: 50px;
height: 50px;
border-radius: 50%;
position: absolute;
bottom: 20px;
right: 20px;
transform: rotate(-90deg);
cursor: pointer;
opacity: 0.2;
display: flex;
justify-content: center;
align-items: center;
z-index: 5;
}
.next-chapter-single-container:hover {
opacity: 0.7;
}
.previous-chapter-single-container:hover {
opacity: 0.5;
}
.previous-chapter-single {
height: 20px;
font-size: 20px;
}
.next-chapter-single {
font-size: 20px;
height: 20px;
}
.reader-setting-icon {
margin: 8px;
opacity: 0.5;
}
.reader-setting-icon-container {
width: 50px;
height: 50px;
position: absolute;
top: 0px;
right: 20px;
font-size: 22px;
cursor: pointer;
z-index: 10;
transition: 0.1s;
line-height: 30px;
display: flex;
justify-content: center;
align-items: center;
transition: 0, 2s;
}
.reader-setting-icon-container:hover {
border-radius: 50%;
}

View File

@@ -0,0 +1,32 @@
import {
handleFetchNotes,
handleFetchBookmarks,
handleFetchChapters,
handleFetchPercentage,
handleMessageBox,
handleFetchBooks,
handleRenderFunc,
} from "../../store/actions";
import "./index.css";
import { connect } from "react-redux";
import { stateType } from "../../store";
import Reader from "./component";
const mapStateToProps = (state: stateType) => {
return {
currentEpub: state.book.currentEpub,
currentBook: state.book.currentBook,
isMessage: state.manager.isMessage,
};
};
const actionCreator = {
handleFetchNotes,
handleFetchBookmarks,
handleFetchChapters,
handleMessageBox,
handleFetchPercentage,
handleFetchBooks,
handleRenderFunc,
};
export default connect(mapStateToProps, actionCreator)(Reader);

View File

@@ -0,0 +1,27 @@
import BookModel from "../../model/Book";
export interface ReaderProps {
currentEpub: any;
currentBook: BookModel;
isMessage: boolean;
handleFetchNotes: () => void;
handleFetchBooks: () => void;
handleRenderFunc: (renderFunc: () => void) => void;
handleFetchBookmarks: () => void;
handleMessageBox: (isShow: boolean) => void;
handleFetchPercentage: (currentBook: BookModel) => void;
handleFetchChapters: (currentEpub: any) => void;
}
export interface ReaderState {
isOpenRightPanel: boolean;
isOpenTopPanel: boolean;
isOpenBottomPanel: boolean;
isOpenLeftPanel: boolean;
isMessage: boolean;
isTouch: boolean;
readerMode: string;
rendition: any;
time: number;
scale: string;
margin: number;
}

View File

@@ -1,5 +1,5 @@
import React from "react";
import EpubViewer from "../epubViewer";
import ViewArea from "../viewArea";
import Background from "../background";
import SettingPanel from "../panels/settingPanel";
import NavigationPanel from "../panels/navigationPanel";
@@ -12,18 +12,18 @@ import OtherUtil from "../../utils/otherUtil";
import ReadingTime from "../../utils/readUtils/readingTime";
class Reader extends React.Component<ReaderProps, ReaderState> {
messageTimer!: NodeJS.Timeout;
tickTimer!: NodeJS.Timeout;
messageTimer!: any;
tickTimer!: any;
rendition: any;
constructor(props: ReaderProps) {
super(props);
this.state = {
isOpenRightPanel:
isOpenSettingPanel:
OtherUtil.getReaderConfig("isSettingLocked") === "yes" ? true : false,
isOpenTopPanel: false,
isOpenBottomPanel: false,
isOpenLeftPanel:
isOpenOperationPanel: false,
isOpenProgressPanel: false,
isOpenNavPanel:
OtherUtil.getReaderConfig("isNavLocked") === "yes" ? true : false,
isMessage: false,
rendition: null,
@@ -48,7 +48,7 @@ class Reader extends React.Component<ReaderProps, ReaderState> {
//控制消息提示两秒之后消失
if (nextProps.isMessage) {
this.messageTimer = global.setTimeout(() => {
this.messageTimer = setTimeout(() => {
this.props.handleMessageBox(false);
this.setState({ isMessage: false });
}, 2000);
@@ -56,87 +56,55 @@ class Reader extends React.Component<ReaderProps, ReaderState> {
}
componentDidMount() {
this.handleRenderBook();
this.props.handleRenderFunc(this.handleRenderBook);
window.addEventListener("resize", () => {
this.handleRenderBook();
});
this.tickTimer = global.setInterval(() => {
let time = this.state.time;
time += 1;
let page = document.querySelector("#page-area");
//解决快速翻页过程中图书消失的bug
let renderedBook = document.querySelector(".epub-view");
if (
renderedBook &&
!renderedBook.innerHTML &&
this.state.readerMode !== "continuous"
) {
this.handleRenderBook();
}
let ele = page!.getElementsByClassName("epub-container")[0];
if (page!.getElementsByClassName("epub-container").length > 1 && ele) {
ele.parentNode?.removeChild(ele);
}
this.setState({ time });
this.handleRecord();
}, 1000);
}
componentWillUnmount() {
clearInterval(this.tickTimer);
}
handleRenderBook = () => {
let page = document.querySelector("#page-area");
let epub = this.props.currentEpub;
if (page!.innerHTML) {
page!.innerHTML = "";
}
this.setState({ rendition: null }, () => {
(window as any).rangy.init(); // 初始化
this.rendition = epub.renderTo(page, {
manager:
this.state.readerMode === "continuous" ? "continuous" : "default",
flow: this.state.readerMode === "continuous" ? "scrolled" : "auto",
width: "100%",
height: "100%",
snap: true,
spread:
OtherUtil.getReaderConfig("readerMode") === "single" ? "none" : "",
});
this.setState({ rendition: this.rendition });
this.state.readerMode !== "continuous" && MouseEvent(this.rendition); // 绑定事件
(window as any).rangy.init(); // 初始化
this.rendition = epub.renderTo(page, {
manager:
this.state.readerMode === "continuous" ? "continuous" : "default",
flow: this.state.readerMode === "continuous" ? "scrolled" : "auto",
width: "100%",
height: "100%",
snap: true,
spread:
OtherUtil.getReaderConfig("readerMode") === "single" ? "none" : "",
});
this.setState({ rendition: this.rendition });
this.state.readerMode !== "continuous" && MouseEvent(this.rendition); // 绑定事件
this.tickTimer = setInterval(() => {
let time = this.state.time;
time += 1;
this.setState({ time });
}, 1000);
};
handleRecord() {
OtherUtil.setReaderConfig("isFullScreen", "no");
OtherUtil.setReaderConfig("windowWidth", document.body.clientWidth + "");
OtherUtil.setReaderConfig("windowHeight", document.body.clientHeight + "");
OtherUtil.setReaderConfig("windowX", window.screenX + "");
OtherUtil.setReaderConfig("windowY", window.screenY + "");
}
//进入阅读器
handleEnterReader = (position: string) => {
//控制上下左右的菜单的显示
switch (position) {
case "right":
this.setState({
isOpenRightPanel: this.state.isOpenRightPanel ? false : true,
isOpenSettingPanel: this.state.isOpenSettingPanel ? false : true,
});
break;
case "left":
this.setState({
isOpenLeftPanel: this.state.isOpenLeftPanel ? false : true,
isOpenNavPanel: this.state.isOpenNavPanel ? false : true,
});
break;
case "top":
this.setState({
isOpenTopPanel: this.state.isOpenTopPanel ? false : true,
isOpenOperationPanel: this.state.isOpenOperationPanel ? false : true,
});
break;
case "bottom":
this.setState({
isOpenBottomPanel: this.state.isOpenBottomPanel ? false : true,
isOpenProgressPanel: this.state.isOpenProgressPanel ? false : true,
});
break;
default:
@@ -151,7 +119,7 @@ class Reader extends React.Component<ReaderProps, ReaderState> {
if (OtherUtil.getReaderConfig("isSettingLocked") === "yes") {
break;
} else {
this.setState({ isOpenRightPanel: false });
this.setState({ isOpenSettingPanel: false });
break;
}
@@ -159,14 +127,14 @@ class Reader extends React.Component<ReaderProps, ReaderState> {
if (OtherUtil.getReaderConfig("isNavLocked") === "yes") {
break;
} else {
this.setState({ isOpenLeftPanel: false });
this.setState({ isOpenNavPanel: false });
break;
}
case "top":
this.setState({ isOpenTopPanel: false });
this.setState({ isOpenOperationPanel: false });
break;
case "bottom":
this.setState({ isOpenBottomPanel: false });
this.setState({ isOpenProgressPanel: false });
break;
default:
break;
@@ -184,33 +152,38 @@ class Reader extends React.Component<ReaderProps, ReaderState> {
handleLeaveReader: this.handleLeaveReader,
handleEnterReader: this.handleEnterReader,
isShow:
this.state.isOpenLeftPanel ||
this.state.isOpenTopPanel ||
this.state.isOpenBottomPanel ||
this.state.isOpenRightPanel,
this.state.isOpenNavPanel ||
this.state.isOpenOperationPanel ||
this.state.isOpenProgressPanel ||
this.state.isOpenSettingPanel,
};
return (
<div className="viewer">
{OtherUtil.getReaderConfig("isHidePageButton") !== "yes" && (
<>
<div
className="previous-chapter-single-container"
onClick={() => {
this.prevPage();
}}
>
<span className="icon-dropdown previous-chapter-single"></span>
</div>
<div
className="next-chapter-single-container"
onClick={() => {
this.nextPage();
}}
>
<span className="icon-dropdown next-chapter-single"></span>
</div>
</>
)}
<div
className="viewer"
style={{
filter: `brightness(${
OtherUtil.getReaderConfig("brightness") || 1
}) invert(${
OtherUtil.getReaderConfig("isInvert") === "yes" ? 1 : 0
})`,
}}
>
<div
className="previous-chapter-single-container"
onClick={() => {
this.prevPage();
}}
>
<span className="icon-dropdown previous-chapter-single"></span>
</div>
<div
className="next-chapter-single-container"
onClick={() => {
this.nextPage();
}}
>
<span className="icon-dropdown next-chapter-single"></span>
</div>
<div
className="reader-setting-icon-container"
onClick={() => {
@@ -226,7 +199,7 @@ class Reader extends React.Component<ReaderProps, ReaderState> {
<div
className="left-panel"
onMouseEnter={() => {
if (this.state.isTouch || this.state.isOpenLeftPanel) {
if (this.state.isTouch || this.state.isOpenNavPanel) {
return;
}
this.handleEnterReader("left");
@@ -238,7 +211,7 @@ class Reader extends React.Component<ReaderProps, ReaderState> {
<div
className="right-panel"
onMouseEnter={() => {
if (this.state.isTouch || this.state.isOpenRightPanel) {
if (this.state.isTouch || this.state.isOpenSettingPanel) {
return;
}
this.handleEnterReader("right");
@@ -250,7 +223,7 @@ class Reader extends React.Component<ReaderProps, ReaderState> {
<div
className="top-panel"
onMouseEnter={() => {
if (this.state.isTouch || this.state.isOpenTopPanel) {
if (this.state.isTouch || this.state.isOpenOperationPanel) {
return;
}
this.handleEnterReader("top");
@@ -262,7 +235,7 @@ class Reader extends React.Component<ReaderProps, ReaderState> {
<div
className="bottom-panel"
onMouseEnter={() => {
if (this.state.isTouch || this.state.isOpenBottomPanel) {
if (this.state.isTouch || this.state.isOpenProgressPanel) {
return;
}
this.handleEnterReader("bottom");
@@ -273,7 +246,7 @@ class Reader extends React.Component<ReaderProps, ReaderState> {
></div>
{this.state.rendition && this.props.currentEpub.rendition && (
<EpubViewer {...renditionProps} />
<ViewArea {...renditionProps} />
)}
<div
className="setting-panel-container"
@@ -281,7 +254,7 @@ class Reader extends React.Component<ReaderProps, ReaderState> {
this.handleLeaveReader("right");
}}
style={
this.state.isOpenRightPanel
this.state.isOpenSettingPanel
? {}
: {
transform: "translateX(309px)",
@@ -296,7 +269,7 @@ class Reader extends React.Component<ReaderProps, ReaderState> {
this.handleLeaveReader("left");
}}
style={
this.state.isOpenLeftPanel
this.state.isOpenNavPanel
? {}
: {
transform: "translateX(-309px)",
@@ -311,7 +284,7 @@ class Reader extends React.Component<ReaderProps, ReaderState> {
this.handleLeaveReader("bottom");
}}
style={
this.state.isOpenBottomPanel
this.state.isOpenProgressPanel
? {}
: {
transform: "translateY(110px)",
@@ -326,7 +299,7 @@ class Reader extends React.Component<ReaderProps, ReaderState> {
this.handleLeaveReader("top");
}}
style={
this.state.isOpenTopPanel
this.state.isOpenOperationPanel
? {}
: {
transform: "translateY(-110px)",
@@ -335,6 +308,7 @@ class Reader extends React.Component<ReaderProps, ReaderState> {
>
<OperationPanel {...{ time: this.state.time }} />
</div>
<div
className="view-area-page"
id="page-area"

View File

@@ -2,13 +2,13 @@ import {
handleFetchNotes,
handleFetchBookmarks,
handleFetchChapters,
handleFetchPercentage,
} from "../../store/actions/reader";
import { handleFetchPercentage } from "../../store/actions/progressPanel";
import {
handleMessageBox,
handleFetchBooks,
handleRenderFunc,
} from "../../store/actions";
import "./index.css";
} from "../../store/actions/manager";
import "./epubViewer.css";
import { connect } from "react-redux";
import { stateType } from "../../store";
import Reader from "./component";
@@ -27,6 +27,5 @@ const actionCreator = {
handleMessageBox,
handleFetchPercentage,
handleFetchBooks,
handleRenderFunc,
};
export default connect(mapStateToProps, actionCreator)(Reader);

View File

@@ -5,7 +5,6 @@ export interface ReaderProps {
isMessage: boolean;
handleFetchNotes: () => void;
handleFetchBooks: () => void;
handleRenderFunc: (renderFunc: () => void) => void;
handleFetchBookmarks: () => void;
handleMessageBox: (isShow: boolean) => void;
handleFetchPercentage: (currentBook: BookModel) => void;
@@ -13,10 +12,10 @@ export interface ReaderProps {
}
export interface ReaderState {
isOpenRightPanel: boolean;
isOpenTopPanel: boolean;
isOpenBottomPanel: boolean;
isOpenLeftPanel: boolean;
isOpenSettingPanel: boolean;
isOpenOperationPanel: boolean;
isOpenProgressPanel: boolean;
isOpenNavPanel: boolean;
isMessage: boolean;
isTouch: boolean;
readerMode: string;

View File

@@ -0,0 +1,124 @@
//阅读器图书内容区域
import React from "react";
import PopupMenu from "../../components/popups/popupMenu";
import { ViewAreaProps, ViewAreaStates } from "./interface";
import RecordLocation from "../../utils/readUtils/recordLocation";
import BookmarkModel from "../../model/Bookmark";
import StyleUtil from "../../utils/readUtils/styleUtil";
import ImageViewer from "../../components/imageViewer";
import Lottie from "react-lottie";
import animationSiri from "../../assets/lotties/siri.json";
const siriOptions = {
loop: true,
autoplay: true,
animationData: animationSiri,
rendererSettings: {
preserveAspectRatio: "xMidYMid slice",
},
};
declare var window: any;
class EpubViewer extends React.Component<ViewAreaProps, ViewAreaStates> {
isFirst: boolean;
constructor(props: ViewAreaProps) {
super(props);
this.state = {
cfiRange: null,
contents: null,
rect: null,
loading: true,
};
this.isFirst = true;
}
componentDidMount() {
let epub = this.props.currentEpub;
window.rangy.init(); // 初始化
this.props.rendition.on("locationChanged", () => {
this.props.handleReadingEpub(epub);
this.props.handleOpenMenu(false);
const currentLocation = this.props.rendition.currentLocation();
if (!currentLocation.start) {
return;
}
const cfi = currentLocation.start.cfi;
this.props.handleShowBookmark(
this.props.bookmarks &&
this.props.bookmarks.filter(
(item: BookmarkModel) => item.cfi === cfi
)[0]
? true
: false
);
if (!this.isFirst && this.props.locations) {
let percentage = this.props.locations.percentageFromCfi(cfi);
RecordLocation.recordCfi(this.props.currentBook.key, cfi, percentage);
this.props.handlePercentage(percentage);
} else if (!this.isFirst) {
//如果过暂时没有解析出locations就直接记录cfi
RecordLocation.recordCfi(
this.props.currentBook.key,
cfi,
RecordLocation.getCfi(this.props.currentBook.key).percentage
);
}
this.isFirst = false;
});
this.props.rendition.on("rendered", () => {
this.setState({ loading: false });
let iframe = document.getElementsByTagName("iframe")[0];
if (!iframe) return;
let doc = iframe.contentDocument;
if (!doc) {
return;
}
StyleUtil.addDefaultCss();
this.props.rendition.themes.default(StyleUtil.getCustomCss(false));
});
this.props.rendition.on("selected", (cfiRange: any, contents: any) => {
var range = contents.range(cfiRange);
var rect = range.getBoundingClientRect();
this.setState({ cfiRange, contents, rect });
});
this.props.rendition.themes.default(StyleUtil.getCustomCss(false));
this.props.rendition.display(
RecordLocation.getCfi(this.props.currentBook.key) === null
? null
: RecordLocation.getCfi(this.props.currentBook.key).cfi
);
}
render() {
const popupMenuProps = {
rendition: this.props.rendition,
cfiRange: this.state.cfiRange,
contents: this.state.contents,
rect: this.state.rect,
};
return (
<div className="view-area">
<ImageViewer
{...{
isShow: this.props.isShow,
rendition: this.props.rendition,
handleEnterReader: this.props.handleEnterReader,
handleLeaveReader: this.props.handleLeaveReader,
}}
/>
<PopupMenu {...popupMenuProps} />
{this.state.loading ? (
<div className="spinner">
<Lottie options={siriOptions} height={100} width={300} />
</div>
) : null}
<>
{this.props.isShowBookmark ? <div className="bookmark"></div> : null}
</>
</div>
);
}
}
export default EpubViewer;

View File

@@ -0,0 +1,35 @@
.popup-menu-container {
position: absolute;
left: 100px;
top: 100px;
z-index: 20;
animation: popup 0.075s ease-in-out 0s 1;
}
.bookmark {
height: 40px;
width: 20px;
padding: 0px;
-webkit-transform: rotate(0deg) skew(0deg);
transform: rotate(0deg) skew(0deg);
border-left: 10px solid red;
border-right: 10px solid red;
border-bottom: 10px solid transparent;
position: fixed;
top: 5px;
right: 70px;
z-index: 15;
}
.bookmark,
.bookmark:before,
.bookmark:after {
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
}
.spinner {
position: absolute;
left: 50%;
top: 50%;
margin-left: -150px;
margin-top: -50px;
}

View File

@@ -0,0 +1,29 @@
import { connect } from "react-redux";
import { stateType } from "../../store";
import ViewArea from "./component";
import {
handlePercentage,
handleOpenMenu,
handleShowBookmark,
handleReadingEpub,
} from "../../store/actions";
import "./index.css";
const mapStateToProps = (state: stateType) => {
return {
chapters: state.reader.chapters,
currentEpub: state.book.currentEpub,
currentBook: state.book.currentBook,
locations: state.progressPanel.locations,
bookmarks: state.reader.bookmarks,
isShowBookmark: state.viewArea.isShowBookmark,
};
};
const actionCreator = {
handlePercentage,
handleOpenMenu,
handleShowBookmark,
handleReadingEpub,
};
export default connect(mapStateToProps, actionCreator)(ViewArea);

View File

@@ -0,0 +1,26 @@
import BookModel from "../../model/Book";
import BookmarkModel from "../../model/Bookmark";
export interface ViewAreaProps {
rendition: any;
currentBook: BookModel;
currentEpub: any;
bookmarks: BookmarkModel[];
locations: any;
isShowBookmark: boolean;
chapters: any[];
isShow: boolean;
handleLeaveReader: (position: string) => void;
handleEnterReader: (position: string) => void;
handlePercentage: (percentage: number) => void;
handleOpenMenu: (isOpenMenu: boolean) => void;
handleShowBookmark: (isShowBookmark: boolean) => void;
handleReadingEpub: (epub: object) => void;
}
export interface ViewAreaStates {
loading: boolean;
cfiRange: any;
contents: any;
// rendition: any;
rect: any;
}

View File

@@ -2,7 +2,7 @@ import React from "react";
import RecentBooks from "../../utils/readUtils/recordRecent";
import { EpubReaderProps, EpubReaderState } from "./interface";
import localforage from "localforage";
import Reader from "../../containers/epubReader";
import Reader from "../../containers/_epubReader";
import { withRouter } from "react-router-dom";
import _ from "underscore";
import BookUtil from "../../utils/fileUtils/bookUtil";