refactor(reader): remove old callback-based feature files
- Delete gestures.ts: replaced by features/gestures.ts - Delete keyboard-shortcuts.ts: replaced by features/keyboard-shortcuts.ts - Delete progress-indicator.ts: replaced by features/progress-indicator.ts - Old files used callback pattern, new files use init(context) pattern
This commit is contained in:
@@ -1,115 +0,0 @@
|
||||
// Touch gesture controls for mobile/tablet
|
||||
// Procedural implementation (no OOP)
|
||||
|
||||
interface GestureHandlers {
|
||||
onSwipeLeft: () => void;
|
||||
onSwipeRight: () => void;
|
||||
onSwipeUp: () => void;
|
||||
onSwipeDown: () => void;
|
||||
onPinch: (scale: number) => void;
|
||||
onTap: () => void;
|
||||
onDoubleTap: () => void;
|
||||
}
|
||||
|
||||
interface GestureState {
|
||||
touchStartX: number;
|
||||
touchStartY: number;
|
||||
touchStartTime: number;
|
||||
lastTapTime: number;
|
||||
initialPinchDistance: number;
|
||||
scale: number;
|
||||
}
|
||||
|
||||
function setupGestureControls(
|
||||
container: HTMLElement,
|
||||
handlers: GestureHandlers,
|
||||
): void {
|
||||
let state: GestureState = {
|
||||
touchStartX: 0,
|
||||
touchStartY: 0,
|
||||
touchStartTime: 0,
|
||||
lastTapTime: 0,
|
||||
initialPinchDistance: 0,
|
||||
scale: 1,
|
||||
};
|
||||
|
||||
container.addEventListener(
|
||||
"touchstart",
|
||||
(e) => {
|
||||
if (e.touches.length === 1) {
|
||||
state.touchStartX = e.touches[0].clientX;
|
||||
state.touchStartY = e.touches[0].clientY;
|
||||
state.touchStartTime = Date.now();
|
||||
} else if (e.touches.length === 2) {
|
||||
state.initialPinchDistance = getPinchDistance(e.touches);
|
||||
}
|
||||
},
|
||||
{ passive: true },
|
||||
);
|
||||
|
||||
container.addEventListener(
|
||||
"touchend",
|
||||
(e) => {
|
||||
const deltaX = e.changedTouches[0].clientX - state.touchStartX;
|
||||
const deltaY = e.changedTouches[0].clientY - state.touchStartY;
|
||||
const deltaTime = Date.now() - state.touchStartTime;
|
||||
|
||||
if (Math.abs(deltaX) < 30 && Math.abs(deltaY) < 30 && deltaTime < 300) {
|
||||
const now = Date.now();
|
||||
if (now - state.lastTapTime < 300) {
|
||||
handlers.onDoubleTap();
|
||||
state.lastTapTime = 0;
|
||||
} else {
|
||||
state.lastTapTime = now;
|
||||
setTimeout(() => {
|
||||
if (state.lastTapTime !== 0) {
|
||||
handlers.onTap();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const minSwipeDistance = 50;
|
||||
const maxSwipeTime = 500;
|
||||
|
||||
if (deltaTime > maxSwipeTime) return;
|
||||
|
||||
if (Math.abs(deltaX) > Math.abs(deltaY)) {
|
||||
if (deltaX > minSwipeDistance) {
|
||||
handlers.onSwipeRight();
|
||||
} else if (deltaX < -minSwipeDistance) {
|
||||
handlers.onSwipeLeft();
|
||||
}
|
||||
} else {
|
||||
if (deltaY > minSwipeDistance) {
|
||||
handlers.onSwipeDown();
|
||||
} else if (deltaY < -minSwipeDistance) {
|
||||
handlers.onSwipeUp();
|
||||
}
|
||||
}
|
||||
},
|
||||
{ passive: true },
|
||||
);
|
||||
|
||||
container.addEventListener(
|
||||
"touchmove",
|
||||
(e) => {
|
||||
if (e.touches.length === 2) {
|
||||
const currentDistance = getPinchDistance(e.touches);
|
||||
if (state.initialPinchDistance > 0) {
|
||||
const scale = currentDistance / state.initialPinchDistance;
|
||||
state.scale = scale;
|
||||
handlers.onPinch(scale);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ passive: true },
|
||||
);
|
||||
}
|
||||
|
||||
function getPinchDistance(touches: TouchList): number {
|
||||
const dx = touches[0].clientX - touches[1].clientX;
|
||||
const dy = touches[0].clientY - touches[1].clientY;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
// Extended keyboard shortcuts for all readers
|
||||
// Procedural implementation (no OOP)
|
||||
|
||||
interface KeyboardShortcutHandler {
|
||||
onNextPage: () => void;
|
||||
onPreviousPage: () => void;
|
||||
onNextChapter: () => void;
|
||||
onPreviousChapter: () => void;
|
||||
onGoToPage: (page: number) => void;
|
||||
onToggleBookmark: () => void;
|
||||
onZoomIn: () => void;
|
||||
onZoomOut: () => void;
|
||||
onToggleFullscreen: () => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function setupKeyboardShortcuts(
|
||||
container: HTMLElement,
|
||||
handlers: KeyboardShortcutHandler,
|
||||
maxPage: number,
|
||||
): void {
|
||||
container.addEventListener("keydown", (e) => {
|
||||
if (
|
||||
e.target instanceof HTMLInputElement ||
|
||||
e.target instanceof HTMLTextAreaElement
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.key) {
|
||||
case "ArrowRight":
|
||||
case "PageDown":
|
||||
case "l":
|
||||
e.preventDefault();
|
||||
handlers.onNextPage();
|
||||
break;
|
||||
|
||||
case "ArrowLeft":
|
||||
case "PageUp":
|
||||
case "h":
|
||||
e.preventDefault();
|
||||
handlers.onPreviousPage();
|
||||
break;
|
||||
|
||||
case "ArrowUp":
|
||||
case "k":
|
||||
e.preventDefault();
|
||||
handlers.onPreviousPage();
|
||||
break;
|
||||
|
||||
case "ArrowDown":
|
||||
case "j":
|
||||
e.preventDefault();
|
||||
handlers.onNextPage();
|
||||
break;
|
||||
|
||||
case " ":
|
||||
e.preventDefault();
|
||||
handlers.onNextPage();
|
||||
break;
|
||||
|
||||
case "Home":
|
||||
e.preventDefault();
|
||||
handlers.onGoToPage(1);
|
||||
break;
|
||||
|
||||
case "End":
|
||||
e.preventDefault();
|
||||
handlers.onGoToPage(maxPage);
|
||||
break;
|
||||
|
||||
case "b":
|
||||
if (!e.ctrlKey && !e.metaKey) {
|
||||
e.preventDefault();
|
||||
handlers.onToggleBookmark();
|
||||
}
|
||||
break;
|
||||
|
||||
case "+":
|
||||
case "=":
|
||||
e.preventDefault();
|
||||
handlers.onZoomIn();
|
||||
break;
|
||||
|
||||
case "-":
|
||||
case "_":
|
||||
e.preventDefault();
|
||||
handlers.onZoomOut();
|
||||
break;
|
||||
|
||||
case "0":
|
||||
e.preventDefault();
|
||||
handlers.onZoomIn();
|
||||
handlers.onZoomIn();
|
||||
handlers.onZoomIn();
|
||||
break;
|
||||
|
||||
case "f":
|
||||
if (!e.ctrlKey && !e.metaKey) {
|
||||
e.preventDefault();
|
||||
handlers.onToggleFullscreen();
|
||||
}
|
||||
break;
|
||||
|
||||
case "Escape":
|
||||
e.preventDefault();
|
||||
handlers.onClose();
|
||||
break;
|
||||
|
||||
default:
|
||||
if (e.key >= "1" && e.key <= "9") {
|
||||
const targetPage = Math.floor((parseInt(e.key) / 10) * maxPage);
|
||||
e.preventDefault();
|
||||
handlers.onGoToPage(targetPage);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showShortcutHelp(): void {
|
||||
const help = document.createElement("div");
|
||||
help.className =
|
||||
"keyboard-shortcut-help fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center z-50";
|
||||
help.innerHTML = `
|
||||
<div class="bg-gray-800 rounded-lg p-6 max-w-md">
|
||||
<h2 class="text-xl font-bold mb-4">Keyboard Shortcuts</h2>
|
||||
<div class="grid grid-cols-2 gap-4 text-sm">
|
||||
<div><kbd class="bg-gray-700 px-2 py-1 rounded">→</kbd> / <kbd class="bg-gray-700 px-2 py-1 rounded">Space</kbd> Next page</div>
|
||||
<div><kbd class="bg-gray-700 px-2 py-1 rounded">←</kbd> Previous page</div>
|
||||
<div><kbd class="bg-gray-700 px-2 py-1 rounded">Home</kbd> First page</div>
|
||||
<div><kbd class="bg-gray-700 px-2 py-1 rounded">End</kbd> Last page</div>
|
||||
<div><kbd class="bg-gray-700 px-2 py-1 rounded">+</kbd> / <kbd class="bg-gray-700 px-2 py-1 rounded">-</kbd> Zoom</div>
|
||||
<div><kbd class="bg-gray-700 px-2 py-1 rounded">B</kbd> Toggle bookmark</div>
|
||||
<div><kbd class="bg-gray-700 px-2 py-1 rounded">F</kbd> Fullscreen</div>
|
||||
<div><kbd class="bg-gray-700 px-2 py-1 rounded">1-9</kbd> Jump to %</div>
|
||||
</div>
|
||||
<button class="mt-4 px-4 py-2 bg-blue-600 rounded" onclick="this.closest('.keyboard-shortcut-help').remove()">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(help);
|
||||
help.addEventListener("click", (e) => {
|
||||
if (e.target === help) help.remove();
|
||||
});
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
// KOReader-style switchable progress indicator
|
||||
|
||||
import { Alpine } from "../alpine";
|
||||
import { getReadingSpeed } from "./api";
|
||||
|
||||
interface ProgressDisplay {
|
||||
mode: "pages" | "chapter" | "percentage" | "time-left";
|
||||
text: string;
|
||||
}
|
||||
|
||||
function calculateProgress(
|
||||
currentPage: number,
|
||||
totalPages: number,
|
||||
currentChapterPage: number,
|
||||
chapterPages: number,
|
||||
readingSpeed?: ReadingSpeed,
|
||||
): ProgressDisplay {
|
||||
const mode = getCurrentProgressMode(); // From settings
|
||||
|
||||
switch (mode) {
|
||||
case "pages":
|
||||
return {
|
||||
mode: "pages",
|
||||
text: `${currentPage}/${totalPages}`,
|
||||
};
|
||||
|
||||
case "chapter":
|
||||
return {
|
||||
mode: "chapter",
|
||||
text: `${currentChapterPage}/${chapterPages}`,
|
||||
};
|
||||
|
||||
case "percentage":
|
||||
const percentage = Math.round((currentPage / totalPages) * 100);
|
||||
return {
|
||||
mode: "percentage",
|
||||
text: `${percentage}%`,
|
||||
};
|
||||
|
||||
case "time-left":
|
||||
if (!readingSpeed) {
|
||||
return { mode: "time-left", text: "--:--" };
|
||||
}
|
||||
const pagesLeft = totalPages - currentPage;
|
||||
const minutesLeft = pagesLeft / readingSpeed.pages_per_minute;
|
||||
const hours = Math.floor(minutesLeft / 60);
|
||||
const mins = Math.round(minutesLeft % 60);
|
||||
return {
|
||||
mode: "time-left",
|
||||
text: `${hours}h ${mins}m`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function cycleProgressMode(): void {
|
||||
const modes: Array<"pages" | "chapter" | "percentage" | "time-left"> = [
|
||||
"pages",
|
||||
"chapter",
|
||||
"percentage",
|
||||
"time-left",
|
||||
];
|
||||
const currentMode = getCurrentProgressMode();
|
||||
const currentIndex = modes.indexOf(currentMode);
|
||||
const nextMode = modes[(currentIndex + 1) % modes.length];
|
||||
setProgressMode(nextMode);
|
||||
}
|
||||
Reference in New Issue
Block a user