aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/pdf/PDFViewer.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/pdf/PDFViewer.tsx')
-rw-r--r--src/client/views/pdf/PDFViewer.tsx109
1 files changed, 74 insertions, 35 deletions
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index b79be2e68..48c7b1762 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -3,14 +3,14 @@ import { observer } from "mobx-react";
import * as Pdfjs from "pdfjs-dist";
import "pdfjs-dist/web/pdf_viewer.css";
import { Dictionary } from "typescript-collections";
-import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym, Field } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
import { Id } from "../../../fields/FieldSymbols";
import { InkTool } from "../../../fields/InkField";
import { List } from "../../../fields/List";
import { createSchema, makeInterface } from "../../../fields/Schema";
import { ScriptField } from "../../../fields/ScriptField";
-import { Cast, NumCast } from "../../../fields/Types";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { PdfField } from "../../../fields/URLField";
import { GetEffectiveAcl, TraceMobx } from "../../../fields/util";
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, emptyPath, intersectRect, returnZero, smoothScroll, Utils, OmitKeys } from "../../../Utils";
@@ -26,7 +26,6 @@ import { undoBatch } from "../../util/UndoManager";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
import { CollectionView } from "../collections/CollectionView";
import { ViewBoxAnnotatableComponent } from "../DocComponent";
-import { DocumentDecorations } from "../DocumentDecorations";
import { Annotation } from "./Annotation";
import { PDFMenu } from "./PDFMenu";
import "./PDFViewer.scss";
@@ -35,6 +34,8 @@ import React = require("react");
import { LinkDocPreview } from "../nodes/LinkDocPreview";
import { FormattedTextBoxComment } from "../nodes/formattedText/FormattedTextBoxComment";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { SharingManager } from "../../util/SharingManager";
+import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox";
const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
const pdfjsLib = require("pdfjs-dist");
const _global = (window /* browser */ || global /* node */) as any;
@@ -59,9 +60,10 @@ interface IViewerProps {
fieldKey: string;
Document: Doc;
DataDoc?: Doc;
- docFilters: () => string[];
searchFilterDocs: () => Doc[];
ContainingCollectionView: Opt<CollectionView>;
+ docFilters: () => string[];
+ docRangeFilters: () => string[];
PanelWidth: () => number;
PanelHeight: () => number;
ContentScaling: () => number;
@@ -71,7 +73,7 @@ interface IViewerProps {
renderDepth: number;
focus: (doc: Doc) => void;
isSelected: (outsideReaction?: boolean) => boolean;
- loaded: (nw: number, nh: number, np: number) => void;
+ loaded?: (nw: number, nh: number, np: number) => void;
active: (outsideReaction?: boolean) => boolean;
isChildActive: (outsideReaction?: boolean) => boolean;
addDocTab: (document: Doc, where: string) => boolean;
@@ -100,6 +102,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@observable private _showWaiting = true;
@observable private _showCover = false;
@observable private _zoomed = 1;
+ @observable private _overlayAnnoInfo: Opt<Doc>;
private _pdfViewer: any;
private _styleRule: any; // stylesheet rule for making hyperlinks clickable
@@ -119,8 +122,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
private _viewerIsSetup = false;
@computed get allAnnotations() {
- return DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]).
- filter(anno => this._script.run({ this: anno }, console.log, true).result);
+ return DocUtils.FilterDocs(DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]), this.props.docFilters(), this.props.docRangeFilters(), undefined);
}
@computed get nonDocAnnotations() { return this.allAnnotations.filter(a => a.annotations); }
@@ -134,7 +136,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
const coreFilename = pathComponents.pop()!.split(".")[0];
const params: any = {
coreFilename,
- pageNum: this.Document._curPage || 1,
+ pageNum: Math.min(this.props.pdf.numPages, Math.max(1, this.Document._curPage || 1)),
};
if (pathComponents.length) {
params.subtree = `${pathComponents.join("/")}/`;
@@ -143,7 +145,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
} else {
const params: any = {
coreFilename: relative.split("/")[relative.split("/").length - 1],
- pageNum: this.Document._curPage || 1,
+ pageNum: Math.min(this.props.pdf.numPages, Math.max(1, this.Document._curPage || 1)),
};
this._coverPath = "http://cs.brown.edu/~bcz/face.gif";//href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" };
}
@@ -153,6 +155,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
this._mainCont.current.scrollTop = this.layoutDoc._scrollTop || 0;
const observer = new _global.ResizeObserver(action((entries: any) => this._mainCont.current && (this._mainCont.current.scrollTop = this.layoutDoc._scrollTop || 0)));
observer.observe(this._mainCont.current);
+ this._mainCont.current.addEventListener("scroll", (e) => (e.target as any).scrollLeft = 0);
}
this._disposers.searchMatch = reaction(() => Doc.IsSearchMatch(this.rootDoc),
@@ -176,12 +179,27 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
(scrollY) => {
if (scrollY !== undefined) {
(this._showCover || this._showWaiting) && this.setupPdfJsViewer();
- if ((this.props.renderDepth === -1 || (!LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc)) && this._mainCont.current) {
- smoothScroll(1000, this._mainCont.current, (this.Document._scrollY || 0));
- } else {
- console.log("Waiting for preview");
+ if (this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) {
+ const delay = this._mainCont.current ? 0 : 250; // wait for mainCont and try again to scroll
+ const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/);
+ const duration = durationStr ? Number(durationStr[1]) : 1000;
+ setTimeout(() => this._mainCont.current && smoothScroll(duration, this._mainCont.current, Math.abs(scrollY || 0)), delay);
+ setTimeout(() => { this.Document._scrollTop = scrollY; this.Document._scrollY = undefined; }, duration + delay);
+ }
+ }
+ },
+ { fireImmediately: true }
+ );
+ this._disposers.scrollPreviewY = reaction(
+ () => Cast(this.Document._scrollPreviewY, "number", null),
+ (scrollY) => {
+ if (scrollY !== undefined) {
+ (this._showCover || this._showWaiting) && this.setupPdfJsViewer();
+ if (this.props.renderDepth === -1 && scrollY >= 0) {
+ if (!this._mainCont.current) setTimeout(() => this._mainCont.current && smoothScroll(1000, this._mainCont.current, scrollY || 0));
+ else smoothScroll(1000, this._mainCont.current, scrollY || 0);
+ this.Document._scrollPreviewY = undefined;
}
- setTimeout(() => this.Document._scrollY = undefined, 1000);
}
},
{ fireImmediately: true }
@@ -220,7 +238,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
width: (page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]),
height: (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0])
});
- i === this.props.pdf.numPages - 1 && this.props.loaded((page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]),
+ i === this.props.pdf.numPages - 1 && this.props.loaded?.((page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]),
(page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0]), i);
}))));
this.Document.scrollHeight = this._pageSizes.reduce((size, page) => size + page.height, 0) * 96 / 72;
@@ -337,6 +355,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
mainAnnoDocProto.y = Math.max(minY, 0);
mainAnnoDocProto.x = Math.max(maxX, 0);
mainAnnoDocProto.type = DocumentType.PDFANNO;
+ mainAnnoDocProto.text = this._selectionText;
mainAnnoDocProto.annotations = new List<Doc>(annoDocs);
}
mainAnnoDocProto.title = "Annotation on " + this.Document.title;
@@ -384,12 +403,12 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@action
onScroll = (e: React.UIEvent<HTMLElement>) => {
if (!LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) {
- this.Document._scrollY === undefined && (this.layoutDoc._scrollTop = this._mainCont.current!.scrollTop);
this.pageDelay && clearTimeout(this.pageDelay);
this.pageDelay = setTimeout(() => {
+ this.Document._scrollY === undefined && this._mainCont.current && (this.layoutDoc._scrollTop = this._mainCont.current.scrollTop);
this.pageDelay = undefined;
- this._pdfViewer && (this.Document._curPage = this._pdfViewer.currentPageNumber);
- }, 250);
+ //this._pdfViewer && (this.Document._curPage = this._pdfViewer.currentPageNumber);
+ }, 1000);
}
}
@@ -457,7 +476,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@action
onPointerDown = (e: React.PointerEvent): void => {
const hit = document.elementFromPoint(e.clientX, e.clientY);
- if (hit && hit.localName === "span" && this.props.isSelected(true)) { // drag selecting text stops propagation
+ if (hit && hit.localName === "span" && this.annotationsActive(true)) { // drag selecting text stops propagation
e.button === 0 && e.stopPropagation();
}
// if alt+left click, drag and annotate
@@ -527,8 +546,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
if (rect) {
const scaleY = this._mainCont.current.offsetHeight / boundingRect.height;
const scaleX = this._mainCont.current.offsetWidth / boundingRect.width;
- if (rect.width !== this._mainCont.current.clientWidth &&
- (i === 0 || !intersectRect(clientRects[i], clientRects[i - 1]))) {
+ if (rect.width !== this._mainCont.current.clientWidth) {
const annoBox = document.createElement("div");
annoBox.className = "pdfViewerDash-annotationBox";
// transforms the positions from screen onto the pdf div
@@ -554,6 +572,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@action
onSelectEnd = (e: PointerEvent): void => {
clearStyleSheetRules(PDFViewer._annotationStyle);
+ this.props.select(false);
this._savedAnnotations.clear();
if (this._marqueeing) {
if (this._marqueeWidth > 10 || this._marqueeHeight > 10) {
@@ -623,6 +642,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
clipDoc._height = this.marqueeHeight();
clipDoc._scrollTop = this.marqueeY();
const targetDoc = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.Document.title, 0, 0, 100, 100);
+ FormattedTextBox.SelectOnLoad = targetDoc[Id];
Doc.GetProto(targetDoc).data = new List<Doc>([clipDoc]);
clipDoc.rootDocument = targetDoc;
// DocUtils.makeCustomViewClicked(targetDoc, Docs.Create.StackingDocument, "slideView", undefined);
@@ -639,6 +659,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
e.annoDragData.linkDocument = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
}
annotationDoc.isLinkButton = true; // prevents link button fro showing up --- maybe not a good thing?
+ annotationDoc.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.props.Document;
e.annoDragData && e.annoDragData.linkDocument && e.annoDragData?.linkDropCallback?.({ linkDocument: e.annoDragData.linkDocument });
}
});
@@ -660,14 +681,14 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
getCoverImage = () => {
- if (!this.props.Document[HeightSym]() || !this.props.Document._nativeHeight) {
+ if (!this.props.Document[HeightSym]() || !Doc.NativeHeight(this.props.Document)) {
setTimeout((() => {
this.Document._height = this.Document[WidthSym]() * this._coverPath.height / this._coverPath.width;
- this.Document._nativeHeight = (this.Document._nativeWidth || 0) * this._coverPath.height / this._coverPath.width;
+ Doc.SetNativeWidth(this.Document, (Doc.NativeWidth(this.Document) || 0) * this._coverPath.height / this._coverPath.width);
}).bind(this), 0);
}
- const nativeWidth = (this.Document._nativeWidth || 0);
- const nativeHeight = (this.Document._nativeHeight || 0);
+ const nativeWidth = Doc.NativeWidth(this.Document);
+ const nativeHeight = Doc.NativeHeight(this.Document);
const resolved = Utils.prepend(this._coverPath.path);
return <img key={resolved} src={resolved} onError={action(() => this._coverPath.path = "http://www.cs.brown.edu/~bcz/face.gif")} onLoad={action(() => this._showWaiting = false)}
style={{ position: "absolute", display: "inline-block", top: 0, left: 0, width: `${nativeWidth}px`, height: `${nativeHeight}px` }} />;
@@ -687,18 +708,33 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@computed get annotationLayer() {
TraceMobx();
- return <div className="pdfViewerDash-annotationLayer" style={{ height: NumCast(this.Document._nativeHeight), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
+ return <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.Document), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
{this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
- <Annotation {...this.props} focus={this.props.focus} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)
+ <Annotation {...this.props} showInfo={this.showInfo} select={this.props.select} focus={this.props.focus} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)
}
</div>;
}
+
+ @computed get overlayInfo() {
+ return !this._overlayAnnoInfo || this._overlayAnnoInfo.author === Doc.CurrentUserEmail ? (null) :
+ <div className="pdfViewerDash-overlayAnno" style={{ top: NumCast(this._overlayAnnoInfo.y), left: NumCast(this._overlayAnnoInfo.x) }}>
+ <div className="pdfViewerDash-overlayAnno" style={{ right: -50, background: SharingManager.Instance.users.find(users => users.user.email === this._overlayAnnoInfo!.author)?.userColor }}>
+ {this._overlayAnnoInfo.author + " " + Field.toString(this._overlayAnnoInfo.creationDate as Field)}
+ </div>
+ </div>;
+ }
+
+ showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno);
overlayTransform = () => this.scrollXf().scale(1 / this._zoomed);
- panelWidth = () => (this.Document.scrollHeight || this.Document._nativeHeight || 0);
- panelHeight = () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : (this.Document._nativeWidth || 0);
+ panelWidth = () => (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
+ panelHeight = () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
@computed get overlayLayer() {
- return <div className={`pdfViewerDash-overlay${Doc.GetSelectedTool() !== InkTool.None || SnappingManager.GetIsDragging() ? "-inking" : ""}`} id="overlay"
- style={{ pointerEvents: SnappingManager.GetIsDragging() ? "all" : undefined, transform: `scale(${this._zoomed})` }}>
+ return <div className={`pdfViewerDash-overlay${Doc.GetSelectedTool() !== InkTool.None || SnappingManager.GetIsDragging() ? "-inking" : ""}`}
+ style={{
+ pointerEvents: SnappingManager.GetIsDragging() ? "all" : undefined,
+ mixBlendMode: this.allAnnotations.some(anno => anno.mixBlendMode) ? "hard-light" : undefined,
+ transform: `scale(${this._zoomed})`
+ }}>
<CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
LibraryPath={this.props.ContainingCollectionView?.props.LibraryPath ?? emptyPath}
annotationsKey={this.annotationKey}
@@ -719,6 +755,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
addDocument={this.addDocument}
+ docFilters={this.props.docFilters}
+ docRangeFilters={this.props.docRangeFilters}
CollectionView={undefined}
ScreenToLocalTransform={this.overlayTransform}
renderDepth={this.props.renderDepth + 1}
@@ -745,17 +783,18 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
contentZoom = () => this._zoomed;
render() {
TraceMobx();
- return <div className={"pdfViewerDash" + (this.active() ? "-interactive" : "")} ref={this._mainCont}
+ return <div className={"pdfViewerDash" + (this.annotationsActive() ? "-interactive" : "")} ref={this._mainCont}
onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick}
style={{
overflowX: this._zoomed !== 1 ? "scroll" : undefined,
- width: !this.props.Document._fitWidth && (window.screen.width > 600) ? NumCast(this.props.Document._nativeWidth) : `${100 / this.contentScaling}%`,
- height: !this.props.Document._fitWidth && (window.screen.width > 600) ? NumCast(this.props.Document._nativeHeight) : `${100 / this.contentScaling}%`,
+ width: !this.props.Document._fitWidth && (window.screen.width > 600) ? Doc.NativeWidth(this.props.Document) : `${100 / this.contentScaling}%`,
+ height: !this.props.Document._fitWidth && (window.screen.width > 600) ? Doc.NativeHeight(this.props.Document) : `${100 / this.contentScaling}%`,
transform: `scale(${this.props.ContentScaling()})`
}} >
{this.pdfViewerDiv}
- {this.overlayLayer}
{this.annotationLayer}
+ {this.overlayLayer}
+ {this.overlayInfo}
{this.standinViews}
<PdfViewerMarquee isMarqueeing={this.marqueeing} width={this.marqueeWidth} height={this.marqueeHeight} x={this.marqueeX} y={this.marqueeY} />
</div >;