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