aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/MarqueeAnnotator.tsx
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2022-07-08 00:17:26 -0400
committerbobzel <zzzman@gmail.com>2022-07-08 00:17:26 -0400
commit146f8622d5bac2edc6b09f57c173bd057dfbcfad (patch)
treef871089c438a476543ca96bac163c0532b9557c7 /src/client/views/MarqueeAnnotator.tsx
parentb7e66da6b23cdb41c127000dfe13843d35f7d0cc (diff)
restructured currentUserUtils to avoid having import cycles.
Diffstat (limited to 'src/client/views/MarqueeAnnotator.tsx')
-rw-r--r--src/client/views/MarqueeAnnotator.tsx226
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>
+ );
}
}