diff options
| author | bobzel <zzzman@gmail.com> | 2023-11-02 19:30:19 -0400 |
|---|---|---|
| committer | bobzel <zzzman@gmail.com> | 2023-11-02 19:30:19 -0400 |
| commit | eec81f7e0b53395e3e2ea25663a9ea06ec83085d (patch) | |
| tree | bb10e997cf9d5d0719049723de5728279bf67b0d /src/client/views/collections | |
| parent | 1bba63b1d15cfe76393424a768d2dbc0f0b8cffb (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')
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( |
