From 203ec5b3570c96f22b1d9517770e36c695677f5e Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Fri, 17 Jan 2020 13:14:22 -0500
Subject: multicolumn performance improvements and refactor

---
 .../collections/CollectionMulticolumnView.tsx      | 149 ++++++++++++++-------
 1 file changed, 104 insertions(+), 45 deletions(-)

(limited to 'src/client/views/collections/CollectionMulticolumnView.tsx')

diff --git a/src/client/views/collections/CollectionMulticolumnView.tsx b/src/client/views/collections/CollectionMulticolumnView.tsx
index 51064d5c3..2cb91f239 100644
--- a/src/client/views/collections/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/CollectionMulticolumnView.tsx
@@ -4,11 +4,12 @@ import { documentSchema } from '../../../new_fields/documentSchemas';
 import { CollectionSubView } from './CollectionSubView';
 import * as React from "react";
 import { Doc } from '../../../new_fields/Doc';
-import { NumCast, StrCast } from '../../../new_fields/Types';
+import { NumCast, StrCast, BoolCast } from '../../../new_fields/Types';
 import { ContentFittingDocumentView } from './../nodes/ContentFittingDocumentView';
 import { Utils } from '../../../Utils';
 import "./collectionMulticolumnView.scss";
-import { computed } from 'mobx';
+import { computed, trace } from 'mobx';
+import { Transform } from '../../util/Transform';
 
 type MulticolumnDocument = makeInterface<[typeof documentSchema]>;
 const MulticolumnDocument = makeInterface(documentSchema);
@@ -33,17 +34,24 @@ interface LayoutData {
 
 const resolvedUnits = ["*", "px"];
 const resizerWidth = 2;
+const resizerOpacity = 0.4;
 
 @observer
 export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocument) {
 
+    @computed
+    private get ratioDefinedDocs() {
+        return this.childLayoutPairs.map(({ layout }) => layout).filter(({ widthUnit }) => StrCast(widthUnit) === "*");
+    }
+
     @computed
     private get resolvedLayoutInformation(): LayoutData {
         const unresolved: Unresolved[] = [];
         let starSum = 0, numFixed = 0, numRatio = 0;
-        for (const pair of this.childLayoutPairs) {
-            const unit = StrCast(pair.layout.widthUnit);
-            const magnitude = NumCast(pair.layout.widthMagnitude);
+
+        for (const { layout } of this.childLayoutPairs) {
+            const unit = StrCast(layout.widthUnit);
+            const magnitude = NumCast(layout.widthMagnitude);
             if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
                 if (unit === "*") {
                     starSum += magnitude;
@@ -51,11 +59,17 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
                 } else {
                     numFixed++;
                 }
-                unresolved.push({ target: pair.layout, magnitude, unit });
+                unresolved.push({ target: layout, magnitude, unit });
             }
             // otherwise, the particular configuration entry is ignored and the remaining
             // space is allocated as if the document were absent from the configuration list
         }
+
+        setTimeout(() => {
+            const minimum = Math.min(...this.ratioDefinedDocs.map(({ widthMagnitude }) => NumCast(widthMagnitude)));
+            this.ratioDefinedDocs.forEach(layout => layout.widthMagnitude = NumCast(layout.widthMagnitude) / minimum);
+        });
+
         return { unresolved, numRatio, numFixed, starSum };
     }
 
@@ -109,48 +123,68 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
         }
     }
 
