From 90c8fe926efa245f256c7e0a496beaecf0572294 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 18 Jan 2021 16:48:57 -0500 Subject: factored out marque selection code for WebBox and PDFBox into MarqueeAnnotator. --- src/client/views/nodes/WebBox.tsx | 196 ++++++-------------------------------- 1 file changed, 29 insertions(+), 167 deletions(-) (limited to 'src/client/views/nodes/WebBox.tsx') diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 4c4f3f15a..f9e71bc92 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -1,9 +1,10 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Dictionary } from "typescript-collections"; import * as WebRequest from 'web-request'; -import { Doc, DocListCast, Opt, AclAddonly, AclEdit, AclAdmin, DataSym, HeightSym, WidthSym } from "../../../fields/Doc"; +import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; import { Id } from "../../../fields/FieldSymbols"; import { HtmlField } from "../../../fields/HtmlField"; @@ -12,9 +13,9 @@ import { List } from "../../../fields/List"; import { listSpec, makeInterface } from "../../../fields/Schema"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { WebField } from "../../../fields/URLField"; -import { TraceMobx, GetEffectiveAcl } from "../../../fields/util"; -import { addStyleSheet, clearStyleSheetRules, emptyFunction, returnOne, returnZero, Utils, returnTrue, OmitKeys, smoothScroll } from "../../../Utils"; -import { Docs, DocUtils } from "../../documents/Documents"; +import { TraceMobx } from "../../../fields/util"; +import { addStyleSheet, clearStyleSheetRules, emptyFunction, OmitKeys, returnOne, smoothScroll, Utils } from "../../../Utils"; +import { Docs } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; import { ImageUtils } from "../../util/Import & Export/ImageUtils"; import { undoBatch } from "../../util/UndoManager"; @@ -25,14 +26,10 @@ import { ViewBoxAnnotatableComponent } from "../DocComponent"; import { DocumentDecorations } from "../DocumentDecorations"; import { Annotation } from "../pdf/Annotation"; import { PDFMenu } from "../pdf/PDFMenu"; -import { PdfViewerMarquee } from "../pdf/PDFViewer"; import { FieldView, FieldViewProps } from './FieldView'; import "./WebBox.scss"; -import "../pdf/PDFViewer.scss"; import React = require("react"); -import { Tooltip } from '@material-ui/core'; -import { CurrentUserUtils } from '../../util/CurrentUserUtils'; -import { FormattedTextBox } from './formattedText/FormattedTextBox'; +import { MarqueeAnnotator } from "../MarqueeAnnotator"; const htmlToText = require("html-to-text"); type WebDocument = makeInterface<[typeof documentSchema]>; @@ -40,17 +37,12 @@ const WebDocument = makeInterface(documentSchema); @observer export class WebBox extends ViewBoxAnnotatableComponent(WebDocument) { - private _annotationLayer: React.RefObject = React.createRef(); static _annotationStyle: any = addStyleSheet(); public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); } private _mainCont: React.RefObject = React.createRef(); - private _startX: number = 0; - private _startY: number = 0; - @observable private _marqueeX: number = 0; - @observable private _marqueeY: number = 0; - @observable private _marqueeWidth: number = 0; - @observable private _marqueeHeight: number = 0; + private _downX: number = 0; + private _downY: number = 0; @observable private _marqueeing: boolean = false; @observable private _url: string = "hello"; @observable private _pressX: number = 0; @@ -211,9 +203,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { - e.preventDefault(); - } + onUrlDragover = (e: React.DragEvent) => { e.preventDefault(); } @undoBatch @action @@ -256,6 +246,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0); } + @action submitURL = () => { if (!this._url.startsWith("http")) this._url = "http://" + this._url; @@ -283,9 +274,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { - if (e.key === "Enter") { - this.submitURL(); - } + if (e.key === "Enter") this.submitURL(); e.stopPropagation(); } @@ -300,22 +289,14 @@ export class WebBox extends ViewBoxAnnotatableComponent { - this._ignore = e.timeStamp; - } - onPrePointer = (e: React.PointerEvent) => { - this._ignore = e.timeStamp; - } + onPreWheel = (e: React.WheelEvent) => { this._ignore = e.timeStamp; } + onPrePointer = (e: React.PointerEvent) => { this._ignore = e.timeStamp; } onPostPointer = (e: React.PointerEvent) => { - if (this._ignore !== e.timeStamp) { - e.stopPropagation(); - } + if (this._ignore !== e.timeStamp) e.stopPropagation(); } onPostWheel = (e: React.WheelEvent) => { - if (this._ignore !== e.timeStamp) { - e.stopPropagation(); - } + if (this._ignore !== e.timeStamp) e.stopPropagation(); } onLongPressDown = (e: React.PointerEvent) => { @@ -431,7 +412,6 @@ export class WebBox extends ViewBoxAnnotatableComponent); } - - @computed get allAnnotations() { return DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]); } @computed get nonDocAnnotations() { return this.allAnnotations.filter(a => a.annotations); } - @undoBatch - @action - makeAnnotationDocument = (color: string): Opt => { - const scale = this.props.scaling?.() || 1; - if (this._savedAnnotations.size() === 0) return undefined; - const anno = this._savedAnnotations.values()[0][0]; - const annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, annotationOn: this.props.Document, title: "Annotation on " + this.Document.title }); - if (anno.style.left) annoDoc.x = parseInt(anno.style.left) / scale; - if (anno.style.top) annoDoc.y = (NumCast(this.layoutDoc._scrollTop) + parseInt(anno.style.top)) / scale; - if (anno.style.height) annoDoc._height = parseInt(anno.style.height) / scale; - if (anno.style.width) annoDoc._width = parseInt(anno.style.width) / scale; - anno.remove(); - this._savedAnnotations.clear(); - return annoDoc; - } @computed get annotationLayer() { TraceMobx(); return
@@ -504,65 +468,13 @@ export class WebBox extends ViewBoxAnnotatableComponent; } - @action - createAnnotation = (div: HTMLDivElement, page: number) => { - if (this._annotationLayer.current) { - if (div.style.top) { - div.style.top = (parseInt(div.style.top)).toString(); - } - this._annotationLayer.current.append(div); - div.style.backgroundColor = "#ACCEF7"; - div.style.opacity = "0.5"; - const savedPage = this._savedAnnotations.getValue(page); - if (savedPage) { - savedPage.push(div); - this._savedAnnotations.setValue(page, savedPage); - } - else { - this._savedAnnotations.setValue(page, [div]); - } - } - } - - @action - highlight = (color: string) => { - // creates annotation documents for current highlights - const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]); - const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) ? this.makeAnnotationDocument(color.replace(/[0-9.]*\)/, "0.3)")) : undefined; - annotationDoc && this.addDocument?.(annotationDoc); - return annotationDoc ?? undefined; - } - /** - * This is temporary for creating annotations from highlights. It will - * start a drag event and create or put the necessary info into the drag event. - */ - @action - startDrag = async (e: PointerEvent, ele: HTMLElement) => { - e.preventDefault(); - e.stopPropagation(); - const targetDoc = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.Document.title, 0, 0, 125, 125); - FormattedTextBox.SelectOnLoad = targetDoc[Id]; - const annotationDoc = this.highlight("rgba(173, 216, 230, 0.35)"); // hyperlink color - if (annotationDoc) { - DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, { - dragComplete: e => { - if (!e.aborted && e.annoDragData && !e.linkDocument) { - e.linkDocument = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation"); - annotationDoc.isLinkButton = true; - annotationDoc.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.props.Document; - } - } - }); - } - } @action onMarqueeDown = (e: React.PointerEvent) => { - this._marqueeing = false; + this._downX = e.clientX; + this._downY = e.clientY; if (!e.altKey && e.button === 0 && this.active(true)) { // clear out old marquees and initialize menu for new selection - PDFMenu.Instance.StartDrag = this.startDrag; - PDFMenu.Instance.Highlight = this.highlight; PDFMenu.Instance.Status = "pdf"; PDFMenu.Instance.fadeOut(true); this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); @@ -570,79 +482,30 @@ export class WebBox extends ViewBoxAnnotatableComponent { - if (this._marqueeing && this._mainCont.current) { - // transform positions and find the width and height to set the marquee to - const boundingRect = this._mainCont.current.getBoundingClientRect(); - this._marqueeWidth = ((e.clientX - boundingRect.left) * (this._mainCont.current.offsetWidth / boundingRect.width)) - this._startX; - this._marqueeHeight = ((e.clientY - boundingRect.top) * (this._mainCont.current.offsetHeight / boundingRect.height)) - this._startY + this._mainCont.current.scrollTop; - this._marqueeX = Math.min(this._startX, this._startX + this._marqueeWidth); - this._marqueeY = Math.min(this._startY, this._startY + this._marqueeHeight); - this._marqueeWidth = Math.abs(this._marqueeWidth); - this._marqueeHeight = Math.abs(this._marqueeHeight); - e.stopPropagation(); - e.preventDefault(); - } - else if (e.target && (e.target as any).parentElement === this._mainCont.current) { - e.stopPropagation(); - } + if (e.target && (e.target as any).parentElement === this._mainCont.current) e.stopPropagation(); } + @action + finishMarquee = () => { this._marqueeing = false; } + @action onSelectEnd = (e: PointerEvent): void => { clearStyleSheetRules(WebBox._annotationStyle); + this.props.select(true); this._savedAnnotations.clear(); - if (this._marqueeWidth > 10 || this._marqueeHeight > 10) { - const marquees = this._mainCont.current!.getElementsByClassName("pdfViewerDash-dragAnnotationBox"); - if (marquees?.length) { // copy the marquee and convert it to a permanent annotation. - const style = (marquees[0] as HTMLDivElement).style; - const copy = document.createElement("div"); - copy.style.left = style.left; - copy.style.top = style.top; - copy.style.width = style.width; - copy.style.height = style.height; - copy.style.border = style.border; - copy.style.opacity = style.opacity; - (copy as any).marqueeing = true; - copy.className = "webBox-annotationBox"; - this.createAnnotation(copy, 0); - } - if (!e.ctrlKey) { - PDFMenu.Instance.Marquee = { left: this._marqueeX, top: this._marqueeY, width: this._marqueeWidth, height: this._marqueeHeight }; - } - PDFMenu.Instance.jumpTo(e.clientX, e.clientY); - } - //this._marqueeing = false; - - if (PDFMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up - this.highlight("rgba(245, 230, 95, 0.616)"); // yellowish highlight color for highlighted text (should match PDFMenu's highlight color) - } - else { - PDFMenu.Instance.StartDrag = this.startDrag; - PDFMenu.Instance.Highlight = this.highlight; - } document.removeEventListener("pointermove", this.onSelectMove); document.removeEventListener("pointerup", this.onSelectEnd); } - marqueeWidth = () => this._marqueeWidth; - marqueeHeight = () => this._marqueeHeight; - marqueeX = () => this._marqueeX; - marqueeY = () => this._marqueeY; - marqueeing = () => this._marqueeing; + scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc._scrollLeft), NumCast(this.layoutDoc._scrollTop)); render() { const inactiveLayer = this.props.layerProvider?.(this.layoutDoc) === false; @@ -655,9 +518,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { @@ -700,7 +561,8 @@ export class WebBox extends ViewBoxAnnotatableComponent
{this.annotationLayer} - + {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) : + } {this.props.isSelected() ? this.editToggleBtn() : null} ); -- cgit v1.2.3-70-g09d2 From 22a2462a6854f31f6f546d56258aec2042073d4b Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 19 Jan 2021 11:31:02 -0500 Subject: more cleanup of marquee annotator. added marquee annotator to images --- src/client/views/MarqueeAnnotator.tsx | 40 ++++++++---- .../collections/collectionFreeForm/MarqueeView.tsx | 1 + src/client/views/nodes/ImageBox.scss | 10 +++ src/client/views/nodes/ImageBox.tsx | 43 +++++++++++-- src/client/views/nodes/WebBox.tsx | 72 ++++++--------------- src/client/views/pdf/PDFViewer.tsx | 74 +++++++++++----------- 6 files changed, 131 insertions(+), 109 deletions(-) (limited to 'src/client/views/nodes/WebBox.tsx') diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index bcc03bea1..fe9c954ca 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -1,4 +1,4 @@ -import { action, observable } from "mobx"; +import { action, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Dictionary } from "typescript-collections"; import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../fields/Doc"; @@ -19,8 +19,7 @@ const _global = (window /* browser */ || global /* node */) as any; export interface MarqueeAnnotatorProps { rootDoc: Doc; - clientX: number; - clientY: number; + down: number[]; scaling?: () => number; mainCont: HTMLDivElement; savedAnnotations: Dictionary; @@ -38,11 +37,22 @@ export class MarqueeAnnotator extends React.Component { @observable private _width: number = 0; @observable private _height: number = 0; + constructor(props: any) { + super(props); + runInAction(() => { + PDFMenu.Instance.Status = "pdf"; + PDFMenu.Instance.fadeOut(true); + // clear out old marquees and initialize menu for new selection + this.props.savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); + this.props.savedAnnotations.clear(); + }); + } + @action componentDidMount() { // set marquee x and y positions to the spatially transformed position const boundingRect = this.props.mainCont.getBoundingClientRect(); - this._startX = this._left = (this.props.clientX - boundingRect.left) * (this.props.mainCont.offsetWidth / boundingRect.width); - this._startY = this._top = (this.props.clientY - boundingRect.top) * (this.props.mainCont.offsetHeight / boundingRect.height) + this.props.mainCont.scrollTop; + this._startX = this._left = (this.props.down[0] - boundingRect.left) * (this.props.mainCont.offsetWidth / boundingRect.width); + this._startY = this._top = (this.props.down[1] - boundingRect.top) * (this.props.mainCont.offsetHeight / boundingRect.height) + this.props.mainCont.scrollTop; this._height = this._width = 0; document.addEventListener("pointermove", this.onSelectMove); document.addEventListener("pointerup", this.onSelectEnd); @@ -134,7 +144,6 @@ export class MarqueeAnnotator extends React.Component { this._width = Math.abs(this._width); this._height = Math.abs(this._height); e.stopPropagation(); - e.preventDefault(); } onSelectEnd = (e: PointerEvent) => { @@ -142,14 +151,6 @@ export class MarqueeAnnotator extends React.Component { PDFMenu.Instance.Marquee = { left: this._left, top: this._top, width: this._width, height: this._height }; } - const marquees = this.props.mainCont.getElementsByClassName("marqueeAnnotator-dragBox"); - if (marquees?.length) { // copy the temporary marquee to allow for multiple selections (not currently available though). - const copy = document.createElement("div"); - ["left", "top", "width", "height", "border", "opacity"].forEach(prop => copy.style[prop as any] = (marquees[0] as HTMLDivElement).style[prop as any]); - copy.className = "marqueeAnnotator-annotationBox"; - (copy as any).marqueeing = true; - MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations, this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this._top) || 0); - } PDFMenu.Instance.Highlight = this.highlight; /** * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation. @@ -176,11 +177,22 @@ export class MarqueeAnnotator extends React.Component { }); if (this._width > 10 || this._height > 10) { // configure and show the annotation/link menu if a the drag region is big enough + const marquees = this.props.mainCont.getElementsByClassName("marqueeAnnotator-dragBox"); + if (marquees?.length) { // copy the temporary marquee to allow for multiple selections (not currently available though). + const copy = document.createElement("div"); + ["left", "top", "width", "height", "border", "opacity"].forEach(prop => copy.style[prop as any] = (marquees[0] as HTMLDivElement).style[prop as any]); + copy.className = "marqueeAnnotator-annotationBox"; + (copy as any).marqueeing = true; + MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations, this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this._top) || 0); + } + PDFMenu.Instance.jumpTo(e.clientX, e.clientY); if (PDFMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up this.highlight("rgba(245, 230, 95, 0.75)"); // yellowish highlight color for highlighted text (should match PDFMenu's highlight color) } + } else { + runInAction(() => this._width = this._height = 0); } this.props.finishMarquee(); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index d20d1abfc..258e839d0 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -217,6 +217,7 @@ export class MarqueeView extends React.Component = React.createRef(); private _dropDisposer?: DragManager.DragDropDisposer; - private _pathDisposer?: IReactionDisposer; + private _disposers: { [name: string]: IReactionDisposer } = {}; @observable private _audioState = 0; @observable static _showControls: boolean; @observable uploadIcon = uploadIcons.idle; @@ -74,7 +78,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }), + this._disposers.selection = reaction(() => this.props.isSelected(), + selected => { + if (!selected) { + this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); + this._savedAnnotations.clear(); + PDFMenu.Instance.fadeOut(true); + } + }, + { fireImmediately: true }); + this._disposers.path = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }), action(({ nativeSize, width }) => { if (!this.layoutDoc._height) { this.layoutDoc._height = width * nativeSize.nativeHeight / nativeSize.nativeWidth; @@ -84,7 +97,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent disposer?.()); } @undoBatch @@ -356,7 +369,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent + return
this.props.ScreenToLocalTransform().translate(0, -this.ycenter); contentFunc = () => [this.content]; + private _mainCont: React.RefObject = React.createRef(); + private _annotationLayer: React.RefObject = React.createRef(); + @observable _marqueeing: number[] | undefined; + @observable _savedAnnotations: Dictionary = new Dictionary(); + @computed get annotationLayer() { + return
; + } + @action + marqueeDown = (e: React.PointerEvent) => { + if (!e.altKey && e.button === 0 && this.active(true)) this._marqueeing = [e.clientX, e.clientY]; + } + @action + finishMarquee = () => { + this._marqueeing = undefined; + this.props.select(true); + } + render() { TraceMobx(); const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding); const borderRadius = borderRad?.includes("px") ? `${Number(borderRad.split("px")[0]) / (this.props.scaling?.() || 1)}px` : borderRad; - return (
{this.contentFunc} + {this.annotationLayer} + {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) : + }
); } } diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index f9e71bc92..ca3d4448d 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -14,7 +14,7 @@ import { listSpec, makeInterface } from "../../../fields/Schema"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { WebField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; -import { addStyleSheet, clearStyleSheetRules, emptyFunction, OmitKeys, returnOne, smoothScroll, Utils } from "../../../Utils"; +import { emptyFunction, OmitKeys, returnOne, smoothScroll, Utils } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; import { ImageUtils } from "../../util/Import & Export/ImageUtils"; @@ -38,26 +38,21 @@ const WebDocument = makeInterface(documentSchema); @observer export class WebBox extends ViewBoxAnnotatableComponent(WebDocument) { private _annotationLayer: React.RefObject = React.createRef(); - static _annotationStyle: any = addStyleSheet(); public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); } private _mainCont: React.RefObject = React.createRef(); - private _downX: number = 0; - private _downY: number = 0; - @observable private _marqueeing: boolean = false; + private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void); + private _disposers: { [name: string]: IReactionDisposer } = {}; + private _longPressSecondsHack?: NodeJS.Timeout; + private _outerRef = React.createRef(); + private _iframeIndicatorRef = React.createRef(); + private _iframeDragRef = React.createRef(); + @observable private _marqueeing: number[] | undefined; @observable private _url: string = "hello"; @observable private _pressX: number = 0; @observable private _pressY: number = 0; @observable private _iframe: HTMLIFrameElement | null = null; @observable private _savedAnnotations: Dictionary = new Dictionary(); - private _selectionReactionDisposer?: IReactionDisposer; - private _scrollReactionDisposer?: IReactionDisposer; - private _scrollTopReactionDisposer?: IReactionDisposer; - private _moveReactionDisposer?: IReactionDisposer; - private _longPressSecondsHack?: NodeJS.Timeout; - private _outerRef = React.createRef(); - private _iframeIndicatorRef = React.createRef(); - private _iframeDragRef = React.createRef(); - private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void); + get scrollHeight() { return this.webpage?.scrollHeight || 1000; } get _collapsed() { return StrCast(this.layoutDoc._chromeStatus) !== "enabled"; } set _collapsed(value) { this.layoutDoc._chromeStatus = !value ? "enabled" : "disabled"; } @@ -91,8 +86,8 @@ export class WebBox extends ViewBoxAnnotatableComponent ({ scrollY: this.layoutDoc._scrollY, scrollX: this.layoutDoc._scrollX }), + this._disposers.scrollReaction?.(); + this._disposers.scrollReaction = reaction(() => ({ scrollY: this.layoutDoc._scrollY, scrollX: this.layoutDoc._scrollX }), ({ scrollY, scrollX }) => { const delay = this._outerRef.current ? 0 : 250; // wait for mainCont and try again to scroll const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/); @@ -110,7 +105,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this.layoutDoc._scrollTop, + this._disposers.scrollTop = reaction(() => this.layoutDoc._scrollTop, scrollTop => { const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/); const duration = durationStr ? Number(durationStr[1]) : 1000; @@ -158,14 +153,14 @@ export class WebBox extends ViewBoxAnnotatableComponent this._url = urlField?.url.toString() || ""); - this._moveReactionDisposer = reaction(() => this.layoutDoc.x || this.layoutDoc.y, + this._disposers.scrollMove = reaction(() => this.layoutDoc.x || this.layoutDoc.y, () => this.updateScroll(this.layoutDoc._scrollLeft, this.layoutDoc._scrollTop)); - this._selectionReactionDisposer = reaction(() => this.props.isSelected(), + this._disposers.selection = reaction(() => this.props.isSelected(), selected => { if (!selected) { this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); - this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, [])); + this._savedAnnotations.clear(); PDFMenu.Instance.fadeOut(true); } }, @@ -194,10 +189,7 @@ export class WebBox extends ViewBoxAnnotatableComponent disposer?.()); document.removeEventListener("pointerup", this.onLongPressUp); document.removeEventListener("pointermove", this.onLongPressMove); this._iframe?.removeEventListener('wheel', this.iframeWheel); @@ -471,39 +463,15 @@ export class WebBox extends ViewBoxAnnotatableComponent { - this._downX = e.clientX; - this._downY = e.clientY; if (!e.altKey && e.button === 0 && this.active(true)) { - // clear out old marquees and initialize menu for new selection - PDFMenu.Instance.Status = "pdf"; - PDFMenu.Instance.fadeOut(true); - this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); - this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, [])); - if ((e.target as any)?.parentElement.className === "textLayer") { - // start selecting text if mouse down on textLayer spans - } - else this._marqueeing = true; - document.addEventListener("pointermove", this.onSelectMove); - document.addEventListener("pointerup", this.onSelectEnd); + this._marqueeing = [e.clientX, e.clientY]; } } @action - onSelectMove = (e: PointerEvent): void => { - if (e.target && (e.target as any).parentElement === this._mainCont.current) e.stopPropagation(); - } - - @action - finishMarquee = () => { this._marqueeing = false; } - - @action - onSelectEnd = (e: PointerEvent): void => { - clearStyleSheetRules(WebBox._annotationStyle); + finishMarquee = () => { + this._marqueeing = undefined; this.props.select(true); - this._savedAnnotations.clear(); - - document.removeEventListener("pointermove", this.onSelectMove); - document.removeEventListener("pointerup", this.onSelectEnd); } scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc._scrollLeft), NumCast(this.layoutDoc._scrollTop)); @@ -562,7 +530,7 @@ export class WebBox extends ViewBoxAnnotatableComponent {this.annotationLayer} {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) : - } + }
{this.props.isSelected() ? this.editToggleBtn() : null}
); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 03ffdb2db..26f936c66 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -68,7 +68,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent = new Dictionary(); @observable private _script: CompiledScript = CompileScript("return true") as CompiledScript; @observable private Index: number = -1; - @observable private _marqueeing: boolean = false; + @observable private _marqueeing: number[] | undefined; @observable private _showWaiting = true; @observable private _showCover = false; @observable private _zoomed = 1; @@ -379,29 +379,30 @@ export class PDFViewer extends ViewBoxAnnotatableComponent v.forEach(a => a.remove())); - this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, [])); - if (e.target && (e.target as any).parentElement.className === "textLayer") { - // start selecting text if mouse down on textLayer spans + if (e.target && (e.target as any).parentElement.className !== "textLayer") { + this._marqueeing = [e.clientX, e.clientY]; // if texLayer is hit, then we select text instead of using a marquee + } else { + // clear out old marquees and initialize menu for new selection + PDFMenu.Instance.Status = "pdf"; + PDFMenu.Instance.fadeOut(true); + this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); + this._savedAnnotations.clear(); + this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, "pdfAnnotation", { "pointer-events": "none" }); + document.addEventListener("pointermove", this.onSelectMove); + document.addEventListener("pointerup", this.onSelectEnd); } - else this._marqueeing = true; - document.addEventListener("pointermove", this.onSelectMove); - document.addEventListener("pointerup", this.onSelectEnd); - document.addEventListener("pointerup", this.removeStyle, true); } } - removeStyle = () => { - clearStyleSheetRules(PDFViewer._annotationStyle); - document.removeEventListener("pointerup", this.removeStyle); + + @action + finishMarquee = () => { + this._marqueeing = undefined; + this.props.select(false); } @action @@ -409,6 +410,21 @@ export class PDFViewer extends ViewBoxAnnotatableComponent { + clearStyleSheetRules(PDFViewer._annotationStyle); + this.props.select(false); + document.removeEventListener("pointermove", this.onSelectMove); + document.removeEventListener("pointerup", this.onSelectEnd); + + const sel = window.getSelection(); + if (sel?.type === "Range") { + const selRange = sel.getRangeAt(0); + this.createTextAnnotation(sel, selRange); + PDFMenu.Instance.jumpTo(e.clientX, e.clientY); + } + } + @action createTextAnnotation = (sel: Selection, selRange: Range) => { if (this._mainCont.current) { @@ -441,35 +457,17 @@ export class PDFViewer extends ViewBoxAnnotatableComponent { this._marqueeing = false; } - - @action - onSelectEnd = (e: PointerEvent): void => { - clearStyleSheetRules(PDFViewer._annotationStyle); - this.props.select(false); - this._savedAnnotations.clear(); - document.removeEventListener("pointermove", this.onSelectMove); - document.removeEventListener("pointerup", this.onSelectEnd); - - const sel = window.getSelection(); - if (sel?.type === "Range") { - const selRange = sel.getRangeAt(0); - this.createTextAnnotation(sel, selRange); - PDFMenu.Instance.jumpTo(e.clientX, e.clientY); - } - } - scrollXf = () => { return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this.layoutDoc._scrollTop || 0) : this.props.ScreenToLocalTransform(); } onClick = (e: React.MouseEvent) => { if (this._setPreviewCursor && e.button === 0 && - Math.abs(e.clientX - this._downX) < 3 && - Math.abs(e.clientY - this._downY) < 3) { + 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); } + e.stopPropagation(); } setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func; @@ -578,7 +576,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent} + }
; } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 68785a97178d229935c0429791081d7c09312dc3 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 20 Jan 2021 18:35:58 -0500 Subject: moved PDFMenu to AnchorMenu. added AnchorMenu to formattedTextBox. Fixed following links from text box huyperlinks when there are multiple different huypertext links on the document. --- src/client/util/DragManager.ts | 24 ++-- src/client/views/GlobalKeyHandler.ts | 4 +- src/client/views/MainView.tsx | 4 +- src/client/views/MarqueeAnnotator.tsx | 53 +++---- src/client/views/collections/CollectionSubView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 9 +- src/client/views/nodes/DocumentView.tsx | 14 +- src/client/views/nodes/ImageBox.tsx | 4 +- src/client/views/nodes/VideoBox.tsx | 4 +- src/client/views/nodes/WebBox.tsx | 4 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 40 ++++++ .../formattedText/FormattedTextBoxComment.tsx | 2 +- src/client/views/pdf/AnchorMenu.scss | 25 ++++ src/client/views/pdf/AnchorMenu.tsx | 157 +++++++++++++++++++++ src/client/views/pdf/Annotation.tsx | 20 +-- src/client/views/pdf/PDFMenu.scss | 25 ---- src/client/views/pdf/PDFMenu.tsx | 157 --------------------- src/client/views/pdf/PDFViewer.tsx | 18 +-- 18 files changed, 311 insertions(+), 257 deletions(-) create mode 100644 src/client/views/pdf/AnchorMenu.scss create mode 100644 src/client/views/pdf/AnchorMenu.tsx delete mode 100644 src/client/views/pdf/PDFMenu.scss delete mode 100644 src/client/views/pdf/PDFMenu.tsx (limited to 'src/client/views/nodes/WebBox.tsx') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 52ccfda74..7b4d43793 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -105,14 +105,14 @@ export namespace DragManager { constructor(aborted: boolean, dragData: { [id: string]: any }) { this.aborted = aborted; this.docDragData = dragData instanceof DocumentDragData ? dragData : undefined; - this.annoDragData = dragData instanceof PdfAnnoDragData ? dragData : undefined; + this.annoDragData = dragData instanceof AnchorAnnoDragData ? dragData : undefined; this.linkDragData = dragData instanceof LinkDragData ? dragData : undefined; this.columnDragData = dragData instanceof ColumnDragData ? dragData : undefined; } linkDocument?: Doc; aborted: boolean; docDragData?: DocumentDragData; - annoDragData?: PdfAnnoDragData; + annoDragData?: AnchorAnnoDragData; linkDragData?: LinkDragData; columnDragData?: ColumnDragData; } @@ -152,19 +152,21 @@ export namespace DragManager { } colKey: SchemaHeaderField; } - // used by PDFs to conditionally (if the drop completes) create a text annotation when dragging from the PDF toolbar when a text region has been selected. + // used by PDFs,Text,Image,Video,Web to conditionally (if the drop completes) create a text annotation when dragging the annotate button from the AnchorMenu when a text/region selection has been made. // this is pretty clunky and should be rethought out using linkDrag or DocumentDrag - export class PdfAnnoDragData { - constructor(dragDoc: Doc, annotationDoc: Doc, dropDoc: Doc) { + export class AnchorAnnoDragData { + constructor(dragDoc: Doc, annotationDocCreator: () => Doc, dropDocCreator: () => Doc) { this.dragDocument = dragDoc; - this.dropDocument = dropDoc; - this.annotationDocument = annotationDoc; + this.dropDocCreator = dropDocCreator; + this.annotationDocCreator = annotationDocCreator; this.offset = [0, 0]; } targetContext: Doc | undefined; dragDocument: Doc; - annotationDocument: Doc; - dropDocument: Doc; + annotationDocCreator: () => Doc; + dropDocCreator: () => Doc; + dropDocument?: Doc; + annotationDocument?: Doc; offset: number[]; dropAction: dropActionType; userDropAction: dropActionType; @@ -250,7 +252,7 @@ export namespace DragManager { } // drag&drop the pdf annotation anchor which will create a text note on drop via a dropCompleted() DragOption - export function StartPdfAnnoDrag(eles: HTMLElement[], dragData: PdfAnnoDragData, downX: number, downY: number, options?: DragOptions) { + export function StartAnchorAnnoDrag(eles: HTMLElement[], dragData: AnchorAnnoDragData, downX: number, downY: number, options?: DragOptions) { StartDrag(eles, dragData, downX, downY, options); } @@ -353,7 +355,7 @@ export namespace DragManager { const xs: number[] = []; const ys: number[] = []; - docsBeingDragged = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof PdfAnnoDragData ? [dragData.dragDocument] : []; + docsBeingDragged = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnchorAnnoDragData ? [dragData.dragDocument] : []; const elesCont = { left: Number.MAX_SAFE_INTEGER, top: Number.MAX_SAFE_INTEGER, diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 8a07c5321..1c5277de0 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -22,7 +22,7 @@ import { DocumentDecorations } from "./DocumentDecorations"; import { InkStrokeProperties } from "./InkStrokeProperties"; import { MainView } from "./MainView"; import { DocumentLinksButton } from "./nodes/DocumentLinksButton"; -import { PDFMenu } from "./pdf/PDFMenu"; +import { AnchorMenu } from "./pdf/AnchorMenu"; import { SnappingManager } from "../util/SnappingManager"; import { SearchBox } from "./search/SearchBox"; import { random } from "lodash"; @@ -261,7 +261,7 @@ export class KeyManager { } break; case "c": - if (!PDFMenu.Instance.Active && DocumentDecorations.Instance.Bounds.r - DocumentDecorations.Instance.Bounds.x > 2) { + if (!AnchorMenu.Instance.Active && DocumentDecorations.Instance.Bounds.r - DocumentDecorations.Instance.Bounds.x > 2) { const bds = DocumentDecorations.Instance.Bounds; const pt = SelectionManager.Views()[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2); const text = `__DashCloneId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views().map(dv => dv.Document[Id]).join(":"); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index c1fafe3e6..bd5db62ef 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -55,7 +55,7 @@ import { RadialMenu } from './nodes/RadialMenu'; import { TaskCompletionBox } from './nodes/TaskCompletedBox'; import { WebBox } from './nodes/WebBox'; import { OverlayView } from './OverlayView'; -import { PDFMenu } from './pdf/PDFMenu'; +import { AnchorMenu } from './pdf/AnchorMenu'; import { PreviewCursor } from './PreviewCursor'; import { PropertiesView } from './PropertiesView'; import { SearchBox } from './search/SearchBox'; @@ -615,7 +615,7 @@ export class MainView extends React.Component { - + diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 21048ebe2..0ab2d1ecf 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -8,7 +8,7 @@ import { DocUtils, Docs } from "../documents/Documents"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { DragManager } from "../util/DragManager"; import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox"; -import { PDFMenu } from "./pdf/PDFMenu"; +import { AnchorMenu } from "./pdf/AnchorMenu"; import "./MarqueeAnnotator.scss"; import React = require("react"); import { undoBatch } from "../util/UndoManager"; @@ -40,8 +40,8 @@ export class MarqueeAnnotator extends React.Component { constructor(props: any) { super(props); runInAction(() => { - PDFMenu.Instance.Status = "marquee"; - PDFMenu.Instance.fadeOut(true); + AnchorMenu.Instance.Status = "marquee"; + AnchorMenu.Instance.fadeOut(true); // clear out old marquees and initialize menu for new selection this.props.savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); this.props.savedAnnotations.clear(); @@ -148,32 +148,37 @@ export class MarqueeAnnotator extends React.Component { onSelectEnd = (e: PointerEvent) => { if (!e.ctrlKey) { - PDFMenu.Instance.Marquee = { left: this._left, top: this._top, width: this._width, height: this._height }; + AnchorMenu.Instance.Marquee = { left: this._left, top: this._top, width: this._width, height: this._height }; } - PDFMenu.Instance.Highlight = this.highlight; + AnchorMenu.Instance.Highlight = this.highlight; /** - * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation. + * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation. * It also initiates a Drag/Drop interaction to place the text annotation. */ - PDFMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => { + AnchorMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => { e.preventDefault(); e.stopPropagation(); - const targetDoc = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.rootDoc.title, 0, 0, 100, 100); - FormattedTextBox.SelectOnLoad = targetDoc[Id]; - const anchorHighlightDoc = this.highlight("rgba(173, 216, 230, 0.75)"); // hyperlink color - if (anchorHighlightDoc) { - DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.rootDoc, anchorHighlightDoc, targetDoc), e.pageX, e.pageY, { - dragComplete: e => { - if (!e.aborted && e.annoDragData && !e.linkDocument) { - e.linkDocument = DocUtils.MakeLink({ doc: anchorHighlightDoc }, { doc: e.annoDragData.dropDocument }, "Annotation"); - anchorHighlightDoc.isLinkButton = true; // prevents link button from showing up --- maybe not a good thing? - anchorHighlightDoc.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.props.rootDoc; - } - e.linkDocument && e.annoDragData?.linkDropCallback?.(e as { linkDocument: Doc });// bcz: typescript can't figure out that this is valid even though we tested e.linkDocument - } - }); + const targetCreator = () => { + const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.rootDoc.title, 0, 0, 100, 100); + FormattedTextBox.SelectOnLoad = target[Id]; + return target; + } + const anchorCreator = () => { + const annoDoc = this.highlight("rgba(173, 216, 230, 0.75)"); // hyperlink color + annoDoc.isLinkButton = true; // prevents link button from showing up --- maybe not a good thing? + this.props.addDocument(annoDoc); + return annoDoc; } + DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.rootDoc, anchorCreator, targetCreator), e.pageX, e.pageY, { + dragComplete: e => { + if (!e.aborted && e.annoDragData && e.annoDragData.annotationDocument && e.annoDragData.dropDocument && !e.linkDocument) { + e.linkDocument = DocUtils.MakeLink({ doc: e.annoDragData.annotationDocument }, { doc: e.annoDragData.dropDocument }, "Annotation"); + e.annoDragData.annotationDocument.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.props.rootDoc; + } + e.linkDocument && e.annoDragData?.linkDropCallback?.(e as { linkDocument: Doc });// bcz: typescript can't figure out that this is valid even though we tested e.linkDocument + } + }); }); if (this._width > 10 || this._height > 10) { // configure and show the annotation/link menu if a the drag region is big enough @@ -186,10 +191,10 @@ export class MarqueeAnnotator extends React.Component { MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations, this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this._top) || 0); } - PDFMenu.Instance.jumpTo(e.clientX, e.clientY); + AnchorMenu.Instance.jumpTo(e.clientX, e.clientY); - if (PDFMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up - this.highlight("rgba(245, 230, 95, 0.75)"); // yellowish highlight color for highlighted text (should match PDFMenu's highlight color) + if (AnchorMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up + this.highlight("rgba(245, 230, 95, 0.75)"); // yellowish highlight color for highlighted text (should match AnchorMenu's highlight color) } } else { runInAction(() => this._width = this._height = 0); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index d7b9d9745..26cb4d156 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -224,7 +224,9 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: } else if (de.complete.annoDragData && (!this.props.isAnnotationOverlay || de.complete.annoDragData.dragDocument === this.props.Document)) { e.stopPropagation(); - return this.addDocument(de.complete.annoDragData.dropDocument); + de.complete.annoDragData.annotationDocument = de.complete.annoDragData.annotationDocCreator(); + de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(); + return de.complete.annoDragData.dropDocument && this.addDocument(de.complete.annoDragData.dropDocument); } return false; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 588ba6922..2bc716928 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -43,7 +43,6 @@ import { CollectionViewType } from "../CollectionView"; import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines"; import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import "./CollectionFreeFormView.scss"; -import { MarqueeOptionsMenu } from "./MarqueeOptionsMenu"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; @@ -267,8 +266,8 @@ export class CollectionFreeFormView extends CollectionSubView { const [xp, yp] = this.getTransform().transformPoint(de.x, de.y); if (this.isAnnotationOverlay !== true && de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp); - if (de.complete.annoDragData?.dropDocument && super.onInternalDrop(e, de)) return this.internalPdfAnnoDrop(e, de.complete.annoDragData, xp, yp); + if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalPdfAnnoDrop(e, de.complete.annoDragData, xp, yp); if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, xp, yp); return false; } @@ -813,7 +812,7 @@ export class CollectionFreeFormView extends CollectionSubView { @computed get panelWidth() { return this.nativeWidth ? this.nativeWidth * this.nativeScaling : this.props.PanelWidth(); } @computed get panelHeight() { if (this.nativeHeight) { - if (this.props.Document._fitWidth) { - return Math.min(this.props.PanelHeight(), NumCast(this.props.Document.scrollHeight, this.props.PanelHeight())); - } - return Math.min(this.props.PanelHeight(), this.nativeHeight * this.nativeScaling); + return Math.min(this.props.PanelHeight(), + this.props.Document._fitWidth ? + Math.max(NumCast(this.props.Document._height), NumCast((this.props.Document.scrollHeight as number) * this.props.PanelWidth() / this.nativeWidth, this.props.PanelHeight())) : + this.nativeHeight * this.nativeScaling + ); } return this.props.PanelHeight(); } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index e202749aa..47fa25951 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -27,7 +27,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); import { StyleProp } from '../StyleProvider'; -import { PDFMenu } from '../pdf/PDFMenu'; +import { AnchorMenu } from '../pdf/AnchorMenu'; import { Dictionary } from 'typescript-collections'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { Annotation } from '../pdf/Annotation'; @@ -83,7 +83,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent v.forEach(a => a.remove())); this._savedAnnotations.clear(); - PDFMenu.Instance.fadeOut(true); + AnchorMenu.Instance.fadeOut(true); } }, { fireImmediately: true }); diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index f1ef6d10b..55a9818ad 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -27,7 +27,7 @@ import { Transform } from "../../util/Transform"; import { StyleProp } from "../StyleProvider"; import { Dictionary } from "typescript-collections"; import { MarqueeAnnotator } from "../MarqueeAnnotator"; -import { PDFMenu } from "../pdf/PDFMenu"; +import { AnchorMenu } from "../pdf/AnchorMenu"; const path = require('path'); export const timeSchema = createSchema({ @@ -195,7 +195,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent v.forEach(a => a.remove())); this._savedAnnotations.clear(); - PDFMenu.Instance.fadeOut(true); + AnchorMenu.Instance.fadeOut(true); } }, { fireImmediately: true }); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index ca3d4448d..c9c4ed159 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -25,7 +25,7 @@ import { ContextMenuProps } from "../ContextMenuItem"; import { ViewBoxAnnotatableComponent } from "../DocComponent"; import { DocumentDecorations } from "../DocumentDecorations"; import { Annotation } from "../pdf/Annotation"; -import { PDFMenu } from "../pdf/PDFMenu"; +import { AnchorMenu } from "../pdf/AnchorMenu"; import { FieldView, FieldViewProps } from './FieldView'; import "./WebBox.scss"; import React = require("react"); @@ -161,7 +161,7 @@ export class WebBox extends ViewBoxAnnotatableComponent v.forEach(a => a.remove())); this._savedAnnotations.clear(); - PDFMenu.Instance.fadeOut(true); + AnchorMenu.Instance.fadeOut(true); } }, { fireImmediately: true }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index c129d0204..f9982f747 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -64,6 +64,8 @@ import { SnappingManager } from '../../../util/SnappingManager'; import { LinkDocPreview } from '../LinkDocPreview'; import { SubCollectionViewProps } from '../../collections/CollectionSubView'; import { StyleProp } from '../../StyleProvider'; +import { AnchorMenu } from '../../pdf/AnchorMenu'; +import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; export interface FormattedTextBoxProps { makeLink?: () => Opt; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text @@ -252,6 +254,43 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp let unchanged = true; const effectiveAcl = GetEffectiveAcl(this.dataDoc); + if (!this._editorView.state.selection.empty) { + runInAction(() => { + AnchorMenu.Instance.Status = "marquee"; + AnchorMenu.Instance.Highlight = action((color: string) => { + this._editorView?.state && RichTextMenu.Instance.insertHighlight(color, this._editorView?.state, this._editorView?.dispatch); + return undefined; + }); + /** + * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation. + * It also initiates a Drag/Drop interaction to place the text annotation. + */ + AnchorMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => { + e.preventDefault(); + e.stopPropagation(); + const targetCreator = () => { + const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.rootDoc.title, 0, 0, 100, 100); + FormattedTextBox.SelectOnLoad = target[Id]; + return target; + } + + DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.rootDoc, () => this.rootDoc, targetCreator), e.pageX, e.pageY, { + dragComplete: e => { + if (!e.aborted && e.annoDragData && e.annoDragData.annotationDocument && e.annoDragData.dropDocument && !e.linkDocument) { + e.linkDocument = DocUtils.MakeLink({ doc: e.annoDragData.annotationDocument }, { doc: e.annoDragData.dropDocument }, "hyperlink", "link to note"); + e.annoDragData.annotationDocument.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.rootDoc; + } + e.linkDocument && e.annoDragData?.dropDocument && this.makeLinkToSelection(e.linkDocument[Id], "a link", "add:right", e.annoDragData.dropDocument[Id]); + e.linkDocument && e.annoDragData?.linkDropCallback?.(e as { linkDocument: Doc });// bcz: typescript can't figure out that this is valid even though we tested e.linkDocument + } + }); + }); + }); + const coordsT = this._editorView!.coordsAtPos(this._editorView!.state.selection.to); + const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to); + AnchorMenu.Instance.jumpTo(Math.min(coordsT.left, coordsB.left), Math.max(coordsT.bottom, coordsB.bottom)); + } + const removeSelection = (json: string | undefined) => { return json?.indexOf("\"storedMarks\"") === -1 ? json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\""); }; @@ -960,6 +999,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._disposers.selected = reaction(() => this.props.isSelected(), action((selected) => { this._recording = false; + AnchorMenu.Instance.fadeOut(true); if (RichTextMenu.Instance?.view === this._editorView && !selected) { RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 038a91aa3..8867595ff 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -255,7 +255,7 @@ export class FormattedTextBoxComment { docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => { if (linkDoc instanceof Doc) { (FormattedTextBoxComment.tooltipText as any).href = href; - FormattedTextBoxComment.linkDoc = DocListCast(textBox.props.Document.links).find(link => link.anchor1 === textBox.props.Document || link.anchor2 === textBox.props.Document ? link : undefined) || linkDoc; + FormattedTextBoxComment.linkDoc = linkDoc; const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc); const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor; if (anchor !== target && anchor && target) { diff --git a/src/client/views/pdf/AnchorMenu.scss b/src/client/views/pdf/AnchorMenu.scss new file mode 100644 index 000000000..b7afb26a5 --- /dev/null +++ b/src/client/views/pdf/AnchorMenu.scss @@ -0,0 +1,25 @@ +.anchorMenu-addTag { + display: grid; + width: 200px; + padding: 5px; + grid-template-columns: 90px 20px 90px; +} + +.color-wrapper { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + + button.color-button { + width: 20px; + height: 20px; + border-radius: 15px !important; + margin: 3px; + border: 2px solid transparent !important; + padding: 3px; + + &.active { + border: 2px solid white; + } + } +} \ No newline at end of file diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx new file mode 100644 index 000000000..e2bd5a73d --- /dev/null +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -0,0 +1,157 @@ +import React = require("react"); +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Tooltip } from "@material-ui/core"; +import { action, computed, observable } from "mobx"; +import { observer } from "mobx-react"; +import { ColorState } from "react-color"; +import { Doc, Opt } from "../../../fields/Doc"; +import { returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from "../../../Utils"; +import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu"; +import { ButtonDropdown } from "../nodes/formattedText/RichTextMenu"; +import "./AnchorMenu.scss"; + +@observer +export class AnchorMenu extends AntimodeMenu { + static Instance: AnchorMenu; + + private _commentCont = React.createRef(); + private _palette = [ + "rgba(208, 2, 27, 0.8)", + "rgba(238, 0, 0, 0.8)", + "rgba(245, 166, 35, 0.8)", + "rgba(248, 231, 28, 0.8)", + "rgba(245, 230, 95, 0.616)", + "rgba(139, 87, 42, 0.8)", + "rgba(126, 211, 33, 0.8)", + "rgba(65, 117, 5, 0.8)", + "rgba(144, 19, 254, 0.8)", + "rgba(238, 169, 184, 0.8)", + "rgba(224, 187, 228, 0.8)", + "rgba(225, 223, 211, 0.8)", + "rgba(255, 255, 255, 0.8)", + "rgba(155, 155, 155, 0.8)", + "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 public _colorBtn = false; + @observable public Highlighting: boolean = false; + @observable public Status: "marquee" | "annotation" | "" = ""; + + public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; + public Highlight: (color: string) => Opt = (color: string) => 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 Marquee: { left: number; top: number; width: number; height: number; } | undefined; + public get Active() { return this._left > 0; } + + constructor(props: Readonly<{}>) { + super(props); + + AnchorMenu.Instance = this; + AnchorMenu.Instance._canFade = false; + } + + pointerDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, (e: PointerEvent) => { + this.StartDrag(e, this._commentCont.current!); + return true; + }, returnFalse, returnFalse); + } + + @action + highlightClicked = (e: React.MouseEvent) => { + if (!this.Highlight(this.highlightColor) && this.Pinned) { + this.Highlighting = !this.Highlighting; + } + } + + @computed get highlighter() { + const button = + ; + + const dropdownContent = +
+

