aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/pdf
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/pdf')
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx163
-rw-r--r--src/client/views/pdf/Annotation.tsx38
-rw-r--r--src/client/views/pdf/PDFViewer.scss12
-rw-r--r--src/client/views/pdf/PDFViewer.tsx179
4 files changed, 196 insertions, 196 deletions
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index ee2ae10a7..7392d2706 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -11,12 +11,14 @@ import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
import { LinkPopup } from '../linking/LinkPopup';
import { ButtonDropdown } from '../nodes/formattedText/RichTextMenu';
import './AnchorMenu.scss';
+import { LightboxView } from '../LightboxView';
@observer
export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
static Instance: AnchorMenu;
private _disposer: IReactionDisposer | undefined;
+ private _disposer2: IReactionDisposer | undefined;
private _commentCont = React.createRef<HTMLButtonElement>();
private _palette = [
'rgba(208, 2, 27, 0.8)',
@@ -36,13 +38,9 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
'rgba(0, 0, 0, 0.8)',
];
- @observable private _keyValue: string = '';
- @observable private _valueValue: string = '';
- @observable private _added: boolean = false;
@observable private highlightColor: string = 'rgba(245, 230, 95, 0.616)';
@observable private _showLinkPopup: boolean = false;
- @observable public Highlighting: boolean = false;
@observable public Status: 'marquee' | 'annotation' | '' = '';
public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
@@ -52,13 +50,13 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
- public Highlight: (color: string, isPushpin: boolean) => Opt<Doc> = (color: string, isPushpin: boolean) => undefined;
- public GetAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
+ public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined;
+ public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
public Delete: () => void = unimplementedFunction;
- public AddTag: (key: string, value: string) => boolean = returnFalse;
public PinToPres: () => void = unimplementedFunction;
- public MakePushpin: () => void = unimplementedFunction;
- public IsPushpin: () => boolean = returnFalse;
+ public MakeTargetToggle: () => void = unimplementedFunction;
+ public ShowTargetTrail: () => void = unimplementedFunction;
+ public IsTargetToggler: () => boolean = returnFalse;
public get Active() {
return this._left > 0;
}
@@ -70,9 +68,19 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
AnchorMenu.Instance._canFade = false;
}
+ componentWillUnmount() {
+ this._disposer?.();
+ this._disposer2?.();
+ }
+
componentDidMount() {
+ this._disposer2 = reaction(
+ () => this._opacity,
+ opacity => !opacity && (this._showLinkPopup = false),
+ { fireImmediately: true }
+ );
this._disposer = reaction(
- () => SelectionManager.Views(),
+ () => SelectionManager.Views().slice(),
selected => {
this._showLinkPopup = false;
AnchorMenu.Instance.fadeOut(true);
@@ -112,9 +120,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@action
highlightClicked = (e: React.MouseEvent) => {
- if (!this.Highlight(this.highlightColor, false) && this.Pinned) {
- this.Highlighting = !this.Highlighting;
- }
+ this.Highlight(this.highlightColor, false, undefined, true);
AnchorMenu.Instance.fadeOut(true);
};
@@ -129,7 +135,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
const button = (
<button className="antimodeMenu-button anchor-color-preview-button" title="" key="highlighter-button" onClick={this.highlightClicked}>
<div className="anchor-color-preview">
- <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: 'transform 0.1s', transform: this.Highlighting ? '' : 'rotate(-45deg)' }} />
+ <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: 'transform 0.1s', transform: 'rotate(-45deg)' }} />
<div className="color-preview" style={{ backgroundColor: this.highlightColor }}></div>
</div>
</button>
@@ -174,83 +180,62 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
this.highlightColor = Utils.colorString(col);
};
- @action keyChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._keyValue = e.currentTarget.value;
- };
- @action valueChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._valueValue = e.currentTarget.value;
- };
- @action addTag = (e: React.PointerEvent) => {
- if (this._keyValue.length > 0 && this._valueValue.length > 0) {
- this._added = this.AddTag(this._keyValue, this._valueValue);
- setTimeout(
- action(() => (this._added = false)),
- 1000
- );
- }
- };
-
render() {
const buttons =
- this.Status === 'marquee'
- ? [
- this.highlighter,
-
- <Tooltip key="annotate" title={<div className="dash-tooltip">{'Drag to Place Annotation'}</div>}>
- <button className="antimodeMenu-button annotate" ref={this._commentCont} onPointerDown={this.pointerDown} style={{ cursor: 'grab' }}>
- <FontAwesomeIcon icon="comment-alt" size="lg" />
- </button>
- </Tooltip>,
- AnchorMenu.Instance.OnAudio === unimplementedFunction ? (
- <></>
- ) : (
- <Tooltip key="annoaudiotate" title={<div className="dash-tooltip">{'Click to Record Annotation'}</div>}>
- <button className="antimodeMenu-button annotate" onPointerDown={this.audioDown} style={{ cursor: 'grab' }}>
- <FontAwesomeIcon icon="microphone" size="lg" />
- </button>
- </Tooltip>
- ),
- //NOTE: link popup is currently in progress
- <Tooltip key="link" title={<div className="dash-tooltip">{'Find document to link to selected text'}</div>}>
- <button className="antimodeMenu-button link" onPointerDown={this.toggleLinkPopup} style={{}}>
- <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(1.5)' }} icon={'search'} size="lg" />
- <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(0.5)', transformOrigin: 'top left', top: 12, left: 12 }} icon={'link'} size="lg" />
- </button>
- </Tooltip>,
- <LinkPopup key="popup" showPopup={this._showLinkPopup} linkFrom={this.onMakeAnchor} />,
- AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? (
- <></>
- ) : (
- <Tooltip key="crop" title={<div className="dash-tooltip">{'Click/Drag to create cropped image'}</div>}>
- <button className="antimodeMenu-button annotate" onPointerDown={this.cropDown} style={{ cursor: 'grab' }}>
- <FontAwesomeIcon icon="image" size="lg" />
- </button>
- </Tooltip>
- ),
- ]
- : [
- <Tooltip key="trash" title={<div className="dash-tooltip">{'Remove Link Anchor'}</div>}>
- <button className="antimodeMenu-button" onPointerDown={this.Delete}>
- <FontAwesomeIcon icon="trash-alt" size="lg" />
- </button>
- </Tooltip>,
- <Tooltip key="Pin" title={<div className="dash-tooltip">{'Pin to Presentation'}</div>}>
- <button className="antimodeMenu-button" onPointerDown={this.PinToPres}>
- <FontAwesomeIcon icon="map-pin" size="lg" />
- </button>
- </Tooltip>,
- <Tooltip key="pushpin" title={<div className="dash-tooltip">{'toggle pushpin behavior'}</div>}>
- <button className="antimodeMenu-button" style={{ color: this.IsPushpin() ? 'black' : 'white', backgroundColor: this.IsPushpin() ? 'white' : 'black' }} onPointerDown={this.MakePushpin}>
- <FontAwesomeIcon icon="thumbtack" size="lg" />
- </button>
- </Tooltip>,
- // <div key="7" className="anchorMenu-addTag" >
- // <input onChange={this.keyChanged} placeholder="Key" style={{ gridColumn: 1 }} />
- // <input onChange={this.valueChanged} placeholder="Value" style={{ gridColumn: 3 }} />
- // </div>,
- // <button key="8" className="antimodeMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}>
- // <FontAwesomeIcon style={{ transition: "all .2s" }} color={this._added ? "#42f560" : "white"} icon="check" size="lg" /></button>,
- ];
+ this.Status === 'marquee' ? (
+ <>
+ {this.highlighter}
+ <Tooltip key="annotate" title={<div className="dash-tooltip">Drag to Place Annotation</div>}>
+ <button className="antimodeMenu-button annotate" ref={this._commentCont} onPointerDown={this.pointerDown} style={{ cursor: 'grab' }}>
+ <FontAwesomeIcon icon="comment-alt" size="lg" />
+ </button>
+ </Tooltip>
+ {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : (
+ <Tooltip key="annoaudiotate" title={<div className="dash-tooltip">Click to Record Annotation</div>}>
+ <button className="antimodeMenu-button annotate" onPointerDown={this.audioDown} style={{ cursor: 'grab' }}>
+ <FontAwesomeIcon icon="microphone" size="lg" />
+ </button>
+ </Tooltip>
+ )}
+ <Tooltip key="link" title={<div className="dash-tooltip">Find document to link to selected text</div>}>
+ <button className="antimodeMenu-button link" onPointerDown={this.toggleLinkPopup}>
+ <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(1.5)' }} icon={'search'} size="lg" />
+ <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(0.5)', transformOrigin: 'top left', top: 12, left: 12 }} icon={'link'} size="lg" />
+ </button>
+ </Tooltip>
+ <LinkPopup key="popup" showPopup={this._showLinkPopup} linkCreateAnchor={this.onMakeAnchor} />,
+ {AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? null : (
+ <Tooltip key="crop" title={<div className="dash-tooltip">Click/Drag to create cropped image</div>}>
+ <button className="antimodeMenu-button annotate" onPointerDown={this.cropDown} style={{ cursor: 'grab' }}>
+ <FontAwesomeIcon icon="image" size="lg" />
+ </button>
+ </Tooltip>
+ )}
+ </>
+ ) : (
+ <>
+ <Tooltip key="trash" title={<div className="dash-tooltip">Remove Link Anchor</div>}>
+ <button className="antimodeMenu-button" onPointerDown={this.Delete}>
+ <FontAwesomeIcon icon="trash-alt" size="lg" />
+ </button>
+ </Tooltip>
+ <Tooltip key="Pin" title={<div className="dash-tooltip">Pin to Presentation</div>}>
+ <button className="antimodeMenu-button" onPointerDown={this.PinToPres}>
+ <FontAwesomeIcon icon="map-pin" size="lg" />
+ </button>
+ </Tooltip>
+ <Tooltip key="trail" title={<div className="dash-tooltip">Show Linked Trail</div>}>
+ <button className="antimodeMenu-button" onPointerDown={this.ShowTargetTrail}>
+ <FontAwesomeIcon icon="taxi" size="lg" />
+ </button>
+ </Tooltip>
+ <Tooltip key="toggle" title={<div className="dash-tooltip">make target visibility toggle on click</div>}>
+ <button className="antimodeMenu-button" style={{ color: this.IsTargetToggler() ? 'black' : 'white', backgroundColor: this.IsTargetToggler() ? 'white' : 'black' }} onPointerDown={this.MakeTargetToggle}>
+ <FontAwesomeIcon icon="thumbtack" size="lg" />
+ </button>
+ </Tooltip>
+ </>
+ );
return this.getElement(buttons);
}
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 44f815336..db6b1f011 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -1,12 +1,13 @@
import React = require('react');
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
-import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
import { LinkFollower } from '../../util/LinkFollower';
import { undoBatch } from '../../util/UndoManager';
+import { OpenWhere } from '../nodes/DocumentView';
import { FieldViewProps } from '../nodes/FieldView';
import { AnchorMenu } from './AnchorMenu';
import './Annotation.scss';
@@ -22,7 +23,7 @@ interface IAnnotationProps extends FieldViewProps {
export class Annotation extends React.Component<IAnnotationProps> {
render() {
return (
- <div>
+ <div style={{ display: this.props.anno.textCopied && !Doc.isBrushedHighlightedDegree(this.props.anno) ? 'none' : undefined }}>
{DocListCast(this.props.anno.textInlineAnnotations).map(a => (
<RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} />
))}
@@ -52,12 +53,20 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
};
@undoBatch
- pinToPres = () => this.props.pinToPres(this.annoTextRegion);
+ pinToPres = () => this.props.pinToPres(this.annoTextRegion, {});
@undoBatch
- makePushpin = () => (this.annoTextRegion.isPushpin = !this.annoTextRegion.isPushpin);
+ makeTargretToggle = () => (this.annoTextRegion.followLinkToggle = !this.annoTextRegion.followLinkToggle);
- isPushpin = () => BoolCast(this.annoTextRegion.isPushpin);
+ isTargetToggler = () => BoolCast(this.annoTextRegion.followLinkToggle);
+ @undoBatch
+ showTargetTrail = (anchor: Doc) => {
+ const trail = DocCast(anchor.presTrail);
+ if (trail) {
+ Doc.ActivePresentation = trail;
+ this.props.addDocTab(trail, OpenWhere.replaceRight);
+ }
+ };
@action
onPointerDown = (e: React.PointerEvent) => {
@@ -65,24 +74,18 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
AnchorMenu.Instance.Status = 'annotation';
AnchorMenu.Instance.Delete = this.deleteAnnotation.bind(this);
AnchorMenu.Instance.Pinned = false;
- AnchorMenu.Instance.AddTag = this.addTag.bind(this);
AnchorMenu.Instance.PinToPres = this.pinToPres;
- AnchorMenu.Instance.MakePushpin = this.makePushpin;
- AnchorMenu.Instance.IsPushpin = this.isPushpin;
+ AnchorMenu.Instance.MakeTargetToggle = this.makeTargretToggle;
+ AnchorMenu.Instance.IsTargetToggler = this.isTargetToggler;
+ AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this.annoTextRegion);
AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
e.stopPropagation();
} else if (e.button === 0) {
e.stopPropagation();
- LinkFollower.FollowLink(undefined, this.annoTextRegion, this.props, false);
+ LinkFollower.FollowLink(undefined, this.annoTextRegion, false);
}
};
- addTag = (key: string, value: string): boolean => {
- const valNum = parseInt(value);
- this.annoTextRegion[key] = isNaN(valNum) ? value : valNum;
- return true;
- };
-
render() {
const brushed = this.annoTextRegion && Doc.isBrushedHighlightedDegree(this.annoTextRegion);
return (
@@ -107,7 +110,8 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
pointerEvents: this.props.pointerEvents?.() as any,
outline: brushed === Doc.DocBrushStatus.linkHighlighted ? 'solid 1px lightBlue' : undefined,
backgroundColor: brushed === Doc.DocBrushStatus.highlighted ? 'orange' : StrCast(this.props.document.backgroundColor),
- }}></div>
+ }}
+ />
);
}
}
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index 822af6a68..470aa3eb1 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -27,14 +27,14 @@
opacity: unset;
mix-blend-mode: multiply; // bcz: makes text fuzzy!
- span {
- padding-right: 5px;
- padding-bottom: 4px;
- }
+ // span {
+ // padding-right: 5px;
+ // padding-bottom: 4px;
+ // }
}
.textLayer ::selection {
- background: #ACCEF7;
+ background: #accef7;
}
// should match the backgroundColor in createAnnotation()
@@ -111,4 +111,4 @@
.pdfViewerDash-interactive {
pointer-events: all;
-} \ No newline at end of file
+}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 2c83082b7..6ab541207 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -7,15 +7,16 @@ 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 { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnAll, returnFalse, returnNone, returnTrue, returnZero, smoothScroll, Utils } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
+import { DragManager } from '../../util/DragManager';
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 { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView';
import { FieldViewProps } from '../nodes/FieldView';
import { LinkDocPreview } from '../nodes/LinkDocPreview';
import { StyleProp } from '../StyleProvider';
@@ -29,7 +30,7 @@ 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.14.305/build/pdf.worker.js';
+pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@2.16.105/build/pdf.worker.js';
interface IViewerProps extends FieldViewProps {
Document: Doc;
@@ -63,19 +64,26 @@ export class PDFViewer extends React.Component<IViewerProps> {
private _styleRule: any; // stylesheet rule for making hyperlinks clickable
private _retries = 0; // number of times tried to create the PDF viewer
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void);
+ private _setBrushViewer: undefined | ((view: { width: number; height: number; panX: number; panY: number }) => void);
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _disposers: { [name: string]: IReactionDisposer } = {};
private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
- public _getAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
_mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _selectionText: string = '';
+ private _selectionContent: DocumentFragment | undefined;
private _downX: number = 0;
private _downY: number = 0;
private _lastSearch = false;
private _viewerIsSetup = false;
private _ignoreScroll = false;
- private _initialScroll: Opt<number>;
+ private _initialScroll: { loc: Opt<number>; easeFunc: 'linear' | 'ease' | undefined } | undefined;
private _forcedScroll = true;
+ get _getAnchor() {
+ return AnchorMenu.Instance?.GetAnchor;
+ }
+
+ selectionText = () => this._selectionText;
+ selectionContent = () => this._selectionContent;
@observable isAnnotating = false;
// key where data is stored
@@ -127,10 +135,17 @@ export class PDFViewer extends React.Component<IViewerProps> {
copy = (e: ClipboardEvent) => {
if (this.props.isContentActive() && e.clipboardData) {
e.clipboardData.setData('text/plain', this._selectionText);
+ const anchor = this._getAnchor(undefined, false);
+ if (anchor) {
+ anchor.textCopied = true;
+ e.clipboardData.setData('dash/pdfAnchor', anchor[Id]);
+ }
e.preventDefault();
}
};
+ @observable _scrollHeight = 0;
+
@action
initialLoad = async () => {
if (this._pageSizes.length === 0) {
@@ -151,33 +166,32 @@ export class PDFViewer extends React.Component<IViewerProps> {
)
)
);
- this.props.Document.scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72;
}
+ runInAction(() => (this._scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72));
};
+ _scrollStopper: undefined | (() => void);
+
// 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.
- scrollFocus = (doc: Doc, smooth: boolean) => {
+ scrollFocus = (doc: Doc, scrollTop: number, options: DocFocusOptions) => {
const mainCont = this._mainCont.current;
let focusSpeed: Opt<number>;
if (doc !== this.props.rootDoc && mainCont) {
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));
+ const scrollTo = Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) {
- focusSpeed = 500;
-
- if (!this._pdfViewer) this._initialScroll = scrollTo;
- else if (smooth) smoothScroll(focusSpeed, mainCont, scrollTo);
+ if (!this._pdfViewer) this._initialScroll = { loc: scrollTo, easeFunc: options.easeFunc };
+ else if (!options.instant) this._scrollStopper = smoothScroll((focusSpeed = options.zoomTime ?? 500), mainCont, scrollTo, options.easeFunc, this._scrollStopper);
else this._mainCont.current?.scrollTo({ top: Math.abs(scrollTo || 0) });
}
} else {
- this._initialScroll = NumCast(this.props.layoutDoc._scrollTop);
+ this._initialScroll = { loc: NumCast(this.props.layoutDoc._scrollTop), easeFunc: options.easeFunc };
}
return focusSpeed;
};
- crop = (region: Doc | undefined, addCrop?: boolean) => {
- return this.props.crop(region, addCrop);
- };
+ crop = (region: Doc | undefined, addCrop?: boolean) => this.props.crop(region, addCrop);
+ brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._setBrushViewer?.(view);
@action
setupPdfJsViewer = async () => {
@@ -196,13 +210,18 @@ export class PDFViewer extends React.Component<IViewerProps> {
this.gotoPage(NumCast(this.props.Document._curPage, 1));
}
document.removeEventListener('pagesinit', this.pagesinit);
- var quickScroll: string | undefined = this._initialScroll ? this._initialScroll.toString() : '';
+ var quickScroll: { loc?: string; easeFunc?: 'ease' | 'linear' } | undefined = { loc: this._initialScroll ? this._initialScroll.loc?.toString() : '', easeFunc: this._initialScroll ? this._initialScroll.easeFunc : undefined };
+ this._disposers.scale = reaction(
+ () => NumCast(this.props.layoutDoc._viewScale, 1),
+ scale => (this._pdfViewer.currentScaleValue = scale),
+ { fireImmediately: true }
+ );
this._disposers.scroll = reaction(
() => Math.abs(NumCast(this.props.Document._scrollTop)),
pos => {
if (!this._ignoreScroll) {
this._showWaiting && this.setupPdfJsViewer();
- const viewTrans = quickScroll ?? StrCast(this.props.Document._viewTransition);
+ const viewTrans = quickScroll?.loc ?? 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;
@@ -210,7 +229,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
if (duration) {
setTimeout(
() => {
- this._mainCont.current && smoothScroll(duration, this._mainCont.current, pos);
+ this._mainCont.current && (this._scrollStopper = smoothScroll(duration, this._mainCont.current, pos, this._initialScroll?.easeFunc ?? 'ease', this._scrollStopper));
setTimeout(() => (this._forcedScroll = false), duration);
},
this._mainCont.current ? 0 : 250
@@ -225,7 +244,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
);
quickScroll = undefined;
if (this._initialScroll !== undefined && this._mainCont.current) {
- this._mainCont.current?.scrollTo({ top: Math.abs(this._initialScroll || 0) });
+ this._mainCont.current?.scrollTo({ top: Math.abs(this._initialScroll?.loc || 0) });
this._initialScroll = undefined;
}
};
@@ -284,7 +303,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
@action
scrollToAnnotation = (scrollToAnnotation: Doc) => {
if (scrollToAnnotation) {
- this.scrollFocus(scrollToAnnotation, true);
+ this.scrollFocus(scrollToAnnotation, NumCast(scrollToAnnotation.y), { zoomTime: 500 });
Doc.linkFollowHighlight(scrollToAnnotation);
}
};
@@ -300,7 +319,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
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);
+ DocUtils.MakeLinkToActiveAudio(() => this.props.DocumentView?.().ComponentView?.getAnchor!(true)!, false);
this._scrollTimer = undefined;
}, 200);
}
@@ -326,13 +345,13 @@ export class PDFViewer extends React.Component<IViewerProps> {
query: searchString,
};
if (clear) {
- this._pdfViewer?.findController.executeCommand('reset', { query: '' });
+ this._pdfViewer?.eventBus.dispatch('reset', {});
} else if (!searchString) {
bwd ? this.prevAnnotation() : this.nextAnnotation();
} else if (this._pdfViewer?.pageViewsReady) {
- this._pdfViewer.findController.executeCommand('findagain', findOpts);
+ this._pdfViewer?.eventBus.dispatch('find', { ...findOpts, type: 'again' });
} else if (this._mainCont.current) {
- const executeFind = () => this._pdfViewer.findController.executeCommand('find', findOpts);
+ const executeFind = () => this._pdfViewer?.eventBus.dispatch('find', findOpts);
this._mainCont.current.addEventListener('pagesloaded', executeFind);
this._mainCont.current.addEventListener('pagerendered', executeFind);
}
@@ -359,9 +378,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
this._marqueeing = [e.clientX, e.clientY];
this.isAnnotating = true;
- if (e.target && ((e.target as any).className.includes('endOfContent') || (e.target as any).parentElement.className !== 'textLayer')) {
+ const target = e.target as any;
+ if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.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
} else {
// if textLayer is hit, then we select text instead of using a marquee so clear out the marquee.
setTimeout(
@@ -371,28 +390,22 @@ export class PDFViewer extends React.Component<IViewerProps> {
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);
};
- 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);
const sel = window.getSelection();
@@ -423,7 +436,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
}
}
- this._selectionText = selRange.cloneContents().textContent || '';
+ this._selectionContent = selRange.cloneContents();
+ this._selectionText = this._selectionContent?.textContent || '';
// clear selection
if (sel.empty) {
@@ -435,11 +449,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
};
- scrollXf = () => {
- return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.layoutDoc._scrollTop)) : this.props.ScreenToLocalTransform();
- };
-
onClick = (e: React.MouseEvent) => {
+ this._scrollStopper?.();
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);
}
@@ -447,6 +458,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
};
setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func);
+ setBrushViewer = (func?: (view: { width: number; height: number; panX: number; panY: number }) => void) => (this._setBrushViewer = func);
@action
onZoomWheel = (e: React.WheelEvent) => {
@@ -466,6 +478,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
<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))
+ .filter(anno => !anno.hidden)
.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`} />
))}
@@ -483,13 +496,14 @@ export class PDFViewer extends React.Component<IViewerProps> {
);
}
+ getScrollHeight = () => this._scrollHeight;
showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
+ scrollXf = () => (this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.layoutDoc._scrollTop)) : this.props.ScreenToLocalTransform());
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')];
+ panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1);
+ panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
- opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
+ opaqueFilter = () => [...this.props.docFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])];
childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
if (doc instanceof Doc && property === StyleProp.PointerEvents) {
if (doc.textInlineAnnotations) return 'none';
@@ -498,56 +512,52 @@ export class PDFViewer extends React.Component<IViewerProps> {
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}
- />
+ renderAnnotations = (docFilters: () => string[], mixBlendMode?: any, display?: string) => (
+ <div
+ className="pdfViewerDash-overlay"
+ style={{
+ mixBlendMode: mixBlendMode,
+ display: display,
+ transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})`,
+ pointerEvents: Doc.ActiveTool !== InkTool.None ? 'all' : undefined,
+ }}>
+ <CollectionFreeFormView
+ {...this.props}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ setContentView={emptyFunction} // override setContentView to do nothing
+ pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone}
+ renderDepth={this.props.renderDepth + 1}
+ isAnnotationOverlay={true}
+ fieldKey={this.props.fieldKey + '-annotations'}
+ CollectionView={undefined}
+ getScrollHeight={this.getScrollHeight}
+ setPreviewCursor={this.setPreviewCursor}
+ setBrushViewer={this.setBrushViewer}
+ PanelHeight={this.panelHeight}
+ PanelWidth={this.panelWidth}
+ ScreenToLocalTransform={this.overlayTransform}
+ isAnyChildContentActive={returnFalse}
+ isAnnotationOverlayScrollable={true}
+ dropAction={'alias'}
+ docFilters={docFilters}
+ select={emptyFunction}
+ bringToFront={emptyFunction}
+ styleProvider={this.childStyleProvider}
+ />
+ </div>
);
@computed get overlayTransparentAnnotations() {
- return this.renderAnnotations(this.transparentFilter, false);
+ return this.renderAnnotations(this.transparentFilter, 'multiply', DragManager.docsBeingDragged.length ? 'none' : undefined);
}
@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>;
+ return this.renderAnnotations(this.opaqueFilter, this.allAnnotations.some(anno => anno.mixBlendMode) ? 'hard-light' : undefined);
}
@computed get overlayLayer() {
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>
+ {this.overlayTransparentAnnotations}
+ {this.overlayOpaqueAnnotations}
</div>
);
}
@@ -586,6 +596,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
docView={this.props.docViewPath().lastElement()}
finishMarquee={this.finishMarquee}
savedAnnotations={this.savedAnnotations}
+ selectionText={this.selectionText}
annotationLayer={this._annotationLayer.current}
mainCont={this._mainCont.current}
anchorMenuCrop={this._textSelecting ? undefined : this.crop}