feat: add zoom functionality to BookReader and related components, enabling zoom state management and touch gesture handling
This commit is contained in:
@@ -19,6 +19,7 @@ export function BookReader({ book, pages, onClose, nextBook }: BookReaderProps)
|
|||||||
const [isDoublePage, setIsDoublePage] = useState(false);
|
const [isDoublePage, setIsDoublePage] = useState(false);
|
||||||
const [showControls, setShowControls] = useState(false);
|
const [showControls, setShowControls] = useState(false);
|
||||||
const [showThumbnails, setShowThumbnails] = useState(false);
|
const [showThumbnails, setShowThumbnails] = useState(false);
|
||||||
|
const [isZoomed, setIsZoomed] = useState(false);
|
||||||
const readerRef = useRef<HTMLDivElement>(null);
|
const readerRef = useRef<HTMLDivElement>(null);
|
||||||
const isLandscape = useOrientation();
|
const isLandscape = useOrientation();
|
||||||
const { direction, toggleDirection, isRTL } = useReadingDirection();
|
const { direction, toggleDirection, isRTL } = useReadingDirection();
|
||||||
@@ -43,6 +44,7 @@ export function BookReader({ book, pages, onClose, nextBook }: BookReaderProps)
|
|||||||
onClose,
|
onClose,
|
||||||
direction,
|
direction,
|
||||||
nextBook,
|
nextBook,
|
||||||
|
isZoomed,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { preloadPage, getPageUrl, cleanCache } = usePageCache({
|
const { preloadPage, getPageUrl, cleanCache } = usePageCache({
|
||||||
@@ -138,6 +140,7 @@ export function BookReader({ book, pages, onClose, nextBook }: BookReaderProps)
|
|||||||
shouldShowDoublePage={shouldShowDoublePage}
|
shouldShowDoublePage={shouldShowDoublePage}
|
||||||
isRTL={isRTL}
|
isRTL={isRTL}
|
||||||
onThumbnailLoad={handleThumbnailLoad}
|
onThumbnailLoad={handleThumbnailLoad}
|
||||||
|
onZoomChange={setIsZoomed}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavigationBar
|
<NavigationBar
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ interface ReaderContentProps {
|
|||||||
shouldShowDoublePage: (page: number) => boolean;
|
shouldShowDoublePage: (page: number) => boolean;
|
||||||
isRTL: boolean;
|
isRTL: boolean;
|
||||||
onThumbnailLoad: (pageNumber: number) => void;
|
onThumbnailLoad: (pageNumber: number) => void;
|
||||||
|
onZoomChange?: (isZoomed: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ReaderContent = ({
|
export const ReaderContent = ({
|
||||||
@@ -22,6 +23,7 @@ export const ReaderContent = ({
|
|||||||
shouldShowDoublePage,
|
shouldShowDoublePage,
|
||||||
isRTL,
|
isRTL,
|
||||||
onThumbnailLoad,
|
onThumbnailLoad,
|
||||||
|
onZoomChange,
|
||||||
}: ReaderContentProps) => {
|
}: ReaderContentProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="relative flex-1 flex items-center justify-center overflow-hidden p-1">
|
<div className="relative flex-1 flex items-center justify-center overflow-hidden p-1">
|
||||||
@@ -34,6 +36,7 @@ export const ReaderContent = ({
|
|||||||
isDoublePage={isDoublePage}
|
isDoublePage={isDoublePage}
|
||||||
isRTL={isRTL}
|
isRTL={isRTL}
|
||||||
order="first"
|
order="first"
|
||||||
|
onZoomChange={onZoomChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isDoublePage && shouldShowDoublePage(currentPage) && (
|
{isDoublePage && shouldShowDoublePage(currentPage) && (
|
||||||
@@ -45,6 +48,7 @@ export const ReaderContent = ({
|
|||||||
isDoublePage={true}
|
isDoublePage={true}
|
||||||
isRTL={isRTL}
|
isRTL={isRTL}
|
||||||
order="second"
|
order="second"
|
||||||
|
onZoomChange={onZoomChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
|
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
interface ZoomablePageProps {
|
interface ZoomablePageProps {
|
||||||
pageUrl: string | null;
|
pageUrl: string | null;
|
||||||
@@ -9,6 +10,7 @@ interface ZoomablePageProps {
|
|||||||
isDoublePage?: boolean;
|
isDoublePage?: boolean;
|
||||||
isRTL?: boolean;
|
isRTL?: boolean;
|
||||||
order?: "first" | "second";
|
order?: "first" | "second";
|
||||||
|
onZoomChange?: (isZoomed: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ZoomablePage = ({
|
export const ZoomablePage = ({
|
||||||
@@ -19,7 +21,14 @@ export const ZoomablePage = ({
|
|||||||
isDoublePage = false,
|
isDoublePage = false,
|
||||||
isRTL = false,
|
isRTL = false,
|
||||||
order = "first",
|
order = "first",
|
||||||
|
onZoomChange,
|
||||||
}: ZoomablePageProps) => {
|
}: ZoomablePageProps) => {
|
||||||
|
const [currentScale, setCurrentScale] = useState(1);
|
||||||
|
|
||||||
|
const handleTransform = (ref: any, state: { scale: number; positionX: number; positionY: number }) => {
|
||||||
|
setCurrentScale(state.scale);
|
||||||
|
onZoomChange?.(state.scale > 1.1);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -55,6 +64,7 @@ export const ZoomablePage = ({
|
|||||||
panning={{ disabled: false }}
|
panning={{ disabled: false }}
|
||||||
limitToBounds={true}
|
limitToBounds={true}
|
||||||
centerZoomedOut={false}
|
centerZoomedOut={false}
|
||||||
|
onTransformed={handleTransform}
|
||||||
>
|
>
|
||||||
<TransformComponent
|
<TransformComponent
|
||||||
wrapperClass="w-full h-full flex items-center justify-center"
|
wrapperClass="w-full h-full flex items-center justify-center"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ interface UsePageNavigationProps {
|
|||||||
onClose?: (currentPage: number) => void;
|
onClose?: (currentPage: number) => void;
|
||||||
direction: "ltr" | "rtl";
|
direction: "ltr" | "rtl";
|
||||||
nextBook?: KomgaBook | null;
|
nextBook?: KomgaBook | null;
|
||||||
|
isZoomed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usePageNavigation = ({
|
export const usePageNavigation = ({
|
||||||
@@ -19,6 +20,7 @@ export const usePageNavigation = ({
|
|||||||
onClose,
|
onClose,
|
||||||
direction,
|
direction,
|
||||||
nextBook,
|
nextBook,
|
||||||
|
isZoomed = false,
|
||||||
}: UsePageNavigationProps) => {
|
}: UsePageNavigationProps) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const cPage = ClientOfflineBookService.getCurrentPage(book);
|
const cPage = ClientOfflineBookService.getCurrentPage(book);
|
||||||
@@ -133,25 +135,22 @@ export const usePageNavigation = ({
|
|||||||
|
|
||||||
const handleTouchStart = useCallback(
|
const handleTouchStart = useCallback(
|
||||||
(event: TouchEvent) => {
|
(event: TouchEvent) => {
|
||||||
// Ne gérer que les gestes à un seul doigt
|
// Si on est zoomé, on ne gère pas la navigation
|
||||||
if (event.touches.length === 1) {
|
if (isZoomed) {
|
||||||
touchStartXRef.current = event.touches[0].clientX;
|
|
||||||
touchStartYRef.current = event.touches[0].clientY;
|
|
||||||
currentPageRef.current = currentPage;
|
|
||||||
} else {
|
|
||||||
// Reset les références pour les gestes multi-touch (pinch)
|
|
||||||
touchStartXRef.current = null;
|
touchStartXRef.current = null;
|
||||||
touchStartYRef.current = null;
|
touchStartYRef.current = null;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
touchStartXRef.current = event.touches[0].clientX;
|
||||||
|
touchStartYRef.current = event.touches[0].clientY;
|
||||||
|
currentPageRef.current = currentPage;
|
||||||
},
|
},
|
||||||
[currentPage]
|
[currentPage, isZoomed]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTouchEnd = useCallback(
|
const handleTouchEnd = useCallback(
|
||||||
(event: TouchEvent) => {
|
(event: TouchEvent) => {
|
||||||
// Ne traiter que les gestes à un seul doigt
|
|
||||||
if (event.touches.length > 1) return;
|
|
||||||
|
|
||||||
if (touchStartXRef.current === null || touchStartYRef.current === null) return;
|
if (touchStartXRef.current === null || touchStartYRef.current === null) return;
|
||||||
|
|
||||||
const touchEndX = event.changedTouches[0].clientX;
|
const touchEndX = event.changedTouches[0].clientX;
|
||||||
@@ -163,7 +162,7 @@ export const usePageNavigation = ({
|
|||||||
// on ne fait rien (pour éviter de confondre avec un scroll)
|
// on ne fait rien (pour éviter de confondre avec un scroll)
|
||||||
if (Math.abs(deltaY) > Math.abs(deltaX)) return;
|
if (Math.abs(deltaY) > Math.abs(deltaX)) return;
|
||||||
|
|
||||||
// Augmenter le seuil pour éviter les changements de page accidentels
|
// Seuil pour éviter les changements de page accidentels
|
||||||
if (Math.abs(deltaX) > 100) {
|
if (Math.abs(deltaX) > 100) {
|
||||||
if (deltaX > 0) {
|
if (deltaX > 0) {
|
||||||
// Swipe vers la droite
|
// Swipe vers la droite
|
||||||
|
|||||||
Reference in New Issue
Block a user