-    @computed
-    private get contents(): JSX.Element[] | null {
-        const layout = this.resolvedLayoutInformation;
+    private getColumnUnitLength = () => this.columnUnitLength;
+
+    private lookupPixels = (layout: Doc): number => {
         const columnUnitLength = this.columnUnitLength;
-        if (layout === null || columnUnitLength === undefined) {
-            return (null); // we're still waiting on promises to resolve
+        if (columnUnitLength === undefined) {
+            return 0; // we're still waiting on promises to resolve
         }
-        const resolved: Resolved[] = [];
-        layout.unresolved.forEach(item => {
-            const { unit, magnitude, ...remaining } = item;
-            let width = magnitude;
-            if (unit === "*") {
-                width = magnitude * columnUnitLength;
+        let width = NumCast(layout.widthMagnitude);
+        if (StrCast(layout.widthUnit) === "*") {
+            width *= columnUnitLength;
+        }
+        return width;
+    }
+
+    private lookupIndividualTransform = (layout: Doc) => {
+        const columnUnitLength = this.columnUnitLength;
+        if (columnUnitLength === undefined) {
+            return Transform.Identity(); // we're still waiting on promises to resolve
+        }
+        let offset = 0;
+        for (const { layout: candidate } of this.childLayoutPairs) {
+            if (candidate === layout) {
+                const shift = offset;
+                return this.props.ScreenToLocalTransform().translate(-shift, 0);
             }
-            resolved.push({ pixels: width, ...remaining });
-        });
+            offset += this.lookupPixels(candidate) + resizerWidth;
+        }
+        return Transform.Identity(); // type coersion, this case should never be hit
+    }
+
+    @computed
+    private get contents(): JSX.Element[] | null {
+        trace();
+        const { childLayoutPairs } = this;
+        const { Document, PanelHeight } = this.props;
         const collector: JSX.Element[] = [];
-        let offset = 0;
-        for (let i = 0; i < resolved.length; i++) {
-            const { target, pixels } = resolved[i];
-            const shiftX = offset;
+        for (let i = 0; i < childLayoutPairs.length; i++) {
+            const { layout } = childLayoutPairs[i];
             collector.push(
-                <div className={"fish"}>
+                <div className={"document-wrapper"}>
                     <ContentFittingDocumentView
                         {...this.props}
                         key={Utils.GenerateGuid()}
-                        Document={target}
-                        DataDocument={target.resolvedDataDoc as Doc}
-                        PanelWidth={() => pixels}
-                        getTransform={() => this.props.ScreenToLocalTransform().translate(-shiftX, 0)}
+                        Document={layout}
+                        DataDocument={layout.resolvedDataDoc as Doc}
+                        PanelWidth={() => this.lookupPixels(layout)}
+                        PanelHeight={() => PanelHeight() - (BoolCast(Document.showWidthLabels) ? 20 : 0)}
+                        getTransform={() => this.lookupIndividualTransform(layout)}
+                    />
+                    <WidthLabel
+                        layout={layout}
+                        collectionDoc={Document}
                     />
-                    <span className={"display"}>{NumCast(target.widthMagnitude).toFixed(3)} {StrCast(target.widthUnit)}</span>
                 </div>,
                 <ResizeBar
                     width={resizerWidth}
                     key={Utils.GenerateGuid()}
-                    columnUnitLength={columnUnitLength}
-                    toLeft={target}
-                    toRight={resolved[i + 1]?.target}
+                    columnUnitLength={this.getColumnUnitLength}
+                    toLeft={layout}
+                    toRight={childLayoutPairs[i + 1]?.layout}
                 />
             );
-            offset += pixels + resizerWidth;
         }
         collector.pop(); // removes the final extraneous resize bar
         return collector;
@@ -171,11 +205,35 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
 
 interface SpacerProps {
     width: number;
-    columnUnitLength: number;
+    columnUnitLength(): number | undefined;
     toLeft?: Doc;
     toRight?: Doc;
 }
 
+interface WidthLabelProps {
+    layout: Doc;
+    collectionDoc: Doc;
+    decimals?: number;
+}
+
+@observer
+class WidthLabel extends React.Component<WidthLabelProps> {
+
+    @computed
+    private get contents() {
+        const { layout, decimals } = this.props;
+        const magnitude = NumCast(layout.widthMagnitude).toFixed(decimals ?? 3);
+        const unit = StrCast(layout.widthUnit);
+        return <span className={"display"}>{magnitude} {unit}</span>;
+    }
+
+    render() {
+        return BoolCast(this.props.collectionDoc.showWidthLabels) ? this.contents : (null);
+    }
+
+}
+
+@observer
 class ResizeBar extends React.Component<SpacerProps> {
 
     private registerResizing = (e: React.PointerEvent<HTMLDivElement>) => {
@@ -190,33 +248,34 @@ class ResizeBar extends React.Component<SpacerProps> {
     private onPointerMove = ({ movementX }: PointerEvent) => {
         const { toLeft, toRight, columnUnitLength } = this.props;
         const target = movementX > 0 ? toRight : toLeft;
-        if (target) {
+        const unitLength = columnUnitLength();
+        if (target && unitLength) {
             const { widthUnit, widthMagnitude } = target;
             if (widthUnit === "*") {
-                target.widthMagnitude = NumCast(widthMagnitude) - Math.abs(movementX) / columnUnitLength;
+                target.widthMagnitude = NumCast(widthMagnitude) - Math.abs(movementX) / unitLength;
             }
         }
     }
 
-    private get opacity() {
+    private get isActivated() {
         const { toLeft, toRight } = this.props;
         if (toLeft && toRight) {
             if (StrCast(toLeft.widthUnit) === "px" && StrCast(toRight.widthUnit) === "px") {
-                return 0;
+                return false;
             }
-            return 0.4;
+            return true;
         } else if (toLeft) {
             if (StrCast(toLeft.widthUnit) === "px") {
-                return 0;
+                return false;
             }
-            return 0.4;
+            return true;
         } else if (toRight) {
             if (StrCast(toRight.widthUnit) === "px") {
-                return 0;
+                return false;
             }
-            return 0.4;
+            return true;
         }
-        return 0;
+        return false;
     }
 
     private onPointerUp = () => {
@@ -230,7 +289,7 @@ class ResizeBar extends React.Component<SpacerProps> {
                 className={"resizer"}
                 style={{
                     width: this.props.width,
-                    opacity: this.opacity
+                    opacity: this.isActivated ? resizerOpacity : 0
                 }}
                 onPointerDown={this.registerResizing}
             />
-- 
cgit v1.2.3-70-g09d2

