aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts9
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx1
-rw-r--r--src/client/views/nodes/PDFBox.tsx363
-rw-r--r--src/client/views/pdf/PDFBox2.tsx28
-rw-r--r--src/client/views/pdf/PDFViewer.scss27
-rw-r--r--src/client/views/pdf/PDFViewer.tsx349
6 files changed, 440 insertions, 337 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ab61b915c..be7356a09 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -33,6 +33,11 @@ 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";
+=======
+>>>>>>> 7d3ef1c914cc1cc0b6c05b14773a8b83e1b95c96
import { UndoManager } from "../util/UndoManager";
import { RouteStore } from "../../server/RouteStore";
var requestImageSize = require('request-image-size');
@@ -174,8 +179,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, 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/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 02396c3af..c2caabb92 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -23,6 +23,7 @@ import { FieldViewProps } from "./FieldView";
import { Without, OmitKeys } from "../../../Utils";
import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
import { List } from "../../../new_fields/List";
+import { PDFBox2 } from "../pdf/PDFBox2";
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
type BindingProps = Without<FieldViewProps, 'fieldKey'>;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index aa29a7170..83f69f7f9 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -4,12 +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 { Id } from "../../../new_fields/FieldSymbols";
-import { makeInterface } from "../../../new_fields/Schema";
-import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
-import { ImageField, PdfField } from "../../../new_fields/URLField";
+// 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";
@@ -23,7 +19,12 @@ import { pageSchema } from "./ImageBox";
import "./PDFBox.scss";
var path = require('path');
import React = require("react");
-import { ContextMenu } from "../ContextMenu";
+import { SelectionManager } from "../../util/SelectionManager";
+import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { Opt, HeightSym, Doc } from "../../../new_fields/Doc";
+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
* This method renders PDF and puts all kinds of functionalities such as annotation, highlighting,
@@ -55,349 +56,41 @@ const PdfDocument = makeInterface(positionSchema, pageSchema);
export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocument) {
public static LayoutString() { return FieldView.LayoutString(PDFBox); }
- private _mainDiv = React.createRef<HTMLDivElement>();
- private renderHeight = 2400;
-
- @observable private _renderAsSvg = true;
@observable private _alt = false;
+ @observable private _scrollY: number = 0;
- 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 = this.props.isSelected();
- this._reactionDisposer = reaction(
- () => [this.props.isSelected(), this.curPage],
- () => {
- if (this.curPage > 0 && !this.props.isTopMost && 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<ChildNode>) => {
- 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 = <Annotation key={Utils.GenerateGuid()} Span={span} X={divX} Y={divY - 300} Highlights={this._pageInfo.divs} Annotations={this._pageInfo.anno} CurrAnno={this._currAnno} />;
- this._pageInfo.anno.push(annotation);
- this._currAnno.push(annotation);
- }
- }
-
- }
-
- /**
- * highlight function for highlighting actual text. This works fine.
- */
- highlight = (color: string) => {
- if (window.getSelection()) {
- try {
- if (!document.execCommand("hiliteColor", false, color)) {
- this.makeEditableAndHighlight(color);
- }
- } catch (ex) {
- this.makeEditableAndHighlight(color);
- }
- }
- }
-
- /**
- * controls the area highlighting (stickies) Kinda temporary
- */
- onPointerDown = (e: React.PointerEvent) => {
- if (this.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);
+ 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 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.
+ 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;
}
- this._interactive = true;
- }
-
-
- @action
- saveThumbnail = () => {
- this.props.Document.thumbnailPage = FieldValue(this.Document.curPage, -1);
- this._renderAsSvg = false;
- setTimeout(() => {
- runInAction(() => this._smallRetryCount = this._mediumRetryCount = this._largeRetryCount = 0);
- let nwidth = FieldValue(this.Document.nativeWidth, 0);
- let nheight = FieldValue(this.Document.nativeHeight, 0);
- htmlToImage.toPng(this._mainDiv.current!, { width: nwidth, height: nheight, quality: 0.8 })
- .then(action((dataUrl: string) => {
- SearchBox.convertDataUri(dataUrl, "icon" + this.Document[Id] + "_" + this.curPage).then((returnedFilename) => {
- if (returnedFilename) {
- let url = DocServer.prepend(returnedFilename);
- this.props.Document.thumbnail = new ImageField(new URL(url));
- }
- runInAction(() => 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)];
+ onScroll = (e: React.UIEvent<HTMLDivElement>) => {
+ if (e.currentTarget) {
+ this._scrollY = e.currentTarget.scrollTop;
}
- 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 <Page height={this.renderHeight} renderTextLayer={false} pageNumber={this.curPage} onLoadSuccess={this.onLoaded} />;
- }
- @computed
- get pdfContent() {
- let pdfUrl = Cast(this.props.Document[this.props.fieldKey], PdfField);
- if (!pdfUrl) {
- return <p>No pdf url to render</p>;
- }
- let pdfpage = this.pdfPage;
- let body = this.Document.nativeHeight ?
- pdfpage :
- <Measure offset onResize={this.setScaling}>
- {({ measureRef }) =>
- <div className="pdfBox-page" ref={measureRef}>
- {pdfpage}
- </div>
- }
- </Measure>;
- let xf = (this.Document.nativeHeight || 0) / this.renderHeight;
- return <div className="pdfBox-contentContainer" key="container" style={{ transform: `scale(${xf}, ${xf})` }}>
- <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl.url}`} renderMode={this._renderAsSvg || this.props.isTopMost ? "svg" : "canvas"}>
- {body}
- </Document>
- </div >;
- }
-
- @computed
- get pdfRenderer() {
- let pdfUrl = Cast(this.props.Document[this.props.fieldKey], PdfField);
- let proxy = this.imageProxyRenderer;
- if ((!this._interactive && proxy && (!this.props.ContainingCollectionView || !this.props.ContainingCollectionView.props.isTopMost)) || !pdfUrl) {
- return proxy;
- }
- return [
- proxy,
- this._pageInfo.area.filter(() => this._pageInfo.area).map((element: any) => element),
- this._currAnno.map((element: any) => element),
- this.pdfContent
- ];
- }
-
- choosePath(url: URL) {
- if (url.protocol === "data" || url.href.indexOf(window.location.origin) === -1)
- return url.href;
- let ext = path.extname(url.href);
- return url.href.replace(ext, this._curSuffix + ext);
- }
- @observable _smallRetryCount = 1;
- @observable _mediumRetryCount = 1;
- @observable _largeRetryCount = 1;
- @action retryPath = () => {
- if (this._curSuffix === "_s") this._smallRetryCount++;
- if (this._curSuffix === "_m") this._mediumRetryCount++;
- if (this._curSuffix === "_l") this._largeRetryCount++;
- }
- @action onError = () => {
- let timeout = this._curSuffix === "_s" ? this._smallRetryCount : this._curSuffix === "_m" ? this._mediumRetryCount : this._largeRetryCount;
- if (timeout < 10)
- setTimeout(this.retryPath, Math.min(10000, timeout * 5));
- }
- _curSuffix = "_m";
-
- @computed
- get imageProxyRenderer() {
- let thumbField = this.props.Document.thumbnail;
- if (thumbField && this._renderAsSvg && NumCast(this.props.Document.thumbnailPage, 0) === this.Document.curPage) {
-
- // let transform = this.props.ScreenToLocalTransform().inverse();
- let pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50;
- // var [sptX, sptY] = transform.transformPoint(0, 0);
- // let [bptX, bptY] = transform.transformPoint(pw, this.props.PanelHeight());
- // let w = bptX - sptX;
-
- let path = thumbField instanceof ImageField ? thumbField.url.href : "http://cs.brown.edu/people/bcz/prairie.jpg";
- // this._curSuffix = "";
- // if (w > 20) {
- let field = thumbField;
- // if (w < 100 && this._smallRetryCount < 10) this._curSuffix = "_s";
- // else if (w < 400 && this._mediumRetryCount < 10) this._curSuffix = "_m";
- // else if (this._largeRetryCount < 10) this._curSuffix = "_l";
- if (field instanceof ImageField) path = this.choosePath(field.url);
- // }
- return <img className="pdfBox-thumbnail" key={path + (this._mediumRetryCount).toString()} src={path} onError={this.onError} />;
- }
- return (null);
- }
- @action onKeyDown = (e: React.KeyboardEvent) => e.key === "Alt" && (this._alt = true);
- @action onKeyUp = (e: React.KeyboardEvent) => e.key === "Alt" && (this._alt = false);
- onContextMenu = (e: React.MouseEvent): void => {
- let field = Cast(this.Document[this.props.fieldKey], PdfField);
- if (field) {
- let url = field.url.href;
- ContextMenu.Instance.addItem({
- description: "Copy path", event: () => {
- Utils.CopyText(url);
- }, icon: "expand-arrows-alt"
- });
- }
- }
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 (
- <div className={classname} tabIndex={0} ref={this._mainDiv} onPointerDown={this.onPointerDown} onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} onContextMenu={this.onContextMenu} >
- {this.pdfRenderer}
- </div >
+ <div onScroll={this.onScroll} style={{ overflow: "scroll", height: `${NumCast(this.props.Document.nativeWidth ? this.props.Document.nativeWidth : 300)}px` }} onWheel={(e: React.WheelEvent) => e.stopPropagation()} className={classname}>
+ <PDFViewer url={pdfUrl} loaded={this.loaded} scrollY={this._scrollY} parent={this} />
+ </div>
);
}
diff --git a/src/client/views/pdf/PDFBox2.tsx b/src/client/views/pdf/PDFBox2.tsx
new file mode 100644
index 000000000..71825c260
--- /dev/null
+++ b/src/client/views/pdf/PDFBox2.tsx
@@ -0,0 +1,28 @@
+import React = require("react");
+import { FieldViewProps, FieldView } from "../nodes/FieldView";
+import { DocComponent } from "../DocComponent";
+import { makeInterface } from "../../../new_fields/Schema";
+import { positionSchema } from "../nodes/DocumentView";
+import { pageSchema } from "../nodes/ImageBox";
+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);
+
+@observer
+export class PDFBox2 extends DocComponent<FieldViewProps, PdfDocument>(PdfDocument) {
+ 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 (
+ <PDFViewer url={pdfUrl} />
+ )
+ }
+} \ No newline at end of file
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
new file mode 100644
index 000000000..9d41a1bb0
--- /dev/null
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -0,0 +1,27 @@
+.textLayer {
+ div {
+ user-select: text;
+ }
+}
+
+.viewer-button-cont {
+ position: absolute;
+ display: flex;
+ justify-content: space-evenly;
+ align-items: center;
+}
+
+.viewer-previousPage,
+.viewer-nextPage {
+ background: grey;
+ font-weight: bold;
+ opacity: 0.5;
+ padding: 0 10px;
+ 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
new file mode 100644
index 000000000..d510ba91c
--- /dev/null
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -0,0 +1,349 @@
+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 } 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
+export class PDFViewer extends React.Component<IPDFViewerProps> {
+ @observable _pdf: Opt<Pdfjs.PDFDocumentProxy>;
+
+ @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;
+
+ promise.then((pdf: Pdfjs.PDFDocumentProxy) => {
+ runInAction(() => this._pdf = pdf);
+ });
+ }
+
+ render() {
+ return (
+ <div>
+ <Viewer pdf={this._pdf} loaded={this.props.loaded} scrollY={this.props.scrollY} parent={this.props.parent} />
+ </div>
+ );
+ }
+}
+
+interface IViewerProps {
+ pdf: Opt<Pdfjs.PDFDocumentProxy>;
+ loaded: (nw: number, nh: number) => void;
+ scrollY: number;
+ parent: PDFBox;
+}
+
+@observer
+class Viewer extends React.Component<IViewerProps> {
+ @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<Pdfjs.PDFDocumentProxy>;
+ @observable private _renderAsSvg = true;
+
+ private _pageBuffer: number = 1;
+ private _reactionDisposer?: IReactionDisposer;
+ private _mainDiv = React.createRef<HTMLDivElement>();
+
+ @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;
+ }
+
+ @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);
+ }
+
+ 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 => (
+ <Page
+ 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"}`}
+ pageLoaded={this.pageLoaded}
+ {...this.props} />
+ ));
+ 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] = (
+ <Page
+ 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"}`}
+ pageLoaded={this.pageLoaded}
+ {...this.props} />
+ );
+ this._isPage[i] = true;
+ }
+ else if (!this._isPage[i]) {
+ this._visibleElements[i] = (
+ <Page
+ 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"}`}
+ pageLoaded={this.pageLoaded}
+ {...this.props} />
+ );
+ this._isPage[i] = true;
+ }
+ }
+
+ for (let i = 0; i < numPages; i++) {
+ if (i < startIndex || i > endIndex) {
+ if (this._isPage[i]) {
+ this._visibleElements[i] = (
+ <div key={`pdfviewer-placeholder-${i}`} className="pdfviewer-placeholder" style={{ width: this._pageSizes[i] ? this._pageSizes[i].width : 0, height: this._pageSizes[i] ? this._pageSizes[i].height : 0 }} />
+ );
+ 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 => (
+ <div key={`pdfviewer-placeholder-${i}`} className="pdfviewer-placeholder" style={{ width: this._pageSizes[i] ? this._pageSizes[i].width : 0, height: this._pageSizes[i] ? this._pageSizes[i].height : 0 }} />
+ ));
+ this._visibleElements = new Array<JSX.Element>(...divs);
+ }
+ }
+
+ render() {
+ console.log(`START: ${this.startIndex}`);
+ console.log(`END: ${this.endIndex}`)
+ let numPages = this.props.pdf ? this.props.pdf.numPages : 0;
+ return (
+ <div className="viewer" ref={this._mainDiv}>
+ {/* {Array.from(Array(numPages).keys()).map((i) => (
+ <Page
+ 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"}`}
+ pageLoaded={this.pageLoaded}
+ {...this.props}
+ />
+ ))} */}
+ {this._visibleElements}
+ </div>
+ );
+ }
+}
+
+interface IPageProps {
+ pdf: Opt<Pdfjs.PDFDocumentProxy>;
+ name: string;
+ numPages: number;
+ page: number;
+ pageLoaded: (index: number, page: Pdfjs.PDFPageViewport) => void;
+}
+
+@observer
+class Page extends React.Component<IPageProps> {
+ @observable _state: string = "N/A";
+ @observable _width: number = 0;
+ @observable _height: number = 0;
+ @observable _page: Opt<Pdfjs.PDFPageProxy>;
+ canvas: React.RefObject<HTMLCanvasElement>;
+ textLayer: React.RefObject<HTMLDivElement>;
+ @observable _currPage: number = this.props.page + 1;
+
+ constructor(props: IPageProps) {
+ super(props);
+ this.canvas = React.createRef();
+ this.textLayer = React.createRef();
+ }
+
+ componentDidMount() {
+ console.log(this.props.pdf);
+ if (this.props.pdf) {
+ this.update(this.props.pdf);
+ }
+ }
+
+ componentDidUpdate() {
+ if (this.props.pdf) {
+ this.update(this.props.pdf);
+ }
+ }
+
+ private update = (pdf: Pdfjs.PDFDocumentProxy) => {
+ if (pdf) {
+ this.loadPage(pdf);
+ }
+ else {
+ this._state = "loading";
+ }
+ }
+
+ private loadPage = (pdf: Pdfjs.PDFDocumentProxy) => {
+ if (this._state === "rendering" || this._page) return;
+
+ pdf.getPage(this._currPage).then(
+ (page: Pdfjs.PDFPageProxy) => {
+ this._state = "rendering";
+ this.renderPage(page);
+ }
+ );
+ }
+
+ @action
+ private renderPage = (page: Pdfjs.PDFPageProxy) => {
+ let scale = 1;
+ let viewport = page.getViewport(scale);
+ let canvas = this.canvas.current;
+ if (canvas) {
+ let context = canvas.getContext("2d");
+ canvas.width = viewport.width;
+ 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) => {
+ //@ts-ignore
+ let textLayer = Pdfjs.renderTextLayer({
+ textContent: res,
+ container: this.textLayer.current,
+ viewport: viewport
+ });
+ // textLayer._render();
+ this._state = "rendered";
+ });
+
+ this._page = page;
+ }
+ }
+ }
+
+ onPointerDown = (e: React.PointerEvent) => {
+ console.log("down");
+ e.stopPropagation();
+ }
+
+ onPointerMove = (e: React.PointerEvent) => {
+ console.log("move")
+ e.stopPropagation();
+ }
+
+ render() {
+ return (
+ <div onPointerDown={this.onPointerDown} onPointerMove={this.onPointerMove} className={this.props.name} style={{ "width": this._width, "height": this._height }}>
+ <div className="canvasContainer">
+ <canvas ref={this.canvas} />
+ </div>
+ <div className="textlayer" ref={this.textLayer} style={{ "position": "relative", "top": `-${this._height}px`, "height": `${this._height}px` }} />
+ </div>
+ );
+ }
+} \ No newline at end of file