diff options
| -rw-r--r-- | src/client/documents/Documents.ts | 2 | ||||
| -rw-r--r-- | src/client/views/MainView.tsx | 4 | ||||
| -rw-r--r-- | src/client/views/nodes/PDFBox.scss | 68 | ||||
| -rw-r--r-- | src/client/views/nodes/PDFBox.tsx | 111 | ||||
| -rw-r--r-- | src/client/views/pdf/PDFMenu.tsx | 45 | ||||
| -rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 196 | ||||
| -rw-r--r-- | src/client/views/pdf/Page.tsx | 31 |
7 files changed, 291 insertions, 166 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b04fc401a..cc67c68e9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -199,7 +199,7 @@ export namespace Docs { return audioProto; } - function CreateInstance(proto: Doc, data: Field, options: DocumentOptions, delegId?: string) { + export function CreateInstance(proto: Doc, data: Field, options: DocumentOptions, delegId?: string) { const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys); if (!("author" in protoProps)) { protoProps.author = CurrentUserUtils.email; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 51630c29b..226eb458b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,5 +1,5 @@ import { IconName, library } from '@fortawesome/fontawesome-svg-core'; -import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faThumbtack, faRedoAlt, faTable, faTree, faUndoAlt, faBell, faCommentAlt } from '@fortawesome/free-solid-svg-icons'; +import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faCheck, faPenNib, faThumbtack, faRedoAlt, faTable, faTree, faUndoAlt, faBell, faCommentAlt, faCut } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; @@ -91,8 +91,10 @@ export class MainView extends React.Component { library.add(faFilm); library.add(faMusic); library.add(faTree); + library.add(faCut); library.add(faCommentAlt); library.add(faThumbtack); + library.add(faCheck); this.initEventListeners(); this.initAuthenticationRouters(); } diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index 8bcae4f1e..5edff69f3 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -36,12 +36,15 @@ pointer-events: none; display: flex; flex-direction: row; + .textlayer { pointer-events: none; + span { pointer-events: none !important; } } + .page-cont { pointer-events: none; } @@ -51,6 +54,7 @@ pointer-events: all; display: flex; flex-direction: row; + .textlayer { span { pointer-events: all !important; @@ -62,4 +66,68 @@ .pdfBox-contentContainer { position: absolute; transform-origin: left top; +} + +.pdfBox-settingsCont { + position: absolute; + right: 0; + top: 0; + + .pdfBox-settingsButton { + border-bottom-left-radius: 50%; + display: flex; + justify-content: space-evenly; + align-items: center; + height: 70px; + background: none; + padding: 0; + + .pdfBox-settingsButton-arrow { + width: 0; + height: 0; + border-top: 25px solid transparent; + border-bottom: 25px solid transparent; + border-right: 25px solid #121721; + transition: all 0.5s; + } + + .pdfBox-settingsButton-iconCont { + background: #121721; + height: 50px; + width: 70px; + display: flex; + justify-content: center; + align-items: center; + margin-left: -2px; + border-radius: 3px; + } + } + + .pdfBox-settingsButton:hover { + background: none; + } + + .pdfBox-settingsFlyout { + width: 600px; + position: absolute; + background: #323232; + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + left: -400px; + border-radius: 7px; + padding: 20px; + display: flex; + flex-direction: column; + font-size: 30px; + transition: all 0.5s; + + .pdfBox-settingsFlyout-title { + color: white; + } + + .pdfBox-settingsFlyout-kvpInput { + margin-top: 20px; + display: grid; + grid-template-columns: 47.5% 5% 47.5%; + } + } }
\ No newline at end of file diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index d2de1cb1c..aa421ff9c 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -1,7 +1,7 @@ import { action, IReactionDisposer, observable, reaction, trace, untracked } from 'mobx'; import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; -import { WidthSym } from "../../../new_fields/Doc"; +import { WidthSym, Doc } from "../../../new_fields/Doc"; import { makeInterface } from "../../../new_fields/Schema"; import { Cast, NumCast } from "../../../new_fields/Types"; import { PdfField } from "../../../new_fields/URLField"; @@ -17,6 +17,10 @@ import { FieldView, FieldViewProps } from './FieldView'; import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; import React = require("react"); +import { CompileScript } from '../../util/Scripting'; +import { ScriptField } from '../../../fields/ScriptField'; +import { Flyout, anchorPoints } from '../DocumentDecorations'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; type PdfDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>; const PdfDocument = makeInterface(positionSchema, pageSchema); @@ -27,12 +31,40 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen @observable private _alt = false; @observable private _scrollY: number = 0; + @observable private _flyout: boolean = false; + private _mainCont: React.RefObject<HTMLDivElement>; private _reactionDisposer?: IReactionDisposer; + private _keyValue: string = ""; + private _valueValue: string = ""; + private _scriptValue: string = ""; + + constructor(props: FieldViewProps) { + super(props); + + this._mainCont = React.createRef(); + 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" }); + } + } + ); + + let script = CompileScript("return this.page === 0", { params: { this: Doc.name } }); + if (script.compiled) { + this.props.Document.filterScript = new ScriptField(script); + } + } componentDidMount() { if (this.props.setPdfBox) this.props.setPdfBox(this); } + componentWillUnmount() { + this._reactionDisposer && this._reactionDisposer(); + } + public GetPage() { return Math.floor(NumCast(this.props.Document.scrollY) / NumCast(this.Document.pdfHeight)) + 1; } @@ -59,11 +91,75 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen } } - createRef = (ele: HTMLDivElement | null) => { - if (this._reactionDisposer) this._reactionDisposer(); - this._reactionDisposer = reaction(() => this.props.Document.scrollY, () => { - ele && ele.scrollTo({ top: NumCast(this.Document.scrollY), behavior: "auto" }); - }); + private newKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => { + this._keyValue = e.currentTarget.value; + } + + private newValueChange = (e: React.ChangeEvent<HTMLInputElement>) => { + this._valueValue = e.currentTarget.value; + } + + private newScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => { + this._scriptValue = e.currentTarget.value; + } + + private applyFilter = (e: React.MouseEvent<HTMLButtonElement>) => { + 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}`; + } + 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; + } + + settingsPanel() { + return !this.props.active() ? (null) : + ( + <div className="pdfBox-settingsCont" onPointerDown={(e) => e.stopPropagation()}> + <button className="pdfBox-settingsButton" onClick={this.toggleFlyout} + 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" onChange={this.newKeyChange} + style={{ gridColumn: 1 }} /> + <input placeholder="Value" className="pdfBox-settingsFlyout-input" onChange={this.newValueChange} + style={{ gridColumn: 3 }} /> + </div> + <div className="pdfBox-settingsFlyout-kvpInput"> + <input placeholder="Custom Script" onChange={this.newScriptChange} style={{ gridColumn: "1 / 4" }} /> + </div> + <div className="pdfBox-settingsFlyout-kvpInput"> + <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) => { @@ -105,12 +201,13 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen overflowY: "scroll", overflowX: "hidden", marginTop: `${NumCast(this.props.ContainingCollectionView!.props.Document.panY)}px` }} - ref={this.createRef} + ref={this._mainCont} onWheel={(e: React.WheelEvent) => { e.stopPropagation(); }} className={classname}> <PDFViewer url={pdfUrl.url.pathname} loaded={this.loaded} scrollY={this._scrollY} parent={this} /> {/* <div style={{ width: "100px", height: "300px" }}></div> */} + {this.settingsPanel()} </div> ); } diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx index 39b15fb11..a8e176858 100644 --- a/src/client/views/pdf/PDFMenu.tsx +++ b/src/client/views/pdf/PDFMenu.tsx @@ -5,6 +5,8 @@ import { observer } from "mobx-react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { emptyFunction } from "../../../Utils"; import { Doc } from "../../../new_fields/Doc"; +import { DragManager } from "../../util/DragManager"; +import { DocUtils } from "../../documents/Documents"; @observer export default class PDFMenu extends React.Component { @@ -16,19 +18,23 @@ export default class PDFMenu extends React.Component { @observable private _transition: string = "opacity 0.5s"; @observable private _transitionDelay: string = ""; - @observable public Pinned: boolean = false; StartDrag: (e: PointerEvent) => 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; @observable public Highlighting: boolean = false; - @observable public Status: "pdf" | "annotation" | "" = ""; + @observable public Status: "pdf" | "annotation" | "snippet" | "" = ""; + @observable public Pinned: boolean = false; + + public Marquee: { left: number; top: number; width: number; height: number; } | undefined; private _offsetY: number = 0; private _offsetX: number = 0; private _mainCont: React.RefObject<HTMLDivElement>; private _dragging: boolean = false; + private _snippetButton: React.RefObject<HTMLButtonElement>; constructor(props: Readonly<{}>) { super(props); @@ -36,6 +42,7 @@ export default class PDFMenu extends React.Component { PDFMenu.Instance = this; this._mainCont = React.createRef(); + this._snippetButton = React.createRef(); } pointerDown = (e: React.PointerEvent) => { @@ -171,13 +178,45 @@ export default class PDFMenu extends React.Component { e.preventDefault(); } + snippetStart = (e: React.PointerEvent) => { + document.removeEventListener("pointermove", this.snippetDrag); + document.addEventListener("pointermove", this.snippetDrag); + document.removeEventListener("pointerup", this.snippetEnd); + document.addEventListener("pointerup", this.snippetEnd); + + e.stopPropagation(); + e.preventDefault(); + } + + snippetDrag = (e: PointerEvent) => { + e.stopPropagation(); + e.preventDefault(); + if (this._dragging) { + return; + } + this._dragging = true; + + if (this.Marquee) { + this.Snippet(this.Marquee); + } + } + + snippetEnd = (e: PointerEvent) => { + this._dragging = false; + document.removeEventListener("pointermove", this.snippetDrag); + document.removeEventListener("pointerup", this.snippetEnd); + e.stopPropagation(); + e.preventDefault(); + } + render() { - let buttons = this.Status === "pdf" ? [ + let buttons = this.Status === "pdf" || this.Status === "snippet" ? [ <button 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" onPointerDown={this.pointerDown}><FontAwesomeIcon icon="comment-alt" size="lg" /></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 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)" : "" }} /> diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 6adead626..1eab13bc5 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -10,7 +10,7 @@ import { List } from "../../../new_fields/List"; import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../../new_fields/Types"; import { emptyFunction } from "../../../Utils"; import { DocServer } from "../../DocServer"; -import { Docs, DocUtils } from "../../documents/Documents"; +import { Docs, DocUtils, DocumentOptions } from "../../documents/Documents"; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager } from "../../util/DragManager"; import { DocumentView } from "../nodes/DocumentView"; @@ -20,6 +20,8 @@ import "./PDFViewer.scss"; import React = require("react"); import PDFMenu from "./PDFMenu"; import { UndoManager } from "../../util/UndoManager"; +import { ScriptField } from "../../../fields/ScriptField"; +import { CompileScript, CompiledScript, CompileResult } from "../../util/Scripting"; export const scale = 2; interface IPDFViewerProps { @@ -75,12 +77,22 @@ class Viewer extends React.Component<IViewerProps> { @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; private _pageBuffer: number = 1; private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); private _reactionDisposer?: IReactionDisposer; private _annotationReactionDisposer?: IReactionDisposer; private _dropDisposer?: DragManager.DragDropDisposer; + private _filterReactionDisposer?: IReactionDisposer; + + @action + constructor(props: IViewerProps) { + super(props); + + let scriptfield = Cast(this.props.parent.Document.filterScript, ScriptField); + this._script = scriptfield ? CompileScript(scriptfield.scriptString, { params: { this: Doc.name } }) : CompileScript("return true");; + } componentDidUpdate = (prevProps: IViewerProps) => { if (this.scrollY !== prevProps.scrollY) { @@ -103,11 +115,24 @@ class Viewer extends React.Component<IViewerProps> { (annotations: Doc[]) => annotations && annotations.length && this.renderAnnotations(annotations, true), { fireImmediately: true }); + + if (this.props.parent.props.ContainingCollectionView) { + this._filterReactionDisposer = reaction( + () => this.props.parent.Document.filterScript || this.props.parent.props.ContainingCollectionView!.props.Document.filterScript, + () => { + runInAction(() => { + let scriptfield = Cast(this.props.parent.Document.filterScript, ScriptField); + this._script = scriptfield ? CompileScript(scriptfield.scriptString, { params: { this: Doc.name } }) : CompileScript("return true");; + }); + } + ); + } } componentWillUnmount = () => { this._reactionDisposer && this._reactionDisposer(); this._annotationReactionDisposer && this._annotationReactionDisposer(); + this._filterReactionDisposer && this._filterReactionDisposer(); } @action @@ -126,6 +151,14 @@ class Viewer extends React.Component<IViewerProps> { 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; } } @@ -138,7 +171,9 @@ class Viewer extends React.Component<IViewerProps> { makeAnnotationDocument = (sourceDoc: Doc | undefined, s: number, color: string): Doc => { let annoDocs: Doc[] = []; - let mainAnnoDoc = new Doc(); + let mainAnnoDoc = Docs.CreateInstance(new Doc(), "", {}); + + mainAnnoDoc.page = Math.round(Math.random()); this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => { for (let anno of value) { let annoDoc = new Doc(); @@ -216,7 +251,7 @@ class Viewer extends React.Component<IViewerProps> { createAnnotation={this.createAnnotation} sendAnnotations={this.receiveAnnotations} makeAnnotationDocuments={this.makeAnnotationDocument} - receiveAnnotations={this.sendAnnotations} + getScrollFromPage={this.getScrollFromPage} {...this.props} /> ); } @@ -288,28 +323,6 @@ class Viewer extends React.Component<IViewerProps> { return this._savedAnnotations.getValue(page); } - // createPinAnnotation = (x: number, y: number, page: number): void => { - // let targetDoc = Docs.TextDocument({ width: 100, height: 50, title: "New Pin Annotation" }); - // let pinAnno = new Doc(); - // pinAnno.x = x; - // pinAnno.y = y + this.getScrollFromPage(page); - // pinAnno.width = pinAnno.height = PinRadius; - // pinAnno.page = page; - // pinAnno.target = targetDoc; - // pinAnno.type = AnnotationTypes.Pin; - // // this._annotations.push(pinAnno); - // let annoDoc = new Doc(); - // annoDoc.annotations = new List<Doc>([pinAnno]); - // let annotations = DocListCast(this.props.parent.Document.annotations); - // if (annotations && annotations.length) { - // annotations.push(annoDoc); - // this.props.parent.Document.annotations = new List<Doc>(annotations); - // } - // else { - // this.props.parent.Document.annotations = new List<Doc>([annoDoc]); - // } - // } - // get the page index that the vertical offset passed in is on getPageFromScroll = (vOffset: number) => { let index = 0; @@ -362,6 +375,7 @@ class Viewer extends React.Component<IViewerProps> { } render() { + let compiled = this._script; return ( <div ref={this.mainCont} style={{ pointerEvents: "all" }}> <div className="viewer"> @@ -373,7 +387,15 @@ class Viewer extends React.Component<IViewerProps> { pointerEvents: this.props.parent.props.active() ? "none" : "all" }}> <div className="pdfViewer-annotationLayer-subCont" ref={this._annotationLayer}> - {this._annotations.map(anno => this.renderAnnotation(anno))} + {this._annotations.filter(anno => { + if (compiled && compiled.compiled) { + let run = compiled.run({ this: anno }); + if (run.success) { + return run.result; + } + } + return true; + }).map(anno => this.renderAnnotation(anno))} </div> </div> </div > @@ -394,119 +416,6 @@ interface IAnnotationProps { document: Doc; } -// @observer -// class PinAnnotation extends React.Component<IAnnotationProps> { -// @observable private _backgroundColor: string = "green"; -// @observable private _display: string = "initial"; - -// private _mainCont: React.RefObject<HTMLDivElement>; - -// constructor(props: IAnnotationProps) { -// super(props); -// this._mainCont = React.createRef(); -// } - -// componentDidMount = () => { -// let selected = this.props.document.selected; -// if (!BoolCast(selected)) { -// runInAction(() => { -// this._backgroundColor = "red"; -// this._display = "none"; -// }); -// } -// if (selected) { -// if (BoolCast(selected)) { -// runInAction(() => { -// this._backgroundColor = "green"; -// this._display = "initial"; -// }); -// } -// else { -// runInAction(() => { -// this._backgroundColor = "red"; -// this._display = "none"; -// }); -// } -// } -// else { -// runInAction(() => { -// this._backgroundColor = "red"; -// this._display = "none"; -// }); -// } -// } - -// @action -// pointerDown = (e: React.PointerEvent) => { -// let selected = this.props.document.selected; -// if (selected && BoolCast(selected)) { -// this._backgroundColor = "red"; -// this._display = "none"; -// this.props.document.selected = false; -// } -// else { -// this._backgroundColor = "green"; -// this._display = "initial"; -// this.props.document.selected = true; -// } -// e.preventDefault(); -// e.stopPropagation(); -// } - -// @action -// doubleClick = (e: React.MouseEvent) => { -// if (this._mainCont.current) { -// let annotations = DocListCast(this.props.parent.props.parent.Document.annotations); -// if (annotations && annotations.length) { -// let index = annotations.indexOf(this.props.document); -// annotations.splice(index, 1); -// this.props.parent.props.parent.Document.annotations = new List<Doc>(annotations); -// } -// // this._mainCont.current.childNodes.forEach(e => e.remove()); -// this._mainCont.current.style.display = "none"; -// // if (this._mainCont.current.parentElement) { -// // this._mainCont.current.remove(); -// // } -// } -// e.stopPropagation(); -// } - -// render() { -// let targetDoc = Cast(this.props.document.target, Doc); -// if (targetDoc instanceof Doc) { -// return ( -// <div className="pdfViewer-pinAnnotation" onPointerDown={this.pointerDown} -// onDoubleClick={this.doubleClick} ref={this._mainCont} -// style={{ -// top: this.props.y * scale - PinRadius / 2, left: this.props.x * scale - PinRadius / 2, width: PinRadius, -// height: PinRadius, pointerEvents: "all", backgroundColor: this._backgroundColor -// }}> -// <div style={{ -// position: "absolute", top: "25px", left: "25px", transform: "scale(3)", transformOrigin: "top left", -// display: this._display, width: targetDoc[WidthSym](), height: targetDoc[HeightSym]() -// }}> -// <DocumentView Document={targetDoc} -// ContainingCollectionView={undefined} -// ScreenToLocalTransform={this.props.parent.props.parent.props.ScreenToLocalTransform} -// isTopMost={false} -// ContentScaling={() => 1} -// PanelWidth={() => NumCast(this.props.parent.props.parent.Document.nativeWidth)} -// PanelHeight={() => NumCast(this.props.parent.props.parent.Document.nativeHeight)} -// focus={emptyFunction} -// selectOnLoad={false} -// parentActive={this.props.parent.props.parent.props.active} -// whenActiveChanged={this.props.parent.props.parent.props.whenActiveChanged} -// bringToFront={emptyFunction} -// addDocTab={this.props.parent.props.parent.props.addDocTab} -// /> -// </div> -// </div > -// ); -// } -// return null; -// } -// } - class RegionAnnotation extends React.Component<IAnnotationProps> { @observable private _backgroundColor: string = "red"; @@ -553,15 +462,6 @@ class RegionAnnotation extends React.Component<IAnnotationProps> { PDFMenu.Instance.fadeOut(true); } - - // annotateThis = (e: PointerEvent) => { - // e.preventDefault(); - // e.stopPropagation(); - // // document that this annotation is linked to - // let targetDoc = Docs.TextDocument({ width: 200, height: 200, title: "New Annotation" }); - // let group = FieldValue(Cast(this.props.document.group, Doc)); - // } - @action onPointerDown = (e: React.PointerEvent) => { if (e.button === 0) { diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index b6f362702..2b69bc47c 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -16,6 +16,7 @@ import { menuBar } from "prosemirror-menu"; import { AnnotationTypes, PDFViewer, scale } from "./PDFViewer"; import PDFMenu from "./PDFMenu"; import { UndoManager } from "../../util/UndoManager"; +import { copy } from "typescript-collections/dist/lib/arrays"; interface IPageProps { @@ -29,9 +30,9 @@ interface IPageProps { renderAnnotations: (annotations: Doc[], removeOld: boolean) => void; makePin: (x: number, y: number, page: number) => void; sendAnnotations: (annotations: HTMLDivElement[], page: number) => void; - receiveAnnotations: (page: number) => HTMLDivElement[] | undefined; createAnnotation: (div: HTMLDivElement, page: number) => void; makeAnnotationDocuments: (doc: Doc | undefined, scale: number, color: string) => Doc; + getScrollFromPage: (page: number) => number; } @observer @@ -178,6 +179,18 @@ export default class Page extends React.Component<IPageProps> { 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!); + view.proto = data; + view.nativeHeight = marquee.height; + view.startY = marquee.top + this.props.getScrollFromPage(this.props.page); + view.width = doc[WidthSym](); + let dragData = new DragManager.DocumentDragData([view]); + DragManager.StartDocumentDrag([], dragData, 0, 0); + } + @action onPointerDown = (e: React.PointerEvent): void => { // if alt+left click, drag and annotate @@ -192,6 +205,7 @@ export default class Page extends React.Component<IPageProps> { else if (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; @@ -281,10 +295,11 @@ export default class Page extends React.Component<IPageProps> { if (this._marquee.current) { let copy = document.createElement("div"); // make a copy of the marquee - copy.style.left = this._marquee.current.style.left; - copy.style.top = this._marquee.current.style.top; - copy.style.width = this._marquee.current.style.width; - copy.style.height = this._marquee.current.style.height; + 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(); @@ -298,7 +313,7 @@ export default class Page extends React.Component<IPageProps> { copy.appendChild(img); } else { - copy.style.opacity = this._marquee.current.style.opacity; + copy.style.opacity = style.opacity; } copy.className = this._marquee.current.className; this.props.createAnnotation(copy, this.props.page); @@ -306,6 +321,10 @@ export default class Page extends React.Component<IPageProps> { } if (this._marqueeWidth > 10 || this._marqueeHeight > 10) { + if (!e.ctrlKey) { + PDFMenu.Instance.Status = "snippet"; + PDFMenu.Instance.Marquee = { left: this._marqueeX, top: this._marqueeY, width: this._marqueeWidth, height: this._marqueeHeight }; + } PDFMenu.Instance.jumpTo(e.clientX, e.clientY); } |
