From 3c2540a1005eac916e4279d80caf317feacadc30 Mon Sep 17 00:00:00 2001 From: Fangdun Tsai Date: Thu, 14 Nov 2024 07:16:15 +0800 Subject: [PATCH] feat: pdf viewer supports fit to page --- .../attachment-viewer/pdf-viewer.tsx | 50 ++++++++++++++----- .../components/hooks/affine/use-share-url.ts | 5 +- .../core/src/modules/pdf/views/components.tsx | 16 ++++-- .../src/modules/pdf/views/page-renderer.tsx | 43 ++++++++++------ .../core/src/utils/clipboard/index.ts | 4 +- 5 files changed, 80 insertions(+), 38 deletions(-) diff --git a/packages/frontend/core/src/components/attachment-viewer/pdf-viewer.tsx b/packages/frontend/core/src/components/attachment-viewer/pdf-viewer.tsx index 4de7630160621..3c5f6bfa9e15f 100644 --- a/packages/frontend/core/src/components/attachment-viewer/pdf-viewer.tsx +++ b/packages/frontend/core/src/components/attachment-viewer/pdf-viewer.tsx @@ -33,6 +33,12 @@ import { calculatePageNum } from './utils'; const THUMBNAIL_WIDTH = 94; +enum DisplayMode { + FitToPage, + FitToWidth, + ActualSize, +} + interface ViewerProps { model: AttachmentBlockModel; } @@ -44,6 +50,8 @@ interface PDFViewerInnerProps { const PDFViewerInner = ({ pdf, state }: PDFViewerInnerProps) => { const [cursor, setCursor] = useState(0); + const [zoom, setZoom] = useState(1); + const [mode, _setMode] = useState(DisplayMode.FitToPage); const [collapsed, setCollapsed] = useState(true); const [viewportInfo, setViewportInfo] = useState({ width: 0, height: 0 }); @@ -69,11 +77,7 @@ const PDFViewerInner = ({ pdf, state }: PDFViewerInnerProps) => { const scroller = pagesScrollerHandleRef.current; if (!scroller) return; - scroller.scrollToIndex({ - index, - align: 'center', - behavior: 'smooth', - }); + scroller.scrollToIndex({ index, align: 'center', behavior: 'smooth' }); }, [pagesScrollerHandleRef] ); @@ -99,13 +103,23 @@ const PDFViewerInner = ({ pdf, state }: PDFViewerInnerProps) => { [pdf] ); + const pagesContext = useMemo(() => { + const { width: w, height: h } = state.meta; + + return { + width: zoom * w, + height: zoom * h, + pageClassName: styles.pdfPage, + }; + }, [state, zoom]); + const thumbnailsConfig = useMemo(() => { const { height: vh } = viewportInfo; - const { pageCount: t, height: h, width: w } = state.meta; + const { width: w, height: h, pageCount: c } = state.meta; const p = h / (w || 1); const pw = THUMBNAIL_WIDTH; const ph = Math.ceil(pw * p); - const height = Math.min(vh - 60 - 24 - 24 - 2 - 8, t * ph + (t - 1) * 12); + const height = Math.min(vh - 60 - 24 - 24 - 2 - 8, c * ph + (c - 1) * 12); return { context: { width: pw, @@ -124,6 +138,22 @@ const PDFViewerInner = ({ pdf, state }: PDFViewerInnerProps) => { }; }, []); + useEffect(() => { + let { width: vw, height: vh } = viewportInfo; + const { width: w, height: h } = state.meta; + + vh -= 40; + vw -= 40; + + if (mode === DisplayMode.FitToPage) { + if (h > w) { + setZoom(vh / h); + } else { + setZoom(vw / w); + } + } + }, [viewportInfo, mode, state]); + useEffect(() => { const viewer = viewerRef.current; if (!viewer) return; @@ -156,11 +186,7 @@ const PDFViewerInner = ({ pdf, state }: PDFViewerInnerProps) => { Footer: ListPadding, ScrollSeekPlaceholder, }} - context={{ - width: state.meta.width, - height: state.meta.height, - pageClassName: styles.pdfPage, - }} + context={pagesContext} scrollSeekConfiguration={scrollSeekConfig} />
diff --git a/packages/frontend/core/src/components/hooks/affine/use-share-url.ts b/packages/frontend/core/src/components/hooks/affine/use-share-url.ts index 8dc9650856467..a8281f80be63e 100644 --- a/packages/frontend/core/src/components/hooks/affine/use-share-url.ts +++ b/packages/frontend/core/src/components/hooks/affine/use-share-url.ts @@ -148,9 +148,8 @@ export const useSharingUrl = ({ workspaceId, pageId }: UseSharingUrl) => { if (sharingUrl) { copyTextToClipboard(sharingUrl) .then(success => { - if (success) { - notify.success({ title: t['Copied link to clipboard']() }); - } + if (!success) return; + notify.success({ title: t['Copied link to clipboard']() }); }) .catch(err => { console.error(err); diff --git a/packages/frontend/core/src/modules/pdf/views/components.tsx b/packages/frontend/core/src/modules/pdf/views/components.tsx index 05c5fa87f6a33..0691f390d8d06 100644 --- a/packages/frontend/core/src/modules/pdf/views/components.tsx +++ b/packages/frontend/core/src/modules/pdf/views/components.tsx @@ -32,16 +32,24 @@ export const ScrollSeekPlaceholder = forwardRef< ScrollSeekPlaceholderProps & { context?: PDFVirtuosoContext; } ->(({ context }, ref) => { +>(({ context, index, height: size }, ref) => { const className = context?.pageClassName; const width = context?.width ?? 537; const height = context?.height ?? 759; const style = { width, aspectRatio: `${width} / ${height}` }; return ( -
- -
+ +
+ +
+
); }); diff --git a/packages/frontend/core/src/modules/pdf/views/page-renderer.tsx b/packages/frontend/core/src/modules/pdf/views/page-renderer.tsx index 80a3dcb6bb840..73c9afc42f1a4 100644 --- a/packages/frontend/core/src/modules/pdf/views/page-renderer.tsx +++ b/packages/frontend/core/src/modules/pdf/views/page-renderer.tsx @@ -1,6 +1,6 @@ import { useI18n } from '@affine/i18n'; import { useLiveData } from '@toeverything/infra'; -import { useEffect, useRef, useState } from 'react'; +import { forwardRef, useEffect, useRef, useState } from 'react'; import type { PDF } from '../entities/pdf'; import type { PDFPage } from '../entities/pdf-page'; @@ -58,27 +58,38 @@ export const PDFPageRenderer = ({ ctx.drawImage(img, 0, 0); }, [img, width, height, scale]); - if (error) { - return ( -
-

- {t['com.affine.pdf.page.render.error']()} -

-
- ); - } - return (
onSelect?.(pageNum)} > - {img === null ? ( - - ) : ( - - )} +
); }; + +interface PageRendererInnerProps { + img: ImageBitmap | null; + err: string | null; +} + +const PageRendererInner = forwardRef( + ({ img, err }, ref) => { + if (img) { + return ; + } + + if (err) { + return

{err}

; + } + + return ; + } +); + +PageRendererInner.displayName = 'pdf-page-renderer-inner'; diff --git a/packages/frontend/core/src/utils/clipboard/index.ts b/packages/frontend/core/src/utils/clipboard/index.ts index aa60ded16d3f9..f82283cd1d116 100644 --- a/packages/frontend/core/src/utils/clipboard/index.ts +++ b/packages/frontend/core/src/utils/clipboard/index.ts @@ -30,9 +30,7 @@ export const copyLinkToBlockStdScopeClipboard = async ( ) => { let success = false; - if (!clipboard) return success; - - if (clipboardWriteIsSupported) { + if (clipboardWriteIsSupported && clipboard) { try { await clipboard.writeToClipboard(items => { items['text/plain'] = text;