From 6df3143ead642356b7823946e9710f840f3faa5a Mon Sep 17 00:00:00 2001 From: _stanleyyip <33562077+yipstanley@users.noreply.github.com> Date: Tue, 14 May 2019 16:02:37 -0400 Subject: argh --- src/client/views/nodes/PDFBox.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index cb27b3f1b..55a37883a 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -21,6 +21,7 @@ import { positionSchema } from "./DocumentView"; import { pageSchema } from "./ImageBox"; import { ImageField, PdfField } from "../../../new_fields/URLField"; import { InkingControl } from "../InkingControl"; +import { PDFViewer } from "../pdf/PDFViewer"; /** ALSO LOOK AT: Annotation.tsx, Sticky.tsx * This method renders PDF and puts all kinds of functionalities such as annotation, highlighting, @@ -354,10 +355,11 @@ export class PDFBox extends DocComponent(PdfDocumen } render() { trace(); + const pdfUrl = window.origin + RouteStore.corsProxy + "/https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf"; let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); return (
- {this.pdfRenderer} +
); } -- cgit v1.2.3-70-g09d2 From 2cc62cd88688ccdec8275fcaaba939d448f9baf5 Mon Sep 17 00:00:00 2001 From: Stanley Date: Sun, 19 May 2019 13:56:30 -0700 Subject: ugh --- src/client/views/nodes/PDFBox.tsx | 18 +++++++++--------- src/client/views/pdf/PDFBox2.tsx | 2 ++ 2 files changed, 11 insertions(+), 9 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 55a37883a..bf3f299bc 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -70,15 +70,15 @@ export class PDFBox extends DocComponent(PdfDocumen @computed private get thumbnailPage() { return Cast(this.props.Document.thumbnailPage, "number", -1); } componentDidMount() { - this._reactionDisposer = reaction( - () => [SelectionManager.SelectedDocuments().slice()], - () => { - if (this.curPage > 0 && this.thumbnailPage > 0 && this.curPage !== this.thumbnailPage && !this.props.isSelected()) { - this.saveThumbnail(); - this._interactive = true; - } - }, - { fireImmediately: true }); + // this._reactionDisposer = reaction( + // () => [SelectionManager.SelectedDocuments().slice()], + // () => { + // if (this.curPage > 0 && this.thumbnailPage > 0 && this.curPage !== this.thumbnailPage && !this.props.isSelected()) { + // this.saveThumbnail(); + // this._interactive = true; + // } + // }, + // { fireImmediately: true }); } diff --git a/src/client/views/pdf/PDFBox2.tsx b/src/client/views/pdf/PDFBox2.tsx index c3f5c19d8..71825c260 100644 --- a/src/client/views/pdf/PDFBox2.tsx +++ b/src/client/views/pdf/PDFBox2.tsx @@ -8,6 +8,7 @@ import { PDFViewer } from "./PDFViewer"; import { RouteStore } from "../../../server/RouteStore"; import { InkingControl } from "../InkingControl"; import { observer } from "mobx-react"; +import { trace } from "mobx"; type PdfDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>; const PdfDocument = makeInterface(positionSchema, pageSchema); @@ -17,6 +18,7 @@ export class PDFBox2 extends DocComponent(PdfDocume public static LayoutString() { return FieldView.LayoutString(PDFBox2); } render() { + trace(); const pdfUrl = "https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf"; let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool); return ( -- cgit v1.2.3-70-g09d2 From 3a9f1a40cb6bcd35783aa83e66c3e253812aa39d Mon Sep 17 00:00:00 2001 From: yipstanley Date: Sun, 19 May 2019 19:10:44 -0400 Subject: stacking ! --- src/client/documents/Documents.ts | 7 +- src/client/views/nodes/PDFBox.tsx | 296 ++----------------------------------- src/client/views/pdf/PDFViewer.tsx | 31 +--- 3 files changed, 21 insertions(+), 313 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 5752bb096..2df733fd5 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -33,12 +33,9 @@ import { DocServer } from "../DocServer"; import { StrokeData, InkField } from "../../new_fields/InkField"; import { dropActionType } from "../util/DragManager"; import { DateField } from "../../new_fields/DateField"; -<<<<<<< HEAD import { PDFBox2 } from "../views/pdf/PDFBox2"; -======= import { schema } from "prosemirror-schema-basic"; import { UndoManager } from "../util/UndoManager"; ->>>>>>> 01a223f2e6685506cc1e5db69e9062d5ff0d3246 export interface DocumentOptions { x?: number; @@ -173,8 +170,8 @@ export namespace Docs { return textProto; } function CreatePdfPrototype(): Doc { - let pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("annotations"), - { x: 0, y: 0, nativeWidth: 1200, width: 300, height: 300, backgroundLayout: PDFBox.LayoutString(), curPage: 1 }); + let pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", PDFBox.LayoutString(), + { x: 0, y: 0, width: 300, height: 300, backgroundLayout: PDFBox.LayoutString(), curPage: 1 }); return pdfProto; } function CreateWebPrototype(): Doc { diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 9b0207d0c..d74dc53c4 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -4,8 +4,8 @@ import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; import Measure from "react-measure"; //@ts-ignore -import { Document, Page } from "react-pdf"; -import 'react-pdf/dist/Page/AnnotationLayer.css'; +// import { Document, Page } from "react-pdf"; +// import 'react-pdf/dist/Page/AnnotationLayer.css'; import { RouteStore } from "../../../server/RouteStore"; import { Utils } from '../../../Utils'; import { Annotation } from './Annotation'; @@ -14,7 +14,7 @@ import "./PDFBox.scss"; import React = require("react"); import { SelectionManager } from "../../util/SelectionManager"; import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; -import { Opt } from "../../../new_fields/Doc"; +import { Opt, HeightSym, Doc } from "../../../new_fields/Doc"; import { DocComponent } from "../DocComponent"; import { makeInterface } from "../../../new_fields/Schema"; import { positionSchema } from "./DocumentView"; @@ -53,300 +53,26 @@ const PdfDocument = makeInterface(positionSchema, pageSchema); export class PDFBox extends DocComponent(PdfDocument) { public static LayoutString() { return FieldView.LayoutString(PDFBox); } - private _mainDiv = React.createRef(); - private renderHeight = 2400; - - @observable private _renderAsSvg = true; @observable private _alt = false; - - private _reactionDisposer?: IReactionDisposer; - - @observable private _perPageInfo: Object[] = []; //stores pageInfo - @observable private _pageInfo: any = { area: [], divs: [], anno: [] }; //divs is array of objects linked to anno - - @observable private _currAnno: any = []; @observable private _interactive: boolean = false; - @observable private _loaded: boolean = false; - - @computed private get curPage() { return NumCast(this.Document.curPage, 1); } - @computed private get thumbnailPage() { return NumCast(this.props.Document.thumbnailPage, -1); } - - componentDidMount() { - let wasSelected = false; - this._reactionDisposer = reaction( - () => this.props.isSelected(), - () => { - if (this.curPage > 0 && this.curPage !== this.thumbnailPage && wasSelected && !this.props.isSelected()) { - this.saveThumbnail(); - } - wasSelected = this._interactive = this.props.isSelected(); - }, - { fireImmediately: true }); - - } - - componentWillUnmount() { - if (this._reactionDisposer) this._reactionDisposer(); - } - - /** - * highlighting helper function - */ - makeEditableAndHighlight = (colour: string) => { - var range, sel = window.getSelection(); - if (sel && 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) { - 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 - */ - @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 && !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); - } + getHeight = (): number => { + if (this.props.Document) { + let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document; + console.log(doc); + return NumCast(doc.height); } + return 0; } - /** - * controls the area highlighting (stickies) Kinda temporary - */ - onPointerDown = (e: React.PointerEvent) => { - if (this.props.isSelected() && !InkingControl.Instance.selectedTool && e.buttons === 1) { - if (e.altKey) { - this._alt = true; - } else { - if (e.metaKey) { - e.stopPropagation(); - } - } - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointerup", this.onPointerUp); - } - if (this.props.isSelected() && e.buttons === 2) { - runInAction(() => this._alt = true); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointerup", this.onPointerUp); - } - } - - /** - * controls area highlighting and partially highlighting. Kinda temporary - */ - @action - onPointerUp = (e: PointerEvent) => { - this._alt = false; - document.removeEventListener("pointerup", this.onPointerUp); - if (this.props.isSelected()) { - this.highlight("rgba(76, 175, 80, 0.3)"); //highlights to this default color. - } - this._interactive = true; - } - - - @action - saveThumbnail = () => { - this._renderAsSvg = false; - setTimeout(() => { - let nwidth = FieldValue(this.Document.nativeWidth, 0); - let nheight = FieldValue(this.Document.nativeHeight, 0); - htmlToImage.toPng(this._mainDiv.current!, { width: nwidth, height: nheight, quality: 1 }) - .then(action((dataUrl: string) => { - this.props.Document.thumbnail = new ImageField(new URL(dataUrl)); - this.props.Document.thumbnailPage = FieldValue(this.Document.curPage, -1); - this._renderAsSvg = true; - })) - .catch(function (error: any) { - console.error('oops, something went wrong!', error); - }); - }, 1250); - } - - @action - onLoaded = (page: any) => { - // bcz: the number of pages should really be set when the document is imported. - this.props.Document.numPages = page._transport.numPages; - if (this._perPageInfo.length === 0) { //Makes sure it only runs once - this._perPageInfo = [...Array(page._transport.numPages)]; - } - this._loaded = true; - } - - @action - setScaling = (r: any) => { - // bcz: the nativeHeight should really be set when the document is imported. - // also, the native dimensions could be different for different pages of the canvas - // so this design is flawed. - var nativeWidth = FieldValue(this.Document.nativeWidth, 0); - if (!FieldValue(this.Document.nativeHeight, 0)) { - var nativeHeight = nativeWidth * r.offset.height / r.offset.width; - this.props.Document.height = nativeHeight / nativeWidth * FieldValue(this.Document.width, 0); - this.props.Document.nativeHeight = nativeHeight; - } - } - @computed - get pdfPage() { - return ; - } - @computed - get pdfContent() { - trace(); - let pdfUrl = Cast(this.props.Document[this.props.fieldKey], PdfField); - if (!pdfUrl) { - return

No pdf url to render

; - } - let pdfpage = this.pdfPage; - let body = this.Document.nativeHeight ? - pdfpage : - - {({ measureRef }) => -
- {pdfpage} -
- } -
; - let xf = (this.Document.nativeHeight || 0) / this.renderHeight; - return
- - {body} - -
; - } - - @computed - get pdfRenderer() { - let proxy = this._loaded ? (null) : this.imageProxyRenderer; - let pdfUrl = Cast(this.props.Document[this.props.fieldKey], PdfField); - if ((!this._interactive && proxy) || !pdfUrl) { - return proxy; - } - return [ - this._pageInfo.area.filter(() => this._pageInfo.area).map((element: any) => element), - this._currAnno.map((element: any) => element), - this.pdfContent, - proxy - ]; - } - - @computed - get imageProxyRenderer() { - let thumbField = this.props.Document.thumbnail; - if (thumbField) { - let path = this.thumbnailPage !== this.curPage ? "https://image.flaticon.com/icons/svg/66/66163.svg" : - thumbField instanceof ImageField ? thumbField.url.href : "http://cs.brown.edu/people/bcz/prairie.jpg"; - return ; - } - return (null); - } - @action onKeyDown = (e: React.KeyboardEvent) => e.key === "Alt" && (this._alt = true); - @action onKeyUp = (e: React.KeyboardEvent) => e.key === "Alt" && (this._alt = false); render() { trace(); const pdfUrl = window.origin + RouteStore.corsProxy + "/https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf"; let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); return ( -
+
e.stopPropagation()} className={classname}> -
+
); } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 1b445eae4..26becebf1 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -5,6 +5,7 @@ import { RouteStore } from "../../../server/RouteStore"; import * as Pdfjs from "pdfjs-dist"; import { Opt } from "../../../new_fields/Doc"; import "./PDFViewer.scss"; +import "pdfjs-dist/web/pdf_viewer.css"; interface IPDFViewerProps { url: string; @@ -52,8 +53,8 @@ class Viewer extends React.Component { pdf={this.props.pdf} page={i} numPages={numPages} - key={`${this.props.pdf ? this.props.pdf.fingerprint + `page${i + 1}` : "undefined"}`} - name={`${this.props.pdf ? this.props.pdf.fingerprint + `page${i + 1}` : "undefined"}`} + key={`${this.props.pdf ? this.props.pdf.fingerprint + `-page${i + 1}` : "undefined"}`} + name={`${this.props.pdf ? this.props.pdf.fingerprint + `-page${i + 1}` : "undefined"}`} {...this.props} /> ))} } @@ -149,24 +150,12 @@ class Page extends React.Component { } } - @action - prevPage = (e: React.MouseEvent) => { - if (this._currPage > 2 && this._state !== "rendering") { - this._currPage = Math.max(this._currPage - 1, 1); - this._page = undefined; - this.loadPage(this.props.pdf!); - this._state = "rendering"; - } + onPointerDown = (e: React.PointerEvent) => { + e.stopPropagation(); } - @action - nextPage = (e: React.MouseEvent) => { - if (this._currPage < this.props.numPages - 1 && this._state !== "rendering") { - this._currPage = Math.min(this._currPage + 1, this.props.numPages) - this._page = undefined; - this.loadPage(this.props.pdf!); - this._state = "rendering"; - } + onPointerMove = (e: React.PointerEvent) => { + e.stopPropagation(); } render() { @@ -175,11 +164,7 @@ class Page extends React.Component {
-
- {/*
-
<
-
>
-
*/} +
); } -- cgit v1.2.3-70-g09d2 From e815a45a50e215e1a5c3ada6404226326a8530a9 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Mon, 20 May 2019 04:02:23 -0400 Subject: YES --- src/client/views/nodes/PDFBox.tsx | 21 ++++- src/client/views/pdf/PDFViewer.scss | 4 + src/client/views/pdf/PDFViewer.tsx | 165 +++++++++++++++++++++++++++++++++--- 3 files changed, 176 insertions(+), 14 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index d74dc53c4..62c8e1c99 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -54,7 +54,7 @@ export class PDFBox extends DocComponent(PdfDocumen public static LayoutString() { return FieldView.LayoutString(PDFBox); } @observable private _alt = false; - @observable private _interactive: boolean = false; + @observable private _scrollY: number = 0; getHeight = (): number => { if (this.props.Document) { @@ -65,13 +65,28 @@ export class PDFBox extends DocComponent(PdfDocumen return 0; } + loaded = (nw: number, nh: number) => { + if (this.props.Document) { + let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document; + doc.nativeWidth = nw; + doc.nativeHeight = nh; + } + } + + @action + onScroll = (e: React.UIEvent) => { + if (e.currentTarget) { + this._scrollY = e.currentTarget.scrollTop; + } + } + render() { trace(); const pdfUrl = window.origin + RouteStore.corsProxy + "/https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf"; let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); return ( -
e.stopPropagation()} className={classname}> - +
e.stopPropagation()} className={classname}> +
); } diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index d8ff06406..3a6045317 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -16,4 +16,8 @@ border-radius: 5px; } +.textLayer { + user-select: auto; +} + .viewer {} \ No newline at end of file diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 26becebf1..6201f6330 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -1,14 +1,18 @@ import { observer } from "mobx-react"; import React = require("react"); -import { observable, action, runInAction } from "mobx"; +import { observable, action, runInAction, computed } from "mobx"; import { RouteStore } from "../../../server/RouteStore"; import * as Pdfjs from "pdfjs-dist"; import { Opt } from "../../../new_fields/Doc"; import "./PDFViewer.scss"; import "pdfjs-dist/web/pdf_viewer.css"; +import { number } from "prop-types"; +import { JSXElement } from "babel-types"; interface IPDFViewerProps { url: string; + loaded: (nw: number, nh: number) => void; + scrollY: number; } @observer @@ -27,11 +31,9 @@ export class PDFViewer extends React.Component { } render() { - console.log("PDFVIEWER"); - console.log(this._pdf); return (
- +
); } @@ -39,25 +41,166 @@ export class PDFViewer extends React.Component { interface IViewerProps { pdf: Opt; + loaded: (nw: number, nh: number) => void; + scrollY: number; } +type PDFItem = React.Component | HTMLDivElement; + +@observer class Viewer extends React.Component { + @observable.shallow private _visibleElements: JSX.Element[] = []; + @observable private _isPage: boolean[] = []; + @observable private _pageSizes: { width: number, height: number }[] = []; + @observable private _startIndex: number = 0; + @observable private _endIndex: number = 1; + @observable private _loaded: boolean = false; + @observable private _pdf: Opt; + + private _pageBuffer: number = 1; + + @computed get scrollY(): number { + return this.props.scrollY; + } + + @computed get startIndex(): number { + return Math.max(0, this.getIndex(this.scrollY) - this._pageBuffer); + } + + @computed get endIndex(): number { + let width = this._pageSizes.map(i => i.width); + return Math.min(this.props.pdf ? this.props.pdf.numPages - 1 : 0, this.getIndex(this.scrollY + Math.max(...width)) + this._pageBuffer); + } + + componentDidMount = () => { + let numPages = this.props.pdf ? this.props.pdf.numPages : 0; + this.renderPages(0, numPages - 1, true); + } + + componentDidUpdate = (prevProps: IViewerProps) => { + if (this.scrollY !== prevProps.scrollY || this._pdf !== this.props.pdf) { + this._pdf = this.props.pdf; + this.renderPages(this.startIndex, this.endIndex); + } + } + + @action + renderPages = (startIndex: number, endIndex: number, forceRender: boolean = false) => { + let numPages = this.props.pdf ? this.props.pdf.numPages : 0; + + if (this._visibleElements.length !== numPages) { + let divs = Array.from(Array(numPages).keys()).map(i => ( + + )); + let arr = Array.from(Array(numPages).keys()).map(i => false); + this._visibleElements.push(...divs); + this._isPage.push(...arr); + } + + if (startIndex === this._startIndex && endIndex === this._endIndex && !forceRender) { + return; + } + + for (let i = startIndex; i <= endIndex; i++) { + if (this._isPage[i] && forceRender) { + this._visibleElements[i] = ( + + ); + this._isPage[i] = true; + } + else if (!this._isPage[i]) { + this._visibleElements[i] = ( + + ); + this._isPage[i] = true; + } + } + + for (let i = 0; i < numPages; i++) { + if (i < startIndex || i > endIndex) { + if (this._isPage[i]) { + this._visibleElements[i] = ( +
+ ); + this._isPage[i] = false; + } + } + } + + return; + } + + getIndex = (vOffset: number) => { + if (this._loaded) { + let index = 0; + let currOffset = vOffset; + while (currOffset - this._pageSizes[index].height > 0) { + currOffset -= this._pageSizes[index].height; + index++; + } + return index; + } + return 0; + } + + @action + pageLoaded = (index: number, page: Pdfjs.PDFPageViewport): void => { + let numPages = this.props.pdf ? this.props.pdf.numPages : 0; + this.props.loaded(page.width, page.height); + if (index > this._pageSizes.length) { + this._pageSizes.push({ width: page.width, height: page.height }); + } + else { + this._pageSizes[index - 1] = { width: page.width, height: page.height }; + } + if (index === numPages) { + this._loaded = true; + let divs = Array.from(Array(numPages).keys()).map(i => ( +
+ )); + this._visibleElements = new Array(...divs); + } + } + render() { - console.log("VIEWER"); + console.log(`START: ${this.startIndex}`); + console.log(`END: ${this.endIndex}`) let numPages = this.props.pdf ? this.props.pdf.numPages : 0; - console.log(numPages); return (
- {Array.from(Array(numPages).keys()).map((i) => ( + {/* {Array.from(Array(numPages).keys()).map((i) => ( - ))} } + ))} */} + {this._visibleElements}
); } @@ -68,6 +211,7 @@ interface IPageProps { name: string; numPages: number; page: number; + pageLoaded: (index: number, page: Pdfjs.PDFPageViewport) => void; } @observer @@ -113,12 +257,10 @@ class Page extends React.Component { pdf.getPage(this._currPage).then( (page: Pdfjs.PDFPageProxy) => { - console.log("PAGE"); - console.log(page); this._state = "rendering"; this.renderPage(page); } - ) + ); } @action @@ -132,6 +274,7 @@ class Page extends React.Component { this._width = viewport.width; canvas.height = viewport.height; this._height = viewport.height; + this.props.pageLoaded(this._currPage, viewport); if (context) { page.render({ canvasContext: context, viewport: viewport }); page.getTextContent().then((res: Pdfjs.TextContent) => { -- cgit v1.2.3-70-g09d2 From 1fb7a7bc185c1ba9bbe0f21ad5e16cf19235b2da Mon Sep 17 00:00:00 2001 From: yipstanley Date: Tue, 4 Jun 2019 13:59:04 -0400 Subject: updatesss --- src/client/views/nodes/PDFBox.tsx | 2 +- src/client/views/pdf/PDFViewer.scss | 6 +++- src/client/views/pdf/PDFViewer.tsx | 59 +++++++++++++++++++++++++++++-------- 3 files changed, 53 insertions(+), 14 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 62c8e1c99..dd945b030 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -86,7 +86,7 @@ export class PDFBox extends DocComponent(PdfDocumen let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); return (
e.stopPropagation()} className={classname}> - +
); } diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index 3a6045317..9d41a1bb0 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -1,4 +1,8 @@ -.canvasContainer {} +.textLayer { + div { + user-select: text; + } +} .viewer-button-cont { position: absolute; diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 6201f6330..d510ba91c 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -1,18 +1,24 @@ import { observer } from "mobx-react"; import React = require("react"); -import { observable, action, runInAction, computed } from "mobx"; +import { observable, action, runInAction, computed, IReactionDisposer, reaction } from "mobx"; import { RouteStore } from "../../../server/RouteStore"; import * as Pdfjs from "pdfjs-dist"; +import * as htmlToImage from "html-to-image"; import { Opt } from "../../../new_fields/Doc"; import "./PDFViewer.scss"; import "pdfjs-dist/web/pdf_viewer.css"; import { number } from "prop-types"; import { JSXElement } from "babel-types"; +import { PDFBox } from "../nodes/PDFBox"; +import { NumCast, FieldValue } from "../../../new_fields/Types"; +import { SearchBox } from "../SearchBox"; +import { Utils } from "../../../Utils"; interface IPDFViewerProps { url: string; loaded: (nw: number, nh: number) => void; scrollY: number; + parent: PDFBox; } @observer @@ -33,7 +39,7 @@ export class PDFViewer extends React.Component { render() { return (
- +
); } @@ -43,10 +49,9 @@ interface IViewerProps { pdf: Opt; loaded: (nw: number, nh: number) => void; scrollY: number; + parent: PDFBox; } -type PDFItem = React.Component | HTMLDivElement; - @observer class Viewer extends React.Component { @observable.shallow private _visibleElements: JSX.Element[] = []; @@ -56,8 +61,41 @@ class Viewer extends React.Component { @observable private _endIndex: number = 1; @observable private _loaded: boolean = false; @observable private _pdf: Opt; + @observable private _renderAsSvg = true; private _pageBuffer: number = 1; + private _reactionDisposer?: IReactionDisposer; + private _mainDiv = React.createRef(); + + @computed private get thumbnailPage() { return NumCast(this.props.parent.Document.thumbnailPage, -1); } + + componentDidMount() { + let wasSelected = this.props.parent.props.isSelected(); + this._reactionDisposer = reaction( + () => [this.props.parent.props.isSelected(), this.startIndex], + () => { + if (this.startIndex > 0 && !this.props.parent.props.isTopMost && this.startIndex !== this.thumbnailPage && wasSelected && !this.props.parent.props.isSelected()) { + this.saveThumbnail(); + } + wasSelected = this.props.parent.props.isSelected(); + }, + { fireImmediately: true } + ); + let numPages = this.props.pdf ? this.props.pdf.numPages : 0; + this.renderPages(0, numPages - 1, true); + } + + saveThumbnail = () => { + this.props.parent.props.Document.thumbnailPage = FieldValue(this.props.parent.Document.curPage, -1); + this._renderAsSvg = false; + setTimeout(() => { + let nwidth = FieldValue(this.props.parent.Document.nativeWidth, 0); + let nheight = FieldValue(this.props.parent.Document.nativeHeight, 0); + htmlToImage.toPng(this._mainDiv.current!, { width: nwidth, height: nheight, quality: 0.8 }) + .then(action((dataUrl: string) => { + })); + }, 1250); + } @computed get scrollY(): number { return this.props.scrollY; @@ -72,11 +110,6 @@ class Viewer extends React.Component { return Math.min(this.props.pdf ? this.props.pdf.numPages - 1 : 0, this.getIndex(this.scrollY + Math.max(...width)) + this._pageBuffer); } - componentDidMount = () => { - let numPages = this.props.pdf ? this.props.pdf.numPages : 0; - this.renderPages(0, numPages - 1, true); - } - componentDidUpdate = (prevProps: IViewerProps) => { if (this.scrollY !== prevProps.scrollY || this._pdf !== this.props.pdf) { this._pdf = this.props.pdf; @@ -188,7 +221,7 @@ class Viewer extends React.Component { console.log(`END: ${this.endIndex}`) let numPages = this.props.pdf ? this.props.pdf.numPages : 0; return ( -
+
{/* {Array.from(Array(numPages).keys()).map((i) => ( { } onPointerDown = (e: React.PointerEvent) => { + console.log("down"); e.stopPropagation(); } onPointerMove = (e: React.PointerEvent) => { + console.log("move") e.stopPropagation(); } render() { return ( -
+
-
+
); } -- cgit v1.2.3-70-g09d2 From cffe1d2d45e241a21fb071573399200567738163 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Tue, 4 Jun 2019 15:45:33 -0400 Subject: additionsss --- src/client/views/nodes/PDFBox.tsx | 10 +----- src/client/views/pdf/PDFViewer.tsx | 63 ++++++++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 22 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 83f69f7f9..217d7b5e1 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -7,23 +7,15 @@ import Measure from "react-measure"; // import { Document, Page } from "react-pdf"; // import 'react-pdf/dist/Page/AnnotationLayer.css'; import { RouteStore } from "../../../server/RouteStore"; -import { Utils } from '../../../Utils'; -import { DocServer } from "../../DocServer"; import { DocComponent } from "../DocComponent"; import { InkingControl } from "../InkingControl"; -import { SearchBox } from "../SearchBox"; -import { Annotation } from './Annotation'; import { positionSchema } from "./DocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; -var path = require('path'); import React = require("react"); -import { SelectionManager } from "../../util/SelectionManager"; -import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; -import { Opt, HeightSym, Doc } from "../../../new_fields/Doc"; +import { NumCast } from "../../../new_fields/Types"; import { makeInterface } from "../../../new_fields/Schema"; -import { ImageField, PdfField } from "../../../new_fields/URLField"; import { PDFViewer } from "../pdf/PDFViewer"; /** ALSO LOOK AT: Annotation.tsx, Sticky.tsx diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index d510ba91c..999cb6378 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -13,6 +13,10 @@ import { PDFBox } from "../nodes/PDFBox"; import { NumCast, FieldValue } from "../../../new_fields/Types"; import { SearchBox } from "../SearchBox"; import { Utils } from "../../../Utils"; +import { Id } from "../../../new_fields/FieldSymbols"; +import { DocServer } from "../../DocServer"; +import { ImageField } from "../../../new_fields/URLField"; +var path = require("path"); interface IPDFViewerProps { url: string; @@ -24,6 +28,7 @@ interface IPDFViewerProps { @observer export class PDFViewer extends React.Component { @observable _pdf: Opt; + private _mainDiv = React.createRef(); @action componentDidMount() { @@ -38,8 +43,8 @@ export class PDFViewer extends React.Component { render() { return ( -
- +
+
); } @@ -50,6 +55,7 @@ interface IViewerProps { loaded: (nw: number, nh: number) => void; scrollY: number; parent: PDFBox; + mainCont: React.RefObject; } @observer @@ -61,20 +67,19 @@ class Viewer extends React.Component { @observable private _endIndex: number = 1; @observable private _loaded: boolean = false; @observable private _pdf: Opt; - @observable private _renderAsSvg = true; + @observable private _renderAsSvg = false; private _pageBuffer: number = 1; private _reactionDisposer?: IReactionDisposer; - private _mainDiv = React.createRef(); - @computed private get thumbnailPage() { return NumCast(this.props.parent.Document.thumbnailPage, -1); } + @computed private get thumbnailY() { return NumCast(this.props.parent.Document.thumbnailY, -1); } - componentDidMount() { + componentDidMount = () => { let wasSelected = this.props.parent.props.isSelected(); this._reactionDisposer = reaction( () => [this.props.parent.props.isSelected(), this.startIndex], () => { - if (this.startIndex > 0 && !this.props.parent.props.isTopMost && this.startIndex !== this.thumbnailPage && wasSelected && !this.props.parent.props.isSelected()) { + if (this.startIndex >= 0 && !this.props.parent.props.isTopMost && this.scrollY !== this.thumbnailY && wasSelected && !this.props.parent.props.isSelected()) { this.saveThumbnail(); } wasSelected = this.props.parent.props.isSelected(); @@ -86,14 +91,23 @@ class Viewer extends React.Component { } saveThumbnail = () => { - this.props.parent.props.Document.thumbnailPage = FieldValue(this.props.parent.Document.curPage, -1); + this.props.parent.props.Document.thumbnailY = FieldValue(this.scrollY, 0); this._renderAsSvg = false; setTimeout(() => { let nwidth = FieldValue(this.props.parent.Document.nativeWidth, 0); - let nheight = FieldValue(this.props.parent.Document.nativeHeight, 0); - htmlToImage.toPng(this._mainDiv.current!, { width: nwidth, height: nheight, quality: 0.8 }) + htmlToImage.toPng(this.props.mainCont.current!, { width: nwidth, height: nwidth, quality: 0.8, }) .then(action((dataUrl: string) => { - })); + SearchBox.convertDataUri(dataUrl, `icon${this.props.parent.Document[Id]}_${this.startIndex}`).then((returnedFilename) => { + if (returnedFilename) { + let url = DocServer.prepend(returnedFilename); + this.props.parent.props.Document.thumbnail = new ImageField(new URL(url)); + } + runInAction(() => this._renderAsSvg = true); + }); + })) + .catch(function (error: any) { + console.error("Oops, something went wrong!", error); + }); }, 1250); } @@ -101,6 +115,29 @@ class Viewer extends React.Component { return this.props.scrollY; } + @computed get imageProxyRenderer() { + let thumbField = this.props.parent.props.Document.thumbnail; + if (thumbField && this._renderAsSvg && NumCast(this.props.parent.props.Document.startY, 0) === this.scrollY) { + let pw = typeof this.props.parent.props.PanelWidth === "function" ? this.props.parent.props.PanelWidth() : typeof this.props.parent.props.PanelWidth === "number" ? (this.props.parent.props.PanelWidth as any) as number : 50; + let path = thumbField instanceof ImageField ? thumbField.url.href : "http://cs.brown.edu/people/bcz/prairie.jpg"; + let field = thumbField; + if (field instanceof ImageField) path = this.choosePath(field.url); + return ; + } + } + + @action onError = () => { + } + + choosePath(url: URL) { + if (url.protocol === "data" || url.href.indexOf(window.location.origin) === -1) { + return url.href; + } + let ext = path.extname(url.href); + ///TODO: Not done lol - syip2 + return url.href; + } + @computed get startIndex(): number { return Math.max(0, this.getIndex(this.scrollY) - this._pageBuffer); } @@ -221,7 +258,7 @@ class Viewer extends React.Component { console.log(`END: ${this.endIndex}`) let numPages = this.props.pdf ? this.props.pdf.numPages : 0; return ( -
+
{/* {Array.from(Array(numPages).keys()).map((i) => ( { {...this.props} /> ))} */} - {this._visibleElements} + {this._renderAsSvg ? this.imageProxyRenderer : this._visibleElements}
); } -- cgit v1.2.3-70-g09d2 From 1708f2b2a19d3d4efc081bcc4ee82b4d5149da08 Mon Sep 17 00:00:00 2001 From: loudonclear Date: Tue, 4 Jun 2019 19:08:01 -0400 Subject: YES --- package.json | 1 + src/client/views/nodes/PDFBox.tsx | 4 +-- src/client/views/pdf/PDFViewer.tsx | 50 ++++++++++++++++++-------------- src/server/Search.ts | 3 +- src/server/index.ts | 58 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 24 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/package.json b/package.json index 58b2cb049..648ebdbf9 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "bluebird": "^3.5.3", "body-parser": "^1.18.3", "bootstrap": "^4.3.1", + "canvas": "^2.5.0", "class-transformer": "^0.2.0", "connect-flash": "^0.1.1", "connect-mongo": "^2.0.3", diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 217d7b5e1..7e335e75e 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -14,7 +14,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; import React = require("react"); -import { NumCast } from "../../../new_fields/Types"; +import { NumCast, StrCast } from "../../../new_fields/Types"; import { makeInterface } from "../../../new_fields/Schema"; import { PDFViewer } from "../pdf/PDFViewer"; @@ -77,7 +77,7 @@ export class PDFBox extends DocComponent(PdfDocumen render() { trace(); - const pdfUrl = window.origin + RouteStore.corsProxy + "/https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf"; + const pdfUrl = window.origin + RouteStore.corsProxy + "/" + StrCast(this.props.Document.title); let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); return (
e.stopPropagation()} className={classname}> diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 999cb6378..374e598a4 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -91,24 +91,24 @@ class Viewer extends React.Component { } saveThumbnail = () => { - this.props.parent.props.Document.thumbnailY = FieldValue(this.scrollY, 0); - this._renderAsSvg = false; - setTimeout(() => { - let nwidth = FieldValue(this.props.parent.Document.nativeWidth, 0); - htmlToImage.toPng(this.props.mainCont.current!, { width: nwidth, height: nwidth, quality: 0.8, }) - .then(action((dataUrl: string) => { - SearchBox.convertDataUri(dataUrl, `icon${this.props.parent.Document[Id]}_${this.startIndex}`).then((returnedFilename) => { - if (returnedFilename) { - let url = DocServer.prepend(returnedFilename); - this.props.parent.props.Document.thumbnail = new ImageField(new URL(url)); - } - runInAction(() => this._renderAsSvg = true); - }); - })) - .catch(function (error: any) { - console.error("Oops, something went wrong!", error); - }); - }, 1250); + // this.props.parent.props.Document.thumbnailY = FieldValue(this.scrollY, 0); + // this._renderAsSvg = false; + // setTimeout(() => { + // let nwidth = FieldValue(this.props.parent.Document.nativeWidth, 0); + // htmlToImage.toPng(this.props.mainCont.current!, { width: nwidth, height: nwidth, quality: 0.8, }) + // .then(action((dataUrl: string) => { + // SearchBox.convertDataUri(dataUrl, `icon${this.props.parent.Document[Id]}_${this.startIndex}`).then((returnedFilename) => { + // if (returnedFilename) { + // let url = DocServer.prepend(returnedFilename); + // this.props.parent.props.Document.thumbnail = new ImageField(new URL(url)); + // } + // runInAction(() => this._renderAsSvg = true); + // }); + // })) + // .catch(function (error: any) { + // console.error("Oops, something went wrong!", error); + // }); + // }, 1250); } @computed get scrollY(): number { @@ -338,15 +338,23 @@ class Page extends React.Component { let scale = 1; let viewport = page.getViewport(scale); let canvas = this.canvas.current; - if (canvas) { - let context = canvas.getContext("2d"); + let tempCanvas = document.createElement('canvas') + if (tempCanvas && canvas) { + let ctx = canvas.getContext("2d"); + let context = tempCanvas.getContext("2d"); + tempCanvas.width = viewport.width; canvas.width = viewport.width; this._width = viewport.width; + tempCanvas.height = viewport.height; canvas.height = viewport.height; this._height = viewport.height; this.props.pageLoaded(this._currPage, viewport); if (context) { - page.render({ canvasContext: context, viewport: viewport }); + page.render({ canvasContext: context, viewport: viewport }).promise.then(() => { + if (context && ctx) { + ctx.putImageData(context.getImageData(0, 0, tempCanvas.width, tempCanvas.height), 0, 0); + } + }); page.getTextContent().then((res: Pdfjs.TextContent) => { //@ts-ignore let textLayer = Pdfjs.renderTextLayer({ diff --git a/src/server/Search.ts b/src/server/Search.ts index 5ca5578a7..fd6ef36a6 100644 --- a/src/server/Search.ts +++ b/src/server/Search.ts @@ -7,6 +7,7 @@ export class Search { private url = 'http://localhost:8983/solr/'; public async updateDocument(document: any) { + return; try { const res = await rp.post(this.url + "dash/update", { headers: { 'content-type': 'application/json' }, @@ -14,7 +15,7 @@ export class Search { }); return res; } catch (e) { - console.warn("Search error: " + e + document); + // console.warn("Search error: " + e + document); } } diff --git a/src/server/index.ts b/src/server/index.ts index fd66c90b4..9f95df4e0 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -7,6 +7,7 @@ import * as expressValidator from 'express-validator'; import * as formidable from 'formidable'; import * as fs from 'fs'; import * as sharp from 'sharp'; +import * as Pdfjs from 'pdfjs-dist'; const imageDataUri = require('image-data-uri'); import * as mobileDetect from 'mobile-detect'; import { ObservableMap } from 'mobx'; @@ -28,6 +29,7 @@ import { MessageStore, Transferable, Types, Diff } from "./Message"; import { RouteStore } from './RouteStore'; const app = express(); const config = require('../../webpack.config'); +import { createCanvas, loadImage, Canvas } from "canvas"; const compiler = webpack(config); const port = 1050; // default port to listen const serverPort = 4321; @@ -168,7 +170,31 @@ addSecureRoute( RouteStore.getCurrUser ); +class NodeCanvasFactory { + create = (width: number, height: number) => { + var canvas = createCanvas(width, height); + var context = canvas.getContext('2d'); + return { + canvas: canvas, + context: context, + }; + } + + reset = (canvasAndContext: any, width: number, height: number) => { + canvasAndContext.canvas.width = width; + canvasAndContext.canvas.height = height; + } + + destroy = (canvasAndContext: any) => { + canvasAndContext.canvas.width = 0; + canvasAndContext.canvas.height = 0; + canvasAndContext.canvas = null; + canvasAndContext.context = null; + } +} + const pngTypes = [".png", ".PNG"]; +const pdfTypes = [".pdf", ".PDF"]; const jpgTypes = [".jpg", ".JPG", ".jpeg", ".JPEG"]; const uploadDir = __dirname + "/public/files/"; // SETTERS @@ -203,6 +229,38 @@ app.post( }); isImage = true; } + else if (pdfTypes.includes(ext)) { + Pdfjs.getDocument(uploadDir + file).promise + .then((pdf: Pdfjs.PDFDocumentProxy) => { + let numPages = pdf.numPages; + let factory = new NodeCanvasFactory(); + for (let pageNum = 0; pageNum < numPages; pageNum++) { + console.log(pageNum); + pdf.getPage(pageNum + 1).then((page: Pdfjs.PDFPageProxy) => { + console.log("reading " + pageNum); + let viewport = page.getViewport(1); + let canvasAndContext = factory.create(viewport.width, viewport.height); + let renderContext = { + canvasContext: canvasAndContext.context, + viewport: viewport, + canvasFactory: factory + } + console.log("read " + pageNum); + + page.render(renderContext).promise + .then(() => { + console.log("saving " + pageNum); + let stream = canvasAndContext.canvas.createPNGStream(); + let out = fs.createWriteStream(uploadDir + file.substring(0, file.length - ext.length) + `-${pageNum + 1}.PNG`); + stream.pipe(out); + out.on("finish", () => console.log(`Success! Saved to ${uploadDir + file.substring(0, file.length - ext.length) + `-${pageNum + 1}.PNG`}`)); + }, (reason: string) => { + console.error(reason + ` ${pageNum}`); + }); + }); + } + }); + } if (isImage) { resizers.forEach(resizer => { fs.createReadStream(uploadDir + file).pipe(resizer.resizer).pipe(fs.createWriteStream(uploadDir + file.substring(0, file.length - ext.length) + resizer.suffix + ext)); -- cgit v1.2.3-70-g09d2 From f774314ddae00f2d2ceda886f0b6699f01f80718 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Wed, 5 Jun 2019 16:33:57 -0400 Subject: better virtualization and thumbnails git status --- src/client/views/nodes/PDFBox.tsx | 12 ++- src/client/views/pdf/PDFViewer.scss | 4 + src/client/views/pdf/PDFViewer.tsx | 158 ++++++++++++++++-------------------- 3 files changed, 82 insertions(+), 92 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 7e335e75e..6d604ec75 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -14,9 +14,11 @@ import { FieldView, FieldViewProps } from './FieldView'; import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; import React = require("react"); -import { NumCast, StrCast } from "../../../new_fields/Types"; +import { NumCast, StrCast, Cast } from "../../../new_fields/Types"; import { makeInterface } from "../../../new_fields/Schema"; import { PDFViewer } from "../pdf/PDFViewer"; +import { PdfField } from "../../../new_fields/URLField"; +import { HeightSym, WidthSym } from "../../../new_fields/Doc"; /** ALSO LOOK AT: Annotation.tsx, Sticky.tsx * This method renders PDF and puts all kinds of functionalities such as annotation, highlighting, @@ -65,6 +67,7 @@ export class PDFBox extends DocComponent(PdfDocumen let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document; doc.nativeWidth = nw; doc.nativeHeight = nh; + doc.height = nh * (doc[WidthSym]() / nw); } } @@ -77,11 +80,12 @@ export class PDFBox extends DocComponent(PdfDocumen render() { trace(); - const pdfUrl = window.origin + RouteStore.corsProxy + "/" + StrCast(this.props.Document.title); + const pdfUrl = Cast(this.props.Document.data, PdfField, new PdfField(window.origin + RouteStore.corsProxy + "/https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf")); + console.log(pdfUrl); let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); return ( -
e.stopPropagation()} className={classname}> - +
e.stopPropagation()} className={classname}> +
); } diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index 9d41a1bb0..42bf023d6 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -24,4 +24,8 @@ user-select: auto; } +.pdfBox-cont-interactive { + overflow-x: visible; +} + .viewer {} \ No newline at end of file diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 374e598a4..84d9f1ad0 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -4,18 +4,18 @@ import { observable, action, runInAction, computed, IReactionDisposer, reaction import { RouteStore } from "../../../server/RouteStore"; import * as Pdfjs from "pdfjs-dist"; import * as htmlToImage from "html-to-image"; -import { Opt } from "../../../new_fields/Doc"; +import { Opt, WidthSym } from "../../../new_fields/Doc"; import "./PDFViewer.scss"; import "pdfjs-dist/web/pdf_viewer.css"; import { number } from "prop-types"; import { JSXElement } from "babel-types"; import { PDFBox } from "../nodes/PDFBox"; -import { NumCast, FieldValue } from "../../../new_fields/Types"; +import { NumCast, FieldValue, Cast } from "../../../new_fields/Types"; import { SearchBox } from "../SearchBox"; import { Utils } from "../../../Utils"; import { Id } from "../../../new_fields/FieldSymbols"; import { DocServer } from "../../DocServer"; -import { ImageField } from "../../../new_fields/URLField"; +import { ImageField, PdfField } from "../../../new_fields/URLField"; var path = require("path"); interface IPDFViewerProps { @@ -44,7 +44,7 @@ export class PDFViewer extends React.Component { render() { return (
- +
); } @@ -56,6 +56,7 @@ interface IViewerProps { scrollY: number; parent: PDFBox; mainCont: React.RefObject; + url: string; } @observer @@ -71,71 +72,63 @@ class Viewer extends React.Component { private _pageBuffer: number = 1; private _reactionDisposer?: IReactionDisposer; - - @computed private get thumbnailY() { return NumCast(this.props.parent.Document.thumbnailY, -1); } + private _widthReactionDisposer?: IReactionDisposer; + private _width: number = 0; componentDidMount = () => { let wasSelected = this.props.parent.props.isSelected(); this._reactionDisposer = reaction( () => [this.props.parent.props.isSelected(), this.startIndex], () => { - if (this.startIndex >= 0 && !this.props.parent.props.isTopMost && this.scrollY !== this.thumbnailY && wasSelected && !this.props.parent.props.isSelected()) { + if (wasSelected && !this.props.parent.props.isSelected()) { this.saveThumbnail(); } + else if (!wasSelected && this.props.parent.props.isSelected()) { + this.renderPages(this.startIndex, this.endIndex, true); + } wasSelected = this.props.parent.props.isSelected(); }, { fireImmediately: true } ); - let numPages = this.props.pdf ? this.props.pdf.numPages : 0; - this.renderPages(0, numPages - 1, true); - } - saveThumbnail = () => { - // this.props.parent.props.Document.thumbnailY = FieldValue(this.scrollY, 0); - // this._renderAsSvg = false; - // setTimeout(() => { - // let nwidth = FieldValue(this.props.parent.Document.nativeWidth, 0); - // htmlToImage.toPng(this.props.mainCont.current!, { width: nwidth, height: nwidth, quality: 0.8, }) - // .then(action((dataUrl: string) => { - // SearchBox.convertDataUri(dataUrl, `icon${this.props.parent.Document[Id]}_${this.startIndex}`).then((returnedFilename) => { - // if (returnedFilename) { - // let url = DocServer.prepend(returnedFilename); - // this.props.parent.props.Document.thumbnail = new ImageField(new URL(url)); - // } - // runInAction(() => this._renderAsSvg = true); - // }); - // })) - // .catch(function (error: any) { - // console.error("Oops, something went wrong!", error); - // }); - // }, 1250); - } + // this._widthReactionDisposer = reaction( + // () => [this._docWidth], + // () => { + // if (this._width !== this._docWidth) { + // this._width = this._docWidth; + // this.renderPages(this.startIndex, this.endIndex, true); + // console.log(this._width); + // } + // }, + // { fireImmediately: true } + // ) - @computed get scrollY(): number { - return this.props.scrollY; + let numPages = this.props.pdf ? this.props.pdf.numPages : 0; + this.renderPages(0, numPages - 1, true); } - @computed get imageProxyRenderer() { - let thumbField = this.props.parent.props.Document.thumbnail; - if (thumbField && this._renderAsSvg && NumCast(this.props.parent.props.Document.startY, 0) === this.scrollY) { - let pw = typeof this.props.parent.props.PanelWidth === "function" ? this.props.parent.props.PanelWidth() : typeof this.props.parent.props.PanelWidth === "number" ? (this.props.parent.props.PanelWidth as any) as number : 50; - let path = thumbField instanceof ImageField ? thumbField.url.href : "http://cs.brown.edu/people/bcz/prairie.jpg"; - let field = thumbField; - if (field instanceof ImageField) path = this.choosePath(field.url); - return ; + componentWillUnmount = () => { + if (this._reactionDisposer) { + this._reactionDisposer(); } } - @action onError = () => { + @action + saveThumbnail = () => { + const address: string = this.props.url; + console.log(address); + for (let i = 0; i < this._visibleElements.length; i++) { + if (this._isPage[i]) { + let thisAddress = `${address.substring(0, address.length - ".pdf".length)}-${i + 1}.PNG`; + let nWidth = this._pageSizes[i].width; + let nHeight = this._pageSizes[i].height; + this._visibleElements[i] = ; + } + } } - choosePath(url: URL) { - if (url.protocol === "data" || url.href.indexOf(window.location.origin) === -1) { - return url.href; - } - let ext = path.extname(url.href); - ///TODO: Not done lol - syip2 - return url.href; + @computed get scrollY(): number { + return this.props.scrollY; } @computed get startIndex(): number { @@ -157,6 +150,9 @@ class Viewer extends React.Component { @action renderPages = (startIndex: number, endIndex: number, forceRender: boolean = false) => { let numPages = this.props.pdf ? this.props.pdf.numPages : 0; + if (!this.props.pdf) { + return; + } if (this._visibleElements.length !== numPages) { let divs = Array.from(Array(numPages).keys()).map(i => ( @@ -178,21 +174,19 @@ class Viewer extends React.Component { return; } - for (let i = startIndex; i <= endIndex; i++) { - if (this._isPage[i] && forceRender) { - this._visibleElements[i] = ( - - ); - this._isPage[i] = true; + for (let i = 0; i < numPages; i++) { + if (i < startIndex || i > endIndex) { + if (this._isPage[i]) { + this._visibleElements[i] = ( +
+ ); + } + this._isPage[i] = false; } - else if (!this._isPage[i]) { + } + + for (let i = startIndex; i <= endIndex; i++) { + if (!this._isPage[i] || forceRender) { this._visibleElements[i] = ( { } } - for (let i = 0; i < numPages; i++) { - if (i < startIndex || i > endIndex) { - if (this._isPage[i]) { - this._visibleElements[i] = ( -
- ); - this._isPage[i] = false; - } - } - } + this._startIndex = startIndex; + this._endIndex = endIndex; return; } getIndex = (vOffset: number) => { if (this._loaded) { + let numPages = this.props.pdf ? this.props.pdf.numPages : 0; let index = 0; let currOffset = vOffset; - while (currOffset - this._pageSizes[index].height > 0) { + while (index < numPages && currOffset - this._pageSizes[index].height > 0) { currOffset -= this._pageSizes[index].height; index++; } @@ -236,6 +223,9 @@ class Viewer extends React.Component { @action pageLoaded = (index: number, page: Pdfjs.PDFPageViewport): void => { + if (this._loaded) { + return; + } let numPages = this.props.pdf ? this.props.pdf.numPages : 0; this.props.loaded(page.width, page.height); if (index > this._pageSizes.length) { @@ -270,7 +260,7 @@ class Viewer extends React.Component { {...this.props} /> ))} */} - {this._renderAsSvg ? this.imageProxyRenderer : this._visibleElements} + {this._visibleElements}
); } @@ -338,32 +328,24 @@ class Page extends React.Component { let scale = 1; let viewport = page.getViewport(scale); let canvas = this.canvas.current; - let tempCanvas = document.createElement('canvas') - if (tempCanvas && canvas) { + let textLayer = this.textLayer.current; + if (canvas && textLayer) { let ctx = canvas.getContext("2d"); - let context = tempCanvas.getContext("2d"); - tempCanvas.width = viewport.width; canvas.width = viewport.width; this._width = viewport.width; - tempCanvas.height = viewport.height; canvas.height = viewport.height; this._height = viewport.height; this.props.pageLoaded(this._currPage, viewport); - if (context) { - page.render({ canvasContext: context, viewport: viewport }).promise.then(() => { - if (context && ctx) { - ctx.putImageData(context.getImageData(0, 0, tempCanvas.width, tempCanvas.height), 0, 0); - } - }); + if (ctx) { + page.render({ canvasContext: ctx, viewport: viewport }) page.getTextContent().then((res: Pdfjs.TextContent) => { //@ts-ignore - let textLayer = Pdfjs.renderTextLayer({ + Pdfjs.renderTextLayer({ textContent: res, - container: this.textLayer.current, + container: textLayer, viewport: viewport }); // textLayer._render(); - this._state = "rendered"; }); this._page = page; -- cgit v1.2.3-70-g09d2 From c4180929255db3bc7a75938e07afd86522f0a15e Mon Sep 17 00:00:00 2001 From: yipstanley Date: Wed, 5 Jun 2019 18:09:39 -0400 Subject: bug fixess --- src/client/views/nodes/PDFBox.tsx | 2 +- src/client/views/pdf/PDFViewer.scss | 4 ---- src/client/views/pdf/PDFViewer.tsx | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 6d604ec75..4ee3ae098 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -84,7 +84,7 @@ export class PDFBox extends DocComponent(PdfDocumen console.log(pdfUrl); let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); return ( -
e.stopPropagation()} className={classname}> +
e.stopPropagation()} className={classname}>
); diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index 42bf023d6..9d41a1bb0 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -24,8 +24,4 @@ user-select: auto; } -.pdfBox-cont-interactive { - overflow-x: visible; -} - .viewer {} \ No newline at end of file diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 84d9f1ad0..95f31bb89 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -104,7 +104,7 @@ class Viewer extends React.Component { // ) let numPages = this.props.pdf ? this.props.pdf.numPages : 0; - this.renderPages(0, numPages - 1, true); + setTimeout(() => this.renderPages(this.startIndex, this.endIndex, true), 1000); } componentWillUnmount = () => { -- cgit v1.2.3-70-g09d2 From a37629f55ef279167a5ef2fec88dc548f36f4938 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Thu, 6 Jun 2019 14:13:55 -0400 Subject: commentss --- src/client/views/nodes/PDFBox.tsx | 38 +--------- src/client/views/pdf/PDFViewer.tsx | 147 ++++++++++++++++++------------------- 2 files changed, 77 insertions(+), 108 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 4ee3ae098..de75c67cf 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -20,29 +20,6 @@ import { PDFViewer } from "../pdf/PDFViewer"; import { PdfField } from "../../../new_fields/URLField"; import { HeightSym, WidthSym } from "../../../new_fields/Doc"; -/** 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 - * - * written by: Andrew Kim - */ - type PdfDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>; const PdfDocument = makeInterface(positionSchema, pageSchema); @@ -53,15 +30,6 @@ export class PDFBox extends DocComponent(PdfDocumen @observable private _alt = false; @observable private _scrollY: number = 0; - getHeight = (): number => { - if (this.props.Document) { - let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document; - console.log(doc); - return NumCast(doc.height); - } - return 0; - } - loaded = (nw: number, nh: number) => { if (this.props.Document) { let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document; @@ -80,11 +48,13 @@ export class PDFBox extends DocComponent(PdfDocumen render() { trace(); + // uses mozilla pdf as default const pdfUrl = Cast(this.props.Document.data, PdfField, new PdfField(window.origin + RouteStore.corsProxy + "/https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf")); - console.log(pdfUrl); let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); return ( -
e.stopPropagation()} className={classname}> +
e.stopPropagation()} className={classname}>
); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index a76527618..d5a0a7aa1 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -1,22 +1,11 @@ import { observer } from "mobx-react"; import React = require("react"); import { observable, action, runInAction, computed, IReactionDisposer, reaction } from "mobx"; -import { RouteStore } from "../../../server/RouteStore"; import * as Pdfjs from "pdfjs-dist"; -import * as htmlToImage from "html-to-image"; -import { Opt, WidthSym } from "../../../new_fields/Doc"; +import { Opt } from "../../../new_fields/Doc"; import "./PDFViewer.scss"; import "pdfjs-dist/web/pdf_viewer.css"; -import { number } from "prop-types"; -import { JSXElement } from "babel-types"; import { PDFBox } from "../nodes/PDFBox"; -import { NumCast, FieldValue, Cast } from "../../../new_fields/Types"; -import { SearchBox } from "../SearchBox"; -import { Utils } from "../../../Utils"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { DocServer } from "../../DocServer"; -import { ImageField, PdfField } from "../../../new_fields/URLField"; -var path = require("path"); interface IPDFViewerProps { url: string; @@ -25,6 +14,9 @@ interface IPDFViewerProps { parent: PDFBox; } +/** + * Wrapper that loads the PDF and cascades the pdf down + */ @observer export class PDFViewer extends React.Component { @observable _pdf: Opt; @@ -32,7 +24,6 @@ export class PDFViewer extends React.Component { @action componentDidMount() { - // const pdfUrl = window.origin + RouteStore.corsProxy + "/https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf"; const pdfUrl = this.props.url; let promise = Pdfjs.getDocument(pdfUrl).promise; @@ -59,30 +50,35 @@ interface IViewerProps { url: string; } +/** + * Handles rendering and virtualization of the pdf + */ @observer class Viewer extends React.Component { + // _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 @observable private _isPage: boolean[] = []; @observable private _pageSizes: { width: number, height: number }[] = []; @observable private _startIndex: number = 0; @observable private _endIndex: number = 1; @observable private _loaded: boolean = false; @observable private _pdf: Opt; - @observable private _renderAsSvg = false; private _pageBuffer: number = 1; private _reactionDisposer?: IReactionDisposer; - private _widthReactionDisposer?: IReactionDisposer; - private _width: number = 0; componentDidMount = () => { let wasSelected = this.props.parent.props.isSelected(); + // reaction for when document gets (de)selected this._reactionDisposer = reaction( () => [this.props.parent.props.isSelected(), this.startIndex], () => { + // if deselected, render images in place of pdf if (wasSelected && !this.props.parent.props.isSelected()) { this.saveThumbnail(); } + // if selected, render pdf else if (!wasSelected && this.props.parent.props.isSelected()) { this.renderPages(this.startIndex, this.endIndex, true); } @@ -91,19 +87,7 @@ class Viewer extends React.Component { { fireImmediately: true } ); - // this._widthReactionDisposer = reaction( - // () => [this._docWidth], - // () => { - // if (this._width !== this._docWidth) { - // this._width = this._docWidth; - // this.renderPages(this.startIndex, this.endIndex, true); - // console.log(this._width); - // } - // }, - // { fireImmediately: true } - // ) - - let numPages = this.props.pdf ? this.props.pdf.numPages : 0; + // On load, render pdf setTimeout(() => this.renderPages(this.startIndex, this.endIndex, true), 1000); } @@ -115,13 +99,15 @@ class Viewer extends React.Component { @action saveThumbnail = () => { + // file address of the pdf const address: string = this.props.url; - console.log(address); for (let i = 0; i < this._visibleElements.length; i++) { if (this._isPage[i]) { + // change the address to be the file address of the PNG version of each page let thisAddress = `${address.substring(0, address.length - ".pdf".length)}-${i + 1}.PNG`; let nWidth = this._pageSizes[i].width; let nHeight = this._pageSizes[i].height; + // replace page with image this._visibleElements[i] = ; } } @@ -143,10 +129,16 @@ class Viewer extends React.Component { componentDidUpdate = (prevProps: IViewerProps) => { if (this.scrollY !== prevProps.scrollY || this._pdf !== this.props.pdf) { this._pdf = this.props.pdf; + // render pages if the scorll position changes this.renderPages(this.startIndex, this.endIndex); } } + /** + * @param startIndex: where to start rendering pages + * @param endIndex: where to end rendering pages + * @param forceRender: (optional), force pdfs to re-render, even if the page already exists + */ @action renderPages = (startIndex: number, endIndex: number, forceRender: boolean = false) => { let numPages = this.props.pdf ? this.props.pdf.numPages : 0; @@ -154,6 +146,7 @@ class Viewer extends React.Component { return; } + // this is only for an initial render to get all of the pages rendered if (this._visibleElements.length !== numPages) { let divs = Array.from(Array(numPages).keys()).map(i => ( { this._isPage.push(...arr); } + // if nothing changed, return if (startIndex === this._startIndex && endIndex === this._endIndex && !forceRender) { return; } + // unrender pages outside of the pdf by replacing them with empty stand-in divs for (let i = 0; i < numPages; i++) { if (i < startIndex || i > endIndex) { if (this._isPage[i]) { @@ -185,6 +180,7 @@ class Viewer extends React.Component { } } + // render pages for any indices that don't already have pages (force rerender will make these render regardless) for (let i = startIndex; i <= endIndex; i++) { if (!this._isPage[i] || forceRender) { this._visibleElements[i] = ( @@ -207,6 +203,7 @@ class Viewer extends React.Component { return; } + // get the page index that the vertical offset passed in is on getIndex = (vOffset: number) => { if (this._loaded) { let numPages = this.props.pdf ? this.props.pdf.numPages : 0; @@ -221,6 +218,10 @@ class Viewer extends React.Component { return 0; } + /** + * Called by the Page class when it gets rendered, initializes the lists and + * puts a placeholder with all of the correct page sizes when all of the pages have been loaded. + */ @action pageLoaded = (index: number, page: Pdfjs.PDFPageViewport): void => { if (this._loaded) { @@ -244,22 +245,8 @@ class Viewer extends React.Component { } render() { - console.log(`START: ${this.startIndex}`); - console.log(`END: ${this.endIndex}`) - let numPages = this.props.pdf ? this.props.pdf.numPages : 0; return (
- {/* {Array.from(Array(numPages).keys()).map((i) => ( - - ))} */} {this._visibleElements}
); @@ -276,22 +263,23 @@ interface IPageProps { @observer class Page extends React.Component { - @observable _state: string = "N/A"; - @observable _width: number = 0; - @observable _height: number = 0; - @observable _page: Opt; - canvas: React.RefObject; - textLayer: React.RefObject; - @observable _currPage: number = this.props.page + 1; + @observable private _state: string = "N/A"; + @observable private _width: number = 0; + @observable private _height: number = 0; + @observable private _page: Opt; + @observable private _currPage: number = this.props.page + 1; + + private _canvas: React.RefObject; + private _currentAnnotations: HTMLDivElement[] = []; + private _textLayer: React.RefObject; constructor(props: IPageProps) { super(props); - this.canvas = React.createRef(); - this.textLayer = React.createRef(); + this._canvas = React.createRef(); + this._textLayer = React.createRef(); } componentDidMount() { - console.log(this.props.pdf); if (this.props.pdf) { this.update(this.props.pdf); } @@ -327,8 +315,8 @@ class Page extends React.Component { private renderPage = (page: Pdfjs.PDFPageProxy) => { let scale = 1; let viewport = page.getViewport(scale); - let canvas = this.canvas.current; - let textLayer = this.textLayer.current; + let canvas = this._canvas.current; + let textLayer = this._textLayer.current; if (canvas && textLayer) { let ctx = canvas.getContext("2d"); canvas.width = viewport.width; @@ -337,7 +325,9 @@ class Page extends React.Component { this._height = viewport.height; this.props.pageLoaded(this._currPage, viewport); if (ctx) { + // renders the page onto the canvas context page.render({ canvasContext: ctx, viewport: viewport }) + // renders text onto the text container page.getTextContent().then((res: Pdfjs.TextContent) => { //@ts-ignore Pdfjs.renderTextLayer({ @@ -345,7 +335,6 @@ class Page extends React.Component { container: textLayer, viewport: viewport }); - // textLayer._render(); }); this._page = page; @@ -358,6 +347,11 @@ class Page extends React.Component { e.stopPropagation(); document.addEventListener("pointermove", this.onPointerMove); document.addEventListener("pointerup", this.onPointerUp); + if (!e.ctrlKey) { + for (let anno of this._currentAnnotations) { + anno.remove(); + } + } } } @@ -367,30 +361,35 @@ class Page extends React.Component { } } + startAnnotation = (e: DragEvent) => { + console.log("drag starting"); + } + + pointerDownCancel = (e: PointerEvent) => { + e.stopPropagation(); + } + onPointerUp = (e: PointerEvent) => { let sel = window.getSelection(); + // if selecting over a range of things if (sel && sel.type === "Range") { - // console.log(sel.getRangeAt(0)); - let commonContainer = sel.getRangeAt(0).commonAncestorContainer; - let startContainer = sel.getRangeAt(0).startContainer; - let endContainer = sel.getRangeAt(0).endContainer; let clientRects = sel.getRangeAt(0).getClientRects(); - console.log(sel.getRangeAt(0)); - let annoBoxes = []; - if (this.textLayer.current) { - // let transform = Utils.GetScreenTransform(this.textLayer.current); - console.log(transform); + if (this._textLayer.current) { + let boundingRect = this._textLayer.current.getBoundingClientRect(); for (let i = 0; i < clientRects.length; i++) { let rect = clientRects.item(i); if (rect) { let annoBox = document.createElement("div"); annoBox.className = "pdfViewer-annotationBox"; - annoBox.style.top = rect.top.toString(); - annoBox.style.left = rect.left.toString(); - annoBox.style.width = rect.width.toString(); - annoBox.style.height = rect.height.toString(); - // annoBox.style.transform = `scale(${1 / transform.scale}) translate(-${transform.translateX * transform.scale}px, -${transform.translateY * transform.scale}px)`; - this.textLayer.current.appendChild(annoBox); + // transforms the positions from screen onto the pdf div + annoBox.style.top = ((rect.top - boundingRect.top) * (this._textLayer.current.offsetHeight / boundingRect.height)).toString(); + annoBox.style.left = ((rect.left - boundingRect.left) * (this._textLayer.current.offsetWidth / boundingRect.width)).toString(); + annoBox.style.width = (rect.width * this._textLayer.current.offsetWidth / boundingRect.width).toString(); + annoBox.style.height = (rect.height * this._textLayer.current.offsetHeight / boundingRect.height).toString(); + annoBox.ondragstart = this.startAnnotation; + annoBox.onpointerdown = this.pointerDownCancel; + this._textLayer.current.appendChild(annoBox); + this._currentAnnotations.push(annoBox); } } } @@ -403,9 +402,9 @@ class Page extends React.Component { return (
- +
-
+
); } -- cgit v1.2.3-70-g09d2 From b96281d18a9c4ca0ea7f8360d7f69d12c325fada Mon Sep 17 00:00:00 2001 From: yipstanley Date: Tue, 11 Jun 2019 19:02:00 -0400 Subject: pin annotations --- src/client/views/nodes/PDFBox.tsx | 1 + src/client/views/pdf/PDFViewer.tsx | 136 +++++++++++++++++++++++++++++-------- src/client/views/pdf/Page.tsx | 4 +- 3 files changed, 110 insertions(+), 31 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index de75c67cf..5b118185b 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -50,6 +50,7 @@ export class PDFBox extends DocComponent(PdfDocumen trace(); // uses mozilla pdf as default const pdfUrl = Cast(this.props.Document.data, PdfField, new PdfField(window.origin + RouteStore.corsProxy + "/https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf")); + console.log(pdfUrl); let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); return (
{ @action componentDidMount() { const pdfUrl = this.props.url; + console.log("pdf starting to load") let promise = Pdfjs.getDocument(pdfUrl).promise; promise.then((pdf: Pdfjs.PDFDocumentProxy) => { - runInAction(() => this._pdf = pdf); + runInAction(() => { + console.log("pdf url received"); + this._pdf = pdf; + }); }); } @@ -77,8 +85,16 @@ class Viewer extends React.Component { @observable private _annotations: Doc[] = []; private _pageBuffer: number = 1; + private _annotationLayer: React.RefObject; private _reactionDisposer?: IReactionDisposer; private _annotationReactionDisposer?: IReactionDisposer; + private _pagesLoaded: number = 0; + + constructor(props: IViewerProps) { + super(props); + + this._annotationLayer = React.createRef(); + } componentDidMount = () => { let wasSelected = this.props.parent.props.isSelected(); @@ -112,8 +128,9 @@ class Viewer extends React.Component { ); } - // On load, render pdf - setTimeout(() => this.renderPages(this.startIndex, this.endIndex, true), 1000); + setTimeout(() => { + this.renderPages(this.startIndex, this.endIndex, true); + }, 1000); } componentWillUnmount = () => { @@ -150,7 +167,7 @@ class Viewer extends React.Component { } @computed get endIndex(): number { - let width = this._pageSizes.map(i => i.width); + let width = this._pageSizes.map(i => i ? i.width : 0); return Math.min(this.props.pdf ? this.props.pdf.numPages - 1 : 0, this.getIndex(this.scrollY + Math.max(...width)) + this._pageBuffer); } @@ -185,6 +202,10 @@ class Viewer extends React.Component { return; } + if (this._pageSizes.length !== numPages) { + this._pageSizes = new Array(numPages).map(i => ({ width: 0, height: 0 })); + } + // this is only for an initial render to get all of the pages rendered if (this._visibleElements.length !== numPages) { let divs = Array.from(Array(numPages).keys()).map(i => ( @@ -248,14 +269,14 @@ class Viewer extends React.Component { return; } - createPinAnnotation = (x: number, y: number): void => { - let targetDoc = Docs.TextDocument({ title: "New Pin Annotation" }); + 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; pinAnno.width = pinAnno.height = PinRadius; - pinAnno.page = this.getIndex(y); + pinAnno.page = page; pinAnno.target = targetDoc; pinAnno.type = AnnotationTypes.Pin; // this._annotations.push(pinAnno); @@ -301,18 +322,18 @@ class Viewer extends React.Component { } let numPages = this.props.pdf ? this.props.pdf.numPages : 0; this.props.loaded(page.width, page.height); - if (index > this._pageSizes.length) { - this._pageSizes.push({ width: page.width, height: page.height }); - } - else { - this._pageSizes[index - 1] = { width: page.width, height: page.height }; - } - if (index === numPages) { + this._pageSizes[index - 1] = { width: page.width, height: page.height }; + this._pagesLoaded++; + if (this._pagesLoaded === numPages) { this._loaded = true; let divs = Array.from(Array(numPages).keys()).map(i => (
)); this._visibleElements = new Array(...divs); + // On load, render pdf + // setTimeout(() => { + this.renderPages(this.startIndex, this.endIndex, true); + // }, 1000); } } @@ -332,21 +353,38 @@ class Viewer extends React.Component { let type = NumCast(anno.type); switch (type) { case AnnotationTypes.Pin: - return ; + return ; case AnnotationTypes.Region: - return ; + return ; default: return
; } } + onDrop = (e: React.DragEvent) => { + console.log("Dropped!"); + } + + // ScreenToLocalTransform = (): Transform => { + // if (this._annotationLayer.current) { + // let boundingRect = this._annotationLayer.current.getBoundingClientRect(); + // let x = boundingRect.left; + // let y = boundingRect.top; + // let scale = NumCast(this.props.parent.Document.nativeWidth) / boundingRect.width; + // let t = new Transform(x, y, scale); + // return t; + // } + // return Transform.Identity(); + // } + render() { + trace(); return ( -
+
{this._visibleElements}
-
+
{this._annotations.map(anno => this.renderAnnotation(anno))}
@@ -365,25 +403,65 @@ interface IAnnotationProps { y: number; width: number; height: number; + parent: Viewer; document: Doc; } +@observer class PinAnnotation extends React.Component { - @observable private _backgroundColor: string = "red"; + @observable private _backgroundColor: string = "green"; + @observable private _display: string = "initial"; - pointerDown = (e: React.PointerEvent) => { + private _selected: boolean = true; + @action + pointerDown = (e: React.PointerEvent) => { + if (this._selected) { + this._backgroundColor = "red"; + this._display = "none"; + this._selected = false; + } + else { + this._backgroundColor = "green"; + this._display = "initial"; + this._selected = true; + } + e.preventDefault(); + e.stopPropagation(); } render() { - let targetDoc = Cast(this.props.document.targetDoc, Doc, Docs.TextDocument({ title: "New Pin Annotation" })); - return ( -
- {/* */} -
- ); + let targetDoc = Cast(this.props.document.target, Doc); + if (targetDoc instanceof Doc) { + return ( +
+
+ 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} + /> +
+
+ ); + } + return null; } } diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index fe7369aeb..73a7a93a0 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -23,7 +23,7 @@ interface IPageProps { pageLoaded: (index: number, page: Pdfjs.PDFPageViewport) => void; parent: PDFBox; renderAnnotations: (annotations: Doc[], removeOld: boolean) => void; - makePin: (x: number, y: number) => void; + makePin: (x: number, y: number, page: number) => void; } @observer @@ -378,7 +378,7 @@ export default class Page extends React.Component { let boundingRect = current.getBoundingClientRect(); let x = (e.clientX - boundingRect.left) * (current.offsetWidth / boundingRect.width); let y = (e.clientY - boundingRect.top) * (current.offsetHeight / boundingRect.height); - this.props.makePin(x, y); + this.props.makePin(x, y, this.props.page); } } -- cgit v1.2.3-70-g09d2 From eaa66ece6340534ad09cf83134b344ef43816cd9 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Wed, 12 Jun 2019 18:30:53 -0400 Subject: deleting pins and better pin saving --- src/client/views/nodes/PDFBox.scss | 23 +++++++++++---- src/client/views/nodes/PDFBox.tsx | 3 ++ src/client/views/pdf/PDFViewer.tsx | 57 ++++++++++++++++++++++++++++++++------ 3 files changed, 70 insertions(+), 13 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index 449408a61..f4d455be7 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -2,39 +2,52 @@ transform-origin: left top; position: absolute; top: 0; - left:0; + left: 0; } + .react-pdf__Page__textContent span { user-select: text; } + .react-pdf__Document { position: absolute; } + .pdfBox-buttonTray { - position:absolute; + position: absolute; top: 0; - left:0; + left: 0; z-index: 25; pointer-events: all; } + .pdfBox-thumbnail { position: absolute; width: 100%; } + .pdfButton { pointer-events: all; width: 100px; - height:100px; + height: 100px; } + .pdfBox-cont { - pointer-events: none ; + pointer-events: none; + display: flex; + flex-direction: row; + span { pointer-events: none !important; } } + .pdfBox-cont-interactive { pointer-events: all; + display: flex; + flex-direction: row; } + .pdfBox-contentContainer { position: absolute; transform-origin: left top; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 5b118185b..4214a6777 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -19,6 +19,7 @@ import { makeInterface } from "../../../new_fields/Schema"; import { PDFViewer } from "../pdf/PDFViewer"; import { PdfField } from "../../../new_fields/URLField"; import { HeightSym, WidthSym } from "../../../new_fields/Doc"; +import { CollectionStackingView } from "../collections/CollectionStackingView"; type PdfDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>; const PdfDocument = makeInterface(positionSchema, pageSchema); @@ -43,6 +44,7 @@ export class PDFBox extends DocComponent(PdfDocumen onScroll = (e: React.UIEvent) => { if (e.currentTarget) { this._scrollY = e.currentTarget.scrollTop; + // e.currentTarget.scrollTo({ top: 1000, behavior: "smooth" }); } } @@ -57,6 +59,7 @@ export class PDFBox extends DocComponent(PdfDocumen style={{ overflowY: "scroll", overflowX: "hidden", height: `${NumCast(this.props.Document.nativeHeight ? this.props.Document.nativeHeight : 300)}px` }} onWheel={(e: React.WheelEvent) => e.stopPropagation()} className={classname}> + {/*
*/}
); } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index e54dfea6f..fe442c906 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -152,7 +152,7 @@ class Viewer extends React.Component { } } - makeAnnotationDocuments = (sourceDoc: Doc): Doc => { + makeAnnotationDocument = (sourceDoc: Doc): Doc => { let annoDocs: Doc[] = []; this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => { for (let anno of value) { @@ -179,7 +179,7 @@ class Viewer extends React.Component { drop = async (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.LinkDragData) { let sourceDoc = de.data.linkSourceDocument; - let destDoc = this.makeAnnotationDocuments(sourceDoc); + let destDoc = this.makeAnnotationDocument(sourceDoc); let targetAnnotations = DocListCast(this.props.parent.Document.annotations); if (targetAnnotations) { targetAnnotations.push(destDoc); @@ -280,7 +280,7 @@ class Viewer extends React.Component { makePin={this.createPinAnnotation} createAnnotation={this.createAnnotation} sendAnnotations={this.receiveAnnotations} - makeAnnotationDocuments={this.makeAnnotationDocuments} + makeAnnotationDocuments={this.makeAnnotationDocument} receiveAnnotations={this.sendAnnotations} {...this.props} /> )); @@ -322,7 +322,7 @@ class Viewer extends React.Component { renderAnnotations={this.renderAnnotations} createAnnotation={this.createAnnotation} sendAnnotations={this.receiveAnnotations} - makeAnnotationDocuments={this.makeAnnotationDocuments} + makeAnnotationDocuments={this.makeAnnotationDocument} receiveAnnotations={this.sendAnnotations} {...this.props} /> ); @@ -492,29 +492,70 @@ class PinAnnotation extends React.Component { @observable private _backgroundColor: string = "green"; @observable private _display: string = "initial"; - private _selected: boolean = true; + private _mainCont: React.RefObject; + + constructor(props: IAnnotationProps) { + super(props); + this._mainCont = React.createRef(); + } + + componentDidMount = () => { + let selected = this.props.document.selected; + if (selected && BoolCast(selected)) { + runInAction(() => { + this._backgroundColor = "green"; + this._display = "initial"; + }) + } + else { + runInAction(() => { + this._backgroundColor = "red"; + this._display = "none"; + }) + } + } @action pointerDown = (e: React.PointerEvent) => { - if (this._selected) { + let selected = this.props.document.selected; + if (selected && BoolCast(selected)) { this._backgroundColor = "red"; this._display = "none"; - this._selected = false; + this.props.document.selected = false; } else { this._backgroundColor = "green"; this._display = "initial"; - this._selected = true; + 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(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 (
Date: Thu, 13 Jun 2019 22:00:33 -0400 Subject: added collection back --- src/client/documents/Documents.ts | 2 +- src/client/views/collections/CollectionPDFView.tsx | 32 +++++++++++++++++++--- src/client/views/nodes/PDFBox.tsx | 13 ++++++++- src/client/views/pdf/PDFViewer.scss | 1 - src/client/views/pdf/PDFViewer.tsx | 30 ++++++++++++++------ src/client/views/pdf/Page.tsx | 2 +- 6 files changed, 64 insertions(+), 16 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index dfbe2e136..91d3707f6 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -170,7 +170,7 @@ export namespace Docs { return textProto; } function CreatePdfPrototype(): Doc { - let pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", PDFBox.LayoutString(), + let pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("annotations"), { x: 0, y: 0, width: 300, height: 300, backgroundLayout: PDFBox.LayoutString(), curPage: 1 }); return pdfProto; } diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index 62e8adbec..4af89d780 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -1,4 +1,4 @@ -import { action, observable } from "mobx"; +import { action, observable, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; import { ContextMenu } from "../ContextMenu"; import "./CollectionPDFView.scss"; @@ -9,12 +9,36 @@ import { CollectionRenderProps, CollectionBaseView, CollectionViewType } from ". import { emptyFunction } from "../../../Utils"; import { NumCast } from "../../../new_fields/Types"; import { Id } from "../../../new_fields/FieldSymbols"; +import { HeightSym, WidthSym } from "../../../new_fields/Doc"; @observer export class CollectionPDFView extends React.Component { + private _reactionDisposer?: IReactionDisposer; + private _buttonTray: React.RefObject; + constructor(props: FieldViewProps) { super(props); + + this._buttonTray = React.createRef(); + } + + componentDidMount() { + this._reactionDisposer = reaction( + () => NumCast(this.props.Document.scrollY), + () => { + // let transform = this.props.ScreenToLocalTransform(); + if (this._buttonTray.current) { + // console.log(this._buttonTray.current.offsetHeight); + // console.log(NumCast(this.props.Document.scrollY)); + let scale = this.nativeWidth() / this.props.Document[WidthSym](); + this.props.Document.panY = NumCast(this.props.Document.scrollY); + // console.log(scale); + } + // console.log(this.props.Document[HeightSym]()); + }, + { fireImmediately: true } + ) } public static LayoutString(fieldKey: string = "data") { @@ -52,12 +76,12 @@ export class CollectionPDFView extends React.Component { private get uIButtons() { let ratio = (this.curPage - 1) / this.numPages * 100; return ( -
+
-
+ {/*
-
+
*/}
); } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 4214a6777..acb430deb 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -36,6 +36,10 @@ export class PDFBox extends DocComponent(PdfDocumen let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document; doc.nativeWidth = nw; doc.nativeHeight = nh; + let ccv = this.props.ContainingCollectionView; + if (ccv) { + ccv.props.Document.pdfHeight = nh; + } doc.height = nh * (doc[WidthSym]() / nw); } } @@ -45,6 +49,10 @@ export class PDFBox extends DocComponent(PdfDocumen if (e.currentTarget) { this._scrollY = e.currentTarget.scrollTop; // e.currentTarget.scrollTo({ top: 1000, behavior: "smooth" }); + let ccv = this.props.ContainingCollectionView; + if (ccv) { + ccv.props.Document.scrollY = this._scrollY; + } } } @@ -56,7 +64,10 @@ export class PDFBox extends DocComponent(PdfDocumen let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); return (
e.stopPropagation()} className={classname}> {/*
*/} diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index 57be04b93..a73df2d58 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -33,7 +33,6 @@ .pdfViewer-annotationLayer { position: absolute; top: 0; - overflow: visible hidden; } .pdfViewer-pinAnnotation { diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index fe442c906..144fca9e0 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -188,8 +188,8 @@ class Viewer extends React.Component { else { this.props.parent.Document.annotations = new List([destDoc]); } + e.stopPropagation(); } - e.stopPropagation(); } componentWillUnmount = () => { @@ -465,7 +465,7 @@ class Viewer extends React.Component { {this._visibleElements}
-
+
{this._annotations.map(anno => this.renderAnnotation(anno))}
@@ -501,17 +501,31 @@ class PinAnnotation extends React.Component { componentDidMount = () => { let selected = this.props.document.selected; - if (selected && BoolCast(selected)) { + if (!BoolCast(selected)) { runInAction(() => { - this._backgroundColor = "green"; - this._display = "initial"; - }) + 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"; - }) + }); } } @@ -572,7 +586,7 @@ class PinAnnotation extends React.Component { PanelWidth={() => NumCast(this.props.parent.props.parent.Document.nativeWidth)} PanelHeight={() => NumCast(this.props.parent.props.parent.Document.nativeHeight)} focus={emptyFunction} - selectOnLoad={false} + selectOnLoad={true} parentActive={this.props.parent.props.parent.props.active} whenActiveChanged={this.props.parent.props.parent.props.whenActiveChanged} bringToFront={emptyFunction} diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index 9e3bf4954..1c305caa3 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -257,7 +257,7 @@ export default class Page extends React.Component { let ratio = this._marqueeWidth / this._marqueeHeight; if (ratio > 1.5) { // vertical - transform = "rotate(90deg) scale(1, 2)"; + transform = "rotate(90deg) scale(1, 5)"; } else if (ratio < 0.5) { // horizontal -- cgit v1.2.3-70-g09d2 From 0b4f3c25471e51d27ddb33dc5ddafafb2c0c03e5 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 14 Jun 2019 13:09:15 -0400 Subject: fixes for full screen. --- src/client/views/nodes/PDFBox.tsx | 3 ++- src/client/views/pdf/PDFViewer.tsx | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index acb430deb..cce7b2631 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -65,7 +65,8 @@ export class PDFBox extends DocComponent(PdfDocumen return (
e.stopPropagation()} className={classname}> diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index d6081142a..440a20e8e 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -111,14 +111,13 @@ class Viewer extends React.Component { // if deselected, render images in place of pdf if (wasSelected && !this.props.parent.props.active()) { this.saveThumbnail(); - this._pointerEvents = "all"; } // if selected, render pdf else if (!wasSelected && this.props.parent.props.active()) { this.renderPages(this.startIndex, this.endIndex, true); - this._pointerEvents = "none"; } wasSelected = this.props.parent.props.active(); + this._pointerEvents = wasSelected ? "none" : "all"; }, { fireImmediately: true } ); -- cgit v1.2.3-70-g09d2 From 2ef1dd2089ad991f1d4897022f2d28c2eb130837 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Fri, 14 Jun 2019 15:47:43 -0400 Subject: highlighting --- src/client/views/nodes/PDFBox.tsx | 10 +++++- src/client/views/pdf/PDFMenu.tsx | 25 +++++++++++-- src/client/views/pdf/PDFViewer.tsx | 6 ++-- src/client/views/pdf/Page.tsx | 74 ++++++++++++++++++++++++++++---------- 4 files changed, 91 insertions(+), 24 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index acb430deb..6ee62bbad 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -34,8 +34,16 @@ export class PDFBox extends DocComponent(PdfDocumen loaded = (nw: number, nh: number) => { if (this.props.Document) { let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document; + let oldnw = NumCast(doc.nativeWidth); doc.nativeWidth = nw; - doc.nativeHeight = nh; + if (!doc.nativeHeight) { + doc.nativeHeight = nh; + } + else { + let oldnh = NumCast(doc.nativeHeight); + let aspect = oldnh / oldnw; + doc.nativeHeight = nw * aspect; + } let ccv = this.props.ContainingCollectionView; if (ccv) { ccv.props.Document.pdfHeight = nh; diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx index cc5c0b77b..a0230113b 100644 --- a/src/client/views/pdf/PDFMenu.tsx +++ b/src/client/views/pdf/PDFMenu.tsx @@ -3,6 +3,8 @@ import "./PDFMenu.scss"; import { observable } from "mobx"; import { observer } from "mobx-react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { emptyFunction } from "../../../Utils"; +import { Doc } from "../../../new_fields/Doc"; @observer export default class PDFMenu extends React.Component { @@ -10,6 +12,8 @@ export default class PDFMenu extends React.Component { @observable Top: number = 0; @observable Left: number = 0; + StartDrag: (e: PointerEvent) => void = emptyFunction; + Highlight: (d: Doc | undefined) => void = emptyFunction; constructor(props: Readonly<{}>) { super(props); @@ -17,11 +21,28 @@ export default class PDFMenu extends React.Component { PDFMenu.Instance = this; } + pointerDown = (e: React.PointerEvent) => { + document.removeEventListener("pointermove", this.StartDrag); + document.addEventListener("pointermove", this.StartDrag); + document.removeEventListener("pointerup", this.pointerUp) + document.addEventListener("pointerup", this.pointerUp) + + e.stopPropagation(); + e.preventDefault(); + } + + pointerUp = (e: PointerEvent) => { + document.removeEventListener("pointermove", this.StartDrag); + document.removeEventListener("pointerup", this.pointerUp); + e.stopPropagation(); + e.preventDefault(); + } + render() { return (
- - + +
) } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index d6081142a..fbad39880 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -152,7 +152,7 @@ class Viewer extends React.Component { } } - makeAnnotationDocument = (sourceDoc: Doc): Doc => { + makeAnnotationDocument = (sourceDoc: Doc | undefined): Doc => { let annoDocs: Doc[] = []; this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => { for (let anno of value) { @@ -171,7 +171,9 @@ class Viewer extends React.Component { let annoDoc = new Doc(); annoDoc.annotations = new List(annoDocs); - DocUtils.MakeLink(sourceDoc, annoDoc, undefined, `Annotation from ${StrCast(this.props.parent.Document.title)}`, "", StrCast(this.props.parent.Document.title)); + if (sourceDoc) { + DocUtils.MakeLink(sourceDoc, annoDoc, undefined, `Annotation from ${StrCast(this.props.parent.Document.title)}`, "", StrCast(this.props.parent.Document.title)); + } this._savedAnnotations.clear(); return annoDoc; } diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index bdb6952cc..44c502a04 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -28,7 +28,7 @@ interface IPageProps { sendAnnotations: (annotations: HTMLDivElement[], page: number) => void; receiveAnnotations: (page: number) => HTMLDivElement[] | undefined; createAnnotation: (div: HTMLDivElement, page: number) => void; - makeAnnotationDocuments: (doc: Doc) => Doc; + makeAnnotationDocuments: (doc: Doc | undefined) => Doc; } @observer @@ -132,6 +132,20 @@ export default class Page extends React.Component { } } + highlight = (targetDoc: Doc | undefined) => { + // creates annotation documents for current highlights + let annotationDoc = this.props.makeAnnotationDocuments(targetDoc); + let targetAnnotations = DocListCast(this.props.parent.Document.annotations); + if (targetAnnotations) { + targetAnnotations.push(annotationDoc); + this.props.parent.Document.annotations = new List(targetAnnotations); + } + else { + this.props.parent.Document.annotations = new List([annotationDoc]); + } + return annotationDoc; + } + /** * This is temporary for creating annotations from highlights. It will * start a drag event and create or put the necessary info into the drag event. @@ -149,16 +163,7 @@ export default class Page extends React.Component { // document that this annotation is linked to let targetDoc = Docs.TextDocument({ width: 200, height: 200, title: "New Annotation" }); targetDoc.targetPage = this.props.page; - // creates annotation documents for current highlights - let annotationDoc = this.props.makeAnnotationDocuments(targetDoc); - let targetAnnotations = DocListCast(this.props.parent.Document.annotations); - if (targetAnnotations) { - targetAnnotations.push(annotationDoc); - this.props.parent.Document.annotations = new List(targetAnnotations); - } - else { - this.props.parent.Document.annotations = new List([annotationDoc]); - } + let annotationDoc = this.highlight(targetDoc); // create dragData and star tdrag let dragData = new DragManager.AnnotationDragData(thisDoc, annotationDoc, targetDoc); if (this._textLayer.current) { @@ -173,8 +178,8 @@ export default class Page extends React.Component { // cleans up events and boolean endDrag = (e: PointerEvent): void => { - document.removeEventListener("pointermove", this.startDrag); - document.removeEventListener("pointerup", this.endDrag); + // document.removeEventListener("pointermove", this.startDrag); + // document.removeEventListener("pointerup", this.endDrag); this._dragging = false; e.stopPropagation(); } @@ -185,10 +190,10 @@ export default class Page extends React.Component { if (e.altKey && e.button === 0) { e.stopPropagation(); - document.removeEventListener("pointermove", this.startDrag); - document.addEventListener("pointermove", this.startDrag); - document.removeEventListener("pointerup", this.endDrag); - document.addEventListener("pointerup", this.endDrag); + // document.removeEventListener("pointermove", this.startDrag); + // document.addEventListener("pointermove", this.startDrag); + // document.removeEventListener("pointerup", this.endDrag); + // document.addEventListener("pointerup", this.endDrag); } else if (e.button === 0) { let target: any = e.target; @@ -299,6 +304,8 @@ export default class Page extends React.Component { } copy.className = this._marquee.current.className; this.props.createAnnotation(copy, this.props.page); + PDFMenu.Instance.StartDrag = this.startDrag; + PDFMenu.Instance.Highlight = this.highlight; this._marquee.current.style.opacity = "0"; } @@ -308,8 +315,10 @@ export default class Page extends React.Component { } else { let sel = window.getSelection(); - if (sel && sel.type === "range") { - + if (sel && sel.type === "Range") { + PDFMenu.Instance.StartDrag = this.startDrag; + PDFMenu.Instance.Highlight = this.highlight; + this.createTextAnnotation(sel); PDFMenu.Instance.Left = e.clientX; PDFMenu.Instance.Top = e.clientY; } @@ -380,6 +389,33 @@ export default class Page extends React.Component { document.removeEventListener("pointerup", this.onSelectEnd); } + @action + createTextAnnotation = (sel: Selection) => { + let clientRects = sel.getRangeAt(0).getClientRects(); + if (this._textLayer.current) { + let boundingRect = this._textLayer.current.getBoundingClientRect(); + for (let i = 0; i < clientRects.length; i++) { + let rect = clientRects.item(i); + if (rect) { + let annoBox = document.createElement("div"); + annoBox.className = "pdfViewer-annotationBox"; + // transforms the positions from screen onto the pdf div + annoBox.style.top = ((rect.top - boundingRect.top) * (this._textLayer.current.offsetHeight / boundingRect.height)).toString(); + annoBox.style.left = ((rect.left - boundingRect.left) * (this._textLayer.current.offsetWidth / boundingRect.width)).toString(); + annoBox.style.width = (rect.width * this._textLayer.current.offsetWidth / boundingRect.width).toString(); + annoBox.style.height = (rect.height * this._textLayer.current.offsetHeight / boundingRect.height).toString(); + this.props.createAnnotation(annoBox, this.props.page); + } + } + } + // clear selection + if (sel.empty) { // Chrome + sel.empty(); + } else if (sel.removeAllRanges) { // Firefox + sel.removeAllRanges(); + } + } + doubleClick = (e: React.MouseEvent) => { let target: any = e.target; // if double clicking text -- cgit v1.2.3-70-g09d2 From 15e8341334419142f4a54db23cc643f18ba0e0f1 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 14 Jun 2019 15:53:13 -0400 Subject: a few tweaks to fix unfreezing documents to give them a margin --- src/client/views/collections/CollectionDockingView.tsx | 8 ++++++-- src/client/views/nodes/DocumentView.tsx | 3 ++- src/client/views/nodes/PDFBox.tsx | 5 ++++- 3 files changed, 12 insertions(+), 4 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 51e29cb54..235bf5ae4 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -9,7 +9,7 @@ import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc"; import { Id } from '../../../new_fields/FieldSymbols'; import { FieldId } from "../../../new_fields/RefField"; import { listSpec } from "../../../new_fields/Schema"; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types"; import { emptyFunction, returnTrue, Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; import { DocumentManager } from '../../util/DocumentManager'; @@ -429,7 +429,11 @@ export class DockedFrameRenderer extends React.Component { } nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth); - nativeHeight = () => NumCast(this._document!.nativeHeight, this._panelHeight); + nativeHeight = () => { + let nh = NumCast(this._document!.nativeHeight, this._panelHeight); + let res = BoolCast(this._document!.ignoreAspect) ? this._panelHeight : nh; + return res; + } contentScaling = () => { const nativeH = this.nativeHeight(); const nativeW = this.nativeWidth(); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6fe01963a..583fa3e1a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -452,8 +452,9 @@ export class DocumentView extends DocComponent(Docu render() { var scaling = this.props.ContentScaling(); - var nativeHeight = this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%"; + let ph = this.props.PanelHeight(); + var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; return (
(PdfDocumen loaded = (nw: number, nh: number) => { if (this.props.Document) { + if (this.props.Document.nativeWidth && this.props.Document.nativeHeight) return; let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document; + let oldaspect = NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth, 1); doc.nativeWidth = nw; - doc.nativeHeight = nh; + if (doc.nativeHeight) doc.nativeHeight = nw * oldaspect; + else doc.nativeHeight = nh; let ccv = this.props.ContainingCollectionView; if (ccv) { ccv.props.Document.pdfHeight = nh; -- cgit v1.2.3-70-g09d2 From f6e8b7a0f8a13ddf059cf701e46b8cbb8d9228f7 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 14 Jun 2019 17:27:49 -0400 Subject: added page fwd/back/goto --- src/client/views/collections/CollectionPDFView.tsx | 48 ++++++----------- src/client/views/nodes/DocumentView.tsx | 1 - src/client/views/nodes/FieldView.tsx | 2 + src/client/views/nodes/PDFBox.tsx | 62 ++++++++++++++++------ src/client/views/pdf/PDFViewer.tsx | 6 +-- src/client/views/pdf/Page.tsx | 12 ++--- 6 files changed, 72 insertions(+), 59 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index 4af89d780..b62d3f7bb 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -1,19 +1,21 @@ -import { action, observable, IReactionDisposer, reaction } from "mobx"; +import { action, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; +import { WidthSym } from "../../../new_fields/Doc"; +import { Id } from "../../../new_fields/FieldSymbols"; +import { NumCast } from "../../../new_fields/Types"; +import { emptyFunction } from "../../../Utils"; import { ContextMenu } from "../ContextMenu"; +import { FieldView, FieldViewProps } from "../nodes/FieldView"; +import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from "./CollectionBaseView"; +import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; import "./CollectionPDFView.scss"; import React = require("react"); -import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; -import { FieldView, FieldViewProps } from "../nodes/FieldView"; -import { CollectionRenderProps, CollectionBaseView, CollectionViewType } from "./CollectionBaseView"; -import { emptyFunction } from "../../../Utils"; -import { NumCast } from "../../../new_fields/Types"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { HeightSym, WidthSym } from "../../../new_fields/Doc"; +import { PDFBox } from "../nodes/PDFBox"; @observer export class CollectionPDFView extends React.Component { + private _pdfBox?: PDFBox; private _reactionDisposer?: IReactionDisposer; private _buttonTray: React.RefObject; @@ -46,31 +48,12 @@ export class CollectionPDFView extends React.Component { } @observable _inThumb = false; - private set curPage(value: number) { this.props.Document.curPage = value; } + private set curPage(value: number) { this._pdfBox && this._pdfBox.GotoPage(value); } private get curPage() { return NumCast(this.props.Document.curPage, -1); } private get numPages() { return NumCast(this.props.Document.numPages); } - @action onPageBack = () => this.curPage > 1 ? (this.props.Document.curPage = this.curPage - 1) : -1; - @action onPageForward = () => this.curPage < this.numPages ? (this.props.Document.curPage = this.curPage + 1) : -1; + @action onPageBack = () => this._pdfBox && this._pdfBox.BackPage(); + @action onPageForward = () => this._pdfBox && this._pdfBox.ForwardPage(); - @action - onThumbDown = (e: React.PointerEvent) => { - document.addEventListener("pointermove", this.onThumbMove, false); - document.addEventListener("pointerup", this.onThumbUp, false); - e.stopPropagation(); - this._inThumb = true; - } - @action - onThumbMove = (e: PointerEvent) => { - let pso = (e.clientY - (e as any).target.parentElement.getBoundingClientRect().top) / (e as any).target.parentElement.getBoundingClientRect().height; - this.curPage = Math.trunc(Math.min(this.numPages, pso * this.numPages + 1)); - e.stopPropagation(); - } - @action - onThumbUp = (e: PointerEvent) => { - this._inThumb = false; - document.removeEventListener("pointermove", this.onThumbMove); - document.removeEventListener("pointerup", this.onThumbUp); - } nativeWidth = () => NumCast(this.props.Document.nativeWidth); nativeHeight = () => NumCast(this.props.Document.nativeHeight); private get uIButtons() { @@ -92,11 +75,14 @@ export class CollectionPDFView extends React.Component { } } + setPdfBox = (pdfBox: PDFBox) => { this._pdfBox = pdfBox; }; + + private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => { let props = { ...this.props, ...renderProps }; return ( <> - + {renderProps.active() ? this.uIButtons : (null)} ); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 583fa3e1a..0d5df550a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -453,7 +453,6 @@ export class DocumentView extends DocComponent(Docu render() { var scaling = this.props.ContentScaling(); var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%"; - let ph = this.props.PanelHeight(); var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; return (
number; PanelHeight: () => number; setVideoBox?: (player: VideoBox) => void; + setPdfBox?: (player: PDFBox) => void; } @observer diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index b9ccd79e4..243982a3b 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -1,25 +1,22 @@ -import * as htmlToImage from "html-to-image"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx'; +import { action, IReactionDisposer, observable, reaction, trace, untracked } from 'mobx'; import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; -import Measure from "react-measure"; +import { WidthSym } from "../../../new_fields/Doc"; +import { makeInterface } from "../../../new_fields/Schema"; +import { Cast, NumCast } from "../../../new_fields/Types"; +import { PdfField } from "../../../new_fields/URLField"; //@ts-ignore // import { Document, Page } from "react-pdf"; // import 'react-pdf/dist/Page/AnnotationLayer.css'; import { RouteStore } from "../../../server/RouteStore"; import { DocComponent } from "../DocComponent"; import { InkingControl } from "../InkingControl"; +import { PDFViewer } from "../pdf/PDFViewer"; import { positionSchema } from "./DocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; import React = require("react"); -import { NumCast, StrCast, Cast } from "../../../new_fields/Types"; -import { makeInterface } from "../../../new_fields/Schema"; -import { PDFViewer } from "../pdf/PDFViewer"; -import { PdfField } from "../../../new_fields/URLField"; -import { HeightSym, WidthSym } from "../../../new_fields/Doc"; -import { CollectionStackingView } from "../collections/CollectionStackingView"; type PdfDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>; const PdfDocument = makeInterface(positionSchema, pageSchema); @@ -31,19 +28,50 @@ export class PDFBox extends DocComponent(PdfDocumen @observable private _alt = false; @observable private _scrollY: number = 0; private _reactionDisposer?: IReactionDisposer; - _targetDiv: any = undefined; - componentDidMount: () => void = () => { + componentDidMount() { + if (this.props.setPdfBox) this.props.setPdfBox(this); + } + + public GetPage() { + return Math.floor(NumCast(this.props.Document.scrollY) / NumCast(this.Document.pdfHeight)) + 1; + } + public BackPage() { + let cp = Math.ceil(NumCast(this.props.Document.scrollY) / NumCast(this.Document.pdfHeight)) + 1; + cp = cp - 1; + if (cp > 0) { + this.props.Document.curPage = cp; + this.props.Document.scrollY = (cp - 1) * NumCast(this.Document.pdfHeight); + } + } + public GotoPage(p: number) { + if (p > 0 && p <= NumCast(this.props.Document.numPages)) { + this.props.Document.curPage = p; + this.props.Document.scrollY = (p - 1) * NumCast(this.Document.pdfHeight); + } + } + + public ForwardPage() { + let cp = this.GetPage() + 1; + if (cp <= NumCast(this.props.Document.numPages)) { + this.props.Document.curPage = cp; + this.props.Document.scrollY = (cp - 1) * NumCast(this.Document.pdfHeight); + } + } + + createRef = (ele: HTMLDivElement | null) => { if (this._reactionDisposer) this._reactionDisposer(); - this._reactionDisposer = reaction(() => this.props.Document.scrollY, () => - this._targetDiv && this._targetDiv.scrollTo({ top: NumCast(this.Document.scrollY), behavior: "smooth" }) - ); + this._reactionDisposer = reaction(() => this.props.Document.scrollY, () => { + ele && ele.scrollTo({ top: NumCast(this.Document.scrollY), behavior: "smooth" }); + }); } - loaded = (nw: number, nh: number) => { + loaded = (nw: number, nh: number, np: number) => { if (this.props.Document) { - if (this.props.Document.nativeWidth && this.props.Document.nativeHeight) return; let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document; + console.log("pages = " + np); + doc.numPages = np; + if (doc.nativeWidth && doc.nativeHeight) return; let oldaspect = NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth, 1); doc.nativeWidth = nw; if (doc.nativeHeight) doc.nativeHeight = nw * oldaspect; @@ -59,7 +87,6 @@ export class PDFBox extends DocComponent(PdfDocumen @action onScroll = (e: React.UIEvent) => { if (e.currentTarget) { - this._targetDiv = e.currentTarget; this._scrollY = e.currentTarget.scrollTop; // e.currentTarget.scrollTo({ top: 1000, behavior: "smooth" }); let ccv = this.props.ContainingCollectionView; @@ -82,6 +109,7 @@ export class PDFBox extends DocComponent(PdfDocumen overflowY: "scroll", overflowX: "hidden", marginTop: `${NumCast(this.props.ContainingCollectionView!.props.Document.panY)}px` }} + ref={this.createRef} onWheel={(e: React.WheelEvent) => e.stopPropagation()} className={classname}> {/*
*/} diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 17f65c7a6..dee891ba6 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -23,7 +23,7 @@ import { Dictionary } from "typescript-collections"; interface IPDFViewerProps { url: string; - loaded: (nw: number, nh: number) => void; + loaded: (nw: number, nh: number, np: number) => void; scrollY: number; parent: PDFBox; } @@ -61,7 +61,7 @@ export class PDFViewer extends React.Component { interface IViewerProps { pdf: Opt; - loaded: (nw: number, nh: number) => void; + loaded: (nw: number, nh: number, np: number) => void; scrollY: number; parent: PDFBox; mainCont: React.RefObject; @@ -400,7 +400,7 @@ class Viewer extends React.Component { return; } let numPages = this.props.pdf ? this.props.pdf.numPages : 0; - this.props.loaded(page.width, page.height); + this.props.loaded(page.width, page.height, numPages); this._pageSizes[index - 1] = { width: page.width, height: page.height }; this._pagesLoaded++; if (this._pagesLoaded === numPages) { diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index 44c502a04..e3dbeaebe 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -132,16 +132,14 @@ export default class Page extends React.Component { } } - highlight = (targetDoc: Doc | undefined) => { + highlight = (targetDoc?: Doc) => { // creates annotation documents for current highlights let annotationDoc = this.props.makeAnnotationDocuments(targetDoc); - let targetAnnotations = DocListCast(this.props.parent.Document.annotations); - if (targetAnnotations) { + let targetAnnotations = Cast(this.props.parent.Document.annotations, listSpec(Doc)); + if (targetAnnotations === undefined) { + Doc.GetProto(this.props.parent.Document).annotations = new List([annotationDoc]); + } else { targetAnnotations.push(annotationDoc); - this.props.parent.Document.annotations = new List(targetAnnotations); - } - else { - this.props.parent.Document.annotations = new List([annotationDoc]); } return annotationDoc; } -- cgit v1.2.3-70-g09d2 From 96f30f9beda568cf4d8687d07cbdd5467ab05b1b Mon Sep 17 00:00:00 2001 From: yipstanley Date: Sun, 16 Jun 2019 19:08:08 -0400 Subject: better virtualization --- package.json | 1 + src/client/views/nodes/PDFBox.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 47 ++++++++------ src/server/index.ts | 122 ++++++++++++++++++++++++++++--------- 4 files changed, 122 insertions(+), 50 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/package.json b/package.json index 0fe26f16d..7fd6c4ba9 100644 --- a/package.json +++ b/package.json @@ -142,6 +142,7 @@ "passport": "^0.4.0", "passport-local": "^1.0.0", "pdfjs-dist": "^2.0.943", + "probe-image-size": "^4.0.0", "prosemirror-commands": "^1.0.7", "prosemirror-example-setup": "^1.0.1", "prosemirror-history": "^1.0.4", diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 243982a3b..655c12ab3 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -111,7 +111,7 @@ export class PDFBox extends DocComponent(PdfDocumen }} ref={this.createRef} onWheel={(e: React.WheelEvent) => e.stopPropagation()} className={classname}> - + {/*
*/}
); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 55d15893a..75c298f55 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -20,6 +20,9 @@ import { emptyFunction, returnTrue, returnFalse } from "../../../Utils"; import { DocumentView } from "../nodes/DocumentView"; import { DragManager } from "../../util/DragManager"; import { Dictionary } from "typescript-collections"; +import * as rp from "request-promise"; +import { restProperty } from "babel-types"; +import { DocServer } from "../../DocServer"; export const scale = 2; interface IPDFViewerProps { @@ -137,7 +140,8 @@ class Viewer extends React.Component { } setTimeout(() => { - this.renderPages(this.startIndex, this.endIndex, true); + // this.renderPages(this.startIndex, this.endIndex, true); + this.saveThumbnail(); }, 1000); } @@ -204,17 +208,19 @@ class Viewer extends React.Component { } @action - saveThumbnail = () => { + saveThumbnail = async () => { // file address of the pdf const address: string = this.props.url; for (let i = 0; i < this._visibleElements.length; i++) { if (this._isPage[i]) { // change the address to be the file address of the PNG version of each page - let thisAddress = `${address.substring(0, address.length - ".pdf".length)}-${i + 1}.PNG`; - let nWidth = this._pageSizes[i].width; - let nHeight = this._pageSizes[i].height; + let res = JSON.parse(await rp.get(DocServer.prepend(`/thumbnail${address.substring("files/".length, address.length - ".pdf".length)}-${i + 1}.PNG`))); + let thisAddress = res.path; + let nWidth = parseInt(res.width); + let nHeight = parseInt(res.height); // replace page with image - this._visibleElements[i] = ; + runInAction(() => + this._visibleElements[i] = ); } } } @@ -236,6 +242,7 @@ class Viewer extends React.Component { if (this.scrollY !== prevProps.scrollY || this._pdf !== this.props.pdf) { this._pdf = this.props.pdf; // render pages if the scorll position changes + console.log(`START: ${this.startIndex}, END: ${this.endIndex}`); this.renderPages(this.startIndex, this.endIndex); } } @@ -269,7 +276,7 @@ class Viewer extends React.Component { // this is only for an initial render to get all of the pages rendered if (this._visibleElements.length !== numPages) { - let divs = Array.from(Array(numPages).keys()).map(i => ( + let divs = Array.from(Array(numPages).keys()).map(i => i < 5 ? ( { makeAnnotationDocuments={this.makeAnnotationDocument} receiveAnnotations={this.sendAnnotations} {...this.props} /> - )); - let arr = Array.from(Array(numPages).keys()).map(() => false); + ) : + (
) + ); + let arr = Array.from(Array(numPages).keys()).map(i => i < 5); this._visibleElements.push(...divs); this._isPage.push(...arr); } @@ -378,16 +387,16 @@ class Viewer extends React.Component { // get the page index that the vertical offset passed in is on getIndex = (vOffset: number) => { - if (this._loaded) { - let numPages = this.props.pdf ? this.props.pdf.numPages : 0; - let index = 0; - let currOffset = vOffset; - while (index < numPages && currOffset - this._pageSizes[index].height > 0) { - currOffset -= this._pageSizes[index].height; - index++; - } - return index; - } + // if (this._loaded) { + let numPages = this.props.pdf ? this.props.pdf.numPages : 0; + let index = 0; + let currOffset = vOffset; + while (index < numPages && currOffset - (this._pageSizes[index] ? this._pageSizes[index].height : 792 * scale) > 0) { + currOffset -= this._pageSizes[index].height; + index++; + } + return index; + // } return 0; } diff --git a/src/server/index.ts b/src/server/index.ts index e22794df1..3e51eb4ff 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -38,8 +38,10 @@ import c = require("crypto"); import { Search } from './Search'; import { debug } from 'util'; import _ = require('lodash'); +import { Response } from 'express-serve-static-core'; const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); +const probe = require("probe-image-size"); const download = (url: string, dest: fs.PathLike) => request.get(url).pipe(fs.createWriteStream(dest)); @@ -134,6 +136,66 @@ app.get("/search", async (req, res) => { res.send(results); }); +app.get("/thumbnail/:filename", (req, res) => { + let filename = req.params.filename; + let noExt = filename.substring(0, filename.length - ".png".length); + let pagenumber = parseInt(noExt[noExt.length - 1]); + fs.exists(uploadDir + filename, (exists: boolean) => { + console.log(`${uploadDir + filename} ${exists ? "exists" : "does not exist"}`); + if (exists) { + let input = fs.createReadStream(uploadDir + filename); + probe(input, (err: any, result: any) => { + if (err) { + console.log(err); + return; + } + console.log(result.width); + console.log(result.height); + res.send({ path: "/files/" + filename, width: result.width, height: result.height }); + }); + } + else { + LoadPage(uploadDir + filename.substring(0, filename.length - "-n.png".length) + ".pdf", pagenumber, res); + } + }); +}); + +function LoadPage(file: string, pageNumber: number, res: Response) { + console.log(file); + Pdfjs.getDocument(file).promise + .then((pdf: Pdfjs.PDFDocumentProxy) => { + let factory = new NodeCanvasFactory(); + console.log(pageNumber); + pdf.getPage(pageNumber).then((page: Pdfjs.PDFPageProxy) => { + console.log("reading " + page); + let viewport = page.getViewport(1); + let canvasAndContext = factory.create(viewport.width, viewport.height); + let renderContext = { + canvasContext: canvasAndContext.context, + viewport: viewport, + canvasFactory: factory + } + console.log("read " + pageNumber); + + page.render(renderContext).promise + .then(() => { + console.log("saving " + pageNumber); + let stream = canvasAndContext.canvas.createPNGStream(); + let pngFile = `${file.substring(0, file.length - ".pdf".length)}-${pageNumber}.PNG`; + let out = fs.createWriteStream(pngFile); + stream.pipe(out); + out.on("finish", () => { + console.log(`Success! Saved to ${pngFile}`); + let name = path.basename(pngFile); + res.send({ path: "/files/" + name, width: viewport.width, height: viewport.height }); + }); + }, (reason: string) => { + console.error(reason + ` ${pageNumber}`); + }); + }); + }); +} + // anyone attempting to navigate to localhost at this port will // first have to login addSecureRoute( @@ -229,36 +291,36 @@ app.post( isImage = true; } else if (pdfTypes.includes(ext)) { - Pdfjs.getDocument(uploadDir + file).promise - .then((pdf: Pdfjs.PDFDocumentProxy) => { - let numPages = pdf.numPages; - let factory = new NodeCanvasFactory(); - for (let pageNum = 0; pageNum < numPages; pageNum++) { - console.log(pageNum); - pdf.getPage(pageNum + 1).then((page: Pdfjs.PDFPageProxy) => { - console.log("reading " + pageNum); - let viewport = page.getViewport(1); - let canvasAndContext = factory.create(viewport.width, viewport.height); - let renderContext = { - canvasContext: canvasAndContext.context, - viewport: viewport, - canvasFactory: factory - } - console.log("read " + pageNum); - - page.render(renderContext).promise - .then(() => { - console.log("saving " + pageNum); - let stream = canvasAndContext.canvas.createPNGStream(); - let out = fs.createWriteStream(uploadDir + file.substring(0, file.length - ext.length) + `-${pageNum + 1}.PNG`); - stream.pipe(out); - out.on("finish", () => console.log(`Success! Saved to ${uploadDir + file.substring(0, file.length - ext.length) + `-${pageNum + 1}.PNG`}`)); - }, (reason: string) => { - console.error(reason + ` ${pageNum}`); - }); - }); - } - }); + // Pdfjs.getDocument(uploadDir + file).promise + // .then((pdf: Pdfjs.PDFDocumentProxy) => { + // let numPages = pdf.numPages; + // let factory = new NodeCanvasFactory(); + // for (let pageNum = 0; pageNum < numPages; pageNum++) { + // console.log(pageNum); + // pdf.getPage(pageNum + 1).then((page: Pdfjs.PDFPageProxy) => { + // console.log("reading " + pageNum); + // let viewport = page.getViewport(1); + // let canvasAndContext = factory.create(viewport.width, viewport.height); + // let renderContext = { + // canvasContext: canvasAndContext.context, + // viewport: viewport, + // canvasFactory: factory + // } + // console.log("read " + pageNum); + + // page.render(renderContext).promise + // .then(() => { + // console.log("saving " + pageNum); + // let stream = canvasAndContext.canvas.createPNGStream(); + // let out = fs.createWriteStream(uploadDir + file.substring(0, file.length - ext.length) + `-${pageNum + 1}.PNG`); + // stream.pipe(out); + // out.on("finish", () => console.log(`Success! Saved to ${uploadDir + file.substring(0, file.length - ext.length) + `-${pageNum + 1}.PNG`}`)); + // }, (reason: string) => { + // console.error(reason + ` ${pageNum}`); + // }); + // }); + // } + // }); } if (isImage) { resizers.forEach(resizer => { -- cgit v1.2.3-70-g09d2 From 62c781c0c79ac395c5e117d208a90485ff1ba599 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 18 Jun 2019 02:19:07 -0400 Subject: faster loading of PDFs --- src/client/views/nodes/PDFBox.tsx | 1 - src/client/views/pdf/PDFViewer.tsx | 340 +++++++++++++------------------------ 2 files changed, 116 insertions(+), 225 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 655c12ab3..3aaa5749d 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -69,7 +69,6 @@ export class PDFBox extends DocComponent(PdfDocumen loaded = (nw: number, nh: number, np: number) => { if (this.props.Document) { let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document; - console.log("pages = " + np); doc.numPages = np; if (doc.nativeWidth && doc.nativeHeight) return; let oldaspect = NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth, 1); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index d74a16f3f..5149e48fd 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -43,16 +43,7 @@ export class PDFViewer extends React.Component { @action componentDidMount() { - const pdfUrl = this.props.url; - console.log("pdf starting to load") - let promise = Pdfjs.getDocument(pdfUrl).promise; - - promise.then((pdf: Pdfjs.PDFDocumentProxy) => { - runInAction(() => { - console.log("pdf url received"); - this._pdf = pdf; - }); - }); + Pdfjs.getDocument(this.props.url).promise.then(pdf => runInAction(() => this._pdf = pdf)); } render() { @@ -83,12 +74,8 @@ class Viewer extends React.Component { // _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 - @observable private _isPage: boolean[] = []; + @observable private _isPage: string[] = []; @observable private _pageSizes: { width: number, height: number }[] = []; - @observable private _startIndex: number = 0; - @observable private _endIndex: number = 1; - @observable private _loaded: boolean = false; - @observable private _pdf: Opt; @observable private _annotations: Doc[] = []; @observable private _pointerEvents: "all" | "none" = "all"; @observable private _savedAnnotations: Dictionary = new Dictionary(); @@ -97,7 +84,6 @@ class Viewer extends React.Component { private _annotationLayer: React.RefObject; private _reactionDisposer?: IReactionDisposer; private _annotationReactionDisposer?: IReactionDisposer; - private _pagesLoaded: number = 0; private _dropDisposer?: DragManager.DragDropDisposer; constructor(props: IViewerProps) { @@ -106,77 +92,62 @@ class Viewer extends React.Component { this._annotationLayer = React.createRef(); } + componentDidUpdate = (prevProps: IViewerProps) => { + if (this.scrollY !== prevProps.scrollY && this._visibleElements.length) { + this.renderPages(this.startIndex, this.endIndex, false); + } + } + @action componentDidMount = () => { let wasSelected = this.props.parent.props.active(); - // reaction for when document gets (de)selected this._reactionDisposer = reaction( - () => [this.props.parent.props.active(), this.startIndex], - () => { - // if deselected, render images in place of pdf - if (wasSelected && !this.props.parent.props.active()) { - this.saveThumbnail(); - } - // if selected, render pdf - else if (!wasSelected && this.props.parent.props.active()) { - this.renderPages(this.startIndex, this.endIndex, true); - } + () => [this.props.parent.props.active(), this.startIndex, this.props.pdf], + async () => { + await this.initialLoad(); wasSelected = this.props.parent.props.active(); - this._pointerEvents = wasSelected ? "none" : "all"; - }, - { fireImmediately: true } - ); + runInAction(() => this._pointerEvents = wasSelected ? "none" : "all"); + this.renderPages(this.startIndex, this.endIndex, false); + }, { fireImmediately: true }); - if (this.props.parent.Document) { - this._annotationReactionDisposer = reaction( - () => DocListCast(this.props.parent.Document.annotations), - () => { - let annotations = DocListCast(this.props.parent.Document.annotations); - if (annotations && annotations.length) { - this.renderAnnotations(annotations, true); - } - }, - { fireImmediately: true } - ); - } + this._annotationReactionDisposer = reaction( + () => this.props.parent.Document && DocListCast(this.props.parent.Document.annotations), + (annotations: Doc[]) => + annotations && annotations.length && this.renderAnnotations(annotations, true), + { fireImmediately: true }); + } - setTimeout(() => { - // this.renderPages(this.startIndex, this.endIndex, true); - this.initialLoad(); - }, 1000); + componentWillUnmount = () => { + this._reactionDisposer && this._reactionDisposer(); + this._annotationReactionDisposer && this._annotationReactionDisposer(); } @action - initialLoad = () => { - let pdf = this.props.pdf; - if (pdf) { - this._pageSizes = Array<{ width: number, height: number }>(pdf.numPages); - let rendered = 0; - for (let i = 0; i < pdf.numPages; i++) { - pdf.getPage(i + 1).then( - (page: Pdfjs.PDFPageProxy) => { - runInAction(() => { - this._pageSizes[i] = { width: page.view[2] * scale, height: page.view[3] * scale }; - }); - console.log(`page ${i} size retreieved`); - rendered++; - if (rendered === pdf!.numPages - 1) { - this.saveThumbnail(); - } - } - ); + initialLoad = async () => { + if (this.props.pdf && this._pageSizes.length === 0) { + let pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages); + 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 }; + if (i === 0) this.props.loaded(pageSizes[i].width, pageSizes[i].height, this.props.pdf!.numPages); + })); } + runInAction(() => { + this._pageSizes = pageSizes; + let divs = Array.from(Array(this._pageSizes.length).keys()).map(i => ( +
+ )); + this._isPage = Array.from(Array(this._pageSizes.length).map(p => "none")); + this._visibleElements = new Array(...divs); + }) } } private mainCont = (div: HTMLDivElement | null) => { - if (this._dropDisposer) { - this._dropDisposer(); - } + this._dropDisposer && this._dropDisposer(); if (div) { - this._dropDisposer = DragManager.MakeDropTarget(div, { - handlers: { drop: this.drop.bind(this) } - }); + this._dropDisposer = div && DragManager.MakeDropTarget(div, { handlers: { drop: this.drop.bind(this) } }); } } @@ -222,154 +193,102 @@ class Viewer extends React.Component { e.stopPropagation(); } } - - componentWillUnmount = () => { - if (this._reactionDisposer) { - this._reactionDisposer(); - } - if (this._annotationReactionDisposer) { - this._annotationReactionDisposer(); - } - } - + /** + * Called by the Page class when it gets rendered, initializes the lists and + * puts a placeholder with all of the correct page sizes when all of the pages have been loaded. + */ @action - saveThumbnail = async () => { - // file address of the pdf - const address: string = this.props.url; - for (let i = 0; i < this._visibleElements.length; i++) { - if (this._isPage[i]) { - // change the address to be the file address of the PNG version of each page - let res = JSON.parse(await rp.get(DocServer.prepend(`/thumbnail${address.substring("files/".length, address.length - ".pdf".length)}-${i + 1}.PNG`))); - let thisAddress = res.path; - let nWidth = parseInt(res.width); - let nHeight = parseInt(res.height); - // replace page with image - runInAction(() => - this._visibleElements[i] = ); - } - } - } - - @computed get scrollY(): number { - return this.props.scrollY; - } - - @computed get startIndex(): number { - return Math.max(0, this.getIndex(this.scrollY) - this._pageBuffer); - } - - @computed get endIndex(): number { - let width = this._pageSizes.map(i => i ? i.width : 0); - return Math.min(this.props.pdf ? this.props.pdf.numPages - 1 : 0, this.getIndex(this.scrollY + Math.max(...width)) + this._pageBuffer); - } - - componentDidUpdate = (prevProps: IViewerProps) => { - if (this.scrollY !== prevProps.scrollY || this._pdf !== this.props.pdf) { - this._pdf = this.props.pdf; - // render pages if the scorll position changes - console.log(`START: ${this.startIndex}, END: ${this.endIndex}`); - this.renderPages(this.startIndex, this.endIndex); - } + pageLoaded = (index: number, page: Pdfjs.PDFPageViewport): void => { + this.props.pdf && this.props.loaded && this.props.loaded(page.width, page.height, this.props.pdf.numPages); } - @action - private renderAnnotations = (annotations: Doc[], removeOldAnnotations: boolean): void => { - if (removeOldAnnotations) { - this._annotations = annotations; - } - else { - this._annotations.push(...annotations); - this._annotations = new Array(...this._annotations); + getPlaceholderPage = (page: number) => { + if (this._isPage[page] !== "none") { + this._isPage[page] = "none"; + this._visibleElements[page] = ( +
+ ); } } - - /** - * @param startIndex: where to start rendering pages - * @param endIndex: where to end rendering pages - * @param forceRender: (optional), force pdfs to re-render, even if the page already exists - */ @action - renderPages = (startIndex: number, endIndex: number, forceRender: boolean = false) => { - let numPages = this.props.pdf ? this.props.pdf.numPages : 0; - if (!this.props.pdf) { - return; - } - - if (this._pageSizes.length !== numPages) { - this._pageSizes = new Array(numPages).map(i => ({ width: 0, height: 0 })); - } - - // this is only for an initial render to get all of the pages rendered - if (this._visibleElements.length !== numPages) { - let divs = Array.from(Array(numPages).keys()).map(i => i < 5 ? ( + getRenderedPage = (page: number) => { + if (this._isPage[page] !== "page") { + this._isPage[page] = "page"; + this._visibleElements[page] = ( - ) : - (
) ); - let arr = Array.from(Array(numPages).keys()).map(i => i < 5); - this._visibleElements.push(...divs); - this._isPage.push(...arr); } + } - // if nothing changed, return - if (startIndex === this._startIndex && endIndex === this._endIndex && !forceRender) { - return; + // change the address to be the file address of the PNG version of each page + // file address of the pdf + @action + getPageImage = async (page: number) => { + let handleError = () => this.getRenderedPage(page); + if (this._isPage[page] != "image") { + this._isPage[page] = "image"; + const address = this.props.url; + let res = JSON.parse(await rp.get(DocServer.prepend(`/thumbnail${address.substring("files/".length, address.length - ".pdf".length)}-${page + 1}.PNG`))); + runInAction(() => this._visibleElements[page] = ); } + } - // unrender pages outside of the pdf by replacing them with empty stand-in divs - for (let i = 0; i < numPages; i++) { - if (i < startIndex || i > endIndex) { - if (this._isPage[i]) { - this._visibleElements[i] = ( -
- ); + @computed get scrollY(): number { return this.props.scrollY; } + + @computed get startIndex(): number { return Math.max(0, this.getPageFromScroll(this.scrollY) - this._pageBuffer); } + + @computed get endIndex(): number { + let width = this._pageSizes.map(i => i ? i.width : 0); + return Math.min(this.props.pdf ? this.props.pdf.numPages - 1 : 0, this.getPageFromScroll(this.scrollY + Math.max(...width)) + this._pageBuffer); + } + + /** + * @param startIndex: where to start rendering pages + * @param endIndex: where to end rendering pages + * @param forceRender: (optional), force pdfs to re-render, even if the page already exists + */ + @action + renderPages = (startIndex: number, endIndex: number, forceRender: boolean = false) => { + if (this.props.pdf) { + // unrender pages outside of the pdf by replacing them with empty stand-in divs + for (let i = 0; i < this.props.pdf.numPages; i++) { + if (i < startIndex || i > endIndex) { + this.getPlaceholderPage(i); + } else { + if (this.props.parent.props.active()) { + this.getRenderedPage(i); + } else { + this.getPageImage(i); + } } - this._isPage[i] = false; } } + } - // render pages for any indices that don't already have pages (force rerender will make these render regardless) - for (let i = startIndex; i <= endIndex; i++) { - if (!this._isPage[i] || (this._isPage[i] && forceRender)) { - this._visibleElements[i] = ( - - ); - this._isPage[i] = true; - } + @action + private renderAnnotations = (annotations: Doc[], removeOldAnnotations: boolean): void => { + if (removeOldAnnotations) { + this._annotations = annotations; + } + else { + this._annotations.push(...annotations); + this._annotations = new Array(...this._annotations); } - - this._startIndex = startIndex; - this._endIndex = endIndex; - - return; } @action @@ -392,7 +311,7 @@ class Viewer extends React.Component { let pinAnno = new Doc(); pinAnno.x = x; - pinAnno.y = y + this.getPageHeight(page); + pinAnno.y = y + this.getScrollFromPage(page); pinAnno.width = pinAnno.height = PinRadius; pinAnno.page = page; pinAnno.target = targetDoc; @@ -411,9 +330,7 @@ class Viewer extends React.Component { } // get the page index that the vertical offset passed in is on - getIndex = (vOffset: number) => { - // if (this._loaded) { - let numPages = this.props.pdf ? this.props.pdf.numPages : 0; + getPageFromScroll = (vOffset: number) => { let index = 0; let currOffset = vOffset; while (index < this._pageSizes.length && currOffset - (this._pageSizes[index] ? this._pageSizes[index].height : 792 * scale) > 0) { @@ -421,34 +338,10 @@ class Viewer extends React.Component { index++; } return index; - // } - return 0; } - /** - * Called by the Page class when it gets rendered, initializes the lists and - * puts a placeholder with all of the correct page sizes when all of the pages have been loaded. - */ - @action - pageLoaded = (index: number, page: Pdfjs.PDFPageViewport): void => { - if (this._loaded) { - return; - } - let numPages = this.props.pdf ? this.props.pdf.numPages : 0; - this.props.loaded(page.width, page.height, numPages); - this._pageSizes[index - 1] = { width: page.width, height: page.height }; - this._pagesLoaded++; - if (this._pagesLoaded === numPages) { - this._loaded = true; - let divs = Array.from(Array(numPages).keys()).map(i => ( -
- )); - this._visibleElements = new Array(...divs); - this.renderPages(this.startIndex, this.endIndex, true); - } - } - getPageHeight = (index: number): number => { + getScrollFromPage = (index: number): number => { let counter = 0; if (this.props.pdf && index < this.props.pdf.numPages) { for (let i = 0; i < index; i++) { @@ -463,7 +356,7 @@ class Viewer extends React.Component { createAnnotation = (div: HTMLDivElement, page: number) => { if (this._annotationLayer.current) { if (div.style.top) { - div.style.top = (parseInt(div.style.top) + this.getPageHeight(page)).toString(); + div.style.top = (parseInt(div.style.top) + this.getScrollFromPage(page)).toString(); } this._annotationLayer.current.append(div); let savedPage = this._savedAnnotations.getValue(page); @@ -494,7 +387,6 @@ class Viewer extends React.Component { } render() { - trace(); return (
-- cgit v1.2.3-70-g09d2 From 4b8324fcf44c5d3c3a4b3f6e98a4d1dfce84811b Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 18 Jun 2019 08:53:01 -0400 Subject: removed trace --- src/client/views/nodes/PDFBox.tsx | 1 - 1 file changed, 1 deletion(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 3aaa5749d..e79182e9b 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -96,7 +96,6 @@ export class PDFBox extends DocComponent(PdfDocumen } render() { - trace(); // uses mozilla pdf as default const pdfUrl = Cast(this.props.Document.data, PdfField, new PdfField(window.origin + RouteStore.corsProxy + "/https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf")); console.log(pdfUrl); -- cgit v1.2.3-70-g09d2 From 64e6a941639aab8d7109178aa151a50909547309 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 18 Jun 2019 09:05:41 -0400 Subject: fixed index out of range --- src/client/views/nodes/PDFBox.tsx | 1 - src/client/views/pdf/PDFViewer.tsx | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index e79182e9b..38e91ac12 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -98,7 +98,6 @@ export class PDFBox extends DocComponent(PdfDocumen render() { // uses mozilla pdf as default const pdfUrl = Cast(this.props.Document.data, PdfField, new PdfField(window.origin + RouteStore.corsProxy + "/https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf")); - console.log(pdfUrl); let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); return (
{ initialLoad = async () => { if (this._pageSizes.length === 0) { let pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages); + this._isPage = Array(this.props.pdf.numPages); 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 })); } - this.props.loaded(pageSizes[0].width, pageSizes[0].height, this.props.pdf.numPages); runInAction(() => - Array.from(Array((this._pageSizes = pageSizes).length).keys()).map(this.getPlaceholderPage)) + Array.from(Array((this._pageSizes = pageSizes).length).keys()).map(this.getPlaceholderPage)); + this.props.loaded(pageSizes[0].width, pageSizes[0].height, this.props.pdf.numPages); } } -- cgit v1.2.3-70-g09d2 From cc032e2f60015728f64f46ef009c9306e36746a0 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Tue, 18 Jun 2019 10:05:49 -0400 Subject: fixes --- src/client/views/nodes/PDFBox.tsx | 2 +- src/client/views/pdf/PDFMenu.tsx | 24 +++++++++++++++++------- src/client/views/pdf/PDFViewer.tsx | 21 +++++++++++++++++++++ 3 files changed, 39 insertions(+), 8 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 655c12ab3..ae68a530e 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -62,7 +62,7 @@ export class PDFBox extends DocComponent(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: "smooth" }); + ele && ele.scrollTo({ top: NumCast(this.Document.scrollY), behavior: "auto" }); }); } diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx index b44370e3d..7817e8c26 100644 --- a/src/client/views/pdf/PDFMenu.tsx +++ b/src/client/views/pdf/PDFMenu.tsx @@ -10,8 +10,8 @@ import { Doc } from "../../../new_fields/Doc"; export default class PDFMenu extends React.Component { static Instance: PDFMenu; - @observable private _top: number = 0; - @observable private _left: number = 0; + @observable private _top: number = -300; + @observable private _left: number = -300; @observable private _opacity: number = 1; @observable private _transition: string = "opacity 0.5s"; @observable private _transitionDelay: string = ""; @@ -22,18 +22,25 @@ export default class PDFMenu extends React.Component { @observable Highlighting: boolean = false; private _timeout: NodeJS.Timeout | undefined; + private _offsetY: number = 0; + private _offsetX: number = 0; + private _mainCont: React.RefObject; constructor(props: Readonly<{}>) { super(props); PDFMenu.Instance = this; + + this._mainCont = React.createRef(); } pointerDown = (e: React.PointerEvent) => { document.removeEventListener("pointermove", this.StartDrag); document.addEventListener("pointermove", this.StartDrag); - document.removeEventListener("pointerup", this.pointerUp) - document.addEventListener("pointerup", this.pointerUp) + document.removeEventListener("pointerup", this.pointerUp); + document.addEventListener("pointerup", this.pointerUp); + + console.log(this.StartDrag); e.stopPropagation(); e.preventDefault(); @@ -102,8 +109,8 @@ export default class PDFMenu extends React.Component { @action dragging = (e: PointerEvent) => { - this._left += e.movementX; - this._top += e.movementY; + this._left = e.pageX - this._offsetX; + this._top = e.pageY - this._offsetY; e.stopPropagation(); e.preventDefault(); @@ -122,6 +129,9 @@ export default class PDFMenu extends React.Component { document.removeEventListener("pointerup", this.dragEnd); document.addEventListener("pointerup", this.dragEnd); + this._offsetX = this._mainCont.current!.getBoundingClientRect().width - e.nativeEvent.offsetX; + this._offsetY = e.nativeEvent.offsetY; + e.stopPropagation(); e.preventDefault(); } @@ -139,7 +149,7 @@ export default class PDFMenu extends React.Component { render() { return ( -
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 86a17c0a6..69372f43b 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -388,7 +388,7 @@ class Viewer extends React.Component { {this._annotations.map(anno => this.renderAnnotation(anno))}
-
+
); } } @@ -522,7 +522,7 @@ class PinAnnotation extends React.Component { class RegionAnnotation extends React.Component { @observable private _backgroundColor: string = "red"; - onPointerDown = (e: React.PointerEvent) => { + onPointerDown = (e: React.MouseEvent) => { let targetDoc = Cast(this.props.document.target, Doc, null); if (targetDoc) { DocumentManager.Instance.jumpToDocument(targetDoc); @@ -531,7 +531,7 @@ class RegionAnnotation extends React.Component { render() { return ( -
); } diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index bb87ec9d4..a19b64eda 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -402,7 +402,8 @@ export default class Page extends React.Component { let boundingRect = this._textLayer.current.getBoundingClientRect(); for (let i = 0; i < clientRects.length; i++) { let rect = clientRects.item(i); - if (rect) { + if (rect && rect.width !== this._textLayer.current.getBoundingClientRect().width && rect.height !== this._textLayer.current.getBoundingClientRect().height) { + console.log(rect); let annoBox = document.createElement("div"); annoBox.className = "pdfViewer-annotationBox"; // transforms the positions from screen onto the pdf div -- cgit v1.2.3-70-g09d2 From 37f327ab659e6fa1221f9f4ed7649402c5dedc00 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Wed, 19 Jun 2019 11:24:32 -0400 Subject: aspect ratio, dragging, and full screen scrolling fixed --- src/client/views/nodes/PDFBox.tsx | 2 +- src/client/views/pdf/PDFMenu.tsx | 20 ++++++++++++++++---- src/client/views/pdf/PDFViewer.tsx | 15 ++++++++++----- src/client/views/pdf/Page.tsx | 10 +++------- 4 files changed, 30 insertions(+), 17 deletions(-) (limited to 'src/client/views/nodes/PDFBox.tsx') diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 7dd2b1dc5..d2de1cb1c 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -97,7 +97,7 @@ export class PDFBox extends DocComponent(PdfDocumen render() { // uses mozilla pdf as default const pdfUrl = Cast(this.props.Document.data, PdfField, new PdfField(window.origin + RouteStore.corsProxy + "/https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf")); - let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); + let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); return (
; + private _dragging: boolean = false; constructor(props: Readonly<{}>) { super(props); @@ -35,19 +36,30 @@ export default class PDFMenu extends React.Component { } pointerDown = (e: React.PointerEvent) => { - document.removeEventListener("pointermove", this.StartDrag); - document.addEventListener("pointermove", this.StartDrag); + document.removeEventListener("pointermove", this.pointerMove); + document.addEventListener("pointermove", this.pointerMove); document.removeEventListener("pointerup", this.pointerUp); document.addEventListener("pointerup", this.pointerUp); - console.log(this.StartDrag); + e.stopPropagation(); + e.preventDefault(); + } + pointerMove = (e: PointerEvent) => { e.stopPropagation(); e.preventDefault(); + + if (this._dragging) { + return; + } + + this.StartDrag(e); + this._dragging = true; } pointerUp = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.StartDrag); + this._dragging = false; + document.removeEventListener("pointermove", this.pointerMove); document.removeEventListener("pointerup", this.pointerUp); e.stopPropagation(); e.preventDefault(); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 8c0aaea00..67278b1eb 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -90,7 +90,8 @@ class Viewer extends React.Component { @action componentDidMount = () => { this._reactionDisposer = reaction( - () => [this.props.parent.props.active(), this.startIndex, this.endIndex], + + () => [this.props.parent.props.active(), this.startIndex, this._pageSizes.length ? this.endIndex : 0], async () => { await this.initialLoad(); this.renderPages(); @@ -114,12 +115,16 @@ class Viewer extends React.Component { let pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages); this._isPage = Array(this.props.pdf.numPages); 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 })); + 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); + pageSizes[i] = { width: x.width, height: x.height }; + })); } runInAction(() => Array.from(Array((this._pageSizes = pageSizes).length).keys()).map(this.getPlaceholderPage)); - this.props.loaded(pageSizes[0].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); + // this.props.loaded(Math.max(...pageSizes.map(i => i.width)), pageSizes[0].height, this.props.pdf.numPages); } } @@ -236,7 +241,7 @@ class Viewer extends React.Component { // endIndex: where to end rendering pages @computed get endIndex(): number { - return Math.min(this.props.pdf.numPages - 1, this.getPageFromScroll(this.scrollY) + this._pageBuffer); + return Math.min(this.props.pdf.numPages - 1, this.getPageFromScroll(this.scrollY + this._pageSizes[0].height) + this._pageBuffer); } @action diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index a19b64eda..2697c9eee 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -51,7 +51,6 @@ export default class Page extends React.Component { private _marquee: React.RefObject; private _curly: React.RefObject; private _marqueeing: boolean = false; - private _dragging: boolean = false; private _reactionDisposer?: IReactionDisposer; constructor(props: IPageProps) { @@ -151,13 +150,9 @@ export default class Page extends React.Component { */ @action startDrag = (e: PointerEvent): void => { - // the first 5 lines is a hack to prevent text selection while dragging e.preventDefault(); e.stopPropagation(); - if (this._dragging) { - return; - } - this._dragging = true; + console.log("dragging"); let thisDoc = this.props.parent.Document; // document that this annotation is linked to let targetDoc = Docs.TextDocument({ width: 200, height: 200, title: "New Annotation" }); @@ -179,7 +174,6 @@ export default class Page extends React.Component { endDrag = (e: PointerEvent): void => { // document.removeEventListener("pointermove", this.startDrag); // document.removeEventListener("pointerup", this.endDrag); - this._dragging = false; e.stopPropagation(); } @@ -195,6 +189,8 @@ export default class Page extends React.Component { // document.addEventListener("pointerup", this.endDrag); } else if (e.button === 0) { + PDFMenu.Instance.StartDrag = this.startDrag; + PDFMenu.Instance.Highlight = this.highlight; PDFMenu.Instance.fadeOut(true); let target: any = e.target; if (target && target.parentElement === this._textLayer.current) { -- cgit v1.2.3-70-g09d2