Convert view-modes.ts to init(context) pattern with: - Paginated mode with pagination controls - Scrolled mode for continuous reading - Single and double column layouts - Event-based navigation and mode switching
199 lines
5.7 KiB
TypeScript
199 lines
5.7 KiB
TypeScript
// Different viewing modes for ebooks
|
|
// Feature Registration Pattern implementation
|
|
|
|
import type { ReaderContext } from "../core/reader-context";
|
|
|
|
export function init(context: ReaderContext): void {
|
|
let state: ViewModeState | null = null;
|
|
|
|
context.events.on("reader:loaded", (detail: { container: HTMLElement }) => {
|
|
state = { currentMode: "paginated", currentPage: 1 };
|
|
setViewMode(detail.container, "paginated");
|
|
});
|
|
|
|
context.events.on("view-mode:set", (detail: { mode: ViewMode }) => {
|
|
if (state && context.elements.readerContent) {
|
|
state.currentMode = detail.mode;
|
|
setViewMode(context.elements.readerContent, detail.mode);
|
|
context.events.emit("view-mode:changed", { mode: detail.mode });
|
|
}
|
|
});
|
|
|
|
context.events.on("view-mode:get", () => {
|
|
if (state) {
|
|
context.events.emit("view-mode:current", state);
|
|
}
|
|
});
|
|
|
|
context.events.on("pagination:go-to-page", (detail: { page: number }) => {
|
|
if (state && context.elements.readerContent) {
|
|
state.currentPage = detail.page;
|
|
goToPage(context.elements.readerContent, detail.page);
|
|
context.events.emit("pagination:page-changed", { page: detail.page });
|
|
}
|
|
});
|
|
}
|
|
|
|
type ViewMode = "paginated" | "scrolled" | "single-column" | "double-column";
|
|
|
|
interface ViewModeState {
|
|
currentMode: ViewMode;
|
|
currentPage: number;
|
|
}
|
|
|
|
function setViewMode(container: HTMLElement, mode: ViewMode): void {
|
|
const content = container.querySelector(".ebook-content");
|
|
if (!content) return;
|
|
|
|
const element = content as HTMLElement;
|
|
|
|
element.classList.remove(
|
|
"paginated",
|
|
"scrolled",
|
|
"single-column",
|
|
"double-column",
|
|
);
|
|
|
|
switch (mode) {
|
|
case "paginated":
|
|
applyPaginatedMode(container, element);
|
|
break;
|
|
case "scrolled":
|
|
applyScrolledMode(container, element);
|
|
break;
|
|
case "single-column":
|
|
applySingleColumn(element);
|
|
break;
|
|
case "double-column":
|
|
applyDoubleColumn(element);
|
|
break;
|
|
}
|
|
}
|
|
|
|
function applyPaginatedMode(
|
|
container: HTMLElement,
|
|
element: HTMLElement,
|
|
): void {
|
|
element.classList.add("paginated");
|
|
|
|
element.style.height = "100vh";
|
|
element.style.overflow = "hidden";
|
|
element.style.columnCount = "1";
|
|
element.style.columnGap = "0";
|
|
|
|
enablePagination(container, element);
|
|
}
|
|
|
|
function applyScrolledMode(container: HTMLElement, element: HTMLElement): void {
|
|
element.classList.add("scrolled");
|
|
|
|
element.style.height = "auto";
|
|
element.style.overflowY = "auto";
|
|
element.style.columnCount = "1";
|
|
|
|
disablePagination(container);
|
|
}
|
|
|
|
function applySingleColumn(element: HTMLElement): void {
|
|
element.classList.add("single-column");
|
|
|
|
element.style.columnCount = "1";
|
|
element.style.columnGap = "0";
|
|
element.style.maxWidth = "800px";
|
|
element.style.margin = "0 auto";
|
|
}
|
|
|
|
function applyDoubleColumn(element: HTMLElement): void {
|
|
element.classList.add("double-column");
|
|
|
|
element.style.columnCount = "2";
|
|
element.style.columnGap = "60px";
|
|
element.style.columnRule = "1px solid var(--text-secondary)";
|
|
element.style.maxWidth = "1400px";
|
|
element.style.margin = "0 auto";
|
|
}
|
|
|
|
function enablePagination(container: HTMLElement, element: HTMLElement): void {
|
|
const totalHeight = element.scrollHeight;
|
|
const pageHeight = element.clientHeight;
|
|
const pageCount = Math.ceil(totalHeight / pageHeight);
|
|
|
|
addPaginationControls(container, pageCount);
|
|
}
|
|
|
|
function disablePagination(container: HTMLElement): void {
|
|
const controls = container.querySelector(".pagination-controls");
|
|
controls?.remove();
|
|
}
|
|
|
|
function addPaginationControls(
|
|
container: HTMLElement,
|
|
pageCount: number,
|
|
): void {
|
|
let currentPage = 1;
|
|
|
|
const controls = document.createElement("div");
|
|
controls.className =
|
|
"pagination-controls fixed bottom-0 left-0 right-0 bg-opacity-95 backdrop-blur border-t";
|
|
controls.innerHTML = `
|
|
<button class="prev-page" ${currentPage === 1 ? "disabled" : ""}>← Previous</button>
|
|
<span class="page-info">Page ${currentPage} of ${pageCount}</span>
|
|
<button class="next-page" ${currentPage === pageCount ? "disabled" : ""}>Next →</button>
|
|
`;
|
|
|
|
controls.querySelector(".prev-page")?.addEventListener("click", () => {
|
|
if (currentPage > 1) {
|
|
currentPage--;
|
|
goToPage(container, currentPage);
|
|
}
|
|
});
|
|
|
|
controls.querySelector(".next-page")?.addEventListener("click", () => {
|
|
if (currentPage < pageCount) {
|
|
currentPage++;
|
|
goToPage(container, currentPage);
|
|
}
|
|
});
|
|
|
|
container.appendChild(controls);
|
|
}
|
|
|
|
function goToPage(container: HTMLElement, pageNumber: number): void {
|
|
const content = container.querySelector(".ebook-content") as HTMLElement;
|
|
if (!content) return;
|
|
|
|
const pageHeight = content.clientHeight;
|
|
const scrollTop = (pageNumber - 1) * pageHeight;
|
|
|
|
content.scrollTo({
|
|
top: scrollTop,
|
|
behavior: "smooth",
|
|
});
|
|
|
|
const pageInfo = container.querySelector(".page-info");
|
|
if (pageInfo) {
|
|
pageInfo.textContent = `Page ${pageNumber} of ${getTotalPageCount(container)}`;
|
|
}
|
|
}
|
|
|
|
function getTotalPageCount(container: HTMLElement): number {
|
|
const content = container.querySelector(".ebook-content") as HTMLElement;
|
|
if (!content) return 1;
|
|
|
|
const totalHeight = content.scrollHeight;
|
|
const pageHeight = content.clientHeight;
|
|
|
|
return Math.ceil(totalHeight / pageHeight);
|
|
}
|
|
|
|
export function getCurrentViewMode(container: HTMLElement): ViewMode {
|
|
const content = container.querySelector(".ebook-content") as HTMLElement;
|
|
if (!content) return "paginated";
|
|
|
|
if (content.classList.contains("paginated")) return "paginated";
|
|
if (content.classList.contains("scrolled")) return "scrolled";
|
|
if (content.classList.contains("single-column")) return "single-column";
|
|
if (content.classList.contains("double-column")) return "double-column";
|
|
|
|
return "paginated";
|
|
} |