mirror of
https://github.com/laurent22/joplin.git
synced 2025-03-11 14:09:55 +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.d.ts
|
||||||
packages/pdf-viewer/Page.js
|
packages/pdf-viewer/Page.js
|
||||||
packages/pdf-viewer/Page.js.map
|
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.d.ts
|
||||||
packages/pdf-viewer/VerticalPages.js
|
packages/pdf-viewer/VerticalPages.js
|
||||||
packages/pdf-viewer/VerticalPages.js.map
|
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.d.ts
|
||||||
packages/pdf-viewer/hooks/useIsVisible.js
|
packages/pdf-viewer/hooks/useIsVisible.js
|
||||||
packages/pdf-viewer/hooks/useIsVisible.js.map
|
packages/pdf-viewer/hooks/useIsVisible.js.map
|
||||||
packages/pdf-viewer/hooks/usePdfData.d.ts
|
packages/pdf-viewer/hooks/usePdfDocument.d.ts
|
||||||
packages/pdf-viewer/hooks/usePdfData.js
|
packages/pdf-viewer/hooks/usePdfDocument.js
|
||||||
packages/pdf-viewer/hooks/usePdfData.js.map
|
packages/pdf-viewer/hooks/usePdfDocument.js.map
|
||||||
packages/pdf-viewer/hooks/useScaledSize.d.ts
|
packages/pdf-viewer/hooks/useScaledSize.d.ts
|
||||||
packages/pdf-viewer/hooks/useScaledSize.js
|
packages/pdf-viewer/hooks/useScaledSize.js
|
||||||
packages/pdf-viewer/hooks/useScaledSize.js.map
|
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.d.ts
|
||||||
packages/pdf-viewer/miniViewer.js
|
packages/pdf-viewer/miniViewer.js
|
||||||
packages/pdf-viewer/miniViewer.js.map
|
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.d.ts
|
||||||
packages/pdf-viewer/pdfSource.test.js
|
packages/pdf-viewer/pdfSource.test.js
|
||||||
packages/pdf-viewer/pdfSource.test.js.map
|
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.d.ts
|
||||||
packages/pdf-viewer/ui/ZoomControls.js
|
packages/pdf-viewer/ui/ZoomControls.js
|
||||||
packages/pdf-viewer/ui/ZoomControls.js.map
|
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.d.ts
|
||||||
packages/pdf-viewer/Page.js
|
packages/pdf-viewer/Page.js
|
||||||
packages/pdf-viewer/Page.js.map
|
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.d.ts
|
||||||
packages/pdf-viewer/VerticalPages.js
|
packages/pdf-viewer/VerticalPages.js
|
||||||
packages/pdf-viewer/VerticalPages.js.map
|
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.d.ts
|
||||||
packages/pdf-viewer/hooks/useIsVisible.js
|
packages/pdf-viewer/hooks/useIsVisible.js
|
||||||
packages/pdf-viewer/hooks/useIsVisible.js.map
|
packages/pdf-viewer/hooks/useIsVisible.js.map
|
||||||
packages/pdf-viewer/hooks/usePdfData.d.ts
|
packages/pdf-viewer/hooks/usePdfDocument.d.ts
|
||||||
packages/pdf-viewer/hooks/usePdfData.js
|
packages/pdf-viewer/hooks/usePdfDocument.js
|
||||||
packages/pdf-viewer/hooks/usePdfData.js.map
|
packages/pdf-viewer/hooks/usePdfDocument.js.map
|
||||||
packages/pdf-viewer/hooks/useScaledSize.d.ts
|
packages/pdf-viewer/hooks/useScaledSize.d.ts
|
||||||
packages/pdf-viewer/hooks/useScaledSize.js
|
packages/pdf-viewer/hooks/useScaledSize.js
|
||||||
packages/pdf-viewer/hooks/useScaledSize.js.map
|
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.d.ts
|
||||||
packages/pdf-viewer/miniViewer.js
|
packages/pdf-viewer/miniViewer.js
|
||||||
packages/pdf-viewer/miniViewer.js.map
|
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.d.ts
|
||||||
packages/pdf-viewer/pdfSource.test.js
|
packages/pdf-viewer/pdfSource.test.js
|
||||||
packages/pdf-viewer/pdfSource.test.js.map
|
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.d.ts
|
||||||
packages/pdf-viewer/ui/ZoomControls.js
|
packages/pdf-viewer/ui/ZoomControls.js
|
||||||
packages/pdf-viewer/ui/ZoomControls.js.map
|
packages/pdf-viewer/ui/ZoomControls.js.map
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { useEffect, useRef, useState, MutableRefObject } from 'react';
|
import { useEffect, useRef, useState, MutableRefObject } from 'react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import useIsVisible from './hooks/useIsVisible';
|
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 useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
@ -35,7 +36,7 @@ const PageInfo = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export interface PageProps {
|
export interface PageProps {
|
||||||
pdf: PdfData;
|
pdfDocument: PdfDocument;
|
||||||
pageNo: number;
|
pageNo: number;
|
||||||
focusOnLoad: boolean;
|
focusOnLoad: boolean;
|
||||||
isAnchored: boolean;
|
isAnchored: boolean;
|
||||||
@ -87,9 +88,9 @@ export default function Page(props: PageProps) {
|
|||||||
}, [page, props.scaledSize, isVisible, props.pageNo]);
|
}, [page, props.scaledSize, isVisible, props.pageNo]);
|
||||||
|
|
||||||
useAsyncEffect(async (event: AsyncEffectEvent) => {
|
useAsyncEffect(async (event: AsyncEffectEvent) => {
|
||||||
if (page || !isVisible || !props.pdf) return;
|
if (page || !isVisible || !props.pdfDocument) return;
|
||||||
try {
|
try {
|
||||||
const _page = await props.pdf.getPage(props.pageNo);
|
const _page = await props.pdfDocument.getPage(props.pageNo);
|
||||||
if (event.cancelled) return;
|
if (event.cancelled) return;
|
||||||
setPage(_page);
|
setPage(_page);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
import * as pdfjsLib from 'pdfjs-dist';
|
import * as pdfjsLib from 'pdfjs-dist';
|
||||||
|
import { ScaledSize } from './types';
|
||||||
|
|
||||||
|
export default class PdfDocument {
|
||||||
export interface ScaledSize {
|
|
||||||
height: number;
|
|
||||||
width: number;
|
|
||||||
scale: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PdfData {
|
|
||||||
public url: string | Uint8Array;
|
public url: string | Uint8Array;
|
||||||
private doc: any = null;
|
private doc: any = null;
|
||||||
public pageCount: number = null;
|
public pageCount: number = null;
|
||||||
@ -16,8 +10,10 @@ export class PdfData {
|
|||||||
height: number;
|
height: number;
|
||||||
width: number;
|
width: number;
|
||||||
} = null;
|
} = null;
|
||||||
|
private document: HTMLDocument = null;
|
||||||
|
|
||||||
public constructor() {
|
public constructor(document: HTMLDocument) {
|
||||||
|
this.document = document;
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadDoc = async (url: string | Uint8Array) => {
|
public loadDoc = async (url: string | Uint8Array) => {
|
||||||
@ -71,4 +67,36 @@ export class PdfData {
|
|||||||
scale,
|
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 { useEffect, useRef, useState, MutableRefObject } from 'react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { PdfData } from './pdfSource';
|
import PdfDocument from './PdfDocument';
|
||||||
import Page from './Page';
|
import Page from './Page';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import useScaledSize, { ScaledSizeParams } from './hooks/useScaledSize';
|
import useScaledSize, { ScaledSizeParams } from './hooks/useScaledSize';
|
||||||
@ -14,20 +14,21 @@ const PagesHolder = styled.div<{ pageGap: number }>`
|
|||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
min-height: 100%;
|
min-height: fit-content;
|
||||||
row-gap: ${(props)=> props.pageGap || 2}px;
|
row-gap: ${(props)=> props.pageGap || 2}px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export interface VerticalPagesProps {
|
export interface VerticalPagesProps {
|
||||||
pdf: PdfData;
|
pdfDocument: PdfDocument;
|
||||||
isDarkTheme: boolean;
|
isDarkTheme: boolean;
|
||||||
anchorPage?: number;
|
anchorPage?: number;
|
||||||
rememberScroll?: boolean;
|
rememberScroll?: boolean;
|
||||||
pdfId?: string;
|
pdfId?: string;
|
||||||
zoom?: number;
|
zoom?: number;
|
||||||
container: MutableRefObject<HTMLElement>;
|
container: MutableRefObject<HTMLElement>;
|
||||||
pageGap?: number;
|
pageGap: number;
|
||||||
showPageNumbers?: boolean;
|
showPageNumbers?: boolean;
|
||||||
|
onActivePageChange: (page: number)=> void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function VerticalPages(props: VerticalPagesProps) {
|
export default function VerticalPages(props: VerticalPagesProps) {
|
||||||
@ -35,7 +36,7 @@ export default function VerticalPages(props: VerticalPagesProps) {
|
|||||||
const innerContainerEl = useRef<HTMLDivElement>(null);
|
const innerContainerEl = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const scaledSize = useScaledSize({
|
const scaledSize = useScaledSize({
|
||||||
pdf: props.pdf,
|
pdfDocument: props.pdfDocument,
|
||||||
pdfId: props.pdfId,
|
pdfId: props.pdfId,
|
||||||
containerWidth,
|
containerWidth,
|
||||||
rememberScroll: props.rememberScroll,
|
rememberScroll: props.rememberScroll,
|
||||||
@ -51,6 +52,9 @@ export default function VerticalPages(props: VerticalPagesProps) {
|
|||||||
scaledSize,
|
scaledSize,
|
||||||
pdfId: props.pdfId,
|
pdfId: props.pdfId,
|
||||||
rememberScroll: props.rememberScroll,
|
rememberScroll: props.rememberScroll,
|
||||||
|
pdfDocument: props.pdfDocument,
|
||||||
|
pageGap: props.pageGap,
|
||||||
|
onActivePageChange: props.onActivePageChange,
|
||||||
} as ScrollSaver);
|
} as ScrollSaver);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -81,13 +85,13 @@ export default function VerticalPages(props: VerticalPagesProps) {
|
|||||||
resizeTimer = null;
|
resizeTimer = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [props.container, props.pdf]);
|
}, [props.container, props.pdfDocument]);
|
||||||
|
|
||||||
return (<PagesHolder pageGap={props.pageGap || 2} ref={innerContainerEl} >
|
return (<PagesHolder pageGap={props.pageGap || 2} ref={innerContainerEl} >
|
||||||
{scaledSize ?
|
{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
|
// 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}
|
isAnchored={props.anchorPage && props.anchorPage === i + 1}
|
||||||
showPageNumbers={props.showPageNumbers}
|
showPageNumbers={props.showPageNumbers}
|
||||||
isDarkTheme={props.isDarkTheme} scaledSize={scaledSize} container={props.container} key={i} />;
|
isDarkTheme={props.isDarkTheme} scaledSize={scaledSize} container={props.container} key={i} />;
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: var(--grey);
|
background: rgb(112, 112, 112);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +27,7 @@
|
|||||||
--white: rgb(255, 255, 255);
|
--white: rgb(255, 255, 255);
|
||||||
--light: rgb(219, 219, 219);
|
--light: rgb(219, 219, 219);
|
||||||
--grey: rgb(128, 128, 128);
|
--grey: rgb(128, 128, 128);
|
||||||
--dark: rgb(1, 0, 34);
|
--dark: rgb(38, 38, 38);
|
||||||
--black: rgb(24, 24, 24);
|
--black: rgb(24, 24, 24);
|
||||||
|
|
||||||
--red: #ff000d;
|
--red: #ff000d;
|
||||||
@ -77,68 +77,11 @@ hr {
|
|||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mini-app {
|
.dark-bg{
|
||||||
display: grid;
|
background-color: rgb(171, 171, 171);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mini-app.focused {
|
[data-theme="dark"] .dark-bg {
|
||||||
border: solid thin var(--grey);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-app.loading {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mini-app {
|
|
||||||
background-color: rgb(54, 54, 54);
|
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 { useRef, useState, MutableRefObject } from 'react';
|
||||||
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
|
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
|
||||||
import { ScaledSize, PdfData } from '../pdfSource';
|
import PdfDocument from '../PdfDocument';
|
||||||
|
import { ScaledSize } from '../types';
|
||||||
|
|
||||||
export interface ScaledSizeParams {
|
export interface ScaledSizeParams {
|
||||||
pdf: PdfData;
|
pdfDocument: PdfDocument;
|
||||||
pdfId: string;
|
pdfId: string;
|
||||||
containerWidth: number;
|
containerWidth: number;
|
||||||
rememberScroll: boolean;
|
rememberScroll: boolean;
|
||||||
@ -14,21 +15,21 @@ export interface ScaledSizeParams {
|
|||||||
zoom: number;
|
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 [scaledSize, setScaledSize] = useState<ScaledSize>(null);
|
||||||
const currentScaleSize = useRef(scaledSize);
|
const currentScaleSize = useRef(scaledSize);
|
||||||
|
|
||||||
useAsyncEffect(async (event: AsyncEffectEvent) => {
|
useAsyncEffect(async (event: AsyncEffectEvent) => {
|
||||||
if (!pdf || !containerWidth) return;
|
if (!pdfDocument || !containerWidth) return;
|
||||||
// console.log('scaledSize calculation triggered');
|
// console.log('scaledSize calculation triggered');
|
||||||
const effectiveWidth = Math.min(containerWidth - 20, 900) * (zoom || 1);
|
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;
|
if (event.cancelled) return;
|
||||||
|
|
||||||
const oldScaleSize = currentScaleSize.current;
|
const oldScaleSize = currentScaleSize.current;
|
||||||
const oldScrollTop = container.current.scrollTop;
|
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
|
// Adjust scroll position after window resize to keep the same page visible
|
||||||
if (oldScaleSize && container.current) {
|
if (oldScaleSize && container.current) {
|
||||||
@ -47,7 +48,7 @@ const useScaledSize = ({ pdf, pdfId, containerWidth, rememberScroll, anchorPage,
|
|||||||
// console.log('scroll set',container.current.scrollTop);
|
// console.log('scroll set',container.current.scrollTop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [pdf, pdfId, rememberScroll, anchorPage, containerWidth, zoom]);
|
}, [pdfDocument, pdfId, rememberScroll, anchorPage, containerWidth, zoom]);
|
||||||
|
|
||||||
return scaledSize;
|
return scaledSize;
|
||||||
};
|
};
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
import { useRef, useEffect, MutableRefObject } from 'react';
|
import { useRef, useEffect, MutableRefObject, useState } from 'react';
|
||||||
import { ScaledSize } from '../pdfSource';
|
import PdfDocument from '../PdfDocument';
|
||||||
|
import { ScaledSize } from '../types';
|
||||||
|
|
||||||
export interface ScrollSaver {
|
export interface ScrollSaver {
|
||||||
container: MutableRefObject<HTMLElement>;
|
container: MutableRefObject<HTMLElement>;
|
||||||
scaledSize: ScaledSize;
|
scaledSize: ScaledSize;
|
||||||
pdfId: string;
|
pdfId: string;
|
||||||
rememberScroll: boolean;
|
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 currentScaleSize = useRef(scaledSize);
|
||||||
|
const [currentActivePage, setCurrentActivePage] = useState(1);
|
||||||
|
const currentActivePageRef = useRef(currentActivePage);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let scrollTimer: number = null;
|
let scrollTimer: number = null;
|
||||||
@ -18,9 +24,18 @@ const useScrollSaver = ({ container, scaledSize, pdfId, rememberScroll }: Scroll
|
|||||||
const saveScroll = () => {
|
const saveScroll = () => {
|
||||||
if (!currentScaleSize.current) return;
|
if (!currentScaleSize.current) return;
|
||||||
const scale = currentScaleSize.current.scale;
|
const scale = currentScaleSize.current.scale;
|
||||||
const scrollTop = container.current.scrollTop / scale;
|
const pdfScrollTop = container.current.scrollTop / scale;
|
||||||
if (rememberScroll && pdfId) {
|
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;
|
scrollTimer = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [container, pdfId, rememberScroll, currentScaleSize]);
|
}, [container, pdfId, rememberScroll, currentScaleSize, onActivePageChange, pdfDocument, pageGap]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
currentScaleSize.current = scaledSize;
|
currentScaleSize.current = scaledSize;
|
||||||
} , [scaledSize]);
|
} , [scaledSize]);
|
||||||
|
|
||||||
return scaledSize;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useScrollSaver;
|
export default useScrollSaver;
|
||||||
|
@ -5,7 +5,7 @@ import { render } from 'react-dom';
|
|||||||
import * as pdfjsLib from 'pdfjs-dist';
|
import * as pdfjsLib from 'pdfjs-dist';
|
||||||
import MiniViewerApp from './miniViewer';
|
import MiniViewerApp from './miniViewer';
|
||||||
|
|
||||||
require('./viewer.css');
|
require('./common.css');
|
||||||
|
|
||||||
// Setting worker path to worker bundle.
|
// Setting worker path to worker bundle.
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdf.worker.js';
|
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdf.worker.js';
|
||||||
@ -13,14 +13,17 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdf.worker.js';
|
|||||||
const url = window.frameElement.getAttribute('x-url');
|
const url = window.frameElement.getAttribute('x-url');
|
||||||
const type = window.frameElement.getAttribute('x-type');
|
const type = window.frameElement.getAttribute('x-type');
|
||||||
const appearance = window.frameElement.getAttribute('x-appearance');
|
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');
|
const pdfId = window.frameElement.getAttribute('id');
|
||||||
|
|
||||||
document.documentElement.setAttribute('data-theme', appearance);
|
document.documentElement.setAttribute('data-theme', appearance);
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
if (type === 'mini') {
|
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>;
|
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 useIsFocused from './hooks/useIsFocused';
|
||||||
import usePdfData from './hooks/usePdfData';
|
import usePdfDocument from './hooks/usePdfDocument';
|
||||||
import VerticalPages from './VerticalPages';
|
import VerticalPages from './VerticalPages';
|
||||||
import ZoomControls from './ui/ZoomControls';
|
import ZoomControls from './ui/ZoomControls';
|
||||||
|
import { DownloadButton, PrintButton } from './ui/IconButtons';
|
||||||
|
|
||||||
|
require('./miniViewer.css');
|
||||||
|
|
||||||
export interface MiniViewerAppProps {
|
export interface MiniViewerAppProps {
|
||||||
pdfPath: string;
|
pdfPath: string;
|
||||||
@ -12,12 +15,17 @@ export interface MiniViewerAppProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function MiniViewerApp(props: MiniViewerAppProps) {
|
export default function MiniViewerApp(props: MiniViewerAppProps) {
|
||||||
const pdf = usePdfData(props.pdfPath);
|
const pdfDocument = usePdfDocument(props.pdfPath);
|
||||||
const isFocused = useIsFocused();
|
const isFocused = useIsFocused();
|
||||||
const [zoom, setZoom] = useState<number>(1);
|
const [zoom, setZoom] = useState<number>(1);
|
||||||
const containerEl = useRef<HTMLDivElement>(null);
|
const containerEl = useRef<HTMLDivElement>(null);
|
||||||
|
const [activePage, setActivePage] = useState(1);
|
||||||
|
|
||||||
if (!pdf) {
|
const onActivePageChange = useCallback((page: number) => {
|
||||||
|
setActivePage(page);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!pdfDocument) {
|
||||||
return (
|
return (
|
||||||
<div className="mini-app loading">
|
<div className="mini-app loading">
|
||||||
<div>Loading pdf..</div>
|
<div>Loading pdf..</div>
|
||||||
@ -28,21 +36,25 @@ export default function MiniViewerApp(props: MiniViewerAppProps) {
|
|||||||
<div className={`mini-app${isFocused ? ' focused' : ''}`}>
|
<div className={`mini-app${isFocused ? ' focused' : ''}`}>
|
||||||
<div className={`app-pages${isFocused ? ' focused' : ''}`} ref={containerEl}>
|
<div className={`app-pages${isFocused ? ' focused' : ''}`} ref={containerEl}>
|
||||||
<VerticalPages
|
<VerticalPages
|
||||||
pdf={pdf}
|
pdfDocument={pdfDocument}
|
||||||
isDarkTheme={props.isDarkTheme}
|
isDarkTheme={props.isDarkTheme}
|
||||||
anchorPage={props.anchorPage}
|
anchorPage={props.anchorPage}
|
||||||
pdfId={props.pdfId}
|
pdfId={props.pdfId}
|
||||||
rememberScroll={true}
|
rememberScroll={true}
|
||||||
container={containerEl}
|
container={containerEl}
|
||||||
showPageNumbers={true}
|
showPageNumbers={true}
|
||||||
zoom={zoom} />
|
zoom={zoom}
|
||||||
|
pageGap={2}
|
||||||
|
onActivePageChange={onActivePageChange} />
|
||||||
</div>
|
</div>
|
||||||
<div className='app-bottom-bar'>
|
<div className='app-bottom-bar'>
|
||||||
<div className='pdf-info'>
|
<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} />
|
<ZoomControls onChange={setZoom} zoom={zoom} />
|
||||||
|
<PrintButton onClick={pdfDocument?.printPdf}/>
|
||||||
|
<DownloadButton onClick={pdfDocument?.downloadPdf}/>
|
||||||
</div>
|
</div>
|
||||||
<div>{isFocused ? '' : 'Click to enable scroll'}</div>
|
<div className="can-hide">{isFocused ? '' : 'Click to enable scroll'}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { PdfData } from './pdfSource';
|
import PdfDocument from './PdfDocument';
|
||||||
import * as pdfjsLib from 'pdfjs-dist';
|
import * as pdfjsLib from 'pdfjs-dist';
|
||||||
import { readFile } from 'fs';
|
import { readFile } from 'fs';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
@ -24,13 +24,13 @@ describe('pdfData', () => {
|
|||||||
|
|
||||||
test('Should have correct page count', async () => {
|
test('Should have correct page count', async () => {
|
||||||
const file = await loadFile(pdfFilePath1);
|
const file = await loadFile(pdfFilePath1);
|
||||||
const pdf = new PdfData();
|
const pdf = new PdfDocument(document);
|
||||||
await pdf.loadDoc(file);
|
await pdf.loadDoc(file);
|
||||||
expect(pdf.pageCount).toBe(1);
|
expect(pdf.pageCount).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Should throw error on invalid file', async () => {
|
test('Should throw error on invalid file', async () => {
|
||||||
const pdf = new PdfData();
|
const pdf = new PdfDocument(document);
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await pdf.loadDoc('');
|
await pdf.loadDoc('');
|
||||||
}).rejects.toThrowError();
|
}).rejects.toThrowError();
|
||||||
@ -38,7 +38,7 @@ describe('pdfData', () => {
|
|||||||
|
|
||||||
test('Should get correct page size', async () => {
|
test('Should get correct page size', async () => {
|
||||||
const file = await loadFile(pdfFilePath1);
|
const file = await loadFile(pdfFilePath1);
|
||||||
const pdf = new PdfData();
|
const pdf = new PdfDocument(document);
|
||||||
await pdf.loadDoc(file);
|
await pdf.loadDoc(file);
|
||||||
const size = await pdf.getPageSize();
|
const size = await pdf.getPageSize();
|
||||||
expect(size.height).toBeCloseTo(841.91998);
|
expect(size.height).toBeCloseTo(841.91998);
|
||||||
@ -47,10 +47,21 @@ describe('pdfData', () => {
|
|||||||
|
|
||||||
test('Should calculate scaled size', async () => {
|
test('Should calculate scaled size', async () => {
|
||||||
const file = await loadFile(pdfFilePath1);
|
const file = await loadFile(pdfFilePath1);
|
||||||
const pdf = new PdfData();
|
const pdf = new PdfDocument(document);
|
||||||
await pdf.loadDoc(file);
|
await pdf.loadDoc(file);
|
||||||
const scaledSize = await pdf.getScaledSize(null, 200);
|
const scaledSize = await pdf.getScaledSize(null, 200);
|
||||||
expect(scaledSize.scale).toBeCloseTo(0.336157);
|
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…
x
Reference in New Issue
Block a user