Change highlighter color:

+
+ {this._palette.map(color => { + if (color) { + return this.highlightColor === color ? + : + ; + } + })} +
+
; + return ( + {"Click to Highlight"}}> + + + ); + } + + @action changeHighlightColor = (color: string, e: React.PointerEvent) => { + const col: ColorState = { + hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: "" }, hsv: { a: 0, h: 0, s: 0, v: 0, source: "" }, + rgb: { a: 0, r: 0, b: 0, g: 0, source: "" }, oldHue: 0, source: "", + }; + e.preventDefault(); + e.stopPropagation(); + this.highlightColor = Utils.colorString(col); + } + + @action keyChanged = (e: React.ChangeEvent) => { this._keyValue = e.currentTarget.value; }; + @action valueChanged = (e: React.ChangeEvent) => { 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, + + {"Drag to Place Annotation"}}> + + , + ] : [ + {"Remove Link Anchor"}}> + + , + {"Pin to Presentation"}}> + + , + {"toggle pushpin behavior"}}> + + , + //
+ // + // + //
, + // , + ]; + + return this.getElement(buttons); + } +} \ No newline at end of file diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 5ef57f986..85dd65901 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -8,7 +8,7 @@ import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast } from "../. import { LinkManager } from "../../util/LinkManager"; import { undoBatch } from "../../util/UndoManager"; import "./Annotation.scss"; -import { PDFMenu } from "./PDFMenu"; +import { AnchorMenu } from "./AnchorMenu"; interface IAnnotationProps { anno: Doc; @@ -84,7 +84,7 @@ class RegionAnnotation extends React.Component { DocListCast(group.annotations).forEach(anno => anno.delete = true); } - PDFMenu.Instance.fadeOut(true); + AnchorMenu.Instance.fadeOut(true); } @undoBatch @@ -105,14 +105,14 @@ class RegionAnnotation extends React.Component { @action onPointerDown = (e: React.PointerEvent) => { if (e.button === 2 || e.ctrlKey) { - PDFMenu.Instance.Status = "annotation"; - PDFMenu.Instance.Delete = this.deleteAnnotation.bind(this); - PDFMenu.Instance.Pinned = false; - PDFMenu.Instance.AddTag = this.addTag.bind(this); - PDFMenu.Instance.PinToPres = this.pinToPres; - PDFMenu.Instance.MakePushpin = this.makePushpin; - PDFMenu.Instance.IsPushpin = this.isPushpin; - PDFMenu.Instance.jumpTo(e.clientX, e.clientY, true); + 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.jumpTo(e.clientX, e.clientY, true); e.stopPropagation(); } else if (e.button === 0) { diff --git a/src/client/views/pdf/PDFMenu.scss b/src/client/views/pdf/PDFMenu.scss deleted file mode 100644 index fa43a99b2..000000000 --- a/src/client/views/pdf/PDFMenu.scss +++ /dev/null @@ -1,25 +0,0 @@ -.pdfMenu-addTag { - display: grid; - width: 200px; - padding: 5px; - grid-template-columns: 90px 20px 90px; -} - -.color-wrapper { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - - button.color-button { - width: 20px; - height: 20px; - border-radius: 15px !important; - margin: 3px; - border: 2px solid transparent !important; - padding: 3px; - - &.active { - border: 2px solid white; - } - } -} \ No newline at end of file diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx deleted file mode 100644 index 603e26021..000000000 --- a/src/client/views/pdf/PDFMenu.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import React = require("react"); -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Tooltip } from "@material-ui/core"; -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import { ColorState } from "react-color"; -import { Doc, Opt } from "../../../fields/Doc"; -import { returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from "../../../Utils"; -import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu"; -import { ButtonDropdown } from "../nodes/formattedText/RichTextMenu"; -import "./PDFMenu.scss"; - -@observer -export class PDFMenu extends AntimodeMenu { - static Instance: PDFMenu; - - private _commentCont = React.createRef(); - private _palette = [ - "rgba(208, 2, 27, 0.8)", - "rgba(238, 0, 0, 0.8)", - "rgba(245, 166, 35, 0.8)", - "rgba(248, 231, 28, 0.8)", - "rgba(245, 230, 95, 0.616)", - "rgba(139, 87, 42, 0.8)", - "rgba(126, 211, 33, 0.8)", - "rgba(65, 117, 5, 0.8)", - "rgba(144, 19, 254, 0.8)", - "rgba(238, 169, 184, 0.8)", - "rgba(224, 187, 228, 0.8)", - "rgba(225, 223, 211, 0.8)", - "rgba(255, 255, 255, 0.8)", - "rgba(155, 155, 155, 0.8)", - "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 public _colorBtn = false; - @observable public Highlighting: boolean = false; - @observable public Status: "marquee" | "annotation" | "" = ""; - - public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; - public Highlight: (color: string) => Opt = (color: string) => 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 Marquee: { left: number; top: number; width: number; height: number; } | undefined; - public get Active() { return this._left > 0; } - - constructor(props: Readonly<{}>) { - super(props); - - PDFMenu.Instance = this; - PDFMenu.Instance._canFade = false; - } - - pointerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, (e: PointerEvent) => { - this.StartDrag(e, this._commentCont.current!); - return true; - }, returnFalse, returnFalse); - } - - @action - highlightClicked = (e: React.MouseEvent) => { - if (!this.Highlight(this.highlightColor) && this.Pinned) { - this.Highlighting = !this.Highlighting; - } - } - - @computed get highlighter() { - const button = - ; - - const dropdownContent = -
-

