Added book paged display
This commit is contained in:
parent
6044e7c375
commit
0ffb9e6456
244
src/hooks/usePagination.ts
Normal file
244
src/hooks/usePagination.ts
Normal file
@ -0,0 +1,244 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
type TextNode = Text & { nodeValue: string };
|
||||
type PositionElement = [number, TextNode];
|
||||
type IdPositions = Record<string, number>;
|
||||
type UsePaginationReturnTuple = [
|
||||
ready: boolean,
|
||||
goToPage: (pageNum: number) => void,
|
||||
currentPage: number
|
||||
];
|
||||
|
||||
const isTextNode = (el: Node): el is TextNode => el.nodeType === Node.TEXT_NODE;
|
||||
const isElementNode = (el: Node): el is HTMLElement =>
|
||||
el.nodeType === Node.ELEMENT_NODE;
|
||||
|
||||
export const usePagination = (
|
||||
contentEl: React.RefObject<HTMLDivElement>,
|
||||
pageContainerEl: React.RefObject<HTMLDivElement>,
|
||||
pageEl: React.RefObject<HTMLDivElement>,
|
||||
bookContent?: string
|
||||
): UsePaginationReturnTuple => {
|
||||
const [ready, setReady] = useState(false);
|
||||
const [positions, setPositions] = useState<PositionElement[]>([]);
|
||||
const [idPositions, setIdPositions] = useState<IdPositions>({});
|
||||
const [pages, setPages] = useState<number[]>([]);
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
|
||||
const computeStartPositionsOfElements = (root: HTMLDivElement) => {
|
||||
const positionToElement: PositionElement[] = [];
|
||||
const idPositions: IdPositions = {};
|
||||
|
||||
const recursive = (currentPosition: number, element: Node): number => {
|
||||
if (isTextNode(element)) {
|
||||
positionToElement.push([currentPosition, element]);
|
||||
return currentPosition + element.nodeValue.length;
|
||||
} else if (isElementNode(element)) {
|
||||
if (element.id && element.id != null)
|
||||
idPositions[element.id] = currentPosition;
|
||||
|
||||
const children = element.childNodes;
|
||||
|
||||
const newCurrentPosition = Array.from(children).reduce(
|
||||
recursive,
|
||||
currentPosition
|
||||
);
|
||||
|
||||
return newCurrentPosition;
|
||||
} else {
|
||||
return currentPosition;
|
||||
}
|
||||
};
|
||||
|
||||
recursive(0, root);
|
||||
|
||||
setPositions(positionToElement);
|
||||
setIdPositions(idPositions);
|
||||
};
|
||||
|
||||
const findPages = (page: HTMLElement, pageContainer: HTMLElement) => {
|
||||
const pages = [];
|
||||
pages.push(0);
|
||||
let jump = 100;
|
||||
|
||||
while (pages[pages.length - 1] < getMaxPosition()) {
|
||||
if (pages.length > 2)
|
||||
jump = pages[pages.length - 1] - pages[pages.length - 2];
|
||||
|
||||
const endPosition = findPage(
|
||||
pages[pages.length - 1],
|
||||
jump,
|
||||
page,
|
||||
pageContainer
|
||||
);
|
||||
pages.push(endPosition);
|
||||
}
|
||||
|
||||
clearPage(page);
|
||||
|
||||
setPages(pages);
|
||||
setCurrentPage(0);
|
||||
console.log(pages);
|
||||
console.log("end");
|
||||
};
|
||||
|
||||
const findPage = (
|
||||
startPosition: number,
|
||||
initialJump: number,
|
||||
page: HTMLElement,
|
||||
pageContainer: HTMLElement
|
||||
) => {
|
||||
let previousEndPosition = getMaxPosition();
|
||||
let endPosition = findNextSpaceForPosition(startPosition + initialJump);
|
||||
|
||||
copyTextToPage(startPosition, endPosition, page);
|
||||
while (!scrollNecessary(pageContainer) && endPosition < getMaxPosition()) {
|
||||
previousEndPosition = endPosition;
|
||||
endPosition = findNextSpaceForPosition(endPosition + 1);
|
||||
copyTextToPage(startPosition, endPosition, page);
|
||||
}
|
||||
|
||||
while (scrollNecessary(pageContainer) && endPosition > startPosition) {
|
||||
previousEndPosition = endPosition;
|
||||
endPosition = findPreviousSpaceForPosition(endPosition - 1);
|
||||
copyTextToPage(startPosition, endPosition, page);
|
||||
}
|
||||
|
||||
if (endPosition === startPosition) return previousEndPosition;
|
||||
return endPosition;
|
||||
};
|
||||
|
||||
const findNextSpaceForPosition = (startPosition: number) => {
|
||||
let i = 0;
|
||||
while (i < positions.length - 1 && positions[i + 1][0] < startPosition) i++;
|
||||
|
||||
const el = positions[i][1];
|
||||
let d = startPosition - positions[i][0];
|
||||
const str = el.nodeValue || "";
|
||||
while (d < str.length && str.charAt(d) != " ") d++;
|
||||
|
||||
if (d >= str.length) {
|
||||
if (i == positions.length - 1) return getMaxPosition();
|
||||
else return positions[i + 1][0];
|
||||
} else {
|
||||
return positions[i][0] + d;
|
||||
}
|
||||
};
|
||||
|
||||
const findPreviousSpaceForPosition = (startPosition: number) => {
|
||||
let i = 0;
|
||||
while (i < positions.length - 1 && positions[i + 1][0] < startPosition) i++;
|
||||
|
||||
const el = positions[i][1];
|
||||
let d = startPosition - positions[i][0];
|
||||
const str = el.nodeValue || "";
|
||||
while (d > 0 && str.charAt(d) != " ") d--;
|
||||
|
||||
return positions[i][0] + d;
|
||||
};
|
||||
|
||||
const scrollNecessary = (pageContainer: HTMLElement) =>
|
||||
pageContainer.scrollHeight > pageContainer.clientHeight ||
|
||||
pageContainer.scrollWidth > pageContainer.clientWidth;
|
||||
|
||||
const copyTextToPage = (from: number, to: number, page: HTMLElement) => {
|
||||
const range = document.createRange();
|
||||
|
||||
const startEl = getElementForPosition(from);
|
||||
const startElement = startEl[1];
|
||||
const locationInStartEl = from - startEl[0];
|
||||
range.setStart(startElement, locationInStartEl);
|
||||
|
||||
const endEl = getElementForPosition(to);
|
||||
const endElement = endEl[1];
|
||||
const locationInEndEl = to - endEl[0];
|
||||
range.setEnd(endElement, locationInEndEl);
|
||||
|
||||
page.innerHTML = "";
|
||||
page.appendChild(range.cloneContents());
|
||||
};
|
||||
|
||||
const clearPage = (page: HTMLElement) => {
|
||||
page.innerHTML = "";
|
||||
};
|
||||
|
||||
const getPageForId = (id: string) => getPageForPosition(getPositionForId(id));
|
||||
|
||||
const getMaxPosition = () => {
|
||||
const [pos, el] = positions[positions.length - 1];
|
||||
return pos + el.nodeValue.length || 0;
|
||||
};
|
||||
|
||||
const getPositionForId = (id: string) => {
|
||||
if (id in idPositions) return idPositions[id];
|
||||
return 0;
|
||||
};
|
||||
|
||||
const getPageForPosition = (pos: number) => {
|
||||
for (const [pageNum, pagePos] of pages.entries())
|
||||
if (pagePos > pos) return pageNum - 1;
|
||||
|
||||
return pages.length - 2;
|
||||
};
|
||||
|
||||
const getElementForPosition = (pos: number) => {
|
||||
for (const [i, [currPos, _]] of positions.entries())
|
||||
if (currPos > pos) return positions[i - 1];
|
||||
|
||||
return positions[positions.length - 1];
|
||||
};
|
||||
|
||||
const jumpToLocation = (page: HTMLElement) => {
|
||||
const url = new URL(window.location.href);
|
||||
|
||||
const positionStr = url.searchParams.get("position");
|
||||
|
||||
if (url.href.lastIndexOf("#") > 0) {
|
||||
const id = url.href.substring(
|
||||
url.href.lastIndexOf("#") + 1,
|
||||
url.href.length
|
||||
);
|
||||
displayPage(getPageForId(id), page);
|
||||
} else if (positionStr) {
|
||||
const pos = parseInt(positionStr);
|
||||
displayPage(getPageForPosition(pos), page);
|
||||
} else {
|
||||
displayPage(0, page);
|
||||
}
|
||||
};
|
||||
|
||||
const displayPage = (pageNum: number, page: HTMLElement) => {
|
||||
console.log(pageNum, pages.length);
|
||||
if (pageNum >= 0 && pageNum < pages.length - 1) {
|
||||
setCurrentPage(pageNum);
|
||||
const startPosition = pages[pageNum];
|
||||
copyTextToPage(startPosition, pages[pageNum + 1], page);
|
||||
setReady(true);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (contentEl.current && bookContent) {
|
||||
contentEl.current.innerHTML = bookContent;
|
||||
computeStartPositionsOfElements(contentEl.current);
|
||||
}
|
||||
}, [bookContent]);
|
||||
|
||||
useEffect(() => {
|
||||
if (positions.length && pageEl.current && pageContainerEl.current)
|
||||
findPages(pageEl.current, pageContainerEl.current);
|
||||
}, [positions, idPositions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (pageEl.current && pages.length && !ready)
|
||||
jumpToLocation(pageEl.current);
|
||||
}, [pages]);
|
||||
|
||||
const makeDisplayPage = (page: React.RefObject<HTMLDivElement>) => {
|
||||
if (page.current)
|
||||
return (pageNum: number) => displayPage(pageNum, page.current!);
|
||||
else return (pageNum: number) => {};
|
||||
};
|
||||
|
||||
return [ready, makeDisplayPage(pageEl), currentPage];
|
||||
};
|
32
src/pages/BookView/BookView.module.css
Normal file
32
src/pages/BookView/BookView.module.css
Normal file
@ -0,0 +1,32 @@
|
||||
.content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pageContainer {
|
||||
display: block;
|
||||
position: fixed;
|
||||
width: 80vw;
|
||||
left: 10vw;
|
||||
height: 100vh;
|
||||
top: 0;
|
||||
/* overflow: hidden; */
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
max-height: 98vh;
|
||||
max-width: 80vw;
|
||||
}
|
||||
|
||||
.loadingIndicator {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
@ -1,8 +1,43 @@
|
||||
import React from "react";
|
||||
import { useRoute } from "wouter";
|
||||
import React, { useContext, useEffect, useRef, useState } from "react";
|
||||
import { Redirect, useRoute } from "wouter";
|
||||
|
||||
import styles from "./BookView.module.css";
|
||||
|
||||
import { BookListContext } from "~/context";
|
||||
import { usePagination } from "~/hooks/usePagination";
|
||||
|
||||
export const BookView = () => {
|
||||
const [match, params] = useRoute("/:hash");
|
||||
const [books] = useContext(BookListContext);
|
||||
|
||||
return <div></div>;
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const pageContainerRef = useRef<HTMLDivElement>(null);
|
||||
const pageRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [ready, goToPage, currentPage] = usePagination(
|
||||
contentRef,
|
||||
pageContainerRef,
|
||||
pageRef,
|
||||
params?.hash ? books[params.hash]?.content : undefined
|
||||
);
|
||||
|
||||
if (params?.hash && params.hash in books)
|
||||
return (
|
||||
<>
|
||||
{!ready && (
|
||||
<div className={styles.loadingIndicator}>
|
||||
<h1>Loading</h1>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.content} ref={contentRef} />
|
||||
<div
|
||||
style={{ visibility: "visible", height: "100%" }}
|
||||
className={styles.pageContainer}
|
||||
ref={pageContainerRef}
|
||||
>
|
||||
<div onClick={() => goToPage(currentPage + 1)} ref={pageRef} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
return <Redirect to="/" />;
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"target": "es6",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
|
Loading…
x
Reference in New Issue
Block a user