reader: convert feature modules to Feature Registration Pattern

- Add init(context) exported function to each feature module
- Store module-level context reference for event handlers
- Fix gestures.ts to use library_type_name instead of library_type
- Features: gestures, keyboard-shortcuts, navigator-panel, offline-manager,
  panel-dock-system, progress-indicator, reading-speed-tracker
This commit is contained in:
2026-04-04 18:17:16 -04:00
parent 4abc814c1b
commit 2b22d7b255
7 changed files with 96 additions and 40 deletions
+2 -2
View File
@@ -101,7 +101,7 @@ function setupGestures() {
function handleSwipeLeft() {
const metadata = context.getState().readerMetadata;
if (metadata?.library_type === "manga") {
if (metadata?.library_type_name === "manga") {
navigateWithPanels("next");
} else {
context.navigation.previousPage();
@@ -110,7 +110,7 @@ function handleSwipeLeft() {
function handleSwipeRight() {
const metadata = context.getState().readerMetadata;
if (metadata?.library_type === "manga") {
if (metadata?.library_type_name === "manga") {
navigateWithPanels("previous");
} else {
context.navigation.nextPage();
@@ -1,6 +1,9 @@
import type { ReaderContext } from "../core/reader-context";
export async function init(context: ReaderContext): Promise<void> {
let context: ReaderContext;
export async function init(readerContext: ReaderContext): Promise<void> {
context = readerContext;
setupKeyboardShortcuts();
}
+24 -15
View File
@@ -4,23 +4,32 @@
import type { ReaderContext } from "../core/reader-context";
export function init(context: ReaderContext): void {
let context: ReaderContext;
export function init(readerContext: ReaderContext): void {
context = readerContext;
let state: NavigatorState | null = null;
context.events.on("navigator:initialize", (detail: { containerSelector: string; totalPages: number }) => {
state = initializeNavigator(detail.containerSelector);
state.totalPages = detail.totalPages;
});
context.events.on(
"navigator:initialize",
(detail: { containerSelector: string; totalPages: number }) => {
state = initializeNavigator(detail.containerSelector);
state.totalPages = detail.totalPages;
},
);
context.events.on("navigator:update", (detail: { currentPage: number; contentImage?: HTMLImageElement }) => {
if (state) {
state.currentPage = detail.currentPage;
if (detail.contentImage) {
state.contentImage = detail.contentImage;
updateNavigatorViewport(state);
context.events.on(
"navigator:update",
(detail: { currentPage: number; contentImage?: HTMLImageElement }) => {
if (state) {
state.currentPage = detail.currentPage;
if (detail.contentImage) {
state.contentImage = detail.contentImage;
updateNavigatorViewport(state);
}
}
}
});
},
);
context.events.on("navigator:pan", (detail: { x: number; y: number }) => {
if (state) {
@@ -124,10 +133,10 @@ function handleNavigatorPan(state: NavigatorState, x: number, y: number): void {
if (!state.contentImage) return;
const imgRect = state.contentImage.getBoundingClientRect();
const viewportX = x * state.container.offsetWidth;
const viewportY = y * state.container.offsetHeight;
state.viewport.style.left = `${viewportX}px`;
state.viewport.style.top = `${viewportY}px`;
}
}
+6 -3
View File
@@ -1,9 +1,12 @@
// Offline manager for PWA functionality
// Feature Registration Pattern implementation
import type { ReaderContext } from "./core/reader-context";
import type { ReaderContext } from "../core/reader-context";
export function init(context: ReaderContext): void {
let context: ReaderContext;
export function init(readerContext: ReaderContext): void {
context = readerContext;
registerServiceWorker();
window.addEventListener("online", () => {
@@ -46,4 +49,4 @@ export function checkOnlineStatus(): boolean {
function syncPendingChanges(context: ReaderContext): void {
context.events.emit("offline:sync", {});
}
}
+49 -15
View File
@@ -3,7 +3,10 @@
import type { ReaderContext } from "../core/reader-context";
export function init(context: ReaderContext): void {
let context: ReaderContext;
export function init(readerContext: ReaderContext): void {
context = readerContext;
const dockZones: DockZone[] = [
{ side: "left", x: 0, width: 400, height: window.innerHeight },
{
@@ -45,9 +48,12 @@ export function init(context: ReaderContext): void {
}
});
context.events.on("panel:move", (detail: { panelId: string; side: "left" | "right" }) => {
movePanelToSide(context, state, detail.panelId, detail.side);
});
context.events.on(
"panel:move",
(detail: { panelId: string; side: "left" | "right" }) => {
movePanelToSide(context, state, detail.panelId, detail.side);
},
);
}
interface PanelDockState {
@@ -88,39 +94,54 @@ async function loadSettings(): Promise<any> {
return await response.json();
}
function createPanel(context: ReaderContext, panelId: string, panelState: PanelState): void {
function createPanel(
context: ReaderContext,
panelId: string,
panelState: PanelState,
): void {
const panel = document.createElement("div");
panel.id = `panel-${panelId}`;
panel.className = `reader-panel panel-${panelState.side}`;
panel.dataset.panelId = panelId;
panel.style.width = `${panelState.width_px}px`;
if (!panelState.visible) {
panel.classList.add("panel-hidden");
}
if (panelState.collapsed) {
panel.classList.add("panel-collapsed");
}
context.elements.readerContent.appendChild(panel);
}
function updatePanelVisibility(context: ReaderContext, panelId: string, visible: boolean): void {
function updatePanelVisibility(
context: ReaderContext,
panelId: string,
visible: boolean,
): void {
const panel = document.getElementById(`panel-${panelId}`);
if (panel) {
panel.classList.toggle("panel-hidden", !visible);
}
}
function updatePanelCollapsed(context: ReaderContext, panelId: string, collapsed: boolean): void {
function updatePanelCollapsed(
context: ReaderContext,
panelId: string,
collapsed: boolean,
): void {
const panel = document.getElementById(`panel-${panelId}`);
if (panel) {
panel.classList.toggle("panel-collapsed", collapsed);
}
}
function setupGlobalDragHandlers(context: ReaderContext, state: PanelDockState): void {
function setupGlobalDragHandlers(
context: ReaderContext,
state: PanelDockState,
): void {
document.addEventListener("mousedown", (e) => {
const panelHeader = (e.target as HTMLElement).closest(".panel-header");
if (panelHeader) {
@@ -149,9 +170,17 @@ function setupGlobalDragHandlers(context: ReaderContext, state: PanelDockState):
document.addEventListener("mouseup", () => {
if (state.dragState) {
const nearestZone = findNearestDockZone(state.dragState.currentX, state.dockZones);
const nearestZone = findNearestDockZone(
state.dragState.currentX,
state.dockZones,
);
if (nearestZone) {
movePanelToSide(context, state, state.dragState.panelId, nearestZone.side);
movePanelToSide(
context,
state,
state.dragState.panelId,
nearestZone.side,
);
}
state.dragState = null;
}
@@ -173,7 +202,12 @@ function findNearestDockZone(x: number, zones: DockZone[]): DockZone | null {
return nearest;
}
function movePanelToSide(context: ReaderContext, state: PanelDockState, panelId: string, side: "left" | "right"): void {
function movePanelToSide(
context: ReaderContext,
state: PanelDockState,
panelId: string,
side: "left" | "right",
): void {
const panelState = state.panels.get(panelId);
if (!panelState) return;
@@ -187,4 +221,4 @@ function movePanelToSide(context: ReaderContext, state: PanelDockState, panelId:
}
context.events.emit("panel:moved", { panelId, side });
}
}
@@ -3,7 +3,11 @@ interface ProgressDisplay {
mode: "pages" | "chapter" | "percentage" | "time-left";
text: string;
}
export async function init(context: ReaderContext): Promise<void> {
let context: ReaderContext;
export async function init(readerContext: ReaderContext): Promise<void> {
context = readerContext;
context.events.on("pageChanged", () => {
updateProgressDisplay(context);
});
@@ -1,9 +1,12 @@
// Track reading speed and update database
// Feature Registration Pattern implementation
import type { ReaderContext } from "./core/reader-context";
import type { ReaderContext } from "../core/reader-context";
export function init(context: ReaderContext): void {
let context: ReaderContext;
export function init(readerContext: ReaderContext): void {
context = readerContext;
let state: ReadingSpeedTrackerState | null = null;
context.events.on("reader:loaded", (detail: { mediaItemId: string }) => {
@@ -122,4 +125,4 @@ async function syncReadingSpeed(
} catch (error) {
console.error("Failed to sync reading speed:", error);
}
}
}