Skip to content

Commit

Permalink
feat: add pdf print option
Browse files Browse the repository at this point in the history
refs: PREV-69 (#29)
  • Loading branch information
CataldoMazzilli authored Aug 7, 2023
1 parent 833fc3e commit e1bdf2c
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 15 deletions.
4 changes: 4 additions & 0 deletions .reuse/dep5
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ License: AGPL-3.0-only
Files: THIRDPARTIES
Copyright: 2023 Zextras <https://www.zextras.com>
License: CC0-1.0

Files: CHANGELOG.md
Copyright: 2023 Zextras <https://www.zextras.com>
License: CC0-1.0
2 changes: 1 addition & 1 deletion src/preview/ImagePreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import styled from 'styled-components';
import FocusWithin from './FocusWithin';
import Header, { HeaderAction, HeaderProps } from './Header';
import { AbsoluteLeftIconButton, AbsoluteRightIconButton } from './StyledComponents';
import { MakeOptional } from '../utils/utils';
import { type MakeOptional } from '../utils/type-utils';

const Overlay = styled.div`
height: 100vh;
Expand Down
76 changes: 65 additions & 11 deletions src/preview/PdfPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';

import { Container, Portal, useCombinedRefs, getColor } from '@zextras/carbonio-design-system';
import { size as lodashSize } from 'lodash';
import map from 'lodash/map';
import noop from 'lodash/noop';
import type { DocumentProps } from 'react-pdf';
import { PageProps } from 'react-pdf';
import type { DocumentProps, PDFPageProxy, PageProps } from 'react-pdf';
import { Document, Page } from 'react-pdf/dist/esm/entry.webpack5';
import 'react-pdf/dist/esm/Page/TextLayer.css';
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
Expand All @@ -27,7 +27,8 @@ import { AbsoluteLeftIconButton, AbsoluteRightIconButton } from './StyledCompone
import { usePageScrollController } from './usePageScrollController';
import { useZoom } from './useZoom';
import { ZoomController } from './ZoomController';
import { MakeOptional } from '../utils/utils';
import { type MakeOptional } from '../utils/type-utils';
import { print } from '../utils/utils';

const Overlay = styled.div`
height: 100vh;
Expand Down Expand Up @@ -142,6 +143,7 @@ type PdfPreviewProps = Partial<Omit<HeaderProps, 'closeAction'>> & {
pageLabel?: string;
errorLabel?: string;
loadingLabel?: string;
printActionTooltipLabel?: string;
} & Omit<PreviewCriteriaAlternativeContentProps, 'downloadSrc'>;

const PdfPreview = React.forwardRef<HTMLDivElement, PdfPreviewProps>(function PreviewFn(
Expand All @@ -154,7 +156,7 @@ const PdfPreview = React.forwardRef<HTMLDivElement, PdfPreviewProps>(function Pr
extension = '',
filename = '',
size = '',
actions = [],
actions: actionsProp = [],
closeAction,
onClose,
useFallback = false,
Expand All @@ -177,16 +179,17 @@ const PdfPreview = React.forwardRef<HTMLDivElement, PdfPreviewProps>(function Pr
onPreviousPreview,
pageLabel,
errorLabel = 'Failed to load document preview.',
loadingLabel = 'Loading document preview…'
loadingLabel = 'Loading document preview…',
printActionTooltipLabel = 'Print'
},
ref
) {
const [documentFile, setDocumentFile] = useState<ArrayBuffer | Blob | string | null>(null);
const [fetchFailed, setFetchFailed] = useState(false);

useEffect(() => {
// Checks whether is a string but not a data URI.
if (typeof src === 'string' && !/^data:/.test(src)) {
// Check whether is a string but not a data URI.
if (typeof src === 'string' && !src.startsWith('data:')) {
const controller = new AbortController();
fetch(src, { signal: controller.signal, cache: forceCache ? 'force-cache' : undefined })
.then((res) => res.blob())
Expand All @@ -205,6 +208,7 @@ const PdfPreview = React.forwardRef<HTMLDivElement, PdfPreviewProps>(function Pr
const previewRef: React.MutableRefObject<HTMLDivElement | null> = useCombinedRefs(ref);
const documentLoaded = useRef(useFallback);
const pageRefs = useRef<React.RefObject<HTMLElement>[]>([]);
const pdfPageProxyListRef = useRef<{ [pageIndex: number]: PDFPageProxy }>({});

const [numPages, setNumPages] = useState<number | null>(null);
const [currentPage, setCurrentPage] = useState<number>(0);
Expand Down Expand Up @@ -280,6 +284,20 @@ const PdfPreview = React.forwardRef<HTMLDivElement, PdfPreviewProps>(function Pr
[observePage]
);

const [printReady, setPrintReady] = useState(false);

const pageOnRenderSuccess = useCallback<NonNullable<PageProps['onRenderSuccess']>>(
(page): void => {
registerPageObserver(page);
setPrintReady(lodashSize(pdfPageProxyListRef.current) === numPages);
},
[numPages, registerPageObserver]
);

const pageOnLoadSuccess = useCallback<NonNullable<PageProps['onLoadSuccess']>>((page) => {
pdfPageProxyListRef.current[page._pageIndex] = page;
}, []);

const pageElements = useMemo(() => {
if (numPages) {
pageRefs.current = [];
Expand All @@ -290,7 +308,8 @@ const PdfPreview = React.forwardRef<HTMLDivElement, PdfPreviewProps>(function Pr
<Page
key={`page_${index + 1}`}
pageNumber={index + 1}
onRenderSuccess={registerPageObserver}
onRenderSuccess={pageOnRenderSuccess}
onLoadSuccess={pageOnLoadSuccess}
width={currentZoom}
renderTextLayer={renderTextLayer}
renderAnnotationLayer={renderAnnotationLayer}
Expand All @@ -300,7 +319,14 @@ const PdfPreview = React.forwardRef<HTMLDivElement, PdfPreviewProps>(function Pr
});
}
return [];
}, [currentZoom, numPages, registerPageObserver, renderAnnotationLayer, renderTextLayer]);
}, [
currentZoom,
numPages,
pageOnLoadSuccess,
pageOnRenderSuccess,
renderAnnotationLayer,
renderTextLayer
]);

const onDocumentLoadSuccess = useCallback<NonNullable<DocumentProps['onLoadSuccess']>>(
(document) => {
Expand All @@ -323,7 +349,7 @@ const PdfPreview = React.forwardRef<HTMLDivElement, PdfPreviewProps>(function Pr
const $customContent = useMemo(() => {
if (useFallback) {
return (
customContent || (
customContent ?? (
<PreviewCriteriaAlternativeContent
downloadSrc={
(typeof src === 'string' && src) ||
Expand Down Expand Up @@ -403,6 +429,34 @@ const PdfPreview = React.forwardRef<HTMLDivElement, PdfPreviewProps>(function Pr
};
}, [eventListener, show]);

const printWithOpen = useCallback<HeaderAction['onClick']>(
(e) => {
e.stopPropagation();
if (documentFile) {
if (typeof documentFile === 'string') {
fetch(documentFile)
.then((res) => res.blob())
.then((file) => print(file));
} else {
print(documentFile);
}
}
},
[documentFile]
);

const printAction = useMemo<HeaderAction>(
() => ({
tooltipLabel: printActionTooltipLabel,
icon: 'PrinterOutline',
onClick: printWithOpen,
id: 'print-open',
disabled: !printReady
}),
[printActionTooltipLabel, printReady, printWithOpen]
);
const actions = useMemo(() => [printAction, ...actionsProp], [actionsProp, printAction]);

return (
<Portal show={show} disablePortal={disablePortal} container={container}>
<Overlay onClick={onOverlayClick}>
Expand All @@ -412,7 +466,7 @@ const PdfPreview = React.forwardRef<HTMLDivElement, PdfPreviewProps>(function Pr
<Navigator>
<PageController
pageLabel={pageLabel}
pagesNumber={numPages || 0}
pagesNumber={numPages ?? 0}
currentPage={currentPage}
onPageChange={onPageChange}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/preview/PreviewManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import findIndex from 'lodash/findIndex';
import { ImagePreviewProps } from './ImagePreview';
import { PdfPreviewProps } from './PdfPreview';
import { PreviewWrapper, PreviewWrapperProps } from './PreviewWrapper';
import { MakeOptional } from '../utils/utils';
import { type MakeOptional } from '../utils/type-utils';

type PreviewArgType = (
| MakeOptional<Omit<ImagePreviewProps, 'show'>, 'onClose'>
Expand Down
6 changes: 6 additions & 0 deletions src/utils/type-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
* SPDX-FileCopyrightText: 2023 Zextras <https://www.zextras.com>
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: T[SubKey] };
16 changes: 14 additions & 2 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,17 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/

export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: T[SubKey] };
export function print(documentFile: ArrayBuffer | Blob): void {
const documentUrl = URL.createObjectURL(
(documentFile instanceof Blob && documentFile) || new Blob([documentFile])
);
const printWin = window.open(documentUrl, '');
if (printWin) {
printWin.onload = (): void => {
printWin.focus();
setTimeout(() => {
printWin.print();
}, 1000);
};
}
}

0 comments on commit e1bdf2c

Please sign in to comment.