diff options
Diffstat (limited to 'src/client/views/pdf')
| -rw-r--r-- | src/client/views/pdf/Annotation.scss | 5 | ||||
| -rw-r--r-- | src/client/views/pdf/Annotation.tsx | 96 | ||||
| -rw-r--r-- | src/client/views/pdf/PDFMenu.tsx | 123 | ||||
| -rw-r--r-- | src/client/views/pdf/PDFViewer.scss | 195 | ||||
| -rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 723 | ||||
| -rw-r--r-- | src/client/views/pdf/Page.scss | 31 | ||||
| -rw-r--r-- | src/client/views/pdf/Page.tsx | 325 |
7 files changed, 517 insertions, 981 deletions
diff --git a/src/client/views/pdf/Annotation.scss b/src/client/views/pdf/Annotation.scss index 0ea85d522..0c6df74f0 100644 --- a/src/client/views/pdf/Annotation.scss +++ b/src/client/views/pdf/Annotation.scss @@ -1,4 +1,7 @@ -.pdfViewer-annotationBox { +.pdfAnnotation { pointer-events: all; user-select: none; + position: absolute; + background-color: red; + opacity: 0.1; }
\ No newline at end of file diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index a08ff5969..7ba7b6d14 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -4,34 +4,26 @@ import { observer } from "mobx-react"; import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; -import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; +import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; import { DocumentManager } from "../../util/DocumentManager"; import { PresentationView } from "../presentationview/PresentationView"; import PDFMenu from "./PDFMenu"; import "./Annotation.scss"; -import { AnnotationTypes, scale, Viewer } from "./PDFViewer"; +import { scale } from "./PDFViewer"; interface IAnnotationProps { anno: Doc; index: number; - parent: Viewer; + ParentIndex: () => number; + fieldExtensionDoc: Doc; + scrollTo?: (n: number) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; } export default class Annotation extends React.Component<IAnnotationProps> { render() { - let annotationDocs = DocListCast(this.props.anno.annotations); - let res = annotationDocs.map(a => { - let type = NumCast(a.type); - switch (type) { - // case AnnotationTypes.Pin: - // return <PinAnnotation parent={this} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />; - case AnnotationTypes.Region: - return <RegionAnnotation parent={this.props.parent} document={a} index={this.props.index} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />; - default: - return <div></div>; - } - }); - return res; + return DocListCast(this.props.anno.annotations).map(a => ( + <RegionAnnotation {...this.props} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />)); } } @@ -41,44 +33,29 @@ interface IRegionAnnotationProps { width: number; height: number; index: number; - parent: Viewer; + ParentIndex: () => number; + fieldExtensionDoc: Doc; + scrollTo?: (n: number) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; document: Doc; } @observer class RegionAnnotation extends React.Component<IRegionAnnotationProps> { - @observable private _backgroundColor: string = "red"; - private _reactionDisposer?: IReactionDisposer; private _scrollDisposer?: IReactionDisposer; - private _mainCont: React.RefObject<HTMLDivElement>; - - constructor(props: IRegionAnnotationProps) { - super(props); - - this._mainCont = React.createRef(); - } + private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); componentDidMount() { this._reactionDisposer = reaction( - () => BoolCast(this.props.document.delete), - () => { - if (BoolCast(this.props.document.delete)) { - if (this._mainCont.current) { - this._mainCont.current.style.display = "none"; - } - } - }, + () => this.props.document.delete, + (del) => del && this._mainCont.current && (this._mainCont.current.style.display = "none"), { fireImmediately: true } ); this._scrollDisposer = reaction( - () => this.props.parent.Index, - () => { - if (this.props.parent.Index === this.props.index) { - this.props.parent.scrollTo(this.props.y * scale); - } - } + () => this.props.ParentIndex(), + (ind) => ind === this.props.index && this.props.scrollTo && this.props.scrollTo(this.props.y * scale) ); } @@ -88,16 +65,15 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> { } deleteAnnotation = () => { - let annotation = DocListCast(this.props.parent.props.parent.fieldExtensionDoc.annotations); + let annotation = DocListCast(this.props.fieldExtensionDoc.annotations); let group = FieldValue(Cast(this.props.document.group, Doc)); - if (group && annotation.indexOf(group) !== -1) { - let newAnnotations = annotation.filter(a => a !== FieldValue(Cast(this.props.document.group, Doc))); - this.props.parent.props.parent.fieldExtensionDoc.annotations = new List<Doc>(newAnnotations); - } - if (group) { - let groupAnnotations = DocListCast(group.annotations); - groupAnnotations.forEach(anno => anno.delete = true); + if (annotation.indexOf(group) !== -1) { + let newAnnotations = annotation.filter(a => a !== FieldValue(Cast(this.props.document.group, Doc))); + this.props.fieldExtensionDoc.annotations = new List<Doc>(newAnnotations); + } + + DocListCast(group.annotations).forEach(anno => anno.delete = true); } PDFMenu.Instance.fadeOut(true); @@ -105,9 +81,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> { pinToPres = () => { let group = FieldValue(Cast(this.props.document.group, Doc)); - if (group) { - PresentationView.Instance.PinDoc(group); - } + group && PresentationView.Instance.PinDoc(group); } @action @@ -118,7 +92,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> { let context = await Cast(targetDoc.targetContext, Doc); if (context) { DocumentManager.Instance.jumpToDocument(targetDoc, false, false, - ((doc) => this.props.parent.props.parent.props.addDocTab(targetDoc!, undefined, e.ctrlKey ? "onRight" : "inTab")), + ((doc) => this.props.addDocTab(targetDoc!, undefined, e.ctrlKey ? "onRight" : "inTab")), undefined, undefined); } } @@ -144,15 +118,13 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> { } render() { - return ( - <div className="pdfViewer-annotationBox" onPointerDown={this.onPointerDown} ref={this._mainCont} - style={{ - top: this.props.y * scale, - left: this.props.x * scale, - width: this.props.width * scale, - height: this.props.height * scale, - backgroundColor: this.props.parent.Index === this.props.index ? "green" : StrCast(this.props.document.color) - }}></div> - ); + return (<div className="pdfAnnotation" onPointerDown={this.onPointerDown} ref={this._mainCont} + style={{ + top: this.props.y, + left: this.props.x, + width: this.props.width, + height: this.props.height, + backgroundColor: this.props.ParentIndex() === this.props.index ? "green" : StrCast(this.props.document.color) + }} />); } }
\ No newline at end of file diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx index 27c2a8f1a..3ed81faef 100644 --- a/src/client/views/pdf/PDFMenu.tsx +++ b/src/client/views/pdf/PDFMenu.tsx @@ -11,36 +11,34 @@ import { handleBackspace } from "../nodes/PDFBox"; export default class PDFMenu extends React.Component { static Instance: PDFMenu; + private _offsetY: number = 0; + private _offsetX: number = 0; + private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); + private _commentCont = React.createRef<HTMLButtonElement>(); + private _snippetButton: React.RefObject<HTMLButtonElement> = React.createRef(); + private _dragging: boolean = false; + @observable private _top: number = -300; @observable private _left: number = -300; @observable private _opacity: number = 1; @observable private _transition: string = "opacity 0.5s"; @observable private _transitionDelay: string = ""; - - - StartDrag: (e: PointerEvent, ele: HTMLElement) => void = emptyFunction; - Highlight: (d: Doc | undefined, color: string | undefined) => void = emptyFunction; - Delete: () => void = emptyFunction; - Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = emptyFunction; - AddTag: (key: string, value: string) => boolean = returnFalse; - PinToPres: () => void = emptyFunction; + @observable private _keyValue: string = ""; + @observable private _valueValue: string = ""; + @observable private _added: boolean = false; @observable public Highlighting: boolean = false; @observable public Status: "pdf" | "annotation" | "snippet" | "" = ""; @observable public Pinned: boolean = false; + public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = emptyFunction; + public Highlight: (d: Doc | undefined, color: string) => void = emptyFunction; + public Delete: () => void = emptyFunction; + public Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = emptyFunction; + public AddTag: (key: string, value: string) => boolean = returnFalse; + public PinToPres: () => void = emptyFunction; public Marquee: { left: number; top: number; width: number; height: number; } | undefined; - private _offsetY: number = 0; - private _offsetX: number = 0; - private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); - private _commentCont = React.createRef<HTMLButtonElement>(); - private _snippetButton: React.RefObject<HTMLButtonElement> = React.createRef(); - private _dragging: boolean = false; - @observable private _keyValue: string = ""; - @observable private _valueValue: string = ""; - @observable private _added: boolean = false; - constructor(props: Readonly<{}>) { super(props); @@ -61,12 +59,10 @@ export default class PDFMenu extends React.Component { e.stopPropagation(); e.preventDefault(); - if (this._dragging) { - return; + if (!this._dragging) { + this.StartDrag(e, this._commentCont.current!); + this._dragging = true; } - - this.StartDrag(e, this._commentCont.current!); - this._dragging = true; } pointerUp = (e: PointerEvent) => { @@ -126,9 +122,20 @@ export default class PDFMenu extends React.Component { @action togglePin = (e: React.MouseEvent) => { this.Pinned = !this.Pinned; - if (!this.Pinned) { - this.Highlighting = false; - } + !this.Pinned && (this.Highlighting = false); + } + + dragStart = (e: React.PointerEvent) => { + document.removeEventListener("pointermove", this.dragging); + document.addEventListener("pointermove", this.dragging); + document.removeEventListener("pointerup", this.dragEnd); + document.addEventListener("pointerup", this.dragEnd); + + this._offsetX = this._mainCont.current!.getBoundingClientRect().width - e.nativeEvent.offsetX; + this._offsetY = e.nativeEvent.offsetY; + + e.stopPropagation(); + e.preventDefault(); } @action @@ -147,19 +154,6 @@ export default class PDFMenu extends React.Component { e.preventDefault(); } - dragStart = (e: React.PointerEvent) => { - document.removeEventListener("pointermove", this.dragging); - document.addEventListener("pointermove", this.dragging); - document.removeEventListener("pointerup", this.dragEnd); - document.addEventListener("pointerup", this.dragEnd); - - this._offsetX = this._mainCont.current!.getBoundingClientRect().width - e.nativeEvent.offsetX; - this._offsetY = e.nativeEvent.offsetY; - - e.stopPropagation(); - e.preventDefault(); - } - @action highlightClicked = (e: React.MouseEvent) => { if (!this.Pinned) { @@ -193,13 +187,10 @@ export default class PDFMenu extends React.Component { snippetDrag = (e: PointerEvent) => { e.stopPropagation(); e.preventDefault(); - if (this._dragging) { - return; - } - this._dragging = true; + if (!this._dragging) { + this._dragging = true; - if (this.Marquee) { - this.Snippet(this.Marquee); + this.Marquee && this.Snippet(this.Marquee); } } @@ -226,36 +217,32 @@ export default class PDFMenu extends React.Component { if (this._keyValue.length > 0 && this._valueValue.length > 0) { this._added = this.AddTag(this._keyValue, this._valueValue); - setTimeout( - () => { - runInAction(() => { - this._added = false; - }); - }, 1000 - ); + setTimeout(action(() => this._added = false), 1000); } } render() { - let buttons = this.Status === "pdf" || this.Status === "snippet" ? [ - <button key="1" className="pdfMenu-button" title="Click to Highlight" onClick={this.highlightClicked} - style={this.Highlighting ? { backgroundColor: "#121212" } : {}}> - <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} /> - </button>, - <button className="pdfMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}><FontAwesomeIcon icon="comment-alt" size="lg" key="2" /></button>, - this.Status === "snippet" ? <button className="pdfMenu-button" title="Drag to Snippetize Selection" onPointerDown={this.snippetStart} ref={this._snippetButton}><FontAwesomeIcon icon="cut" size="lg" /></button> : undefined, - <button key="3" className="pdfMenu-button" title="Pin Menu" onClick={this.togglePin} - style={this.Pinned ? { backgroundColor: "#121212" } : {}}> - <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} /> - </button> - ] : [ - <button key="4" className="pdfMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}><FontAwesomeIcon icon="trash-alt" size="lg" key="1" /></button>, - <button key="5" className="pdfMenu-button" title="Pin to Presentation" onPointerDown={this.PinToPres}><FontAwesomeIcon icon="map-pin" size="lg" key="2" /></button>, - <div className="pdfMenu-addTag" key="3"> + let buttons = this.Status === "pdf" || this.Status === "snippet" ? + [ + <button key="1" className="pdfMenu-button" title="Click to Highlight" onClick={this.highlightClicked} style={this.Highlighting ? { backgroundColor: "#121212" } : {}}> + <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} /></button>, + <button key="2" className="pdfMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}> + <FontAwesomeIcon icon="comment-alt" size="lg" /></button>, + <button key="3" className="pdfMenu-button" title="Drag to Snippetize Selection" style={{ display: this.Status === "snippet" ? "" : "none" }} onPointerDown={this.snippetStart} ref={this._snippetButton}> + <FontAwesomeIcon icon="cut" size="lg" /></button>, + <button key="4" className="pdfMenu-button" title="Pin Menu" onClick={this.togglePin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}> + <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} /> </button> + ] : [ + <button key="5" className="pdfMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}> + <FontAwesomeIcon icon="trash-alt" size="lg" /></button>, + <button key="6" className="pdfMenu-button" title="Pin to Presentation" onPointerDown={this.PinToPres}> + <FontAwesomeIcon icon="map-pin" size="lg" /></button>, + <div key="7" className="pdfMenu-addTag" > <input onKeyDown={handleBackspace} onChange={this.keyChanged} placeholder="Key" style={{ gridColumn: 1 }} /> <input onKeyDown={handleBackspace} onChange={this.valueChanged} placeholder="Value" style={{ gridColumn: 3 }} /> </div>, - <button key="6" className="pdfMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}><FontAwesomeIcon style={{ transition: "all .2s" }} color={this._added ? "#42f560" : "white"} icon="check" size="lg" key="4" /></button>, + <button key="8" className="pdfMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}> + <FontAwesomeIcon style={{ transition: "all .2s" }} color={this._added ? "#42f560" : "white"} icon="check" size="lg" /></button>, ]; return ( diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index 7158aaffa..a2f3911c5 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -1,136 +1,93 @@ -.textLayer { - div { - user-select: text; - } -} -.viewer-button-cont { - position: absolute; - display: flex; - justify-content: space-evenly; - align-items: center; -} - -.viewer-previousPage, -.viewer-nextPage { - background: grey; - font-weight: bold; - opacity: 0.5; - padding: 0 10px; - border-radius: 5px; -} - -.textLayer { - user-select: auto; -} -.viewer { - // position: absolute; - // top: 0; -} -.pdfViewere-viewer { +.pdfViewer-viewer { pointer-events:inherit; -} -.pdfViewer-text { - transform: scale(1.5); - transform-origin: top left; - .page { - .canvasWrapper { - display: none; - } - - .textLayer { - position: relative; - user-select: none; + width: 100%; + .pdfViewer-visibleElements { + .pdfPage-cont { + .pdfPage-textLayer { + div { + user-select: text; + } + span { + color: transparent; + position: absolute; + white-space: pre; + cursor: text; + -webkit-transform-origin: 0% 0%; + transform-origin: 0% 0%; + } + } } } -} -.pdfViewer-viewerCont { - width:100%; -} - -.page-cont { - .textLayer { - user-select: auto; - - div { - user-select: text; - } + .pdfViewer-text { + transform: scale(1.5); + transform-origin: top left; } -} - -.pdfViewer-overlayCont { - position: absolute; - width: 100%; - height: 100px; - background: #121721; - bottom: 0; - display: flex; - justify-content: center; - align-items: center; - padding: 20px; - overflow: hidden; - transition: left .5s; -} - -.pdfViewer-overlaySearchBar { - width: 20%; - height: 100%; - font-size: 30px; - padding: 5px; -} -.pdfViewer-overlayButton { - border-bottom-left-radius: 50%; - display: flex; - justify-content: space-evenly; - align-items: center; - height: 70px; - background: none; - padding: 0; - position: absolute; - - .pdfViewer-overlayButton-arrow { - width: 0; - height: 0; - border-top: 25px solid transparent; - border-bottom: 25px solid transparent; - border-right: 25px solid #121721; - transition: all 0.5s; + .pdfViewer-annotationLayer { + position: absolute; + top: 0; + width: 100%; + pointer-events: none; + .pdfPage-annotationBox { + position: absolute; + background-color: red; + opacity: 0.1; + } } - .pdfViewer-overlayButton-iconCont { + .pdfViewer-overlayCont { + position: absolute; + width: 100%; + height: 100px; background: #121721; - height: 50px; - width: 70px; + bottom: 0; display: flex; justify-content: center; align-items: center; - margin-left: -2px; - border-radius: 3px; + padding: 20px; + overflow: hidden; + transition: left .5s; + .pdfViewer-overlaySearchBar { + width: 20%; + height: 100%; + font-size: 30px; + padding: 5px; + } } -} -.pdfViewer-overlayButton:hover { - background: none; -} + .pdfViewer-overlayButton { + border-bottom-left-radius: 50%; + display: flex; + justify-content: space-evenly; + align-items: center; + height: 70px; + background: none; + padding: 0; + position: absolute; + + .pdfViewer-overlayButton-arrow { + width: 0; + height: 0; + border-top: 25px solid transparent; + border-bottom: 25px solid transparent; + border-right: 25px solid #121721; + transition: all 0.5s; + } -.pdfViewer-annotationBox { - position: absolute; - background-color: red; - opacity: 0.1; -} + .pdfViewer-overlayButton-iconCont { + background: #121721; + height: 50px; + width: 70px; + display: flex; + justify-content: center; + align-items: center; + margin-left: -2px; + border-radius: 3px; + } + } -.pdfViewer-annotationLayer { - position: absolute; - top: 0; - width: 100%; - pointer-events: none; + .pdfViewer-overlayButton:hover { + background: none; + } } - - - -.pdfViewer-pinAnnotation { - background-color: red; - position: absolute; - border-radius: 100%; -}
\ No newline at end of file diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 6a99cec59..7cd62f4e0 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -1,167 +1,114 @@ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; import * as rp from "request-promise"; import { Dictionary } from "typescript-collections"; -import { Doc, DocListCast, Opt } from "../../../new_fields/Doc"; +import { Doc, DocListCast, FieldResult } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; +import { ScriptField } from "../../../new_fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { emptyFunction, Utils } from "../../../Utils"; +import { Utils, numberRange } from "../../../Utils"; +import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from "../../documents/Documents"; -import { DragManager } from "../../util/DragManager"; -import { PDFBox } from "../nodes/PDFBox"; +import { KeyCodes } from "../../northstar/utils/KeyCodes"; +import { CompileScript, CompiledScript } from "../../util/Scripting"; +import Annotation from "./Annotation"; import Page from "./Page"; import "./PDFViewer.scss"; import React = require("react"); -import { CompileScript, CompileResult } from "../../util/Scripting"; -import { ScriptField } from "../../../new_fields/ScriptField"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import Annotation from "./Annotation"; -import { KeyCodes } from "../../northstar/utils/KeyCodes"; -import { DocServer } from "../../DocServer"; const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); export const scale = 2; -interface IPDFViewerProps { - url: string; - loaded: (nw: number, nh: number, np: number) => void; - scrollY: number; - parent: PDFBox; -} - -/** - * Wrapper that loads the PDF and cascades the pdf down - */ -@observer -export class PDFViewer extends React.Component<IPDFViewerProps> { - @observable _pdf: Opt<Pdfjs.PDFDocumentProxy>; - private _mainDiv = React.createRef<HTMLDivElement>(); - - @action - componentDidMount() { - Pdfjs.getDocument(this.props.url).promise.then(pdf => runInAction(() => this._pdf = pdf)); - } - - render() { - return ( - <div className="pdfViewer-viewerCont" ref={this._mainDiv}> - {!this._pdf ? (null) : - <Viewer pdf={this._pdf} loaded={this.props.loaded} scrollY={this.props.scrollY} parent={this.props.parent} mainCont={this._mainDiv} url={this.props.url} />} - </div> - ); - } -} interface IViewerProps { pdf: Pdfjs.PDFDocumentProxy; - loaded: (nw: number, nh: number, np: number) => void; - scrollY: number; - parent: PDFBox; - mainCont: React.RefObject<HTMLDivElement>; url: string; + Document: Doc; + DataDoc?: Doc; + fieldExtensionDoc: Doc; + fieldKey: string; + loaded: (nw: number, nh: number, np: number) => void; + panY: number; + scrollTo: (y: number) => void; + active: () => boolean; + setPanY?: (n: number) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean; } /** * Handles rendering and virtualization of the pdf */ @observer -export class Viewer extends React.Component<IViewerProps> { - // _visibleElements is the array of JSX elements that gets rendered - @observable.shallow private _visibleElements: JSX.Element[] = []; - // _isPage is an array that tells us whether or not an index is rendered as a page or as a placeholder - @observable private _isPage: string[] = []; +export class PDFViewer extends React.Component<IViewerProps> { + @observable.shallow private _visibleElements: JSX.Element[] = []; // _visibleElements is the array of JSX elements that gets rendered + @observable private _isPage: string[] = [];// _isPage is an array that tells us whether or not an index is rendered as a page or as a placeholder @observable private _pageSizes: { width: number, height: number }[] = []; @observable private _annotations: Doc[] = []; @observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>(); - @observable private _script: CompileResult | undefined; + @observable private _script: CompiledScript = CompileScript("return true") as CompiledScript; @observable private _searching: boolean = false; - - @observable public Index: number = -1; + @observable private Index: number = -1; private _pageBuffer: number = 1; private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); private _reactionDisposer?: IReactionDisposer; private _annotationReactionDisposer?: IReactionDisposer; - private _dropDisposer?: DragManager.DragDropDisposer; private _filterReactionDisposer?: IReactionDisposer; - private _viewer: React.RefObject<HTMLDivElement>; - private _mainCont: React.RefObject<HTMLDivElement>; + private _viewer: React.RefObject<HTMLDivElement> = React.createRef(); + private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); private _pdfViewer: any; - // private _textContent: Pdfjs.TextContent[] = []; private _pdfFindController: any; private _searchString: string = ""; private _selectionText: string = ""; - constructor(props: IViewerProps) { - super(props); + @computed get panY(): number { return this.props.panY; } - let scriptfield = Cast(this.props.parent.Document.filterScript, ScriptField); - this._script = scriptfield ? scriptfield.script : CompileScript("return true"); - this._viewer = React.createRef(); - this._mainCont = React.createRef(); - } + // startIndex: where to start rendering pages + @computed get startIndex(): number { return Math.max(0, this.getPageFromScroll(this.panY) - this._pageBuffer); } - setSelectionText = (text: string) => { - this._selectionText = text; + // endIndex: where to end rendering pages + @computed get endIndex(): number { + return Math.min(this.props.pdf.numPages - 1, this.getPageFromScroll(this.panY + (this._pageSizes[0] ? this._pageSizes[0].height : 0)) + this._pageBuffer); } - componentDidUpdate = (prevProps: IViewerProps) => { - if (this.scrollY !== prevProps.scrollY) { - this.renderPages(); - } + @computed get filteredAnnotations() { + return this._annotations.filter(anno => { + let run = this._script.run({ this: anno }); + return run.success ? run.result : true; + }); } - @action - componentDidMount = () => { - this._reactionDisposer = reaction( + componentDidUpdate = (prevProps: IViewerProps) => this.panY !== prevProps.panY && this.renderPages(); - () => [this.props.parent.props.active(), this.startIndex, this._pageSizes.length ? this.endIndex : 0], - async () => { - await this.initialLoad(); - this.renderPages(); - }, { fireImmediately: true }); + componentDidMount = async () => { + await this.initialLoad(); - this._annotationReactionDisposer = reaction( - () => { - return this.props.parent && this.props.parent.fieldExtensionDoc && DocListCast(this.props.parent.fieldExtensionDoc.annotations); - }, - (annotations: Doc[]) => { - annotations && annotations.length && this.renderAnnotations(annotations, true); - }, + this._reactionDisposer = reaction( + () => [this.props.active(), this.startIndex, this._pageSizes.length ? this.endIndex : 0], + () => this.renderPages(), { fireImmediately: true }); + this._annotationReactionDisposer = reaction( + () => this.props.fieldExtensionDoc && DocListCast(this.props.fieldExtensionDoc.annotations), + annotations => annotations && annotations.length && this.renderAnnotations(annotations, true), + { fireImmediately: true }); - if (this.props.parent.props.ContainingCollectionView) { - this._filterReactionDisposer = reaction( - () => this.props.parent.Document.filterScript, - () => { - runInAction(() => { - let scriptfield = Cast(this.props.parent.Document.filterScript, ScriptField); - this._script = scriptfield ? scriptfield.script : CompileScript("return true"); - if (this.props.parent.props.ContainingCollectionView) { - let fieldDoc = Doc.resolvedFieldDataDoc(this.props.parent.props.ContainingCollectionView.props.DataDoc ? - this.props.parent.props.ContainingCollectionView.props.DataDoc : this.props.parent.props.ContainingCollectionView.props.Document, this.props.parent.props.ContainingCollectionView.props.fieldKey, "true"); - let ccvAnnos = DocListCast(fieldDoc.annotations); - ccvAnnos.forEach(d => { - if (this._script && this._script.compiled) { - let run = this._script.run(d); - if (run.success) { - d.opacity = run.result ? 1 : 0; - } - } - }); - } - this.Index = -1; - }); - } - ); - } - - if (this._mainCont.current) { - this._dropDisposer = this._mainCont.current && DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }); - } + this._filterReactionDisposer = reaction( + () => ({ scriptField: Cast(this.props.Document.filterScript, ScriptField), annos: this._annotations.slice() }), + action(({ scriptField, annos }: { scriptField: FieldResult<ScriptField>, annos: Doc[] }) => { + this._script = scriptField && scriptField.script.compiled ? scriptField.script : CompileScript("return true") as CompiledScript; + annos.forEach(d => { + let run = this._script.run(d); + d.opacity = !run.success || run.result ? 1 : 0; + }); + this.Index = -1; + }), + { fireImmediately: true } + ); document.removeEventListener("copy", this.copy); document.addEventListener("copy", this.copy); @@ -171,162 +118,115 @@ export class Viewer extends React.Component<IViewerProps> { this._reactionDisposer && this._reactionDisposer(); this._annotationReactionDisposer && this._annotationReactionDisposer(); this._filterReactionDisposer && this._filterReactionDisposer(); - this._dropDisposer && this._dropDisposer(); document.removeEventListener("copy", this.copy); } - private copy = (e: ClipboardEvent) => { - if (this.props.parent.props.active()) { - let text = this._selectionText; - if (e.clipboardData) { - e.clipboardData.setData("text/plain", text); - e.clipboardData.setData("dash/pdfOrigin", this.props.parent.props.Document[Id]); - let annoDoc = this.makeAnnotationDocument(undefined, 0, "#0390fc"); - e.clipboardData.setData("dash/pdfRegion", annoDoc[Id]); - e.preventDefault(); - } + copy = (e: ClipboardEvent) => { + if (this.props.active() && e.clipboardData) { + e.clipboardData.setData("text/plain", this._selectionText); + e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]); + e.clipboardData.setData("dash/pdfRegion", this.makeAnnotationDocument(undefined, "#0390fc")[Id]); + e.preventDefault(); } - // let targetAnnotations = DocListCast(this.props.parent.fieldExtensionDoc.annotations); - // if (targetAnnotations) { - // targetAnnotations.push(destDoc); - // } } paste = (e: ClipboardEvent) => { - if (e.clipboardData) { - if (e.clipboardData.getData("dash/pdfOrigin") === this.props.parent.props.Document[Id]) { - let linkDocId = e.clipboardData.getData("dash/linkDoc"); - if (linkDocId) { - DocServer.GetRefField(linkDocId).then(async (link) => { - if (!(link instanceof Doc)) { - return; - } - let proto = Doc.GetProto(link); - let source = await Cast(proto.anchor1, Doc); - proto.anchor2 = this.makeAnnotationDocument(source, 0, "#0390fc", false); - }); - } - } + if (e.clipboardData && e.clipboardData.getData("dash/pdfOrigin") === this.props.Document[Id]) { + let linkDocId = e.clipboardData.getData("dash/linkDoc"); + linkDocId && DocServer.GetRefField(linkDocId).then(async (link) => + (link instanceof Doc) && (Doc.GetProto(link).anchor2 = this.makeAnnotationDocument(await Cast(Doc.GetProto(link), Doc), "#0390fc", false))); } } - scrollTo(y: number) { - if (this.props.mainCont.current) { - this.props.parent.scrollTo(y); - } - } + searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => this._searchString = e.currentTarget.value; + + pageLoaded = (page: Pdfjs.PDFPageViewport): void => this.props.loaded(page.width, page.height, this.props.pdf.numPages); + + setSelectionText = (text: string) => this._selectionText = text; + + getIndex = () => this.Index; @action initialLoad = async () => { if (this._pageSizes.length === 0) { - let pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages); this._isPage = Array<string>(this.props.pdf.numPages); - // this._textContent = Array<Pdfjs.TextContent>(this.props.pdf.numPages); - const proms: Pdfjs.PDFPromise<any>[] = []; - for (let i = 0; i < this.props.pdf.numPages; i++) { - proms.push(this.props.pdf.getPage(i + 1).then(page => runInAction(() => { - pageSizes[i] = { + this._pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages); + await Promise.all(this._pageSizes.map<Pdfjs.PDFPromise<any>>((val, i) => + this.props.pdf.getPage(i + 1).then(action((page: Pdfjs.PDFPageProxy) => { + this._pageSizes.splice(i, 1, { width: (page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]) * scale, height: (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0]) * scale - }; - // let x = page.getViewport(scale); - // page.getTextContent().then((text: Pdfjs.TextContent) => { - // // let tc = new Pdfjs.TextContentItem() - // // let tc = {str: } - // this._textContent[i] = text; - // // text.items.forEach(t => { - // // tcStr += t.str; - // // }) - // }); - // pageSizes[i] = { width: x.width, height: x.height }; - }))); - } - await Promise.all(proms); - runInAction(() => - Array.from(Array((this._pageSizes = pageSizes).length).keys()).map(this.getPlaceholderPage)); - this.props.loaded(Math.max(...pageSizes.map(i => i.width)), pageSizes[0].height, this.props.pdf.numPages); - // this.props.loaded(Math.max(...pageSizes.map(i => i.width)), pageSizes[0].height, this.props.pdf.numPages); - - let startY = NumCast(this.props.parent.Document.startY); - let ccv = this.props.parent.props.ContainingCollectionView; - if (ccv) { - ccv.props.Document.panY = startY; - } - this.props.parent.Document.scrollY = 0; - this.props.parent.Document.scrollY = startY + 1; + }); + this.getPlaceholderPage(i); + })))); + this.props.loaded(Math.max(...this._pageSizes.map(i => i.width)), this._pageSizes[0].height, this.props.pdf.numPages); + + let startY = NumCast(this.props.Document.startY, NumCast(this.props.Document.panY)); + this.props.setPanY && this.props.setPanY(startY); } } @action - makeAnnotationDocument = (sourceDoc: Doc | undefined, s: number, color: string, createLink: boolean = true): Doc => { - let annoDocs: Doc[] = []; + makeAnnotationDocument = (sourceDoc: Doc | undefined, color: string, createLink: boolean = true): Doc => { let mainAnnoDoc = Docs.Create.InstanceFromProto(new Doc(), "", {}); - - mainAnnoDoc.title = "Annotation on " + StrCast(this.props.parent.Document.title); - mainAnnoDoc.pdfDoc = this.props.parent.props.Document; + let mainAnnoDocProto = Doc.GetProto(mainAnnoDoc); + let annoDocs: Doc[] = []; let minY = Number.MAX_VALUE; - this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => { - for (let anno of value) { + if (this._savedAnnotations.size() === 1 && this._savedAnnotations.values()[0].length === 1 && !createLink) { + let anno = this._savedAnnotations.values()[0][0]; + let annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: "rgba(255, 0, 0, 0.1)", title: "Annotation on " + StrCast(this.props.Document.title) }); + if (anno.style.left) annoDoc.x = parseInt(anno.style.left); + if (anno.style.top) annoDoc.y = parseInt(anno.style.top); + if (anno.style.height) annoDoc.height = parseInt(anno.style.height); + if (anno.style.width) annoDoc.width = parseInt(anno.style.width); + annoDoc.target = sourceDoc; + annoDoc.group = mainAnnoDoc; + annoDoc.color = color; + annoDoc.type = AnnotationTypes.Region; + annoDocs.push(annoDoc); + annoDoc.isButton = true; + anno.remove(); + this.props.addDocument && this.props.addDocument(annoDoc, false); + mainAnnoDoc = annoDoc; + mainAnnoDocProto = Doc.GetProto(annoDoc); + } else { + this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => value.map(anno => { let annoDoc = new Doc(); - if (anno.style.left) annoDoc.x = parseInt(anno.style.left) / scale; - if (anno.style.top) { - annoDoc.y = parseInt(anno.style.top) / scale; - minY = Math.min(parseInt(anno.style.top), minY); - } - if (anno.style.height) annoDoc.height = parseInt(anno.style.height) / scale; - if (anno.style.width) annoDoc.width = parseInt(anno.style.width) / scale; - annoDoc.page = key; + if (anno.style.left) annoDoc.x = parseInt(anno.style.left); + if (anno.style.top) annoDoc.y = parseInt(anno.style.top); + if (anno.style.height) annoDoc.height = parseInt(anno.style.height); + if (anno.style.width) annoDoc.width = parseInt(anno.style.width); annoDoc.target = sourceDoc; annoDoc.group = mainAnnoDoc; annoDoc.color = color; annoDoc.type = AnnotationTypes.Region; annoDocs.push(annoDoc); anno.remove(); - } - }); + (annoDoc.y !== undefined) && (minY = Math.min(NumCast(annoDoc.y), minY)); + })); - mainAnnoDoc.y = Math.max(minY, 0); - mainAnnoDoc.annotations = new List<Doc>(annoDocs); + mainAnnoDocProto.y = Math.max(minY, 0); + mainAnnoDocProto.annotations = new List<Doc>(annoDocs); + } + mainAnnoDocProto.title = "Annotation on " + StrCast(this.props.Document.title); + mainAnnoDocProto.annotationOn = this.props.Document; if (sourceDoc && createLink) { - DocUtils.MakeLink(sourceDoc, mainAnnoDoc, undefined, `Annotation from ${StrCast(this.props.parent.Document.title)}`, "", StrCast(this.props.parent.Document.title)); + DocUtils.MakeLink(sourceDoc, mainAnnoDocProto, undefined, `Annotation from ${StrCast(this.props.Document.title)}`, "", StrCast(this.props.Document.title)); } this._savedAnnotations.clear(); this.Index = -1; return mainAnnoDoc; } - drop = async (e: Event, de: DragManager.DropEvent) => { - // if (de.data instanceof DragManager.LinkDragData) { - // let sourceDoc = de.data.linkSourceDocument; - // let destDoc = this.makeAnnotationDocument(sourceDoc, 1, "red"); - // de.data.droppedDocuments.push(destDoc); - // let targetAnnotations = DocListCast(this.props.parent.fieldExtensionDoc.annotations); - // if (targetAnnotations) { - // targetAnnotations.push(destDoc); - // } - // else { - // this.props.parent.fieldExtensionDoc.annotations = new List<Doc>([destDoc]); - // } - // e.stopPropagation(); - // } - } - /** - * Called by the Page class when it gets rendered, initializes the lists and - * puts a placeholder with all of the correct page sizes when all of the pages have been loaded. - */ - @action - pageLoaded = (index: number, page: Pdfjs.PDFPageViewport): void => { - this.props.loaded(page.width, page.height, this.props.pdf.numPages); - } - @action getPlaceholderPage = (page: number) => { if (this._isPage[page] !== "none") { this._isPage[page] = "none"; this._visibleElements[page] = ( <div key={`${this.props.url}-placeholder-${page + 1}`} className="pdfviewer-placeholder" - style={{ width: this._pageSizes[page].width, height: this._pageSizes[page].height }} /> - ); + style={{ width: this._pageSizes[page].width, height: this._pageSizes[page].height }}> + "PAGE IS LOADING... " + </div>); } } @@ -334,25 +234,19 @@ export class Viewer extends React.Component<IViewerProps> { getRenderedPage = (page: number) => { if (this._isPage[page] !== "page") { this._isPage[page] = "page"; - this._visibleElements[page] = ( - <Page - setSelectionText={this.setSelectionText} - size={this._pageSizes[page]} - pdf={this.props.pdf} - page={page} - numPages={this.props.pdf.numPages} - key={`${this.props.url}-rendered-${page + 1}`} - name={`${this.props.pdf.fingerprint + `-page${page + 1}`}`} - pageLoaded={this.pageLoaded} - parent={this.props.parent} - makePin={emptyFunction} - renderAnnotations={this.renderAnnotations} - createAnnotation={this.createAnnotation} - sendAnnotations={this.receiveAnnotations} - makeAnnotationDocuments={this.makeAnnotationDocument} - getScrollFromPage={this.getScrollFromPage} - {...this.props} /> - ); + this._visibleElements[page] = (<Page {...this.props} + size={this._pageSizes[page]} + numPages={this.props.pdf.numPages} + setSelectionText={this.setSelectionText} + page={page} + key={`${this.props.url}-rendered-${page + 1}`} + name={`${this.props.pdf.fingerprint + `-page${page + 1}`}`} + pageLoaded={this.pageLoaded} + renderAnnotations={this.renderAnnotations} + createAnnotation={this.createAnnotation} + sendAnnotations={this.receiveAnnotations} + makeAnnotationDocuments={this.makeAnnotationDocument} + getScrollFromPage={this.getScrollFromPage} />); } } @@ -360,14 +254,12 @@ export class Viewer extends React.Component<IViewerProps> { // file address of the pdf @action getPageImage = async (page: number) => { - let handleError = () => this.getRenderedPage(page); if (this._isPage[page] !== "image") { this._isPage[page] = "image"; - const address = this.props.url; try { - let res = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${address.substring("files/".length, address.length - ".pdf".length)}-${page + 1}.PNG`))); + let res = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${this.props.url.substring("files/".length, this.props.url.length - ".pdf".length)}-${page + 1}.PNG`))); runInAction(() => this._visibleElements[page] = - <img key={res.path} src={res.path} onError={handleError} + <img key={res.path} src={res.path} onError={() => this.getRenderedPage(page)} style={{ width: `${parseInt(res.width) * scale}px`, height: `${parseInt(res.height) * scale}px` }} />); } catch (e) { console.log(e); @@ -375,33 +267,14 @@ export class Viewer extends React.Component<IViewerProps> { } } - @computed get scrollY(): number { return this.props.scrollY; } - - // startIndex: where to start rendering pages - @computed get startIndex(): number { return Math.max(0, this.getPageFromScroll(this.scrollY) - this._pageBuffer); } - - // endIndex: where to end rendering pages - @computed get endIndex(): number { - return Math.min(this.props.pdf.numPages - 1, this.getPageFromScroll(this.scrollY + this._pageSizes[0].height) + this._pageBuffer); - } - - @action renderPages = () => { - for (let i = 0; i < this.props.pdf.numPages; i++) { - if (i < this.startIndex || i > this.endIndex) { - this.getPlaceholderPage(i); // pages outside of the pdf use empty stand-in divs - } else { - if (this.props.parent.props.active()) { - this.getRenderedPage(i); - } else { - this.getPageImage(i); - } - } - } + numberRange(this.props.pdf.numPages).filter(p => this._isPage[p] !== undefined).map(i => + (i < this.startIndex || i > this.endIndex) ? this.getPlaceholderPage(i) : // pages outside of the pdf use empty stand-in divs + this.props.active() ? this.getRenderedPage(i) : this.getPageImage(i)); } @action - private renderAnnotations = (annotations: Doc[], removeOldAnnotations: boolean): void => { + renderAnnotations = (annotations: Doc[], removeOldAnnotations: boolean): void => { if (removeOldAnnotations) { this._annotations = annotations; } @@ -412,6 +285,21 @@ export class Viewer extends React.Component<IViewerProps> { } @action + prevAnnotation = (e: React.MouseEvent) => { + e.stopPropagation(); + this.Index = Math.max(this.Index - 1, 0); + } + + @action + nextAnnotation = (e: React.MouseEvent) => { + e.stopPropagation(); + this.Index = Math.min(this.Index + 1, this.filteredAnnotations.length - 1); + } + + sendAnnotations = (page: number) => { + return this._savedAnnotations.getValue(page); + } + receiveAnnotations = (annotations: HTMLDivElement[], page: number) => { if (page === -1) { this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); @@ -422,28 +310,21 @@ export class Viewer extends React.Component<IViewerProps> { } } - sendAnnotations = (page: number): HTMLDivElement[] | undefined => { - return this._savedAnnotations.getValue(page); - } - // get the page index that the vertical offset passed in is on getPageFromScroll = (vOffset: number) => { let index = 0; let currOffset = vOffset; - while (index < this._pageSizes.length && currOffset - this._pageSizes[index].height > 0) { + while (index < this._pageSizes.length && this._pageSizes[index] && currOffset - this._pageSizes[index].height > 0) { currOffset -= this._pageSizes[index++].height; } return index; } getScrollFromPage = (index: number): number => { - let counter = 0; - for (let i = 0; i < Math.min(this.props.pdf.numPages, index); i++) { - counter += this._pageSizes[i].height; - } - return counter; + return numberRange(Math.min(this.props.pdf.numPages, index)).reduce((counter, i) => counter + this._pageSizes[i].height, 0); } + @action createAnnotation = (div: HTMLDivElement, page: number) => { if (this._annotationLayer.current) { if (div.style.top) { @@ -461,101 +342,30 @@ export class Viewer extends React.Component<IViewerProps> { } } - renderAnnotation = (anno: Doc, index: number): JSX.Element => { - return <Annotation anno={anno} index={index} parent={this} key={`${anno[Id]}-annotation`} />; - } - - @action - pointerDown = () => { - // this._searching = false; - } - @action search = (searchString: string) => { if (this._pdfViewer._pageViewsReady) { - this._pdfFindController.executeCommand('find', - { - caseSensitive: false, - findPrevious: undefined, - highlightAll: true, - phraseSearch: true, - query: searchString - }); + this._pdfFindController.executeCommand('find', { + caseSensitive: false, + findPrevious: undefined, + highlightAll: true, + phraseSearch: true, + query: searchString + }); } - else { - let container = this._mainCont.current; - if (container) { - container.addEventListener("pagesloaded", () => { - console.log("rendered"); - this._pdfFindController.executeCommand('find', - { - caseSensitive: false, - findPrevious: undefined, - highlightAll: true, - phraseSearch: true, - query: searchString - }); - }); - container.addEventListener("pagerendered", () => { - console.log("rendered"); - this._pdfFindController.executeCommand('find', - { - caseSensitive: false, - findPrevious: undefined, - highlightAll: true, - phraseSearch: true, - query: searchString - }); - }); - } + else if (this._mainCont.current) { + let executeFind = () => this._pdfFindController.executeCommand('find', { + caseSensitive: false, + findPrevious: undefined, + highlightAll: true, + phraseSearch: true, + query: searchString + }); + this._mainCont.current.addEventListener("pagesloaded", executeFind); + this._mainCont.current.addEventListener("pagerendered", executeFind); } - - // let viewer = this._viewer.current; - - // if (!this._pdfFindController) { - // if (container && viewer) { - // let simpleLinkService = new SimpleLinkService(); - // let pdfViewer = new PDFJSViewer.PDFViewer({ - // container: container, - // viewer: viewer, - // linkService: simpleLinkService - // }); - // simpleLinkService.setPdf(this.props.pdf); - // container.addEventListener("pagesinit", () => { - // pdfViewer.currentScaleValue = 1; - // }); - // container.addEventListener("pagerendered", () => { - // console.log("rendered"); - // this._pdfFindController.executeCommand('find', - // { - // caseSensitive: false, - // findPrevious: undefined, - // highlightAll: true, - // phraseSearch: true, - // query: searchString - // }); - // }); - // pdfViewer.setDocument(this.props.pdf); - // this._pdfFindController = new PDFJSViewer.PDFFindController(pdfViewer); - // // this._pdfFindController._linkService = pdfLinkService; - // pdfViewer.findController = this._pdfFindController; - // } - // } - // else { - // this._pdfFindController.executeCommand('find', - // { - // caseSensitive: false, - // findPrevious: undefined, - // highlightAll: true, - // phraseSearch: true, - // query: searchString - // }); - // } } - searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => { - this._searchString = e.currentTarget.value; - } @action toggleSearch = (e: React.MouseEvent) => { @@ -563,29 +373,19 @@ export class Viewer extends React.Component<IViewerProps> { this._searching = !this._searching; if (this._searching) { - let container = this._mainCont.current; - let viewer = this._viewer.current; - - if (!this._pdfFindController) { - if (container && viewer) { - let simpleLinkService = new SimpleLinkService(); - this._pdfViewer = new PDFJSViewer.PDFViewer({ - container: container, - viewer: viewer, - linkService: simpleLinkService - }); - simpleLinkService.setPdf(this.props.pdf); - container.addEventListener("pagesinit", () => { - this._pdfViewer.currentScaleValue = 1; - }); - container.addEventListener("pagerendered", () => { - console.log("rendered"); - }); - this._pdfViewer.setDocument(this.props.pdf); - this._pdfFindController = new PDFJSViewer.PDFFindController(this._pdfViewer); - // this._pdfFindController._linkService = pdfLinkService; - this._pdfViewer.findController = this._pdfFindController; - } + if (!this._pdfFindController && this._mainCont.current && this._viewer.current) { + let simpleLinkService = new SimpleLinkService(); + this._pdfViewer = new PDFJSViewer.PDFViewer({ + container: this._mainCont.current, + viewer: this._viewer.current, + linkService: simpleLinkService + }); + simpleLinkService.setPdf(this.props.pdf); + this._mainCont.current.addEventListener("pagesinit", () => this._pdfViewer.currentScaleValue = 1); + this._mainCont.current.addEventListener("pagerendered", () => console.log("rendered")); + this._pdfViewer.setDocument(this.props.pdf); + this._pdfFindController = new PDFJSViewer.PDFFindController(this._pdfViewer); + this._pdfViewer.findController = this._pdfFindController; } } else { @@ -599,116 +399,45 @@ export class Viewer extends React.Component<IViewerProps> { } } - @action - prevAnnotation = (e: React.MouseEvent) => { - e.stopPropagation(); - - // if (this.Index > 0) { - // this.Index--; - // } - this.Index = Math.max(this.Index - 1, 0); - } - - @action - nextAnnotation = (e: React.MouseEvent) => { - e.stopPropagation(); - - let compiled = this._script; - let filtered = this._annotations.filter(anno => { - if (compiled && compiled.compiled) { - let run = compiled.run({ this: anno }); - if (run.success) { - return run.result; - } - } - return true; - }); - this.Index = Math.min(this.Index + 1, filtered.length - 1); - } - - nextResult = () => { - // if (this._viewer.current) { - // let results = this._pdfFindController.pageMatches; - // if (results && results.length) { - // if (this._pageIndex === this.props.pdf.numPages && this._matchIndex === results[this._pageIndex].length - 1) { - // return; - // } - // if (this._pageIndex === -1 || this._matchIndex === results[this._pageIndex].length - 1) { - // this._matchIndex = 0; - // this._pageIndex++; - // } - // else { - // this._matchIndex++; - // } - // this._pdfFindController._nextMatch() - // let nextMatch = this._viewer.current.children[this._pageIndex].children[1].children[results[this._pageIndex][this._matchIndex]]; - // rconsole.log(nextMatch); - // this.props.parent.scrollTo(nextMatch.getBoundingClientRect().top); - // nextMatch.setAttribute("style", nextMatch.getAttribute("style") ? nextMatch.getAttribute("style") + ", background-color: green" : "background-color: green"); - // } - // } - } - render() { - let compiled = this._script; - return ( - <div className="pdfViewer-viewer" ref={this._mainCont} onPointerDown={this.pointerDown}> - <div className="viewer" style={this._searching ? { position: "absolute", top: 0 } : {}}> - {this._visibleElements} - </div> - <div className="pdfViewer-text" ref={this._viewer} /> - <div className="pdfViewer-annotationLayer" style={{ height: this.props.parent.Document.nativeHeight }}> - <div className="pdfViewer-annotationLayer-subCont" ref={this._annotationLayer}> - {this._annotations.filter(anno => { - if (compiled && compiled.compiled) { - let run = compiled.run({ this: anno }); - if (run.success) { - return run.result; - } - } - return true; - }).sort((a: Doc, b: Doc) => NumCast(a.y) - NumCast(b.y)) - .map((anno: Doc, index: number) => this.renderAnnotation(anno, index))} - </div> - </div> - <div className="pdfViewer-overlayCont" onPointerDown={(e) => e.stopPropagation()} - style={{ - bottom: -this.props.scrollY, - left: `${this._searching ? 0 : 100}%` - }}> - <button className="pdfViewer-overlayButton" title="Open Search Bar"></button> - {/* <button title="Previous Result" onClick={() => this.search(this._searchString)}><FontAwesomeIcon icon="arrow-up" size="3x" color="white" /></button> - <button title="Next Result" onClick={this.nextResult}><FontAwesomeIcon icon="arrow-down" size="3x" color="white" /></button> */} - <input onKeyDown={(e: React.KeyboardEvent) => e.keyCode === KeyCodes.ENTER ? this.search(this._searchString) : e.keyCode === KeyCodes.BACKSPACE ? e.stopPropagation() : true} placeholder="Search" className="pdfViewer-overlaySearchBar" onChange={this.searchStringChanged} /> - <button title="Search" onClick={() => this.search(this._searchString)}><FontAwesomeIcon icon="search" size="3x" color="white" /></button> - </div> - <button className="pdfViewer-overlayButton" onClick={this.prevAnnotation} title="Previous Annotation" - style={{ bottom: -this.props.scrollY + 280, right: 10, display: this.props.parent.props.active() ? "flex" : "none" }}> - <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}> - <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-up"} size="3x" /> - </div> - </button> - <button className="pdfViewer-overlayButton" onClick={this.nextAnnotation} title="Next Annotation" - style={{ bottom: -this.props.scrollY + 200, right: 10, display: this.props.parent.props.active() ? "flex" : "none" }}> - <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}> - <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-down"} size="3x" /> - </div> - </button> - <button className="pdfViewer-overlayButton" onClick={this.toggleSearch} title="Open Search Bar" - style={{ bottom: -this.props.scrollY + 10, right: 0, display: this.props.parent.props.active() ? "flex" : "none" }}> - <div className="pdfViewer-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()}></div> - <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}> - <FontAwesomeIcon style={{ color: "white" }} icon={this._searching ? "times" : "search"} size="3x" /> - </div> - </button> - </div > - ); + return (<div className="pdfViewer-viewer" ref={this._mainCont} > + <div className="pdfViewer-visibleElements" style={this._searching ? { position: "absolute", top: 0 } : {}}> + {this._visibleElements} + </div> + <div className="pdfViewer-text" ref={this._viewer} /> + <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.props.Document.nativeHeight) }} ref={this._annotationLayer}> + {this.filteredAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) => + <Annotation {...this.props} ParentIndex={this.getIndex} anno={anno} index={index} key={`${anno[Id]}-annotation`} />)} + </div> + <div className="pdfViewer-overlayCont" onPointerDown={(e) => e.stopPropagation()} + style={{ bottom: -this.props.panY, left: `${this._searching ? 0 : 100}%` }}> + <button className="pdfViewer-overlayButton" title="Open Search Bar" /> + <input className="pdfViewer-overlaySearchBar" placeholder="Search" onChange={this.searchStringChanged} + onKeyDown={(e: React.KeyboardEvent) => e.keyCode === KeyCodes.ENTER ? this.search(this._searchString) : e.keyCode === KeyCodes.BACKSPACE ? e.stopPropagation() : true} /> + <button title="Search" onClick={() => this.search(this._searchString)}> + <FontAwesomeIcon icon="search" size="3x" color="white" /></button> + </div> + <button className="pdfViewer-overlayButton" onClick={this.prevAnnotation} title="Previous Annotation" + style={{ bottom: -this.props.panY + 280, right: 10, display: this.props.active() ? "flex" : "none" }}> + <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}> + <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-up"} size="3x" /></div> + </button> + <button className="pdfViewer-overlayButton" onClick={this.nextAnnotation} title="Next Annotation" + style={{ bottom: -this.props.panY + 200, right: 10, display: this.props.active() ? "flex" : "none" }}> + <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}> + <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-down"} size="3x" /></div> + </button> + <button className="pdfViewer-overlayButton" onClick={this.toggleSearch} title="Open Search Bar" + style={{ bottom: -this.props.panY + 10, right: 0, display: this.props.active() ? "flex" : "none" }}> + <div className="pdfViewer-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()}></div> + <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}> + <FontAwesomeIcon style={{ color: "white" }} icon={this._searching ? "times" : "search"} size="3x" /></div> + </button> + </div >); } } -export enum AnnotationTypes { - Region -} +export enum AnnotationTypes { Region } class SimpleLinkService { externalLinkTarget: any = null; diff --git a/src/client/views/pdf/Page.scss b/src/client/views/pdf/Page.scss new file mode 100644 index 000000000..af1628a6f --- /dev/null +++ b/src/client/views/pdf/Page.scss @@ -0,0 +1,31 @@ + +.pdfPage-cont { + position: relative; + + .pdfPage-canvasContainer { + position: absolute; + } + + .pdfPage-dragAnnotationBox { + position: absolute; + background-color: transparent; + opacity: 0.1; + } + + .pdfPage-textLayer { + position: absolute; + width: 100%; + height: 100%; + div { + user-select: text; + } + span { + color: transparent; + position: absolute; + white-space: pre; + cursor: text; + -webkit-transform-origin: 0% 0%; + transform-origin: 0% 0%; + } + } +}
\ No newline at end of file diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index dea4e0da1..6bd98cbaa 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -3,38 +3,35 @@ import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; import { Doc, DocListCastAsync, Opt, WidthSym } from "../../../new_fields/Doc"; -import { List } from "../../../new_fields/List"; -import { listSpec } from "../../../new_fields/Schema"; -import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { Docs, DocUtils } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; -import { PDFBox } from "../nodes/PDFBox"; import PDFMenu from "./PDFMenu"; import { scale } from "./PDFViewer"; -import "./PDFViewer.scss"; +import "./Page.scss"; import React = require("react"); interface IPageProps { size: { width: number, height: number }; - pdf: Opt<Pdfjs.PDFDocumentProxy>; + pdf: Pdfjs.PDFDocumentProxy; name: string; numPages: number; page: number; - pageLoaded: (index: number, page: Pdfjs.PDFPageViewport) => void; - parent: PDFBox; + pageLoaded: (page: Pdfjs.PDFPageViewport) => void; + fieldExtensionDoc: Doc, + Document: Doc, renderAnnotations: (annotations: Doc[], removeOld: boolean) => void; - makePin: (x: number, y: number, page: number) => void; sendAnnotations: (annotations: HTMLDivElement[], page: number) => void; createAnnotation: (div: HTMLDivElement, page: number) => void; - makeAnnotationDocuments: (doc: Doc | undefined, scale: number, color: string, linkTo: boolean) => Doc; + makeAnnotationDocuments: (doc: Doc | undefined, color: string, linkTo: boolean) => Doc; getScrollFromPage: (page: number) => number; setSelectionText: (text: string) => void; } @observer export default class Page extends React.Component<IPageProps> { - @observable private _state: string = "N/A"; + @observable private _state: "N/A" | "rendering" = "N/A"; @observable private _width: number = this.props.size.width; @observable private _height: number = this.props.size.height; @observable private _page: Opt<Pdfjs.PDFPageProxy>; @@ -43,90 +40,44 @@ export default class Page extends React.Component<IPageProps> { @observable private _marqueeY: number = 0; @observable private _marqueeWidth: number = 0; @observable private _marqueeHeight: number = 0; - @observable private _rotate: string = ""; - private _canvas: React.RefObject<HTMLCanvasElement>; - private _textLayer: React.RefObject<HTMLDivElement>; - private _annotationLayer: React.RefObject<HTMLDivElement>; - private _marquee: React.RefObject<HTMLDivElement>; - // private _curly: React.RefObject<HTMLImageElement>; + private _canvas: React.RefObject<HTMLCanvasElement> = React.createRef(); + private _textLayer: React.RefObject<HTMLDivElement> = React.createRef(); + private _marquee: React.RefObject<HTMLDivElement> = React.createRef(); private _marqueeing: boolean = false; private _reactionDisposer?: IReactionDisposer; private _startY: number = 0; private _startX: number = 0; - constructor(props: IPageProps) { - super(props); - this._canvas = React.createRef(); - this._textLayer = React.createRef(); - this._annotationLayer = React.createRef(); - this._marquee = React.createRef(); - // this._curly = React.createRef(); - } + componentDidMount = (): void => this.loadPage(this.props.pdf); - componentDidMount = (): void => { - if (this.props.pdf) { - this.update(this.props.pdf); - } - } + componentDidUpdate = (): void => this.loadPage(this.props.pdf); - componentWillUnmount = (): void => { - if (this._reactionDisposer) { - this._reactionDisposer(); - } - } + componentWillUnmount = (): void => this._reactionDisposer && this._reactionDisposer(); - componentDidUpdate = (): void => { - if (this.props.pdf) { - this.update(this.props.pdf); - } - } - - private update = (pdf: Pdfjs.PDFDocumentProxy): void => { - if (pdf) { - this.loadPage(pdf); - } - else { - this._state = "loading"; - } - } - - private loadPage = (pdf: Pdfjs.PDFDocumentProxy): void => { - if (this._state === "rendering" || this._page) return; - - pdf.getPage(this._currPage).then( - (page: Pdfjs.PDFPageProxy): void => { - this._state = "rendering"; - this.renderPage(page); - } - ); + loadPage = (pdf: Pdfjs.PDFDocumentProxy): void => { + pdf.getPage(this._currPage).then(page => this.renderPage(page)); } @action - private renderPage = (page: Pdfjs.PDFPageProxy): void => { + renderPage = (page: Pdfjs.PDFPageProxy): void => { // lower scale = easier to read at small sizes, higher scale = easier to read at large sizes - let viewport = page.getViewport(scale); - let canvas = this._canvas.current; - let textLayer = this._textLayer.current; - if (canvas && textLayer) { - let ctx = canvas.getContext("2d"); - canvas.width = viewport.width; - this._width = viewport.width; - canvas.height = viewport.height; - this._height = viewport.height; - this.props.pageLoaded(this._currPage, viewport); + if (this._state !== "rendering" && !this._page && this._canvas.current && this._textLayer.current) { + this._state = "rendering"; + let viewport = page.getViewport(scale); + this._canvas.current.width = this._width = viewport.width; + this._canvas.current.height = this._height = viewport.height; + this.props.pageLoaded(viewport); + let ctx = this._canvas.current.getContext("2d"); if (ctx) { - // renders the page onto the canvas context - page.render({ canvasContext: ctx, viewport: viewport }); - // renders text onto the text container - page.getTextContent().then((res: Pdfjs.TextContent): void => { + page.render({ canvasContext: ctx, viewport: viewport }); // renders the page onto the canvas context + page.getTextContent().then(res => // renders text onto the text container //@ts-ignore Pdfjs.renderTextLayer({ textContent: res, - container: textLayer, + container: this._textLayer.current, viewport: viewport - }); - }); + })); this._page = page; } @@ -134,15 +85,10 @@ export default class Page extends React.Component<IPageProps> { } @action - highlight = (targetDoc?: Doc, color: string = "red") => { + highlight = (targetDoc: Doc | undefined, color: string) => { // creates annotation documents for current highlights - let annotationDoc = this.props.makeAnnotationDocuments(targetDoc, scale, color, false); - let targetAnnotations = Cast(this.props.parent.fieldExtensionDoc.annotations, listSpec(Doc)); - if (targetAnnotations === undefined) { - Doc.GetProto(this.props.parent.fieldExtensionDoc).annotations = new List([annotationDoc]); - } else { - targetAnnotations.push(annotationDoc); - } + let annotationDoc = this.props.makeAnnotationDocuments(targetDoc, color, false); + Doc.AddDocToList(this.props.fieldExtensionDoc, "annotations", annotationDoc); return annotationDoc; } @@ -154,26 +100,20 @@ export default class Page extends React.Component<IPageProps> { startDrag = (e: PointerEvent, ele: HTMLElement): void => { e.preventDefault(); e.stopPropagation(); - let thisDoc = this.props.parent.Document; - // document that this annotation is linked to - let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "New Annotation" }); - targetDoc.targetPage = this.props.page; - let annotationDoc = this.highlight(undefined, "red"); - Doc.GetProto(annotationDoc).annotationOn = thisDoc; - annotationDoc.linkedToDoc = false; - // create dragData and star tdrag - let dragData = new DragManager.AnnotationDragData(thisDoc, annotationDoc, targetDoc); if (this._textLayer.current) { + let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "New Annotation" }); + targetDoc.targetPage = this.props.page; + let annotationDoc = this.highlight(undefined, "red"); + annotationDoc.linkedToDoc = false; + let dragData = new DragManager.AnnotationDragData(this.props.Document, annotationDoc, targetDoc); DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, { handlers: { dragComplete: async () => { if (!BoolCast(annotationDoc.linkedToDoc)) { let annotations = await DocListCastAsync(annotationDoc.annotations); annotations && annotations.forEach(anno => anno.target = targetDoc); - let pdfDoc = await Cast(annotationDoc.pdfDoc, Doc); - if (pdfDoc) { - DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title)); - } + let parentDoc = await Cast(annotationDoc.annotationOn, Doc); + parentDoc && DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(parentDoc.title)}`, "", StrCast(parentDoc.title)) } } }, @@ -184,57 +124,44 @@ export default class Page extends React.Component<IPageProps> { // cleans up events and boolean endDrag = (e: PointerEvent): void => { - // document.removeEventListener("pointermove", this.startDrag); - // document.removeEventListener("pointerup", this.endDrag); e.stopPropagation(); } createSnippet = (marquee: { left: number, top: number, width: number, height: number }): void => { - let doc = this.props.parent.Document; - let view = Doc.MakeAlias(doc); - let data = Doc.MakeDelegate(doc.proto!); + let view = Doc.MakeAlias(this.props.Document); + let data = Doc.MakeDelegate(Doc.GetProto(this.props.Document)); data.title = StrCast(data.title) + "_snippet"; view.proto = data; view.nativeHeight = marquee.height; - view.height = (doc[WidthSym]() / NumCast(doc.nativeWidth)) * marquee.height; - view.nativeWidth = doc.nativeWidth; + view.height = (this.props.Document[WidthSym]() / NumCast(this.props.Document.nativeWidth)) * marquee.height; + view.nativeWidth = this.props.Document.nativeWidth; view.startY = marquee.top + this.props.getScrollFromPage(this.props.page); - view.width = doc[WidthSym](); - let dragData = new DragManager.DocumentDragData([view], [undefined]); - DragManager.StartDocumentDrag([], dragData, 0, 0); + view.width = this.props.Document[WidthSym](); + DragManager.StartDocumentDrag([], new DragManager.DocumentDragData([view], [undefined]), 0, 0); } @action onPointerDown = (e: React.PointerEvent): void => { // if alt+left click, drag and annotate - if (e.altKey && e.button === 0) { - e.stopPropagation(); - - // document.removeEventListener("pointermove", this.startDrag); - // document.addEventListener("pointermove", this.startDrag); - // document.removeEventListener("pointerup", this.endDrag); - // document.addEventListener("pointerup", this.endDrag); - } - else if (e.button === 0) { + if (NumCast(this.props.Document.scale, 1) !== 1) return; + if (!e.altKey && e.button === 0) { PDFMenu.Instance.StartDrag = this.startDrag; PDFMenu.Instance.Highlight = this.highlight; PDFMenu.Instance.Snippet = this.createSnippet; PDFMenu.Instance.Status = "pdf"; PDFMenu.Instance.fadeOut(true); - let target: any = e.target; - if (target && target.parentElement === this._textLayer.current) { + if (e.target && (e.target as any).parentElement === this._textLayer.current) { e.stopPropagation(); } else { // set marquee x and y positions to the spatially transformed position - let current = this._textLayer.current; - if (current) { - let boundingRect = current.getBoundingClientRect(); - this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (current.offsetWidth / boundingRect.width); - this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (current.offsetHeight / boundingRect.height); + if (this._textLayer.current) { + let boundingRect = this._textLayer.current.getBoundingClientRect(); + this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (this._textLayer.current.offsetWidth / boundingRect.width); + this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (this._textLayer.current.offsetHeight / boundingRect.height); } this._marqueeing = true; - if (this._marquee.current) this._marquee.current.style.opacity = "0.2"; + this._marquee.current && (this._marquee.current.style.opacity = "0.2"); } document.removeEventListener("pointermove", this.onSelectStart); document.addEventListener("pointermove", this.onSelectStart); @@ -248,97 +175,41 @@ export default class Page extends React.Component<IPageProps> { @action onSelectStart = (e: PointerEvent): void => { - let target: any = e.target; - if (this._marqueeing) { - let current = this._textLayer.current; - if (current) { - // transform positions and find the width and height to set the marquee to - let boundingRect = current.getBoundingClientRect(); - this._marqueeWidth = ((e.clientX - boundingRect.left) * (current.offsetWidth / boundingRect.width)) - this._startX; - this._marqueeHeight = ((e.clientY - boundingRect.top) * (current.offsetHeight / boundingRect.height)) - this._startY; - this._marqueeX = Math.min(this._startX, this._startX + this._marqueeWidth); - this._marqueeY = Math.min(this._startY, this._startY + this._marqueeHeight); - this._marqueeWidth = Math.abs(this._marqueeWidth); - this._marqueeHeight = Math.abs(this._marqueeHeight); - let { background, opacity, transform: transform } = this.getCurlyTransform(); - if (this._marquee.current /*&& this._curly.current*/) { - this._marquee.current.style.background = background; - // this._curly.current.style.opacity = opacity; - this._rotate = transform; - } - } + if (this._marqueeing && this._textLayer.current) { + // transform positions and find the width and height to set the marquee to + let boundingRect = this._textLayer.current.getBoundingClientRect(); + this._marqueeWidth = ((e.clientX - boundingRect.left) * (this._textLayer.current.offsetWidth / boundingRect.width)) - this._startX; + this._marqueeHeight = ((e.clientY - boundingRect.top) * (this._textLayer.current.offsetHeight / boundingRect.height)) - this._startY; + this._marqueeX = Math.min(this._startX, this._startX + this._marqueeWidth); + this._marqueeY = Math.min(this._startY, this._startY + this._marqueeHeight); + this._marqueeWidth = Math.abs(this._marqueeWidth); e.stopPropagation(); e.preventDefault(); } - else if (target && target.parentElement === this._textLayer.current) { + else if (e.target && (e.target as any).parentElement === this._textLayer.current) { e.stopPropagation(); } } - getCurlyTransform = (): { background: string, opacity: string, transform: string } => { - // let background = "", opacity = "", transform = ""; - // if (this._marquee.current && this._curly.current) { - // if (this._marqueeWidth > 100 && this._marqueeHeight > 100) { - // background = "red"; - // opacity = "0"; - // } - // else { - // background = "transparent"; - // opacity = "1"; - // } - - // // split up for simplicity, could be done in a nested ternary. please do not. -syip2 - // let ratio = this._marqueeWidth / this._marqueeHeight; - // if (ratio > 1.5) { - // // vertical - // transform = "rotate(90deg) scale(1, 5)"; - // } - // else if (ratio < 0.5) { - // // horizontal - // transform = "scale(2, 1)"; - // } - // else { - // // diagonal - // transform = "rotate(45deg) scale(1.5, 1.5)"; - // } - // } - return { background: "red", opacity: "0.5", transform: "" }; - } - @action onSelectEnd = (e: PointerEvent): void => { if (this._marqueeing) { this._marqueeing = false; - if (this._marquee.current) { - let copy = document.createElement("div"); - // make a copy of the marquee - let style = this._marquee.current.style; - copy.style.left = style.left; - copy.style.top = style.top; - copy.style.width = style.width; - copy.style.height = style.height; - - // apply the appropriate background, opacity, and transform - let { background, opacity, transform } = this.getCurlyTransform(); - copy.style.background = background; - // if curly bracing, add a curly brace - // if (opacity === "1" && this._curly.current) { - // copy.style.opacity = opacity; - // let img = this._curly.current.cloneNode(); - // (img as any).style.opacity = opacity; - // (img as any).style.transform = transform; - // copy.appendChild(img); - // } - // else { - copy.style.border = style.border; - copy.style.opacity = style.opacity; - // } - copy.className = this._marquee.current.className; - this.props.createAnnotation(copy, this.props.page); - this._marquee.current.style.opacity = "0"; - } - if (this._marqueeWidth > 10 || this._marqueeHeight > 10) { + if (this._marquee.current) { // make a copy of the marquee + let copy = document.createElement("div"); + let style = this._marquee.current.style; + copy.style.left = style.left; + copy.style.top = style.top; + copy.style.width = style.width; + copy.style.height = style.height; + copy.style.border = style.border; + copy.style.opacity = style.opacity; + copy.className = "pdfPage-annotationBox"; + this.props.createAnnotation(copy, this.props.page); + this._marquee.current.style.opacity = "0"; + } + if (!e.ctrlKey) { PDFMenu.Instance.Status = "snippet"; PDFMenu.Instance.Marquee = { left: this._marqueeX, top: this._marqueeY, width: this._marqueeWidth, height: this._marqueeHeight }; @@ -357,7 +228,6 @@ export default class Page extends React.Component<IPageProps> { } } - if (PDFMenu.Instance.Highlighting) { this.highlight(undefined, "goldenrod"); } @@ -371,14 +241,14 @@ export default class Page extends React.Component<IPageProps> { @action createTextAnnotation = (sel: Selection, selRange: Range) => { - let clientRects = selRange.getClientRects(); if (this._textLayer.current) { let boundingRect = this._textLayer.current.getBoundingClientRect(); + let clientRects = selRange.getClientRects(); for (let i = 0; i < clientRects.length; i++) { let rect = clientRects.item(i); if (rect && rect.width !== this._textLayer.current.getBoundingClientRect().width && rect.height !== this._textLayer.current.getBoundingClientRect().height) { let annoBox = document.createElement("div"); - annoBox.className = "pdfViewer-annotationBox"; + annoBox.className = "pdfPage-annotationBox"; // transforms the positions from screen onto the pdf div annoBox.style.top = ((rect.top - boundingRect.top) * (this._textLayer.current.offsetHeight / boundingRect.height)).toString(); annoBox.style.left = ((rect.left - boundingRect.left) * (this._textLayer.current.offsetWidth / boundingRect.width)).toString(); @@ -389,9 +259,8 @@ export default class Page extends React.Component<IPageProps> { } } let text = selRange.extractContents().textContent; - if (text) { - this.props.setSelectionText(text); - } + text && this.props.setSelectionText(text); + // clear selection if (sel.empty) { // Chrome sel.empty(); @@ -401,35 +270,23 @@ export default class Page extends React.Component<IPageProps> { } doubleClick = (e: React.MouseEvent) => { - let target: any = e.target; - // if double clicking text - if (target && target.parentElement === this._textLayer.current) { + if (e.target && (e.target as any).parentElement === this._textLayer.current) { // do something to select the paragraph ideally } - - let current = this._textLayer.current; - if (current) { - let boundingRect = current.getBoundingClientRect(); - let x = (e.clientX - boundingRect.left) * (current.offsetWidth / boundingRect.width); - let y = (e.clientY - boundingRect.top) * (current.offsetHeight / boundingRect.height); - this.props.makePin(x, y, this.props.page); - } } render() { return ( - <div onPointerDown={this.onPointerDown} onDoubleClick={this.doubleClick} className={"page-cont"} style={{ "width": this._width, "height": this._height }}> - <div className="canvasContainer"> - <canvas ref={this._canvas} /> + <div className={"pdfPage-cont"} onPointerDown={this.onPointerDown} onDoubleClick={this.doubleClick} style={{ "width": this._width, "height": this._height }}> + <canvas className="PdfPage-canvasContainer" ref={this._canvas} /> + <div className="pdfPage-dragAnnotationBox" ref={this._marquee} + style={{ + left: `${this._marqueeX}px`, top: `${this._marqueeY}px`, + width: `${this._marqueeWidth}px`, height: `${this._marqueeHeight}px`, + border: `${this._marqueeWidth === 0 ? "" : "10px dashed black"}` + }}> </div> - <div className="pdfInkingLayer-cont" ref={this._annotationLayer} style={{ width: "100%", height: "100%", position: "relative", top: "-100%" }}> - <div className="pdfViewer-annotationBox" ref={this._marquee} - style={{ left: `${this._marqueeX}px`, top: `${this._marqueeY}px`, width: `${this._marqueeWidth}px`, height: `${this._marqueeHeight}px`, background: "red", border: `${this._marqueeWidth === 0 ? "" : "10px dashed black"}` }}> - {/* <img ref={this._curly} src="https://static.thenounproject.com/png/331760-200.png" style={{ width: "100%", height: "100%", transform: `${this._rotate}` }} /> */} - </div> - </div> - <div className="textlayer" ref={this._textLayer} style={{ "position": "relative", "top": `-${2 * this._height}px`, "height": `${this._height}px` }} /> - </div> - ); + <div className="pdfPage-textlayer" ref={this._textLayer} /> + </div>); } -} +}
\ No newline at end of file |