Change highlighter color:

-
- {this._palette.map(color => { - if (color) { - return this.highlightColor === color ? - : - ; - } - })} -
-
; - return ( - {"Click to Highlight"}}> - - - ); - } - - @action changeHighlightColor = (color: string, e: React.PointerEvent) => { - const col: ColorState = { - hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: "" }, hsv: { a: 0, h: 0, s: 0, v: 0, source: "" }, - rgb: { a: 0, r: 0, b: 0, g: 0, source: "" }, oldHue: 0, source: "", - }; - e.preventDefault(); - e.stopPropagation(); - this.highlightColor = Utils.colorString(col); - } - - @action keyChanged = (e: React.ChangeEvent) => { this._keyValue = e.currentTarget.value; }; - @action valueChanged = (e: React.ChangeEvent) => { 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, - - {"Drag to Place Annotation"}}> - - , - ] : [ - {"Remove Link Anchor"}}> - - , - {"Pin to Presentation"}}> - - , - {"toggle pushpin behavior"}}> - - , - //
- // - // - //
, - // , - ]; - - return this.getElement(buttons); - } -} \ No newline at end of file diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index f9139220b..f2052d454 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -26,7 +26,7 @@ import { FieldViewProps } from "../nodes/FieldView"; import { FormattedTextBoxComment } from "../nodes/formattedText/FormattedTextBoxComment"; import { LinkDocPreview } from "../nodes/LinkDocPreview"; import { Annotation } from "./Annotation"; -import { PDFMenu } from "./PDFMenu"; +import { AnchorMenu } from "./AnchorMenu"; import "./PDFViewer.scss"; const pdfjs = require('pdfjs-dist/es5/build/pdf.js'); import React = require("react"); @@ -137,7 +137,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent v.forEach(a => a.remove())); this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, [])); - PDFMenu.Instance.fadeOut(true); + AnchorMenu.Instance.fadeOut(true); } (SelectionManager.Views().length === 1) && this.setupPdfJsViewer(); }, @@ -384,30 +384,32 @@ export class PDFViewer extends ViewBoxAnnotatableComponent v.forEach(a => a.remove())); this._savedAnnotations.clear(); this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, "pdfAnnotation", { "pointer-events": "none" }); - document.addEventListener("pointermove", this.onSelectMove); document.addEventListener("pointerup", this.onSelectEnd); } + document.addEventListener("pointermove", this.onSelectMove); } } @action finishMarquee = () => { this._marqueeing = undefined; + document.removeEventListener("pointermove", this.onSelectMove); this.props.select(false); } @action onSelectMove = (e: PointerEvent): void => { - if (e.target && (e.target as any).parentElement === this._mainCont.current) e.stopPropagation(); + // if (e.target && (e.target as any).parentElement === this._mainCont.current) + e.stopPropagation(); } @action @@ -421,7 +423,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent