diff options
-rw-r--r-- | src/client/views/collections/CollectionPDFView.tsx | 61 | ||||
-rw-r--r-- | src/client/views/nodes/PDFBox.tsx | 241 | ||||
-rw-r--r-- | src/client/views/pdf/Annotation.tsx | 69 | ||||
-rw-r--r-- | src/client/views/pdf/PDFMenu.tsx | 123 | ||||
-rw-r--r-- | src/client/views/pdf/PDFViewer.scss | 14 | ||||
-rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 412 | ||||
-rw-r--r-- | src/client/views/pdf/Page.tsx | 246 |
7 files changed, 389 insertions, 777 deletions
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index 70010819a..3736ebada 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -1,36 +1,31 @@ -import { action, IReactionDisposer, observable, reaction } from "mobx"; +import { action, IReactionDisposer, observable, reaction, computed } from "mobx"; import { observer } from "mobx-react"; -import { WidthSym, HeightSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { NumCast } from "../../../new_fields/Types"; import { emptyFunction } from "../../../Utils"; import { ContextMenu } from "../ContextMenu"; import { FieldView, FieldViewProps } from "../nodes/FieldView"; +import { PDFBox } from "../nodes/PDFBox"; import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from "./CollectionBaseView"; import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; import "./CollectionPDFView.scss"; import React = require("react"); -import { PDFBox } from "../nodes/PDFBox"; @observer export class CollectionPDFView extends React.Component<FieldViewProps> { + public static LayoutString(fieldKey: string = "data", fieldExt: string = "annotations") { + return FieldView.LayoutString(CollectionPDFView, fieldKey, fieldExt); + } + private _pdfBox?: PDFBox; private _reactionDisposer?: IReactionDisposer; - private _buttonTray: React.RefObject<HTMLDivElement>; - - constructor(props: FieldViewProps) { - super(props); - - this._buttonTray = React.createRef(); - } + private _buttonTray: React.RefObject<HTMLDivElement> = React.createRef(); componentDidMount() { this._reactionDisposer = reaction( () => NumCast(this.props.Document.scrollY), - () => { - this.props.Document.panY = NumCast(this.props.Document.scrollY); - }, + () => this.props.Document.panY = NumCast(this.props.Document.scrollY), { fireImmediately: true } ); } @@ -39,28 +34,12 @@ export class CollectionPDFView extends React.Component<FieldViewProps> { this._reactionDisposer && this._reactionDisposer(); } - public static LayoutString(fieldKey: string = "data", fieldExt: string = "annotations") { - return FieldView.LayoutString(CollectionPDFView, fieldKey, fieldExt); - } - @observable _inThumb = false; - - private set curPage(value: number) { this._pdfBox && this._pdfBox.GotoPage(value); } - private get curPage() { return NumCast(this.props.Document.curPage, -1); } - private get numPages() { return NumCast(this.props.Document.numPages); } - @action onPageBack = () => this._pdfBox && this._pdfBox.BackPage(); - @action onPageForward = () => this._pdfBox && this._pdfBox.ForwardPage(); - - nativeWidth = () => NumCast(this.props.Document.nativeWidth); - nativeHeight = () => NumCast(this.props.Document.nativeHeight); - private get uIButtons() { - let ratio = (this.curPage - 1) / this.numPages * 100; + @computed + get uIButtons() { return ( <div className="collectionPdfView-buttonTray" ref={this._buttonTray} key="tray" style={{ height: "100%" }}> - <button className="collectionPdfView-backward" onClick={this.onPageBack}>{"<"}</button> - <button className="collectionPdfView-forward" onClick={this.onPageForward}>{">"}</button> - {/* <div className="collectionPdfView-slider" onPointerDown={this.onThumbDown} style={{ top: 60, left: -20, width: 50, height: `calc(100% - 80px)` }} > - <div className="collectionPdfView-thumb" onPointerDown={this.onThumbDown} style={{ top: `${ratio}%`, width: 50, height: 50 }} /> - </div> */} + <button className="collectionPdfView-backward" onClick={() => this._pdfBox && this._pdfBox.BackPage()}>{"<"}</button> + <button className="collectionPdfView-forward" onClick={() => this._pdfBox && this._pdfBox.ForwardPage()}>{">"}</button> </div> ); } @@ -73,20 +52,16 @@ export class CollectionPDFView extends React.Component<FieldViewProps> { setPdfBox = (pdfBox: PDFBox) => { this._pdfBox = pdfBox; }; - - private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => { - let props = { ...this.props, ...renderProps }; - return ( - <> - <CollectionFreeFormView {...props} setPdfBox={this.setPdfBox} CollectionView={this} chromeCollapsed={true} /> - {renderProps.active() ? this.uIButtons : (null)} - </> - ); + subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => { + return (<> + <CollectionFreeFormView {...this.props} {...renderProps} setPdfBox={this.setPdfBox} CollectionView={this} chromeCollapsed={true} /> + {renderProps.active() ? this.uIButtons : (null)} + </>); } render() { return ( - <CollectionBaseView {...this.props} className={`collectionPdfView-cont${this._inThumb ? "-dragging" : ""}`} onContextMenu={this.onContextMenu}> + <CollectionBaseView {...this.props} className={"collectionPdfView-cont"} onContextMenu={this.onContextMenu}> {this.subView} </CollectionBaseView> ); diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index a49709e83..56e720bf7 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -1,31 +1,24 @@ -import { action, IReactionDisposer, observable, reaction, trace, untracked, computed } from 'mobx'; +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 'react-image-lightbox/style.css'; -import { WidthSym, Doc } from "../../../new_fields/Doc"; +import { Doc, WidthSym, Opt } from "../../../new_fields/Doc"; import { makeInterface } from "../../../new_fields/Schema"; -import { Cast, NumCast, BoolCast } from "../../../new_fields/Types"; +import { ScriptField } from '../../../new_fields/ScriptField'; +import { BoolCast, Cast, NumCast } from "../../../new_fields/Types"; import { PdfField } from "../../../new_fields/URLField"; -//@ts-ignore -// import { Document, Page } from "react-pdf"; -// import 'react-pdf/dist/Page/AnnotationLayer.css'; -import { RouteStore } from "../../../server/RouteStore"; +import { KeyCodes } from '../../northstar/utils/KeyCodes'; +import { CompileScript } from '../../util/Scripting'; import { DocComponent } from "../DocComponent"; import { InkingControl } from "../InkingControl"; -import { FilterBox } from "../search/FilterBox"; -import { Annotation } from './Annotation'; import { PDFViewer } from "../pdf/PDFViewer"; import { positionSchema } from "./DocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; import React = require("react"); -import { CompileScript } from '../../util/Scripting'; -import { Flyout, anchorPoints } from '../DocumentDecorations'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ScriptField } from '../../../new_fields/ScriptField'; -import { KeyCodes } from '../../northstar/utils/KeyCodes'; -import { Utils } from '../../../Utils'; -import { Id } from '../../../new_fields/FieldSymbols'; type PdfDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>; const PdfDocument = makeInterface(positionSchema, pageSchema); @@ -35,40 +28,33 @@ export const handleBackspace = (e: React.KeyboardEvent) => { if (e.keyCode === K export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocument) { public static LayoutString() { return FieldView.LayoutString(PDFBox); } + @observable private _flyout: boolean = false; @observable private _alt = false; @observable private _scrollY: number = 0; + @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>; + @computed get containingCollectionDocument() { return this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document; } @computed get dataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; } - @observable private _flyout: boolean = false; - private _mainCont: React.RefObject<HTMLDivElement>; + private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); private _reactionDisposer?: IReactionDisposer; private _keyValue: string = ""; private _valueValue: string = ""; private _scriptValue: string = ""; - private _keyRef: React.RefObject<HTMLInputElement>; - private _valueRef: React.RefObject<HTMLInputElement>; - private _scriptRef: React.RefObject<HTMLInputElement>; + private _keyRef: React.RefObject<HTMLInputElement> = React.createRef(); + private _valueRef: React.RefObject<HTMLInputElement> = React.createRef(); + private _scriptRef: React.RefObject<HTMLInputElement> = React.createRef(); - constructor(props: FieldViewProps) { - super(props); + componentDidMount() { + this.props.setPdfBox && this.props.setPdfBox(this); - this._mainCont = React.createRef(); + const pdfUrl = Cast(this.props.Document.data, PdfField); + if (pdfUrl instanceof PdfField) { + Pdfjs.getDocument(pdfUrl.url.pathname).promise.then(pdf => runInAction(() => this._pdf = pdf)); + } this._reactionDisposer = reaction( () => this.props.Document.scrollY, - () => { - if (this._mainCont.current) { - this._mainCont.current && this._mainCont.current.scrollTo({ top: NumCast(this.Document.scrollY), behavior: "auto" }); - } - } + () => this._mainCont.current && this._mainCont.current.scrollTo({ top: NumCast(this.Document.scrollY), behavior: "auto" }) ); - - this._keyRef = React.createRef(); - this._valueRef = React.createRef(); - this._scriptRef = React.createRef(); - } - - componentDidMount() { - if (this.props.setPdfBox) this.props.setPdfBox(this); } componentWillUnmount() { @@ -78,6 +64,8 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen public GetPage() { return Math.floor(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.pdfHeight)) + 1; } + + @action public BackPage() { let cp = Math.ceil(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.pdfHeight)) + 1; cp = cp - 1; @@ -86,6 +74,8 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen this.props.Document.scrollY = (cp - 1) * NumCast(this.dataDoc.pdfHeight); } } + + @action public GotoPage(p: number) { if (p > 0 && p <= NumCast(this.props.Document.numPages)) { this.props.Document.curPage = p; @@ -93,6 +83,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen } } + @action public ForwardPage() { let cp = this.GetPage() + 1; if (cp <= NumCast(this.props.Document.numPages)) { @@ -101,6 +92,15 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen } } + scrollTo = (y: number) => { + this._mainCont.current && this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current.offsetHeight / 2), 0), behavior: "auto" }); + } + + @action + setPanY = (y: number) => { + this.containingCollectionDocument && (this.containingCollectionDocument.panY = y); + } + private newKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => { this._keyValue = e.currentTarget.value; } @@ -109,151 +109,110 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen this._valueValue = e.currentTarget.value; } - @action private newScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => { this._scriptValue = e.currentTarget.value; } private applyFilter = () => { - let scriptText = ""; - if (this._scriptValue.length > 0) { - scriptText = this._scriptValue; - } else if (this._keyValue.length > 0 && this._valueValue.length > 0) { - scriptText = `return this.${this._keyValue} === ${this._valueValue}`; - } - else { - scriptText = "return true"; - } + let scriptText = this._scriptValue.length > 0 ? this._scriptValue : + this._keyValue.length > 0 && this._valueValue.length > 0 ? + `return this.${this._keyValue} === ${this._valueValue}` : "return true"; let script = CompileScript(scriptText, { params: { this: Doc.name } }); - if (script.compiled) { - this.props.Document.filterScript = new ScriptField(script); - } - } - - @action - private toggleFlyout = () => { - this._flyout = !this._flyout; + script.compiled && (this.props.Document.filterScript = new ScriptField(script)); } @action private resetFilters = () => { this._keyValue = this._valueValue = ""; this._scriptValue = "return true"; - if (this._keyRef.current) { - this._keyRef.current.value = ""; - } - if (this._valueRef.current) { - this._valueRef.current.value = ""; - } - if (this._scriptRef.current) { - this._scriptRef.current.value = ""; - } + this._keyRef.current && (this._keyRef.current.value = ""); + this._valueRef.current && (this._valueRef.current.value = ""); + this._scriptRef.current && (this._scriptRef.current.value = ""); this.applyFilter(); } - scrollTo(y: number) { - this._mainCont.current && this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current.offsetHeight / 2), 0), behavior: "auto" }); - } - settingsPanel() { return !this.props.active() ? (null) : - ( - <div className="pdfBox-settingsCont" onPointerDown={(e) => e.stopPropagation()}> - <button className="pdfBox-settingsButton" onClick={this.toggleFlyout} title="Open Annotation Settings" - style={{ marginTop: `${NumCast(this.props.ContainingCollectionView!.props.Document.panY)}px` }}> - <div className="pdfBox-settingsButton-arrow" - style={{ - borderTop: `25px solid ${this._flyout ? "#121721" : "transparent"}`, - borderBottom: `25px solid ${this._flyout ? "#121721" : "transparent"}`, - borderRight: `25px solid ${this._flyout ? "transparent" : "#121721"}`, - transform: `scaleX(${this._flyout ? -1 : 1})` - }}></div> - <div className="pdfBox-settingsButton-iconCont"> - <FontAwesomeIcon style={{ color: "white" }} icon="cog" size="3x" /> - </div> - </button> - <div className="pdfBox-settingsFlyout" style={{ left: `${this._flyout ? -600 : 100}px` }} > - <div className="pdfBox-settingsFlyout-title"> - Annotation View Settings - </div> - <div className="pdfBox-settingsFlyout-kvpInput"> - <input placeholder="Key" className="pdfBox-settingsFlyout-input" onKeyDown={handleBackspace} onChange={this.newKeyChange} - style={{ gridColumn: 1 }} ref={this._keyRef} /> - <input placeholder="Value" className="pdfBox-settingsFlyout-input" onKeyDown={handleBackspace} onChange={this.newValueChange} - style={{ gridColumn: 3 }} ref={this._valueRef} /> - </div> - <div className="pdfBox-settingsFlyout-kvpInput"> - <input placeholder="Custom Script" onChange={this.newScriptChange} onKeyDown={handleBackspace} style={{ gridColumn: "1 / 4" }} ref={this._scriptRef} /> - </div> - <div className="pdfBox-settingsFlyout-kvpInput"> - <button style={{ gridColumn: 1 }} onClick={this.resetFilters}> - <FontAwesomeIcon style={{ color: "white" }} icon="trash" size="lg" /> - Reset Filters - </button> - <button style={{ gridColumn: 3 }} onClick={this.applyFilter}> - <FontAwesomeIcon style={{ color: "white" }} icon="check" size="lg" /> - Apply - </button> - </div> + (<div className="pdfBox-settingsCont" onPointerDown={(e) => e.stopPropagation()}> + <button className="pdfBox-settingsButton" onClick={action(() => this._flyout = !this._flyout)} title="Open Annotation Settings" + style={{ marginTop: `${this.containingCollectionDocument ? NumCast(this.containingCollectionDocument.panY) : 0}px` }}> + <div className="pdfBox-settingsButton-arrow" + style={{ + borderTop: `25px solid ${this._flyout ? "#121721" : "transparent"}`, + borderBottom: `25px solid ${this._flyout ? "#121721" : "transparent"}`, + borderRight: `25px solid ${this._flyout ? "transparent" : "#121721"}`, + transform: `scaleX(${this._flyout ? -1 : 1})` + }} /> + <div className="pdfBox-settingsButton-iconCont"> + <FontAwesomeIcon style={{ color: "white" }} icon="cog" size="3x" /> + </div> + </button> + <div className="pdfBox-settingsFlyout" style={{ left: `${this._flyout ? -600 : 100}px` }} > + <div className="pdfBox-settingsFlyout-title"> + Annotation View Settings + </div> + <div className="pdfBox-settingsFlyout-kvpInput"> + <input placeholder="Key" className="pdfBox-settingsFlyout-input" onKeyDown={handleBackspace} onChange={this.newKeyChange} + style={{ gridColumn: 1 }} ref={this._keyRef} /> + <input placeholder="Value" className="pdfBox-settingsFlyout-input" onKeyDown={handleBackspace} onChange={this.newValueChange} + style={{ gridColumn: 3 }} ref={this._valueRef} /> + </div> + <div className="pdfBox-settingsFlyout-kvpInput"> + <input placeholder="Custom Script" onChange={this.newScriptChange} onKeyDown={handleBackspace} style={{ gridColumn: "1 / 4" }} ref={this._scriptRef} /> + </div> + <div className="pdfBox-settingsFlyout-kvpInput"> + <button style={{ gridColumn: 1 }} onClick={this.resetFilters}> + <FontAwesomeIcon style={{ color: "white" }} icon="trash" size="lg" /> + Reset Filters + </button> + <button style={{ gridColumn: 3 }} onClick={this.applyFilter}> + <FontAwesomeIcon style={{ color: "white" }} icon="check" size="lg" /> + Apply + </button> </div> </div> - ); + </div>); } loaded = (nw: number, nh: number, np: number) => { - if (this.props.Document) { - let doc = this.dataDoc; - doc.numPages = np; - if (doc.nativeWidth && doc.nativeHeight) return; - let oldaspect = NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth, 1); - doc.nativeWidth = nw; - if (doc.nativeHeight) doc.nativeHeight = nw * oldaspect; - else doc.nativeHeight = nh; - let ccv = this.props.ContainingCollectionView; - if (ccv) { - ccv.props.Document.pdfHeight = nh; - } - doc.height = nh * (doc[WidthSym]() / nw); + this.dataDoc.numPages = np; + if (!this.dataDoc.nativeWidth || !this.dataDoc.nativeHeight) { + let oldaspect = NumCast(this.dataDoc.nativeHeight) / NumCast(this.dataDoc.nativeWidth, 1); + this.dataDoc.nativeWidth = nw; + this.dataDoc.nativeHeight = this.dataDoc.nativeHeight ? nw * oldaspect : nh; + this.containingCollectionDocument && (this.containingCollectionDocument.pdfHeight = nh); + this.dataDoc.height = nh * (this.dataDoc[WidthSym]() / nw); } } @action onScroll = (e: React.UIEvent<HTMLDivElement>) => { - - if (e.currentTarget) { - this._scrollY = e.currentTarget.scrollTop; - let ccv = this.props.ContainingCollectionView; - if (ccv) { - ccv.props.Document.panTransformType = "None"; - ccv.props.Document.scrollY = this._scrollY; - } + if (e.currentTarget && this.containingCollectionDocument) { + this.containingCollectionDocument.panTransformType = "None"; + this.containingCollectionDocument.scrollY = this._scrollY = e.currentTarget.scrollTop; } } - @computed get fieldExtensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); } + render() { - // uses mozilla pdf as default const pdfUrl = Cast(this.props.Document.data, PdfField); - if (!(pdfUrl instanceof PdfField)) return <div>{`pdf, ${this.props.Document.data}, not found`}</div>; let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); - return ( + return (!(pdfUrl instanceof PdfField) || !this._pdf ? + <div>{`pdf, ${this.props.Document.data}, not found`}</div> : <div className={classname} onScroll={this.onScroll} - style={{ - marginTop: `${NumCast(this.props.ContainingCollectionView!.props.Document.panY)}px` - }} + style={{ marginTop: `${this.containingCollectionDocument ? NumCast(this.containingCollectionDocument.panY) : 0}px` }} ref={this._mainCont} - onWheel={(e: React.WheelEvent) => { - e.stopPropagation(); - }}> - <PDFViewer url={pdfUrl.url.pathname} loaded={this.loaded} scrollY={this._scrollY} parent={this} /> - {/* <div style={{ width: "100px", height: "300px" }}></div> */} + onWheel={(e: React.WheelEvent) => { e.stopPropagation(); }}> + <PDFViewer pdf={this._pdf} url={pdfUrl.url.pathname} active={this.props.active} scrollTo={this.scrollTo} loaded={this.loaded} scrollY={this._scrollY} + Document={this.props.Document} DataDoc={this.props.DataDoc} + addDocTab={this.props.addDocTab} setPanY={this.setPanY} + fieldKey={this.props.fieldKey} fieldExtensionDoc={this.fieldExtensionDoc} /> {this.settingsPanel()} </div> ); } - }
\ No newline at end of file diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index a08ff5969..9d68a86b8 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -4,17 +4,20 @@ 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 { AnnotationTypes, 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> { @@ -23,10 +26,8 @@ export default class Annotation extends React.Component<IAnnotationProps> { 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]} />; + return <RegionAnnotation addDocTab={this.props.addDocTab} document={a} fieldExtensionDoc={this.props.fieldExtensionDoc} scrollTo={this.props.scrollTo} ParentIndex={this.props.ParentIndex} 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>; } @@ -41,7 +42,10 @@ 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; } @@ -51,34 +55,18 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> { 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, + () => this.props.document.delete && 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(), + () => this.props.ParentIndex() === this.props.index && this.props.scrollTo && this.props.scrollTo(this.props.y * scale) ); } @@ -88,16 +76,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 +92,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 +103,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); } } @@ -151,8 +136,8 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> { 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> + 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..7b3d5bfae 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 | undefined) => 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..9bb18c87f 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -23,12 +23,13 @@ .textLayer { user-select: auto; } -.viewer { - // position: absolute; - // top: 0; -} -.pdfViewere-viewer { +.pdfViewer-viewer { pointer-events:inherit; + width: 100%; +} + +.pdfviewer-placeholder { + content: "HELLO WORLD" } .pdfViewer-text { transform: scale(1.5); @@ -44,9 +45,6 @@ } } } -.pdfViewer-viewerCont { - width:100%; -} .page-cont { .textLayer { diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 6a99cec59..524bb0420 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -1,167 +1,104 @@ +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 } 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 { 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 { CompileResult, CompileScript } 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; + url: string; + Document: Doc; + DataDoc?: Doc; + fieldExtensionDoc: Doc; + fieldKey: string; loaded: (nw: number, nh: number, np: number) => void; scrollY: number; - parent: PDFBox; - mainCont: React.RefObject<HTMLDivElement>; - url: string; + scrollTo: (y: number) => void; + active: () => boolean; + setPanY?: (n: number) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; } /** * 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: CompileResult | undefined = CompileScript("return true"); @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); - - 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(); - } - - setSelectionText = (text: string) => { - this._selectionText = text; - } - componentDidUpdate = (prevProps: IViewerProps) => { - if (this.scrollY !== prevProps.scrollY) { - this.renderPages(); - } + this.scrollY !== prevProps.scrollY && this.renderPages(); } @action componentDidMount = () => { this._reactionDisposer = reaction( - - () => [this.props.parent.props.active(), this.startIndex, this._pageSizes.length ? this.endIndex : 0], + () => [this.props.active(), this.startIndex, this._pageSizes.length ? this.endIndex : 0], async () => { await this.initialLoad(); this.renderPages(); }, { fireImmediately: true }); 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.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._filterReactionDisposer = reaction( + () => this.props.Document.filterScript, + action(() => { + let scriptfield = Cast(this.props.Document.filterScript, ScriptField); + this._script = scriptfield ? scriptfield.script : CompileScript("return true"); + DocListCast(this.props.fieldExtensionDoc.annotations).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.Index = -1; + }), + { fireImmediately: true } + ); document.removeEventListener("copy", this.copy); document.addEventListener("copy", this.copy); @@ -171,89 +108,50 @@ 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()) { + if (this.props.active() && e.clipboardData) { 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(); - } + let annoDoc = this.makeAnnotationDocument(undefined, 0, "#0390fc"); + e.clipboardData.setData("text/plain", text); + e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]); + e.clipboardData.setData("dash/pdfRegion", annoDoc[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), 0, "#0390fc", false))); } } - scrollTo(y: number) { - if (this.props.mainCont.current) { - this.props.parent.scrollTo(y); - } + setSelectionText = (text: string) => { + this._selectionText = text; } @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); + this.props.setPanY && this.props.setPanY(startY); + this.props.Document.scrollY = startY + 1; } } @@ -262,8 +160,8 @@ export class Viewer extends React.Component<IViewerProps> { let annoDocs: 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; + mainAnnoDoc.title = "Annotation on " + StrCast(this.props.Document.title); + mainAnnoDoc.pdfDoc = this.props.Document; let minY = Number.MAX_VALUE; this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => { for (let anno of value) { @@ -288,34 +186,19 @@ export class Viewer extends React.Component<IViewerProps> { mainAnnoDoc.y = Math.max(minY, 0); mainAnnoDoc.annotations = new List<Doc>(annoDocs); 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, mainAnnoDoc, 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 => { + pageLoaded = (page: Pdfjs.PDFPageViewport): void => { this.props.loaded(page.width, page.height, this.props.pdf.numPages); } @@ -325,8 +208,9 @@ export class Viewer extends React.Component<IViewerProps> { 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>); } } @@ -344,8 +228,8 @@ export class Viewer extends React.Component<IViewerProps> { key={`${this.props.url}-rendered-${page + 1}`} name={`${this.props.pdf.fingerprint + `-page${page + 1}`}`} pageLoaded={this.pageLoaded} - parent={this.props.parent} - makePin={emptyFunction} + fieldExtensionDoc={this.props.fieldExtensionDoc} + Document={this.props.Document} renderAnnotations={this.renderAnnotations} createAnnotation={this.createAnnotation} sendAnnotations={this.receiveAnnotations} @@ -363,9 +247,8 @@ export class Viewer extends React.Component<IViewerProps> { 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} style={{ width: `${parseInt(res.width) * scale}px`, height: `${parseInt(res.height) * scale}px` }} />); @@ -382,22 +265,15 @@ export class Viewer extends React.Component<IViewerProps> { // 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); + return Math.min(this.props.pdf.numPages - 1, this.getPageFromScroll(this.scrollY + (this._pageSizes[0] ? this._pageSizes[0].height : 0)) + 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); - } - } - } + Array.from(Array(this.props.pdf.numPages).keys()).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 @@ -430,7 +306,7 @@ export class Viewer extends React.Component<IViewerProps> { 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; @@ -461,52 +337,39 @@ 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; - } + getIndex = () => this.Index; @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 - }); - }); + if (this._mainCont.current) { + this._mainCont.current.addEventListener("pagesloaded", () => + this._pdfFindController.executeCommand('find', { + caseSensitive: false, + findPrevious: undefined, + highlightAll: true, + phraseSearch: true, + query: searchString + }) + ); + this._mainCont.current.addEventListener("pagerendered", () => + this._pdfFindController.executeCommand('find', { + caseSensitive: false, + findPrevious: undefined, + highlightAll: true, + phraseSearch: true, + query: searchString + }) + ); } } @@ -575,15 +438,10 @@ export class Viewer extends React.Component<IViewerProps> { linkService: simpleLinkService }); simpleLinkService.setPdf(this.props.pdf); - container.addEventListener("pagesinit", () => { - this._pdfViewer.currentScaleValue = 1; - }); - container.addEventListener("pagerendered", () => { - console.log("rendered"); - }); + 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; } } @@ -602,10 +460,6 @@ 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); } @@ -626,49 +480,27 @@ export class Viewer extends React.Component<IViewerProps> { 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="pdfViewer-viewer" ref={this._mainCont} > <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" style={{ height: NumCast(this.props.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 (this._script && this._script.compiled) { + let run = this._script.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))} + .map((anno: Doc, index: number) => + <Annotation anno={anno} scrollTo={this.props.scrollTo} fieldExtensionDoc={this.props.fieldExtensionDoc} ParentIndex={this.getIndex} addDocTab={this.props.addDocTab} index={index} key={`${anno[Id]}-annotation`} /> + )} </div> </div> <div className="pdfViewer-overlayCont" onPointerDown={(e) => e.stopPropagation()} @@ -683,19 +515,19 @@ export class Viewer extends React.Component<IViewerProps> { <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" }}> + style={{ bottom: -this.props.scrollY + 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.scrollY + 200, right: 10, display: this.props.parent.props.active() ? "flex" : "none" }}> + style={{ bottom: -this.props.scrollY + 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.scrollY + 10, right: 0, display: this.props.parent.props.active() ? "flex" : "none" }}> + style={{ bottom: -this.props.scrollY + 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" /> diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index dea4e0da1..1da41bca1 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -3,12 +3,9 @@ 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"; @@ -17,14 +14,14 @@ 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; @@ -34,7 +31,7 @@ interface IPageProps { @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,54 @@ 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 _annotationLayer: 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 => { - if (this.props.pdf) { - this.update(this.props.pdf); - } - } - - componentWillUnmount = (): void => { - if (this._reactionDisposer) { - this._reactionDisposer(); - } + this.loadPage(this.props.pdf); } componentDidUpdate = (): void => { - if (this.props.pdf) { - this.update(this.props.pdf); - } + this.loadPage(this.props.pdf); } - private update = (pdf: Pdfjs.PDFDocumentProxy): void => { - if (pdf) { - this.loadPage(pdf); - } - else { - this._state = "loading"; - } + componentWillUnmount = (): void => { + this._reactionDisposer && this._reactionDisposer(); } private loadPage = (pdf: Pdfjs.PDFDocumentProxy): void => { - if (this._state === "rendering" || this._page) return; - - pdf.getPage(this._currPage).then( + this._state !== "rendering" && !this._page && pdf.getPage(this._currPage).then( (page: Pdfjs.PDFPageProxy): void => { this._state = "rendering"; this.renderPage(page); - } - ); + }); } @action private 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._canvas.current && this._textLayer.current) { + 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; } @@ -137,12 +98,8 @@ export default class Page extends React.Component<IPageProps> { highlight = (targetDoc?: Doc, color: string = "red") => { // 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); - } + Doc.GetProto(annotationDoc).annotationOn = this.props.Document; + Doc.AddDocToList(this.props.fieldExtensionDoc, "annotations", annotationDoc); return annotationDoc; } @@ -154,16 +111,12 @@ 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 () => { @@ -171,9 +124,7 @@ export default class Page extends React.Component<IPageProps> { 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)); - } + pdfDoc && DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title)) } } }, @@ -184,24 +135,20 @@ 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 @@ -209,11 +156,6 @@ export default class Page extends React.Component<IPageProps> { // 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) { PDFMenu.Instance.StartDrag = this.startDrag; @@ -221,20 +163,18 @@ export default class Page extends React.Component<IPageProps> { 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,63 +188,23 @@ 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); + this._marquee.current && (this._marquee.current.style.background = "red"); 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) { @@ -319,20 +219,9 @@ export default class Page extends React.Component<IPageProps> { 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.background = "red"; 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"; @@ -357,7 +246,6 @@ export default class Page extends React.Component<IPageProps> { } } - if (PDFMenu.Instance.Highlighting) { this.highlight(undefined, "goldenrod"); } @@ -371,9 +259,9 @@ 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) { @@ -389,9 +277,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,19 +288,9 @@ 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() { @@ -425,7 +302,6 @@ export default class Page extends React.Component<IPageProps> { <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` }} /> |