258 lines
8.0 KiB
JavaScript
258 lines
8.0 KiB
JavaScript
import axios from "axios";
|
||
import { useEffect, useRef, useState } from "react";
|
||
import { useNavigate, useParams } from "react-router";
|
||
import "./reader.css";
|
||
import OutlinedButton from "../md-components/OutlinedButton";
|
||
import FilledButton from "../md-components/FilledButton";
|
||
import Contents from "../components/contents/Contents";
|
||
import Icon from "../md-components/Icon";
|
||
import { Link } from "react-router-dom";
|
||
import Skeleton from "react-loading-skeleton";
|
||
import LinearProgress from "../md-components/LinearProgress";
|
||
import {
|
||
addBook,
|
||
getBook,
|
||
getReadBook,
|
||
putReadBook,
|
||
removeBook,
|
||
saveReadBook,
|
||
updateReadBook,
|
||
} from "../db";
|
||
import IconButton from "../md-components/IconButton";
|
||
|
||
const ReaderFB2 = () => {
|
||
const { id } = useParams();
|
||
const readerRef = useRef();
|
||
const containerRef = useRef();
|
||
const progressRef = useRef();
|
||
const contentsRef = useRef();
|
||
const contentsBackdropRef = useRef();
|
||
const navigate = useNavigate();
|
||
|
||
const [totalHeight, setTotalHeight] = useState(0);
|
||
const [currentHeight, setCurrentHeight] = useState(0);
|
||
const [readerHeight, setReaderHeight] = useState(0);
|
||
const [totalPages, setTotalPages] = useState(1);
|
||
const [currentPage, setCurrentPage] = useState(0);
|
||
const [isLocal, setIsLocal] = useState(false);
|
||
const [onBookshelf, setOnBookshelf] = useState(false);
|
||
|
||
const [bookInfo, setBookInfo] = useState({ title: "" });
|
||
const [contents, setContents] = useState([]);
|
||
|
||
function nextPage() {
|
||
setCurrentPage((prev) => {
|
||
if (totalPages - prev < 1) {
|
||
return prev;
|
||
}
|
||
return prev + 1;
|
||
});
|
||
}
|
||
function prevPage() {
|
||
setCurrentPage((prev) => {
|
||
if (prev <= 1) {
|
||
return prev;
|
||
}
|
||
return prev - 1;
|
||
});
|
||
}
|
||
|
||
const leftRef = useRef();
|
||
const rightRef = useRef();
|
||
function keyboardPagination(e) {
|
||
if (!(leftRef.current && rightRef.current)) {
|
||
console.log("harakiri");
|
||
document.removeEventListener("keydown", keyboardPagination, false);
|
||
return;
|
||
}
|
||
if (e.key === "ArrowRight") {
|
||
rightRef.current.click();
|
||
}
|
||
if (e.key === "ArrowLeft") {
|
||
leftRef.current.click();
|
||
}
|
||
}
|
||
useEffect(() => {
|
||
if (!(leftRef.current && rightRef.current)) return;
|
||
console.log("add listener");
|
||
document.addEventListener("keydown", keyboardPagination, false);
|
||
}, [leftRef, rightRef]);
|
||
|
||
function toggleFloatingContents() {
|
||
if (!contentsRef.current || !contentsBackdropRef.current) return;
|
||
contentsRef.current.classList.toggle("show");
|
||
contentsBackdropRef.current.classList.toggle("show");
|
||
}
|
||
useEffect(() => {
|
||
if (currentPage === 0) return;
|
||
if (readerHeight === totalHeight) return;
|
||
setCurrentHeight(readerHeight * (currentPage - 1));
|
||
readerRef.current.scrollTop = readerHeight * (currentPage - 1);
|
||
progressRef.current.style.width = `${(currentPage / totalPages) * 100}%`;
|
||
//console.log(readerHeight * (currentPage-1) / totalHeight, "update read book", currentPage)
|
||
//console.log(readerHeight, currentPage, totalHeight)
|
||
updateReadBook(id, (readerHeight * (currentPage - 1)) / totalHeight);
|
||
}, [currentPage]);
|
||
|
||
async function loadBook() {
|
||
let readBook = await getReadBook(id);
|
||
if (readBook) {
|
||
setOnBookshelf(readBook.onBookshelf === true);
|
||
}
|
||
let caches = undefined;
|
||
let localHTML = undefined;
|
||
if (window.caches !== undefined) {
|
||
caches = await window.caches.open("books");
|
||
localHTML = await caches.match(`/api/book/${id}/reader`);
|
||
}
|
||
let localBook = await getBook(id);
|
||
let innerHTML = "";
|
||
if (localBook !== undefined && localHTML !== undefined) {
|
||
setIsLocal(true);
|
||
setBookInfo(localBook);
|
||
setContents(localBook.contents);
|
||
await localHTML.text().then((html) => {
|
||
innerHTML = html;
|
||
});
|
||
} else {
|
||
if (!window.onLine) {
|
||
localStorage.removeItem("lastReadBook");
|
||
navigate("/offline");
|
||
}
|
||
setIsLocal(false);
|
||
axios
|
||
.get(`/book/${id}`)
|
||
.then((res) => setBookInfo(res.data))
|
||
.catch(() => navigate("/notfound"));
|
||
axios.get(`/book/${id}/contents`).then((res) => setContents(res.data));
|
||
await axios.get(`/book/${id}/reader`).then((res) => {
|
||
innerHTML = res.data;
|
||
});
|
||
}
|
||
localStorage.setItem("lastReadBook", id);
|
||
readerRef.current.innerHTML =
|
||
innerHTML +
|
||
"<br>".repeat(Math.floor(containerRef.current.clientHeight / 23) * 2); // заполнение двух страниц пустотой для возможности прокрутки не до конца
|
||
resizingReader(true);
|
||
}
|
||
async function resizingReader(firstLoad) {
|
||
if (!readerRef.current) return;
|
||
if (readerRef.current.scrollHeight === 0) return;
|
||
let progressRatio;
|
||
if (firstLoad) {
|
||
let readBook = await getReadBook(id);
|
||
if (readBook === undefined) {
|
||
progressRatio = 0;
|
||
updateReadBook(id, 0);
|
||
} else {
|
||
progressRatio = readBook.progress;
|
||
}
|
||
}
|
||
readerRef.current.style.height = `${
|
||
Math.floor(containerRef.current.clientHeight / 23) * 23 - 23
|
||
}px`; // подгоняем блок контента под окно ридера, и не забывает учитывать прогресс
|
||
setTotalHeight(readerRef.current.scrollHeight);
|
||
setReaderHeight(readerRef.current.clientHeight);
|
||
setTotalPages(
|
||
Math.ceil(
|
||
readerRef.current.scrollHeight / readerRef.current.clientHeight
|
||
) - 2
|
||
);
|
||
|
||
setCurrentPage(
|
||
Math.ceil(
|
||
Math.floor(progressRatio * readerRef.current.scrollHeight) /
|
||
readerRef.current.clientHeight
|
||
) + 1
|
||
);
|
||
}
|
||
|
||
useEffect(() => {
|
||
if (!readerRef.current) return;
|
||
loadBook();
|
||
}, [id]);
|
||
// изменение размера изображений для соответствия с шириной строки (23px)
|
||
useEffect(() => {
|
||
if (!readerRef.current) return;
|
||
let images = Array.from(readerRef.current.getElementsByTagName("img"));
|
||
images.map((img) => {
|
||
img.height = Math.floor(img.height / 23) * 23;
|
||
});
|
||
}, [totalHeight]);
|
||
|
||
return (
|
||
<>
|
||
<div
|
||
className="reader_contents_backdrop"
|
||
ref={contentsBackdropRef}
|
||
onClick={toggleFloatingContents}
|
||
></div>
|
||
<div className="reader_contents" ref={contentsRef}>
|
||
<Contents
|
||
data={contents}
|
||
readerRef={readerRef}
|
||
readerHeight={readerHeight}
|
||
currentPage={currentPage}
|
||
setCurrentPage={setCurrentPage}
|
||
/>
|
||
</div>
|
||
<div style={{ display: "flex", gap: "10px" }} className="mb-4">
|
||
{!(contents.length === 1 && contents[0].title === "") &&
|
||
contents.length !== 0 ? (
|
||
<OutlinedButton
|
||
className="contents_button reader_action_button"
|
||
onClick={toggleFloatingContents}
|
||
>
|
||
<Icon slot="icon">article</Icon>
|
||
Содержание
|
||
</OutlinedButton>
|
||
) : (
|
||
<></>
|
||
)}
|
||
</div>
|
||
<div className="reader_container" ref={containerRef}>
|
||
<div className="book_content" ref={readerRef} id="reader">
|
||
<LinearProgress indeterminate />
|
||
<br />
|
||
<Skeleton
|
||
count={
|
||
containerRef.current
|
||
? Math.floor(containerRef.current.clientHeight / 23) - 2
|
||
: 0
|
||
}
|
||
/>
|
||
</div>
|
||
<div className="reader_progress" ref={progressRef}></div>
|
||
</div>
|
||
<div
|
||
style={{
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
gap: "20px",
|
||
}}
|
||
>
|
||
<IconButton
|
||
onClick={prevPage}
|
||
disabled={currentHeight === 0}
|
||
ref={leftRef}
|
||
>
|
||
<Icon>arrow_back</Icon>
|
||
</IconButton>
|
||
<span>
|
||
{currentPage} из {totalPages}
|
||
</span>
|
||
<IconButton
|
||
onClick={nextPage}
|
||
disabled={totalPages - currentPage < 1}
|
||
ref={rightRef}
|
||
>
|
||
<Icon>arrow_forward</Icon>
|
||
</IconButton>
|
||
</div>
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default ReaderFB2;
|