diff --git a/packages/discovery-react-components/package.json b/packages/discovery-react-components/package.json index b3866ed52..0f5baa81a 100644 --- a/packages/discovery-react-components/package.json +++ b/packages/discovery-react-components/package.json @@ -49,6 +49,7 @@ "@storybook/core": "^5.3.21", "@storybook/react": "^5.3.21", "@storybook/source-loader": "^5.3.21", + "@types/pdfjs-dist": "2.1.7", "cross-env": "^7.0.3", "css-loader": "^3.4.2", "madge": "^5.0.1", diff --git a/packages/discovery-react-components/src/components/DocumentPreview/components/PdfViewer/PdfViewer.stories.tsx b/packages/discovery-react-components/src/components/DocumentPreview/components/PdfViewer/PdfViewer.stories.tsx index e6cdc54b1..9284c5c16 100644 --- a/packages/discovery-react-components/src/components/DocumentPreview/components/PdfViewer/PdfViewer.stories.tsx +++ b/packages/discovery-react-components/src/components/DocumentPreview/components/PdfViewer/PdfViewer.stories.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import { withKnobs, radios, number } from '@storybook/addon-knobs'; +import { action } from '@storybook/addon-actions'; import PdfViewer from './PdfViewer'; import { document as doc } from 'components/DocumentPreview/__fixtures__/Art Effects.pdf'; @@ -33,5 +34,16 @@ storiesOf('DocumentPreview/components/PdfViewer', module) const zoom = radios(zoomKnob.label, zoomKnob.options, zoomKnob.defaultValue); const scale = parseFloat(zoom); - return {}} />; + const setLoadingAction = action('setLoading'); + const setRenderedTextAction = action('setRenderedText'); + + return ( + + ); }); diff --git a/packages/discovery-react-components/src/components/DocumentPreview/components/PdfViewer/PdfViewer.tsx b/packages/discovery-react-components/src/components/DocumentPreview/components/PdfViewer/PdfViewer.tsx index 753f263e1..ea37114a6 100644 --- a/packages/discovery-react-components/src/components/DocumentPreview/components/PdfViewer/PdfViewer.tsx +++ b/packages/discovery-react-components/src/components/DocumentPreview/components/PdfViewer/PdfViewer.tsx @@ -1,25 +1,32 @@ -import React, { SFC, useEffect, useRef, useState, useMemo } from 'react'; -import PdfjsLib from 'pdfjs-dist'; +import React, { FC, useEffect, useRef, useMemo, useCallback } from 'react'; +import cx from 'classnames'; +import PdfjsLib, { + PDFDocumentProxy, + PDFPageProxy, + PDFPageViewport, + PDFPromise, + PDFRenderTask +} from 'pdfjs-dist'; import PdfjsWorkerAsText from 'pdfjs-dist/build/pdf.worker.min.js'; import { settings } from 'carbon-components'; +import useAsyncFunctionCall from 'utils/useAsyncFunctionCall'; +import PdfViewerTextLayer, { PdfRenderedText } from './PdfViewerTextLayer'; +import { PdfDisplayProps } from './types'; setupPdfjs(); -interface Props { +type Props = PdfDisplayProps & { + className?: string; + /** * PDF file data as base64-encoded string */ file: string; /** - * Page number, starting at 1 - */ - page: number; - - /** - * Zoom factor, where `1` is equal to 100% + * Text layer class name */ - scale: number; + textLayerClassName?: string; /** * Callback invoked with page count, once `file` has been parsed @@ -33,60 +40,35 @@ interface Props { * Callback which is invoked with whether to enable/disable toolbar controls */ setHideToolbarControls?: (disabled: boolean) => void; -} + /** + * Callback for text layer info + */ + setRenderedText?: (info: PdfRenderedText | null) => any; +}; -const PdfViewer: SFC = ({ +const PdfViewer: FC = ({ + className, file, page, scale, + textLayerClassName, setPageCount, setLoading, - setHideToolbarControls + setHideToolbarControls, + setRenderedText, + children }) => { const canvasRef = useRef(null); - // In order to prevent unnecessary re-loading, loaded file and page are stored in state - const [loadedFile, setLoadedFile] = useState(null); - const [loadedPage, setLoadedPage] = useState(null); - - useEffect(() => { - let didCancel = false; - - async function loadPdf(): Promise { - if (file) { - const newPdf = await _loadPdf(file); - if (!didCancel) { - setLoadedFile(newPdf); - if (setPageCount) { - setPageCount(newPdf.numPages); - } - } - } - } - loadPdf(); - - return (): void => { - didCancel = true; - }; - }, [file, setPageCount]); - - useEffect(() => { - let didCancel = false; - - async function loadPage(): Promise { - if (loadedFile && page > 0) { - const newPage = await _loadPage(loadedFile, page); - if (!didCancel) { - setLoadedPage(newPage); - } - } - } - loadPage(); - - return (): void => { - didCancel = true; - }; - }, [loadedFile, page]); + const loadedFile = useAsyncFunctionCall( + useCallback(async () => (file ? await _loadPdf(file) : null), [file]) + ); + const loadedPage = useAsyncFunctionCall( + useCallback( + async () => (loadedFile && page > 0 ? await _loadPage(loadedFile, page) : null), + [loadedFile, page] + ) + ); const [viewport, canvasInfo] = useMemo(() => { const viewport = loadedPage?.getViewport({ scale }); @@ -94,12 +76,27 @@ const PdfViewer: SFC = ({ return [viewport, canvasInfo]; }, [loadedPage, scale]); + // render page + useAsyncFunctionCall( + useCallback( + async (abortSignal: AbortSignal) => { + if (loadedPage && !(loadedPage as any).then && viewport && canvasInfo) { + const task = _renderPage(loadedPage, canvasRef.current!, viewport, canvasInfo); + abortSignal.addEventListener('abort', () => task?.cancel()); + await task?.promise; + + setLoading(false); + } + }, + [canvasInfo, loadedPage, setLoading, viewport] + ) + ); + useEffect(() => { - if (loadedPage && !loadedPage.then && viewport && canvasInfo) { - _renderPage(loadedPage, canvasRef.current!, viewport, canvasInfo); - setLoading(false); + if (setPageCount && loadedFile) { + setPageCount(loadedFile.numPages); } - }, [loadedPage, viewport, canvasInfo, setLoading]); + }, [loadedFile, setPageCount]); useEffect(() => { if (setHideToolbarControls) { @@ -107,14 +104,24 @@ const PdfViewer: SFC = ({ } }, [setHideToolbarControls]); + const classNameBase = `${settings.prefix}--document-preview-pdf-viewer`; return ( - +
+ + + {children} +
); }; @@ -123,24 +130,27 @@ PdfViewer.defaultProps = { scale: 1 }; -function _loadPdf(data: string): Promise { +function _loadPdf(data: string): PDFPromise { return PdfjsLib.getDocument({ data }).promise; } -function _loadPage(file: any, page: number): Promise { +function _loadPage(file: PDFDocumentProxy, page: number) { return file.getPage(page); } function _renderPage( - pdfPage: any, + pdfPage: PDFPageProxy, canvas: HTMLCanvasElement, - viewport: any, + viewport: PDFPageViewport, canvasInfo: CanvasInfo -): void { +): PDFRenderTask | null { const canvasContext = canvas.getContext('2d'); - canvasContext?.resetTransform(); - canvasContext?.scale(canvasInfo.canvasScale, canvasInfo.canvasScale); - pdfPage.render({ canvasContext, viewport }); + if (canvasContext) { + canvasContext.resetTransform(); + canvasContext.scale(canvasInfo.canvasScale, canvasInfo.canvasScale); + return pdfPage.render({ canvasContext, viewport }); + } + return null; } // set up web worker for use by PDF.js library @@ -148,7 +158,8 @@ function _renderPage( function setupPdfjs(): void { if (typeof Worker !== 'undefined') { const blob = new Blob([PdfjsWorkerAsText], { type: 'text/javascript' }); - const pdfjsWorker = new Worker(URL.createObjectURL(blob)); + const pdfjsWorker = new Worker(URL.createObjectURL(blob)) as any; + // @ts-expect-error Upgrading pdfjs-dist and its typings would resolve the issue PdfjsLib.GlobalWorkerOptions.workerPort = pdfjsWorker; } else { PdfjsLib.GlobalWorkerOptions.workerSrc = PdfjsWorkerAsText; @@ -173,4 +184,5 @@ function getCanvasInfo(viewport: any): CanvasInfo { return { width, height, canvasWidth, canvasHeight, canvasScale }; } +export type PdfViewerProps = Props; export default PdfViewer; diff --git a/packages/discovery-react-components/src/components/DocumentPreview/components/PdfViewer/PdfViewerTextLayer.tsx b/packages/discovery-react-components/src/components/DocumentPreview/components/PdfViewer/PdfViewerTextLayer.tsx new file mode 100644 index 000000000..5c76fb515 --- /dev/null +++ b/packages/discovery-react-components/src/components/DocumentPreview/components/PdfViewer/PdfViewerTextLayer.tsx @@ -0,0 +1,175 @@ +import React, { FC, useEffect, useRef, useCallback } from 'react'; +import cx from 'classnames'; +import { PDFPageProxy, PDFPageViewport, TextContent, TextContentItem } from 'pdfjs-dist'; +import { EventBus } from 'pdfjs-dist/lib/web/ui_utils'; +import { TextLayerBuilder } from 'pdfjs-dist/lib/web/text_layer_builder'; +import useAsyncFunctionCall from 'utils/useAsyncFunctionCall'; +import { PdfDisplayProps } from './types'; + +type Props = Pick & { + className?: string; + + /** + * PDF page from pdfjs + */ + loadedPage: PDFPageProxy | null | undefined; + + /** + * Callback for text layer info + */ + setRenderedText?: (info: PdfRenderedText | null) => any; +}; + +export type PdfRenderedText = { + /** + * PDF text content + */ + textContent: TextContent & { + styles: { [styleName: string]: CSSStyleDeclaration }; + }; + + /** + * Text span DOM elements rendered on the text layer + */ + textDivs: HTMLElement[]; + + /** + * Pdf page viewport used to render text items + */ + viewport: PDFPageViewport; + + /** + * Page number, starting at 1 + */ + page: number; +}; + +const PdfViewerTextLayer: FC = ({ + className, + loadedPage, + scale = 1, + setRenderedText = () => {} +}) => { + const textLayerRef = useRef(null); + const textLayerDiv = textLayerRef.current; + + // load text content from the page + const loadedText = useAsyncFunctionCall( + useCallback(async () => { + if (loadedPage) { + const viewport = loadedPage.getViewport({ scale }); + const textContent = await loadedPage.getTextContent(); + return { textContent, viewport, page: loadedPage.pageNumber, scale }; + } + return null; + }, [loadedPage, scale]) + ); + + // render text content + const renderedText = useAsyncFunctionCall( + useCallback( + async (signal: AbortSignal) => { + if (textLayerDiv && loadedText) { + const { textContent, viewport, scale, page } = loadedText; + + const builder = new TextLayerBuilder({ + textLayerDiv, + viewport, + eventBus: new EventBus(), + pageIndex: page - 1 + }); + signal.addEventListener('abort', () => builder.cancel()); + + await _renderTextLayer(builder, textContent, textLayerDiv, scale); + return { textContent, viewport, page, textDivs: builder.textDivs }; + } + return undefined; + }, + [loadedText, textLayerDiv] + ) + ); + + useEffect(() => { + if (renderedText !== undefined) { + setRenderedText(renderedText); + } + }, [renderedText, setRenderedText]); + + const rootClassName = cx(className, `textLayer`); + return ( +
+ ); +}; + +/** + * Render text into DOM using the text layer builder + */ +async function _renderTextLayer( + builder: TextLayerBuilder, + textContent: TextContent, + textLayerDiv: HTMLDivElement, + scale: number +) { + builder.setTextContent(textContent); + + // render + textLayerDiv.innerHTML = ''; + const deferredRenderEndPromise = new Promise(resolve => { + const listener = () => { + resolve(); + builder?.eventBus.off('textlayerrendered', listener); + }; + builder?.eventBus.on('textlayerrendered', listener); + }); + + builder.render(); + await deferredRenderEndPromise; + + _adjustTextDivs(builder.textDivs, textContent.items, scale); +} + +/** + * Adjust text span width based on scale + * @param textDivs + * @param textItems + * @param scale + */ +function _adjustTextDivs( + textDivs: HTMLElement[], + textItems: TextContentItem[] | null, + scale: number +): void { + const scaleXPattern = /scaleX\(([\d.]+)\)/; + (textDivs || []).forEach((textDivElm, index) => { + const textItem = textItems?.[index]; + if (!textItem) return; + + const expectedWidth = textItem.width * scale; + const actualWidth = textDivElm.getBoundingClientRect().width; + + function getScaleX(element: HTMLElement) { + const match = element.style.transform?.match(scaleXPattern); + if (match) { + return parseFloat(match[1]); + } + return null; + } + const currentScaleX = getScaleX(textDivElm); + if (currentScaleX && !isNaN(currentScaleX)) { + const newScale = `scaleX(${(expectedWidth / actualWidth) * currentScaleX})`; + textDivElm.style.transform = textDivElm.style.transform.replace(scaleXPattern, newScale); + } else { + const newScale = `scaleX(${expectedWidth / actualWidth})`; + textDivElm.style.transform = newScale; + } + }); +} + +export default PdfViewerTextLayer; diff --git a/packages/discovery-react-components/src/components/DocumentPreview/components/PdfViewer/types.ts b/packages/discovery-react-components/src/components/DocumentPreview/components/PdfViewer/types.ts new file mode 100644 index 000000000..063a65818 --- /dev/null +++ b/packages/discovery-react-components/src/components/DocumentPreview/components/PdfViewer/types.ts @@ -0,0 +1,11 @@ +export type PdfDisplayProps = { + /** + * Page number, starting at 1 + */ + page: number; + + /** + * Zoom factor, where `1` is equal to 100% + */ + scale: number; +}; diff --git a/packages/discovery-react-components/src/components/DocumentPreview/components/PdfViewer/typings.d.ts b/packages/discovery-react-components/src/components/DocumentPreview/components/PdfViewer/typings.d.ts index fa31151b5..44544e96f 100644 --- a/packages/discovery-react-components/src/components/DocumentPreview/components/PdfViewer/typings.d.ts +++ b/packages/discovery-react-components/src/components/DocumentPreview/components/PdfViewer/typings.d.ts @@ -1,2 +1,54 @@ -declare module 'pdfjs-dist'; declare module 'pdfjs-dist/build/pdf.worker.min.js'; + +// +// Declare modules and their types that is referred from PDF text layer rendering. +// Unused properties are commented out +// +declare module 'pdfjs-dist/lib/web/ui_utils' { + export class EventBus { + on(eventName: string, listener: any): void; + off(eventName: string, listener: any): void; + dispatch(eventName: string, args?: any): void; + } + // export function getGlobalEventBus(): EventBus; +} + +declare module 'pdfjs-dist/lib/web/text_layer_builder' { + import { EventBus } from 'pdfjs-dist/lib/web/ui_utils'; + import PdfjsLib from 'pdfjs-dist'; + + export class TextLayerBuilder { + constructor(options: TextLayerBuilder.Options); + + textLayerDiv: HTMLElement; + eventBus: EventBus; + textContent: PdfjsLib.TextContent | null; + // textContentItemsStr: any[]; + renderingDone: boolean; + // pageIdx: number; + pageNumber: number; + // matches: any[]; + // viewport: PdfjsLib.PDFPageViewport; + textDivs: HTMLElement[]; + // findController: any; + textLayerRenderTask: TextLayerRenderTask; + // enhanceTextSelection: any; + + render(timeout?: number): void; + cancel(): void; + // setTextContentStream(readableStream: any): void; + setTextContent(textContent: PdfjsLib.TextContent): void; + } + export const DefaultTextLayerFactory; + + declare namespace TextLayerBuilder { + export interface Options { + textLayerDiv: HTMLElement; + eventBus: EventBus; + pageIndex: number; + viewport: PdfjsLib.PDFPageViewport; + // findController?: any; + // enhanceTextSelection?: any; + } + } +} diff --git a/packages/discovery-react-components/src/utils/useAsyncFunctionCall.ts b/packages/discovery-react-components/src/utils/useAsyncFunctionCall.ts new file mode 100644 index 000000000..fe97a789e --- /dev/null +++ b/packages/discovery-react-components/src/utils/useAsyncFunctionCall.ts @@ -0,0 +1,46 @@ +import { useEffect, useState } from 'react'; + +type AsyncFunc = (signal: AbortSignal) => Promise; +type AsyncFuncReturnType = T extends AsyncFunc ? U : never; + +/** + * Call async function WRAPPED BY `useCallback` and return its result + * + * @param asyncFunction async function wrapped by `useCallback`. + * Take one parameter `setCancellable` to set _cancellable_ of the current async call. + * @returns the result of the async function + */ +function useAsyncFunctionCall, ReturnType = AsyncFuncReturnType>( + asyncFunction: Func +): ReturnType | undefined { + const [result, setResult] = useState(); + + useEffect(() => { + let resolved = false; + const abortController = new AbortController(); + + asyncFunction(abortController.signal) + .then((promiseResult: ReturnType) => { + resolved = true; + if (!abortController.signal.aborted && promiseResult !== undefined) { + setResult(promiseResult); + } + }) + .catch(err => { + resolved = true; + if (!abortController.signal.aborted) { + throw err; + } + }); + + return (): void => { + if (!resolved) { + abortController.abort(); + } + }; + }, [asyncFunction]); + + return result; +} + +export default useAsyncFunctionCall; diff --git a/packages/discovery-styles/package.json b/packages/discovery-styles/package.json index 3bbdf2c64..e8c84e651 100644 --- a/packages/discovery-styles/package.json +++ b/packages/discovery-styles/package.json @@ -10,7 +10,8 @@ "build": "node-sass --importer=../../node_modules/node-sass-tilde-importer --source-map=true scss/index.scss css/index.css", "prepublish": "yarn run build", "start": "yarn run build -- --watch", - "analyze": "yarn run g:analyze css/index.css" + "analyze": "yarn run g:analyze css/index.css", + "update-styles-from-pdfjs": "scripts/update-styles-from-pdfjs.sh" }, "files": [ "css/**/*", @@ -24,5 +25,9 @@ }, "publishConfig": { "access": "public" + }, + "devDependencies": { + "@types/prettier": "^2", + "prettier": "^2.4.1" } } diff --git a/packages/discovery-styles/scripts/generate-pdfjs_web_mixin.js b/packages/discovery-styles/scripts/generate-pdfjs_web_mixin.js new file mode 100644 index 000000000..ef99bdf52 --- /dev/null +++ b/packages/discovery-styles/scripts/generate-pdfjs_web_mixin.js @@ -0,0 +1,41 @@ +/** + * Generate mixin SCSS for pdfjs web.css .textLayer styles + * + * Usage: node + */ +const postcss = require('postcss'); +const fs = require('fs'); + +const originalPdfjsWebCss = process.argv[2]; +const mixinPdfjsWebScss = process.argv[3]; + +// load the original style +const cssText = fs.readFileSync(originalPdfjsWebCss, { encoding: 'utf-8' }); +const cssRoot = postcss.parse(cssText); + +// remove rules not related to .textLayer +cssRoot.walkRules(rule => { + if (rule.selector.includes('.textLayer')) { + return; + } + rule.remove(); +}); + +// keep copyright comment +cssRoot.walkComments(comment => { + if (comment.text.includes('Copyright')) { + return; + } + comment.remove(); +}); + +// write mixin scss +const generatedCss = ` +/* DO NOT EDIT. THIS FILE IS AUTOMATICALLY GENERATED FROM \`update-styles-from-pdfjs.sh\`. */ +@mixin pdfjsTextLayer { + // CSS from ~pdfjs-dist/web/pdf_viewer.css for scoped style + ${cssRoot.toString()} +} +`; + +fs.writeFileSync(mixinPdfjsWebScss, generatedCss, { encoding: 'utf-8' }); diff --git a/packages/discovery-styles/scripts/update-styles-from-pdfjs.sh b/packages/discovery-styles/scripts/update-styles-from-pdfjs.sh new file mode 100755 index 000000000..d4cf33c89 --- /dev/null +++ b/packages/discovery-styles/scripts/update-styles-from-pdfjs.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# +# This script updates PDF text layer CSS from the `pdfjs-dist` npm package +# +# When you upgrade the `pdfjs-dist` package, you have to run this script +# to include the style changes. +# +# Usage: $ scripts/update-styles.sh +# - You must run `yarn` to install `pdfjs-dist` package before running +# +BASEDIR=$(dirname "$0")/.. + +# pdfjs textLayer styles +PDFJS_WEB_CSS=$BASEDIR/../../node_modules/pdfjs-dist/web/pdf_viewer.css +PDFJS_SCSS=$BASEDIR/scss/components/document-preview/_pdfjs_web_mixins.scss +yarn node $BASEDIR/scripts/generate-pdfjs_web_mixin.js "$PDFJS_WEB_CSS" "$PDFJS_SCSS" + +# prettier +yarn run prettier --write "$PDFJS_SCSS" diff --git a/packages/discovery-styles/scss/components/document-preview/_document-preview-pdf-viewer.scss b/packages/discovery-styles/scss/components/document-preview/_document-preview-pdf-viewer.scss index 19169fe5f..dce987167 100644 --- a/packages/discovery-styles/scss/components/document-preview/_document-preview-pdf-viewer.scss +++ b/packages/discovery-styles/scss/components/document-preview/_document-preview-pdf-viewer.scss @@ -1,3 +1,14 @@ +@import './pdfjs_web_mixins'; + .#{$prefix}--document-preview-pdf-viewer { + position: relative; + @include pdfjsTextLayer; +} + +.#{$prefix}--document-preview-pdf-viewer--canvas { + transform-origin: left top 0px; +} + +.#{$prefix}--document-preview-pdf-viewer--text { transform-origin: left top 0px; } diff --git a/packages/discovery-styles/scss/components/document-preview/_pdfjs_web_mixins.scss b/packages/discovery-styles/scss/components/document-preview/_pdfjs_web_mixins.scss new file mode 100644 index 000000000..f8d96b2f4 --- /dev/null +++ b/packages/discovery-styles/scss/components/document-preview/_pdfjs_web_mixins.scss @@ -0,0 +1,89 @@ +/* DO NOT EDIT. THIS FILE IS AUTOMATICALLY GENERATED FROM `update-styles-from-pdfjs.sh`. */ +@mixin pdfjsTextLayer { + // CSS from ~pdfjs-dist/web/pdf_viewer.css for scoped style + /* Copyright 2014 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + .textLayer { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + overflow: hidden; + opacity: 0.2; + line-height: 1; + } + + .textLayer > span { + color: transparent; + position: absolute; + white-space: pre; + cursor: text; + -webkit-transform-origin: 0% 0%; + transform-origin: 0% 0%; + } + + .textLayer .highlight { + margin: -1px; + padding: 1px; + + background-color: rgb(180, 0, 170); + border-radius: 4px; + } + + .textLayer .highlight.begin { + border-radius: 4px 0px 0px 4px; + } + + .textLayer .highlight.end { + border-radius: 0px 4px 4px 0px; + } + + .textLayer .highlight.middle { + border-radius: 0px; + } + + .textLayer .highlight.selected { + background-color: rgb(0, 100, 0); + } + + .textLayer ::-moz-selection { + background: rgb(0, 0, 255); + } + + .textLayer ::selection { + background: rgb(0, 0, 255); + } + + .textLayer .endOfContent { + display: block; + position: absolute; + left: 0px; + top: 100%; + right: 0px; + bottom: 0px; + z-index: -1; + cursor: default; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + .textLayer .endOfContent.active { + top: 0px; + } +} diff --git a/yarn.lock b/yarn.lock index 50af4d66a..6ebd9e514 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2277,6 +2277,7 @@ __metadata: "@storybook/core": ^5.3.21 "@storybook/react": ^5.3.21 "@storybook/source-loader": ^5.3.21 + "@types/pdfjs-dist": 2.1.7 classnames: ^2.2.6 cross-env: ^7.0.3 css-loader: ^3.4.2 @@ -2307,6 +2308,9 @@ __metadata: "@ibm-watson/discovery-styles@^1.5.0-beta.2, @ibm-watson/discovery-styles@workspace:packages/discovery-styles": version: 0.0.0-use.local resolution: "@ibm-watson/discovery-styles@workspace:packages/discovery-styles" + dependencies: + "@types/prettier": ^2 + prettier: ^2.4.1 peerDependencies: carbon-components: ">= 10.6.0 < 11" languageName: unknown @@ -4944,6 +4948,20 @@ __metadata: languageName: node linkType: hard +"@types/pdfjs-dist@npm:2.1.7": + version: 2.1.7 + resolution: "@types/pdfjs-dist@npm:2.1.7" + checksum: 14ca335658a85ab5cab908f3ef3ec104cb62487acc2465bb74b6d0430cd720dca30a8804440e152967b1dd7fb384c65ef05da1702a1ec87b905c0f5c73bbe653 + languageName: node + linkType: hard + +"@types/prettier@npm:^2": + version: 2.4.2 + resolution: "@types/prettier@npm:2.4.2" + checksum: 76e230b2d11028af11fe12e09b2d5b10b03738e9abf819ae6ebb0f78cac13d39f860755ce05ac3855b608222518d956628f5d00322dc206cc6d1f2d8d1519f1e + languageName: node + linkType: hard + "@types/prop-types@npm:*": version: 15.7.3 resolution: "@types/prop-types@npm:15.7.3"