From 2ec32aee559749e1978d779705c84a8343615bfe Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 22 Feb 2022 18:05:36 -0500 Subject: improve efficiency for scenes with lots of documents by using computed functions to avoid invalidations. moved grid renderer to its own component to avoid invalidations when panning/zooming. --- .../views/collections/CollectionCarousel3DView.tsx | 2 +- .../views/collections/CollectionCarouselView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 129 +++++++++++++-------- .../collectionLinear/CollectionLinearView.tsx | 2 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 10 +- src/client/views/nodes/DocumentView.tsx | 8 +- 6 files changed, 96 insertions(+), 57 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 3c66faf0c..3bdc427d6 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -28,7 +28,7 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume componentWillUnmount() { this._dropDisposer?.(); } - protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view + protected createDashEventsTarget = (ele: HTMLDivElement | null) => { //used for stacking and masonry view this._dropDisposer?.(); if (ele) { this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 6c2c27e8e..733c07031 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -23,7 +23,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) componentWillUnmount() { this._dropDisposer?.(); } - protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view + protected createDashEventsTarget = (ele: HTMLDivElement | null) => { //used for stacking and masonry view this._dropDisposer?.(); if (ele) { this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3443f33e1..3d664e146 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,5 +1,5 @@ import { Bezier } from "bezier-js"; -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 { DateField } from "../../../../fields/DateField"; @@ -95,6 +95,7 @@ export class CollectionFreeFormView extends CollectionSubView(); private _layoutPoolData = observable.map(); private _layoutSizeData = observable.map(); private _cachedPool: Map = new Map(); @@ -1155,7 +1156,7 @@ export class CollectionFreeFormView extends CollectionSubView { return this._layoutPoolData.get(doc[Id] + (replica || "")); } @@ -1304,7 +1311,6 @@ export class CollectionFreeFormView extends CollectionSubView this._numLoaded; get doLayoutComputation() { const { newPool, computedElementData } = this.doInternalLayoutComputation; const array = Array.from(newPool.entries()); @@ -1567,9 +1573,13 @@ export class CollectionFreeFormView extends CollectionSubView { if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) { - this._numLoaded++; + const unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])); + const loadIncrement = 5; + for (var i = 0; i < Math.min(unrendered.length, loadIncrement); i++) { + this._renderCutoffData.set(unrendered[i][Id] + "", true); + } } - this._numLoaded < this.views.length && setTimeout(this.incrementalRender, 1); + this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1); }); children = () => { @@ -1582,46 +1592,6 @@ export class CollectionFreeFormView extends CollectionSubView { - if (!this.zoomScaling()) return 50; - const divisions = this.props.PanelWidth() / this.zoomScaling() / gridSpace + 3; - return divisions < 60 ? gridSpace : this.chooseGridSpace(gridSpace * 10); - } - - @computed get backgroundGrid() { - const gridSpace = this.chooseGridSpace(NumCast(this.layoutDoc["_backgroundGrid-spacing"], 50)); - const shiftX = (this.props.isAnnotationOverlay ? 0 : -this.panX() % gridSpace - gridSpace) * this.zoomScaling(); - const shiftY = (this.props.isAnnotationOverlay ? 0 : -this.panY() % gridSpace - gridSpace) * this.zoomScaling(); - const renderGridSpace = gridSpace * this.zoomScaling(); - const w = this.props.PanelWidth() + 2 * renderGridSpace; - const h = this.props.PanelHeight() + 2 * renderGridSpace; - const strokeStyle = CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "rgba(255,255,255,0.5)" : "rgba(0, 0,0,0.5)"; - return { - const ctx = el?.getContext('2d'); - if (ctx) { - const Cx = this.cachedCenteringShiftX % renderGridSpace; - const Cy = this.cachedCenteringShiftY % renderGridSpace; - ctx.lineWidth = Math.min(1, Math.max(0.5, this.zoomScaling())); - ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]); - ctx.clearRect(0, 0, w, h); - if (ctx) { - ctx.strokeStyle = strokeStyle; - ctx.beginPath(); - for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) { - ctx.moveTo(x, Cy - h); - ctx.lineTo(x, Cy + h); - } - for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) { - ctx.moveTo(Cx - w, y); - ctx.lineTo(Cx + w, y); - } - ctx.stroke(); - } - } - }} />; - } - @computed get placeholder() { return
{this.props.Document.title?.toString()} @@ -1629,6 +1599,7 @@ export class CollectionFreeFormView extends CollectionSubView
- {this.layoutDoc._backgroundGridShow ? this.backgroundGrid : (null)} + {this.layoutDoc._backgroundGridShow ? + : (null)} ; } +} + +interface CollectionFreeFormViewBackgroundGridProps { + panX: () => number; + panY: () => number; + PanelWidth: () => number; + PanelHeight: () => number; + isAnnotationOverlay?: boolean; + zoomScaling: () => number; + layoutDoc: Doc; + cachedCenteringShiftX: number; + cachedCenteringShiftY: number; +} +@observer +class CollectionFreeFormBackgroundGrid extends React.Component { + + + chooseGridSpace = (gridSpace: number): number => { + if (!this.props.zoomScaling()) return 50; + const divisions = this.props.PanelWidth() / this.props.zoomScaling() / gridSpace + 3; + return divisions < 60 ? gridSpace : this.chooseGridSpace(gridSpace * 10); + } + render() { + const gridSpace = this.chooseGridSpace(NumCast(this.props.layoutDoc["_backgroundGrid-spacing"], 50)); + const shiftX = (this.props.isAnnotationOverlay ? 0 : -this.props.panX() % gridSpace - gridSpace) * this.props.zoomScaling(); + const shiftY = (this.props.isAnnotationOverlay ? 0 : -this.props.panY() % gridSpace - gridSpace) * this.props.zoomScaling(); + const renderGridSpace = gridSpace * this.props.zoomScaling(); + const w = this.props.PanelWidth() + 2 * renderGridSpace; + const h = this.props.PanelHeight() + 2 * renderGridSpace; + const strokeStyle = CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "rgba(255,255,255,0.5)" : "rgba(0, 0,0,0.5)"; + return { + const ctx = el?.getContext('2d'); + if (ctx) { + const Cx = this.props.cachedCenteringShiftX % renderGridSpace; + const Cy = this.props.cachedCenteringShiftY % renderGridSpace; + ctx.lineWidth = Math.min(1, Math.max(0.5, this.props.zoomScaling())); + ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]); + ctx.clearRect(0, 0, w, h); + if (ctx) { + ctx.strokeStyle = strokeStyle; + ctx.beginPath(); + for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) { + ctx.moveTo(x, Cy - h); + ctx.lineTo(x, Cy + h); + } + for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) { + ctx.moveTo(Cx - w, y); + ctx.lineTo(Cx + w, y); + } + ctx.stroke(); + } + } + }} />; + } } \ No newline at end of file diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index d67122eff..9466d8753 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -74,7 +74,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { { fireImmediately: true } ); } - protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view + protected createDashEventsTarget = (ele: HTMLDivElement | null) => { //used for stacking and masonry view this._dropDisposer && this._dropDisposer(); if (ele) { this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 460982c8a..235c8accb 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -18,17 +18,18 @@ import { StyleProp } from "../StyleProvider"; import "./CollectionFreeFormDocumentView.scss"; import { DocumentView, DocumentViewProps } from "./DocumentView"; import React = require("react"); +import { Id } from "../../../fields/FieldSymbols"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined; sizeProvider?: (doc: Doc, replica: string) => { width: number, height: number } | undefined; layerProvider: ((doc: Doc, assign?: boolean) => boolean) | undefined; + renderCutoffProvider: (doc: Doc) => boolean; zIndex?: number; highlight?: boolean; jitterRotation: number; dataTransition?: string; replica: string; - renderCutoff: () => number; renderIndex: number; CollectionFreeFormView: CollectionFreeFormView; } @@ -178,10 +179,11 @@ export class CollectionFreeFormDocumentView extends DocComponent - {this.props.renderCutoff() >= this.props.renderIndex ? - this._contentView = r)} /> + {this.props.renderCutoffProvider(this.props.Document) ? +
: -
} + this._contentView = r)} /> + }
; } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 91f2359af..6468913fb 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -986,7 +986,7 @@ export class DocumentViewInternal extends DocComponent, props: Opt, property: string) => this.props?.styleProvider?.(doc, props, property + ":caption"); @computed get innards() { TraceMobx(); - const ffscale = (this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1); + const ffscale = () => (this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1); const showTitle = this.ShowTitle?.split(":")[0]; const showTitleHover = this.ShowTitle?.includes(":hover"); const showCaption = !this.props.hideCaptions && this.Document._viewType !== CollectionViewType.Carousel ? StrCast(this.layoutDoc._showCaption) : undefined; @@ -994,14 +994,14 @@ export class DocumentViewInternal extends DocComponent