mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-21 09:38:01 +02:00
Desktop: Added PDF viewer options (#6800)
This commit is contained in:
parent
eb7083d788
commit
1504cb71ae
@ -2001,6 +2001,9 @@ packages/lib/versionInfo.js.map
|
||||
packages/pdf-viewer/Page.d.ts
|
||||
packages/pdf-viewer/Page.js
|
||||
packages/pdf-viewer/Page.js.map
|
||||
packages/pdf-viewer/PdfDocument.d.ts
|
||||
packages/pdf-viewer/PdfDocument.js
|
||||
packages/pdf-viewer/PdfDocument.js.map
|
||||
packages/pdf-viewer/VerticalPages.d.ts
|
||||
packages/pdf-viewer/VerticalPages.js
|
||||
packages/pdf-viewer/VerticalPages.js.map
|
||||
@ -2010,9 +2013,9 @@ packages/pdf-viewer/hooks/useIsFocused.js.map
|
||||
packages/pdf-viewer/hooks/useIsVisible.d.ts
|
||||
packages/pdf-viewer/hooks/useIsVisible.js
|
||||
packages/pdf-viewer/hooks/useIsVisible.js.map
|
||||
packages/pdf-viewer/hooks/usePdfData.d.ts
|
||||
packages/pdf-viewer/hooks/usePdfData.js
|
||||
packages/pdf-viewer/hooks/usePdfData.js.map
|
||||
packages/pdf-viewer/hooks/usePdfDocument.d.ts
|
||||
packages/pdf-viewer/hooks/usePdfDocument.js
|
||||
packages/pdf-viewer/hooks/usePdfDocument.js.map
|
||||
packages/pdf-viewer/hooks/useScaledSize.d.ts
|
||||
packages/pdf-viewer/hooks/useScaledSize.js
|
||||
packages/pdf-viewer/hooks/useScaledSize.js.map
|
||||
@ -2025,12 +2028,15 @@ packages/pdf-viewer/main.js.map
|
||||
packages/pdf-viewer/miniViewer.d.ts
|
||||
packages/pdf-viewer/miniViewer.js
|
||||
packages/pdf-viewer/miniViewer.js.map
|
||||
packages/pdf-viewer/pdfSource.d.ts
|
||||
packages/pdf-viewer/pdfSource.js
|
||||
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/types.d.ts
|
||||
packages/pdf-viewer/types.js
|
||||
packages/pdf-viewer/types.js.map
|
||||
packages/pdf-viewer/ui/IconButtons.d.ts
|
||||
packages/pdf-viewer/ui/IconButtons.js
|
||||
packages/pdf-viewer/ui/IconButtons.js.map
|
||||
packages/pdf-viewer/ui/ZoomControls.d.ts
|
||||
packages/pdf-viewer/ui/ZoomControls.js
|
||||
packages/pdf-viewer/ui/ZoomControls.js.map
|
||||
|
18
.gitignore
vendored
18
.gitignore
vendored
@ -1989,6 +1989,9 @@ packages/lib/versionInfo.js.map
|
||||
packages/pdf-viewer/Page.d.ts
|
||||
packages/pdf-viewer/Page.js
|
||||
packages/pdf-viewer/Page.js.map
|
||||
packages/pdf-viewer/PdfDocument.d.ts
|
||||
packages/pdf-viewer/PdfDocument.js
|
||||
packages/pdf-viewer/PdfDocument.js.map
|
||||
packages/pdf-viewer/VerticalPages.d.ts
|
||||
packages/pdf-viewer/VerticalPages.js
|
||||
packages/pdf-viewer/VerticalPages.js.map
|
||||
@ -1998,9 +2001,9 @@ packages/pdf-viewer/hooks/useIsFocused.js.map
|
||||
packages/pdf-viewer/hooks/useIsVisible.d.ts
|
||||
packages/pdf-viewer/hooks/useIsVisible.js
|
||||
packages/pdf-viewer/hooks/useIsVisible.js.map
|
||||
packages/pdf-viewer/hooks/usePdfData.d.ts
|
||||
packages/pdf-viewer/hooks/usePdfData.js
|
||||
packages/pdf-viewer/hooks/usePdfData.js.map
|
||||
packages/pdf-viewer/hooks/usePdfDocument.d.ts
|
||||
packages/pdf-viewer/hooks/usePdfDocument.js
|
||||
packages/pdf-viewer/hooks/usePdfDocument.js.map
|
||||
packages/pdf-viewer/hooks/useScaledSize.d.ts
|
||||
packages/pdf-viewer/hooks/useScaledSize.js
|
||||
packages/pdf-viewer/hooks/useScaledSize.js.map
|
||||
@ -2013,12 +2016,15 @@ packages/pdf-viewer/main.js.map
|
||||
packages/pdf-viewer/miniViewer.d.ts
|
||||
packages/pdf-viewer/miniViewer.js
|
||||
packages/pdf-viewer/miniViewer.js.map
|
||||
packages/pdf-viewer/pdfSource.d.ts
|
||||
packages/pdf-viewer/pdfSource.js
|
||||
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/types.d.ts
|
||||
packages/pdf-viewer/types.js
|
||||
packages/pdf-viewer/types.js.map
|
||||
packages/pdf-viewer/ui/IconButtons.d.ts
|
||||
packages/pdf-viewer/ui/IconButtons.js
|
||||
packages/pdf-viewer/ui/IconButtons.js.map
|
||||
packages/pdf-viewer/ui/ZoomControls.d.ts
|
||||
packages/pdf-viewer/ui/ZoomControls.js
|
||||
packages/pdf-viewer/ui/ZoomControls.js.map
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { useEffect, useRef, useState, MutableRefObject } from 'react';
|
||||
import * as React from 'react';
|
||||
import useIsVisible from './hooks/useIsVisible';
|
||||
import { PdfData, ScaledSize } from './pdfSource';
|
||||
import PdfDocument from './PdfDocument';
|
||||
import { ScaledSize } from './types';
|
||||
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
|
||||
import styled from 'styled-components';
|
||||
|
||||
@ -35,7 +36,7 @@ const PageInfo = styled.div`
|
||||
`;
|
||||
|
||||
export interface PageProps {
|
||||
pdf: PdfData;
|
||||
pdfDocument: PdfDocument;
|
||||
pageNo: number;
|
||||
focusOnLoad: boolean;
|
||||
isAnchored: boolean;
|
||||
@ -87,9 +88,9 @@ export default function Page(props: PageProps) {
|
||||
}, [page, props.scaledSize, isVisible, props.pageNo]);
|
||||
|
||||
useAsyncEffect(async (event: AsyncEffectEvent) => {
|
||||
if (page || !isVisible || !props.pdf) return;
|
||||
if (page || !isVisible || !props.pdfDocument) return;
|
||||
try {
|
||||
const _page = await props.pdf.getPage(props.pageNo);
|
||||
const _page = await props.pdfDocument.getPage(props.pageNo);
|
||||
if (event.cancelled) return;
|
||||
setPage(_page);
|
||||
} catch (error) {
|
||||
|
@ -1,13 +1,7 @@
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { ScaledSize } from './types';
|
||||
|
||||
|
||||
export interface ScaledSize {
|
||||
height: number;
|
||||
width: number;
|
||||
scale: number;
|
||||
}
|
||||
|
||||
export class PdfData {
|
||||
export default class PdfDocument {
|
||||
public url: string | Uint8Array;
|
||||
private doc: any = null;
|
||||
public pageCount: number = null;
|
||||
@ -16,8 +10,10 @@ export class PdfData {
|
||||
height: number;
|
||||
width: number;
|
||||
} = null;
|
||||
private document: HTMLDocument = null;
|
||||
|
||||
public constructor() {
|
||||
public constructor(document: HTMLDocument) {
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
public loadDoc = async (url: string | Uint8Array) => {
|
||||
@ -71,4 +67,36 @@ export class PdfData {
|
||||
scale,
|
||||
};
|
||||
};
|
||||
|
||||
public getActivePageNo = (scaledSize: ScaledSize, pageGap: number, scrollTop: number): number => {
|
||||
const pageHeight = scaledSize.height + pageGap;
|
||||
const pageNo = Math.floor(scrollTop / pageHeight) + 1;
|
||||
return Math.min(pageNo, this.pageCount);
|
||||
};
|
||||
|
||||
public printPdf = () => {
|
||||
const frame = this.document.createElement('iframe');
|
||||
frame.style.position = 'fixed';
|
||||
frame.style.display = 'none';
|
||||
frame.style.height = '100%';
|
||||
frame.style.width = '100%';
|
||||
this.document.body.appendChild(frame);
|
||||
frame.onload = () => {
|
||||
frame.contentWindow.onafterprint = () => {
|
||||
frame.remove();
|
||||
};
|
||||
frame.focus();
|
||||
frame.contentWindow.print();
|
||||
};
|
||||
frame.src = this.url as string;
|
||||
};
|
||||
|
||||
public downloadPdf = async () => {
|
||||
const url = this.url as string;
|
||||
const link = this.document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = url;
|
||||
link.click();
|
||||
link.remove();
|
||||
};
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { useEffect, useRef, useState, MutableRefObject } from 'react';
|
||||
import * as React from 'react';
|
||||
import { PdfData } from './pdfSource';
|
||||
import PdfDocument from './PdfDocument';
|
||||
import Page from './Page';
|
||||
import styled from 'styled-components';
|
||||
import useScaledSize, { ScaledSizeParams } from './hooks/useScaledSize';
|
||||
@ -14,20 +14,21 @@ const PagesHolder = styled.div<{ pageGap: number }>`
|
||||
flex-flow: column;
|
||||
width: fit-content;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
min-height: fit-content;
|
||||
row-gap: ${(props)=> props.pageGap || 2}px;
|
||||
`;
|
||||
|
||||
export interface VerticalPagesProps {
|
||||
pdf: PdfData;
|
||||
pdfDocument: PdfDocument;
|
||||
isDarkTheme: boolean;
|
||||
anchorPage?: number;
|
||||
rememberScroll?: boolean;
|
||||
pdfId?: string;
|
||||
zoom?: number;
|
||||
container: MutableRefObject<HTMLElement>;
|
||||
pageGap?: number;
|
||||
pageGap: number;
|
||||
showPageNumbers?: boolean;
|
||||
onActivePageChange: (page: number)=> void;
|
||||
}
|
||||
|
||||
export default function VerticalPages(props: VerticalPagesProps) {
|
||||
@ -35,7 +36,7 @@ export default function VerticalPages(props: VerticalPagesProps) {
|
||||
const innerContainerEl = useRef<HTMLDivElement>(null);
|
||||
|
||||
const scaledSize = useScaledSize({
|
||||
pdf: props.pdf,
|
||||
pdfDocument: props.pdfDocument,
|
||||
pdfId: props.pdfId,
|
||||
containerWidth,
|
||||
rememberScroll: props.rememberScroll,
|
||||
@ -51,6 +52,9 @@ export default function VerticalPages(props: VerticalPagesProps) {
|
||||
scaledSize,
|
||||
pdfId: props.pdfId,
|
||||
rememberScroll: props.rememberScroll,
|
||||
pdfDocument: props.pdfDocument,
|
||||
pageGap: props.pageGap,
|
||||
onActivePageChange: props.onActivePageChange,
|
||||
} as ScrollSaver);
|
||||
|
||||
useEffect(() => {
|
||||
@ -81,13 +85,13 @@ export default function VerticalPages(props: VerticalPagesProps) {
|
||||
resizeTimer = null;
|
||||
}
|
||||
};
|
||||
}, [props.container, props.pdf]);
|
||||
}, [props.container, props.pdfDocument]);
|
||||
|
||||
return (<PagesHolder pageGap={props.pageGap || 2} ref={innerContainerEl} >
|
||||
{scaledSize ?
|
||||
Array.from(Array(props.pdf.pageCount).keys()).map((i: number) => {
|
||||
Array.from(Array(props.pdfDocument.pageCount).keys()).map((i: number) => {
|
||||
// setting focusOnLoad only after scaledSize is set so that the container height is set correctly
|
||||
return <Page pdf={props.pdf} pageNo={i + 1} focusOnLoad={scaledSize && props.anchorPage && props.anchorPage === i + 1}
|
||||
return <Page pdfDocument={props.pdfDocument} pageNo={i + 1} focusOnLoad={scaledSize && props.anchorPage && props.anchorPage === i + 1}
|
||||
isAnchored={props.anchorPage && props.anchorPage === i + 1}
|
||||
showPageNumbers={props.showPageNumbers}
|
||||
isDarkTheme={props.isDarkTheme} scaledSize={scaledSize} container={props.container} key={i} />;
|
||||
|
@ -19,7 +19,7 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--grey);
|
||||
background: rgb(112, 112, 112);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
--white: rgb(255, 255, 255);
|
||||
--light: rgb(219, 219, 219);
|
||||
--grey: rgb(128, 128, 128);
|
||||
--dark: rgb(1, 0, 34);
|
||||
--dark: rgb(38, 38, 38);
|
||||
--black: rgb(24, 24, 24);
|
||||
|
||||
--red: #ff000d;
|
||||
@ -77,68 +77,11 @@ hr {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.mini-app {
|
||||
display: grid;
|
||||
grid-template-rows: auto 2rem;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background-color: var(--bg);
|
||||
overflow: hidden;
|
||||
border-radius: 0.4rem;
|
||||
border: solid thin var(--tertiary);
|
||||
padding-top: 0.6rem;
|
||||
padding-right: 0.25rem;
|
||||
background-color: rgb(240, 241, 245);
|
||||
.dark-bg{
|
||||
background-color: rgb(171, 171, 171);
|
||||
}
|
||||
|
||||
.mini-app.focused {
|
||||
border: solid thin var(--grey);
|
||||
}
|
||||
|
||||
.mini-app.loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .mini-app {
|
||||
[data-theme="dark"] .dark-bg {
|
||||
background-color: rgb(54, 54, 54);
|
||||
}
|
||||
|
||||
.app-bottom-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.2rem 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--grey);
|
||||
}
|
||||
|
||||
.app-pages {
|
||||
display: block;
|
||||
margin: 0px auto;
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0.5rem;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
overflow-y: hidden;
|
||||
padding-right: 0.25rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.app-pages.focused {
|
||||
padding-right: 0px;
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.pdf-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
column-gap: 0.2rem;
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
|
||||
import { PdfData } from '../pdfSource';
|
||||
|
||||
|
||||
const usePdfData = (pdfPath: string) => {
|
||||
const [pdf, setPdf] = useState<PdfData>(null);
|
||||
|
||||
useAsyncEffect(async (event: AsyncEffectEvent) => {
|
||||
const pdfData = new PdfData();
|
||||
await pdfData.loadDoc(pdfPath);
|
||||
if (event.cancelled) return;
|
||||
setPdf(pdfData);
|
||||
}, [pdfPath]);
|
||||
|
||||
return pdf;
|
||||
};
|
||||
|
||||
export default usePdfData;
|
19
packages/pdf-viewer/hooks/usePdfDocument.ts
Normal file
19
packages/pdf-viewer/hooks/usePdfDocument.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { useState } from 'react';
|
||||
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
|
||||
import PdfDocument from '../PdfDocument';
|
||||
|
||||
|
||||
const usePdfDocument = (pdfPath: string) => {
|
||||
const [pdfDocument, setPdfDocument] = useState<PdfDocument>(null);
|
||||
|
||||
useAsyncEffect(async (event: AsyncEffectEvent) => {
|
||||
const pdfData = new PdfDocument(document);
|
||||
await pdfData.loadDoc(pdfPath);
|
||||
if (event.cancelled) return;
|
||||
setPdfDocument(pdfData);
|
||||
}, [pdfPath]);
|
||||
|
||||
return pdfDocument;
|
||||
};
|
||||
|
||||
export default usePdfDocument;
|
@ -1,9 +1,10 @@
|
||||
import { useRef, useState, MutableRefObject } from 'react';
|
||||
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
|
||||
import { ScaledSize, PdfData } from '../pdfSource';
|
||||
import PdfDocument from '../PdfDocument';
|
||||
import { ScaledSize } from '../types';
|
||||
|
||||
export interface ScaledSizeParams {
|
||||
pdf: PdfData;
|
||||
pdfDocument: PdfDocument;
|
||||
pdfId: string;
|
||||
containerWidth: number;
|
||||
rememberScroll: boolean;
|
||||
@ -14,21 +15,21 @@ export interface ScaledSizeParams {
|
||||
zoom: number;
|
||||
}
|
||||
|
||||
const useScaledSize = ({ pdf, pdfId, containerWidth, rememberScroll, anchorPage, container, innerContainerEl, pageGap, zoom }: ScaledSizeParams) => {
|
||||
const useScaledSize = ({ pdfDocument, 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;
|
||||
if (!pdfDocument || !containerWidth) return;
|
||||
// console.log('scaledSize calculation triggered');
|
||||
const effectiveWidth = Math.min(containerWidth - 20, 900) * (zoom || 1);
|
||||
const scaledSize_ = await pdf.getScaledSize(null, effectiveWidth);
|
||||
const scaledSize_ = await pdfDocument.getScaledSize(null, effectiveWidth);
|
||||
if (event.cancelled) return;
|
||||
|
||||
const oldScaleSize = currentScaleSize.current;
|
||||
const oldScrollTop = container.current.scrollTop;
|
||||
|
||||
innerContainerEl.current.style.height = `${(scaledSize_.height + (pageGap || 2)) * pdf.pageCount}px`;
|
||||
innerContainerEl.current.style.height = `${(scaledSize_.height + pageGap) * pdfDocument.pageCount}px`;
|
||||
|
||||
// Adjust scroll position after window resize to keep the same page visible
|
||||
if (oldScaleSize && container.current) {
|
||||
@ -47,7 +48,7 @@ const useScaledSize = ({ pdf, pdfId, containerWidth, rememberScroll, anchorPage,
|
||||
// console.log('scroll set',container.current.scrollTop);
|
||||
}
|
||||
}
|
||||
}, [pdf, pdfId, rememberScroll, anchorPage, containerWidth, zoom]);
|
||||
}, [pdfDocument, pdfId, rememberScroll, anchorPage, containerWidth, zoom]);
|
||||
|
||||
return scaledSize;
|
||||
};
|
||||
|
@ -1,15 +1,21 @@
|
||||
import { useRef, useEffect, MutableRefObject } from 'react';
|
||||
import { ScaledSize } from '../pdfSource';
|
||||
import { useRef, useEffect, MutableRefObject, useState } from 'react';
|
||||
import PdfDocument from '../PdfDocument';
|
||||
import { ScaledSize } from '../types';
|
||||
|
||||
export interface ScrollSaver {
|
||||
container: MutableRefObject<HTMLElement>;
|
||||
scaledSize: ScaledSize;
|
||||
pdfId: string;
|
||||
rememberScroll: boolean;
|
||||
onActivePageChange: (activePage: number)=> void;
|
||||
pdfDocument: PdfDocument;
|
||||
pageGap: number;
|
||||
}
|
||||
|
||||
const useScrollSaver = ({ container, scaledSize, pdfId, rememberScroll }: ScrollSaver) => {
|
||||
const useScrollSaver = ({ container, scaledSize, pdfId, rememberScroll, onActivePageChange, pdfDocument, pageGap }: ScrollSaver) => {
|
||||
const currentScaleSize = useRef(scaledSize);
|
||||
const [currentActivePage, setCurrentActivePage] = useState(1);
|
||||
const currentActivePageRef = useRef(currentActivePage);
|
||||
|
||||
useEffect(() => {
|
||||
let scrollTimer: number = null;
|
||||
@ -18,9 +24,18 @@ const useScrollSaver = ({ container, scaledSize, pdfId, rememberScroll }: Scroll
|
||||
const saveScroll = () => {
|
||||
if (!currentScaleSize.current) return;
|
||||
const scale = currentScaleSize.current.scale;
|
||||
const scrollTop = container.current.scrollTop / scale;
|
||||
const pdfScrollTop = container.current.scrollTop / scale;
|
||||
if (rememberScroll && pdfId) {
|
||||
sessionStorage.setItem(`pdf.${pdfId}.scrollTop`, `${scrollTop}`);
|
||||
sessionStorage.setItem(`pdf.${pdfId}.scrollTop`, `${pdfScrollTop}`);
|
||||
}
|
||||
if (onActivePageChange && currentScaleSize.current) {
|
||||
const activePage = pdfDocument.getActivePageNo(currentScaleSize.current, pageGap, container.current.scrollTop);
|
||||
if (currentActivePageRef.current !== activePage) {
|
||||
// console.log('Active page changed', activePage, container.current.scrollTop);
|
||||
currentActivePageRef.current = activePage;
|
||||
onActivePageChange(activePage);
|
||||
setCurrentActivePage(activePage);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -41,13 +56,11 @@ const useScrollSaver = ({ container, scaledSize, pdfId, rememberScroll }: Scroll
|
||||
scrollTimer = null;
|
||||
}
|
||||
};
|
||||
}, [container, pdfId, rememberScroll, currentScaleSize]);
|
||||
}, [container, pdfId, rememberScroll, currentScaleSize, onActivePageChange, pdfDocument, pageGap]);
|
||||
|
||||
useEffect(() => {
|
||||
currentScaleSize.current = scaledSize;
|
||||
} , [scaledSize]);
|
||||
|
||||
return scaledSize;
|
||||
};
|
||||
|
||||
export default useScrollSaver;
|
||||
|
@ -5,7 +5,7 @@ import { render } from 'react-dom';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import MiniViewerApp from './miniViewer';
|
||||
|
||||
require('./viewer.css');
|
||||
require('./common.css');
|
||||
|
||||
// Setting worker path to worker bundle.
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdf.worker.js';
|
||||
@ -13,14 +13,17 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdf.worker.js';
|
||||
const url = window.frameElement.getAttribute('x-url');
|
||||
const type = window.frameElement.getAttribute('x-type');
|
||||
const appearance = window.frameElement.getAttribute('x-appearance');
|
||||
const anchorPage = window.frameElement.getAttribute('x-anchorPage');
|
||||
const anchorPage = Number(window.frameElement.getAttribute('x-anchorPage')) || null;
|
||||
const pdfId = window.frameElement.getAttribute('id');
|
||||
|
||||
document.documentElement.setAttribute('data-theme', appearance);
|
||||
|
||||
function App() {
|
||||
if (type === 'mini') {
|
||||
return <MiniViewerApp pdfPath={url} isDarkTheme={appearance === 'dark'} anchorPage={anchorPage ? Number(anchorPage) : null} pdfId={pdfId} />;
|
||||
return <MiniViewerApp pdfPath={url}
|
||||
isDarkTheme={appearance === 'dark'}
|
||||
anchorPage={anchorPage}
|
||||
pdfId={pdfId} />;
|
||||
}
|
||||
return <div>Error: Unknown app type "{type}"</div>;
|
||||
}
|
||||
|
70
packages/pdf-viewer/miniViewer.css
Normal file
70
packages/pdf-viewer/miniViewer.css
Normal file
@ -0,0 +1,70 @@
|
||||
.mini-app {
|
||||
display: grid;
|
||||
grid-template-rows: auto 2rem;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background-color: var(--bg);
|
||||
overflow: hidden;
|
||||
border-radius: 0.4rem;
|
||||
border: solid thin var(--tertiary);
|
||||
padding-top: 0.6rem;
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
|
||||
.mini-app.focused {
|
||||
border: solid thin var(--grey);
|
||||
}
|
||||
|
||||
.mini-app.loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.app-bottom-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.2rem 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--grey);
|
||||
}
|
||||
|
||||
.app-pages {
|
||||
display: block;
|
||||
margin: 0px auto;
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0.5rem;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
overflow-y: hidden;
|
||||
padding-right: 0.25rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.app-pages.focused {
|
||||
padding-right: 0px;
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.pdf-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
column-gap: 0.2rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 380px) {
|
||||
.can-hide {
|
||||
display: none;
|
||||
}
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import React, { useRef, useState, useCallback } from 'react';
|
||||
import useIsFocused from './hooks/useIsFocused';
|
||||
import usePdfData from './hooks/usePdfData';
|
||||
import usePdfDocument from './hooks/usePdfDocument';
|
||||
import VerticalPages from './VerticalPages';
|
||||
import ZoomControls from './ui/ZoomControls';
|
||||
import { DownloadButton, PrintButton } from './ui/IconButtons';
|
||||
|
||||
require('./miniViewer.css');
|
||||
|
||||
export interface MiniViewerAppProps {
|
||||
pdfPath: string;
|
||||
@ -12,12 +15,17 @@ export interface MiniViewerAppProps {
|
||||
}
|
||||
|
||||
export default function MiniViewerApp(props: MiniViewerAppProps) {
|
||||
const pdf = usePdfData(props.pdfPath);
|
||||
const pdfDocument = usePdfDocument(props.pdfPath);
|
||||
const isFocused = useIsFocused();
|
||||
const [zoom, setZoom] = useState<number>(1);
|
||||
const containerEl = useRef<HTMLDivElement>(null);
|
||||
const [activePage, setActivePage] = useState(1);
|
||||
|
||||
if (!pdf) {
|
||||
const onActivePageChange = useCallback((page: number) => {
|
||||
setActivePage(page);
|
||||
}, []);
|
||||
|
||||
if (!pdfDocument) {
|
||||
return (
|
||||
<div className="mini-app loading">
|
||||
<div>Loading pdf..</div>
|
||||
@ -28,21 +36,25 @@ export default function MiniViewerApp(props: MiniViewerAppProps) {
|
||||
<div className={`mini-app${isFocused ? ' focused' : ''}`}>
|
||||
<div className={`app-pages${isFocused ? ' focused' : ''}`} ref={containerEl}>
|
||||
<VerticalPages
|
||||
pdf={pdf}
|
||||
pdfDocument={pdfDocument}
|
||||
isDarkTheme={props.isDarkTheme}
|
||||
anchorPage={props.anchorPage}
|
||||
pdfId={props.pdfId}
|
||||
rememberScroll={true}
|
||||
container={containerEl}
|
||||
showPageNumbers={true}
|
||||
zoom={zoom} />
|
||||
zoom={zoom}
|
||||
pageGap={2}
|
||||
onActivePageChange={onActivePageChange} />
|
||||
</div>
|
||||
<div className='app-bottom-bar'>
|
||||
<div className='pdf-info'>
|
||||
<div style={{ paddingRight: '0.4rem' }}>{pdf.pageCount} pages</div>
|
||||
<div style={{ paddingRight: '0.4rem' }}>{activePage}/{pdfDocument.pageCount} pages</div>
|
||||
<ZoomControls onChange={setZoom} zoom={zoom} />
|
||||
<PrintButton onClick={pdfDocument?.printPdf}/>
|
||||
<DownloadButton onClick={pdfDocument?.downloadPdf}/>
|
||||
</div>
|
||||
<div>{isFocused ? '' : 'Click to enable scroll'}</div>
|
||||
<div className="can-hide">{isFocused ? '' : 'Click to enable scroll'}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { PdfData } from './pdfSource';
|
||||
import PdfDocument from './PdfDocument';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { readFile } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
@ -24,13 +24,13 @@ describe('pdfData', () => {
|
||||
|
||||
test('Should have correct page count', async () => {
|
||||
const file = await loadFile(pdfFilePath1);
|
||||
const pdf = new PdfData();
|
||||
const pdf = new PdfDocument(document);
|
||||
await pdf.loadDoc(file);
|
||||
expect(pdf.pageCount).toBe(1);
|
||||
});
|
||||
|
||||
test('Should throw error on invalid file', async () => {
|
||||
const pdf = new PdfData();
|
||||
const pdf = new PdfDocument(document);
|
||||
await expect(async () => {
|
||||
await pdf.loadDoc('');
|
||||
}).rejects.toThrowError();
|
||||
@ -38,7 +38,7 @@ describe('pdfData', () => {
|
||||
|
||||
test('Should get correct page size', async () => {
|
||||
const file = await loadFile(pdfFilePath1);
|
||||
const pdf = new PdfData();
|
||||
const pdf = new PdfDocument(document);
|
||||
await pdf.loadDoc(file);
|
||||
const size = await pdf.getPageSize();
|
||||
expect(size.height).toBeCloseTo(841.91998);
|
||||
@ -47,10 +47,21 @@ describe('pdfData', () => {
|
||||
|
||||
test('Should calculate scaled size', async () => {
|
||||
const file = await loadFile(pdfFilePath1);
|
||||
const pdf = new PdfData();
|
||||
const pdf = new PdfDocument(document);
|
||||
await pdf.loadDoc(file);
|
||||
const scaledSize = await pdf.getScaledSize(null, 200);
|
||||
expect(scaledSize.scale).toBeCloseTo(0.336157);
|
||||
});
|
||||
|
||||
test('Should get correct active page', async () => {
|
||||
const file = await loadFile(pdfFilePath1);
|
||||
const pdf = new PdfDocument(document);
|
||||
await pdf.loadDoc(file);
|
||||
const scaledSize = await pdf.getScaledSize(null, 200);
|
||||
const activePage = pdf.getActivePageNo(scaledSize, 3, 0);
|
||||
expect(activePage).toBe(1);
|
||||
const activePage2 = pdf.getActivePageNo(scaledSize, 4, 8000);
|
||||
expect(activePage2).toBe(1);
|
||||
});
|
||||
|
||||
});
|
||||
|
11
packages/pdf-viewer/types.ts
Normal file
11
packages/pdf-viewer/types.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export interface ScaledSize {
|
||||
height: number;
|
||||
width: number;
|
||||
scale: number;
|
||||
}
|
||||
|
||||
export interface IconButtonProps {
|
||||
onClick: ()=> void;
|
||||
size?: number;
|
||||
color?: string;
|
||||
}
|
56
packages/pdf-viewer/ui/IconButtons.tsx
Normal file
56
packages/pdf-viewer/ui/IconButtons.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faPrint, faDownload, IconDefinition } from '@fortawesome/free-solid-svg-icons';
|
||||
import { IconButtonProps } from '../types';
|
||||
|
||||
|
||||
const ButtonElement = styled.button<{ hoverColor?: string; size?: number; color?: string }>`
|
||||
padding: 0.2rem 0.7rem;
|
||||
cursor: pointer;
|
||||
border: solid thin transparent;
|
||||
border-radius: 5px;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: ${props=> props.size || 1}rem;
|
||||
background: transparent;
|
||||
color: var(--${props=>props.color || 'grey'});
|
||||
&:hover {
|
||||
background: #7676764d;
|
||||
${props=>props.hoverColor && `color: var(--${props.hoverColor})`}
|
||||
}
|
||||
`;
|
||||
|
||||
interface BaseButtonProps {
|
||||
icon: IconDefinition;
|
||||
onClick: ()=> void;
|
||||
name: string;
|
||||
size: number;
|
||||
color: string;
|
||||
hoverColor?: string;
|
||||
}
|
||||
|
||||
function BaseButton({ onClick, icon, name, size, color, hoverColor }: BaseButtonProps) {
|
||||
return (
|
||||
<ButtonElement onClick={onClick} title={name}
|
||||
color={color}
|
||||
size={size}
|
||||
hoverColor={hoverColor || 'secondary'}>
|
||||
<FontAwesomeIcon icon={icon} />
|
||||
</ButtonElement>
|
||||
);
|
||||
}
|
||||
|
||||
export function DownloadButton({ onClick, size, color }: IconButtonProps) {
|
||||
return (
|
||||
<BaseButton onClick={onClick} icon={faDownload} name='Download' size={size} color={color} />
|
||||
);
|
||||
}
|
||||
|
||||
export function PrintButton({ onClick, size, color }: IconButtonProps) {
|
||||
return (
|
||||
<BaseButton onClick={onClick} icon={faPrint} name='Print' size={size} color={color} />
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user