1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-02 12:47:41 +02:00

Desktop: Add zoom feature on PDF viewer (#6748)

This commit is contained in:
asrient 2022-08-28 16:48:51 +05:30 committed by GitHub
parent ae300de42f
commit 6498f94c36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 210 additions and 63 deletions

View File

@ -842,12 +842,12 @@ packages/app-mobile/components/BackButtonDialogBox.js.map
packages/app-mobile/components/CameraView.d.ts
packages/app-mobile/components/CameraView.js
packages/app-mobile/components/CameraView.js.map
packages/app-mobile/components/Dropdown.d.ts
packages/app-mobile/components/Dropdown.js
packages/app-mobile/components/Dropdown.js.map
packages/app-mobile/components/CustomButton.d.ts
packages/app-mobile/components/CustomButton.js
packages/app-mobile/components/CustomButton.js.map
packages/app-mobile/components/Dropdown.d.ts
packages/app-mobile/components/Dropdown.js
packages/app-mobile/components/Dropdown.js.map
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.d.ts
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js.map
@ -1985,6 +1985,9 @@ packages/pdf-viewer/hooks/usePdfData.js.map
packages/pdf-viewer/hooks/useScaledSize.d.ts
packages/pdf-viewer/hooks/useScaledSize.js
packages/pdf-viewer/hooks/useScaledSize.js.map
packages/pdf-viewer/hooks/useScrollSaver.d.ts
packages/pdf-viewer/hooks/useScrollSaver.js
packages/pdf-viewer/hooks/useScrollSaver.js.map
packages/pdf-viewer/main.d.ts
packages/pdf-viewer/main.js
packages/pdf-viewer/main.js.map
@ -1997,6 +2000,9 @@ packages/pdf-viewer/pdfSource.js.map
packages/pdf-viewer/pdfSource.test.d.ts
packages/pdf-viewer/pdfSource.test.js
packages/pdf-viewer/pdfSource.test.js.map
packages/pdf-viewer/ui/ZoomControls.d.ts
packages/pdf-viewer/ui/ZoomControls.js
packages/pdf-viewer/ui/ZoomControls.js.map
packages/plugin-repo-cli/commands/updateRelease.d.ts
packages/plugin-repo-cli/commands/updateRelease.js
packages/plugin-repo-cli/commands/updateRelease.js.map

6
.gitignore vendored
View File

@ -1974,6 +1974,9 @@ packages/pdf-viewer/hooks/usePdfData.js.map
packages/pdf-viewer/hooks/useScaledSize.d.ts
packages/pdf-viewer/hooks/useScaledSize.js
packages/pdf-viewer/hooks/useScaledSize.js.map
packages/pdf-viewer/hooks/useScrollSaver.d.ts
packages/pdf-viewer/hooks/useScrollSaver.js
packages/pdf-viewer/hooks/useScrollSaver.js.map
packages/pdf-viewer/main.d.ts
packages/pdf-viewer/main.js
packages/pdf-viewer/main.js.map
@ -1986,6 +1989,9 @@ packages/pdf-viewer/pdfSource.js.map
packages/pdf-viewer/pdfSource.test.d.ts
packages/pdf-viewer/pdfSource.test.js
packages/pdf-viewer/pdfSource.test.js.map
packages/pdf-viewer/ui/ZoomControls.d.ts
packages/pdf-viewer/ui/ZoomControls.js
packages/pdf-viewer/ui/ZoomControls.js.map
packages/plugin-repo-cli/commands/updateRelease.d.ts
packages/plugin-repo-cli/commands/updateRelease.js
packages/plugin-repo-cli/commands/updateRelease.js.map

View File

@ -13,7 +13,7 @@
},
"scripts": {
"buildParallel": "yarn workspaces foreach --verbose --interlaced --parallel --jobs 2 run build && yarn run tsc",
"buildSequential": "yarn workspaces foreach --verbose --interlaced run build && yarn run tsc",
"buildSequential": "yarn workspaces foreach --verbose --interlaced --topological run build && yarn run tsc",
"buildApiDoc": "yarn workspace joplin start apidoc ../../readme/api/references/rest_api.md",
"buildCommandIndex": "gulp buildCommandIndex",
"buildPluginDoc": "typedoc --name 'Joplin Plugin API Documentation' --mode file -theme './Assets/PluginDocTheme/' --readme './Assets/PluginDocTheme/index.md' --excludeNotExported --excludeExternals --excludePrivate --excludeProtected --out ../joplin-website/docs/api/references/plugin_api packages/lib/services/plugins/api/",

View File

@ -4,6 +4,7 @@ import { PdfData } from './pdfSource';
import Page from './Page';
import styled from 'styled-components';
import useScaledSize, { ScaledSizeParams } from './hooks/useScaledSize';
import useScrollSaver, { ScrollSaver } from './hooks/useScrollSaver';
const PagesHolder = styled.div<{ pageGap: number }>`
@ -11,7 +12,8 @@ const PagesHolder = styled.div<{ pageGap: number }>`
justify-content: center;
align-items: center;
flex-flow: column;
width: 100%;
width: fit-content;
min-width: 100%;
min-height: 100%;
row-gap: ${(props)=> props.pageGap || 2}px;
`;
@ -22,15 +24,16 @@ export interface VerticalPagesProps {
anchorPage?: number;
rememberScroll?: boolean;
pdfId?: string;
zoom?: number;
container: MutableRefObject<HTMLElement>;
pageGap?: number;
showPageNumbers?: boolean;
}
export default function VerticalPages(props: VerticalPagesProps) {
const [containerWidth, setContainerWidth] = useState<number>(null);
const innerContainerEl = useRef<HTMLDivElement>(null);
const scaledSize = useScaledSize({
pdf: props.pdf,
pdfId: props.pdfId,
@ -40,15 +43,23 @@ export default function VerticalPages(props: VerticalPagesProps) {
container: props.container,
innerContainerEl,
pageGap: props.pageGap,
zoom: props.zoom,
} as ScaledSizeParams);
useScrollSaver({
container: props.container,
scaledSize,
pdfId: props.pdfId,
rememberScroll: props.rememberScroll,
} as ScrollSaver);
useEffect(() => {
let resizeTimer: number = null;
let cancelled = false;
const updateWidth = () => {
if (cancelled) return;
setContainerWidth(innerContainerEl.current.clientWidth);
setContainerWidth(props.container.current.clientWidth);
};
const onResize = () => {
@ -70,37 +81,7 @@ export default function VerticalPages(props: VerticalPagesProps) {
resizeTimer = null;
}
};
}, [props.pdf]);
useEffect(() => {
let scrollTimer: number = null;
const saveScroll = () => {
const scrollTop = props.container.current.scrollTop;
if (props.rememberScroll && props.pdfId) {
sessionStorage.setItem(`pdf.${props.pdfId}.scrollTop`, `${scrollTop}`);
}
};
const onScroll = () => {
if (scrollTimer) {
clearTimeout(scrollTimer);
scrollTimer = null;
}
scrollTimer = window.setTimeout(saveScroll, 200);
};
props.container.current.addEventListener('scroll', onScroll);
return () => {
props.container.current.removeEventListener('scroll', onScroll);
if (scrollTimer) {
clearTimeout(scrollTimer);
scrollTimer = null;
}
};
}, [props.container, props.pdfId, props.rememberScroll]);
}, [props.container, props.pdf]);
return (<PagesHolder pageGap={props.pageGap || 2} ref={innerContainerEl} >
{scaledSize ?

View File

@ -3,6 +3,7 @@ import { useEffect, useState, MutableRefObject } from 'react';
const useIsVisible = (elementRef: MutableRefObject<HTMLElement>, rootRef: MutableRefObject<HTMLElement>) => {
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
let observer: IntersectionObserver = null;
if (elementRef.current) {

View File

@ -11,16 +11,18 @@ export interface ScaledSizeParams {
container: MutableRefObject<HTMLElement>;
innerContainerEl: MutableRefObject<HTMLElement>;
pageGap: number;
zoom: number;
}
const useScaledSize = ({ pdf, pdfId, containerWidth, rememberScroll, anchorPage, container, innerContainerEl, pageGap }: ScaledSizeParams) => {
const useScaledSize = ({ pdf, pdfId, containerWidth, rememberScroll, anchorPage, container, innerContainerEl, pageGap, zoom }: ScaledSizeParams) => {
const [scaledSize, setScaledSize] = useState<ScaledSize>(null);
const currentScaleSize = useRef(scaledSize);
useAsyncEffect(async (event: AsyncEffectEvent) => {
if (!pdf || !containerWidth) return;
// console.log('scaledSize calculation triggered');
const scaledSize_ = await pdf.getScaledSize(null, containerWidth - 10);
const effectiveWidth = Math.min(containerWidth - 20, 900) * (zoom || 1);
const scaledSize_ = await pdf.getScaledSize(null, effectiveWidth);
if (event.cancelled) return;
const oldScaleSize = currentScaleSize.current;
@ -40,11 +42,12 @@ const useScaledSize = ({ pdf, pdfId, containerWidth, rememberScroll, anchorPage,
if (rememberScroll && pdfId && !oldScaleSize && !anchorPage) {
const scrollOffset = parseInt(sessionStorage.getItem(`pdf.${pdfId}.scrollTop`), 10) || null;
if (scrollOffset) {
container.current.scrollTop = scrollOffset;
// Adjusting it according to the new scale
container.current.scrollTop = scrollOffset * scaledSize_.scale;
// console.log('scroll set',container.current.scrollTop);
}
}
}, [pdf, pdfId, rememberScroll, anchorPage, containerWidth]);
}, [pdf, pdfId, rememberScroll, anchorPage, containerWidth, zoom]);
return scaledSize;
};

View File

@ -0,0 +1,53 @@
import { useRef, useEffect, MutableRefObject } from 'react';
import { ScaledSize } from '../pdfSource';
export interface ScrollSaver {
container: MutableRefObject<HTMLElement>;
scaledSize: ScaledSize;
pdfId: string;
rememberScroll: boolean;
}
const useScrollSaver = ({ container, scaledSize, pdfId, rememberScroll }: ScrollSaver) => {
const currentScaleSize = useRef(scaledSize);
useEffect(() => {
let scrollTimer: number = null;
const containerElement = container.current;
const saveScroll = () => {
if (!currentScaleSize.current) return;
const scale = currentScaleSize.current.scale;
const scrollTop = container.current.scrollTop / scale;
if (rememberScroll && pdfId) {
sessionStorage.setItem(`pdf.${pdfId}.scrollTop`, `${scrollTop}`);
}
};
const onScroll = () => {
if (scrollTimer) {
clearTimeout(scrollTimer);
scrollTimer = null;
}
scrollTimer = window.setTimeout(saveScroll, 200);
};
containerElement.addEventListener('scroll', onScroll);
return () => {
containerElement.removeEventListener('scroll', onScroll);
if (scrollTimer) {
clearTimeout(scrollTimer);
scrollTimer = null;
}
};
}, [container, pdfId, rememberScroll, currentScaleSize]);
useEffect(() => {
currentScaleSize.current = scaledSize;
} , [scaledSize]);
return scaledSize;
};
export default useScrollSaver;

View File

@ -1,7 +1,8 @@
import React, { useRef } from 'react';
import React, { useRef, useState } from 'react';
import useIsFocused from './hooks/useIsFocused';
import usePdfData from './hooks/usePdfData';
import VerticalPages from './VerticalPages';
import ZoomControls from './ui/ZoomControls';
export interface MiniViewerAppProps {
pdfPath: string;
@ -13,6 +14,7 @@ export interface MiniViewerAppProps {
export default function MiniViewerApp(props: MiniViewerAppProps) {
const pdf = usePdfData(props.pdfPath);
const isFocused = useIsFocused();
const [zoom, setZoom] = useState<number>(1);
const containerEl = useRef<HTMLDivElement>(null);
if (!pdf) {
@ -25,12 +27,20 @@ export default function MiniViewerApp(props: MiniViewerAppProps) {
return (
<div className={`mini-app${isFocused ? ' focused' : ''}`}>
<div className={`app-pages${isFocused ? ' focused' : ''}`} ref={containerEl}>
<VerticalPages pdf={pdf} isDarkTheme={props.isDarkTheme} anchorPage={props.anchorPage} pdfId={props.pdfId} rememberScroll={true}
container={containerEl} showPageNumbers={true} />
<VerticalPages
pdf={pdf}
isDarkTheme={props.isDarkTheme}
anchorPage={props.anchorPage}
pdfId={props.pdfId}
rememberScroll={true}
container={containerEl}
showPageNumbers={true}
zoom={zoom} />
</div>
<div className='app-bottom-bar'>
<div className='pdf-info'>
{pdf.pageCount} pages
<div style={{ paddingRight: '0.4rem' }}>{pdf.pageCount} pages</div>
<ZoomControls onChange={setZoom} zoom={zoom} />
</div>
<div>{isFocused ? '' : 'Click to enable scroll'}</div>
</div>

View File

@ -36,6 +36,9 @@
"webpack-cli": "^4.10.0"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.1.2",
"@fortawesome/free-solid-svg-icons": "^6.1.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@joplin/lib": "workspace:^",
"pdfjs-dist": "^2.14.305",
"react": "16.13.1",

View File

@ -0,0 +1,46 @@
import React from 'react';
import styled from 'styled-components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faMinus, faPlus } from '@fortawesome/free-solid-svg-icons';
const ZoomGroup = styled.div<{ size: number }>`
display: flex;
justify-content: center;
align-items: center;
flex-flow: row;
color: var(--grey);
cursor: initial;
font-size: ${props => props.size}rem;
padding: 0.2rem 0.4rem;
user-select: none;
border-radius: 5px;
&:hover {
background: #7676764d;
}
svg:hover {
color: var(--secondary);
}
`;
export interface ZoomControlsProps {
zoom: number;
onChange: (zoom: number)=> void;
size?: number;
}
export default function ZoomControls(props: ZoomControlsProps) {
const zoomIn = () => {
props.onChange(Math.min(props.zoom + 0.25, 2));
};
const zoomOut = () => {
props.onChange(Math.max(props.zoom - 0.25, 0.5));
};
return (<ZoomGroup size={props.size || 0.8}>
<FontAwesomeIcon icon={faMinus} title="Zoom Out" style={{ paddingRight: '0.2rem', cursor: 'pointer' }} onClick={zoomOut} />
<span style={{ color: 'grey' }} >{props.zoom * 100}%</span>
<FontAwesomeIcon icon={faPlus} title="Zoom In" style={{ paddingLeft: '0.2rem', cursor: 'pointer' }} onClick={zoomIn} />
</ZoomGroup>);
}

View File

@ -115,23 +115,9 @@ hr {
color: var(--grey);
}
.pdf-info>a {
padding: 0rem 0.4rem;
color: var(--blue);
cursor: pointer;
border: solid thin transparent;
border-radius: 5px;
user-select: none;
}
.pdf-info>a:hover {
border-color: var(--grey);
}
.app-pages {
display: flex;
justify-content: center;
align-items: start;
display: block;
margin: 0px auto;
overflow-x: hidden;
width: 100%;
height: 100%;
@ -154,4 +140,5 @@ hr {
flex-direction: row;
justify-content: space-around;
align-items: center;
column-gap: 0.2rem;
}

View File

@ -3170,6 +3170,13 @@ __metadata:
languageName: node
linkType: hard
"@fortawesome/fontawesome-common-types@npm:6.1.2":
version: 6.1.2
resolution: "@fortawesome/fontawesome-common-types@npm:6.1.2"
checksum: 16ba97f73256adef5cd680a027e6dbd3e76f34ba9227f7bd9e8c372cb4f337c7ccbf41f35dcfbc22b11320bbb579d01daccf1aa7ae47df5d0b17260c0097bb30
languageName: node
linkType: hard
"@fortawesome/fontawesome-common-types@npm:^0.2.36":
version: 0.2.36
resolution: "@fortawesome/fontawesome-common-types@npm:0.2.36"
@ -3193,6 +3200,15 @@ __metadata:
languageName: node
linkType: hard
"@fortawesome/fontawesome-svg-core@npm:^6.1.2":
version: 6.1.2
resolution: "@fortawesome/fontawesome-svg-core@npm:6.1.2"
dependencies:
"@fortawesome/fontawesome-common-types": 6.1.2
checksum: bb82ed1e79ade9977746b4bb28ac0d03de7557e7c923446adec78cb3447740aca0859e91d6310007d758055a1b8f3a6f740915f04cac738931135c9bdbfe5d32
languageName: node
linkType: hard
"@fortawesome/free-regular-svg-icons@npm:^5.13.0":
version: 5.15.4
resolution: "@fortawesome/free-regular-svg-icons@npm:5.15.4"
@ -3211,6 +3227,27 @@ __metadata:
languageName: node
linkType: hard
"@fortawesome/free-solid-svg-icons@npm:^6.1.2":
version: 6.1.2
resolution: "@fortawesome/free-solid-svg-icons@npm:6.1.2"
dependencies:
"@fortawesome/fontawesome-common-types": 6.1.2
checksum: b7258cd092304f9bd1e557cd3a59e9ff560165631b04f16785da14ce3148873fa8e688d62f95d68f668be32e1207c47a1fe556978ae7e9573a5b0fec09fc0f0c
languageName: node
linkType: hard
"@fortawesome/react-fontawesome@npm:^0.2.0":
version: 0.2.0
resolution: "@fortawesome/react-fontawesome@npm:0.2.0"
dependencies:
prop-types: ^15.8.1
peerDependencies:
"@fortawesome/fontawesome-svg-core": ~1 || ~6
react: ">=16.3"
checksum: f652a0c2172e7b209e2d9e7e511f9b8c17abad85f55e0bd09bb1175ea1927693215da47eb6cd95b1f3a23bd124368553c677907fa76cb17c5093afc1fcffe338
languageName: node
linkType: hard
"@gar/promisify@npm:^1.0.1":
version: 1.1.2
resolution: "@gar/promisify@npm:1.1.2"
@ -4293,6 +4330,9 @@ __metadata:
version: 0.0.0-use.local
resolution: "@joplin/pdf-viewer@workspace:packages/pdf-viewer"
dependencies:
"@fortawesome/fontawesome-svg-core": ^6.1.2
"@fortawesome/free-solid-svg-icons": ^6.1.2
"@fortawesome/react-fontawesome": ^0.2.0
"@joplin/lib": "workspace:^"
"@types/jest": ^28.1.6
"@types/pdfjs-dist": ^2.10.378
@ -27614,6 +27654,17 @@ __metadata:
languageName: node
linkType: hard
"prop-types@npm:^15.8.1":
version: 15.8.1
resolution: "prop-types@npm:15.8.1"
dependencies:
loose-envify: ^1.4.0
object-assign: ^4.1.1
react-is: ^16.13.1
checksum: c056d3f1c057cb7ff8344c645450e14f088a915d078dcda795041765047fa080d38e5d626560ccaac94a4e16e3aa15f3557c1a9a8d1174530955e992c675e459
languageName: node
linkType: hard
"proper-lockfile@npm:^2.0.1":
version: 2.0.1
resolution: "proper-lockfile@npm:2.0.1"
@ -28036,7 +28087,7 @@ __metadata:
languageName: node
linkType: hard
"react-is@npm:^16.12.0, react-is@npm:^16.7.0, react-is@npm:^16.8.1, react-is@npm:^16.8.6":
"react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.7.0, react-is@npm:^16.8.1, react-is@npm:^16.8.6":
version: 16.13.1
resolution: "react-is@npm:16.13.1"
checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f