diff options
| author | bobzel <zzzman@gmail.com> | 2022-07-08 00:17:26 -0400 |
|---|---|---|
| committer | bobzel <zzzman@gmail.com> | 2022-07-08 00:17:26 -0400 |
| commit | 146f8622d5bac2edc6b09f57c173bd057dfbcfad (patch) | |
| tree | f871089c438a476543ca96bac163c0532b9557c7 /src/client/views/MarqueeAnnotator.tsx | |
| parent | b7e66da6b23cdb41c127000dfe13843d35f7d0cc (diff) | |
restructured currentUserUtils to avoid having import cycles.
Diffstat (limited to 'src/client/views/MarqueeAnnotator.tsx')
| -rw-r--r-- | src/client/views/MarqueeAnnotator.tsx | 226 |
1 files changed, 118 insertions, 108 deletions
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 20b99788c..b01ee5f42 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -1,21 +1,20 @@ -import { action, observable, ObservableMap, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { AclAugment, AclAdmin, AclEdit, DataSym, Doc, Opt, AclSelfEdit } from "../../fields/Doc"; -import { Id } from "../../fields/FieldSymbols"; -import { List } from "../../fields/List"; -import { NumCast } from "../../fields/Types"; -import { GetEffectiveAcl } from "../../fields/util"; -import { unimplementedFunction, Utils } from "../../Utils"; -import { Docs } from "../documents/Documents"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import { DragManager } from "../util/DragManager"; -import { undoBatch } from "../util/UndoManager"; -import "./MarqueeAnnotator.scss"; -import { DocumentView } from "./nodes/DocumentView"; -import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox"; -import { AnchorMenu } from "./pdf/AnchorMenu"; -import React = require("react"); -const _global = (window /* browser */ || global /* node */) as any; +import { action, observable, ObservableMap, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, Opt } from '../../fields/Doc'; +import { Id } from '../../fields/FieldSymbols'; +import { List } from '../../fields/List'; +import { NumCast } from '../../fields/Types'; +import { GetEffectiveAcl } from '../../fields/util'; +import { unimplementedFunction, Utils } from '../../Utils'; +import { Docs, DocUtils } from '../documents/Documents'; +import { DragManager } from '../util/DragManager'; +import { undoBatch } from '../util/UndoManager'; +import './MarqueeAnnotator.scss'; +import { DocumentView } from './nodes/DocumentView'; +import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; +import { AnchorMenu } from './pdf/AnchorMenu'; +import React = require('react'); +const _global = (window /* browser */ || global) /* node */ as any; export interface MarqueeAnnotatorProps { rootDoc: Doc; @@ -46,7 +45,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { @action static clearAnnotations(savedAnnotations: ObservableMap<number, HTMLDivElement[]>) { - AnchorMenu.Instance.Status = "marquee"; + AnchorMenu.Instance.Status = 'marquee'; AnchorMenu.Instance.fadeOut(true); // clear out old marquees and initialize menu for new selection Array.from(savedAnnotations.values()).forEach(v => v.forEach(a => a.remove())); @@ -60,30 +59,30 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { 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; - const doc = (this.props.iframe?.()?.contentDocument ?? document); - doc.addEventListener("pointermove", this.onSelectMove); - doc.addEventListener("pointerup", this.onSelectEnd); + const doc = this.props.iframe?.()?.contentDocument ?? document; + doc.addEventListener('pointermove', this.onSelectMove); + doc.addEventListener('pointerup', this.onSelectEnd); - AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight("rgba(173, 216, 230, 0.75)", true), true); - AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight("rgba(173, 216, 230, 0.75)", true)); + AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight('rgba(173, 216, 230, 0.75)', true), true); + AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight('rgba(173, 216, 230, 0.75)', true)); AnchorMenu.Instance.Highlight = this.highlight; - AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => this.highlight("rgba(173, 216, 230, 0.75)", true, savedAnnotations); + AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations); AnchorMenu.Instance.onMakeAnchor = AnchorMenu.Instance.GetAnchor; /** - * This function is used by the AnchorMenu 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. */ AnchorMenu.Instance.StartDrag = action((e: PointerEvent, ele: HTMLElement) => { e.preventDefault(); e.stopPropagation(); const sourceAnchorCreator = () => { - const annoDoc = this.highlight("rgba(173, 216, 230, 0.75)", true); // hyperlink color + const annoDoc = this.highlight('rgba(173, 216, 230, 0.75)', true); // hyperlink color annoDoc && this.props.addDocument(annoDoc); return annoDoc; }; const targetCreator = (annotationOn: Doc | undefined) => { - const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, "yellow"); + const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow'); FormattedTextBox.SelectOnLoad = target[Id]; return target; }; @@ -92,37 +91,39 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) { e.annoDragData.linkSourceDoc.isPushpin = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc; } - } + }, }); }); /** - * This function is used by the AnchorMenu 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. */ - AnchorMenu.Instance.StartCropDrag = !this.props.anchorMenuCrop ? unimplementedFunction : action((e: PointerEvent, ele: HTMLElement) => { - e.preventDefault(); - e.stopPropagation(); - var cropRegion: Doc | undefined; - const sourceAnchorCreator = () => { - cropRegion = this.highlight("rgba(173, 216, 230, 0.75)", true); // hyperlink color - cropRegion && this.props.addDocument(cropRegion); - return cropRegion; - }; - const targetCreator = (annotationOn: Doc | undefined) => this.props.anchorMenuCrop!(cropRegion, false)!; - DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, { - dragComplete: e => { - if (!e.aborted && e.linkDocument) { - Doc.GetProto(e.linkDocument).linkRelationship = "cropped image"; - Doc.GetProto(e.linkDocument).title = "crop: " + this.props.docView.rootDoc.title; - } - } - }); - }); + AnchorMenu.Instance.StartCropDrag = !this.props.anchorMenuCrop + ? unimplementedFunction + : action((e: PointerEvent, ele: HTMLElement) => { + e.preventDefault(); + e.stopPropagation(); + var cropRegion: Doc | undefined; + const sourceAnchorCreator = () => { + cropRegion = this.highlight('rgba(173, 216, 230, 0.75)', true); // hyperlink color + cropRegion && this.props.addDocument(cropRegion); + return cropRegion; + }; + const targetCreator = (annotationOn: Doc | undefined) => this.props.anchorMenuCrop!(cropRegion, false)!; + DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, { + dragComplete: e => { + if (!e.aborted && e.linkDocument) { + Doc.GetProto(e.linkDocument).linkRelationship = 'cropped image'; + Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.docView.rootDoc.title; + } + }, + }); + }); } componentWillUnmount() { - const doc = (this.props.iframe?.()?.contentDocument ?? document); - doc.removeEventListener("pointermove", this.onSelectMove); - doc.removeEventListener("pointerup", this.onSelectEnd); + const doc = this.props.iframe?.()?.contentDocument ?? document; + doc.removeEventListener('pointermove', this.onSelectMove); + doc.removeEventListener('pointerup', this.onSelectEnd); } @undoBatch @@ -132,40 +133,42 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { if (savedAnnoMap.size === 0) return undefined; const savedAnnos = Array.from(savedAnnoMap.values())[0]; if (savedAnnos.length && (savedAnnos[0] as any).marqueeing) { - const scale = (this.props.scaling?.() || 1); + const scale = this.props.scaling?.() || 1; const anno = savedAnnos[0]; const containerOffset = this.props.containerOffset?.() || [0, 0]; - const marqueeAnno = Docs.Create.FreeformDocument([], { _isLinkButton: isLinkButton, backgroundColor: color, annotationOn: this.props.rootDoc, title: "Annotation on " + this.props.rootDoc.title }); - marqueeAnno.x = NumCast(this.props.docView.props.Document.panXMin) + (parseInt(anno.style.left || "0") - containerOffset[0]) / scale/ NumCast(this.props.docView.props.Document._viewScale,1); - marqueeAnno.y = NumCast(this.props.docView.props.Document.panYMin) + (parseInt(anno.style.top || "0") - containerOffset[1]) / scale/ NumCast(this.props.docView.props.Document._viewScale,1) + NumCast(this.props.scrollTop); - marqueeAnno._height = parseInt(anno.style.height || "0") / scale/ NumCast(this.props.docView.props.Document._viewScale,1); - marqueeAnno._width = parseInt(anno.style.width || "0") / scale/ NumCast(this.props.docView.props.Document._viewScale,1); + const marqueeAnno = Docs.Create.FreeformDocument([], { _isLinkButton: isLinkButton, backgroundColor: color, annotationOn: this.props.rootDoc, title: 'Annotation on ' + this.props.rootDoc.title }); + marqueeAnno.x = NumCast(this.props.docView.props.Document.panXMin) + (parseInt(anno.style.left || '0') - containerOffset[0]) / scale / NumCast(this.props.docView.props.Document._viewScale, 1); + marqueeAnno.y = NumCast(this.props.docView.props.Document.panYMin) + (parseInt(anno.style.top || '0') - containerOffset[1]) / scale / NumCast(this.props.docView.props.Document._viewScale, 1) + NumCast(this.props.scrollTop); + marqueeAnno._height = parseInt(anno.style.height || '0') / scale / NumCast(this.props.docView.props.Document._viewScale, 1); + marqueeAnno._width = parseInt(anno.style.width || '0') / scale / NumCast(this.props.docView.props.Document._viewScale, 1); anno.remove(); savedAnnoMap.clear(); return marqueeAnno; } - const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, backgroundColor: "transparent", title: "Selection on " + this.props.rootDoc.title }); + const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, backgroundColor: 'transparent', title: 'Selection on ' + this.props.rootDoc.title }); let minX = Number.MAX_VALUE; let maxX = -Number.MAX_VALUE; let minY = Number.MAX_VALUE; let maxY = -Number.MIN_VALUE; const annoDocs: Doc[] = []; - savedAnnoMap.forEach((value: HTMLDivElement[], key: number) => value.map(anno => { - const textRegion = new Doc(); - textRegion.x = parseInt(anno.style.left ?? "0"); - textRegion.y = parseInt(anno.style.top ?? "0"); - textRegion._height = parseInt(anno.style.height ?? "0"); - textRegion._width = parseInt(anno.style.width ?? "0"); - textRegion.annoTextRegion = textRegionAnno; - textRegion.backgroundColor = color; - annoDocs.push(textRegion); - anno.remove(); - minY = Math.min(NumCast(textRegion.y), minY); - minX = Math.min(NumCast(textRegion.x), minX); - maxY = Math.max(NumCast(textRegion.y) + NumCast(textRegion._height), maxY); - maxX = Math.max(NumCast(textRegion.x) + NumCast(textRegion._width), maxX); - })); + savedAnnoMap.forEach((value: HTMLDivElement[], key: number) => + value.map(anno => { + const textRegion = new Doc(); + textRegion.x = parseInt(anno.style.left ?? '0'); + textRegion.y = parseInt(anno.style.top ?? '0'); + textRegion._height = parseInt(anno.style.height ?? '0'); + textRegion._width = parseInt(anno.style.width ?? '0'); + textRegion.annoTextRegion = textRegionAnno; + textRegion.backgroundColor = color; + annoDocs.push(textRegion); + anno.remove(); + minY = Math.min(NumCast(textRegion.y), minY); + minX = Math.min(NumCast(textRegion.x), minX); + maxY = Math.max(NumCast(textRegion.y) + NumCast(textRegion._height), maxY); + maxX = Math.max(NumCast(textRegion.x) + NumCast(textRegion._width), maxX); + }) + ); const textRegionAnnoProto = Doc.GetProto(textRegionAnno); textRegionAnnoProto.y = Math.max(minY, 0); @@ -176,29 +179,29 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { textRegionAnnoProto.textInlineAnnotations = new List<Doc>(annoDocs); savedAnnoMap.clear(); return textRegionAnno; - } + }; @action highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => { // creates annotation documents for current highlights const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]); const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations); !savedAnnotations && annotationDoc && this.props.addDocument(annotationDoc); - return annotationDoc as Doc ?? undefined; - } + return (annotationDoc as Doc) ?? undefined; + }; public static previewNewAnnotation = action((savedAnnotations: ObservableMap<number, HTMLDivElement[]>, annotationLayer: HTMLDivElement, div: HTMLDivElement, page: number) => { if (div.style.top) { - div.style.top = (parseInt(div.style.top)/*+ this.getScrollFromPage(page)*/).toString(); + div.style.top = parseInt(div.style.top) /*+ this.getScrollFromPage(page)*/ + .toString(); } annotationLayer.append(div); - div.style.backgroundColor = "#ACCEF7"; - div.style.opacity = "0.5"; + div.style.backgroundColor = '#ACCEF7'; + div.style.opacity = '0.5'; const savedPage = savedAnnotations.get(page); if (savedPage) { savedPage.push(div); savedAnnotations.set(page, savedPage); - } - else { + } else { savedAnnotations.set(page, [div]); } }); @@ -210,58 +213,65 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { const mainRect = this.props.mainCont.getBoundingClientRect(); const cliX = e.clientX * (this.props.iframeScaling?.() || 1) - boundingRect.left; const cliY = e.clientY * (this.props.iframeScaling?.() || 1) - boundingRect.top; - this._width = (cliX * (this.props.mainCont.offsetWidth / mainRect.width)) - this._startX; - this._height = (cliY * (this.props.mainCont.offsetHeight / mainRect.height)) - this._startY + this.props.mainCont.scrollTop; + this._width = cliX * (this.props.mainCont.offsetWidth / mainRect.width) - this._startX; + this._height = cliY * (this.props.mainCont.offsetHeight / mainRect.height) - this._startY + this.props.mainCont.scrollTop; this._left = Math.min(this._startX, this._startX + this._width); this._top = Math.min(this._startY, this._startY + this._height); this._width = Math.abs(this._width); this._height = Math.abs(this._height); e.stopPropagation(); - } + }; onSelectEnd = (e: PointerEvent) => { const mainRect = this.props.mainCont.getBoundingClientRect(); const cliX = e.clientX * (this.props.iframeScaling?.() || 1) + (this.props.iframe ? mainRect.left : 0); const cliY = e.clientY * (this.props.iframeScaling?.() || 1) + (this.props.iframe ? mainRect.top : 0); - 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"); - ["border", "opacity"].forEach(prop => copy.style[prop as any] = (marquees[0] as HTMLDivElement).style[prop as any]); + 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'); + ['border', 'opacity'].forEach(prop => (copy.style[prop as any] = (marquees[0] as HTMLDivElement).style[prop as any])); const bounds = (marquees[0] as HTMLDivElement).getBoundingClientRect(); const uitls = Utils.GetScreenTransform(marquees[0] as HTMLDivElement); - const rbounds = { top: uitls.translateY, left: uitls.translateX, width: (bounds.right - bounds.left), height: (bounds.bottom - bounds.top) }; + const rbounds = { top: uitls.translateY, left: uitls.translateX, width: bounds.right - bounds.left, height: bounds.bottom - bounds.top }; const otls = Utils.GetScreenTransform(this.props.annotationLayer); const fbounds = { top: (rbounds.top - otls.translateY) / otls.scale, left: (rbounds.left - otls.translateX) / otls.scale, width: rbounds.width / otls.scale, height: rbounds.height / otls.scale }; - copy.style.top = fbounds.top.toString() + "px"; - copy.style.left = fbounds.left.toString() + "px"; - copy.style.width = fbounds.width.toString() + "px"; - copy.style.height = fbounds.height.toString() + "px"; - copy.className = "marqueeAnnotator-annotationBox"; + copy.style.top = fbounds.top.toString() + 'px'; + copy.style.left = fbounds.left.toString() + 'px'; + copy.style.width = fbounds.width.toString() + 'px'; + copy.style.height = fbounds.height.toString() + 'px'; + copy.className = 'marqueeAnnotator-annotationBox'; (copy as any).marqueeing = true; MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations(), this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this._top) || 0); } AnchorMenu.Instance.jumpTo(cliX, cliY); - 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)", false); // yellowish highlight color for highlighted text (should match AnchorMenu'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)', false); // yellowish highlight color for highlighted text (should match AnchorMenu's highlight color) } this.props.finishMarquee(undefined, undefined, e); } else { - runInAction(() => this._width = this._height = 0); + runInAction(() => (this._width = this._height = 0)); this.props.finishMarquee(cliX, cliY, e); } - } + }; render() { - return <div className="marqueeAnnotator-dragBox" - style={{ - left: `${this._left}px`, top: `${this._top}px`, - width: `${this._width}px`, height: `${this._height}px`, - border: `${this._width === 0 ? "" : "2px dashed black"}`, - opacity: 0.2 - }}> - </div>; + return ( + <div + className="marqueeAnnotator-dragBox" + style={{ + left: `${this._left}px`, + top: `${this._top}px`, + width: `${this._width}px`, + height: `${this._height}px`, + border: `${this._width === 0 ? '' : '2px dashed black'}`, + opacity: 0.2, + }}></div> + ); } } |
