From 2b22d7b2551915158193daaa14d6389bcbfac450 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sat, 4 Apr 2026 18:17:16 -0400 Subject: [PATCH] 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 --- web/src/reader/features/gestures.ts | 4 +- web/src/reader/features/keyboard-shortcuts.ts | 5 +- web/src/reader/features/navigator-panel.ts | 39 ++++++----- web/src/reader/features/offline-manager.ts | 9 ++- web/src/reader/features/panel-dock-system.ts | 64 ++++++++++++++----- web/src/reader/features/progress-indicator.ts | 6 +- .../reader/features/reading-speed-tracker.ts | 9 ++- 7 files changed, 96 insertions(+), 40 deletions(-) diff --git a/web/src/reader/features/gestures.ts b/web/src/reader/features/gestures.ts index f0e351a..1a50072 100644 --- a/web/src/reader/features/gestures.ts +++ b/web/src/reader/features/gestures.ts @@ -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(); diff --git a/web/src/reader/features/keyboard-shortcuts.ts b/web/src/reader/features/keyboard-shortcuts.ts index b1cd35b..9bffe5d 100644 --- a/web/src/reader/features/keyboard-shortcuts.ts +++ b/web/src/reader/features/keyboard-shortcuts.ts @@ -1,6 +1,9 @@ import type { ReaderContext } from "../core/reader-context"; -export async function init(context: ReaderContext): Promise { +let context: ReaderContext; + +export async function init(readerContext: ReaderContext): Promise { + context = readerContext; setupKeyboardShortcuts(); } diff --git a/web/src/reader/features/navigator-panel.ts b/web/src/reader/features/navigator-panel.ts index cdac449..d3a5967 100644 --- a/web/src/reader/features/navigator-panel.ts +++ b/web/src/reader/features/navigator-panel.ts @@ -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`; -} \ No newline at end of file +} diff --git a/web/src/reader/features/offline-manager.ts b/web/src/reader/features/offline-manager.ts index 0aab3f1..c6522c1 100644 --- a/web/src/reader/features/offline-manager.ts +++ b/web/src/reader/features/offline-manager.ts @@ -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", {}); -} \ No newline at end of file +} diff --git a/web/src/reader/features/panel-dock-system.ts b/web/src/reader/features/panel-dock-system.ts index 5578fa4..12b5f3e 100644 --- a/web/src/reader/features/panel-dock-system.ts +++ b/web/src/reader/features/panel-dock-system.ts @@ -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 { 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 }); -} \ No newline at end of file +} diff --git a/web/src/reader/features/progress-indicator.ts b/web/src/reader/features/progress-indicator.ts index b05f182..ceb56f5 100644 --- a/web/src/reader/features/progress-indicator.ts +++ b/web/src/reader/features/progress-indicator.ts @@ -3,7 +3,11 @@ interface ProgressDisplay { mode: "pages" | "chapter" | "percentage" | "time-left"; text: string; } -export async function init(context: ReaderContext): Promise { + +let context: ReaderContext; + +export async function init(readerContext: ReaderContext): Promise { + context = readerContext; context.events.on("pageChanged", () => { updateProgressDisplay(context); }); diff --git a/web/src/reader/features/reading-speed-tracker.ts b/web/src/reader/features/reading-speed-tracker.ts index b663402..a278a80 100644 --- a/web/src/reader/features/reading-speed-tracker.ts +++ b/web/src/reader/features/reading-speed-tracker.ts @@ -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); } -} \ No newline at end of file +}