aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Kim <andrewdkim@users.noreply.github.com>2019-02-26 17:49:46 -0500
committerAndrew Kim <andrewdkim@users.noreply.github.com>2019-02-26 17:49:46 -0500
commit3898ac2a436a1a6a39b07afb419f433e02df5e29 (patch)
tree43ebbdba88422fc5538efa2663750da8b050266b
parent0320123a5d0836dd11324a7eed6757a453c793d9 (diff)
In process of making the conversion from ImageBox to PDFNode
-rw-r--r--src/Main.tsx3
-rw-r--r--src/documents/Documents.ts52
-rw-r--r--src/fields/PDFField.ts21
-rw-r--r--src/views/nodes/PDFNode.tsx399
4 files changed, 449 insertions, 26 deletions
diff --git a/src/Main.tsx b/src/Main.tsx
index dd8313ac1..03d44e0e5 100644
--- a/src/Main.tsx
+++ b/src/Main.tsx
@@ -38,8 +38,9 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) {
}), true)
-//runInAction(() =>
+//runInAction(() =>
{
+ let PDFDoc = Documents.PDFDocument("");
let doc1 = Documents.TextDocument({ title: "hello" });
let doc2 = doc1.MakeDelegate();
doc2.Set(KS.X, new NumberField(150));
diff --git a/src/documents/Documents.ts b/src/documents/Documents.ts
index 61d2e18b9..6dce4e8f0 100644
--- a/src/documents/Documents.ts
+++ b/src/documents/Documents.ts
@@ -11,6 +11,8 @@ import { ImageField } from "../fields/ImageField";
import { ImageBox } from "../views/nodes/ImageBox";
import { CollectionFreeFormView } from "../views/collections/CollectionFreeFormView";
import { FIELD_ID } from "../fields/Field";
+import {PDFField} from "../fields/PDFField";
+import {PDFNode} from "../views/nodes/PDFNode"
interface DocumentOptions {
x?: number;
@@ -127,31 +129,31 @@ export namespace Documents {
}
//for PDF
- //let PDFProtoId: FIELD_ID;
- //function GetPDFPrototype(): Document {
- //if (PDFProtoId === undefined) {
- //let PDFProto = new Document();
- //PDFProtoId = PDFProto.Id;
- //PDFProto.Set(KeyStore.Title, new TextField("PDF PROTO"));
- // PDFProto.Set(KeyStore.X, new NumberField(0));
- //PDFProto.Set(KeyStore.Y, new NumberField(0));
- //PDFProto.Set(KeyStore.Width, new NumberField(300));
- // PDFProto.Set(KeyStore.Height, new NumberField(300));
- // PDFProto.Set(KeyStore.Layout, new TextField(PDFBox.LayoutString()));
- //PDFProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
- //Server.AddDocument(PDFProto);
- // return PDFProto;
- //}
- //return Server.GetDocument(PDFProtoId, true)!;
- //}
-
- //export function PDFDocument(url: string, options: DocumentOptions = {}): Document{
- //let doc = GetPDFPrototype().MakeDelegate();
- //setupOptions(doc, options);
- //doc.Set(KeyStore.Data, new PDFField(new URL(url)));
- //Server.AddDocument(doc);
- //return Server.GetDocument(doc.Id, true);
- //}
+ let PDFProtoId: FIELD_ID;
+ function GetPDFPrototype(): Document {
+ if (PDFProtoId === undefined) {
+ let PDFProto = new Document();
+ PDFProtoId = PDFProto.Id;
+ PDFProto.Set(KeyStore.Title, new TextField("PDF PROTO"));
+ PDFProto.Set(KeyStore.X, new NumberField(0));
+ PDFProto.Set(KeyStore.Y, new NumberField(0));
+ PDFProto.Set(KeyStore.Width, new NumberField(300));
+ PDFProto.Set(KeyStore.Height, new NumberField(300));
+ PDFProto.Set(KeyStore.Layout, new TextField(PDFNode.LayoutString()));
+ PDFProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
+ Server.AddDocument(PDFProto);
+ return PDFProto;
+ }
+ return Server.GetDocument(PDFProtoId, true)!;
+ }
+
+ export function PDFDocument(url: string, options: DocumentOptions = {}): Document{
+ let doc = GetPDFPrototype().MakeDelegate();
+ setupOptions(doc, options);
+ doc.Set(KeyStore.Data, new PDFField(new URL(url)));
+ Server.AddDocument(doc);
+ return Server.GetDocument(doc.Id, true);
+ }
export function ImageDocument(url: string, options: DocumentOptions = {}): Document {
let doc = GetImagePrototype().MakeDelegate();
diff --git a/src/fields/PDFField.ts b/src/fields/PDFField.ts
new file mode 100644
index 000000000..2d8b0f635
--- /dev/null
+++ b/src/fields/PDFField.ts
@@ -0,0 +1,21 @@
+import { BasicField } from "./BasicField";
+import { Field } from "./Field";
+import {observable} from "mobx"
+
+export class PDFField extends BasicField<URL> {
+ constructor(data: URL | undefined = undefined) {
+ super(data == undefined ? new URL("http://cs.brown.edu/~bcz/face.gif") : data);
+ }
+
+ toString(): string {
+ return this.Data.href;
+ }
+
+ Copy(): Field {
+ return new PDFField(this.Data);
+ }
+
+ @observable
+ Page:Number = 1;
+
+} \ No newline at end of file
diff --git a/src/views/nodes/PDFNode.tsx b/src/views/nodes/PDFNode.tsx
new file mode 100644
index 000000000..e626dc748
--- /dev/null
+++ b/src/views/nodes/PDFNode.tsx
@@ -0,0 +1,399 @@
+import 'react-image-lightbox/style.css';
+import "./ImageBox.scss";
+import React = require("react")
+import { FieldViewProps, FieldView } from './FieldView';
+import { observer } from "mobx-react"
+import { observable, action } from 'mobx';
+import 'react-pdf/dist/Page/AnnotationLayer.css'
+//@ts-ignore
+import { Document, Page, PDFPageProxy, PageAnnotation} from "react-pdf";
+import { Utils } from '../../Utils';
+import { Sticky } from './Sticky'; //you should look at sticky and annotation, because they are used here
+import { Annotation } from './Annotation';
+
+/** ALSO LOOK AT: Annotation.tsx, Sticky.tsx
+ *
+ * Ok, so I know I built PDFNode on a ImageBox, but this method works... maybe make a duplicate
+ * and call it PDFNode.
+ * This method renders PDF and puts all kinds of functionalities such as annotation, highlighting,
+ * area selection (I call it stickies), embedded ink node for directly annotating using a pen or
+ * mouse, and pagination.
+ *
+ * Clearly, everything works perfectly. No bugs. Might as well publish it.
+ *
+ * ps watch out for some bugs. When highlighting, just highlight a section of one line... do not multiline highlight... plz
+ *
+ *
+ *
+ *
+ *
+ * HOW TO USE:
+ * AREA selection:
+ * 1) Click on Area button.
+ * 2) click on any part of the PDF, and drag to get desired sized area shape
+ * 3) You can write on the area (hence the reason why it's called sticky)
+ * 4) to make another area, you need to click on area button AGAIN.
+ *
+ * HIGHLIGHT: (Buggy. No multiline/multidiv text highlighting for now...)
+ * 1) just click and drag on a text
+ * 2) click highlight
+ * 3) for annotation, just pull your cursor over to that text
+ * 4) another method: click on highlight first and then drag on your desired text
+ * 5) To make another highlight, you need to reclick on the button
+ *
+ * Draw:
+ * 1) click draw and select color. then just draw like there's no tomorrow.
+ * 2) once you finish drawing your masterpiece, just reclick on the draw button to end your drawing session.
+ *
+ * Pagination:
+ * 1) click on arrows. You'll notice that stickies will stay in those page. But... highlights won't.
+ * 2) to test this out, make few area/stickies and then click on next page then come back. You'll see that they are all saved.
+ *
+ *
+ * written by: Andrew Kim
+ */
+@observer
+export class PDFNode extends React.Component<FieldViewProps> {
+
+ private _mainDiv = React.createRef<HTMLDivElement>()
+ private _pdf = React.createRef<HTMLCanvasElement>();
+
+ //very useful for keeping track of X and y position throughout the PDF Canvas
+ private initX:number = 0;
+ private initY:number = 0;
+
+ //checks if tool is on
+ private _toolOn:boolean = false; //checks if tool is on
+ private _pdfContext:any = null; //gets pdf context
+ private bool:Boolean = false; //general boolean debounce
+ private currSpan:any;//keeps track of current span (for highlighting)
+
+ private _currTool: any; //keeps track of current tool button reference
+ private _drawToolOn:boolean = false; //boolean that keeps track of the drawing tool
+ private _drawTool = React.createRef<HTMLButtonElement>()//drawing tool button reference
+
+ private _colorTool = React.createRef<HTMLButtonElement>(); //color button reference
+ private _currColor:string = "black"; //current color that user selected (for ink/pen)
+
+ private _highlightTool = React.createRef<HTMLButtonElement>(); //highlighter button reference
+ private _highlightToolOn:boolean = false;
+
+ @observable private stickies:any[] = [] //for storing CURRENT stickies
+ @observable private page:number = 1; //default is the first page.
+ @observable private numPage:number = 1; //default number of pages
+ @observable private stickiesPerPage: any = null; //for indexing stickies for EVERY PAGE
+ @observable private annotations:any[] = []; //keeps track of annotations
+
+ /**
+ * for pagination backwards
+ */
+ @action
+ onPageBack = () => {
+ if (this.page > 1){
+ this.page -= 1;
+ this.stickiesPerPage[this.page] = this.stickies; //stores previous sticky and indexes to stickiesPerPage
+ this.stickies = []; //sets stickies to null array
+ if (this.stickies){//checks stickies is null or not
+ this.stickies = this.stickiesPerPage[this.page - 1]; //pulls up stickies for this page
+ }
+
+ }
+ }
+
+ /**
+ * for pagination forwards
+ */
+ @action
+ onPageForward = () => {
+ if (this.page < this.numPage){
+ this.page += 1;
+ this.stickiesPerPage[this.page - 2] = this.stickies; //stores previous sticky and indexes to stickiesPerPage
+ this.stickies = []; //sets stickies to null array
+ if (this.stickiesPerPage[this.page - 1]){
+ this.stickies = this.stickiesPerPage[this.page - 1]; //pulls up sticky for this page
+ }
+ }
+ }
+
+ /**
+ * selection tool used for area highlighting (stickies). Kinda temporary
+ */
+ selectionTool = () => {
+ this._toolOn = true;
+ }
+ /**
+ * when user draws on the canvas. When mouse pointer is down
+ */
+ drawDown = (e:PointerEvent) => {
+ this.initX = e.offsetX;
+ this.initY = e.offsetY;
+ this._pdfContext.beginPath();
+ this._pdfContext.lineTo(this.initX, this.initY);
+ this._pdfContext.strokeStyle = this._currColor;
+ document.addEventListener("pointermove", this.drawMove);
+ document.addEventListener("pointerup", this.drawUp);
+
+ }
+
+ //when user drags
+ drawMove = (e: PointerEvent):void =>{
+ //x and y mouse movement
+ let x = this.initX += e.movementX,
+ y = this.initY += e.movementY;
+ //connects the point
+ this._pdfContext.lineTo(x, y);
+ this._pdfContext.stroke();
+ }
+
+ drawUp = (e:PointerEvent) => {
+ this._pdfContext.closePath();
+ document.removeEventListener("pointermove", this.drawMove);
+ document.removeEventListener("pointerdown", this.drawDown);
+ document.addEventListener("pointerdown", this.drawDown);
+ }
+
+
+ /**
+ * highlighting helper function
+ */
+ makeEditableAndHighlight = (colour:string) => {
+ var range, sel = window.getSelection();
+ if (sel.rangeCount && sel.getRangeAt) {
+ range = sel.getRangeAt(0);
+ }
+ document.designMode = "on";
+ if (!document.execCommand("HiliteColor", false, colour)) {
+ document.execCommand("HiliteColor", false, colour);
+ }
+
+ if (range) {
+ sel.removeAllRanges();
+ sel.addRange(range);
+ let element = range.commonAncestorContainer.parentElement
+ if (element){
+ let childNodes = element.childNodes;
+ childNodes.forEach((e) => {
+ if (e.nodeName == "SPAN"){
+ let span = e;
+ span.addEventListener("mouseover", this.onEnter);
+ }
+ })
+ }
+ }
+ document.designMode = "off";
+ }
+
+ /**
+ * when the cursor enters the highlight, it pops out annotation. ONLY WORKS FOR SINGLE DIV LINES
+ */
+ @action
+ onEnter = (e:any) => {
+ let span:HTMLSpanElement = e.toElement;
+ this.currSpan = span;
+ if (e.toElement instanceof HTMLSpanElement){
+ this.bool = true;
+ this.currSpan = span;
+ if(span.children.length == 0){ //this is why it only works for one div text lines... needs fix
+ if(span.offsetParent){
+ let div = span.offsetParent;
+ //@ts-ignore
+ let divX = div.style.left
+ //@ts-ignore
+ let divY = div.style.top
+ //slicing "px" from the end
+ divX = divX.slice(0, divX.length - 2); //gets X of the DIV element (parent of Span)
+ divY = divY.slice(0, divY.length - 2); //gets Y of the DIV element (parent of Span)
+ let annotation = <Annotation key ={Utils.GenerateGuid()} Span = {this.currSpan} X = {divX} Y = {divY - 300} />
+ this.annotations.push(annotation);
+ }
+ }
+ }
+ }
+
+ /**
+ * highlight function for highlighting actual text. This works fine.
+ */
+ highlight = (color:string) => {
+ if (window.getSelection()) {
+ try {
+ if (!document.execCommand("hiliteColor", false, color)) {
+ this.makeEditableAndHighlight(color);
+ }
+ //when the color is not the highlight color
+ } catch (ex) {
+ this.makeEditableAndHighlight(color)
+ }
+ }
+ }
+
+ /**
+ * controls the area highlighting (stickies) Kinda temporary
+ */
+ onPointerDown = (e: React.PointerEvent) => {
+ if (this._toolOn){
+ let mouse = e.nativeEvent;
+ this.initX = mouse.offsetX;
+ this.initY = mouse.offsetY;
+
+ }
+ }
+
+ /**
+ * controls area highlighting and partially highlighting. Kinda temporary
+ */
+ @action
+ onPointerUp = (e:React.PointerEvent) => {
+ if (this._highlightToolOn){
+ this.highlight("rgba(76, 175, 80, 0.3)"); //highlights to this default color.
+ this._highlightToolOn = false;
+ }
+ if (this._toolOn){
+ let mouse = e.nativeEvent;
+ let finalX = mouse.offsetX;
+ let finalY = mouse.offsetY;
+ let width = Math.abs(finalX - this.initX); //width
+ let height = Math.abs(finalY - this.initY); //height
+
+ //these two if statements are bidirectional dragging. You can drag from any point to another point and generate sticky
+ if (finalX < this.initX){
+ this.initX = finalX;
+ }
+ if (finalY < this.initY){
+ this.initY = finalY;
+ }
+
+ if (this._mainDiv.current){
+ let sticky = <Sticky key ={Utils.GenerateGuid()} Height = {height} Width = {width} X = {this.initX} Y = {this.initY}/>
+ this.stickies.push(sticky);
+ }
+ this._toolOn = false;
+ }
+
+ }
+
+ /**
+ * starts drawing the line when user presses down.
+ */
+ onDraw = () => {
+ if (this._currTool != null){
+ this._currTool.style.backgroundColor = "grey";
+ }
+ this._highlightToolOn = false;
+ if (this._drawTool.current){
+ this._currTool = this._drawTool.current;
+ if (this._drawToolOn){
+ this._drawToolOn = false;
+ document.removeEventListener("pointerdown", this.drawDown);
+ document.removeEventListener("pointerup", this.drawUp);
+ document.removeEventListener("pointermove", this.drawMove);
+ this._drawTool.current.style.backgroundColor = "grey";
+ } else {
+ this._drawToolOn = true;
+ document.addEventListener("pointerdown", this.drawDown);
+ this._drawTool.current.style.backgroundColor = "cyan";
+ }
+ }
+ }
+
+
+ /**
+ * for changing color (for ink/pen)
+ */
+ onColorChange = (e:React.PointerEvent) => {
+ if (e.currentTarget.innerHTML == "Red"){
+ this._currColor = "red";
+ } else if (e.currentTarget.innerHTML == "Blue"){
+ this._currColor = "blue";
+ } else if (e.currentTarget.innerHTML == "Green"){
+ this._currColor = "green";
+ } else if (e.currentTarget.innerHTML == "Black"){
+ this._currColor = "black";
+ }
+
+ }
+
+
+ /**
+ * For highlighting (text drag highlighting)
+ */
+ onHighlight = () => {
+ this._drawToolOn = false;
+ if (this._currTool != null){
+ this._currTool.style.backgroundColor = "grey";
+ }
+ if (this._highlightTool.current){
+ this._currTool = this._drawTool.current;
+ if (this._highlightToolOn){
+ this._highlightToolOn = false;
+ this._highlightTool.current.style.backgroundColor = "grey";
+ } else {
+ this._highlightToolOn = true;
+ this.highlight("rgba(76, 175, 80, 0.3)");
+ this._highlightTool.current.style.backgroundColor = "orange";
+ }
+ }
+ }
+
+
+ /**
+ * renders whole lot of shets, including pdf, stickies, and annotations.
+ */
+
+ render() {
+ return (
+ <div ref = {this._mainDiv}
+ onPointerDown ={this.onPointerDown}
+ onPointerUp = {this.onPointerUp}
+ >
+ {this.stickies.filter( () => { //for loading stickies (area)
+ return this.stickies[this.stickies.length - 1]
+ }).map( (element: any) => {
+ return element
+ })
+ }
+ {this.annotations.filter( () => { //for loading annotations
+ return this.annotations[this.annotations.length - 1]
+ }).map( (element: any) => {
+ return element
+ })
+ }
+
+ <button onClick = {this.onPageBack}>{"<"}</button>
+ <button onClick = {this.onPageForward}>{">"}</button>
+ <button onClick ={this.selectionTool}>{"Area"}</button>
+ <button style ={{color: "white", backgroundColor: "grey"}} onClick = {this.onHighlight} ref = {this._highlightTool}>Highlight</button>
+ <button style ={{color: "white", backgroundColor: "grey"}} ref = {this._drawTool} onClick = {this.onDraw}>{"Draw"}</button>
+ <button ref = {this._colorTool} onPointerDown = {this.onColorChange}>{"Red"}</button>
+ <button ref = {this._colorTool} onPointerDown = {this.onColorChange}>{"Blue"}</button>
+ <button ref = {this._colorTool} onPointerDown = {this.onColorChange}>{"Green"}</button>
+ <button ref = {this._colorTool} onPointerDown = {this.onColorChange}>{"Black"}</button>
+
+ <Document file={Utils.pdf_example}>
+ <Page
+ pageNumber={this.page}
+ onLoadSuccess={
+ (page:any) => {
+ if (this._mainDiv.current){
+ this._mainDiv.current.childNodes.forEach((element) => {
+ if (element.nodeName == "DIV"){
+ element.childNodes[0].childNodes.forEach((e) => {
+ if (e.nodeName == "CANVAS"){
+ //@ts-ignore
+ this._pdfContext = e.getContext("2d")
+ }
+ })
+ }
+ })
+ }
+ this.numPage = page.transport.numPages
+ if (this.stickiesPerPage == null){ //only runs once, when stickiesPerPage is null
+ this.stickiesPerPage = [...Array(this.numPage)].map(() => Array(1));
+ }
+ }
+ }
+ />
+ </Document>
+ </div>
+ );
+ }
+} \ No newline at end of file