From c1ee9f4e3393117c5f11036cec9f526f11c87e2f Mon Sep 17 00:00:00 2001 From: codedogQBY <1369175442@qq.com> Date: Sat, 13 Jun 2026 07:45:43 +0800 Subject: [PATCH] fix(reader): add desktop pdf zoom controls --- .../src/components/reader/FoliateViewer.tsx | 9 ++- .../src/components/reader/ReaderToolbar.tsx | 64 ++++++++++++++++++- .../app/src/components/reader/ReaderView.tsx | 21 ++++++ packages/core/src/i18n/locales/en/reader.json | 4 ++ packages/core/src/i18n/locales/zh/reader.json | 4 ++ packages/core/src/stores/settings-store.ts | 1 + packages/core/src/types/book.ts | 1 + packages/foliate-js/fixed-layout.js | 22 +++++-- 8 files changed, 117 insertions(+), 9 deletions(-) diff --git a/packages/app/src/components/reader/FoliateViewer.tsx b/packages/app/src/components/reader/FoliateViewer.tsx index 1179c838..d309ee75 100644 --- a/packages/app/src/components/reader/FoliateViewer.tsx +++ b/packages/app/src/components/reader/FoliateViewer.tsx @@ -2783,7 +2783,13 @@ export const FoliateViewer = forwardRef } applyReflowLayoutSettings(view, viewSettings); - }, [viewSettings.viewMode, viewSettings.paginatedLayout, isFixedLayout, appTheme]); + }, [ + viewSettings.viewMode, + viewSettings.paginatedLayout, + viewSettings.fixedLayoutZoom, + isFixedLayout, + appTheme, + ]); const handleViewerShellClick = useCallback( (event: { @@ -3068,6 +3074,7 @@ function applyRendererSettings( // EPUBs do not look "shrunk inside a spread". Double-page mode still uses // fit-page to keep both pages fully visible inside the viewport. renderer.setAttribute("zoom", isSinglePage ? "fit-width" : "fit-page"); + renderer.setAttribute("zoom-factor", String(settings.fixedLayoutZoom ?? 1)); if (view.book?.rendition) { view.book.rendition.spread = spreadMode; } diff --git a/packages/app/src/components/reader/ReaderToolbar.tsx b/packages/app/src/components/reader/ReaderToolbar.tsx index 590c744a..58cd4398 100644 --- a/packages/app/src/components/reader/ReaderToolbar.tsx +++ b/packages/app/src/components/reader/ReaderToolbar.tsx @@ -5,8 +5,8 @@ import { useAnnotationStore } from "@/stores/annotation-store"; import { useAppStore } from "@/stores/app-store"; import { useNotebookStore } from "@/stores/notebook-store"; import { useReaderStore } from "@/stores/reader-store"; -import { generateId } from "@readany/core/utils"; import type { ChapterTranslationState } from "@readany/core/hooks"; +import { generateId } from "@readany/core/utils"; import { ArrowLeft, Bookmark, @@ -16,9 +16,12 @@ import { MessageSquare, NotebookPen, Pin, + RotateCcw, Search, Settings, Undo, + ZoomIn, + ZoomOut, } from "lucide-react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; @@ -46,6 +49,11 @@ interface ReaderToolbarProps { isChatOpen?: boolean; isTTSActive?: boolean; isFixedLayout?: boolean; + fixedLayoutZoom?: number; + fixedLayoutZoomMin?: number; + fixedLayoutZoomMax?: number; + fixedLayoutZoomStep?: number; + onFixedLayoutZoomChange?: (zoom: number) => void; isPinned?: boolean; onTogglePinned?: () => void; onMouseEnter?: () => void; @@ -73,7 +81,12 @@ export function ReaderToolbar({ onChapterTranslationReset, isChatOpen, isTTSActive, - isFixedLayout: _isFixedLayout = false, + isFixedLayout = false, + fixedLayoutZoom = 1, + fixedLayoutZoomMin = 0.5, + fixedLayoutZoomMax = 3, + fixedLayoutZoomStep = 0.1, + onFixedLayoutZoomChange, isPinned = false, onTogglePinned, onMouseEnter, @@ -95,6 +108,10 @@ export function ReaderToolbar({ const bookId = tab?.bookId || ""; const existingBookmark = bookmarks.find((b) => b.bookId === bookId && b.cfi === currentCfi); const isBookmarked = !!existingBookmark; + const fixedLayoutZoomPercent = Math.round(fixedLayoutZoom * 100); + const canZoomOut = fixedLayoutZoom > fixedLayoutZoomMin + 0.001; + const canZoomIn = fixedLayoutZoom < fixedLayoutZoomMax - 0.001; + const canResetZoom = Math.abs(fixedLayoutZoom - 1) > 0.001; const handleToggleBookmark = () => { if (!currentCfi || !bookId) return; @@ -211,6 +228,49 @@ export function ReaderToolbar({ onReset={onChapterTranslationReset} /> + {isFixedLayout && ( +
+ + + {fixedLayoutZoomPercent}% + + + +
+ )}