aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/util/DragManager.ts7
-rw-r--r--src/client/util/SnappingManager.ts7
-rw-r--r--src/client/views/collections/CollectionSubView.tsx18
-rw-r--r--src/client/views/collections/CollectionView.tsx21
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx107
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx2
-rw-r--r--src/client/views/nodes/DocumentView.tsx62
-rw-r--r--src/client/views/nodes/LinkBox.tsx90
-rw-r--r--src/client/views/nodes/WebBox.tsx77
-rw-r--r--src/client/views/pdf/PDFViewer.tsx4
10 files changed, 248 insertions, 147 deletions
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index ea13eaa5b..9d6bd4f60 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -329,7 +329,7 @@ export namespace DragManager {
DocDragData = dragData as DocumentDragData;
const batch = UndoManager.StartBatch(dragUndoName ?? 'document drag');
eles = eles.filter(e => e);
- CanEmbed = dragData.canEmbed || false;
+ SnappingManager.SetCanEmbed(dragData.canEmbed || false);
if (!dragDiv) {
dragDiv = document.createElement('div');
dragDiv.className = 'dragManager-dragDiv';
@@ -455,7 +455,7 @@ export namespace DragManager {
runInAction(() => docsBeingDragged.push(...docsToDrag));
const hideDragShowOriginalElements = (hide: boolean) => {
- dragLabel.style.display = hide && !CanEmbed ? '' : 'none';
+ dragLabel.style.display = hide && !SnappingManager.GetCanEmbed() ? '' : 'none';
!hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
setTimeout(() => eles.forEach(ele => (ele.hidden = hide)));
};
@@ -482,6 +482,7 @@ export namespace DragManager {
SnappingManager.SetIsDragging(false);
if (batch.end() && undo) UndoManager.Undo();
docsBeingDragged.length = 0;
+ SnappingManager.SetCanEmbed(false);
});
var startWindowDragTimer: any;
const moveHandler = (e: PointerEvent) => {
@@ -588,7 +589,7 @@ export namespace DragManager {
altKey: e.altKey,
metaKey: e.metaKey,
ctrlKey: e.ctrlKey,
- embedKey: CanEmbed,
+ embedKey: SnappingManager.GetCanEmbed(),
},
};
target.dispatchEvent(new CustomEvent<DropEvent>('dashPreDrop', dropArgs));
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index 3cb41ab4d..c0cd94067 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -5,6 +5,7 @@ export namespace SnappingManager {
class Manager {
@observable IsDragging: boolean = false;
@observable IsResizing: Doc | undefined;
+ @observable CanEmbed: boolean = false;
@observable public horizSnapLines: number[] = [];
@observable public vertSnapLines: number[] = [];
@action public clearSnapLines() {
@@ -38,10 +39,16 @@ export namespace SnappingManager {
export function SetIsResizing(doc: Doc | undefined) {
runInAction(() => (manager.IsResizing = doc));
}
+ export function SetCanEmbed(canEmbed: boolean) {
+ runInAction(() => (manager.CanEmbed = canEmbed));
+ }
export function GetIsDragging() {
return manager.IsDragging;
}
export function GetIsResizing() {
return manager.IsResizing;
}
+ export function GetCanEmbed() {
+ return manager.CanEmbed;
+ }
}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 09e7cdb32..8a1ba0df1 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -8,14 +8,22 @@ import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { Cast, ScriptCast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
+import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
import { returnFalse, Utils } from '../../../Utils';
import { DocServer } from '../../DocServer';
+import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents';
+import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { Networking } from '../../Network';
+import { DragManager, dropActionType } from '../../util/DragManager';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
import { InteractionUtils } from '../../util/InteractionUtils';
+import { SelectionManager } from '../../util/SelectionManager';
+import { SnappingManager } from '../../util/SnappingManager';
import { undoBatch, UndoManager } from '../../util/UndoManager';
import { DocComponent } from '../DocComponent';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+import { CollectionView, CollectionViewProps } from './CollectionView';
import React = require('react');
export interface SubCollectionViewProps extends CollectionViewProps {
@@ -118,7 +126,7 @@ export function CollectionSubView<X>(moreProps?: X) {
childDocs.forEach(d => {
// dragging facets
const dragged = this.props.childFilters?.().some(f => f.includes(Utils.noDragsDocFilter));
- if (dragged && DragManager.docsBeingDragged.includes(d)) return false;
+ if (dragged && SnappingManager.GetCanEmbed() && DragManager.docsBeingDragged.includes(d)) return false;
let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.props.Document).length > 0;
if (notFiltered) {
notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.props.Document).length > 0;
@@ -486,11 +494,3 @@ export function CollectionSubView<X>(moreProps?: X) {
return CollectionSubView;
}
-
-import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
-import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents';
-import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { DragManager, dropActionType } from '../../util/DragManager';
-import { SelectionManager } from '../../util/SelectionManager';
-import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
-import { CollectionView, CollectionViewProps } from './CollectionView';
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 493f40d77..6e4b9ec07 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,4 +1,4 @@
-import { computed, observable, runInAction } from 'mobx';
+import { computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
@@ -78,6 +78,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
public static SetSafeMode(safeMode: boolean) {
this._safeMode = safeMode;
}
+ private reactionDisposer: IReactionDisposer | undefined;
+ @observable _isContentActive: boolean | undefined;
protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
@@ -86,6 +88,21 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
runInAction(() => (this._annotationKeySuffix = returnEmptyString));
}
+ componentDidMount() {
+ // we use a reaction/observable instead of a computed value to reduce invalidations.
+ // There are many variables that aggregate into this boolean output - a change in any of them
+ // will cause downstream invalidations even if the computed value doesn't change. By making
+ // this a reaction, downstream invalidations only occur when the reaction value actually changes.
+ this.reactionDisposer = reaction(
+ () => (this.isAnyChildContentActive() ? true : this.props.isContentActive()),
+ active => (this._isContentActive = active),
+ { fireImmediately: true }
+ );
+ }
+ componentWillUnmount() {
+ this.reactionDisposer?.();
+ }
+
get collectionViewType(): CollectionViewType | undefined {
const viewField = StrCast(this.layoutDoc._type_collection);
if (CollectionView._safeMode) {
@@ -220,7 +237,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
childHideResizeHandles = () => this.props.childHideResizeHandles?.() ?? BoolCast(this.Document.childHideResizeHandles);
childHideDecorationTitle = () => this.props.childHideDecorationTitle?.() ?? BoolCast(this.Document.childHideDecorationTitle);
childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null);
- isContentActive = (outsideReaction?: boolean) => (this.isAnyChildContentActive() ? true : this.props.isContentActive());
+ isContentActive = (outsideReaction?: boolean) => this._isContentActive;
render() {
TraceMobx();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d80ea2cb2..2897cac0e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1280,12 +1280,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
@computed get childPointerEvents() {
const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
- const pointerEvents = DocumentView.Interacting
+ const pointerevents = DocumentView.Interacting
? 'none'
- : this.props.childPointerEvents?.() ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) || this.isContentActive() === false ? 'none' : this.props.pointerEvents?.());
- return pointerEvents;
+ : this.props.childPointerEvents?.() ??
+ (this.props.viewDefDivClick || //
+ (engine === computePassLayout.name && !this.props.isSelected(true)) ||
+ this.isContentActive() === false
+ ? 'none'
+ : this.props.pointerEvents?.());
+ console.log(`${this.rootDoc.title} pe = ` + pointerevents);
+ return pointerevents;
}
- childPointerEventsFunc = () => this.childPointerEvents;
+
+ @observable _childPointerEvents: 'none' | 'all' | 'visiblepainted' | undefined;
+ childPointerEventsFunc = () => this._childPointerEvents;
childContentsActive = () => (this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)();
getChildDocView(entry: PoolData) {
const childLayout = entry.pair.layout;
@@ -1372,9 +1380,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
});
@observable _lightboxDoc: Opt<Doc>;
- getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData {
+ getCalculatedPositions(pair: { layout: Doc; data?: Doc }): PoolData {
const random = (min: number, max: number, x: number, y: number) => /* min should not be equal to max */ min + (((Math.abs(x * y) * 9301 + 49297) % 233280) / 233280) * (max - min);
- const childDoc = params.pair.layout;
+ const childDoc = pair.layout;
const childDocLayout = Doc.Layout(childDoc);
const layoutFrameNumber = Cast(this.Document._currentFrame, 'number'); // frame number that container is at which determines layout frame values
const contentFrameNumber = Cast(childDocLayout._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
@@ -1400,7 +1408,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
width: _width,
height: _height,
transition: StrCast(childDocLayout.dataTransition),
- pair: params.pair,
+ pair,
pointerEvents: Cast(childDoc.pointerEvents, 'string', null),
replica: '',
};
@@ -1480,7 +1488,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
doFreeformLayout(poolData: Map<string, PoolData>) {
- this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => poolData.set(pair.layout[Id], this.getCalculatedPositions({ pair, index: i, collection: this.Document })));
+ this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => poolData.set(pair.layout[Id], this.getCalculatedPositions(pair)));
return [] as ViewDefResult[];
}
@@ -1502,38 +1510,39 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _numLoaded = 1;
_lastPoolSize = 0;
- get doLayoutComputation() {
- const { newPool, computedElementData } = this.doInternalLayoutComputation;
+ @action
+ doLayoutComputation = (newPool: Map<string, PoolData>, computedElementData: ViewDefResult[]) => {
const array = Array.from(newPool.entries());
- let somethingChanged = array.length !== this._lastPoolSize;
this._lastPoolSize = array.length;
- runInAction(() => {
- for (const entry of array) {
- const lastPos = this._cachedPool.get(entry[0]); // last computed pos
- const newPos = entry[1];
- if (
- !lastPos ||
- newPos.color !== lastPos.color ||
- newPos.backgroundColor !== lastPos.backgroundColor ||
- newPos.opacity !== lastPos.opacity ||
- newPos.x !== lastPos.x ||
- newPos.y !== lastPos.y ||
- newPos.z !== lastPos.z ||
- newPos.rotation !== lastPos.rotation ||
- newPos.zIndex !== lastPos.zIndex ||
- newPos.transition !== lastPos.transition ||
- newPos.pointerEvents !== lastPos.pointerEvents
- ) {
- this._layoutPoolData.set(entry[0], newPos);
- somethingChanged = true;
- }
- if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
- this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height });
- somethingChanged = true;
- }
+ for (const entry of array) {
+ const lastPos = this._cachedPool.get(entry[0]); // last computed pos
+ const newPos = entry[1];
+ if (
+ !lastPos ||
+ newPos.color !== lastPos.color ||
+ newPos.backgroundColor !== lastPos.backgroundColor ||
+ newPos.opacity !== lastPos.opacity ||
+ newPos.x !== lastPos.x ||
+ newPos.y !== lastPos.y ||
+ newPos.z !== lastPos.z ||
+ newPos.rotation !== lastPos.rotation ||
+ newPos.zIndex !== lastPos.zIndex ||
+ newPos.transition !== lastPos.transition ||
+ newPos.pointerEvents !== lastPos.pointerEvents
+ ) {
+ this._layoutPoolData.set(entry[0], newPos);
}
- });
- if (!somethingChanged) return undefined;
+ if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
+ this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height });
+ }
+ }
+ // by returning undefined, we prevent an edit being made to layoutElements when nothing has happened
+ // this short circuit, prevents lots of downstream mobx invalidations which would have no effect but cause
+ // a distinct lag at the start of dragging.
+ // The reason we're here in the first place without a change is that when dragging a document,
+ // filters are changed on the annotation layers (eg. WebBox) which invalidate the childDoc list
+ // for the overlay views -- however, in many cases, this filter change doesn't actually affect anything
+ // (e.g, no annotations, or only opaque annotations).
this._cachedPool.clear();
Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
const elements = computedElementData.slice();
@@ -1551,7 +1560,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.Document._freeform_useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
return elements;
- }
+ };
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
// create an anchor that saves information about the current state of the freeform view (pan, zoom, view type)
@@ -1605,10 +1614,26 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
{ fireImmediately: true }
);
+ this._disposers.pointerevents = reaction(
+ () => {
+ const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
+ return DocumentView.Interacting
+ ? 'none'
+ : this.props.childPointerEvents?.() ??
+ (this.props.viewDefDivClick || //
+ (engine === computePassLayout.name && !this.props.isSelected(true)) ||
+ this.isContentActive() === false
+ ? 'none'
+ : this.props.pointerEvents?.());
+ },
+ pointerevents => (this._childPointerEvents = pointerevents as any),
+ { fireImmediately: true }
+ );
+
this._disposers.layoutComputation = reaction(
- () => this.doLayoutComputation,
- elements => elements !== undefined && (this._layoutElements = elements || []),
- { fireImmediately: true, name: 'doLayout' }
+ () => this.doInternalLayoutComputation,
+ ({ newPool, computedElementData }) => (this._layoutElements = this.doLayoutComputation(newPool, computedElementData)),
+ { fireImmediately: true, name: 'layoutComputationReaction' }
);
this._disposers.active = reaction(
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 4db0bf5fa..a26d2e9f3 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -27,6 +27,7 @@ interface DocumentLinksButtonProps {
StartLink?: boolean; //whether the link HAS been started (i.e. now needs to be completed)
ShowCount?: boolean;
scaling?: () => number; // how uch doc is scaled so that link buttons can invert it
+ hideCount?: () => boolean;
}
@observer
export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
@@ -239,6 +240,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
}
render() {
+ if (this.props.hideCount?.()) return null;
const menuTitle = this.props.StartLink ? 'Drag or tap to start link' : 'Tap to complete link';
const buttonTitle = 'Tap to view links; double tap to open link collection';
const title = this.props.ShowCount ? buttonTitle : menuTitle;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3d6b53ccc..30d5a5184 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { Dropdown, DropdownType, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
@@ -136,7 +136,7 @@ export interface DocComponentView {
componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
incrementalRendering?: () => void;
layout_fitWidth?: () => boolean; // whether the component always fits width (eg, KeyValueBox)
- overridePointerEvents?: () => 'all' | 'none' | undefined; // if the conmponent overrides the pointer events for the document
+ overridePointerEvents?: () => 'all' | 'none' | undefined; // if the conmponent overrides the pointer events for the document (e.g, KeyValueBox always allows pointer events)
fieldKey?: string;
annotationKey?: string;
getTitle?: () => string;
@@ -272,8 +272,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
return this._animateScaleTime ?? 100;
}
public get displayName() {
- return 'DocumentView(' + this.props.Document.title + ')';
+ return 'DocumentViewInternal(' + this.props.Document.title + ')';
} // this makes mobx trace() statements more descriptive
+
public get ContentDiv() {
return this._mainCont.current;
}
@@ -317,8 +318,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
@computed get titleHeight() {
return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0;
}
+ @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined;
@computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined {
- return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents);
+ TraceMobx();
+ return this._pointerEvents;
}
@computed get finalLayoutKey() {
return StrCast(this.Document.layout_fieldKey, 'layout');
@@ -330,6 +333,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
return this.props.NativeHeight();
}
@computed get disableClickScriptFunc() {
+ TraceMobx();
const onScriptDisable = this.props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
// prettier-ignore
return (
@@ -356,6 +360,25 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
componentDidMount() {
this.setupHandlers();
+ this._disposers.contentActive = reaction(
+ () => {
+ // true - if the document has been activated directly or indirectly (by having its children selected)
+ // false - if its pointer events are explicitly turned off or if it's container tells it that it's inactive
+ // undefined - it is not active, but it should be responsive to actions that might activate it or its contents (eg clicking)
+ return this.props.isContentActive() === false || this.props.pointerEvents?.() === 'none'
+ ? false
+ : Doc.ActiveTool !== InkTool.None || SnappingManager.GetCanEmbed() || this.rootSelected() || this.rootDoc.forceActive || this._componentView?.isAnyChildContentActive?.() || this.props.isContentActive()
+ ? true
+ : undefined;
+ },
+ active => (this._isContentActive = active),
+ { fireImmediately: true }
+ );
+ this._disposers.pointerevents = reaction(
+ () => this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents),
+ pointerevents => (this._pointerEvents = pointerevents),
+ { fireImmediately: true }
+ );
}
preDropFunc = (e: Event, de: DragManager.DropEvent) => {
const dropAction = this.layoutDoc.dropAction as dropActionType;
@@ -883,23 +906,22 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
setHeight = (height: number) => (this.layoutDoc._height = height);
setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
- @computed get _isContentActive() {
- // true - if the document has been activated directly or indirectly (by having its children selected)
- // false - if its pointer events are explicitly turned off or if it's container tells it that it's inactive
- // undefined - it is not active, but it should be responsive to actions that might active it or its contents (eg clicking)
- return this.props.isContentActive() === false || this.props.pointerEvents?.() === 'none'
- ? false
- : Doc.ActiveTool !== InkTool.None || SnappingManager.GetIsDragging() || this.rootSelected() || this.rootDoc.forceActive || this._componentView?.isAnyChildContentActive?.() || this.props.isContentActive()
- ? true
- : undefined;
- }
+ @observable _isContentActive: boolean | undefined;
+
isContentActive = (): boolean | undefined => this._isContentActive;
childFilters = () => [...this.props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
/// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive
@computed get _contentPointerEvents() {
- if (this.props.contentPointerEvents) return this.props.contentPointerEvents;
- return (!this.disableClickScriptFunc && this.onClickHandler && !this.props.onBrowseClick?.() && this.isContentActive() !== true) || this.isContentActive() === false ? 'none' : this.pointerEvents;
+ TraceMobx();
+ return this.props.contentPointerEvents ??
+ ((!this.disableClickScriptFunc && //
+ this.onClickHandler &&
+ !this.props.onBrowseClick?.() &&
+ this.isContentActive() !== true) ||
+ this.isContentActive() === false)
+ ? 'none'
+ : this.pointerEvents;
}
contentPointerEvents = () => this._contentPointerEvents;
@computed get contents() {
@@ -1304,8 +1326,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
onContextMenu={this.onContextMenu}
onPointerDown={this.onPointerDown}
onClick={this.onClick}
- onPointerEnter={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.rootDoc)}
- onPointerOver={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.rootDoc)}
+ onPointerEnter={e => (!SnappingManager.GetIsDragging() || SnappingManager.GetCanEmbed()) && Doc.BrushDoc(this.rootDoc)}
+ onPointerOver={e => (!SnappingManager.GetIsDragging() || SnappingManager.GetCanEmbed()) && Doc.BrushDoc(this.rootDoc)}
onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.rootDoc)}
style={{
borderRadius: this.borderRounding,
@@ -1426,9 +1448,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
@computed get hideLinkButton() {
return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkBtn + (this.isSelected() ? ':selected' : ''));
}
+ hideLinkCount = () => this.props.renderDepth === -1 || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton;
@computed get linkCountView() {
- const hideCount = this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton;
- return hideCount ? null : <DocumentLinksButton View={this} scaling={this.scaleToScreenSpace} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
+ return <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.scaleToScreenSpace} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
}
@computed get docViewPath(): DocumentView[] {
return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this];
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 38ff21209..e66fed84b 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,19 +1,19 @@
import React = require('react');
import { Bezier } from 'bezier-js';
-import { computed, action } from 'mobx';
+import { computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { Height, Width } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { DocCast, NumCast, StrCast } from '../../../fields/Types';
import { aggregateBounds, emptyFunction, returnAlways, returnFalse, Utils } from '../../../Utils';
import { DocumentManager } from '../../util/DocumentManager';
+import { Transform } from '../../util/Transform';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm';
import { ViewBoxBaseComponent } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
import { ComparisonBox } from './ComparisonBox';
import { FieldView, FieldViewProps } from './FieldView';
import './LinkBox.scss';
-import { CollectionFreeFormView } from '../collections/collectionFreeForm';
-import { Transform } from '../../util/Transform';
@observer
export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -22,9 +22,6 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
onClickScriptDisable = returnAlways;
- componentDidMount() {
- this.props.setContentView?.(this);
- }
@computed get anchor1() {
const anchor1 = DocCast(this.rootDoc.link_anchor_1);
const anchor_1 = anchor1?.layout_unrendered ? DocCast(anchor1.annotationOn) : anchor1;
@@ -56,45 +53,70 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
return { left: 0, top: 0, right: 0, bottom: 0, center: undefined };
};
- render() {
- if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) {
- const a = (this.anchor1 ?? this.anchor2)!;
- const b = (this.anchor2 ?? this.anchor1)!;
-
- const parxf = this.props.docViewPath()[this.props.docViewPath().length - 2].ComponentView as CollectionFreeFormView;
- const this_xf = parxf?.getTransform() ?? Transform.Identity; //this.props.ScreenToLocalTransform();
- const a_invXf = a.props.ScreenToLocalTransform().inverse();
- const b_invXf = b.props.ScreenToLocalTransform().inverse();
- const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(a.rootDoc[Width](), a.rootDoc[Height]()) };
- const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(b.rootDoc[Width](), b.rootDoc[Height]()) };
- const a_bds = { tl: this_xf.transformPoint(a_scrBds.tl[0], a_scrBds.tl[1]), br: this_xf.transformPoint(a_scrBds.br[0], a_scrBds.br[1]) };
- const b_bds = { tl: this_xf.transformPoint(b_scrBds.tl[0], b_scrBds.tl[1]), br: this_xf.transformPoint(b_scrBds.br[0], b_scrBds.br[1]) };
+ disposer: IReactionDisposer | undefined;
+ componentDidMount() {
+ this.props.setContentView?.(this);
+ this.disposer = reaction(
+ () => {
+ if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) {
+ const a = (this.anchor1 ?? this.anchor2)!;
+ const b = (this.anchor2 ?? this.anchor1)!;
- const ppt1 = [(a_bds.tl[0] + a_bds.br[0]) / 2, (a_bds.tl[1] + a_bds.br[1]) / 2];
- const pt1 = Utils.getNearestPointInPerimeter(a_bds.tl[0], a_bds.tl[1], a_bds.br[0] - a_bds.tl[0], a_bds.br[1] - a_bds.tl[1], (b_bds.tl[0] + b_bds.br[0]) / 2, (b_bds.tl[1] + b_bds.br[1]) / 2);
- const pt2 = Utils.getNearestPointInPerimeter(b_bds.tl[0], b_bds.tl[1], b_bds.br[0] - b_bds.tl[0], b_bds.br[1] - b_bds.tl[1], (a_bds.tl[0] + a_bds.br[0]) / 2, (a_bds.tl[1] + a_bds.br[1]) / 2);
- const ppt2 = [(b_bds.tl[0] + b_bds.br[0]) / 2, (b_bds.tl[1] + b_bds.br[1]) / 2];
+ const parxf = this.props.docViewPath()[this.props.docViewPath().length - 2].ComponentView as CollectionFreeFormView;
+ const this_xf = parxf?.getTransform() ?? Transform.Identity; //this.props.ScreenToLocalTransform();
+ const a_invXf = a.props.ScreenToLocalTransform().inverse();
+ const b_invXf = b.props.ScreenToLocalTransform().inverse();
+ const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(a.rootDoc[Width](), a.rootDoc[Height]()) };
+ const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(b.rootDoc[Width](), b.rootDoc[Height]()) };
+ const a_bds = { tl: this_xf.transformPoint(a_scrBds.tl[0], a_scrBds.tl[1]), br: this_xf.transformPoint(a_scrBds.br[0], a_scrBds.br[1]) };
+ const b_bds = { tl: this_xf.transformPoint(b_scrBds.tl[0], b_scrBds.tl[1]), br: this_xf.transformPoint(b_scrBds.br[0], b_scrBds.br[1]) };
- const pts = [ppt1, pt1, pt2, ppt2].map(pt => [pt[0], pt[1]]);
- const [lx, rx, ty, by] = [Math.min(pt1[0], pt2[0]), Math.max(pt1[0], pt2[0]), Math.min(pt1[1], pt2[1]), Math.max(pt1[1], pt2[1])];
- setTimeout(
- action(() => {
- this.layoutDoc.x = lx;
- this.layoutDoc.y = ty;
- this.layoutDoc._width = rx - lx;
- this.layoutDoc._height = by - ty;
- })
- );
+ const ppt1 = [(a_bds.tl[0] + a_bds.br[0]) / 2, (a_bds.tl[1] + a_bds.br[1]) / 2];
+ const pt1 = Utils.getNearestPointInPerimeter(a_bds.tl[0], a_bds.tl[1], a_bds.br[0] - a_bds.tl[0], a_bds.br[1] - a_bds.tl[1], (b_bds.tl[0] + b_bds.br[0]) / 2, (b_bds.tl[1] + b_bds.br[1]) / 2);
+ const pt2 = Utils.getNearestPointInPerimeter(b_bds.tl[0], b_bds.tl[1], b_bds.br[0] - b_bds.tl[0], b_bds.br[1] - b_bds.tl[1], (a_bds.tl[0] + a_bds.br[0]) / 2, (a_bds.tl[1] + a_bds.br[1]) / 2);
+ const ppt2 = [(b_bds.tl[0] + b_bds.br[0]) / 2, (b_bds.tl[1] + b_bds.br[1]) / 2];
+ const pts = [ppt1, pt1, pt2, ppt2].map(pt => [pt[0], pt[1]]);
+ const [lx, rx, ty, by] = [Math.min(pt1[0], pt2[0]), Math.max(pt1[0], pt2[0]), Math.min(pt1[1], pt2[1]), Math.max(pt1[1], pt2[1])];
+ return { pts, lx, rx, ty, by };
+ }
+ return undefined;
+ },
+ params => {
+ this.renderProps = params;
+ if (params) {
+ if (
+ Math.abs(params.lx - NumCast(this.layoutDoc.x)) > 1e-5 ||
+ Math.abs(params.ty - NumCast(this.layoutDoc.y)) > 1e-5 ||
+ Math.abs(params.rx - params.lx - NumCast(this.layoutDoc._width)) > 1e-5 ||
+ Math.abs(params.by - params.ty - NumCast(this.layoutDoc._height)) > 1e-5
+ ) {
+ this.layoutDoc.x = params?.lx;
+ this.layoutDoc.y = params?.ty;
+ this.layoutDoc._width = params.rx - params?.lx;
+ this.layoutDoc._height = params?.by - params?.ty;
+ }
+ }
+ },
+ { fireImmediately: true }
+ );
+ }
+ componentWillUnmount(): void {
+ this.disposer?.();
+ }
+ @observable renderProps: { lx: number; rx: number; ty: number; by: number; pts: number[][] } | undefined;
+ render() {
+ if (this.renderProps) {
const highlight = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting);
const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined;
- const bez = new Bezier(pts.map(p => ({ x: p[0], y: p[1] })));
+ const bez = new Bezier(this.renderProps.pts.map(p => ({ x: p[0], y: p[1] })));
const text = bez.get(0.5);
const linkDesc = StrCast(this.rootDoc.link_description) || 'description';
const strokeWidth = NumCast(this.rootDoc.stroke_width, 4);
const dash = StrCast(this.rootDoc.stroke_dash);
const strokeDasharray = dash && Number(dash) ? String(strokeWidth * Number(dash)) : undefined;
+ const { pts, lx, ty, rx, by } = this.renderProps;
return (
<div style={{ transition: 'inherit', pointerEvents: 'none', position: 'absolute', width: '100%', height: '100%' }}>
<svg width={Math.max(100, rx - lx)} height={Math.max(100, by - ty)} style={{ transition: 'inherit', overflow: 'visible' }}>
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 66e0ed21f..a94279e88 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -861,7 +861,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
key="sidebar"
title="Toggle Sidebar"
style={{
- display: !this.props.isContentActive() ? 'none' : undefined,
top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 5,
backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
}}
@@ -950,42 +949,48 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@computed get SidebarShown() {
return this._showSidebar || this.layoutDoc._layout_showSidebar ? true : false;
}
+ renderAnnotations = (childFilters: () => string[]) => (
+ <CollectionFreeFormView
+ {...this.props}
+ setContentView={this.setInnerContent}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ originTopLeft={false}
+ isAnnotationOverlayScrollable={true}
+ renderDepth={this.props.renderDepth + 1}
+ isAnnotationOverlay={true}
+ fieldKey={this.annotationKey}
+ setPreviewCursor={this.setPreviewCursor}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
+ ScreenToLocalTransform={this.scrollXf}
+ NativeDimScaling={returnOne}
+ focus={this.focus}
+ childFilters={childFilters}
+ select={emptyFunction}
+ isAnyChildContentActive={returnFalse}
+ bringToFront={emptyFunction}
+ styleProvider={this.childStyleProvider}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ removeDocument={this.removeDocument}
+ moveDocument={this.moveDocument}
+ addDocument={this.addDocumentWrapper}
+ childPointerEvents={this.childPointerEvents}
+ pointerEvents={this.annotationPointerEvents}
+ />
+ );
+ @computed get renderOpaqueAnnotations() {
+ return this.renderAnnotations(this.opaqueFilter);
+ }
+ @computed get renderTransparentAnnotations() {
+ return this.renderAnnotations(this.transparentFilter);
+ }
childPointerEvents = () => (this.props.isContentActive() ? 'all' : undefined);
@computed get webpage() {
const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any);
const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
- const renderAnnotations = (childFilters: () => string[]) => (
- <CollectionFreeFormView
- {...this.props}
- setContentView={this.setInnerContent}
- NativeWidth={returnZero}
- NativeHeight={returnZero}
- originTopLeft={false}
- isAnnotationOverlayScrollable={true}
- renderDepth={this.props.renderDepth + 1}
- isAnnotationOverlay={true}
- fieldKey={this.annotationKey}
- setPreviewCursor={this.setPreviewCursor}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight}
- ScreenToLocalTransform={this.scrollXf}
- NativeDimScaling={returnOne}
- focus={this.focus}
- childFilters={childFilters}
- select={emptyFunction}
- isAnyChildContentActive={returnFalse}
- bringToFront={emptyFunction}
- styleProvider={this.childStyleProvider}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- removeDocument={this.removeDocument}
- moveDocument={this.moveDocument}
- addDocument={this.addDocumentWrapper}
- childPointerEvents={this.childPointerEvents}
- pointerEvents={this.annotationPointerEvents}
- />
- );
return (
<div
className="webBox-outerContent"
@@ -1000,8 +1005,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
onPointerDown={this.onMarqueeDown}>
<div className="webBox-innerContent" style={{ height: (this._webPageHasBeenRendered && this._scrollHeight) || '100%', pointerEvents }}>
{this.content}
- {<div style={{ display: DragManager.docsBeingDragged.length ? 'none' : undefined, mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>}
- {renderAnnotations(this.opaqueFilter)}
+ <div style={{ display: SnappingManager.GetCanEmbed() ? 'none' : undefined, mixBlendMode: 'multiply' }}>{this.renderTransparentAnnotations}</div>
+ {this.renderOpaqueAnnotations}
{this.annotationLayer}
</div>
</div>
@@ -1050,7 +1055,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()];
- opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])];
+ opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(SnappingManager.GetCanEmbed() ? [] : [Utils.IsOpaqueFilter()])];
childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
if (doc instanceof Doc && property === StyleProp.PointerEvents) {
if (this.inlineTextAnnotations.includes(doc)) return 'none';
@@ -1131,8 +1136,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
removeDocument={this.removeDocument}
/>
</div>
- {this.sidebarHandle}
- {!this.props.isContentActive() ? null : this.searchUI}
+ {!this.props.isContentActive() || SnappingManager.GetIsDragging() ? null : this.sidebarHandle}
+ {!this.props.isContentActive() || SnappingManager.GetIsDragging() ? null : this.searchUI}
</div>
);
}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 2e494aa45..712a5e92f 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -512,7 +512,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1);
panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()];
- opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length && this.props.isContentActive() ? [] : [Utils.IsOpaqueFilter()])];
+ opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(SnappingManager.GetCanEmbed() && this.props.isContentActive() ? [] : [Utils.IsOpaqueFilter()])];
childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
if (doc instanceof Doc && property === StyleProp.PointerEvents) {
if (this.inlineTextAnnotations.includes(doc) || this.props.isContentActive() === false) return 'none';
@@ -557,7 +557,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
);
@computed get overlayTransparentAnnotations() {
const transparentChildren = DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']), this.transparentFilter(), []);
- return !transparentChildren.length ? null : this.renderAnnotations(this.transparentFilter, 'multiply', DragManager.docsBeingDragged.length && this.props.isContentActive() ? 'none' : undefined);
+ return !transparentChildren.length ? null : this.renderAnnotations(this.transparentFilter, 'multiply', SnappingManager.GetCanEmbed() && this.props.isContentActive() ? 'none' : undefined);
}
@computed get overlayOpaqueAnnotations() {
return this.renderAnnotations(this.opaqueFilter, this.allAnnotations.some(anno => anno.mixBlendMode) ? 'hard-light' : undefined);