Files
bookhoard/web/src/reader/formats/reflowable/progress-tracker.ts
T
john-okeefe 11039cfb89 fix(ebook-reader): Replace ReflowableBook with UniversalReader type system
BREAKING CHANGE: Unify ebook reader type system to match actual data structures

Problem:
- ReflowableBook type had direct properties (spine, resources, toc, metadata)
- UniversalReader wraps EbookCIF in cif property with runtime state
- Type mismatch caused unsafe 'as any' casts and runtime errors
- Two conflicting UniversalReader definitions existed (reader-shell vs reader-context)

Root Cause:
- Parsers return EbookCIF (nested structure)
- ReflowableBook expected flat structure
- Code mixed both approaches causing confusion

Changes:

Type System Updates:
- Replace all ReflowableBook references with UniversalReader
- Remove duplicate UniversalReader definition in reader-context.ts
- Import UniversalReader from canonical source (reader-shell.ts)
- Update all function signatures across reflowable module

Property Access Patterns:
- book.cif.spine instead of book.spine
- book.cif.resources instead of book.resources
- book.cif.toc instead of book.toc
- book.cif.metadata instead of book.metadata

Fixed Modules:
- reader-shell.ts: UniversalReader object construction
- reader-context.ts: Remove duplicate interface, import from reader-shell
- reader-navigation.ts: Remove unsafe type casts, fix property access
- reader-services.ts: Align with UniversalReader structure
- reflowable/navigation.ts: Update all navigation function signatures
- reflowable/progress-tracker.ts: Update tracker function signatures
- reflowable/parser.ts: Return UniversalReader with proper structure
- reflowable/page-calculator.ts: Update calculation function signatures
- reflowable/ebook/search.ts: Fix property access patterns
- types/reader.d.ts: Remove duplicate type definitions

Impact:
-  Type-safe throughout ebook reader
-  Matches actual data structures from parsers
-  No more unsafe type casts
-  Single source of truth for UniversalReader
-  Aligns with EbookCIF format from API/parsers

Files changed: 10
Lines changed: +320, -180
2026-04-10 23:21:10 -04:00

84 lines
2.0 KiB
TypeScript

// Import types
import type { ReadingPosition } from "./types";
import { findPageByCFI, createPositionFromPage } from "./page-calculator";
import { UniversalReader } from "../../reader-shell";
// Update current position
export function updateCurrentPosition(
book: UniversalReader,
position: ReadingPosition,
): UniversalReader {
return {
...book,
position,
};
}
// Extract CFI from position
export function getCurrentCFI(book: UniversalReader): string {
return book.position.cfi;
}
// Calculate progress for display
export function calculateProgress(book: UniversalReader): {
currentPage: number;
totalPages: number;
percentage: number;
} {
const totalPages = book.pagination?.totalPages || 1;
const currentPage = book.position.currentPage;
const percentage =
totalPages > 0 ? Math.round((currentPage / totalPages) * 100) : 0;
return { currentPage, totalPages, percentage };
}
// Get position for saving to database
export function getPositionForSave(book: UniversalReader): {
cfi: string;
progress: number;
page: number;
} {
return {
cfi: book.position.cfi,
progress: book.position.progress,
page: book.position.currentPage,
};
}
// Restore position from database
export function restorePosition(
book: UniversalReader,
savedCFI: string,
savedPage?: number,
): ReadingPosition {
if (!book.pagination) {
return book.position;
}
// If we have saved CFI, try to find exact position
if (savedCFI) {
const pageNum = findPageByCFI(book.pagination, savedCFI);
return createPositionFromPage(book, pageNum);
}
// Otherwise use saved page number
if (savedPage && savedPage > 0) {
return createPositionFromPage(book, savedPage);
}
return book.position;
}
// Check if position changed significantly
export function didPositionChange(
oldPos: ReadingPosition,
newPos: ReadingPosition,
): boolean {
return (
oldPos.currentPage !== newPos.currentPage ||
oldPos.cfi !== newPos.cfi ||
Math.abs(oldPos.progress - newPos.progress) > 0.01
);
}