aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/DocumentView.tsx13
-rw-r--r--src/client/views/nodes/FontIconBox.tsx10
-rw-r--r--src/client/views/nodes/ImageBox.tsx84
-rw-r--r--src/client/views/nodes/PDFBox.tsx2
-rw-r--r--src/client/views/nodes/PresBox.tsx2
-rw-r--r--src/client/views/nodes/VideoBox.tsx9
-rw-r--r--src/client/views/nodes/WebBox.tsx414
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx23
8 files changed, 225 insertions, 332 deletions
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c7b2f2df6..358446a57 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -481,7 +481,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
} else {
- this.props.select(e.ctrlKey || e.shiftKey);
+ const ctrlPressed = e.ctrlKey || e.shiftKey;
+ if (this.props.Document.type === DocumentType.WEB) {
+ this._timeout = setTimeout(() => { this._timeout = undefined; this.props.select(ctrlPressed); }, 350);
+ } else this.props.select(ctrlPressed);
}
preventDefault = false;
}
@@ -593,7 +596,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
@undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document);
@undoBatch toggleDetail = () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`);
- @undoBatch toggleLockPosition = () => this.Document._lockedPosition = this.Document._lockedPosition ? undefined : true;
@undoBatch @action
drop = async (e: Event, de: DragManager.DropEvent) => {
@@ -688,7 +690,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (!this.Document.annotationOn) {
const options = cm.findByDescription("Options...");
const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
- this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform && optionItems.push({ description: this.Document._lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document._lockedPosition) ? "unlock" : "lock" });
!options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
@@ -720,7 +721,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
(this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.UserDoc().noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: "users" });
if (!Doc.UserDoc().noviceMode) {
moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
- moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
+ moreItems.push({ description: `${this.Document._chromeStatus ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus ? undefined : "enabled"), icon: "project-diagram" });
if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
@@ -1114,8 +1115,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
position: this.props.Document.isInkMask ? "absolute" : undefined,
transform: `translate(${this.centeringX}px, ${this.centeringY}px)`,
width: xshift ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
- height: yshift ?? this.props.Document._fitWidth ? `${this.panelHeight}px` :
- `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`,
+ height: yshift ?? (this.props.Document._fitWidth ? `${this.panelHeight}px` :
+ `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`),
}}>
<DocumentViewInternal {...this.props}
DocumentView={this.selfView}
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index 56c79cde9..6ae4b9726 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -49,14 +49,12 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
const presTrailsIcon = <img src={`/assets/${"presTrails.png"}`}
style={{ width: presSize, height: presSize, filter: `invert(${color === "white" ? "100%" : "0%"})`, marginBottom: "5px" }} />;
const button = <button className={`menuButton-${shape}`} onContextMenu={this.specificContextMenu}
- style={{
- backgroundColor: this.layoutDoc.iconShape === "square" ? backgroundColor : "",
- }}>
+ style={{ backgroundColor: backgroundColor, }}>
<div className="menuButton-wrap">
{icon === 'pres-trail' ? presTrailsIcon : <FontAwesomeIcon className={`menuButton-icon-${shape}`} icon={icon} color={color}
size={this.layoutDoc.iconShape === "square" ? "sm" : "sm"} />}
{!label ? (null) : <div className="fontIconBox-label" style={{ color, backgroundColor }}> {label} </div>}
- {this.props.Document.watchedDocuments ? <FontIconBadge collection={Cast(this.props.Document.watchedDocuments, Doc, null)} /> : (null)}
+ <FontIconBadge collection={Cast(this.rootDoc.watchedDocuments, Doc, null)} />
</div>
</button>;
return !this.layoutDoc.toolTip ? button :
@@ -67,7 +65,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
}
interface FontIconBadgeProps {
- collection: Doc;
+ collection: Doc | undefined;
}
@observer
@@ -77,7 +75,7 @@ export class FontIconBadge extends React.Component<FontIconBadgeProps> {
onPointerDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e,
(e: PointerEvent) => {
- const dragData = new DragManager.DocumentDragData([this.props.collection]);
+ const dragData = new DragManager.DocumentDragData([this.props.collection!]);
DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y);
return true;
},
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index bc01acdfd..9426f6afc 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, ObservableMap, untracked } from 'mobx';
import { observer } from "mobx-react";
import { Dictionary } from 'typescript-collections';
import { DataSym, Doc, DocListCast, WidthSym } from '../../../fields/Doc';
@@ -50,10 +50,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
- private _curSuffix = "_m";
private _dropDisposer?: DragManager.DragDropDisposer;
private _disposers: { [name: string]: IReactionDisposer } = {};
- @observable uploadIcon = uploadIcons.idle;
+ @observable _curSuffix = "";
+ @observable _uploadIcon = uploadIcons.idle;
protected createDropTarget = (ele: HTMLDivElement) => {
this._dropDisposer?.();
@@ -61,17 +61,25 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
}
componentDidMount() {
+ this._disposers.sizer = reaction(() => (
+ {
+ forceFull: this.props.renderDepth < 1 || this.layoutDoc._showFullRes,
+ scrSize: this.props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0],
+ selected: this.props.isSelected()
+ }),
+ ({ forceFull, scrSize, selected }) => this._curSuffix = forceFull ? "_o" : scrSize < 100 ? "_s" : scrSize < 400 ? "_m" : scrSize < 800 || !selected ? "_l" : "_o",
+ { fireImmediately: true, delay: 1000 });
this._disposers.selection = reaction(() => this.props.isSelected(),
selected => !selected && setTimeout(() => {
- this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
+ Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
this._savedAnnotations.clear();
}));
this._disposers.path = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }),
- action(({ nativeSize, width }) => {
+ ({ nativeSize, width }) => {
if (!this.layoutDoc._height) {
this.layoutDoc._height = width * nativeSize.nativeHeight / nativeSize.nativeWidth;
}
- }),
+ },
{ fireImmediately: true });
}
@@ -176,32 +184,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return url.href; //Why is this here
const ext = path.extname(url.href);
- const scrSize = this.props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight);
- this._curSuffix = this.props.renderDepth < 1 || this.layoutDoc._showFullRes ? "_o" : scrSize[0] < 100 ? "_s" : scrSize[0] < 400 ? "_m" : scrSize[0] < 800 || !this.props.isSelected() ? "_l" : "_o";
return url.href.replace(ext, this._curSuffix + ext);
}
- @observable _smallRetryCount = 1;
- @observable _mediumRetryCount = 1;
- @observable _largeRetryCount = 1;
- @action retryPath = () => {
- if (this._curSuffix === "_s") this._smallRetryCount++;
- if (this._curSuffix === "_m") this._mediumRetryCount++;
- if (this._curSuffix === "_l") this._largeRetryCount++;
- }
-
- @action onError = (error: any) => {
- const timeout = this._curSuffix === "_s" ? this._smallRetryCount : this._curSuffix === "_m" ? this._mediumRetryCount : this._largeRetryCount;
- if (timeout < 5) {
- setTimeout(this.retryPath, 500);
- } else {
- const original = StrCast(this.dataDoc[this.fieldKey + "-originalUrl"]);
- if (error.type === "error" && original) {
- this.dataDoc[this.fieldKey] = new ImageField(original);
- }
- }
- }
-
considerGooglePhotosLink = () => {
const remoteUrl = this.dataDoc.googlePhotosUrl;
return !remoteUrl ? (null) : (<img draggable={false}
@@ -231,11 +216,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
<img
id={"upload-icon"} draggable={false}
style={{ transformOrigin: "bottom right" }}
- src={`/assets/${this.uploadIcon}`}
+ src={`/assets/${this._uploadIcon}`}
onClick={async () => {
const { dataDoc } = this;
const { success, failure, idle, loading } = uploadIcons;
- runInAction(() => this.uploadIcon = loading);
+ runInAction(() => this._uploadIcon = loading);
const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [primary] });
dataDoc[this.props.fieldKey + "-originalUrl"] = primary;
let succeeded = true;
@@ -245,9 +230,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
} catch {
succeeded = false;
}
- runInAction(() => this.uploadIcon = succeeded ? success : failure);
+ runInAction(() => this._uploadIcon = succeeded ? success : failure);
setTimeout(action(() => {
- this.uploadIcon = idle;
+ this._uploadIcon = idle;
if (data) {
dataDoc[this.fieldKey] = data;
}
@@ -265,11 +250,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
return { nativeWidth, nativeHeight, nativeOrientation };
}
- // this._curSuffix = "";
- // if (w > 20) {
- // if (w < 100 && this._smallRetryCount < 10) this._curSuffix = "_s";
- // else if (w < 600 && this._mediumRetryCount < 10) this._curSuffix = "_m";
- // else if (this._largeRetryCount < 10) this._curSuffix = "_l";
@computed get paths() {
const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
const alts = DocListCast(this.dataDoc[this.fieldKey + "-alternates"]); // retrieve alternate documents that may be rendered as alternate images
@@ -300,20 +280,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
return <div className="imageBox-cont" key={this.layoutDoc[Id]} ref={this.createDropTarget} onPointerDown={this.marqueeDown}>
<div className="imageBox-fader" >
- <img key={this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
+ <img key="paths" ref={this._imgRef}
src={srcpath}
style={{ transform, transformOrigin }} draggable={false}
- width={nativeWidth}
- ref={this._imgRef}
- onError={this.onError} />
+ width={nativeWidth} />
{fadepath === srcpath ? (null) : <div className="imageBox-fadeBlocker">
- <img className="imageBox-fadeaway"
- key={"fadeaway" + this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
+ <img className="imageBox-fadeaway" key={"fadeaway"} ref={this._imgRef}
src={fadepath}
style={{ transform, transformOrigin }} draggable={false}
- width={nativeWidth}
- ref={this._imgRef}
- onError={this.onError} /></div>}
+ width={nativeWidth} />
+ </div>}
</div>
{this.considerDownloadIcon}
{this.considerGooglePhotosLink()}
@@ -337,7 +313,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
@observable _marqueeing: number[] | undefined;
- @observable _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
+ @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@computed get annotationLayer() {
return <div className="imageBox-annotationLayer" style={{ height: this.props.PanelHeight() }} ref={this._annotationLayer} />;
}
@@ -388,7 +364,17 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
</CollectionFreeFormView>
{this.annotationLayer}
{!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
- <MarqueeAnnotator rootDoc={this.rootDoc} scrollTop={0} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocument} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
+ <MarqueeAnnotator rootDoc={this.rootDoc}
+ scrollTop={0} down={this._marqueeing}
+ scaling={this.props.scaling}
+ docView={this.props.docViewPath().lastElement()}
+ addDocument={this.addDocument}
+ finishMarquee={this.finishMarquee}
+ savedAnnotations={this._savedAnnotations}
+ annotationLayer={this._annotationLayer.current}
+ mainCont={this._mainCont.current}
+ />}
</div >);
}
+
}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index e4aa639ff..0dbe0c917 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -231,7 +231,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
} else {
this.layoutDoc.nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
}
- this.layoutDoc._width = NumCast(this.layoutDoc._nativeWidth) * (NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) / NumCast(this.layoutDoc[this.fieldKey + "-nativeHeight"]))
+ this.layoutDoc._width = NumCast(this.layoutDoc._nativeWidth) * (NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) / NumCast(this.layoutDoc[this.fieldKey + "-nativeHeight"]));
}
settingsPanel() {
const pageBtns = <>
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 844afe1e2..682ec5356 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -2262,7 +2262,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
const isMini: boolean = this.toolbarWidth <= 100;
return (
- <div className="presBox-buttons" style={{ display: this.rootDoc._chromeStatus === "disabled" ? "none" : undefined }}>
+ <div className="presBox-buttons" style={{ display: !this.rootDoc._chromeStatus ? "none" : undefined }}>
{isMini ? (null) : <select className="presBox-viewPicker"
style={{ display: this.layoutDoc.presStatus === "edit" ? "block" : "none" }}
onPointerDown={e => e.stopPropagation()}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 3e1edb927..575fbcf2e 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,6 +1,6 @@
import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked, ObservableMap } from "mobx";
import { observer } from "mobx-react";
import * as rp from 'request-promise';
import { Dictionary } from "typescript-collections";
@@ -51,7 +51,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
private _playRegionDuration = 0;
@observable static _showControls: boolean;
@observable _marqueeing: number[] | undefined;
- @observable _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
+ @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@observable _screenCapture = false;
@observable _clicking = false;
@observable _forceCreateYouTubeIFrame = false;
@@ -206,7 +206,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
this._disposers.selection = reaction(() => this.props.isSelected(),
selected => !selected && setTimeout(() => {
- this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
+ Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
this._savedAnnotations.clear();
}));
this._disposers.triggerVideo = reaction(
@@ -345,7 +345,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
this._disposers.youtubeReactionDisposer?.();
this._disposers.reactionDisposer = reaction(() => this.layoutDoc._currentTimecode, () => !this._playing && this.Seek((this.layoutDoc._currentTimecode || 0)));
this._disposers.youtubeReactionDisposer = reaction(
- () => !this.props.Document.isAnnotating && CurrentUserUtils.SelectedTool === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting,
+ () => CurrentUserUtils.SelectedTool === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting,
(interactive) => iframe.style.pointerEvents = interactive ? "all" : "none", { fireImmediately: true });
};
if (typeof (YT) === undefined) setTimeout(() => this.loadYouTube(iframe), 100);
@@ -575,6 +575,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
scrollTop={0}
rootDoc={this.rootDoc}
down={this._marqueeing}
+ docView={this.props.docViewPath().lastElement()}
scaling={this.marqueeFitScaling}
containerOffset={this.marqueeOffset}
addDocument={this.addDocWithTimecode}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 6127f82e3..f15a249da 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,10 +1,8 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from '@material-ui/core';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Dictionary } from "typescript-collections";
import * as WebRequest from 'web-request';
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, StrListCast } from "../../../fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
import { Id } from "../../../fields/FieldSymbols";
import { HtmlField } from "../../../fields/HtmlField";
@@ -14,29 +12,30 @@ import { listSpec, makeInterface } from "../../../fields/Schema";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { WebField } from "../../../fields/URLField";
import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, OmitKeys, returnOne, smoothScroll, Utils, returnZero, returnTrue } from "../../../Utils";
+import { emptyFunction, getWordAtPoint, OmitKeys, returnOne, returnTrue, returnZero, smoothScroll, Utils } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
-import { DragManager } from "../../util/DragManager";
-import { ImageUtils } from "../../util/Import & Export/ImageUtils";
+import { DocumentType } from '../../documents/DocumentTypes';
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { SnappingManager } from "../../util/SnappingManager";
import { undoBatch } from "../../util/UndoManager";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { CollectionStackingView } from "../collections/CollectionStackingView";
+import { CollectionViewType } from "../collections/CollectionView";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { DocumentDecorations } from "../DocumentDecorations";
+import { LightboxView } from "../LightboxView";
import { MarqueeAnnotator } from "../MarqueeAnnotator";
+import { AnchorMenu } from "../pdf/AnchorMenu";
import { Annotation } from "../pdf/Annotation";
+import { SearchBox } from "../search/SearchBox";
+import { StyleProp } from "../StyleProvider";
import { FieldView, FieldViewProps } from './FieldView';
+import { FormattedTextBox } from "./formattedText/FormattedTextBox";
import { LinkDocPreview } from "./LinkDocPreview";
import "./WebBox.scss";
-import { DocumentType } from '../../documents/DocumentTypes';
import React = require("react");
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { SearchBox } from "../search/SearchBox";
-import { CollectionStackingView } from "../collections/CollectionStackingView";
-import { StyleProp } from "../StyleProvider";
-import { FormattedTextBox } from "./formattedText/FormattedTextBox";
-import { CollectionViewType } from "../collections/CollectionView";
const htmlToText = require("html-to-text");
type WebDocument = makeInterface<[typeof documentSchema]>;
@@ -46,24 +45,22 @@ const WebDocument = makeInterface(documentSchema);
export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+ private _outerRef: React.RefObject<HTMLDivElement> = React.createRef();
private _disposers: { [name: string]: IReactionDisposer } = {};
- private _longPressSecondsHack?: NodeJS.Timeout;
- private _outerRef = React.createRef<HTMLDivElement>();
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
- private _iframeIndicatorRef = React.createRef<HTMLDivElement>();
- private _iframeDragRef = React.createRef<HTMLDivElement>();
private _keyInput = React.createRef<HTMLInputElement>();
- private _ignoreScroll = "";
- private _scrollTimer: any;
+ @observable _scrollTimer: any;
+ @observable private _overlayAnnoInfo: Opt<Doc>;
private _initialScroll: Opt<number>;
+ private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
@observable private _marqueeing: number[] | undefined;
@observable private _url: string = "hello";
- @observable private _pressX: number = 0;
- @observable private _pressY: number = 0;
+ @observable private _isAnnotating = false;
@observable private _iframe: HTMLIFrameElement | null = null;
- @observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
- get scrollHeight() { return this.webpage?.scrollHeight || 1000; }
- get webpage() { return this._iframe?.contentDocument?.children[0]; }
+ @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
+ @observable private _scrollHeight = 1500;
+ @computed get scrollHeight() { return this._scrollHeight; }
+ @computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); }
constructor(props: any) {
super(props);
@@ -71,14 +68,80 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || 850);
Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || this.Document[HeightSym]() / this.Document[WidthSym]() * 850);
}
- this._annotationKey = this._annotationKey + "-" + this.urlHash(this._url);
+ if (this.layoutDoc[this.fieldKey + "-contentWidth"] === undefined) {
+ this.layoutDoc[this.fieldKey + "-contentWidth"] = Doc.NativeWidth(this.layoutDoc);
+ }
+ this._annotationKey = "annotations-" + this.urlHash(this._url);
+ }
+
+ @action
+ createTextAnnotation = (sel: Selection, selRange: Range) => {
+ if (this._mainCont.current) {
+ const clientRects = selRange.getClientRects();
+ for (let i = 0; i < clientRects.length; i++) {
+ const rect = clientRects.item(i);
+ if (rect && rect.width !== this._mainCont.current.clientWidth) {
+ const annoBox = document.createElement("div");
+ annoBox.className = "marqueeAnnotator-annotationBox";
+ // transforms the positions from screen onto the pdf div
+ annoBox.style.top = (rect.top + this._mainCont.current.scrollTop).toString();
+ annoBox.style.left = (rect.left).toString();
+ annoBox.style.width = (rect.width).toString();
+ annoBox.style.height = (rect.height).toString();
+ this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, 1);
+ }
+ }
+ }
+ //this._selectionText = selRange.cloneContents().textContent || "";
+
+ // clear selection
+ if (sel.empty) { // Chrome
+ sel.empty();
+ } else if (sel.removeAllRanges) { // Firefox
+ sel.removeAllRanges();
+ }
}
+ @action
+ iframeUp = (e: PointerEvent) => {
+ if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) {
+ this._iframe.contentDocument.addEventListener("pointerup", this.iframeUp);
+ const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
+ const scale = (this.props.scaling?.() || 1) * mainContBounds.scale;
+ const sel = this._iframe.contentWindow.getSelection();
+ if (sel) {
+ this.createTextAnnotation(sel, sel.getRangeAt(0));
+ AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX,
+ e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale);
+ }
+ } else AnchorMenu.Instance.fadeOut(true);
+ }
+ @action
+ iframeDown = (e: PointerEvent) => {
+ const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
+ const scale = (this.props.scaling?.() || 1) * mainContBounds.scale;
+ const word = getWordAtPoint(e.target, e.clientX, e.clientY);
+ this._marqueeing = [e.clientX * scale + mainContBounds.translateX,
+ e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale];
+ if (word) {
+ this._iframe?.contentDocument?.addEventListener("pointerup", this.iframeUp);
+ setTimeout(action(() => this._marqueeing = undefined), 100); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
+ } else {
+ this._isAnnotating = true;
+ this.props.select(false);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+
+ @action
iframeLoaded = (e: any) => {
const iframe = this._iframe;
if (iframe?.contentDocument) {
- if (this._initialScroll !== undefined && this._outerRef.current && this.webpage) {
- this.webpage.scrollTop = this._initialScroll;
+ iframe?.contentDocument.addEventListener("pointerdown", this.iframeDown);
+ this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument.body.scrollHeight);
+ setTimeout(action(() => this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0)), 5000);
+ if (this._initialScroll !== undefined && this._outerRef.current) {
this._outerRef.current.scrollTop = this._initialScroll;
this._initialScroll = undefined;
}
@@ -90,76 +153,57 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
}
if (href) {
this.submitURL(href.replace(Utils.prepend(""), Cast(this.dataDoc[this.fieldKey], WebField, null)?.url.origin));
- if (this.webpage) {
- this.webpage.scrollTop = NumCast(this.layoutDoc._scrollTop);
- this.webpage.scrollLeft = 0;
+ if (this._outerRef.current) {
+ this._outerRef.current.scrollTop = NumCast(this.layoutDoc._scrollTop);
+ this._outerRef.current.scrollLeft = 0;
}
}
})));
iframe.contentDocument.addEventListener('wheel', this.iframeWheel, false);
- iframe.contentDocument.addEventListener('scroll', this.iframeScroll, false);
+ //iframe.contentDocument.addEventListener('scroll', () => !this.active() && this._iframe && (this._iframe.scrollTop = NumCast(this.layoutDoc._scrollTop), false));
+ iframe.contentDocument.addEventListener('scroll', () => {
+ console.log("Scroll = " + this._iframe?.scrollTop)
+ }
+ , true);
}
}
- resetIgnoreScroll = () => {
+ @action
+ setDashScrollTop = (scrollTop: number, timeout: number = 250) => {
+ const iframeHeight = Math.max(1000, this._scrollHeight - this.panelHeight());
+ timeout = scrollTop > iframeHeight ? 0 : timeout;
this._scrollTimer && clearTimeout(this._scrollTimer);
- this._scrollTimer = setTimeout(() => {
+ this._scrollTimer = setTimeout(action(() => {
this._scrollTimer = undefined;
- this._ignoreScroll = "";
- }, 250);
- this._outerRef.current && (this._outerRef.current.scrollLeft = 0);
+ if (!LinkDocPreview.LinkInfo && this._outerRef.current &&
+ (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) {
+ this.layoutDoc._scrollTop = this._outerRef.current.scrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop;
+ }
+ }), timeout);
}
+ @action
iframeWheel = (e: any) => {
- this._ignoreScroll = "iframe";
- this.resetIgnoreScroll();
- e.stopPropagation();
- }
- onWebWheel = (e: React.WheelEvent) => {
- this._ignoreScroll = "iframe";
- this.goTo(Math.max(0, (this.webpage?.scrollTop || 0) + (this._accumulatedGoTo + 1) * e.deltaY), 100);
- this.resetIgnoreScroll();
- e.stopPropagation();
- }
- onWheel = (e: React.WheelEvent) => {
- this._ignoreScroll = "outer";
- this.resetIgnoreScroll();
- e.stopPropagation();
- }
- iframeScroll = (e: any) => {
- if (!this._ignoreScroll.includes("outer") && this._outerRef.current) {
- this._outerRef.current.scrollTop = this.webpage?.scrollTop || 0;
- this.layoutDoc._scrollTop = this.webpage?.scrollTop;
+ if (!this._scrollTimer) {
+ this._scrollTimer = setTimeout(action(() => this._scrollTimer = undefined), 250); // this turns events off on the iframe which allows scrolling to change direction smoothly
}
}
- onScroll = (e: any) => {
- if (!this._ignoreScroll.includes("iframe") && this.webpage) {
- this.webpage.scrollTop = this._outerRef.current?.scrollTop || 0;
- this.layoutDoc._scrollTop = this._outerRef.current?.scrollTop;
- }
+ onWheel = (e: any) => {
+ e.stopPropagation();
+ e.preventDefault();
}
+ onScroll = (e: any) => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0);
scrollFocus = (doc: Doc, smooth: boolean) => {
- let focusSpeed: Opt<number>;
- if (doc !== this.rootDoc && this.webpage && this._outerRef.current) {
+ if (doc !== this.rootDoc && this._outerRef.current) {
const scrollTo = doc.type === DocumentType.TEXTANCHOR ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1));
if (scrollTo !== undefined) {
+ const focusSpeed = smooth ? 500 : 0;
this._initialScroll !== undefined && (this._initialScroll = scrollTo);
- if (!LinkDocPreview.LinkInfo) {
- this._ignoreScroll = "iframe|outer";
- this.layoutDoc._scrollTop = scrollTo;
- this._ignoreScroll = "";
- }
- this._ignoreScroll = "iframe|outer";
- this.goTo(scrollTo, focusSpeed = smooth ? 500 : 0);
- setTimeout(() => {
- this._scrollTimer = undefined;
- this._ignoreScroll = "";
- }, focusSpeed);
+ this.goTo(scrollTo, focusSpeed);
+ return focusSpeed;
}
- } else {
- this._initialScroll = NumCast(doc.y);
}
-
- return focusSpeed;
+ this._initialScroll = NumCast(doc.y);
+ return 0;
}
getAnchor = () => {
@@ -182,12 +226,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
this._disposers.selection = reaction(() => this.props.isSelected(),
selected => !selected && setTimeout(() => {
- this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
+ Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
this._savedAnnotations.clear();
}));
- document.addEventListener("pointerup", this.onLongPressUp);
- document.addEventListener("pointermove", this.onLongPressMove);
const field = Cast(this.rootDoc[this.props.fieldKey], WebField);
if (field?.url.href.indexOf("youtube") !== -1) {
const youtubeaspect = 400 / 315;
@@ -226,29 +268,22 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
quickScroll = false;
}
- _accumulatedGoTo = 0;
- _resetGoTo: { resetGoTo: { to: number, duration: number } | undefined } = { resetGoTo: undefined };
goTo = (scrollTop: number, duration: number) => {
- if (this._outerRef.current && this.webpage) {
+ if (this._outerRef.current) {
+ const iframeHeight = Math.max(1000, this._scrollHeight - this.panelHeight());
+ scrollTop = scrollTop > iframeHeight + 50 ? iframeHeight : scrollTop;
if (duration) {
- if (this._accumulatedGoTo++) {
- this._resetGoTo.resetGoTo = { to: scrollTop, duration };
- } else {
- smoothScroll(duration, [this.webpage as any as HTMLElement, this._outerRef.current], scrollTop, () => this._accumulatedGoTo = 0, this._resetGoTo);
- }
+ smoothScroll(duration, [this._outerRef.current], scrollTop);
+ this.setDashScrollTop(scrollTop, duration);
} else {
- this.webpage.scrollTop = scrollTop;
- this._outerRef.current.scrollTop = scrollTop;
+ this.setDashScrollTop(scrollTop);
}
}
}
componentWillUnmount() {
Object.values(this._disposers).forEach(disposer => disposer?.());
- document.removeEventListener("pointerup", this.onLongPressUp);
- document.removeEventListener("pointermove", this.onLongPressMove);
- this._iframe?.removeEventListener('wheel', this.iframeWheel);
- this._iframe?.removeEventListener('scroll', this.iframeScroll);
+ this._iframe?.removeEventListener('wheel', this.iframeWheel, true);
}
@action
@@ -258,7 +293,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
if (future.length) {
history.push(this._url);
this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = future.pop()!));
- this._annotationKey = this.fieldKey + "-annotations-" + this.urlHash(this._url);
+ this._annotationKey = "annotations-" + this.urlHash(this._url);
return true;
}
return false;
@@ -272,7 +307,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List<string>([this._url]);
else future.push(this._url);
this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = history.pop()!));
- this._annotationKey = this.fieldKey + "-annotations-" + this.urlHash(this._url);
+ this._annotationKey = "annotations-" + this.urlHash(this._url);
return true;
}
return false;
@@ -299,7 +334,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
future && (future.length = 0);
}
this._url = newUrl;
- this._annotationKey = this.fieldKey + "-annotations-" + this.urlHash(this._url);
+ this._annotationKey = "annotations-" + this.urlHash(this._url);
this.dataDoc[this.fieldKey] = new WebField(new URL(newUrl));
} catch (e) {
console.log("WebBox URL error:" + this._url);
@@ -353,114 +388,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
);
}
- editToggleBtn() {
- return <Tooltip title={<div className="dash-tooltip" >{`${this.props.Document.isAnnotating ? "Exit" : "Enter"} annotation mode`}</div>}>
- <div className="webBox-annotationToggle"
- style={{ color: this.props.Document.isAnnotating ? "black" : "white", backgroundColor: this.props.Document.isAnnotating ? "white" : "black" }}
- onClick={action(() => this.layoutDoc.isAnnotating = !this.layoutDoc.isAnnotating)}>
- <FontAwesomeIcon icon="edit" size="sm" />
- </div>
- </Tooltip>;
- }
-
- _ignore = 0;
- onPreWheel = (e: React.WheelEvent) => this._ignore = e.timeStamp;
- onPrePointer = (e: React.PointerEvent) => this._ignore = e.timeStamp;
- onPostPointer = (e: React.PointerEvent) => this._ignore !== e.timeStamp && e.stopPropagation();
- onPostWheel = (e: React.WheelEvent) => this._ignore !== e.timeStamp && e.stopPropagation();
-
- onLongPressDown = (e: React.PointerEvent) => {
- this._pressX = e.clientX;
- this._pressY = e.clientY;
-
- // find the pressed element in the iframe (currently only works if its an img)
- let pressedElement: HTMLElement | undefined;
- let pressedBound: ClientRect | undefined;
- let selectedText: string = "";
- let pressedImg: boolean = false;
- if (this._iframe) {
- const B = this._iframe.getBoundingClientRect();
- const iframeDoc = this._iframe.contentDocument;
- if (B && iframeDoc) {
- // TODO: this only works when scale = 1 as it is currently only inteded for mobile upload
- const element = iframeDoc.elementFromPoint(this._pressX - B.left, this._pressY - B.top);
- if (element && element.nodeName === "IMG") {
- pressedBound = element.getBoundingClientRect();
- pressedElement = element.cloneNode(true) as HTMLElement;
- pressedImg = true;
- } else {
- // check if there is selected text
- const text = iframeDoc.getSelection();
- if (text && text.toString().length > 0) {
- selectedText = text.toString();
-
- // get html of the selected text
- const range = text.getRangeAt(0);
- const contents = range.cloneContents();
- const div = document.createElement("div");
- div.appendChild(contents);
- pressedElement = div;
-
- pressedBound = range.getBoundingClientRect();
- }
- }
- }
- }
-
- // mark the pressed element
- if (pressedElement && pressedBound) {
- if (this._iframeIndicatorRef.current) {
- this._iframeIndicatorRef.current.style.top = pressedBound.top + "px";
- this._iframeIndicatorRef.current.style.left = pressedBound.left + "px";
- this._iframeIndicatorRef.current.style.width = pressedBound.width + "px";
- this._iframeIndicatorRef.current.style.height = pressedBound.height + "px";
- this._iframeIndicatorRef.current.classList.add("active");
- }
- }
-
- // start dragging the pressed element if long pressed
- this._longPressSecondsHack = setTimeout(() => {
- if (pressedImg && pressedElement && pressedBound) {
- e.stopPropagation();
- e.preventDefault();
- if (pressedElement.nodeName === "IMG") {
- const src = pressedElement.getAttribute("src"); // TODO: may not always work
- if (src) {
- const doc = Docs.Create.ImageDocument(src);
- ImageUtils.ExtractExif(doc);
-
- // add clone to div so that dragging ghost is placed properly
- if (this._iframeDragRef.current) this._iframeDragRef.current.appendChild(pressedElement);
-
- const dragData = new DragManager.DocumentDragData([doc]);
- DragManager.StartDocumentDrag([pressedElement], dragData, this._pressX, this._pressY, { hideSource: true });
- }
- }
- } else if (selectedText && pressedBound && pressedElement) {
- e.stopPropagation();
- e.preventDefault();
- // create doc with the selected text's html
- const doc = Docs.Create.HtmlDocument(pressedElement.innerHTML);
-
- // create dragging ghost with the selected text
- if (this._iframeDragRef.current) this._iframeDragRef.current.appendChild(pressedElement);
-
- // start the drag
- const dragData = new DragManager.DocumentDragData([doc]);
- DragManager.StartDocumentDrag([pressedElement], dragData, this._pressX - pressedBound.top, this._pressY - pressedBound.top, { hideSource: true });
- }
- }, 1500);
- }
- onLongPressMove = (e: PointerEvent) => {
- // this._pressX = e.clientX;
- // this._pressY = e.clientY;
- }
- onLongPressUp = (e: PointerEvent) => {
- this._longPressSecondsHack && clearTimeout(this._longPressSecondsHack);
- this._iframeIndicatorRef.current?.classList.remove("active");
- while (this._iframeDragRef.current?.firstChild) this._iframeDragRef.current.removeChild(this._iframeDragRef.current.firstChild);
- }
-
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
const funcs: ContextMenuProps[] = [];
@@ -479,12 +406,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
} else if (field instanceof WebField) {
const url = this.layoutDoc.useCors ? Utils.CorsProxy(field.url.href) : field.url.href;
// view = <iframe className="webBox-iframe" src={url} onLoad={e => { e.currentTarget.before((e.currentTarget.contentDocument?.body || e.currentTarget.contentDocument)?.children[0]!); e.currentTarget.remove(); }}
- view = <iframe className="webBox-iframe" enable-annotation={"true"} ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={url} onLoad={this.iframeLoaded}
+ view = <iframe className="webBox-iframe" enable-annotation={"true"}
+ style={{ pointerEvents: this._scrollTimer ? "none" : undefined }}
+ ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={url} onLoad={this.iframeLoaded}
// the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page
// sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />;
sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin"} />;
} else {
- view = <iframe className="webBox-iframe" enable-annotation={"true"} ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={"https://crossorigin.me/https://cs.brown.edu"} />;
+ view = <iframe className="webBox-iframe" enable-annotation={"true"}
+ style={{ pointerEvents: this._scrollTimer ? "none" : undefined }} // if we allow pointer events when scrolling is on, then reversing direction does not work smoothly
+ ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={"https://crossorigin.me/https://cs.brown.edu"} />;
}
return view;
}
@@ -509,7 +440,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
} else {
this.layoutDoc.nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
}
- this.layoutDoc._width = NumCast(this.layoutDoc._nativeWidth) * (NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) / NumCast(this.layoutDoc[this.fieldKey + "-nativeHeight"]))
+ this.layoutDoc._width = NumCast(this.layoutDoc._nativeWidth) * (NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) / NumCast(this.layoutDoc[this.fieldKey + "-nativeHeight"]));
}
sidebarKey = () => this.fieldKey + "-sidebar";
sidebarFiltersHeight = () => 50;
@@ -576,36 +507,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
@computed
get content() {
- const frozen = !this.props.isSelected() || DocumentDecorations.Instance?.Interacting;
- const scale = this.props.scaling?.() || 1;
- return (<>
- <div className={"webBox-cont" + (this.props.isSelected() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
- style={{
- width: NumCast(this.layoutDoc[this.fieldKey + "-contentWidth"]) || `${100 / scale}%`,
- height: `${100 / scale}%`,
- transform: `scale(${scale})`
- }}
- onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
- {this.urlContent}
- </div>
- {!frozen ? (null) :
- <div className="webBox-overlay" style={{ pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? undefined : "all" }}
- onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer}>
- <div className="touch-iframe-overlay" onPointerDown={this.onLongPressDown} >
- <div className="indicator" ref={this._iframeIndicatorRef}></div>
- <div className="dragger" ref={this._iframeDragRef}></div>
- </div>
- </div>}
- </>);
+ return <div className={"webBox-cont" + (this.active() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
+ style={{ width: NumCast(this.layoutDoc[this.fieldKey + "-contentWidth"]) || `${100 / (this.props.scaling?.() || 1)}%`, }}>
+ {this.urlContent}
+ </div>;
}
- @computed get allAnnotations() { return DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]); }
- @computed get nonDocAnnotations() { return this.allAnnotations.filter(a => a.annotations); }
+ showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno);
+ @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); }
@computed get annotationLayer() {
TraceMobx();
return <div className="webBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
- {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
- <Annotation {...this.props} showInfo={emptyFunction} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)
+ {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
+ <Annotation {...this.props} fieldKey={this.annotationKey} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)
}
</div>;
}
@@ -617,9 +531,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
this.props.select(false);
}
}
-
- @action
- finishMarquee = () => this._marqueeing = undefined;
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func;
+ @action finishMarquee = (x?: number, y?: number) => {
+ this._marqueeing = undefined;
+ this._isAnnotating = false;
+ x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false);
+ }
panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
@@ -628,50 +545,54 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
const inactiveLayer = this.props.layerProvider?.(this.layoutDoc) === false;
const scale = this.props.scaling?.() || 1;
return (
- <div className="webBox" ref={this._mainCont} >
+ <div className="webBox" ref={this._mainCont} style={{ pointerEvents: this.active() || SnappingManager.GetIsDragging() ? undefined : "none" }} >
<div className={`webBox-container`}
style={{ pointerEvents: inactiveLayer ? "none" : undefined }}
- onWheel={this.onWebWheel}
onContextMenu={this.specificContextMenu}>
<base target="_blank" />
- {this.content}
<div className={"webBox-outerContent"} ref={this._outerRef}
style={{
- width: `calc(${100 / scale}% - ${this.sidebarWidth()}px)`, height: `${100 / scale}%`, transform: `scale(${scale})`,
- pointerEvents: !this.layoutDoc.isAnnotating || inactiveLayer ? "none" : "all"
+ width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale}px)`,
+ height: `${100 / scale}%`,
+ transform: `scale(${scale})`,
+ pointerEvents: inactiveLayer ? "none" : undefined
}}
onWheel={this.onWheel}
- onPointerDown={this.onMarqueeDown}
onScroll={this.onScroll}
+ onPointerDown={this.onMarqueeDown}
>
<div className={"webBox-innerContent"} style={{
height: NumCast(this.scrollHeight, 50),
pointerEvents: inactiveLayer ? "none" : undefined
}}>
+ {this.content}
<CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
renderDepth={this.props.renderDepth + 1}
CollectionView={undefined}
fieldKey={this.annotationKey}
isAnnotationOverlay={true}
scaling={returnOne}
+ pointerEvents={this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"}
PanelWidth={this.panelWidth}
PanelHeight={this.panelHeight}
ScreenToLocalTransform={this.scrollXf}
+ setPreviewCursor={this.setPreviewCursor}
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
addDocument={this.addDocument}
select={emptyFunction}
active={this.active}
whenActiveChanged={this.whenActiveChanged} />
+ {this.annotationLayer}
</div>
</div>
- {this.annotationLayer}
{!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
<MarqueeAnnotator rootDoc={this.rootDoc}
anchorMenuClick={this.anchorMenuClick}
- scrollTop={NumCast(this.rootDoc._scrollTop)}
- down={this._marqueeing} scaling={this.props.scaling}
+ scrollTop={0}
+ down={this._marqueeing} scaling={returnOne}
addDocument={this.addDocument}
+ docView={this.props.docViewPath().lastElement()}
finishMarquee={this.finishMarquee}
savedAnnotations={this._savedAnnotations}
annotationLayer={this._annotationLayer.current}
@@ -682,7 +603,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
<FontAwesomeIcon style={{ color: "white" }} icon={"chevron-left"} size="sm" />
</button>
{this.sidebarOverlay}
- {this.props.isSelected() ? this.editToggleBtn() : null}
</div>);
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 9259e6c25..e4c481014 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -84,9 +84,6 @@ type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, da
export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
- public static get DefaultLayout() {
- return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null);
- }
public static Instance: FormattedTextBox;
public static LiveTextUndo: UndoManager.Batch | undefined;
static _highlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"];
@@ -229,7 +226,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
return target;
};
- DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.rootDoc, this.getAnchor, targetCreator), e.pageX, e.pageY);
+ DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docViewPath().lastElement(), this.getAnchor, targetCreator), e.pageX, e.pageY);
});
const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
@@ -1203,19 +1200,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
prosediv && (prosediv.keeplocation = undefined);
const pos = this._editorView?.state.selection.$from.pos || 1;
keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
- const coords = !Number.isNaN(this._downX) ? { left: this._downX, top: this._downY, bottom: this._downY, right: this._downX } : this._editorView?.coordsAtPos(pos);
-
- // jump rich text menu to this textbox
- const bounds = this._ref.current?.getBoundingClientRect();
- if (bounds && this.layoutDoc._chromeStatus !== "disabled" && RichTextMenu.Instance) {
- const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width);
- let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height);
- if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) {
- y = Math.min(bounds.bottom, window.innerHeight - RichTextMenu.Instance.height);
- }
- this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
- setTimeout(() => window.document.activeElement === this.ProseRef?.children[0] && RichTextMenu.Instance.jumpTo(x, y), 250);
- }
+
+ this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
}
onPointerWheel = (e: React.WheelEvent): void => {
// if a text note is selected and scrollable, stop event to prevent, say, outer collection from zooming.
@@ -1421,11 +1407,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
</div>;
}
@computed get sidebarHandle() {
+ TraceMobx();
const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
return (!annotated && !this.active()) ? (null) : <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
style={{
left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`,
- background: this.props.styleProvider?.(this.props.Document, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : ""))
+ background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : ""))
}} />;
}
@computed get sidebarCollection() {