import 'react-image-lightbox/style.css'; import "./ImageBox.scss"; import React = require("react") 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'; import { ObjectPositionProperty } from 'csstype'; import { keydownHandler } from 'prosemirror-keymap'; import { FieldViewProps, FieldView } from './FieldView'; /** ALSO LOOK AT: Annotation.tsx, Sticky.tsx * 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. * * * 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 { public static LayoutString() { return FieldView.LayoutString(PDFNode); } 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 perPage: Object[] = []; //stores pageInfo @observable pageInfo: any = { area: [], divs: [], anno: [] }; //divs is array of objects linked to anno @observable private page: number = 1; //default is the first page. @observable private numPage: number = 1; //default number of pages private _pdfCanvas: any; /** * for pagination backwards */ @action onPageBack = () => { if (this.page > 1) { this.page -= 1; this.currAnno = []; this.perPage[this.page] = this.pageInfo this.pageInfo = { area: [], divs: [], anno: [] }; //resets the object to default if (this.perPage[this.page - 1]) { this.pageInfo = this.perPage[this.page - 1]; } } } /** * for pagination forwards */ @action onPageForward = () => { if (this.page < this.numPage) { this.page += 1; this.currAnno = []; this.perPage[this.page - 2] = this.pageInfo; this.pageInfo = { area: [], divs: [], anno: [] }; //resets the object to default if (this.perPage[this.page - 1]) { this.pageInfo = this.perPage[this.page - 1]; } } } /** * 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; this._pdfCanvas.addEventListener("pointermove", this.drawMove); this._pdfCanvas.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(); this._pdfCanvas.removeEventListener("pointermove", this.drawMove); this._pdfCanvas.removeEventListener("pointerdown", this.drawDown); this._pdfCanvas.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 obj: Object = { parentDivs: [], spans: [] }; //@ts-ignore if (range.commonAncestorContainer.className == 'react-pdf__Page__textContent') { //multiline highlighting case obj = this.highlightNodes(range.commonAncestorContainer.childNodes) } else { //single line highlighting case let parentDiv = range.commonAncestorContainer.parentElement if (parentDiv) { if (parentDiv.className == 'react-pdf__Page__textContent') { //when highlight is overwritten obj = this.highlightNodes(parentDiv.childNodes) } else { parentDiv.childNodes.forEach((child) => { if (child.nodeName == 'SPAN') { //@ts-ignore obj.parentDivs.push(parentDiv) //@ts-ignore child.id = "highlighted" //@ts-ignore obj.spans.push(child) child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler } }) } } } this.pageInfo.divs.push(obj); } document.designMode = "off"; } highlightNodes = (nodes: NodeListOf) => { let temp = { parentDivs: [], spans: [] } nodes.forEach((div) => { div.childNodes.forEach((child) => { if (child.nodeName == 'SPAN') { //@ts-ignore temp.parentDivs.push(div) //@ts-ignore child.id = "highlighted" //@ts-ignore temp.spans.push(child) child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler } }) }) return temp; } /** * when the cursor enters the highlight, it pops out annotation. ONLY WORKS FOR SINGLE DIV LINES */ @observable private currAnno: any = [] @action onEnter = (e: any) => { let span: HTMLSpanElement = e.toElement; let index: any; this.pageInfo.divs.forEach((obj: any) => { obj.spans.forEach((element: any) => { if (element == span) { if (!index) { index = this.pageInfo.divs.indexOf(obj); } } }) }) if (this.pageInfo.anno.length >= index + 1) { if (this.currAnno.length == 0) { this.currAnno.push(this.pageInfo.anno[index]); } } else { if (this.currAnno.length == 0) { //if there are no current annotation 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.pageInfo.anno.push(annotation); this.currAnno.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); } } 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.pageInfo.area.push(sticky); } this._toolOn = false; } } /** * starts drawing the line when user presses down. */ onDraw = () => { if (this._currTool != null) { this._currTool.style.backgroundColor = "grey"; } if (this._drawTool.current) { this._currTool = this._drawTool.current; if (this._drawToolOn) { this._drawToolOn = false; this._pdfCanvas.removeEventListener("pointerdown", this.drawDown); this._pdfCanvas.removeEventListener("pointerup", this.drawUp); this._pdfCanvas.removeEventListener("pointermove", this.drawMove); this._drawTool.current.style.backgroundColor = "grey"; } else { this._drawToolOn = true; this._pdfCanvas.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._highlightTool.current.style.backgroundColor = "orange"; } } } /** * renders whole lot of shets, including pdf, stickies, and annotations. */ reHighlight = () => { let div = document.getElementsByClassName("react-pdf__Page__textContent"); if (div) { } } render() { return (
{this.pageInfo.area.filter(() => { return this.pageInfo.area }).map((element: any) => { return element }) } {this.currAnno.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") { this._pdfCanvas = e; //@ts-ignore this._pdfContext = e.getContext("2d") } }) } }) } this.numPage = page.transport.numPages if (this.perPage.length == 0) { //Makes sure it only runs once this.perPage = [...Array(this.numPage)] } } } />
); } }