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.tsx574
1 files changed, 289 insertions, 285 deletions
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 1856c5353..2c83082b7 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,39 +1,35 @@
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import * as Pdfjs from "pdfjs-dist";
-import "pdfjs-dist/web/pdf_viewer.css";
-import { Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { InkTool } from "../../../fields/InkField";
-import { Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
-import { PdfField } from "../../../fields/URLField";
-import { TraceMobx } from "../../../fields/util";
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils } from "../../../Utils";
-import { DocUtils } from "../../documents/Documents";
-import { Networking } from "../../Network";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { CompiledScript, CompileScript } from "../../util/Scripting";
-import { SelectionManager } from "../../util/SelectionManager";
-import { SharingManager } from "../../util/SharingManager";
-import { SnappingManager } from "../../util/SnappingManager";
-import { MarqueeOptionsMenu } from "../collections/collectionFreeForm";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
-import { MarqueeAnnotator } from "../MarqueeAnnotator";
-import { DocumentViewProps } from "../nodes/DocumentView";
-import { FieldViewProps } from "../nodes/FieldView";
-import { LinkDocPreview } from "../nodes/LinkDocPreview";
-import { StyleProp } from "../StyleProvider";
-import { AnchorMenu } from "./AnchorMenu";
-import { Annotation } from "./Annotation";
-import "./PDFViewer.scss";
-import React = require("react");
-const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
-const pdfjsLib = require("pdfjs-dist");
-const _global = (window /* browser */ || global /* node */) as any;
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as Pdfjs from 'pdfjs-dist';
+import 'pdfjs-dist/web/pdf_viewer.css';
+import { Doc, DocListCast, Field, HeightSym, Opt } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
+import { InkTool } from '../../../fields/InkField';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { TraceMobx } from '../../../fields/util';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils } from '../../../Utils';
+import { DocUtils } from '../../documents/Documents';
+import { SelectionManager } from '../../util/SelectionManager';
+import { SharingManager } from '../../util/SharingManager';
+import { SnappingManager } from '../../util/SnappingManager';
+import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
+import { MarqueeAnnotator } from '../MarqueeAnnotator';
+import { DocumentViewProps } from '../nodes/DocumentView';
+import { FieldViewProps } from '../nodes/FieldView';
+import { LinkDocPreview } from '../nodes/LinkDocPreview';
+import { StyleProp } from '../StyleProvider';
+import { AnchorMenu } from './AnchorMenu';
+import { Annotation } from './Annotation';
+import './PDFViewer.scss';
+import React = require('react');
+const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer');
+const pdfjsLib = require('pdfjs-dist');
+const _global = (window /* browser */ || global) /* node */ as any;
//pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
// The workerSrc property shall be specified.
-pdfjsLib.GlobalWorkerOptions.workerSrc = "https://unpkg.com/pdfjs-dist@2.13.216/build/pdf.worker.js";
+pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@2.14.305/build/pdf.worker.js';
interface IViewerProps extends FieldViewProps {
Document: Doc;
@@ -43,11 +39,10 @@ interface IViewerProps extends FieldViewProps {
fieldKey: string;
pdf: Pdfjs.PDFDocumentProxy;
url: string;
- startupLive: boolean;
loaded?: (nw: number, nh: number, np: number) => void;
setPdfViewer: (view: PDFViewer) => void;
- ContentScaling?: () => number;
anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
+ crop: (region: Doc | undefined, addCrop?: boolean) => Doc | undefined;
}
/**
@@ -56,14 +51,11 @@ interface IViewerProps extends FieldViewProps {
@observer
export class PDFViewer extends React.Component<IViewerProps> {
static _annotationStyle: any = addStyleSheet();
- @observable private _pageSizes: { width: number, height: number }[] = [];
+ @observable private _pageSizes: { width: number; height: number }[] = [];
@observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
- @observable private _script: CompiledScript = CompileScript("return true") as CompiledScript;
@observable private _marqueeing: number[] | undefined;
@observable private _textSelecting = true;
@observable private _showWaiting = true;
- @observable private _showCover = false;
- @observable private _zoomed = 1;
@observable private _overlayAnnoInfo: Opt<Doc>;
@observable private Index: number = -1;
@@ -74,105 +66,94 @@ export class PDFViewer extends React.Component<IViewerProps> {
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _disposers: { [name: string]: IReactionDisposer } = {};
private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
- private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
- private _selectionText: string = "";
+ public _getAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
+ _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+ private _selectionText: string = '';
private _downX: number = 0;
private _downY: number = 0;
- private _coverPath: any;
private _lastSearch = false;
private _viewerIsSetup = false;
private _ignoreScroll = false;
private _initialScroll: Opt<number>;
private _forcedScroll = true;
-
+ @observable isAnnotating = false;
// key where data is stored
@computed get allAnnotations() {
- return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + "-annotations"]), this.props.docFilters(), this.props.docRangeFilters(), undefined);
+ return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '-annotations']), this.props.docFilters(), this.props.docRangeFilters(), undefined);
+ }
+ @computed get inlineTextAnnotations() {
+ return this.allAnnotations.filter(a => a.textInlineAnnotations);
}
- @computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); }
componentDidMount = async () => {
- // change the address to be the file address of the PNG version of each page
- // file address of the pdf
- const { url: { href } } = Cast(this.props.dataDoc[this.props.fieldKey], PdfField)!;
- const { url: relative } = this.props;
- if (relative.includes("/pdfs/")) {
- const pathComponents = relative.split("/pdfs/")[1].split("/");
- const coreFilename = pathComponents.pop()!.split(".")[0];
- const params: any = {
- coreFilename,
- pageNum: Math.min(this.props.pdf.numPages, Math.max(1, NumCast(this.props.Document._curPage, 1))),
- };
- if (pathComponents.length) {
- params.subtree = `${pathComponents.join("/")}/`;
- }
- this._coverPath = href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" };
- } else {
- const params: any = {
- coreFilename: relative.split("/")[relative.split("/").length - 1],
- pageNum: Math.min(this.props.pdf.numPages, Math.max(1, NumCast(this.props.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: "" };
- }
- runInAction(() => this._showWaiting = true);
- this.props.startupLive && this.setupPdfJsViewer();
- this._mainCont.current?.addEventListener("scroll", e => (e.target as any).scrollLeft = 0);
+ runInAction(() => (this._showWaiting = true));
+ this.setupPdfJsViewer();
+ this._mainCont.current?.addEventListener('scroll', e => ((e.target as any).scrollLeft = 0));
- this._disposers.autoHeight = reaction(() => this.props.layoutDoc._autoHeight,
+ this._disposers.autoHeight = reaction(
+ () => this.props.layoutDoc._autoHeight,
autoHeight => {
if (autoHeight) {
- this.props.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]);
- this.props.setHeight(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1));
+ this.props.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']);
+ this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']) * (this.props.NativeDimScaling?.() || 1));
}
- });
+ }
+ );
- this._disposers.selected = reaction(() => this.props.isSelected(),
+ this._disposers.selected = reaction(
+ () => this.props.isSelected(),
selected => {
// if (!selected) {
// Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
// Array.from(this._savedAnnotations.keys()).forEach(k => this._savedAnnotations.set(k, []));
// }
- (SelectionManager.Views().length === 1) && this.setupPdfJsViewer();
+ SelectionManager.Views().length === 1 && this.setupPdfJsViewer();
},
- { fireImmediately: true });
- this._disposers.curPage = reaction(() => Cast(this.props.Document._curPage, "number", null),
- (page) => page !== undefined && page !== this._pdfViewer?.currentPageNumber && this.gotoPage(page),
{ fireImmediately: true }
);
- }
+ this._disposers.curPage = reaction(
+ () => Cast(this.props.Document._curPage, 'number', null),
+ page => page !== undefined && page !== this._pdfViewer?.currentPageNumber && this.gotoPage(page),
+ { fireImmediately: true }
+ );
+ };
componentWillUnmount = () => {
Object.values(this._disposers).forEach(disposer => disposer?.());
- document.removeEventListener("copy", this.copy);
- }
+ document.removeEventListener('copy', this.copy);
+ };
copy = (e: ClipboardEvent) => {
if (this.props.isContentActive() && e.clipboardData) {
- e.clipboardData.setData("text/plain", this._selectionText);
+ e.clipboardData.setData('text/plain', this._selectionText);
e.preventDefault();
}
- }
+ };
@action
initialLoad = async () => {
if (this._pageSizes.length === 0) {
- this._pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages);
- await Promise.all(this._pageSizes.map((val, i) =>
- this.props.pdf.getPage(i + 1).then(action((page: Pdfjs.PDFPageProxy) => {
- const page0or180 = page.rotate === 0 || page.rotate === 180;
- this._pageSizes.splice(i, 1, {
- width: (page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1]),
- height: (page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0])
- });
- if (i === this.props.pdf.numPages - 1) {
- this.props.loaded?.(page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1],
- page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], i);
- }
- }))));
- this.props.Document.scrollHeight = this._pageSizes.reduce((size, page) => size + page.height, 0) * 96 / 72;
+ this._pageSizes = Array<{ width: number; height: number }>(this.props.pdf.numPages);
+ await Promise.all(
+ this._pageSizes.map((val, i) =>
+ this.props.pdf.getPage(i + 1).then(
+ action((page: Pdfjs.PDFPageProxy) => {
+ const page0or180 = page.rotate === 0 || page.rotate === 180;
+ this._pageSizes.splice(i, 1, {
+ width: page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1],
+ height: page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0],
+ });
+ if (i === this.props.pdf.numPages - 1) {
+ this.props.loaded?.(page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1], page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], this.props.pdf.numPages);
+ }
+ })
+ )
+ )
+ );
+ this.props.Document.scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72;
}
- }
+ };
// scrolls to focus on a nested annotation document. if this is part a link preview then it will jump to the scroll location,
// otherwise it will scroll smoothly.
@@ -180,9 +161,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
const mainCont = this._mainCont.current;
let focusSpeed: Opt<number>;
if (doc !== this.props.rootDoc && mainCont) {
- const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
- const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, .1 * windowHeight);
- if (scrollTo !== undefined) {
+ const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
+ const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, NumCast(this.props.Document.scrollHeight));
+ if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) {
focusSpeed = 500;
if (!this._pdfViewer) this._initialScroll = scrollTo;
@@ -193,7 +174,10 @@ export class PDFViewer extends React.Component<IViewerProps> {
this._initialScroll = NumCast(this.props.layoutDoc._scrollTop);
}
return focusSpeed;
- }
+ };
+ crop = (region: Doc | undefined, addCrop?: boolean) => {
+ return this.props.crop(region, addCrop);
+ };
@action
setupPdfJsViewer = async () => {
@@ -203,42 +187,34 @@ export class PDFViewer extends React.Component<IViewerProps> {
this.props.setPdfViewer(this);
await this.initialLoad();
- this._disposers.filterScript = reaction(
- () => ScriptCast(this.props.Document.filterScript),
- action(scriptField => {
- const oldScript = this._script.originalScript;
- this._script = scriptField?.script.compiled ? scriptField.script : CompileScript("return true") as CompiledScript;
- if (this._script.originalScript !== oldScript) {
- this.Index = -1;
- }
- }),
- { fireImmediately: true });
-
this.createPdfViewer();
- }
+ };
pagesinit = () => {
if (this._pdfViewer._setDocumentViewerElement?.offsetParent) {
- runInAction(() => this._pdfViewer.currentScaleValue = this._zoomed = 1);
+ runInAction(() => (this._pdfViewer.currentScaleValue = this.props.layoutDoc._viewScale = 1));
this.gotoPage(NumCast(this.props.Document._curPage, 1));
}
- document.removeEventListener("pagesinit", this.pagesinit);
- var quickScroll: string | undefined = this._initialScroll ? this._initialScroll.toString() : "";
+ document.removeEventListener('pagesinit', this.pagesinit);
+ var quickScroll: string | undefined = this._initialScroll ? this._initialScroll.toString() : '';
this._disposers.scroll = reaction(
() => Math.abs(NumCast(this.props.Document._scrollTop)),
- (pos) => {
+ pos => {
if (!this._ignoreScroll) {
- (this._showCover || this._showWaiting) && this.setupPdfJsViewer();
+ this._showWaiting && this.setupPdfJsViewer();
const viewTrans = quickScroll ?? StrCast(this.props.Document._viewTransition);
const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
const durationSecStr = viewTrans.match(/([0-9.]*)s/);
const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
this._forcedScroll = true;
if (duration) {
- setTimeout(() => {
- this._mainCont.current && smoothScroll(duration, this._mainCont.current, pos);
- setTimeout(() => this._forcedScroll = false, duration);
- }, this._mainCont.current ? 0 : 250); // wait for mainCont and try again to scroll
+ setTimeout(
+ () => {
+ this._mainCont.current && smoothScroll(duration, this._mainCont.current, pos);
+ setTimeout(() => (this._forcedScroll = false), duration);
+ },
+ this._mainCont.current ? 0 : 250
+ ); // wait for mainCont and try again to scroll
} else {
this._mainCont.current?.scrollTo({ top: pos });
this._forcedScroll = false;
@@ -252,23 +228,27 @@ export class PDFViewer extends React.Component<IViewerProps> {
this._mainCont.current?.scrollTo({ top: Math.abs(this._initialScroll || 0) });
this._initialScroll = undefined;
}
- }
+ };
createPdfViewer() {
- if (!this._mainCont.current) { // bcz: I don't think this is ever triggered or needed
- console.log("PDFViewer- I guess we got here");
+ if (!this._mainCont.current) {
+ // bcz: I don't think this is ever triggered or needed
+ console.log('PDFViewer- I guess we got here');
if (this._retries < 5) {
this._retries++;
- console.log("PDFViewer- retry num:" + this._retries);
+ console.log('PDFViewer- retry num:' + this._retries);
setTimeout(() => this.createPdfViewer(), 1000);
}
return;
}
- document.removeEventListener("copy", this.copy);
- document.addEventListener("copy", this.copy);
+ document.removeEventListener('copy', this.copy);
+ document.addEventListener('copy', this.copy);
const eventBus = new PDFJSViewer.EventBus(true);
- eventBus._on("pagesinit", this.pagesinit);
- eventBus._on("pagerendered", action(() => this._showWaiting = false));
+ eventBus._on('pagesinit', this.pagesinit);
+ eventBus._on(
+ 'pagerendered',
+ action(() => (this._showWaiting = false))
+ );
const pdfLinkService = new PDFJSViewer.PDFLinkService({ eventBus });
const pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, eventBus });
this._pdfViewer = new PDFJSViewer.PDFViewer({
@@ -276,33 +256,30 @@ export class PDFViewer extends React.Component<IViewerProps> {
viewer: this._viewer.current,
linkService: pdfLinkService,
findController: pdfFindController,
- renderer: "canvas",
- eventBus
+ renderer: 'canvas',
+ eventBus,
});
pdfLinkService.setViewer(this._pdfViewer);
pdfLinkService.setDocument(this.props.pdf, null);
this._pdfViewer.setDocument(this.props.pdf);
}
-
@action
prevAnnotation = () => {
this.Index = Math.max(this.Index - 1, 0);
this.scrollToAnnotation(this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index]);
- }
+ };
@action
nextAnnotation = () => {
this.Index = Math.min(this.Index + 1, this.allAnnotations.length - 1);
this.scrollToAnnotation(this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index]);
- }
+ };
@action
gotoPage = (p: number) => {
- if (this._pdfViewer?._setDocumentViewerElement?.offsetParent) {
- this._pdfViewer?.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) });
- }
- }
+ this._pdfViewer?.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) });
+ };
@action
scrollToAnnotation = (scrollToAnnotation: Doc) => {
@@ -310,7 +287,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
this.scrollFocus(scrollToAnnotation, true);
Doc.linkFollowHighlight(scrollToAnnotation);
}
- }
+ };
+
+ @observable private _scrollTimer: any;
onScroll = (e: React.UIEvent<HTMLElement>) => {
if (this._mainCont.current && !this._forcedScroll) {
@@ -319,8 +298,13 @@ export class PDFViewer extends React.Component<IViewerProps> {
this.props.layoutDoc._scrollTop = this._mainCont.current.scrollTop;
}
this._ignoreScroll = false;
+ if (this._scrollTimer) clearTimeout(this._scrollTimer); // wait until a scrolling pause, then create an anchor to audio
+ this._scrollTimer = setTimeout(() => {
+ DocUtils.MakeLinkToActiveAudio(() => this.props.DocumentView?.().ComponentView?.getAnchor!()!, false);
+ this._scrollTimer = undefined;
+ }, 200);
}
- }
+ };
// get the page index that the vertical offset passed in is on
getPageFromScroll = (vOffset: number) => {
@@ -330,7 +314,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
currOffset -= this._pageSizes[index++].height;
}
return index;
- }
+ };
@action
search = (searchString: string, bwd?: boolean, clear: boolean = false) => {
@@ -339,22 +323,21 @@ export class PDFViewer extends React.Component<IViewerProps> {
findPrevious: bwd,
highlightAll: true,
phraseSearch: true,
- query: searchString
+ query: searchString,
};
if (clear) {
- this._pdfViewer?.findController.executeCommand('reset', { query: "" });
+ this._pdfViewer?.findController.executeCommand('reset', { query: '' });
} else if (!searchString) {
bwd ? this.prevAnnotation() : this.nextAnnotation();
} else if (this._pdfViewer?.pageViewsReady) {
this._pdfViewer.findController.executeCommand('findagain', findOpts);
- }
- else if (this._mainCont.current) {
+ } else if (this._mainCont.current) {
const executeFind = () => this._pdfViewer.findController.executeCommand('find', findOpts);
- this._mainCont.current.addEventListener("pagesloaded", executeFind);
- this._mainCont.current.addEventListener("pagerendered", executeFind);
+ this._mainCont.current.addEventListener('pagesloaded', executeFind);
+ this._mainCont.current.addEventListener('pagerendered', executeFind);
}
return true;
- }
+ };
@action
onPointerDown = (e: React.PointerEvent): void => {
@@ -371,46 +354,53 @@ export class PDFViewer extends React.Component<IViewerProps> {
if ((e.button !== 0 || e.altKey) && this.props.isContentActive(true)) {
this._setPreviewCursor?.(e.clientX, e.clientY, true, false);
}
- if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+ if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
this.props.select(false);
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
this._marqueeing = [e.clientX, e.clientY];
- if (e.target && ((e.target as any).className.includes("endOfContent") || ((e.target as any).parentElement.className !== "textLayer"))) {
+ this.isAnnotating = true;
+ if (e.target && ((e.target as any).className.includes('endOfContent') || (e.target as any).parentElement.className !== 'textLayer')) {
this._textSelecting = false;
- document.addEventListener("pointermove", this.onSelectMove); // need this to prevent document from being dragged if stopPropagation doesn't get called
+ document.addEventListener('pointermove', this.onSelectMove); // need this to prevent document from being dragged if stopPropagation doesn't get called
} else {
// if textLayer is hit, then we select text instead of using a marquee so clear out the marquee.
- setTimeout(action(() => this._marqueeing = undefined), 100); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
-
- this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, "htmlAnnotation", { "pointer-events": "none" });
- document.addEventListener("pointerup", this.onSelectEnd);
- document.addEventListener("pointermove", this.onSelectMove);
+ setTimeout(
+ action(() => (this._marqueeing = undefined)),
+ 100
+ ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
+
+ this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, 'htmlAnnotation', { 'pointer-events': 'none' });
+ document.addEventListener('pointerup', this.onSelectEnd);
+ document.addEventListener('pointermove', this.onSelectMove);
}
}
- }
+ };
@action
finishMarquee = (x?: number, y?: number) => {
+ this._getAnchor = AnchorMenu.Instance?.GetAnchor;
+ this.isAnnotating = false;
this._marqueeing = undefined;
this._textSelecting = true;
- document.removeEventListener("pointermove", this.onSelectMove);
- }
+ document.removeEventListener('pointermove', this.onSelectMove);
+ };
onSelectMove = (e: PointerEvent) => e.stopPropagation();
@action
onSelectEnd = (e: PointerEvent): void => {
+ this.isAnnotating = false;
clearStyleSheetRules(PDFViewer._annotationStyle);
this.props.select(false);
- document.removeEventListener("pointermove", this.onSelectMove);
- document.removeEventListener("pointerup", this.onSelectEnd);
+ document.removeEventListener('pointermove', this.onSelectMove);
+ document.removeEventListener('pointerup', this.onSelectEnd);
const sel = window.getSelection();
- if (sel?.type === "Range") {
+ if (sel?.type === 'Range') {
this.createTextAnnotation(sel, sel.getRangeAt(0));
AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
}
- }
+ };
@action
createTextAnnotation = (sel: Selection, selRange: Range) => {
@@ -419,57 +409,44 @@ export class PDFViewer extends React.Component<IViewerProps> {
const clientRects = selRange.getClientRects();
for (let i = 0; i < clientRects.length; i++) {
const rect = clientRects.item(i);
- if (rect && rect.width !== this._mainCont.current.clientWidth) {
+ if (rect && rect.width !== this._mainCont.current.clientWidth && rect.width) {
const scaleX = this._mainCont.current.offsetWidth / boundingRect.width;
- const annoBox = document.createElement("div");
- annoBox.className = "marqueeAnnotator-annotationBox";
+ const pdfScale = NumCast(this.props.layoutDoc._viewScale, 1);
+ const annoBox = document.createElement('div');
+ annoBox.className = 'marqueeAnnotator-annotationBox';
// transforms the positions from screen onto the pdf div
- annoBox.style.top = ((rect.top - boundingRect.top) * scaleX / this._zoomed + this._mainCont.current.scrollTop).toString();
- annoBox.style.left = ((rect.left - boundingRect.left) * scaleX / this._zoomed).toString();
- annoBox.style.width = (rect.width * this._mainCont.current.offsetWidth / boundingRect.width / this._zoomed).toString();
- annoBox.style.height = (rect.height * this._mainCont.current.offsetHeight / boundingRect.height / this._zoomed).toString();
+ annoBox.style.top = (((rect.top - boundingRect.top) * scaleX) / pdfScale + this._mainCont.current.scrollTop).toString();
+ annoBox.style.left = (((rect.left - boundingRect.left) * scaleX) / pdfScale).toString();
+ annoBox.style.width = ((rect.width * this._mainCont.current.offsetWidth) / boundingRect.width / pdfScale).toString();
+ annoBox.style.height = ((rect.height * this._mainCont.current.offsetHeight) / boundingRect.height / pdfScale).toString();
this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, this.getPageFromScroll(rect.top));
}
}
}
- this._selectionText = selRange.cloneContents().textContent || "";
+ this._selectionText = selRange.cloneContents().textContent || '';
// clear selection
- if (sel.empty) { // Chrome
+ if (sel.empty) {
+ // Chrome
sel.empty();
- } else if (sel.removeAllRanges) { // Firefox
+ } else if (sel.removeAllRanges) {
+ // Firefox
sel.removeAllRanges();
}
- }
+ };
scrollXf = () => {
return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.layoutDoc._scrollTop)) : this.props.ScreenToLocalTransform();
- }
+ };
onClick = (e: React.MouseEvent) => {
- if (this._setPreviewCursor && e.button === 0 &&
- Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
- Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+ if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
this._setPreviewCursor(e.clientX, e.clientY, false, false);
}
// e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks
- }
+ };
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => this._setPreviewCursor = func;
-
- getCoverImage = () => {
- if (!this.props.Document[HeightSym]() || !Doc.NativeHeight(this.props.Document)) {
- setTimeout((() => {
- this.props.Document._height = this.props.Document[WidthSym]() * this._coverPath.height / this._coverPath.width;
- Doc.SetNativeWidth(this.props.Document, (Doc.NativeWidth(this.props.Document) || 0) * this._coverPath.height / this._coverPath.width);
- }).bind(this), 0);
- }
- const nativeWidth = Doc.NativeWidth(this.props.Document);
- const nativeHeight = Doc.NativeHeight(this.props.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` }} />;
- }
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func);
@action
onZoomWheel = (e: React.WheelEvent) => {
@@ -477,118 +454,145 @@ export class PDFViewer extends React.Component<IViewerProps> {
e.stopPropagation();
if (e.ctrlKey) {
const curScale = Number(this._pdfViewer.currentScaleValue);
- this._pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale - curScale * e.deltaY / 1000));
- this._zoomed = Number(this._pdfViewer.currentScaleValue);
+ this._pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale - (curScale * e.deltaY) / 1000));
+ this.props.layoutDoc._viewScale = Number(this._pdfViewer.currentScaleValue);
}
}
- }
+ };
- pointerEvents = () => this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none";
+ pointerEvents = () => (this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none');
@computed get annotationLayer() {
- const pe = this.pointerEvents();
- return <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
- {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
- <Annotation {...this.props} fieldKey={this.props.fieldKey + "-annotations"} pointerEvents={pe} showInfo={this.showInfo} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)}
- </div>;
+ return (
+ <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})` }} ref={this._annotationLayer}>
+ {this.inlineTextAnnotations
+ .sort((a, b) => NumCast(a.y) - NumCast(b.y))
+ .map(anno => (
+ <Annotation {...this.props} fieldKey={this.props.fieldKey + '-annotations'} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
+ ))}
+ </div>
+ );
}
@computed get overlayInfo() {
- return !this._overlayAnnoInfo || this._overlayAnnoInfo.author === Doc.CurrentUserEmail ? (null) :
+ return !this._overlayAnnoInfo ? 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)}
+ {this._overlayAnnoInfo.author + ' ' + Field.toString(this._overlayAnnoInfo.creationDate as Field)}
</div>
- </div>;
+ </div>
+ );
}
- showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno);
- overlayTransform = () => this.scrollXf().scale(1 / this._zoomed);
- panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
- panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
- basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter("textInlineAnnotations")];
+ showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
+ overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._viewScale, 1));
+ panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
+ panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
+ basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter('textInlineAnnotations')];
transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
- childStyleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => {
+ childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
if (doc instanceof Doc && property === StyleProp.PointerEvents) {
- if (doc.textInlineAnnotations) return "none";
- return "all";
+ if (doc.textInlineAnnotations) return 'none';
+ return 'all';
}
return this.props.styleProvider?.(doc, props, property);
+ };
+
+ renderAnnotations = (docFilters?: () => string[], dontRender?: boolean) => (
+ <CollectionFreeFormView
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+ isAnnotationOverlay={true}
+ fieldKey={this.props.fieldKey + '-annotations'}
+ setPreviewCursor={this.setPreviewCursor}
+ PanelHeight={this.panelHeight}
+ PanelWidth={this.panelWidth}
+ dropAction={'alias'}
+ select={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={docFilters || this.basicFilter}
+ styleProvider={this.childStyleProvider}
+ dontRenderDocuments={dontRender}
+ CollectionView={undefined}
+ ScreenToLocalTransform={this.overlayTransform}
+ renderDepth={this.props.renderDepth + 1}
+ />
+ );
+ @computed get overlayTransparentAnnotations() {
+ return this.renderAnnotations(this.transparentFilter, false);
+ }
+ @computed get overlayOpaqueAnnotations() {
+ return this.renderAnnotations(this.opaqueFilter, false);
+ }
+ @computed get overlayClickableAnnotations() {
+ return <div style={{ height: NumCast(this.props.rootDoc.scrollHeight) }}>{this.renderAnnotations(undefined, true)}</div>;
}
@computed get overlayLayer() {
- const renderAnnotations = (docFilters?: () => string[]) =>
- <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
- isAnnotationOverlay={true}
- fieldKey={this.props.fieldKey + "-annotations"}
- setPreviewCursor={this.setPreviewCursor}
- PanelHeight={this.panelHeight}
- PanelWidth={this.panelWidth}
- dropAction={"alias"}
- select={emptyFunction}
- ContentScaling={this.contentZoom}
- bringToFront={emptyFunction}
- docFilters={docFilters || this.basicFilter}
- styleProvider={this.childStyleProvider}
- dontRenderDocuments={docFilters ? false : true}
- CollectionView={undefined}
- ScreenToLocalTransform={this.overlayTransform}
- renderDepth={this.props.renderDepth + 1} />;
- return <div>
- <div className={`pdfViewerDash-overlay${CurrentUserUtils.SelectedTool !== InkTool.None || SnappingManager.GetIsDragging() ? "-inking" : ""}`}
- style={{
- pointerEvents: SnappingManager.GetIsDragging() ? "all" : undefined,
- mixBlendMode: "multiply",
- transform: `scale(${this._zoomed})`
- }}>
- {renderAnnotations(this.transparentFilter)}
- </div>
- <div className={`pdfViewerDash-overlay${CurrentUserUtils.SelectedTool !== 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})`
- }}>
- {renderAnnotations(this.opaqueFilter)}
- {SnappingManager.GetIsDragging() ? (null) : renderAnnotations()}
+ return (
+ <div style={{ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : 'none' }}>
+ <div
+ className="pdfViewerDash-overlay"
+ style={{
+ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : 'none',
+ mixBlendMode: 'multiply',
+ transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})`,
+ }}>
+ {this.overlayTransparentAnnotations}
+ </div>
+ <div
+ className="pdfViewerDash-overlay"
+ style={{
+ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : 'none',
+ mixBlendMode: this.allAnnotations.some(anno => anno.mixBlendMode) ? 'hard-light' : undefined,
+ transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})`,
+ }}>
+ {this.overlayOpaqueAnnotations}
+ {this.overlayClickableAnnotations}
+ </div>
</div>
- </div>;
+ );
}
@computed get pdfViewerDiv() {
- return <div className={"pdfViewerDash-text" + (this.props.pointerEvents !== "none" && this._textSelecting && this.props.isContentActive() ? "-selected" : "")} ref={this._viewer} />;
- }
- @computed get contentScaling() { return this.props.ContentScaling?.() || 1; }
- @computed get standinViews() {
- return <>
- {this._showCover ? this.getCoverImage() : (null)}
- {this._showWaiting ? <img className="pdfViewerDash-waiting" key="waiting" src={"/assets/loading.gif"} /> : (null)}
- </>;
+ return <div className={'pdfViewerDash-text' + (this.props.pointerEvents?.() !== 'none' && this._textSelecting && this.props.isContentActive() ? '-selected' : '')} ref={this._viewer} />;
}
- contentZoom = () => this._zoomed;
+ savedAnnotations = () => this._savedAnnotations;
render() {
TraceMobx();
- return <div className="pdfViewer-content">
- <div className={`pdfViewerDash${this.props.isContentActive() && this.props.pointerEvents !== "none" ? "-interactive" : ""}`} ref={this._mainCont}
- onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick}
- style={{
- overflowX: this._zoomed !== 1 ? "scroll" : undefined,
- height: !this.props.Document._fitWidth && (window.screen.width > 600) ? Doc.NativeHeight(this.props.Document) : `${100 / this.contentScaling}%`,
- transform: `scale(${this.contentScaling})`
- }} >
- {this.pdfViewerDiv}
- {this.annotationLayer}
- {this.overlayLayer}
- {this.overlayInfo}
- {this.standinViews}
- {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
- <MarqueeAnnotator rootDoc={this.props.rootDoc} scrollTop={0} down={this._marqueeing}
- anchorMenuClick={this.props.anchorMenuClick}
- addDocument={(doc: Doc | Doc[]) => this.props.addDocument!(doc)}
- finishMarquee={this.finishMarquee}
- docView={this.props.docViewPath().lastElement()}
- getPageFromScroll={this.getPageFromScroll}
- savedAnnotations={this._savedAnnotations}
- annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
+ return (
+ <div className="pdfViewer-content">
+ <div
+ className={`pdfViewerDash${this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' ? '-interactive' : ''}`}
+ ref={this._mainCont}
+ onScroll={this.onScroll}
+ onWheel={this.onZoomWheel}
+ onPointerDown={this.onPointerDown}
+ onClick={this.onClick}
+ style={{
+ overflowX: NumCast(this.props.layoutDoc._viewScale, 1) !== 1 ? 'scroll' : undefined,
+ height: !this.props.Document._fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this.props.Document) : `100%`,
+ }}>
+ {this.pdfViewerDiv}
+ {this.annotationLayer}
+ {this.overlayLayer}
+ {this.overlayInfo}
+ {this._showWaiting ? <img className="pdfViewerDash-waiting" src={'/assets/loading.gif'} /> : null}
+ {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
+ <MarqueeAnnotator
+ rootDoc={this.props.rootDoc}
+ getPageFromScroll={this.getPageFromScroll}
+ anchorMenuClick={this.props.anchorMenuClick}
+ scrollTop={0}
+ down={this._marqueeing}
+ addDocument={(doc: Doc | Doc[]) => this.props.addDocument!(doc)}
+ docView={this.props.docViewPath().lastElement()}
+ finishMarquee={this.finishMarquee}
+ savedAnnotations={this.savedAnnotations}
+ annotationLayer={this._annotationLayer.current}
+ mainCont={this._mainCont.current}
+ anchorMenuCrop={this._textSelecting ? undefined : this.crop}
+ />
+ )}
+ </div>
</div>
- </div>;
+ );
}
-} \ No newline at end of file
+}