aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/pdf
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/pdf')
-rw-r--r--src/client/views/pdf/Annotation.tsx153
-rw-r--r--src/client/views/pdf/PDFMenu.scss4
-rw-r--r--src/client/views/pdf/PDFMenu.tsx35
-rw-r--r--src/client/views/pdf/PDFViewer.scss3
-rw-r--r--src/client/views/pdf/PDFViewer.tsx232
-rw-r--r--src/client/views/pdf/Page.tsx138
6 files changed, 328 insertions, 237 deletions
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
new file mode 100644
index 000000000..104241237
--- /dev/null
+++ b/src/client/views/pdf/Annotation.tsx
@@ -0,0 +1,153 @@
+import React = require("react");
+import { Doc, DocListCast, WidthSym, HeightSym } from "../../../new_fields/Doc";
+import { AnnotationTypes, Viewer, scale } from "./PDFViewer";
+import { observer } from "mobx-react";
+import { observable, IReactionDisposer, reaction, action } from "mobx";
+import { BoolCast, NumCast, FieldValue, Cast, StrCast } from "../../../new_fields/Types";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { List } from "../../../new_fields/List";
+import PDFMenu from "./PDFMenu";
+import { DocumentManager } from "../../util/DocumentManager";
+import { PresentationView } from "../presentationview/PresentationView";
+
+interface IAnnotationProps {
+ anno: Doc;
+ index: number;
+ parent: Viewer;
+}
+
+export default class Annotation extends React.Component<IAnnotationProps> {
+ render() {
+ let annotationDocs = DocListCast(this.props.anno.annotations);
+ let res = annotationDocs.map(a => {
+ let type = NumCast(a.type);
+ switch (type) {
+ // case AnnotationTypes.Pin:
+ // return <PinAnnotation parent={this} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />;
+ case AnnotationTypes.Region:
+ return <RegionAnnotation parent={this.props.parent} document={a} index={this.props.index} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />;
+ default:
+ return <div></div>;
+ }
+ });
+ return res;
+ }
+}
+
+interface IRegionAnnotationProps {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ index: number;
+ parent: Viewer;
+ document: Doc;
+}
+
+@observer
+class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
+ @observable private _backgroundColor: string = "red";
+
+ private _reactionDisposer?: IReactionDisposer;
+ private _scrollDisposer?: IReactionDisposer;
+ private _mainCont: React.RefObject<HTMLDivElement>;
+
+ constructor(props: IRegionAnnotationProps) {
+ super(props);
+
+ this._mainCont = React.createRef();
+ }
+
+ 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";
+ }
+ }
+ },
+ { 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 - (NumCast(this.props.parent.props.parent.Document.pdfHeight) / 2));
+ }
+ }
+ );
+ }
+
+ componentWillUnmount() {
+ this._reactionDisposer && this._reactionDisposer();
+ this._scrollDisposer && this._scrollDisposer();
+ }
+
+ deleteAnnotation = () => {
+ let annotation = DocListCast(this.props.parent.props.parent.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);
+ }
+
+ PDFMenu.Instance.fadeOut(true);
+ }
+
+ pinToPres = () => {
+ let group = FieldValue(Cast(this.props.document.group, Doc));
+ if (group) {
+ PresentationView.Instance.PinDoc(group);
+ }
+ }
+
+ @action
+ onPointerDown = (e: React.PointerEvent) => {
+ if (e.button === 0) {
+ let targetDoc = Cast(this.props.document.target, Doc, null);
+ if (targetDoc) {
+ DocumentManager.Instance.jumpToDocument(targetDoc, false);
+ }
+ }
+ if (e.button === 2) {
+ PDFMenu.Instance.Status = "annotation";
+ PDFMenu.Instance.Delete = this.deleteAnnotation.bind(this);
+ PDFMenu.Instance.Pinned = false;
+ PDFMenu.Instance.AddTag = this.addTag.bind(this);
+ PDFMenu.Instance.PinToPres = this.pinToPres;
+ PDFMenu.Instance.jumpTo(e.clientX, e.clientY, true);
+ }
+ }
+
+ addTag = (key: string, value: string): boolean => {
+ let group = FieldValue(Cast(this.props.document.group, Doc));
+ if (group) {
+ let valNum = parseInt(value);
+ group[key] = isNaN(valNum) ? value : valNum;
+ return true;
+ }
+ return false;
+ }
+
+ render() {
+ return (
+ <div className="pdfViewer-annotationBox" onPointerDown={this.onPointerDown} ref={this._mainCont}
+ style={{
+ top: this.props.y * scale,
+ left: this.props.x * scale,
+ width: this.props.width * scale,
+ height: this.props.height * scale,
+ pointerEvents: "all",
+ backgroundColor: this.props.parent.Index === this.props.index ? "green" : StrCast(this.props.document.color)
+ }}></div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/pdf/PDFMenu.scss b/src/client/views/pdf/PDFMenu.scss
index a4624b1f6..b06d19c53 100644
--- a/src/client/views/pdf/PDFMenu.scss
+++ b/src/client/views/pdf/PDFMenu.scss
@@ -21,6 +21,10 @@
.pdfMenu-dragger {
height: 100%;
transition: width .2s;
+ background-image: url("https://logodix.com/logo/1020374.png");
+ background-size: 90% 100%;
+ background-repeat: no-repeat;
+ background-position: left center;
}
.pdfMenu-addTag {
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index aeed5213c..27c2a8f1a 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -3,10 +3,9 @@ import "./PDFMenu.scss";
import { observable, action, runInAction } from "mobx";
import { observer } from "mobx-react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { emptyFunction, returnZero, returnTrue, returnFalse } from "../../../Utils";
+import { emptyFunction, returnFalse } from "../../../Utils";
import { Doc } from "../../../new_fields/Doc";
-import { DragManager } from "../../util/DragManager";
-import { DocUtils } from "../../documents/Documents";
+import { handleBackspace } from "../nodes/PDFBox";
@observer
export default class PDFMenu extends React.Component {
@@ -19,11 +18,12 @@ export default class PDFMenu extends React.Component {
@observable private _transitionDelay: string = "";
- StartDrag: (e: PointerEvent) => void = emptyFunction;
+ 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 public Highlighting: boolean = false;
@observable public Status: "pdf" | "annotation" | "snippet" | "" = "";
@@ -33,9 +33,10 @@ export default class PDFMenu extends React.Component {
private _offsetY: number = 0;
private _offsetX: number = 0;
- private _mainCont: React.RefObject<HTMLDivElement>;
+ private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+ private _commentCont = React.createRef<HTMLButtonElement>();
+ private _snippetButton: React.RefObject<HTMLButtonElement> = React.createRef();
private _dragging: boolean = false;
- private _snippetButton: React.RefObject<HTMLButtonElement>;
@observable private _keyValue: string = "";
@observable private _valueValue: string = "";
@observable private _added: boolean = false;
@@ -44,9 +45,6 @@ export default class PDFMenu extends React.Component {
super(props);
PDFMenu.Instance = this;
-
- this._mainCont = React.createRef();
- this._snippetButton = React.createRef();
}
pointerDown = (e: React.PointerEvent) => {
@@ -67,7 +65,7 @@ export default class PDFMenu extends React.Component {
return;
}
- this.StartDrag(e);
+ this.StartDrag(e, this._commentCont.current!);
this._dragging = true;
}
@@ -240,23 +238,24 @@ export default class PDFMenu extends React.Component {
render() {
let buttons = this.Status === "pdf" || this.Status === "snippet" ? [
- <button className="pdfMenu-button" title="Click to Highlight" onClick={this.highlightClicked} key="1"
+ <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" onPointerDown={this.pointerDown}><FontAwesomeIcon icon="comment-alt" size="lg" key="2" /></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 className="pdfMenu-button" title="Pin Menu" onClick={this.togglePin} key="3"
+ <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 className="pdfMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}><FontAwesomeIcon icon="trash-alt" size="lg" key="1" /></button>,
- <div className="pdfMenu-addTag" key="2">
- <input onChange={this.keyChanged} placeholder="Key" style={{ gridColumn: 1 }} />
- <input onChange={this.valueChanged} placeholder="Value" style={{ gridColumn: 3 }} />
+ <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">
+ <input onKeyDown={handleBackspace} onChange={this.keyChanged} placeholder="Key" style={{ gridColumn: 1 }} />
+ <input onKeyDown={handleBackspace} onChange={this.valueChanged} placeholder="Value" style={{ gridColumn: 3 }} />
</div>,
- <button 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="3" /></button>,
+ <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>,
];
return (
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index 11d3d7e27..0fde764d0 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -41,6 +41,9 @@
}
}
}
+.pdfViewer-viewerCont {
+ width:100%;
+}
.page-cont {
.textLayer {
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index a440a1f27..c560e581c 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -14,7 +14,7 @@ import { Docs, DocUtils, DocumentOptions } from "../../documents/Documents";
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager } from "../../util/DragManager";
import { DocumentView } from "../nodes/DocumentView";
-import { PDFBox } from "../nodes/PDFBox";
+import { PDFBox, handleBackspace } from "../nodes/PDFBox";
import Page from "./Page";
import "./PDFViewer.scss";
import React = require("react");
@@ -23,6 +23,8 @@ import { UndoManager } from "../../util/UndoManager";
import { CompileScript, CompiledScript, 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";
const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
export const scale = 2;
@@ -48,7 +50,7 @@ export class PDFViewer extends React.Component<IPDFViewerProps> {
render() {
return (
- <div ref={this._mainDiv}>
+ <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>
@@ -69,7 +71,7 @@ interface IViewerProps {
* Handles rendering and virtualization of the pdf
*/
@observer
-class Viewer extends React.Component<IViewerProps> {
+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
@@ -91,6 +93,7 @@ class Viewer extends React.Component<IViewerProps> {
private _activeReactionDisposer?: IReactionDisposer;
private _viewer: React.RefObject<HTMLDivElement>;
private _mainCont: React.RefObject<HTMLDivElement>;
+ private _pdfViewer: any;
// private _textContent: Pdfjs.TextContent[] = [];
private _pdfFindController: any;
private _searchString: string = "";
@@ -124,9 +127,12 @@ class Viewer extends React.Component<IViewerProps> {
}, { fireImmediately: true });
this._annotationReactionDisposer = reaction(
- () => this.props.parent.Document && DocListCast(this.props.parent.Document.annotations),
- (annotations: Doc[]) =>
- annotations && annotations.length && this.renderAnnotations(annotations, true),
+ () => {
+ return this.props.parent && this.props.parent.fieldExtensionDoc && DocListCast(this.props.parent.fieldExtensionDoc.annotations);
+ },
+ (annotations: Doc[]) => {
+ annotations && annotations.length && this.renderAnnotations(annotations, true);
+ },
{ fireImmediately: true });
this._activeReactionDisposer = reaction(
@@ -155,7 +161,9 @@ class Viewer extends React.Component<IViewerProps> {
let scriptfield = Cast(this.props.parent.Document.filterScript, ScriptField);
this._script = scriptfield ? scriptfield.script : CompileScript("return true");
if (this.props.parent.props.ContainingCollectionView) {
- let ccvAnnos = DocListCast(this.props.parent.props.ContainingCollectionView.props.Document.annotations);
+ 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);
@@ -165,6 +173,7 @@ class Viewer extends React.Component<IViewerProps> {
}
});
}
+ this.Index = -1;
});
}
);
@@ -192,10 +201,14 @@ class Viewer extends React.Component<IViewerProps> {
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++) {
- await this.props.pdf.getPage(i + 1).then(page => runInAction(() => {
- // pageSizes[i] = { width: page.view[2] * scale, height: page.view[3] * scale };
- let x = page.getViewport(scale);
+ proms.push(this.props.pdf.getPage(i + 1).then(page => runInAction(() => {
+ pageSizes[i] = {
+ 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: }
@@ -204,9 +217,10 @@ class Viewer extends React.Component<IViewerProps> {
// // tcStr += t.str;
// // })
// });
- pageSizes[i] = { width: x.width, height: x.height };
- }));
+ // 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);
@@ -222,17 +236,22 @@ class Viewer extends React.Component<IViewerProps> {
}
}
+ @action
makeAnnotationDocument = (sourceDoc: Doc | undefined, s: number, color: string): Doc => {
let annoDocs: Doc[] = [];
- let mainAnnoDoc = Docs.CreateInstance(new Doc(), "", {});
+ let mainAnnoDoc = Docs.Create.InstanceFromProto(new Doc(), "", {});
mainAnnoDoc.title = "Annotation on " + StrCast(this.props.parent.Document.title);
- mainAnnoDoc.pdfDoc = this.props.parent.Document;
+ mainAnnoDoc.pdfDoc = this.props.parent.props.Document;
+ let minY = Number.MAX_VALUE;
this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => {
for (let anno of value) {
let annoDoc = new Doc();
if (anno.style.left) annoDoc.x = parseInt(anno.style.left) / scale;
- if (anno.style.top) annoDoc.y = parseInt(anno.style.top) / scale;
+ if (anno.style.top) {
+ annoDoc.y = parseInt(anno.style.top) / scale;
+ minY = Math.min(parseInt(anno.style.top), minY);
+ }
if (anno.style.height) annoDoc.height = parseInt(anno.style.height) / scale;
if (anno.style.width) annoDoc.width = parseInt(anno.style.width) / scale;
annoDoc.page = key;
@@ -245,12 +264,13 @@ class Viewer extends React.Component<IViewerProps> {
}
});
- mainAnnoDoc.y = Math.max((NumCast(annoDocs[0].y) * scale) - 100, 0);
+ mainAnnoDoc.y = Math.max(minY, 0);
mainAnnoDoc.annotations = new List<Doc>(annoDocs);
if (sourceDoc) {
DocUtils.MakeLink(sourceDoc, mainAnnoDoc, undefined, `Annotation from ${StrCast(this.props.parent.Document.title)}`, "", StrCast(this.props.parent.Document.title));
}
this._savedAnnotations.clear();
+ this.Index = -1;
return mainAnnoDoc;
}
@@ -258,13 +278,14 @@ class Viewer extends React.Component<IViewerProps> {
if (de.data instanceof DragManager.LinkDragData) {
let sourceDoc = de.data.linkSourceDocument;
let destDoc = this.makeAnnotationDocument(sourceDoc, 1, "red");
- let targetAnnotations = DocListCast(this.props.parent.Document.annotations);
+ de.data.droppedDocuments.push(destDoc);
+ let targetAnnotations = DocListCast(this.props.parent.fieldExtensionDoc.annotations);
if (targetAnnotations) {
targetAnnotations.push(destDoc);
- this.props.parent.Document.annotations = new List<Doc>(targetAnnotations);
+ this.props.parent.fieldExtensionDoc.annotations = new List<Doc>(targetAnnotations);
}
else {
- this.props.parent.Document.annotations = new List<Doc>([destDoc]);
+ this.props.parent.fieldExtensionDoc.annotations = new List<Doc>([destDoc]);
}
e.stopPropagation();
}
@@ -419,20 +440,8 @@ class Viewer extends React.Component<IViewerProps> {
}
}
- renderAnnotation = (anno: Doc, index: number): JSX.Element[] => {
- let annotationDocs = DocListCast(anno.annotations);
- let res = annotationDocs.map(a => {
- let type = NumCast(a.type);
- switch (type) {
- // case AnnotationTypes.Pin:
- // return <PinAnnotation parent={this} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />;
- case AnnotationTypes.Region:
- return <RegionAnnotation parent={this} document={a} index={index} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />;
- default:
- return <div></div>;
- }
- });
- return res;
+ renderAnnotation = (anno: Doc, index: number): JSX.Element => {
+ return <Annotation anno={anno} index={index} parent={this} key={`${anno[Id]}-annotation`} />;
}
@action
@@ -442,11 +451,7 @@ class Viewer extends React.Component<IViewerProps> {
@action
search = (searchString: string) => {
- if (searchString.length === 0) {
- return;
- }
-
- if (this._rendered) {
+ if (this._pdfViewer._pageViewsReady) {
this._pdfFindController.executeCommand('find',
{
caseSensitive: false,
@@ -459,6 +464,18 @@ class Viewer extends React.Component<IViewerProps> {
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
+ });
+ this._rendered = true;
+ });
container.addEventListener("pagerendered", () => {
console.log("rendered");
this._pdfFindController.executeCommand('find',
@@ -533,23 +550,23 @@ class Viewer extends React.Component<IViewerProps> {
if (!this._pdfFindController) {
if (container && viewer) {
let simpleLinkService = new SimpleLinkService();
- let pdfViewer = new PDFJSViewer.PDFViewer({
+ this._pdfViewer = new PDFJSViewer.PDFViewer({
container: container,
viewer: viewer,
linkService: simpleLinkService
});
simpleLinkService.setPdf(this.props.pdf);
container.addEventListener("pagesinit", () => {
- pdfViewer.currentScaleValue = 1;
+ this._pdfViewer.currentScaleValue = 1;
});
container.addEventListener("pagerendered", () => {
console.log("rendered");
this._rendered = true;
});
- pdfViewer.setDocument(this.props.pdf);
- this._pdfFindController = new PDFJSViewer.PDFFindController(pdfViewer);
+ this._pdfViewer.setDocument(this.props.pdf);
+ this._pdfFindController = new PDFJSViewer.PDFFindController(this._pdfViewer);
// this._pdfFindController._linkService = pdfLinkService;
- pdfViewer.findController = this._pdfFindController;
+ this._pdfViewer.findController = this._pdfFindController;
}
}
}
@@ -568,9 +585,10 @@ class Viewer extends React.Component<IViewerProps> {
prevAnnotation = (e: React.MouseEvent) => {
e.stopPropagation();
- if (this.Index > 0) {
- this.Index--;
- }
+ // if (this.Index > 0) {
+ // this.Index--;
+ // }
+ this.Index = Math.max(this.Index - 1, 0);
}
@action
@@ -578,7 +596,7 @@ class Viewer extends React.Component<IViewerProps> {
e.stopPropagation();
let compiled = this._script;
- if (this.Index < this._annotations.filter(anno => {
+ let filtered = this._annotations.filter(anno => {
if (compiled && compiled.compiled) {
let run = compiled.run({ this: anno });
if (run.success) {
@@ -586,9 +604,8 @@ class Viewer extends React.Component<IViewerProps> {
}
}
return true;
- }).length) {
- this.Index++;
- }
+ });
+ this.Index = Math.min(this.Index + 1, filtered.length - 1);
}
nextResult = () => {
@@ -636,7 +653,8 @@ class Viewer extends React.Component<IViewerProps> {
}
}
return true;
- }).map((anno: Doc, index: number) => this.renderAnnotation(anno, index))}
+ }).sort((a: Doc, b: Doc) => NumCast(a.y) - NumCast(b.y))
+ .map((anno: Doc, index: number) => this.renderAnnotation(anno, index))}
</div>
</div>
<div className="pdfViewer-overlayCont" onPointerDown={(e) => e.stopPropagation()}
@@ -647,7 +665,7 @@ class Viewer extends React.Component<IViewerProps> {
<button className="pdfViewer-overlayButton" title="Open Search Bar"></button>
{/* <button title="Previous Result" onClick={() => this.search(this._searchString)}><FontAwesomeIcon icon="arrow-up" size="3x" color="white" /></button>
<button title="Next Result" onClick={this.nextResult}><FontAwesomeIcon icon="arrow-down" size="3x" color="white" /></button> */}
- <input placeholder="Search" className="pdfViewer-overlaySearchBar" onChange={this.searchStringChanged} />
+ <input onKeyDown={(e: React.KeyboardEvent) => e.keyCode === KeyCodes.ENTER ? this.search(this._searchString) : e.keyCode === KeyCodes.BACKSPACE ? e.stopPropagation() : true} placeholder="Search" className="pdfViewer-overlaySearchBar" onChange={this.searchStringChanged} />
<button title="Search" onClick={() => this.search(this._searchString)}><FontAwesomeIcon icon="search" size="3x" color="white" /></button>
</div>
<button className="pdfViewer-overlayButton" onClick={this.prevAnnotation} title="Previous Annotation"
@@ -678,116 +696,6 @@ export enum AnnotationTypes {
Region
}
-interface IAnnotationProps {
- x: number;
- y: number;
- width: number;
- height: number;
- index: number;
- parent: Viewer;
- document: Doc;
-}
-
-@observer
-class RegionAnnotation extends React.Component<IAnnotationProps> {
- @observable private _backgroundColor: string = "red";
-
- private _reactionDisposer?: IReactionDisposer;
- private _scrollDisposer?: IReactionDisposer;
- private _mainCont: React.RefObject<HTMLDivElement>;
-
- constructor(props: IAnnotationProps) {
- super(props);
-
- this._mainCont = 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";
- }
- }
- },
- { fireImmediately: true }
- );
-
- this._scrollDisposer = reaction(
- () => this.props.parent.Index,
- () => {
- if (this.props.parent.Index === this.props.index) {
- this.props.parent.scrollTo(this.props.y - 50);
- }
- }
- );
- }
-
- componentWillUnmount() {
- this._reactionDisposer && this._reactionDisposer();
- this._scrollDisposer && this._scrollDisposer();
- }
-
- deleteAnnotation = () => {
- let annotation = DocListCast(this.props.parent.props.parent.Document.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.Document.annotations = new List<Doc>(newAnnotations);
- }
-
- if (group) {
- let groupAnnotations = DocListCast(group.annotations);
- groupAnnotations.forEach(anno => anno.delete = true);
- }
-
- PDFMenu.Instance.fadeOut(true);
- }
-
- @action
- onPointerDown = (e: React.PointerEvent) => {
- if (e.button === 0) {
- let targetDoc = Cast(this.props.document.target, Doc, null);
- if (targetDoc) {
- DocumentManager.Instance.jumpToDocument(targetDoc, true);
- }
- }
- if (e.button === 2) {
- PDFMenu.Instance.Status = "annotation";
- PDFMenu.Instance.Delete = this.deleteAnnotation.bind(this);
- PDFMenu.Instance.Pinned = false;
- PDFMenu.Instance.AddTag = this.addTag.bind(this);
- PDFMenu.Instance.jumpTo(e.clientX, e.clientY, true);
- }
- }
-
- addTag = (key: string, value: string): boolean => {
- let group = FieldValue(Cast(this.props.document.group, Doc));
- if (group) {
- let valNum = parseInt(value);
- group[key] = isNaN(valNum) ? value : valNum;
- return true;
- }
- return false;
- }
-
- render() {
- return (
- <div className="pdfViewer-annotationBox" onPointerDown={this.onPointerDown} ref={this._mainCont}
- style={{
- top: this.props.y * scale,
- left: this.props.x * scale,
- width: this.props.width * scale,
- height: this.props.height * scale,
- pointerEvents: "all",
- backgroundColor: this.props.parent.Index === this.props.index ? "goldenrod" : StrCast(this.props.document.color)
- }}></div>
- );
- }
-}
-
class SimpleLinkService {
externalLinkTarget: any = null;
externalLinkRel: any = null;
diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx
index 57e36be43..c9d442fe5 100644
--- a/src/client/views/pdf/Page.tsx
+++ b/src/client/views/pdf/Page.tsx
@@ -2,7 +2,7 @@ import { observer } from "mobx-react";
import React = require("react");
import { observable, action, runInAction, IReactionDisposer, reaction } from "mobx";
import * as Pdfjs from "pdfjs-dist";
-import { Opt, Doc, FieldResult, Field, DocListCast, WidthSym, HeightSym } from "../../../new_fields/Doc";
+import { Opt, Doc, FieldResult, Field, DocListCast, WidthSym, HeightSym, DocListCastAsync } from "../../../new_fields/Doc";
import "./PDFViewer.scss";
import "pdfjs-dist/web/pdf_viewer.css";
import { PDFBox } from "../nodes/PDFBox";
@@ -10,7 +10,7 @@ import { DragManager } from "../../util/DragManager";
import { Docs, DocUtils } from "../../documents/Documents";
import { List } from "../../../new_fields/List";
import { emptyFunction } from "../../../Utils";
-import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
import { listSpec } from "../../../new_fields/Schema";
import { menuBar } from "prosemirror-menu";
import { AnnotationTypes, PDFViewer, scale } from "./PDFViewer";
@@ -52,9 +52,11 @@ export default class Page extends React.Component<IPageProps> {
private _textLayer: React.RefObject<HTMLDivElement>;
private _annotationLayer: React.RefObject<HTMLDivElement>;
private _marquee: React.RefObject<HTMLDivElement>;
- private _curly: React.RefObject<HTMLImageElement>;
+ // private _curly: React.RefObject<HTMLImageElement>;
private _marqueeing: boolean = false;
private _reactionDisposer?: IReactionDisposer;
+ private _startY: number = 0;
+ private _startX: number = 0;
constructor(props: IPageProps) {
super(props);
@@ -62,7 +64,7 @@ export default class Page extends React.Component<IPageProps> {
this._textLayer = React.createRef();
this._annotationLayer = React.createRef();
this._marquee = React.createRef();
- this._curly = React.createRef();
+ // this._curly = React.createRef();
}
componentDidMount = (): void => {
@@ -138,9 +140,9 @@ 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.Document.annotations, listSpec(Doc));
+ let targetAnnotations = Cast(this.props.parent.fieldExtensionDoc.annotations, listSpec(Doc));
if (targetAnnotations === undefined) {
- Doc.GetProto(this.props.parent.Document).annotations = new List([annotationDoc]);
+ Doc.GetProto(this.props.parent.fieldExtensionDoc).annotations = new List([annotationDoc]);
} else {
targetAnnotations.push(annotationDoc);
}
@@ -152,20 +154,34 @@ export default class Page extends React.Component<IPageProps> {
* start a drag event and create or put the necessary info into the drag event.
*/
@action
- startDrag = (e: PointerEvent): void => {
+ 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.TextDocument({ width: 200, height: 200, title: "New Annotation" });
+ let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "New Annotation" });
targetDoc.targetPage = this.props.page;
- let annotationDoc = this.highlight(targetDoc, "red");
+ let annotationDoc = this.highlight(undefined, "red");
+ annotationDoc.linkedToDoc = false;
// create dragData and star tdrag
let dragData = new DragManager.AnnotationDragData(thisDoc, annotationDoc, targetDoc);
if (this._textLayer.current) {
- DragManager.StartAnnotationDrag([this._textLayer.current], dragData, e.pageX, e.pageY, {
+ DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, {
handlers: {
- dragComplete: emptyFunction,
+ dragComplete: async () => {
+ if (!(await annotationDoc.linkedToDoc)) {
+ let annotations = await DocListCastAsync(annotationDoc.annotations);
+ if (annotations) {
+ annotations.forEach(anno => {
+ anno.target = targetDoc;
+ });
+ }
+ let pdfDoc = await Cast(annotationDoc.pdfDoc, Doc);
+ if (pdfDoc) {
+ DocUtils.MakeLink(annotationDoc, targetDoc, undefined, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title));
+ }
+ }
+ }
},
hideSource: false
});
@@ -183,8 +199,11 @@ export default class Page extends React.Component<IPageProps> {
let doc = this.props.parent.Document;
let view = Doc.MakeAlias(doc);
let data = Doc.MakeDelegate(doc.proto!);
+ 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.startY = marquee.top + this.props.getScrollFromPage(this.props.page);
view.width = doc[WidthSym]();
let dragData = new DragManager.DocumentDragData([view], [undefined]);
@@ -217,8 +236,8 @@ export default class Page extends React.Component<IPageProps> {
let current = this._textLayer.current;
if (current) {
let boundingRect = current.getBoundingClientRect();
- this._marqueeX = (e.clientX - boundingRect.left) * (current.offsetWidth / boundingRect.width);
- this._marqueeY = (e.clientY - boundingRect.top) * (current.offsetHeight / boundingRect.height);
+ this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (current.offsetWidth / boundingRect.width);
+ this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (current.offsetHeight / boundingRect.height);
}
this._marqueeing = true;
if (this._marquee.current) this._marquee.current.style.opacity = "0.2";
@@ -241,12 +260,16 @@ export default class Page extends React.Component<IPageProps> {
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._marqueeX;
- this._marqueeHeight = (e.clientY - boundingRect.top) * (current.offsetHeight / boundingRect.height) - this._marqueeY;
+ 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) {
+ if (this._marquee.current /*&& this._curly.current*/) {
this._marquee.current.style.background = background;
- this._curly.current.style.opacity = opacity;
+ // this._curly.current.style.opacity = opacity;
this._rotate = transform;
}
}
@@ -259,33 +282,33 @@ export default class Page extends React.Component<IPageProps> {
}
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: background, opacity: opacity, transform: transform };
+ // 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
@@ -305,16 +328,17 @@ export default class Page extends React.Component<IPageProps> {
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.opacity = style.opacity;
- }
+ // if (opacity === "1" && this._curly.current) {
+ // copy.style.opacity = opacity;
+ // let img = this._curly.current.cloneNode();
+ // (img as any).style.opacity = opacity;
+ // (img as any).style.transform = transform;
+ // copy.appendChild(img);
+ // }
+ // else {
+ copy.style.border = style.border;
+ copy.style.opacity = style.opacity;
+ // }
copy.className = this._marquee.current.className;
this.props.createAnnotation(copy, this.props.page);
this._marquee.current.style.opacity = "0";
@@ -340,7 +364,7 @@ export default class Page extends React.Component<IPageProps> {
if (PDFMenu.Instance.Highlighting) {
- this.highlight(undefined, "#f4f442");
+ this.highlight(undefined, "goldenrod");
}
else {
PDFMenu.Instance.StartDrag = this.startDrag;
@@ -401,8 +425,8 @@ export default class Page extends React.Component<IPageProps> {
</div>
<div className="pdfInkingLayer-cont" ref={this._annotationLayer} style={{ width: "100%", height: "100%", position: "relative", top: "-100%" }}>
<div className="pdfViewer-annotationBox" ref={this._marquee}
- style={{ left: `${this._marqueeX}px`, top: `${this._marqueeY}px`, width: `${this._marqueeWidth}px`, height: `${this._marqueeHeight}px`, background: "transparent" }}>
- <img ref={this._curly} src="https://static.thenounproject.com/png/331760-200.png" style={{ width: "100%", height: "100%", transform: `${this._rotate}` }} />
+ 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` }} />