diff options
Diffstat (limited to 'src/client/views/pdf')
| -rw-r--r-- | src/client/views/pdf/AnchorMenu.tsx | 16 | ||||
| -rw-r--r-- | src/client/views/pdf/Annotation.tsx | 43 | ||||
| -rw-r--r-- | src/client/views/pdf/PDFViewer.scss | 50 | ||||
| -rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 222 |
4 files changed, 162 insertions, 169 deletions
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index ad3afb775..29d068817 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -46,8 +46,10 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search + public OnCrop: (e: PointerEvent) => void = unimplementedFunction; public OnClick: (e: PointerEvent) => void = unimplementedFunction; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; + public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public Highlight: (color: string, isPushpin: boolean) => Opt<Doc> = (color: string, isPushpin: boolean) => undefined; public GetAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined; public Delete: () => void = unimplementedFunction; @@ -79,6 +81,13 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { }, returnFalse, e => this.OnClick?.(e)); } + cropDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, (e: PointerEvent) => { + this.StartCropDrag(e, this._commentCont.current!); + return true; + }, returnFalse, e => this.OnCrop?.(e)); + } + @action highlightClicked = (e: React.MouseEvent) => { if (!this.Highlight(this.highlightColor, false) && this.Pinned) { @@ -161,7 +170,12 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { <FontAwesomeIcon style={{ position: "absolute", transform: "scale(0.5)", transformOrigin: "top left", top: 12, left: 12 }} icon={"link"} size="lg" /> </button> </Tooltip>, - <LinkPopup key="popup" showPopup={this._showLinkPopup} linkFrom={this.onMakeAnchor} /> + <LinkPopup key="popup" showPopup={this._showLinkPopup} linkFrom={this.onMakeAnchor} />, + AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? <></> : <Tooltip key="crop" title={<div className="dash-tooltip">{"Click/Drag to create cropped image"}</div>}> + <button className="antimodeMenu-button annotate" onPointerDown={this.cropDown} style={{ cursor: "grab" }}> + <FontAwesomeIcon icon="image" size="lg" /> + </button> + </Tooltip>, ] : [ <Tooltip key="trash" title={<div className="dash-tooltip">{"Remove Link Anchor"}</div>}> <button className="antimodeMenu-button" onPointerDown={this.Delete}> diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index b1d1d8293..5bdce273d 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -16,39 +16,30 @@ interface IAnnotationProps extends FieldViewProps { dataDoc: Doc; fieldKey: string; showInfo: (anno: Opt<Doc>) => void; - pointerEvents?: string; + pointerEvents?: () => Opt<string>; } @observer export class Annotation extends React.Component<IAnnotationProps> { render() { - return DocListCast(this.props.anno.textInlineAnnotations).map(a => <RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} />); + return <div> + {DocListCast(this.props.anno.textInlineAnnotations).map(a => + <RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} /> + )} + </div>; } } interface IRegionAnnotationProps extends IAnnotationProps { document: Doc; - pointerEvents?: string; + pointerEvents?: () => Opt<string>; } @observer class RegionAnnotation extends React.Component<IRegionAnnotationProps> { - private _disposers: { [name: string]: IReactionDisposer } = {}; private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); - @observable _brushed: boolean = false; @computed get annoTextRegion() { return Cast(this.props.document.annoTextRegion, Doc, null) || this.props.document; } - componentDidMount() { - this._disposers.brush = reaction( - () => this.annoTextRegion && Doc.isBrushedHighlightedDegree(this.annoTextRegion), - brushed => brushed !== undefined && runInAction(() => this._brushed = brushed !== 0) - ); - } - - componentWillUnmount() { - Object.values(this._disposers).forEach(disposer => disposer?.()); - } - @undoBatch deleteAnnotation = () => { const docAnnotations = DocListCast(this.props.dataDoc[this.props.fieldKey]); @@ -91,15 +82,27 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> { } render() { - return (<div className="htmlAnnotation" onPointerEnter={() => this.props.showInfo(this.props.anno)} onPointerLeave={() => this.props.showInfo(undefined)} onPointerDown={this.onPointerDown} ref={this._mainCont} + const brushed = this.annoTextRegion && Doc.isBrushedHighlightedDegree(this.annoTextRegion); + return (<div className="htmlAnnotation" ref={this._mainCont} + onPointerEnter={action(() => { + Doc.BrushDoc(this.props.anno); + this.props.showInfo(this.props.anno); + })} + onPointerLeave={action(() => { + Doc.UnBrushDoc(this.props.anno); + this.props.showInfo(undefined); + })} + onPointerDown={this.onPointerDown} style={{ left: NumCast(this.props.document.x), top: NumCast(this.props.document.y), width: NumCast(this.props.document._width), height: NumCast(this.props.document._height), - opacity: this._brushed ? 0.5 : undefined, - pointerEvents: this.props.pointerEvents as any, - backgroundColor: this._brushed ? "orange" : StrCast(this.props.document.backgroundColor), + opacity: brushed === Doc.DocBrushStatus.highlighted ? 0.5 : undefined, + pointerEvents: this.props.pointerEvents?.() as any, + outline: brushed === Doc.DocBrushStatus.linkHighlighted ? "solid 1px lightBlue" : undefined, + backgroundColor: brushed === Doc.DocBrushStatus.highlighted ? "orange" : + StrCast(this.props.document.backgroundColor), }} > </div>); } diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index 390aed1e0..822af6a68 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -1,5 +1,3 @@ - - .pdfViewer-content { height: 100%; width: 100%; @@ -8,33 +6,42 @@ top: 0; left: 0; } -.pdfViewerDash, .pdfViewerDash-interactive { + +.pdfViewerDash, +.pdfViewerDash-interactive { position: absolute; width: 100%; height: 100%; top: 0; - left:0; + left: 0; position: absolute; overflow-y: auto; overflow-x: hidden; transform-origin: top left; - + // .canvasWrapper { // transform: scale(0.75); // transform-origin: top left; // } .textLayer { opacity: unset; - mix-blend-mode: multiply;// bcz: makes text fuzzy! + mix-blend-mode: multiply; // bcz: makes text fuzzy! + span { padding-right: 5px; padding-bottom: 4px; } } - .textLayer ::selection { background: #ACCEF7; } // should match the backgroundColor in createAnnotation() + + .textLayer ::selection { + background: #ACCEF7; + } + + // should match the backgroundColor in createAnnotation() .textLayer .highlight { background-color: yellow; } + .textLayer .highlight.selected { background-color: orange; } @@ -43,32 +50,32 @@ position: relative; border: unset; } + .pdfViewerDash-text-selected { - // position: relative; // bcz: this breaks auto-scrolling using the inline search box + // position: relative; // bcz: this breaks auto-scrolling using the inline search box z-index: -1; - .textLayer{ - pointer-events: all; - user-select: text; + + .textLayer { + pointer-events: all; + user-select: text; } } + .pdfViewerDash-text { transform-origin: top left; + .textLayer { will-change: transform; } } - .pdfViewerDash-overlay, .pdfViewerDash-overlay-inking { + .pdfViewerDash-overlay { transform-origin: left top; position: absolute; top: 0px; left: 0px; display: inline-block; - width:100%; - pointer-events: all; - } - .pdfViewerDash-overlay { - pointer-events: none; + width: 100%; } .pdfViewerDash-overlayAnno { @@ -81,7 +88,7 @@ border-radius: 5px; display: block; } - + .pdfViewerDash-annotationLayer { position: absolute; transform-origin: left top; @@ -90,12 +97,13 @@ pointer-events: none; mix-blend-mode: multiply; // bcz: makes text fuzzy! } + .pdfViewerDash-waiting { width: 70%; height: 70%; - margin : 15%; + margin: 15%; transition: 0.4s opacity ease; - opacity: 0.7; + opacity: 0.7; position: absolute; z-index: 10; } @@ -103,4 +111,4 @@ .pdfViewerDash-interactive { pointer-events: all; -}
\ No newline at end of file +}
\ No newline at end of file diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 1856c5353..2869d4f2d 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -1,18 +1,15 @@ -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; -import { Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; +import { Doc, DocListCast, Field, HeightSym, Opt } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { InkTool } from "../../../fields/InkField"; -import { Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; -import { PdfField } from "../../../fields/URLField"; +import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { TraceMobx } from "../../../fields/util"; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils } from "../../../Utils"; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, returnFalse, smoothScroll, Utils } from "../../../Utils"; import { DocUtils } from "../../documents/Documents"; -import { Networking } from "../../Network"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; -import { CompiledScript, CompileScript } from "../../util/Scripting"; import { SelectionManager } from "../../util/SelectionManager"; import { SharingManager } from "../../util/SharingManager"; import { SnappingManager } from "../../util/SnappingManager"; @@ -43,11 +40,11 @@ interface IViewerProps extends FieldViewProps { fieldKey: string; pdf: Pdfjs.PDFDocumentProxy; url: string; - startupLive: boolean; loaded?: (nw: number, nh: number, np: number) => void; setPdfViewer: (view: PDFViewer) => void; ContentScaling?: () => number; anchorMenuClick?: () => undefined | ((anchor: Doc) => void); + crop: (region: Doc | undefined, addCrop?: boolean) => Doc | undefined; } /** @@ -58,12 +55,9 @@ export class PDFViewer extends React.Component<IViewerProps> { static _annotationStyle: any = addStyleSheet(); @observable private _pageSizes: { width: number, height: number }[] = []; @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); - @observable private _script: CompiledScript = CompileScript("return true") as CompiledScript; @observable private _marqueeing: number[] | undefined; @observable private _textSelecting = true; @observable private _showWaiting = true; - @observable private _showCover = false; - @observable private _zoomed = 1; @observable private _overlayAnnoInfo: Opt<Doc>; @observable private Index: number = -1; @@ -74,18 +68,18 @@ export class PDFViewer extends React.Component<IViewerProps> { private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); private _disposers: { [name: string]: IReactionDisposer } = {}; private _viewer: React.RefObject<HTMLDivElement> = React.createRef(); - private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); + public _getAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined; + _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); private _selectionText: string = ""; private _downX: number = 0; private _downY: number = 0; - private _coverPath: any; private _lastSearch = false; private _viewerIsSetup = false; private _ignoreScroll = false; private _initialScroll: Opt<number>; private _forcedScroll = true; - + @observable isAnnotating = false; // key where data is stored @computed get allAnnotations() { return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + "-annotations"]), this.props.docFilters(), this.props.docRangeFilters(), undefined); @@ -93,37 +87,15 @@ export class PDFViewer extends React.Component<IViewerProps> { @computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); } componentDidMount = async () => { - // change the address to be the file address of the PNG version of each page - // file address of the pdf - const { url: { href } } = Cast(this.props.dataDoc[this.props.fieldKey], PdfField)!; - const { url: relative } = this.props; - if (relative.includes("/pdfs/")) { - const pathComponents = relative.split("/pdfs/")[1].split("/"); - const coreFilename = pathComponents.pop()!.split(".")[0]; - const params: any = { - coreFilename, - pageNum: Math.min(this.props.pdf.numPages, Math.max(1, NumCast(this.props.Document._curPage, 1))), - }; - if (pathComponents.length) { - params.subtree = `${pathComponents.join("/")}/`; - } - this._coverPath = href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" }; - } else { - const params: any = { - coreFilename: relative.split("/")[relative.split("/").length - 1], - pageNum: Math.min(this.props.pdf.numPages, Math.max(1, NumCast(this.props.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: "" }; - } runInAction(() => this._showWaiting = true); - this.props.startupLive && this.setupPdfJsViewer(); + this.setupPdfJsViewer(); this._mainCont.current?.addEventListener("scroll", e => (e.target as any).scrollLeft = 0); this._disposers.autoHeight = reaction(() => this.props.layoutDoc._autoHeight, autoHeight => { if (autoHeight) { this.props.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]); - this.props.setHeight(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1)); + this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1)); } }); @@ -167,7 +139,7 @@ export class PDFViewer extends React.Component<IViewerProps> { }); 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], i); + page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], this.props.pdf.numPages); } })))); this.props.Document.scrollHeight = this._pageSizes.reduce((size, page) => size + page.height, 0) * 96 / 72; @@ -181,8 +153,8 @@ export class PDFViewer extends React.Component<IViewerProps> { let focusSpeed: Opt<number>; if (doc !== this.props.rootDoc && mainCont) { const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1); - const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, .1 * windowHeight); - if (scrollTo !== undefined) { + const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, .1 * windowHeight, NumCast(this.props.Document.scrollHeight)); + if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) { focusSpeed = 500; if (!this._pdfViewer) this._initialScroll = scrollTo; @@ -194,6 +166,9 @@ export class PDFViewer extends React.Component<IViewerProps> { } return focusSpeed; } + crop = (region: Doc | undefined, addCrop?: boolean) => { + return this.props.crop(region, addCrop); + } @action setupPdfJsViewer = async () => { @@ -203,23 +178,12 @@ export class PDFViewer extends React.Component<IViewerProps> { this.props.setPdfViewer(this); await this.initialLoad(); - this._disposers.filterScript = reaction( - () => ScriptCast(this.props.Document.filterScript), - action(scriptField => { - const oldScript = this._script.originalScript; - this._script = scriptField?.script.compiled ? scriptField.script : CompileScript("return true") as CompiledScript; - if (this._script.originalScript !== oldScript) { - this.Index = -1; - } - }), - { fireImmediately: true }); - this.createPdfViewer(); } pagesinit = () => { if (this._pdfViewer._setDocumentViewerElement?.offsetParent) { - runInAction(() => this._pdfViewer.currentScaleValue = this._zoomed = 1); + runInAction(() => this._pdfViewer.currentScaleValue = this.props.layoutDoc._viewScale = 1); this.gotoPage(NumCast(this.props.Document._curPage, 1)); } document.removeEventListener("pagesinit", this.pagesinit); @@ -228,7 +192,7 @@ export class PDFViewer extends React.Component<IViewerProps> { () => Math.abs(NumCast(this.props.Document._scrollTop)), (pos) => { if (!this._ignoreScroll) { - (this._showCover || this._showWaiting) && this.setupPdfJsViewer(); + this._showWaiting && this.setupPdfJsViewer(); const viewTrans = quickScroll ?? StrCast(this.props.Document._viewTransition); const durationMiliStr = viewTrans.match(/([0-9]*)ms/); const durationSecStr = viewTrans.match(/([0-9.]*)s/); @@ -299,9 +263,7 @@ export class PDFViewer extends React.Component<IViewerProps> { @action gotoPage = (p: number) => { - if (this._pdfViewer?._setDocumentViewerElement?.offsetParent) { - this._pdfViewer?.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) }); - } + this._pdfViewer?.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) }); } @action @@ -312,6 +274,8 @@ export class PDFViewer extends React.Component<IViewerProps> { } } + @observable private _scrollTimer: any; + onScroll = (e: React.UIEvent<HTMLElement>) => { if (this._mainCont.current && !this._forcedScroll) { this._ignoreScroll = true; // the pdf scrolled, so we need to tell the Doc to scroll but we don't want the doc to then try to set the PDF scroll pos (which would interfere with the smooth scroll animation) @@ -319,6 +283,11 @@ export class PDFViewer extends React.Component<IViewerProps> { this.props.layoutDoc._scrollTop = this._mainCont.current.scrollTop; } this._ignoreScroll = false; + if (this._scrollTimer) clearTimeout(this._scrollTimer); // wait until a scrolling pause, then create an anchor to audio + this._scrollTimer = setTimeout(() => { + DocUtils.MakeLinkToActiveAudio(() => this.props.DocumentView?.().ComponentView?.getAnchor!()!, false); + this._scrollTimer = undefined; + }, 200); } } @@ -371,10 +340,11 @@ export class PDFViewer extends React.Component<IViewerProps> { if ((e.button !== 0 || e.altKey) && this.props.isContentActive(true)) { this._setPreviewCursor?.(e.clientX, e.clientY, true, false); } - if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { + if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) { this.props.select(false); MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; + this.isAnnotating = true; if (e.target && ((e.target as any).className.includes("endOfContent") || ((e.target as any).parentElement.className !== "textLayer"))) { this._textSelecting = false; document.addEventListener("pointermove", this.onSelectMove); // need this to prevent document from being dragged if stopPropagation doesn't get called @@ -391,6 +361,8 @@ export class PDFViewer extends React.Component<IViewerProps> { @action finishMarquee = (x?: number, y?: number) => { + this._getAnchor = AnchorMenu.Instance?.GetAnchor; + this.isAnnotating = false; this._marqueeing = undefined; this._textSelecting = true; document.removeEventListener("pointermove", this.onSelectMove); @@ -400,6 +372,7 @@ export class PDFViewer extends React.Component<IViewerProps> { @action onSelectEnd = (e: PointerEvent): void => { + this.isAnnotating = false; clearStyleSheetRules(PDFViewer._annotationStyle); this.props.select(false); document.removeEventListener("pointermove", this.onSelectMove); @@ -419,15 +392,16 @@ export class PDFViewer extends React.Component<IViewerProps> { const clientRects = selRange.getClientRects(); for (let i = 0; i < clientRects.length; i++) { const rect = clientRects.item(i); - if (rect && rect.width !== this._mainCont.current.clientWidth) { + if (rect && rect.width !== this._mainCont.current.clientWidth && rect.width) { const scaleX = this._mainCont.current.offsetWidth / boundingRect.width; + const pdfScale = NumCast(this.props.layoutDoc._viewScale, 1); const annoBox = document.createElement("div"); annoBox.className = "marqueeAnnotator-annotationBox"; // transforms the positions from screen onto the pdf div - annoBox.style.top = ((rect.top - boundingRect.top) * scaleX / this._zoomed + this._mainCont.current.scrollTop).toString(); - annoBox.style.left = ((rect.left - boundingRect.left) * scaleX / this._zoomed).toString(); - annoBox.style.width = (rect.width * this._mainCont.current.offsetWidth / boundingRect.width / this._zoomed).toString(); - annoBox.style.height = (rect.height * this._mainCont.current.offsetHeight / boundingRect.height / this._zoomed).toString(); + annoBox.style.top = ((rect.top - boundingRect.top) * scaleX / pdfScale + this._mainCont.current.scrollTop).toString(); + annoBox.style.left = ((rect.left - boundingRect.left) * scaleX / pdfScale).toString(); + annoBox.style.width = (rect.width * this._mainCont.current.offsetWidth / boundingRect.width / pdfScale).toString(); + annoBox.style.height = (rect.height * this._mainCont.current.offsetHeight / boundingRect.height / pdfScale).toString(); this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, this.getPageFromScroll(rect.top)); } } @@ -457,20 +431,6 @@ export class PDFViewer extends React.Component<IViewerProps> { setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => this._setPreviewCursor = func; - getCoverImage = () => { - if (!this.props.Document[HeightSym]() || !Doc.NativeHeight(this.props.Document)) { - setTimeout((() => { - this.props.Document._height = this.props.Document[WidthSym]() * this._coverPath.height / this._coverPath.width; - Doc.SetNativeWidth(this.props.Document, (Doc.NativeWidth(this.props.Document) || 0) * this._coverPath.height / this._coverPath.width); - }).bind(this), 0); - } - const nativeWidth = Doc.NativeWidth(this.props.Document); - const nativeHeight = Doc.NativeHeight(this.props.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` }} />; - } - @action onZoomWheel = (e: React.WheelEvent) => { if (this.props.isContentActive(true)) { @@ -478,22 +438,21 @@ export class PDFViewer extends React.Component<IViewerProps> { if (e.ctrlKey) { const curScale = Number(this._pdfViewer.currentScaleValue); this._pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale - curScale * e.deltaY / 1000)); - this._zoomed = Number(this._pdfViewer.currentScaleValue); + this.props.layoutDoc._viewScale = Number(this._pdfViewer.currentScaleValue); } } } - pointerEvents = () => this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none"; + pointerEvents = () => this.props.isContentActive() && this.props.pointerEvents?.() !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none"; @computed get annotationLayer() { - const pe = this.pointerEvents(); - return <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}> + return <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})` }} ref={this._annotationLayer}> {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno => - <Annotation {...this.props} fieldKey={this.props.fieldKey + "-annotations"} pointerEvents={pe} showInfo={this.showInfo} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)} + <Annotation {...this.props} fieldKey={this.props.fieldKey + "-annotations"} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)} </div>; } @computed get overlayInfo() { - return !this._overlayAnnoInfo || this._overlayAnnoInfo.author === Doc.CurrentUserEmail ? (null) : + return !this._overlayAnnoInfo ? (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)} @@ -502,7 +461,7 @@ export class PDFViewer extends React.Component<IViewerProps> { } showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno); - overlayTransform = () => this.scrollXf().scale(1 / this._zoomed); + overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._viewScale, 1)); panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter("textInlineAnnotations")]; @@ -515,62 +474,66 @@ export class PDFViewer extends React.Component<IViewerProps> { } return this.props.styleProvider?.(doc, props, property); } + + renderAnnotations = (docFilters?: () => string[], dontRender?: boolean) => + <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit} + isAnnotationOverlay={true} + fieldKey={this.props.fieldKey + "-annotations"} + setPreviewCursor={this.setPreviewCursor} + PanelHeight={this.panelHeight} + PanelWidth={this.panelWidth} + dropAction={"alias"} + select={emptyFunction} + ContentScaling={this.contentZoom} + bringToFront={emptyFunction} + docFilters={docFilters || this.basicFilter} + styleProvider={this.childStyleProvider} + dontRenderDocuments={dontRender} + CollectionView={undefined} + ScreenToLocalTransform={this.overlayTransform} + renderDepth={this.props.renderDepth + 1} + /> + @computed get overlayTransparentAnnotations() { return this.renderAnnotations(this.transparentFilter, false); } + @computed get overlayOpaqueAnnotations() { return this.renderAnnotations(this.opaqueFilter, false); } + @computed get overlayClickableAnnotations() { + return <div style={{ height: NumCast(this.props.rootDoc.scrollHeight) }}> + {this.renderAnnotations(undefined, true)} + </div>; + } @computed get overlayLayer() { - const renderAnnotations = (docFilters?: () => string[]) => - <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit} - isAnnotationOverlay={true} - fieldKey={this.props.fieldKey + "-annotations"} - setPreviewCursor={this.setPreviewCursor} - PanelHeight={this.panelHeight} - PanelWidth={this.panelWidth} - dropAction={"alias"} - select={emptyFunction} - ContentScaling={this.contentZoom} - bringToFront={emptyFunction} - docFilters={docFilters || this.basicFilter} - styleProvider={this.childStyleProvider} - dontRenderDocuments={docFilters ? false : true} - CollectionView={undefined} - ScreenToLocalTransform={this.overlayTransform} - renderDepth={this.props.renderDepth + 1} />; - return <div> - <div className={`pdfViewerDash-overlay${CurrentUserUtils.SelectedTool !== InkTool.None || SnappingManager.GetIsDragging() ? "-inking" : ""}`} + return <div style={{ pointerEvents: SnappingManager.GetIsDragging() ? "all" : "none" }}> + <div className="pdfViewerDash-overlay" style={{ - pointerEvents: SnappingManager.GetIsDragging() ? "all" : undefined, + pointerEvents: SnappingManager.GetIsDragging() ? "all" : "none", mixBlendMode: "multiply", - transform: `scale(${this._zoomed})` + transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})` }}> - {renderAnnotations(this.transparentFilter)} + {this.overlayTransparentAnnotations} </div> - <div className={`pdfViewerDash-overlay${CurrentUserUtils.SelectedTool !== InkTool.None || SnappingManager.GetIsDragging() ? "-inking" : ""}`} + <div className="pdfViewerDash-overlay" style={{ - pointerEvents: SnappingManager.GetIsDragging() ? "all" : undefined, + pointerEvents: SnappingManager.GetIsDragging() ? "all" : "none", mixBlendMode: this.allAnnotations.some(anno => anno.mixBlendMode) ? "hard-light" : undefined, - transform: `scale(${this._zoomed})` + transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})` }}> - {renderAnnotations(this.opaqueFilter)} - {SnappingManager.GetIsDragging() ? (null) : renderAnnotations()} + {this.overlayOpaqueAnnotations} + {this.overlayClickableAnnotations} </div> </div>; } @computed get pdfViewerDiv() { - return <div className={"pdfViewerDash-text" + (this.props.pointerEvents !== "none" && this._textSelecting && this.props.isContentActive() ? "-selected" : "")} ref={this._viewer} />; + return <div className={"pdfViewerDash-text" + (this.props.pointerEvents?.() !== "none" && this._textSelecting && this.props.isContentActive() ? "-selected" : "")} ref={this._viewer} />; } @computed get contentScaling() { return this.props.ContentScaling?.() || 1; } - @computed get standinViews() { - return <> - {this._showCover ? this.getCoverImage() : (null)} - {this._showWaiting ? <img className="pdfViewerDash-waiting" key="waiting" src={"/assets/loading.gif"} /> : (null)} - </>; - } - contentZoom = () => this._zoomed; + contentZoom = () => NumCast(this.props.layoutDoc._viewScale, 1); + savedAnnotations = () => this._savedAnnotations; render() { TraceMobx(); return <div className="pdfViewer-content"> - <div className={`pdfViewerDash${this.props.isContentActive() && this.props.pointerEvents !== "none" ? "-interactive" : ""}`} ref={this._mainCont} + <div className={`pdfViewerDash${this.props.isContentActive() && this.props.pointerEvents?.() !== "none" ? "-interactive" : ""}`} ref={this._mainCont} onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick} style={{ - overflowX: this._zoomed !== 1 ? "scroll" : undefined, + overflowX: NumCast(this.props.layoutDoc._viewScale, 1) !== 1 ? "scroll" : undefined, height: !this.props.Document._fitWidth && (window.screen.width > 600) ? Doc.NativeHeight(this.props.Document) : `${100 / this.contentScaling}%`, transform: `scale(${this.contentScaling})` }} > @@ -578,16 +541,21 @@ export class PDFViewer extends React.Component<IViewerProps> { {this.annotationLayer} {this.overlayLayer} {this.overlayInfo} - {this.standinViews} + {this._showWaiting ? <img className="pdfViewerDash-waiting" src={"/assets/loading.gif"} /> : (null)} {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) : - <MarqueeAnnotator rootDoc={this.props.rootDoc} scrollTop={0} down={this._marqueeing} + <MarqueeAnnotator rootDoc={this.props.rootDoc} + getPageFromScroll={this.getPageFromScroll} anchorMenuClick={this.props.anchorMenuClick} + scrollTop={0} + down={this._marqueeing} addDocument={(doc: Doc | Doc[]) => this.props.addDocument!(doc)} - finishMarquee={this.finishMarquee} docView={this.props.docViewPath().lastElement()} - getPageFromScroll={this.getPageFromScroll} - savedAnnotations={this._savedAnnotations} - annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />} + finishMarquee={this.finishMarquee} + savedAnnotations={this.savedAnnotations} + annotationLayer={this._annotationLayer.current} + mainCont={this._mainCont.current} + anchorMenuCrop={this._textSelecting ? undefined : this.crop} + />} </div> </div>; } |
