diff options
Diffstat (limited to 'src/client/views/pdf/PDFViewer.tsx')
-rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 101 |
1 files changed, 65 insertions, 36 deletions
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 7a86ee802..a50526223 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -11,7 +11,7 @@ import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction } from '../../../Utils'; +import { emptyFunction, numberRange, unimplementedFunction } from '../../../Utils'; import { DocUtils } from '../../documents/DocUtils'; import { SnappingManager } from '../../util/SnappingManager'; import { MarqueeOptionsMenu } from '../collections/collectionFreeForm'; @@ -28,8 +28,9 @@ import { AnchorMenu } from './AnchorMenu'; import { Annotation } from './Annotation'; import { GPTPopup } from './GPTPopup/GPTPopup'; import './PDFViewer.scss'; -// The workerSrc property shall be specified. -// Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.4.168/build/pdf.worker.mjs'; +import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT'; +import ReactLoading from 'react-loading'; +import { Transform } from '../../util/Transform'; interface IViewerProps extends FieldViewProps { pdfBox: PDFBox; @@ -40,7 +41,7 @@ interface IViewerProps extends FieldViewProps { pdf: Pdfjs.PDFDocumentProxy; url: string; sidebarAddDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean; - loaded?: (nw: number, nh: number, np: number) => void; + loaded: (p: { width: number; height: number }, pages: number) => void; // eslint-disable-next-line no-use-before-define setPdfViewer: (view: PDFViewer) => void; anchorMenuClick?: () => undefined | ((anchor: Doc) => void); @@ -64,6 +65,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { @observable _textSelecting = true; @observable _showWaiting = true; @observable Index: number = -1; + @observable private _loading = false; private _pdfViewer!: PDFJSViewer.PDFViewer; private _styleRule: number | undefined; // stylesheet rule for making hyperlinks clickable @@ -145,32 +147,30 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { } }; - @observable _scrollHeight = 0; + @computed get _scrollHeight() { + return this._pageSizes.reduce((size, page) => size + page.height, 0); + } - @action - initialLoad = async () => { + initialLoad = () => { + const page0or180 = (page: { rotate: number }) => page.rotate === 0 || page.rotate === 180; if (this._pageSizes.length === 0) { - this._pageSizes = Array<{ width: number; height: number }>(this._props.pdf.numPages); - await Promise.all( - this._pageSizes.map((val, i) => - this._props.pdf.getPage(i + 1).then( - action((page: Pdfjs.PDFPageProxy) => { - const page0or180 = page.rotate === 0 || page.rotate === 180; - this._pageSizes.splice(i, 1, { - width: page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1], - height: page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], - }); - if (i === this._props.pdf.numPages - 1) { - this._props.loaded?.(page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1], page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], this._props.pdf.numPages); - } - }) - ) + const devicePixelRatio = window.devicePixelRatio; + document.documentElement?.style.setProperty('--devicePixelRatio', window.devicePixelRatio.toString()); // set so that css can use this to adjust various PDFJs divs + Promise.all( + numberRange(this._props.pdf.numPages).map(i => + this._props.pdf.getPage(i + 1).then(page => ({ + width: (page.view[page0or180(page) ? 2 : 3] - page.view[page0or180(page) ? 0 : 1]) * devicePixelRatio, + height: (page.view[page0or180(page) ? 3 : 2] - page.view[page0or180(page) ? 1 : 0]) * devicePixelRatio, + })) ) + ).then( + action(pages => { + this._pageSizes = pages; + this._props.loaded(pages.lastElement(), this._props.pdf.numPages); + this.createPdfViewer(); + }) ); } - runInAction(() => { - this._scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72; - }); }; _scrollStopper: undefined | (() => void); @@ -196,14 +196,12 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { crop = (region: Doc | undefined, addCrop?: boolean) => this._props.crop(region, addCrop); @action - setupPdfJsViewer = async () => { + setupPdfJsViewer = () => { if (this._viewerIsSetup) return; this._viewerIsSetup = true; this._showWaiting = true; this._props.setPdfViewer(this); - await this.initialLoad(); - - this.createPdfViewer(); + this.initialLoad(); }; pagesinit = () => { @@ -376,7 +374,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { if ((e.button !== 0 || e.altKey) && this._props.isContentActive()) { this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this._props.Document); } - if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { + if (!e.altKey && e.button === 0 && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) { this._props.select(false); MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this.isAnnotating = true; @@ -394,8 +392,26 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { } }; + /** + * Create a flashcard pile based on the selected text of a pdf. + */ + gptPDFFlashcards = async () => { + const queryText = this._selectionText; + runInAction(() => (this._loading = true)); + try { + const res = await gptAPICall(queryText, GPTCallType.FLASHCARD); + + AnchorMenu.Instance.transferToFlashcard(res || 'Something went wrong', NumCast(this._props.layoutDoc['x']), NumCast(this._props.layoutDoc['y'])); + this._selectionText = ''; + } catch (err) { + console.error(err); + } + runInAction(() => (this._loading = false)); + }; + @action finishMarquee = (/* x?: number, y?: number */) => { + AnchorMenu.Instance.makeLabels = unimplementedFunction; this._getAnchor = AnchorMenu.Instance?.GetAnchor; this.isAnnotating = false; this._marqueeref.current?.onTerminateSelection(); @@ -411,8 +427,10 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { document.removeEventListener('pointerup', this.onSelectEnd); const sel = window.getSelection(); + if (sel) { AnchorMenu.Instance.setSelectedText(sel.toString()); + AnchorMenu.Instance.setLocation(NumCast(this._props.layoutDoc['x']), NumCast(this._props.layoutDoc['y'])); } if (sel?.type === 'Range') { @@ -420,10 +438,12 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { AnchorMenu.Instance.jumpTo(e.clientX, e.clientY); } - GPTPopup.Instance.setSidebarId('data_sidebar'); + GPTPopup.Instance.setSidebarFieldKey('data_sidebar'); GPTPopup.Instance.addDoc = this._props.sidebarAddDoc; // allows for creating collection AnchorMenu.Instance.addToCollection = this._props.DocumentView?.()._props.addDocument; + AnchorMenu.Instance.gptFlashcards = this.gptPDFFlashcards; + AnchorMenu.Instance.makeLabels = unimplementedFunction; AnchorMenu.Instance.AddDrawingAnnotation = this.addDrawingAnnotation; }; @@ -459,6 +479,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { this._mainCont.current!.style.transform = ''; } this._selectionContent = selRange.cloneContents(); + this._selectionText = this._selectionContent?.textContent || ''; // clear selection @@ -511,7 +532,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { } getScrollHeight = () => this._scrollHeight; - scrollXf = () => this._props.ScreenToLocalTransform().translate(0, this._mainCont.current ? NumCast(this._props.layoutDoc._layout_scrollTop) : 0); + scrollXf = () => this._props.ScreenToLocalTransform().translate(0, this._mainCont.current ? NumCast(this._props.layoutDoc._layout_scrollTop) / 1.333 : 0); overlayTransform = () => this.scrollXf().scale(1 / NumCast(this._props.layoutDoc._freeform_scale, 1)); panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1); panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1); @@ -532,7 +553,8 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { className="pdfViewerDash-overlay" style={{ mixBlendMode, - display: display, + display, + transform: `scale(${Pdfjs.PixelsPerInch.PDF_TO_CSS_UNITS})`, pointerEvents: Doc.ActiveTool !== InkTool.None ? 'all' : undefined, }}> <CollectionFreeFormView @@ -578,6 +600,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { } savedAnnotations = () => this._savedAnnotations; addDocumentWrapper = (doc: Doc | Doc[]) => this._props.addDocument!(doc); + screenToMarqueeXf = () => this.props.pdfBox.DocumentView?.()?.screenToContentsTransform().scale(Pdfjs.PixelsPerInch.PDF_TO_CSS_UNITS) ?? Transform.Identity(); render() { TraceMobx(); return ( @@ -597,17 +620,18 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { {this.annotationLayer} {this.overlayLayer} {this._showWaiting ? <img alt="" className="pdfViewerDash-waiting" src="/assets/loading.gif" /> : null} - {!this._mainCont.current || !this._annotationLayer.current ? null : ( + {!this._mainCont.current || !this._annotationLayer.current || !this.props.pdfBox.DocumentView ? null : ( <MarqueeAnnotator ref={this._marqueeref} Document={this._props.Document} getPageFromScroll={this.getPageFromScroll} anchorMenuClick={this._props.anchorMenuClick} scrollTop={0} - isNativeScaled + annotationLayerScaling={() => Pdfjs.PixelsPerInch.PDF_TO_CSS_UNITS} annotationLayerScrollTop={NumCast(this._props.Document._layout_scrollTop)} addDocument={this.addDocumentWrapper} - docView={this._props.pdfBox.DocumentView!} + docView={this.props.pdfBox.DocumentView} + screenTransform={this.screenToMarqueeXf} finishMarquee={this.finishMarquee} savedAnnotations={this.savedAnnotations} selectionText={this.selectionText} @@ -617,6 +641,11 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { /> )} </div> + {this._loading ? ( + <div className="loading-spinner" style={{ position: 'absolute' }}> + <ReactLoading type="spin" height={80} width={80} color={'blue'} /> + </div> + ) : null} </div> ); } |