diff options
Diffstat (limited to 'src/client/views/pdf/PDFViewer.tsx')
-rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 109 |
1 files changed, 74 insertions, 35 deletions
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index b79be2e68..48c7b1762 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -3,14 +3,14 @@ import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; import { Dictionary } from "typescript-collections"; -import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; +import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym, Field } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; import { Id } from "../../../fields/FieldSymbols"; import { InkTool } from "../../../fields/InkField"; import { List } from "../../../fields/List"; import { createSchema, makeInterface } from "../../../fields/Schema"; import { ScriptField } from "../../../fields/ScriptField"; -import { Cast, NumCast } from "../../../fields/Types"; +import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { PdfField } from "../../../fields/URLField"; import { GetEffectiveAcl, TraceMobx } from "../../../fields/util"; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, emptyPath, intersectRect, returnZero, smoothScroll, Utils, OmitKeys } from "../../../Utils"; @@ -26,7 +26,6 @@ import { undoBatch } from "../../util/UndoManager"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { CollectionView } from "../collections/CollectionView"; import { ViewBoxAnnotatableComponent } from "../DocComponent"; -import { DocumentDecorations } from "../DocumentDecorations"; import { Annotation } from "./Annotation"; import { PDFMenu } from "./PDFMenu"; import "./PDFViewer.scss"; @@ -35,6 +34,8 @@ import React = require("react"); import { LinkDocPreview } from "../nodes/LinkDocPreview"; import { FormattedTextBoxComment } from "../nodes/formattedText/FormattedTextBoxComment"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; +import { SharingManager } from "../../util/SharingManager"; +import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox"; const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); const pdfjsLib = require("pdfjs-dist"); const _global = (window /* browser */ || global /* node */) as any; @@ -59,9 +60,10 @@ interface IViewerProps { fieldKey: string; Document: Doc; DataDoc?: Doc; - docFilters: () => string[]; searchFilterDocs: () => Doc[]; ContainingCollectionView: Opt<CollectionView>; + docFilters: () => string[]; + docRangeFilters: () => string[]; PanelWidth: () => number; PanelHeight: () => number; ContentScaling: () => number; @@ -71,7 +73,7 @@ interface IViewerProps { renderDepth: number; focus: (doc: Doc) => void; isSelected: (outsideReaction?: boolean) => boolean; - loaded: (nw: number, nh: number, np: number) => void; + loaded?: (nw: number, nh: number, np: number) => void; active: (outsideReaction?: boolean) => boolean; isChildActive: (outsideReaction?: boolean) => boolean; addDocTab: (document: Doc, where: string) => boolean; @@ -100,6 +102,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu @observable private _showWaiting = true; @observable private _showCover = false; @observable private _zoomed = 1; + @observable private _overlayAnnoInfo: Opt<Doc>; private _pdfViewer: any; private _styleRule: any; // stylesheet rule for making hyperlinks clickable @@ -119,8 +122,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu private _viewerIsSetup = false; @computed get allAnnotations() { - return DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]). - filter(anno => this._script.run({ this: anno }, console.log, true).result); + return DocUtils.FilterDocs(DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]), this.props.docFilters(), this.props.docRangeFilters(), undefined); } @computed get nonDocAnnotations() { return this.allAnnotations.filter(a => a.annotations); } @@ -134,7 +136,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu const coreFilename = pathComponents.pop()!.split(".")[0]; const params: any = { coreFilename, - pageNum: this.Document._curPage || 1, + pageNum: Math.min(this.props.pdf.numPages, Math.max(1, this.Document._curPage || 1)), }; if (pathComponents.length) { params.subtree = `${pathComponents.join("/")}/`; @@ -143,7 +145,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu } else { const params: any = { coreFilename: relative.split("/")[relative.split("/").length - 1], - pageNum: this.Document._curPage || 1, + pageNum: Math.min(this.props.pdf.numPages, Math.max(1, this.Document._curPage || 1)), }; this._coverPath = "http://cs.brown.edu/~bcz/face.gif";//href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" }; } @@ -153,6 +155,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu this._mainCont.current.scrollTop = this.layoutDoc._scrollTop || 0; const observer = new _global.ResizeObserver(action((entries: any) => this._mainCont.current && (this._mainCont.current.scrollTop = this.layoutDoc._scrollTop || 0))); observer.observe(this._mainCont.current); + this._mainCont.current.addEventListener("scroll", (e) => (e.target as any).scrollLeft = 0); } this._disposers.searchMatch = reaction(() => Doc.IsSearchMatch(this.rootDoc), @@ -176,12 +179,27 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu (scrollY) => { if (scrollY !== undefined) { (this._showCover || this._showWaiting) && this.setupPdfJsViewer(); - if ((this.props.renderDepth === -1 || (!LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc)) && this._mainCont.current) { - smoothScroll(1000, this._mainCont.current, (this.Document._scrollY || 0)); - } else { - console.log("Waiting for preview"); + if (this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) { + const delay = this._mainCont.current ? 0 : 250; // wait for mainCont and try again to scroll + const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/); + const duration = durationStr ? Number(durationStr[1]) : 1000; + setTimeout(() => this._mainCont.current && smoothScroll(duration, this._mainCont.current, Math.abs(scrollY || 0)), delay); + setTimeout(() => { this.Document._scrollTop = scrollY; this.Document._scrollY = undefined; }, duration + delay); + } + } + }, + { fireImmediately: true } + ); + this._disposers.scrollPreviewY = reaction( + () => Cast(this.Document._scrollPreviewY, "number", null), + (scrollY) => { + if (scrollY !== undefined) { + (this._showCover || this._showWaiting) && this.setupPdfJsViewer(); + if (this.props.renderDepth === -1 && scrollY >= 0) { + if (!this._mainCont.current) setTimeout(() => this._mainCont.current && smoothScroll(1000, this._mainCont.current, scrollY || 0)); + else smoothScroll(1000, this._mainCont.current, scrollY || 0); + this.Document._scrollPreviewY = undefined; } - setTimeout(() => this.Document._scrollY = undefined, 1000); } }, { fireImmediately: true } @@ -220,7 +238,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu width: (page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]), height: (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0]) }); - i === this.props.pdf.numPages - 1 && this.props.loaded((page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]), + i === this.props.pdf.numPages - 1 && this.props.loaded?.((page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]), (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0]), i); })))); this.Document.scrollHeight = this._pageSizes.reduce((size, page) => size + page.height, 0) * 96 / 72; @@ -337,6 +355,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu mainAnnoDocProto.y = Math.max(minY, 0); mainAnnoDocProto.x = Math.max(maxX, 0); mainAnnoDocProto.type = DocumentType.PDFANNO; + mainAnnoDocProto.text = this._selectionText; mainAnnoDocProto.annotations = new List<Doc>(annoDocs); } mainAnnoDocProto.title = "Annotation on " + this.Document.title; @@ -384,12 +403,12 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu @action onScroll = (e: React.UIEvent<HTMLElement>) => { if (!LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) { - this.Document._scrollY === undefined && (this.layoutDoc._scrollTop = this._mainCont.current!.scrollTop); this.pageDelay && clearTimeout(this.pageDelay); this.pageDelay = setTimeout(() => { + this.Document._scrollY === undefined && this._mainCont.current && (this.layoutDoc._scrollTop = this._mainCont.current.scrollTop); this.pageDelay = undefined; - this._pdfViewer && (this.Document._curPage = this._pdfViewer.currentPageNumber); - }, 250); + //this._pdfViewer && (this.Document._curPage = this._pdfViewer.currentPageNumber); + }, 1000); } } @@ -457,7 +476,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu @action onPointerDown = (e: React.PointerEvent): void => { const hit = document.elementFromPoint(e.clientX, e.clientY); - if (hit && hit.localName === "span" && this.props.isSelected(true)) { // drag selecting text stops propagation + if (hit && hit.localName === "span" && this.annotationsActive(true)) { // drag selecting text stops propagation e.button === 0 && e.stopPropagation(); } // if alt+left click, drag and annotate @@ -527,8 +546,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu if (rect) { const scaleY = this._mainCont.current.offsetHeight / boundingRect.height; const scaleX = this._mainCont.current.offsetWidth / boundingRect.width; - if (rect.width !== this._mainCont.current.clientWidth && - (i === 0 || !intersectRect(clientRects[i], clientRects[i - 1]))) { + if (rect.width !== this._mainCont.current.clientWidth) { const annoBox = document.createElement("div"); annoBox.className = "pdfViewerDash-annotationBox"; // transforms the positions from screen onto the pdf div @@ -554,6 +572,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu @action onSelectEnd = (e: PointerEvent): void => { clearStyleSheetRules(PDFViewer._annotationStyle); + this.props.select(false); this._savedAnnotations.clear(); if (this._marqueeing) { if (this._marqueeWidth > 10 || this._marqueeHeight > 10) { @@ -623,6 +642,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu clipDoc._height = this.marqueeHeight(); clipDoc._scrollTop = this.marqueeY(); const targetDoc = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.Document.title, 0, 0, 100, 100); + FormattedTextBox.SelectOnLoad = targetDoc[Id]; Doc.GetProto(targetDoc).data = new List<Doc>([clipDoc]); clipDoc.rootDocument = targetDoc; // DocUtils.makeCustomViewClicked(targetDoc, Docs.Create.StackingDocument, "slideView", undefined); @@ -639,6 +659,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu e.annoDragData.linkDocument = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation"); } annotationDoc.isLinkButton = true; // prevents link button fro showing up --- maybe not a good thing? + annotationDoc.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.props.Document; e.annoDragData && e.annoDragData.linkDocument && e.annoDragData?.linkDropCallback?.({ linkDocument: e.annoDragData.linkDocument }); } }); @@ -660,14 +681,14 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu getCoverImage = () => { - if (!this.props.Document[HeightSym]() || !this.props.Document._nativeHeight) { + if (!this.props.Document[HeightSym]() || !Doc.NativeHeight(this.props.Document)) { setTimeout((() => { this.Document._height = this.Document[WidthSym]() * this._coverPath.height / this._coverPath.width; - this.Document._nativeHeight = (this.Document._nativeWidth || 0) * this._coverPath.height / this._coverPath.width; + Doc.SetNativeWidth(this.Document, (Doc.NativeWidth(this.Document) || 0) * this._coverPath.height / this._coverPath.width); }).bind(this), 0); } - const nativeWidth = (this.Document._nativeWidth || 0); - const nativeHeight = (this.Document._nativeHeight || 0); + const nativeWidth = Doc.NativeWidth(this.Document); + const nativeHeight = Doc.NativeHeight(this.Document); const resolved = Utils.prepend(this._coverPath.path); return <img key={resolved} src={resolved} onError={action(() => this._coverPath.path = "http://www.cs.brown.edu/~bcz/face.gif")} onLoad={action(() => this._showWaiting = false)} style={{ position: "absolute", display: "inline-block", top: 0, left: 0, width: `${nativeWidth}px`, height: `${nativeHeight}px` }} />; @@ -687,18 +708,33 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu @computed get annotationLayer() { TraceMobx(); - return <div className="pdfViewerDash-annotationLayer" style={{ height: NumCast(this.Document._nativeHeight), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}> + return <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.Document), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}> {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno => - <Annotation {...this.props} focus={this.props.focus} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />) + <Annotation {...this.props} showInfo={this.showInfo} select={this.props.select} focus={this.props.focus} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />) } </div>; } + + @computed get overlayInfo() { + return !this._overlayAnnoInfo || this._overlayAnnoInfo.author === Doc.CurrentUserEmail ? (null) : + <div className="pdfViewerDash-overlayAnno" style={{ top: NumCast(this._overlayAnnoInfo.y), left: NumCast(this._overlayAnnoInfo.x) }}> + <div className="pdfViewerDash-overlayAnno" style={{ right: -50, background: SharingManager.Instance.users.find(users => users.user.email === this._overlayAnnoInfo!.author)?.userColor }}> + {this._overlayAnnoInfo.author + " " + Field.toString(this._overlayAnnoInfo.creationDate as Field)} + </div> + </div>; + } + + showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno); overlayTransform = () => this.scrollXf().scale(1 / this._zoomed); - panelWidth = () => (this.Document.scrollHeight || this.Document._nativeHeight || 0); - panelHeight = () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : (this.Document._nativeWidth || 0); + panelWidth = () => (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); + panelHeight = () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); @computed get overlayLayer() { - return <div className={`pdfViewerDash-overlay${Doc.GetSelectedTool() !== InkTool.None || SnappingManager.GetIsDragging() ? "-inking" : ""}`} id="overlay" - style={{ pointerEvents: SnappingManager.GetIsDragging() ? "all" : undefined, transform: `scale(${this._zoomed})` }}> + return <div className={`pdfViewerDash-overlay${Doc.GetSelectedTool() !== InkTool.None || SnappingManager.GetIsDragging() ? "-inking" : ""}`} + style={{ + pointerEvents: SnappingManager.GetIsDragging() ? "all" : undefined, + mixBlendMode: this.allAnnotations.some(anno => anno.mixBlendMode) ? "hard-light" : undefined, + transform: `scale(${this._zoomed})` + }}> <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit} LibraryPath={this.props.ContainingCollectionView?.props.LibraryPath ?? emptyPath} annotationsKey={this.annotationKey} @@ -719,6 +755,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu removeDocument={this.removeDocument} moveDocument={this.moveDocument} addDocument={this.addDocument} + docFilters={this.props.docFilters} + docRangeFilters={this.props.docRangeFilters} CollectionView={undefined} ScreenToLocalTransform={this.overlayTransform} renderDepth={this.props.renderDepth + 1} @@ -745,17 +783,18 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu contentZoom = () => this._zoomed; render() { TraceMobx(); - return <div className={"pdfViewerDash" + (this.active() ? "-interactive" : "")} ref={this._mainCont} + return <div className={"pdfViewerDash" + (this.annotationsActive() ? "-interactive" : "")} ref={this._mainCont} onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick} style={{ overflowX: this._zoomed !== 1 ? "scroll" : undefined, - width: !this.props.Document._fitWidth && (window.screen.width > 600) ? NumCast(this.props.Document._nativeWidth) : `${100 / this.contentScaling}%`, - height: !this.props.Document._fitWidth && (window.screen.width > 600) ? NumCast(this.props.Document._nativeHeight) : `${100 / this.contentScaling}%`, + width: !this.props.Document._fitWidth && (window.screen.width > 600) ? Doc.NativeWidth(this.props.Document) : `${100 / this.contentScaling}%`, + height: !this.props.Document._fitWidth && (window.screen.width > 600) ? Doc.NativeHeight(this.props.Document) : `${100 / this.contentScaling}%`, transform: `scale(${this.props.ContentScaling()})` }} > {this.pdfViewerDiv} - {this.overlayLayer} {this.annotationLayer} + {this.overlayLayer} + {this.overlayInfo} {this.standinViews} <PdfViewerMarquee isMarqueeing={this.marqueeing} width={this.marqueeWidth} height={this.marqueeHeight} x={this.marqueeX} y={this.marqueeY} /> </div >; |