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:
@@ -101,7 +101,7 @@ function setupGestures() {
|
|||||||
|
|
||||||
function handleSwipeLeft() {
|
function handleSwipeLeft() {
|
||||||
const metadata = context.getState().readerMetadata;
|
const metadata = context.getState().readerMetadata;
|
||||||
if (metadata?.library_type === "manga") {
|
if (metadata?.library_type_name === "manga") {
|
||||||
navigateWithPanels("next");
|
navigateWithPanels("next");
|
||||||
} else {
|
} else {
|
||||||
context.navigation.previousPage();
|
context.navigation.previousPage();
|
||||||
@@ -110,7 +110,7 @@ function handleSwipeLeft() {
|
|||||||
|
|
||||||
function handleSwipeRight() {
|
function handleSwipeRight() {
|
||||||
const metadata = context.getState().readerMetadata;
|
const metadata = context.getState().readerMetadata;
|
||||||
if (metadata?.library_type === "manga") {
|
if (metadata?.library_type_name === "manga") {
|
||||||
navigateWithPanels("previous");
|
navigateWithPanels("previous");
|
||||||
} else {
|
} else {
|
||||||
context.navigation.nextPage();
|
context.navigation.nextPage();
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import type { ReaderContext } from "../core/reader-context";
|
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();
|
setupKeyboardShortcuts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,23 +4,32 @@
|
|||||||
|
|
||||||
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: NavigatorState | null = null;
|
let state: NavigatorState | null = null;
|
||||||
|
|
||||||
context.events.on("navigator:initialize", (detail: { containerSelector: string; totalPages: number }) => {
|
context.events.on(
|
||||||
state = initializeNavigator(detail.containerSelector);
|
"navigator:initialize",
|
||||||
state.totalPages = detail.totalPages;
|
(detail: { containerSelector: string; totalPages: number }) => {
|
||||||
});
|
state = initializeNavigator(detail.containerSelector);
|
||||||
|
state.totalPages = detail.totalPages;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
context.events.on("navigator:update", (detail: { currentPage: number; contentImage?: HTMLImageElement }) => {
|
context.events.on(
|
||||||
if (state) {
|
"navigator:update",
|
||||||
state.currentPage = detail.currentPage;
|
(detail: { currentPage: number; contentImage?: HTMLImageElement }) => {
|
||||||
if (detail.contentImage) {
|
if (state) {
|
||||||
state.contentImage = detail.contentImage;
|
state.currentPage = detail.currentPage;
|
||||||
updateNavigatorViewport(state);
|
if (detail.contentImage) {
|
||||||
|
state.contentImage = detail.contentImage;
|
||||||
|
updateNavigatorViewport(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
context.events.on("navigator:pan", (detail: { x: number; y: number }) => {
|
context.events.on("navigator:pan", (detail: { x: number; y: number }) => {
|
||||||
if (state) {
|
if (state) {
|
||||||
@@ -124,10 +133,10 @@ function handleNavigatorPan(state: NavigatorState, x: number, y: number): void {
|
|||||||
if (!state.contentImage) return;
|
if (!state.contentImage) return;
|
||||||
|
|
||||||
const imgRect = state.contentImage.getBoundingClientRect();
|
const imgRect = state.contentImage.getBoundingClientRect();
|
||||||
|
|
||||||
const viewportX = x * state.container.offsetWidth;
|
const viewportX = x * state.container.offsetWidth;
|
||||||
const viewportY = y * state.container.offsetHeight;
|
const viewportY = y * state.container.offsetHeight;
|
||||||
|
|
||||||
state.viewport.style.left = `${viewportX}px`;
|
state.viewport.style.left = `${viewportX}px`;
|
||||||
state.viewport.style.top = `${viewportY}px`;
|
state.viewport.style.top = `${viewportY}px`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
// Offline manager for PWA functionality
|
// Offline manager for PWA functionality
|
||||||
// Feature Registration Pattern implementation
|
// 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();
|
registerServiceWorker();
|
||||||
|
|
||||||
window.addEventListener("online", () => {
|
window.addEventListener("online", () => {
|
||||||
@@ -46,4 +49,4 @@ export function checkOnlineStatus(): boolean {
|
|||||||
|
|
||||||
function syncPendingChanges(context: ReaderContext): void {
|
function syncPendingChanges(context: ReaderContext): void {
|
||||||
context.events.emit("offline:sync", {});
|
context.events.emit("offline:sync", {});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
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;
|
||||||
const dockZones: DockZone[] = [
|
const dockZones: DockZone[] = [
|
||||||
{ side: "left", x: 0, width: 400, height: window.innerHeight },
|
{ 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" }) => {
|
context.events.on(
|
||||||
movePanelToSide(context, state, detail.panelId, detail.side);
|
"panel:move",
|
||||||
});
|
(detail: { panelId: string; side: "left" | "right" }) => {
|
||||||
|
movePanelToSide(context, state, detail.panelId, detail.side);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PanelDockState {
|
interface PanelDockState {
|
||||||
@@ -88,39 +94,54 @@ async function loadSettings(): Promise<any> {
|
|||||||
return await response.json();
|
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");
|
const panel = document.createElement("div");
|
||||||
panel.id = `panel-${panelId}`;
|
panel.id = `panel-${panelId}`;
|
||||||
panel.className = `reader-panel panel-${panelState.side}`;
|
panel.className = `reader-panel panel-${panelState.side}`;
|
||||||
panel.dataset.panelId = panelId;
|
panel.dataset.panelId = panelId;
|
||||||
panel.style.width = `${panelState.width_px}px`;
|
panel.style.width = `${panelState.width_px}px`;
|
||||||
|
|
||||||
if (!panelState.visible) {
|
if (!panelState.visible) {
|
||||||
panel.classList.add("panel-hidden");
|
panel.classList.add("panel-hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (panelState.collapsed) {
|
if (panelState.collapsed) {
|
||||||
panel.classList.add("panel-collapsed");
|
panel.classList.add("panel-collapsed");
|
||||||
}
|
}
|
||||||
|
|
||||||
context.elements.readerContent.appendChild(panel);
|
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}`);
|
const panel = document.getElementById(`panel-${panelId}`);
|
||||||
if (panel) {
|
if (panel) {
|
||||||
panel.classList.toggle("panel-hidden", !visible);
|
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}`);
|
const panel = document.getElementById(`panel-${panelId}`);
|
||||||
if (panel) {
|
if (panel) {
|
||||||
panel.classList.toggle("panel-collapsed", collapsed);
|
panel.classList.toggle("panel-collapsed", collapsed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupGlobalDragHandlers(context: ReaderContext, state: PanelDockState): void {
|
function setupGlobalDragHandlers(
|
||||||
|
context: ReaderContext,
|
||||||
|
state: PanelDockState,
|
||||||
|
): void {
|
||||||
document.addEventListener("mousedown", (e) => {
|
document.addEventListener("mousedown", (e) => {
|
||||||
const panelHeader = (e.target as HTMLElement).closest(".panel-header");
|
const panelHeader = (e.target as HTMLElement).closest(".panel-header");
|
||||||
if (panelHeader) {
|
if (panelHeader) {
|
||||||
@@ -149,9 +170,17 @@ function setupGlobalDragHandlers(context: ReaderContext, state: PanelDockState):
|
|||||||
|
|
||||||
document.addEventListener("mouseup", () => {
|
document.addEventListener("mouseup", () => {
|
||||||
if (state.dragState) {
|
if (state.dragState) {
|
||||||
const nearestZone = findNearestDockZone(state.dragState.currentX, state.dockZones);
|
const nearestZone = findNearestDockZone(
|
||||||
|
state.dragState.currentX,
|
||||||
|
state.dockZones,
|
||||||
|
);
|
||||||
if (nearestZone) {
|
if (nearestZone) {
|
||||||
movePanelToSide(context, state, state.dragState.panelId, nearestZone.side);
|
movePanelToSide(
|
||||||
|
context,
|
||||||
|
state,
|
||||||
|
state.dragState.panelId,
|
||||||
|
nearestZone.side,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
state.dragState = null;
|
state.dragState = null;
|
||||||
}
|
}
|
||||||
@@ -173,7 +202,12 @@ function findNearestDockZone(x: number, zones: DockZone[]): DockZone | null {
|
|||||||
return nearest;
|
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);
|
const panelState = state.panels.get(panelId);
|
||||||
if (!panelState) return;
|
if (!panelState) return;
|
||||||
|
|
||||||
@@ -187,4 +221,4 @@ function movePanelToSide(context: ReaderContext, state: PanelDockState, panelId:
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.events.emit("panel:moved", { panelId, side });
|
context.events.emit("panel:moved", { panelId, side });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ interface ProgressDisplay {
|
|||||||
mode: "pages" | "chapter" | "percentage" | "time-left";
|
mode: "pages" | "chapter" | "percentage" | "time-left";
|
||||||
text: string;
|
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", () => {
|
context.events.on("pageChanged", () => {
|
||||||
updateProgressDisplay(context);
|
updateProgressDisplay(context);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
// Track reading speed and update database
|
// Track reading speed and update database
|
||||||
// Feature Registration Pattern implementation
|
// 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;
|
let state: ReadingSpeedTrackerState | null = null;
|
||||||
|
|
||||||
context.events.on("reader:loaded", (detail: { mediaItemId: string }) => {
|
context.events.on("reader:loaded", (detail: { mediaItemId: string }) => {
|
||||||
@@ -122,4 +125,4 @@ async function syncReadingSpeed(
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to sync reading speed:", error);
|
console.error("Failed to sync reading speed:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user