From 094b766c7d3097180a6022273001d08672456ef8 Mon Sep 17 00:00:00 2001 From: Andrew Kim Date: Tue, 26 Feb 2019 19:20:23 -0500 Subject: transition from ImageBox to PDFNode. Annotations also now indexable per page, similar to stickies/area selection --- src/Main.tsx | 7 +- src/documents/Documents.ts | 8 +- src/views/nodes/Annotation.tsx | 12 +- src/views/nodes/DocumentView.tsx | 3 +- src/views/nodes/ImageBox.tsx | 386 +-------------------------------------- src/views/nodes/PDFNode.tsx | 52 ++++-- 6 files changed, 62 insertions(+), 406 deletions(-) (limited to 'src') diff --git a/src/Main.tsx b/src/Main.tsx index f29dd192e..e2e1137da 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -48,9 +48,10 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) { let doc2 = doc1.MakeDelegate(); doc2.Set(KS.X, new NumberField(150)); doc2.Set(KS.Y, new NumberField(20)); - let doc3 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { - x:0, y: 0, width: 500, height: 500,title: "cat 1" - }); + + //let doc3 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { + //x:0, y: 0, width: 500, height: 500,title: "cat 1" + //}); // const schemaDocs = Array.from(Array(5).keys()).map(v => Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { // x: 50 + 100 * v, y: 50, width: 100, height: 100, title: "cat" + v diff --git a/src/documents/Documents.ts b/src/documents/Documents.ts index 56ebbf565..657856801 100644 --- a/src/documents/Documents.ts +++ b/src/documents/Documents.ts @@ -135,14 +135,14 @@ export namespace Documents { 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.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.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 PDFProto; } return Server.GetDocument(PDFProtoId, true)!; } diff --git a/src/views/nodes/Annotation.tsx b/src/views/nodes/Annotation.tsx index 6929aea76..0ba05ba3d 100644 --- a/src/views/nodes/Annotation.tsx +++ b/src/views/nodes/Annotation.tsx @@ -8,6 +8,7 @@ interface IProps{ Span: HTMLSpanElement; X: number; Y: number; + Highlights: any[]; } @@ -47,8 +48,15 @@ export class Annotation extends React.Component { let nodesArray = this.props.Span.parentElement.childNodes; nodesArray.forEach((e) => { if (e == this.props.Span){ - if (this.props.Span.parentElement){ - e.remove(); + if (this.props.Span.parentElement){ + this.props.Highlights.forEach((item) => { + if (item == e){ + item.remove(); + } + }) + e.remove(); + + } } diff --git a/src/views/nodes/DocumentView.tsx b/src/views/nodes/DocumentView.tsx index 81353cd60..df97a0281 100644 --- a/src/views/nodes/DocumentView.tsx +++ b/src/views/nodes/DocumentView.tsx @@ -13,6 +13,7 @@ import { CollectionSchemaView } from "../collections/CollectionSchemaView"; import { CollectionViewBase, COLLECTION_BORDER_WIDTH } from "../collections/CollectionViewBase"; import { FormattedTextBox } from "../nodes/FormattedTextBox"; import { ImageBox } from "../nodes/ImageBox"; +import {PDFNode} from "../nodes/PDFNode"; import "./NodeView.scss"; import React = require("react"); const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this? @@ -141,7 +142,7 @@ export class DocumentView extends React.Component { return (
{ public static LayoutString() { return FieldView.LayoutString("ImageBox"); } - - private _mainDiv = React.createRef() - private _pdf = React.createRef(); - - //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()//drawing tool button reference - - private _colorTool = React.createRef(); //color button reference - private _currColor:string = "black"; //current color that user selected (for ink/pen) - - private _highlightTool = React.createRef(); //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 = - 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 = - 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 ( -
- {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 - }) - } - - - - - - - - - - - - - { - 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)); - } - } - } - /> - -
- ); - } } \ No newline at end of file diff --git a/src/views/nodes/PDFNode.tsx b/src/views/nodes/PDFNode.tsx index 814a59500..d55e4d6d7 100644 --- a/src/views/nodes/PDFNode.tsx +++ b/src/views/nodes/PDFNode.tsx @@ -12,9 +12,6 @@ import { Sticky } from './Sticky'; //you should look at sticky and annotation, b 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. @@ -22,10 +19,7 @@ import { Annotation } from './Annotation'; * 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 - * - * - * - * + * Annotations and Stickies save per page. Highlights do not. * * HOW TO USE: * AREA selection: @@ -54,7 +48,7 @@ import { Annotation } from './Annotation'; */ @observer export class PDFNode extends React.Component { - public static LayoutString() { return FieldView.LayoutString("ImageBox"); } + public static LayoutString() { return FieldView.LayoutString("PDFNode"); } private _mainDiv = React.createRef() private _pdf = React.createRef(); @@ -78,12 +72,17 @@ export class PDFNode extends React.Component { private _highlightTool = React.createRef(); //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 stickies:any[] = [] //for storing CURRENT stickies + @observable private stickiesPerPage: any = null; //for indexing stickies for EVERY PAGE @observable private annotations:any[] = []; //keeps track of annotations + @observable private annotationsPerPage: any = null; // for indexing annotations for EVERY PAGE + @observable private highlights:any[] = []; //keeps track of highlights. + @observable private highlightsPerPage:any = null; // for indexing highlights for EVERY PAGE + /** * for pagination backwards @@ -94,9 +93,20 @@ export class PDFNode extends React.Component { this.page -= 1; this.stickiesPerPage[this.page] = this.stickies; //stores previous sticky and indexes to stickiesPerPage this.stickies = []; //sets stickies to null array + this.annotationsPerPage[this.page] = this.annotations; + this.annotations = []; + this.highlightsPerPage[this.page] = this.highlights; + this.highlights = []; if (this.stickies){//checks stickies is null or not this.stickies = this.stickiesPerPage[this.page - 1]; //pulls up stickies for this page } + if (this.annotations){//checks if annotation is null or not + this.annotations = this.annotationsPerPage[this.page - 1]; + } + if (this.highlights){//checks if annotation is null or not + this.highlights = this.highlightsPerPage[this.page - 1]; + } + } } @@ -104,15 +114,28 @@ export class PDFNode extends React.Component { /** * 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 + this.annotationsPerPage[this.page - 2] = this.annotations; + this.annotations = []; + this.highlightsPerPage[this.page - 2] = this.highlights; + this.highlights = []; if (this.stickiesPerPage[this.page - 1]){ this.stickies = this.stickiesPerPage[this.page - 1]; //pulls up sticky for this page } + if (this.annotationsPerPage[this.page - 1]){ + this.annotations = this.annotationsPerPage[this.page - 1]; //similar to sticky, it binds to previous annotation. + } + if (this.annotationsPerPage[this.page - 1]){ + this.highlights = this.highlightsPerPage[this.page - 1]; //similar to sticky, it binds to previous highlight. + } + } } @@ -176,7 +199,8 @@ export class PDFNode extends React.Component { childNodes.forEach((e) => { if (e.nodeName == "SPAN"){ let span = e; - span.addEventListener("mouseover", this.onEnter); + this.highlights.push(span); //pushes span into highglights. + span.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler } }) } @@ -204,7 +228,7 @@ export class PDFNode extends React.Component { //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 = + let annotation = this.annotations.push(annotation); } } @@ -389,6 +413,8 @@ export class PDFNode extends React.Component { this.numPage = page.transport.numPages if (this.stickiesPerPage == null){ //only runs once, when stickiesPerPage is null this.stickiesPerPage = [...Array(this.numPage)].map(() => Array(1)); + this.annotationsPerPage = [...Array(this.numPage).map(()=> Array(1))]; + this.highlightsPerPage = [...Array(this.numPage).map(() => Array(1))]; } } } -- cgit v1.2.3-70-g09d2