aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2023-11-02 19:30:19 -0400
committerbobzel <zzzman@gmail.com>2023-11-02 19:30:19 -0400
commiteec81f7e0b53395e3e2ea25663a9ea06ec83085d (patch)
treebb10e997cf9d5d0719049723de5728279bf67b0d /src/client/views/collections
parent1bba63b1d15cfe76393424a768d2dbc0f0b8cffb (diff)
performance fixes - don't invalidate as much by using reactions in place of computd values; don't make things active when things are dragged unless CanEmbed; fix for linkBox to use reaction.
Diffstat (limited to 'src/client/views/collections')
-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
3 files changed, 94 insertions, 52 deletions
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(