From 6caae7aa4212d84ef320fe2be412c7e603483e81 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Thu, 16 Jan 2020 19:09:13 -0500
Subject: basic multicolumn resizing implemented, still not performant at all

---
 src/client/views/CollectionMulticolumnView.tsx | 171 +++++++++++++++++++++----
 1 file changed, 144 insertions(+), 27 deletions(-)

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

diff --git a/src/client/views/CollectionMulticolumnView.tsx b/src/client/views/CollectionMulticolumnView.tsx
index 3231c0da1..ded2aa9df 100644
--- a/src/client/views/CollectionMulticolumnView.tsx
+++ b/src/client/views/CollectionMulticolumnView.tsx
@@ -11,10 +11,24 @@ import { ContentFittingDocumentView } from './nodes/ContentFittingDocumentView';
 import { Utils } from '../../Utils';
 import { Transform } from '../util/Transform';
 import "./collectionMulticolumnView.scss";
+import { computed } from 'mobx';
 
 type MulticolumnDocument = makeInterface<[typeof documentSchema]>;
 const MulticolumnDocument = makeInterface(documentSchema);
 
+interface LayoutUnit {
+    config: Doc;
+    target: Doc;
+}
+
+interface Fixed extends LayoutUnit {
+    pixels: number;
+}
+
+interface Proportional extends LayoutUnit {
+    ratio: number;
+}
+
 @observer
 export default class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocument) {
     private _dropDisposer?: DragManager.DragDropDisposer;
@@ -41,37 +55,140 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
 
     public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
 
+    @computed
+    private get layoutInformation() {
+        const fixed: Fixed[] = [];
+        const proportional: Proportional[] = [];
+        let ratioSum = 0;
+        for (const config of this.configuration) {
+            const { columnWidth, target } = config;
+            if (!(target instanceof Doc)) {
+                // we're still waiting on promises, so it's not worth rendering anything yet
+                return (null);
+            }
+            const widthSpecifier = Cast(columnWidth, "number");
+            let matches: RegExpExecArray | null;
+            if (widthSpecifier !== undefined) {
+                // we've gotten a number, referring to a pixel value
+                fixed.push({ config, target, pixels: widthSpecifier });
+            } else if ((matches = /^(\d+(\.\d+)?)\*/.exec(StrCast(columnWidth))) !== null) {
+                // we've gotten a proportional measure, like 1.8*
+                const ratio = Number(matches[1]);
+                ratioSum += ratio;
+                proportional.push({ config, target, ratio });
+            }
+            // otherwise, the particular configuration entry is ignored and the remaining
+            // space is allocated as if the document were absent from the configuration list
+        }
+        return { fixed, proportional, ratioSum };
+    }
+
+    @computed private get totalFixedPool() {
+        return this.layoutInformation?.fixed.reduce((sum, unit) => sum + unit.pixels, 0);
+    }
+
+    @computed private get totalProportionalPool() {
+        const { totalFixedPool } = this;
+        return totalFixedPool !== undefined ? this.props.PanelWidth() - totalFixedPool : undefined;
+    }
+
+    @computed private get columnUnitLength() {
+        const layout = this.layoutInformation;
+        const { totalProportionalPool } = this;
+        if (layout !== null && totalProportionalPool !== undefined) {
+            const { ratioSum, proportional } = layout;
+            return (totalProportionalPool - 2 * (proportional.length - 1)) / ratioSum;
+        }
+        return undefined;
+    }
+
+    @computed
+    private get contents(): JSX.Element[] | null {
+        const layout = this.layoutInformation;
+        if (layout === null) {
+            return (null);
+        }
+        const { fixed, proportional } = layout;
+        const { columnUnitLength } = this;
+        if (columnUnitLength === undefined) {
+            return (null);
+        }
+        const { GenerateGuid } = Utils;
+        const toView = ({ target, pixels }: Fixed) =>
+            <ContentFittingDocumentView
+                {...this.props}
+                key={GenerateGuid()}
+                Document={target}
+                DataDocument={undefined}
+                PanelWidth={() => pixels}
+                getTransform={this.props.ScreenToLocalTransform}
+            />;
+        const collector: JSX.Element[] = fixed.map(toView);
+        const resolvedColumns = proportional.map(({ target, ratio, config }) => ({ target, pixels: ratio * columnUnitLength, config }));
+        for (let i = 0; i < resolvedColumns.length; i++) {
+            collector.push(toView(resolvedColumns[i]));
+            collector.push(
+                <MulticolumnSpacer
+                    key={GenerateGuid()}
+                    columnBaseUnit={columnUnitLength}
+                    toLeft={resolvedColumns[i].config}
+                    toRight={resolvedColumns[i + 1]?.config}
+                />
+            );
+        }
+        collector.pop();
+        return collector;
+    }
+
     render() {
-        const { PanelWidth } = this.props;
         return (
-            <div className={"collectionMulticolumnView_outer"}>
-                <div className={"collectionMulticolumnView_contents"}>
-                    {this.configuration.map(config => {
-                        const { target, columnWidth } = config;
-                        if (target instanceof Doc) {
-                            let computedWidth: number = 0;
-                            const widthSpecifier = Cast(columnWidth, "number");
-                            let matches: RegExpExecArray | null;
-                            if (widthSpecifier !== undefined) {
-                                computedWidth = widthSpecifier;
-                            } else if ((matches = /([\d.]+)\%/.exec(StrCast(columnWidth))) !== null) {
-                                computedWidth = Number(matches[1]) / 100 * PanelWidth();
-                            }
-                            return (!computedWidth ? (null) :
-                                <ContentFittingDocumentView
-                                    {...this.props}
-                                    Document={target}
-                                    DataDocument={undefined}
-                                    PanelWidth={() => computedWidth}
-                                    getTransform={this.props.ScreenToLocalTransform}
-                                />
-                            );
-                        }
-                        return (null);
-                    })}
-                </div>
+            <div className={"collectionMulticolumnView_contents"}>
+                {this.contents}
             </div>
         );
     }
 
+}
+
+interface SpacerProps {
+    columnBaseUnit: number;
+    toLeft?: Doc;
+    toRight?: Doc;
+}
+
+class MulticolumnSpacer extends React.Component<SpacerProps> {
+
+    private registerResizing = (e: React.PointerEvent<HTMLDivElement>) => {
+        e.stopPropagation();
+        e.preventDefault();
+        window.removeEventListener("pointermove", this.onPointerMove);
+        window.removeEventListener("pointerup", this.onPointerUp);
+        window.addEventListener("pointermove", this.onPointerMove);
+        window.addEventListener("pointerup", this.onPointerUp);
+    }
+
+    private onPointerMove = ({ movementX }: PointerEvent) => {
+        const { toLeft, toRight, columnBaseUnit } = this.props;
+        const target = movementX > 0 ? toRight : toLeft;
+        if (target) {
+            let widthSpecifier = Number(StrCast(target.columnWidth).replace("*", ""));
+            widthSpecifier -= Math.abs(movementX) / columnBaseUnit;
+            target.columnWidth = `${widthSpecifier}*`;
+        }
+    }
+
+    private onPointerUp = () => {
+        window.removeEventListener("pointermove", this.onPointerMove);
+        window.removeEventListener("pointerup", this.onPointerUp);
+    }
+
+    render() {
+        return (
+            <div
+                className={"spacer"}
+                onPointerDown={this.registerResizing}
+            />
+        );
+    }
+
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 42b20c17047037ddc2c8e7f3c3625276ea8df5fa Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Thu, 16 Jan 2020 20:04:58 -0500
Subject: much more flexible approach for distributing fixed width targets

---
 src/client/views/CollectionMulticolumnView.tsx | 70 ++++++++++++++++++--------
 1 file changed, 49 insertions(+), 21 deletions(-)

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

diff --git a/src/client/views/CollectionMulticolumnView.tsx b/src/client/views/CollectionMulticolumnView.tsx
index ded2aa9df..653ff5b84 100644
--- a/src/client/views/CollectionMulticolumnView.tsx
+++ b/src/client/views/CollectionMulticolumnView.tsx
@@ -16,17 +16,22 @@ import { computed } from 'mobx';
 type MulticolumnDocument = makeInterface<[typeof documentSchema]>;
 const MulticolumnDocument = makeInterface(documentSchema);
 
-interface LayoutUnit {
+interface UnitBase {
     config: Doc;
     target: Doc;
 }
 
-interface Fixed extends LayoutUnit {
-    pixels: number;
+type WidthSpecifier = { pixels: number } | { ratio: number };
+interface LayoutUnit extends UnitBase {
+    specifier: WidthSpecifier;
 }
 
-interface Proportional extends LayoutUnit {
-    ratio: number;
+interface Fixed extends UnitBase {
+    specifier: { pixels: number };
+}
+
+interface Ratio extends UnitBase {
+    specifier: { ratio: number };
 }
 
 @observer
@@ -57,9 +62,10 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
 
     @computed
     private get layoutInformation() {
-        const fixed: Fixed[] = [];
-        const proportional: Proportional[] = [];
+        const values: LayoutUnit[] = [];
         let ratioSum = 0;
+        let fixedCount = 0;
+        let ratioCount = 0;
         for (const config of this.configuration) {
             const { columnWidth, target } = config;
             if (!(target instanceof Doc)) {
@@ -68,23 +74,37 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
             }
             const widthSpecifier = Cast(columnWidth, "number");
             let matches: RegExpExecArray | null;
+            let specifier: WidthSpecifier | null = null;
             if (widthSpecifier !== undefined) {
                 // we've gotten a number, referring to a pixel value
-                fixed.push({ config, target, pixels: widthSpecifier });
+                specifier = { pixels: widthSpecifier };
+                fixedCount++;
             } else if ((matches = /^(\d+(\.\d+)?)\*/.exec(StrCast(columnWidth))) !== null) {
                 // we've gotten a proportional measure, like 1.8*
                 const ratio = Number(matches[1]);
                 ratioSum += ratio;
-                proportional.push({ config, target, ratio });
+                specifier = { ratio };
+                ratioCount++;
+            }
+            if (specifier !== null) {
+                values.push({ config, target, specifier });
             }
             // otherwise, the particular configuration entry is ignored and the remaining
             // space is allocated as if the document were absent from the configuration list
         }
-        return { fixed, proportional, ratioSum };
+        return { values, ratioCount, fixedCount, ratioSum };
     }
 
     @computed private get totalFixedPool() {
-        return this.layoutInformation?.fixed.reduce((sum, unit) => sum + unit.pixels, 0);
+        const fixed: Fixed[] = [];
+        const layout = this.layoutInformation;
+        if (!layout) {
+            return undefined;
+        }
+        layout.values.forEach(unit => {
+            ("pixels" in unit.specifier) && fixed.push(unit as Fixed);
+        });
+        return fixed.reduce((sum, unit) => sum + unit.specifier.pixels, 0);
     }
 
     @computed private get totalProportionalPool() {
@@ -96,8 +116,8 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
         const layout = this.layoutInformation;
         const { totalProportionalPool } = this;
         if (layout !== null && totalProportionalPool !== undefined) {
-            const { ratioSum, proportional } = layout;
-            return (totalProportionalPool - 2 * (proportional.length - 1)) / ratioSum;
+            const { ratioSum, values } = layout;
+            return (totalProportionalPool - 2 * (values.length - 1)) / ratioSum;
         }
         return undefined;
     }
@@ -108,13 +128,13 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
         if (layout === null) {
             return (null);
         }
-        const { fixed, proportional } = layout;
+        const { values } = layout;
         const { columnUnitLength } = this;
         if (columnUnitLength === undefined) {
             return (null);
         }
         const { GenerateGuid } = Utils;
-        const toView = ({ target, pixels }: Fixed) =>
+        const toView = ({ target, specifier: { pixels } }: Fixed) =>
             <ContentFittingDocumentView
                 {...this.props}
                 key={GenerateGuid()}
@@ -123,16 +143,24 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
                 PanelWidth={() => pixels}
                 getTransform={this.props.ScreenToLocalTransform}
             />;
-        const collector: JSX.Element[] = fixed.map(toView);
-        const resolvedColumns = proportional.map(({ target, ratio, config }) => ({ target, pixels: ratio * columnUnitLength, config }));
-        for (let i = 0; i < resolvedColumns.length; i++) {
-            collector.push(toView(resolvedColumns[i]));
+        const resolved: Fixed[] = [];
+        values.forEach(value => {
+            const { specifier, ...remaining } = value;
+            if ("ratio" in specifier) {
+                resolved.push({ ...remaining, specifier: { pixels: specifier.ratio * columnUnitLength } });
+            } else {
+                resolved.push(value as Fixed);
+            }
+        });
+        const collector: JSX.Element[] = [];
+        for (let i = 0; i < resolved.length; i++) {
+            collector.push(toView(resolved[i]));
             collector.push(
                 <MulticolumnSpacer
                     key={GenerateGuid()}
                     columnBaseUnit={columnUnitLength}
-                    toLeft={resolvedColumns[i].config}
-                    toRight={resolvedColumns[i + 1]?.config}
+                    toLeft={resolved[i].config}
+                    toRight={resolved[i + 1]?.config}
                 />
             );
         }
-- 
cgit v1.2.3-70-g09d2


From 1055a2db6b14cbcd1eb63727995a51153ba79128 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Thu, 16 Jan 2020 22:09:55 -0500
Subject: logical differentiation between unit and magnitude for multicolumn
 view config width data, additional commenting and cleanup

---
 src/client/views/CollectionMulticolumnView.tsx | 221 +++++++++++++------------
 1 file changed, 116 insertions(+), 105 deletions(-)

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

diff --git a/src/client/views/CollectionMulticolumnView.tsx b/src/client/views/CollectionMulticolumnView.tsx
index 653ff5b84..61ce8d99c 100644
--- a/src/client/views/CollectionMulticolumnView.tsx
+++ b/src/client/views/CollectionMulticolumnView.tsx
@@ -16,27 +16,31 @@ import { computed } from 'mobx';
 type MulticolumnDocument = makeInterface<[typeof documentSchema]>;
 const MulticolumnDocument = makeInterface(documentSchema);
 
-interface UnitBase {
+interface Unresolved {
     config: Doc;
     target: Doc;
+    magnitude: number;
+    unit: string;
 }
 
-type WidthSpecifier = { pixels: number } | { ratio: number };
-interface LayoutUnit extends UnitBase {
-    specifier: WidthSpecifier;
-}
-
-interface Fixed extends UnitBase {
-    specifier: { pixels: number };
+interface Resolved {
+    config: Doc;
+    target: Doc;
+    pixels: number;
 }
 
-interface Ratio extends UnitBase {
-    specifier: { ratio: number };
-}
+const resolvedUnits = ["*", "px"];
 
 @observer
 export default class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocument) {
     private _dropDisposer?: DragManager.DragDropDisposer;
+
+    /**
+     * Returns the list of so-called configuration documents.
+     * Each one is a wrapper around what we typically think of as
+     * the child document, just also encoding the magnitude and unit
+     * of the specified width.
+     */
     private get configuration() {
         const { Document } = this.props;
         if (!Document.multicolumnData) {
@@ -45,126 +49,132 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
         return DocListCast(this.Document.multicolumnData);
     }
 
-    protected createDropTarget = (ele: HTMLDivElement) => {
-        this._dropDisposer && this._dropDisposer();
-        if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
-        }
-    }
-
-    getTransform = (ele: React.RefObject<HTMLDivElement>) => () => {
-        if (!ele.current) return Transform.Identity();
-        const { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current);
-        return new Transform(-translateX, -translateY, 1 / scale);
-    }
-
-    public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
-
     @computed
-    private get layoutInformation() {
-        const values: LayoutUnit[] = [];
-        let ratioSum = 0;
-        let fixedCount = 0;
-        let ratioCount = 0;
+    private get resolvedLayoutInformation() {
+        const unresolved: Unresolved[] = [];
+        let ratioSum = 0, numFixed = 0, numRatio = 0;
         for (const config of this.configuration) {
-            const { columnWidth, target } = config;
-            if (!(target instanceof Doc)) {
-                // we're still waiting on promises, so it's not worth rendering anything yet
-                return (null);
+            const { target, widthMagnitude, widthUnit } = config;
+            if (target instanceof Doc) {
+                const unit = StrCast(widthUnit);
+                const magnitude = NumCast(widthMagnitude);
+                if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
+                    switch (unit) {
+                        case "*":
+                            ratioSum += magnitude;
+                            numRatio++;
+                            break;
+                        case "px":
+                            numFixed++;
+                            break;
+                    }
+                    unresolved.push({ config, target, 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
             }
-            const widthSpecifier = Cast(columnWidth, "number");
-            let matches: RegExpExecArray | null;
-            let specifier: WidthSpecifier | null = null;
-            if (widthSpecifier !== undefined) {
-                // we've gotten a number, referring to a pixel value
-                specifier = { pixels: widthSpecifier };
-                fixedCount++;
-            } else if ((matches = /^(\d+(\.\d+)?)\*/.exec(StrCast(columnWidth))) !== null) {
-                // we've gotten a proportional measure, like 1.8*
-                const ratio = Number(matches[1]);
-                ratioSum += ratio;
-                specifier = { ratio };
-                ratioCount++;
-            }
-            if (specifier !== null) {
-                values.push({ config, target, specifier });
-            }
-            // otherwise, the particular configuration entry is ignored and the remaining
-            // space is allocated as if the document were absent from the configuration list
         }
-        return { values, ratioCount, fixedCount, ratioSum };
+        return { unresolved, numRatio, numFixed, ratioSum };
     }
 
-    @computed private get totalFixedPool() {
-        const fixed: Fixed[] = [];
-        const layout = this.layoutInformation;
+    /**
+     * This returns the total quantity, in pixels, that this
+     * view needs to reserve for child documents that have
+     * (with higher priority) requested a fixed pixel width.
+     * 
+     * If the underlying resolvedLayoutInformation returns null
+     * because we're waiting on promises to resolve, this value will be undefined as well.
+     */
+    @computed
+    private get totalFixedAllocation(): number | undefined {
+        const layout = this.resolvedLayoutInformation;
         if (!layout) {
             return undefined;
         }
-        layout.values.forEach(unit => {
-            ("pixels" in unit.specifier) && fixed.push(unit as Fixed);
-        });
-        return fixed.reduce((sum, unit) => sum + unit.specifier.pixels, 0);
+        let sum = 0;
+        for (const { magnitude, unit } of layout.unresolved) {
+            if (unit === "px") {
+                sum += magnitude;
+            }
+        }
+        return sum;
     }
 
-    @computed private get totalProportionalPool() {
-        const { totalFixedPool } = this;
-        return totalFixedPool !== undefined ? this.props.PanelWidth() - totalFixedPool : undefined;
+    /**
+     * This returns the total quantity, in pixels, that this
+     * view needs to reserve for child documents that have
+     * (with lower priority) requested a certain relative proportion of the
+     * remaining pixel width not allocated for fixed widths.
+     * 
+     * If the underlying totalFixedAllocation returns undefined
+     * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
+     */
+    @computed
+    private get totalRatioAllocation(): number | undefined {
+        const { totalFixedAllocation } = this;
+        return totalFixedAllocation !== undefined ? this.props.PanelWidth() - totalFixedAllocation : undefined;
     }
 
-    @computed private get columnUnitLength() {
-        const layout = this.layoutInformation;
-        const { totalProportionalPool } = this;
-        if (layout !== null && totalProportionalPool !== undefined) {
-            const { ratioSum, values } = layout;
-            return (totalProportionalPool - 2 * (values.length - 1)) / ratioSum;
+    /**
+     * This returns the total quantity, in pixels, that
+     * 1* (relative / star unit) is worth. For example,
+     * if the configuration had three documents, with, respectively,
+     * widths of 2*, 2* and 1*, and the panel width was 1000px,
+     * this value would return 1000 / (2 + 2 + 1), or 200px.
+     * This is then multiplied by each relative-width
+     * document's * factor to compute its actual width (400px, 400px and 200px).
+     * 
+     * If the underlying totalRatioAllocation or this.resolveLayoutInformation return undefined
+     * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
+     */
+    @computed
+    private get columnUnitLength(): number | undefined {
+        const layout = this.resolvedLayoutInformation;
+        const { totalRatioAllocation } = this;
+        if (layout !== null && totalRatioAllocation !== undefined) {
+            const { ratioSum, unresolved } = layout;
+            return (totalRatioAllocation - 2 * (unresolved.length - 1)) / ratioSum;
         }
         return undefined;
     }
 
     @computed
     private get contents(): JSX.Element[] | null {
-        const layout = this.layoutInformation;
-        if (layout === null) {
-            return (null);
-        }
-        const { values } = layout;
-        const { columnUnitLength } = this;
-        if (columnUnitLength === undefined) {
-            return (null);
+        const layout = this.resolvedLayoutInformation;
+        const columnUnitLength = this.columnUnitLength;
+        if (layout === null || columnUnitLength === undefined) {
+            return (null); // we're still waiting on promises to resolve
         }
-        const { GenerateGuid } = Utils;
-        const toView = ({ target, specifier: { pixels } }: Fixed) =>
-            <ContentFittingDocumentView
+        const resolved: Resolved[] = [];
+        layout.unresolved.forEach(value => {
+            const { unit, magnitude, ...remaining } = value;
+            let width = magnitude;
+            if (unit === "*") {
+                width = magnitude * columnUnitLength;
+            }
+            resolved.push({ pixels: width, ...remaining, });
+        });
+        const collector: JSX.Element[] = [];
+        for (let i = 0; i < resolved.length; i++) {
+            const { target, pixels, config } = resolved[i];
+            collector.push(<ContentFittingDocumentView
                 {...this.props}
-                key={GenerateGuid()}
+                key={Utils.GenerateGuid()}
                 Document={target}
                 DataDocument={undefined}
                 PanelWidth={() => pixels}
                 getTransform={this.props.ScreenToLocalTransform}
-            />;
-        const resolved: Fixed[] = [];
-        values.forEach(value => {
-            const { specifier, ...remaining } = value;
-            if ("ratio" in specifier) {
-                resolved.push({ ...remaining, specifier: { pixels: specifier.ratio * columnUnitLength } });
-            } else {
-                resolved.push(value as Fixed);
-            }
-        });
-        const collector: JSX.Element[] = [];
-        for (let i = 0; i < resolved.length; i++) {
-            collector.push(toView(resolved[i]));
+            />);
             collector.push(
                 <MulticolumnSpacer
-                    key={GenerateGuid()}
-                    columnBaseUnit={columnUnitLength}
-                    toLeft={resolved[i].config}
+                    key={Utils.GenerateGuid()}
+                    columnUnitLength={columnUnitLength}
+                    toLeft={config}
                     toRight={resolved[i + 1]?.config}
                 />
             );
         }
-        collector.pop();
+        collector.pop(); // not the cleanest, but simply removes the final extraneous spacer
         return collector;
     }
 
@@ -179,7 +189,7 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
 }
 
 interface SpacerProps {
-    columnBaseUnit: number;
+    columnUnitLength: number;
     toLeft?: Doc;
     toRight?: Doc;
 }
@@ -196,12 +206,13 @@ class MulticolumnSpacer extends React.Component<SpacerProps> {
     }
 
     private onPointerMove = ({ movementX }: PointerEvent) => {
-        const { toLeft, toRight, columnBaseUnit } = this.props;
+        const { toLeft, toRight, columnUnitLength } = this.props;
         const target = movementX > 0 ? toRight : toLeft;
         if (target) {
-            let widthSpecifier = Number(StrCast(target.columnWidth).replace("*", ""));
-            widthSpecifier -= Math.abs(movementX) / columnBaseUnit;
-            target.columnWidth = `${widthSpecifier}*`;
+            const { widthUnit, widthMagnitude } = target;
+            if (widthUnit === "*") {
+                target.widthMagnitude = NumCast(widthMagnitude) - Math.abs(movementX) / columnUnitLength;
+            }
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 05eb320bef0395fef704f583d69673bfa5abaa77 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Fri, 17 Jan 2020 00:39:03 -0500
Subject: visual displays for widths

---
 src/client/views/CollectionMulticolumnView.scss    | 12 ++-
 src/client/views/CollectionMulticolumnView.tsx     | 91 ++++++++++++----------
 .../views/collections/CollectionDockingView.tsx    |  2 +-
 .../views/collections/CollectionPivotView.tsx      |  2 +-
 4 files changed, 65 insertions(+), 42 deletions(-)

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

diff --git a/src/client/views/CollectionMulticolumnView.scss b/src/client/views/CollectionMulticolumnView.scss
index 1c2389809..8c4410f01 100644
--- a/src/client/views/CollectionMulticolumnView.scss
+++ b/src/client/views/CollectionMulticolumnView.scss
@@ -4,8 +4,18 @@
     height: 100%;
     overflow: hidden;
 
+    .fish {
+        display: flex;
+        flex-direction: column;
+
+        .display {
+            text-align: center;
+            height: 20px;
+        }
+
+    }
+
     .spacer {
-        width: 2px;
         background: black;
         cursor: ew-resize;
         opacity: 0.2;
diff --git a/src/client/views/CollectionMulticolumnView.tsx b/src/client/views/CollectionMulticolumnView.tsx
index 61ce8d99c..20923d8e6 100644
--- a/src/client/views/CollectionMulticolumnView.tsx
+++ b/src/client/views/CollectionMulticolumnView.tsx
@@ -1,15 +1,14 @@
 import { observer } from 'mobx-react';
-import { makeInterface, listSpec } from '../../new_fields/Schema';
+import { makeInterface } from '../../new_fields/Schema';
 import { documentSchema } from '../../new_fields/documentSchemas';
 import { CollectionSubView } from './collections/CollectionSubView';
 import { DragManager } from '../util/DragManager';
 import * as React from "react";
 import { Doc, DocListCast } from '../../new_fields/Doc';
-import { NumCast, Cast, StrCast } from '../../new_fields/Types';
+import { NumCast, StrCast } from '../../new_fields/Types';
 import { List } from '../../new_fields/List';
 import { ContentFittingDocumentView } from './nodes/ContentFittingDocumentView';
 import { Utils } from '../../Utils';
-import { Transform } from '../util/Transform';
 import "./collectionMulticolumnView.scss";
 import { computed } from 'mobx';
 
@@ -29,7 +28,15 @@ interface Resolved {
     pixels: number;
 }
 
+interface LayoutData {
+    unresolved: Unresolved[];
+    numFixed: number;
+    numRatio: number;
+    starSum: number;
+}
+
 const resolvedUnits = ["*", "px"];
+const resizerWidth = 2;
 
 @observer
 export default class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocument) {
@@ -50,23 +57,20 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
     }
 
     @computed
-    private get resolvedLayoutInformation() {
+    private get resolvedLayoutInformation(): LayoutData {
         const unresolved: Unresolved[] = [];
-        let ratioSum = 0, numFixed = 0, numRatio = 0;
+        let starSum = 0, numFixed = 0, numRatio = 0;
         for (const config of this.configuration) {
             const { target, widthMagnitude, widthUnit } = config;
             if (target instanceof Doc) {
                 const unit = StrCast(widthUnit);
                 const magnitude = NumCast(widthMagnitude);
                 if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
-                    switch (unit) {
-                        case "*":
-                            ratioSum += magnitude;
-                            numRatio++;
-                            break;
-                        case "px":
-                            numFixed++;
-                            break;
+                    if (unit === "*") {
+                        starSum += magnitude;
+                        numRatio++;
+                    } else {
+                        numFixed++;
                     }
                     unresolved.push({ config, target, magnitude, unit });
                 }
@@ -74,7 +78,7 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
                 // space is allocated as if the document were absent from the configuration list
             }
         }
-        return { unresolved, numRatio, numFixed, ratioSum };
+        return { unresolved, numRatio, numFixed, starSum };
     }
 
     /**
@@ -112,17 +116,21 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
     @computed
     private get totalRatioAllocation(): number | undefined {
         const { totalFixedAllocation } = this;
-        return totalFixedAllocation !== undefined ? this.props.PanelWidth() - totalFixedAllocation : undefined;
+        const layout = this.resolvedLayoutInformation;
+        if (!layout) {
+            return undefined;
+        }
+        return totalFixedAllocation !== undefined ? this.props.PanelWidth() - (totalFixedAllocation + resizerWidth * (layout.unresolved.length - 1)) : undefined;
     }
 
     /**
      * This returns the total quantity, in pixels, that
      * 1* (relative / star unit) is worth. For example,
-     * if the configuration had three documents, with, respectively,
-     * widths of 2*, 2* and 1*, and the panel width was 1000px,
-     * this value would return 1000 / (2 + 2 + 1), or 200px.
-     * This is then multiplied by each relative-width
-     * document's * factor to compute its actual width (400px, 400px and 200px).
+     * if the configuration has three documents, with, respectively,
+     * widths of 2*, 2* and 1*, and the panel width returns 1000px,
+     * this accessor returns 1000 / (2 + 2 + 1), or 200px.
+     * Elsewhere, this is then multiplied by each relative-width
+     * document's (potentially decimal) * count to compute its actual width (400px, 400px and 200px).
      * 
      * If the underlying totalRatioAllocation or this.resolveLayoutInformation return undefined
      * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
@@ -131,11 +139,10 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
     private get columnUnitLength(): number | undefined {
         const layout = this.resolvedLayoutInformation;
         const { totalRatioAllocation } = this;
-        if (layout !== null && totalRatioAllocation !== undefined) {
-            const { ratioSum, unresolved } = layout;
-            return (totalRatioAllocation - 2 * (unresolved.length - 1)) / ratioSum;
+        if (layout === null || totalRatioAllocation === undefined) {
+            return undefined;
         }
-        return undefined;
+        return totalRatioAllocation / layout.starSum;
     }
 
     @computed
@@ -146,27 +153,31 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
             return (null); // we're still waiting on promises to resolve
         }
         const resolved: Resolved[] = [];
-        layout.unresolved.forEach(value => {
-            const { unit, magnitude, ...remaining } = value;
+        layout.unresolved.forEach(item => {
+            const { unit, magnitude, ...remaining } = item;
             let width = magnitude;
             if (unit === "*") {
                 width = magnitude * columnUnitLength;
             }
-            resolved.push({ pixels: width, ...remaining, });
+            resolved.push({ pixels: width, ...remaining });
         });
         const collector: JSX.Element[] = [];
         for (let i = 0; i < resolved.length; i++) {
             const { target, pixels, config } = resolved[i];
-            collector.push(<ContentFittingDocumentView
-                {...this.props}
-                key={Utils.GenerateGuid()}
-                Document={target}
-                DataDocument={undefined}
-                PanelWidth={() => pixels}
-                getTransform={this.props.ScreenToLocalTransform}
-            />);
             collector.push(
-                <MulticolumnSpacer
+                <div className={"fish"}>
+                    <ContentFittingDocumentView
+                        {...this.props}
+                        key={Utils.GenerateGuid()}
+                        Document={target}
+                        DataDocument={undefined}
+                        PanelWidth={() => pixels}
+                        getTransform={this.props.ScreenToLocalTransform}
+                    />
+                    <span className={"display"}>{NumCast(config.widthMagnitude).toFixed(3)} {StrCast(config.widthUnit)}</span>
+                </div>,
+                <ResizeBar
+                    width={resizerWidth}
                     key={Utils.GenerateGuid()}
                     columnUnitLength={columnUnitLength}
                     toLeft={config}
@@ -174,11 +185,11 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
                 />
             );
         }
-        collector.pop(); // not the cleanest, but simply removes the final extraneous spacer
+        collector.pop(); // removes the final extraneous resize bar
         return collector;
     }
 
-    render() {
+    render(): JSX.Element {
         return (
             <div className={"collectionMulticolumnView_contents"}>
                 {this.contents}
@@ -189,12 +200,13 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
 }
 
 interface SpacerProps {
+    width: number;
     columnUnitLength: number;
     toLeft?: Doc;
     toRight?: Doc;
 }
 
-class MulticolumnSpacer extends React.Component<SpacerProps> {
+class ResizeBar extends React.Component<SpacerProps> {
 
     private registerResizing = (e: React.PointerEvent<HTMLDivElement>) => {
         e.stopPropagation();
@@ -225,6 +237,7 @@ class MulticolumnSpacer extends React.Component<SpacerProps> {
         return (
             <div
                 className={"spacer"}
+                style={{ width: this.props.width }}
                 onPointerDown={this.registerResizing}
             />
         );
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index a7a124825..022eccc13 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -234,7 +234,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             if (found) {
                 Doc.GetProto(found.props.Document).data = new List<Doc>([document]);
             } else {
-                const stackView = Docs.Create.FreeformDocument([document], { fitToBox: true, isDisplayPanel: true, title: "document viewer" })
+                const stackView = Docs.Create.FreeformDocument([document], { fitToBox: true, isDisplayPanel: true, title: "document viewer" });
                 CollectionDockingView.AddRightSplit(stackView, undefined, []);
             }
         }
diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx
index d56052ed5..d6261c7ee 100644
--- a/src/client/views/collections/CollectionPivotView.tsx
+++ b/src/client/views/collections/CollectionPivotView.tsx
@@ -65,7 +65,7 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
     facetClick = (facet: string) => {
         const facetCollection = this.props.Document.facetCollection;
         if (facetCollection instanceof Doc) {
-            let found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facet);
+            const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facet);
             if (found !== -1) {
                 //Doc.RemoveDocFromList(facetCollection, "data", DocListCast(facetCollection.data)[found]);
                 (facetCollection.data as List<Doc>).splice(found, 1);
-- 
cgit v1.2.3-70-g09d2


From 2706f80b352e8a4fba692a9ad0b69ea2a7e25c2d Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Fri, 17 Jan 2020 01:25:02 -0500
Subject: opacity

---
 src/client/views/CollectionMulticolumnView.scss |  3 +--
 src/client/views/CollectionMulticolumnView.tsx  | 28 +++++++++++++++++++++++--
 2 files changed, 27 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/CollectionMulticolumnView.scss b/src/client/views/CollectionMulticolumnView.scss
index 8c4410f01..120603a0b 100644
--- a/src/client/views/CollectionMulticolumnView.scss
+++ b/src/client/views/CollectionMulticolumnView.scss
@@ -15,10 +15,9 @@
 
     }
 
-    .spacer {
+    .resizer {
         background: black;
         cursor: ew-resize;
-        opacity: 0.2;
     }
 
 }
\ No newline at end of file
diff --git a/src/client/views/CollectionMulticolumnView.tsx b/src/client/views/CollectionMulticolumnView.tsx
index 20923d8e6..0b2ca82f1 100644
--- a/src/client/views/CollectionMulticolumnView.tsx
+++ b/src/client/views/CollectionMulticolumnView.tsx
@@ -228,6 +228,27 @@ class ResizeBar extends React.Component<SpacerProps> {
         }
     }
 
+    private get opacity() {
+        const { toLeft, toRight } = this.props;
+        if (toLeft && toRight) {
+            if (StrCast(toLeft.widthUnit) === "px" && StrCast(toRight.widthUnit) === "px") {
+                return 0;
+            }
+            return 0.4;
+        } else if (toLeft) {
+            if (StrCast(toLeft.widthUnit) === "px") {
+                return 0;
+            }
+            return 0.4;
+        } else if (toRight) {
+            if (StrCast(toRight.widthUnit) === "px") {
+                return 0;
+            }
+            return 0.4;
+        }
+        return 0;
+    }
+
     private onPointerUp = () => {
         window.removeEventListener("pointermove", this.onPointerMove);
         window.removeEventListener("pointerup", this.onPointerUp);
@@ -236,8 +257,11 @@ class ResizeBar extends React.Component<SpacerProps> {
     render() {
         return (
             <div
-                className={"spacer"}
-                style={{ width: this.props.width }}
+                className={"resizer"}
+                style={{
+                    width: this.props.width,
+                    opacity: this.opacity
+                }}
                 onPointerDown={this.registerResizing}
             />
         );
-- 
cgit v1.2.3-70-g09d2


From 12f3a4a1bfce522c7e647317035ec0deda5c73d5 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 17 Jan 2020 10:28:52 -0500
Subject: moving things around.  changed multiColumnView to not use
 intermediate config docs.

---
 src/client/views/CollectionLinearView.scss         |  75 ---------------
 src/client/views/CollectionLinearView.tsx          | 106 ---------------------
 src/client/views/CollectionMulticolumnView.tsx     |  85 +++++------------
 src/client/views/MainView.tsx                      |   2 +-
 .../views/collections/CollectionLinearView.scss    |  75 +++++++++++++++
 .../views/collections/CollectionLinearView.tsx     | 106 +++++++++++++++++++++
 .../views/collections/CollectionPivotView.tsx      |   1 -
 src/client/views/collections/CollectionView.tsx    |  43 ++++-----
 8 files changed, 227 insertions(+), 266 deletions(-)
 delete mode 100644 src/client/views/CollectionLinearView.scss
 delete mode 100644 src/client/views/CollectionLinearView.tsx
 create mode 100644 src/client/views/collections/CollectionLinearView.scss
 create mode 100644 src/client/views/collections/CollectionLinearView.tsx

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

diff --git a/src/client/views/CollectionLinearView.scss b/src/client/views/CollectionLinearView.scss
deleted file mode 100644
index 81210d7ae..000000000
--- a/src/client/views/CollectionLinearView.scss
+++ /dev/null
@@ -1,75 +0,0 @@
-@import "globalCssVariables";
-@import "nodeModuleOverrides";
-
-.collectionLinearView-outer{
-    overflow: hidden;
-    height:100%;
-    .collectionLinearView {
-        display:flex;
-        height: 100%;
-        >label {
-            background: $dark-color;
-            color: $light-color;
-            display: inline-block;
-            border-radius: 18px;
-            font-size: 12.5px;
-            width: 18px;
-            height: 18px;
-            margin-top:auto;
-            margin-bottom:auto;
-            margin-right: 3px;
-            cursor: pointer;
-            transition: transform 0.2s;
-        }
-
-        label p {
-            padding-left:5px;
-        }
-
-        label:hover {
-            background: $main-accent;
-            transform: scale(1.15);
-        }
-
-        >input {
-            display: none;
-        }
-        >input:not(:checked)~.collectionLinearView-content {
-            display: none;
-        }
-
-        >input:checked~label {
-            transform: rotate(45deg);
-            transition: transform 0.5s;
-            cursor: pointer;
-        }
-
-        .collectionLinearView-content {
-            display: flex;
-            opacity: 1;
-            position: relative;
-            margin-top: auto;
-
-            .collectionLinearView-docBtn, .collectionLinearView-docBtn-scalable {
-                position:relative;
-                margin:auto;
-                margin-left: 3px;
-                transform-origin: center 80%;
-            }
-            .collectionLinearView-docBtn-scalable:hover {
-                transform: scale(1.15);
-            }
-
-            .collectionLinearView-round-button {
-                width: 18px;
-                height: 18px;
-                border-radius: 18px;
-                font-size: 15px;
-            }
-            
-            .collectionLinearView-round-button:hover {
-                transform: scale(1.15);
-            }
-        }
-    }
-}
diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx
deleted file mode 100644
index 5ca861f71..000000000
--- a/src/client/views/CollectionLinearView.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import { action, IReactionDisposer, observable, reaction } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Doc, HeightSym, WidthSym } from '../../new_fields/Doc';
-import { makeInterface } from '../../new_fields/Schema';
-import { BoolCast, NumCast, StrCast } from '../../new_fields/Types';
-import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils } from '../../Utils';
-import { DragManager } from '../util/DragManager';
-import { Transform } from '../util/Transform';
-import "./CollectionLinearView.scss";
-import { CollectionViewType } from './collections/CollectionView';
-import { CollectionSubView } from './collections/CollectionSubView';
-import { DocumentView } from './nodes/DocumentView';
-import { documentSchema } from '../../new_fields/documentSchemas';
-import { Id } from '../../new_fields/FieldSymbols';
-
-
-type LinearDocument = makeInterface<[typeof documentSchema,]>;
-const LinearDocument = makeInterface(documentSchema);
-
-@observer
-export class CollectionLinearView extends CollectionSubView(LinearDocument) {
-    @observable public addMenuToggle = React.createRef<HTMLInputElement>();
-    private _dropDisposer?: DragManager.DragDropDisposer;
-    private _widthDisposer?: IReactionDisposer;
-
-    componentWillUnmount() {
-        this._dropDisposer && this._dropDisposer();
-        this._widthDisposer && this._widthDisposer();
-    }
-
-    componentDidMount() {
-        // is there any reason this needs to exist? -syip.  yes, it handles autoHeight for stacking views (masonry isn't yet supported).
-        this._widthDisposer = reaction(() => NumCast(this.props.Document.height, 0) + this.childDocs.length + (this.props.Document.isExpanded ? 1 : 0),
-            () => this.props.Document.width = 5 + (this.props.Document.isExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
-            { fireImmediately: true }
-        );
-    }
-    protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
-        this._dropDisposer && this._dropDisposer();
-        if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
-        }
-    }
-
-    public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
-
-    dimension = () => NumCast(this.props.Document.height); // 2 * the padding
-    getTransform = (ele: React.RefObject<HTMLDivElement>) => () => {
-        if (!ele.current) return Transform.Identity();
-        const { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current);
-        return new Transform(-translateX, -translateY, 1 / scale);
-    }
-
-    render() {
-        const guid = Utils.GenerateGuid();
-        return <div className="collectionLinearView-outer">
-            <div className="collectionLinearView" ref={this.createDropTarget} >
-                <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.isExpanded)} ref={this.addMenuToggle}
-                    onChange={action((e: any) => this.props.Document.isExpanded = this.addMenuToggle.current!.checked)} />
-                <label htmlFor={`${guid}`} style={{ marginTop: "auto", marginBottom: "auto", background: StrCast(this.props.Document.backgroundColor, "black") === StrCast(this.props.Document.color, "white") ? "black" : StrCast(this.props.Document.backgroundColor, "black") }} title="Close Menu"><p>+</p></label>
-
-                <div className="collectionLinearView-content" style={{ height: this.dimension(), width: NumCast(this.props.Document.width, 25) }}>
-                    {this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => {
-                        const nested = pair.layout.viewType === CollectionViewType.Linear;
-                        const dref = React.createRef<HTMLDivElement>();
-                        const nativeWidth = NumCast(pair.layout.nativeWidth, this.dimension());
-                        const deltaSize = nativeWidth * .15 / 2;
-                        return <div className={`collectionLinearView-docBtn` + (pair.layout.onClick || pair.layout.onDragStart ? "-scalable" : "")} key={pair.layout[Id]} ref={dref}
-                            style={{
-                                width: nested ? pair.layout[WidthSym]() : this.dimension() - deltaSize,
-                                height: nested && pair.layout.isExpanded ? pair.layout[HeightSym]() : this.dimension() - deltaSize,
-                            }}  >
-                            <DocumentView
-                                Document={pair.layout}
-                                DataDoc={pair.data}
-                                LibraryPath={this.props.LibraryPath}
-                                addDocument={this.props.addDocument}
-                                moveDocument={this.props.moveDocument}
-                                addDocTab={this.props.addDocTab}
-                                pinToPres={emptyFunction}
-                                removeDocument={this.props.removeDocument}
-                                ruleProvider={undefined}
-                                onClick={undefined}
-                                ScreenToLocalTransform={this.getTransform(dref)}
-                                ContentScaling={returnOne}
-                                PanelWidth={nested ? pair.layout[WidthSym] : () => this.dimension()}// ugh - need to get rid of this inline function to avoid recomputing
-                                PanelHeight={nested ? pair.layout[HeightSym] : () => this.dimension()}
-                                renderDepth={this.props.renderDepth + 1}
-                                focus={emptyFunction}
-                                backgroundColor={returnEmptyString}
-                                parentActive={returnTrue}
-                                whenActiveChanged={emptyFunction}
-                                bringToFront={emptyFunction}
-                                ContainingCollectionView={undefined}
-                                ContainingCollectionDoc={undefined}
-                                zoomToScale={emptyFunction}
-                                getScale={returnOne}>
-                            </DocumentView>
-                        </div>;
-                    })}
-                </div>
-            </div>
-        </div>;
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/CollectionMulticolumnView.tsx b/src/client/views/CollectionMulticolumnView.tsx
index 0b2ca82f1..99242e8be 100644
--- a/src/client/views/CollectionMulticolumnView.tsx
+++ b/src/client/views/CollectionMulticolumnView.tsx
@@ -4,9 +4,8 @@ import { documentSchema } from '../../new_fields/documentSchemas';
 import { CollectionSubView } from './collections/CollectionSubView';
 import { DragManager } from '../util/DragManager';
 import * as React from "react";
-import { Doc, DocListCast } from '../../new_fields/Doc';
+import { Doc } from '../../new_fields/Doc';
 import { NumCast, StrCast } from '../../new_fields/Types';
-import { List } from '../../new_fields/List';
 import { ContentFittingDocumentView } from './nodes/ContentFittingDocumentView';
 import { Utils } from '../../Utils';
 import "./collectionMulticolumnView.scss";
@@ -16,14 +15,12 @@ type MulticolumnDocument = makeInterface<[typeof documentSchema]>;
 const MulticolumnDocument = makeInterface(documentSchema);
 
 interface Unresolved {
-    config: Doc;
     target: Doc;
     magnitude: number;
     unit: string;
 }
 
 interface Resolved {
-    config: Doc;
     target: Doc;
     pixels: number;
 }
@@ -40,43 +37,24 @@ const resizerWidth = 2;
 
 @observer
 export default class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocument) {
-    private _dropDisposer?: DragManager.DragDropDisposer;
-
-    /**
-     * Returns the list of so-called configuration documents.
-     * Each one is a wrapper around what we typically think of as
-     * the child document, just also encoding the magnitude and unit
-     * of the specified width.
-     */
-    private get configuration() {
-        const { Document } = this.props;
-        if (!Document.multicolumnData) {
-            Document.multicolumnData = new List<Doc>();
-        }
-        return DocListCast(this.Document.multicolumnData);
-    }
-
     @computed
     private get resolvedLayoutInformation(): LayoutData {
         const unresolved: Unresolved[] = [];
         let starSum = 0, numFixed = 0, numRatio = 0;
-        for (const config of this.configuration) {
-            const { target, widthMagnitude, widthUnit } = config;
-            if (target instanceof Doc) {
-                const unit = StrCast(widthUnit);
-                const magnitude = NumCast(widthMagnitude);
-                if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
-                    if (unit === "*") {
-                        starSum += magnitude;
-                        numRatio++;
-                    } else {
-                        numFixed++;
-                    }
-                    unresolved.push({ config, target, magnitude, unit });
+        for (const target of this.childDocs) {
+            const unit = StrCast(target.widthUnit);
+            const magnitude = NumCast(target.widthMagnitude);
+            if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
+                if (unit === "*") {
+                    starSum += magnitude;
+                    numRatio++;
+                } else {
+                    numFixed++;
                 }
-                // otherwise, the particular configuration entry is ignored and the remaining
-                // space is allocated as if the document were absent from the configuration list
+                unresolved.push({ target, 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
         }
         return { unresolved, numRatio, numFixed, starSum };
     }
@@ -91,17 +69,8 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
      */
     @computed
     private get totalFixedAllocation(): number | undefined {
-        const layout = this.resolvedLayoutInformation;
-        if (!layout) {
-            return undefined;
-        }
-        let sum = 0;
-        for (const { magnitude, unit } of layout.unresolved) {
-            if (unit === "px") {
-                sum += magnitude;
-            }
-        }
-        return sum;
+        return this.resolvedLayoutInformation?.unresolved.reduce(
+            (sum, { magnitude, unit }) => sum + (unit === "px" ? magnitude : 0), 0);
     }
 
     /**
@@ -115,12 +84,9 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
      */
     @computed
     private get totalRatioAllocation(): number | undefined {
-        const { totalFixedAllocation } = this;
-        const layout = this.resolvedLayoutInformation;
-        if (!layout) {
-            return undefined;
-        }
-        return totalFixedAllocation !== undefined ? this.props.PanelWidth() - (totalFixedAllocation + resizerWidth * (layout.unresolved.length - 1)) : undefined;
+        const layoutInfoLen = this.resolvedLayoutInformation?.unresolved.length;
+        if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined)
+            return this.props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1));
     }
 
     /**
@@ -137,12 +103,9 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
      */
     @computed
     private get columnUnitLength(): number | undefined {
-        const layout = this.resolvedLayoutInformation;
-        const { totalRatioAllocation } = this;
-        if (layout === null || totalRatioAllocation === undefined) {
-            return undefined;
+        if (this.resolvedLayoutInformation && this.totalRatioAllocation !== undefined) {
+            return this.totalRatioAllocation / this.resolvedLayoutInformation.starSum;
         }
-        return totalRatioAllocation / layout.starSum;
     }
 
     @computed
@@ -163,7 +126,7 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
         });
         const collector: JSX.Element[] = [];
         for (let i = 0; i < resolved.length; i++) {
-            const { target, pixels, config } = resolved[i];
+            const { target, pixels } = resolved[i];
             collector.push(
                 <div className={"fish"}>
                     <ContentFittingDocumentView
@@ -174,14 +137,14 @@ export default class CollectionMulticolumnView extends CollectionSubView(Multico
                         PanelWidth={() => pixels}
                         getTransform={this.props.ScreenToLocalTransform}
                     />
-                    <span className={"display"}>{NumCast(config.widthMagnitude).toFixed(3)} {StrCast(config.widthUnit)}</span>
+                    <span className={"display"}>{NumCast(target.widthMagnitude).toFixed(3)} {StrCast(target.widthUnit)}</span>
                 </div>,
                 <ResizeBar
                     width={resizerWidth}
                     key={Utils.GenerateGuid()}
                     columnUnitLength={columnUnitLength}
-                    toLeft={config}
-                    toRight={resolved[i + 1]?.config}
+                    toLeft={target}
+                    toRight={resolved[i + 1]?.target}
                 />
             );
         }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 91c7f909b..3648ddccf 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -22,7 +22,7 @@ import { Docs, DocumentOptions } from '../documents/Documents';
 import { HistoryUtil } from '../util/History';
 import SharingManager from '../util/SharingManager';
 import { Transform } from '../util/Transform';
-import { CollectionLinearView } from './CollectionLinearView';
+import { CollectionLinearView } from './collections/CollectionLinearView';
 import { CollectionViewType, CollectionView } from './collections/CollectionView';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { ContextMenu } from './ContextMenu';
diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss
new file mode 100644
index 000000000..eae9e0220
--- /dev/null
+++ b/src/client/views/collections/CollectionLinearView.scss
@@ -0,0 +1,75 @@
+@import "../globalCssVariables";
+@import "../_nodeModuleOverrides";
+
+.collectionLinearView-outer{
+    overflow: hidden;
+    height:100%;
+    .collectionLinearView {
+        display:flex;
+        height: 100%;
+        >label {
+            background: $dark-color;
+            color: $light-color;
+            display: inline-block;
+            border-radius: 18px;
+            font-size: 12.5px;
+            width: 18px;
+            height: 18px;
+            margin-top:auto;
+            margin-bottom:auto;
+            margin-right: 3px;
+            cursor: pointer;
+            transition: transform 0.2s;
+        }
+
+        label p {
+            padding-left:5px;
+        }
+
+        label:hover {
+            background: $main-accent;
+            transform: scale(1.15);
+        }
+
+        >input {
+            display: none;
+        }
+        >input:not(:checked)~.collectionLinearView-content {
+            display: none;
+        }
+
+        >input:checked~label {
+            transform: rotate(45deg);
+            transition: transform 0.5s;
+            cursor: pointer;
+        }
+
+        .collectionLinearView-content {
+            display: flex;
+            opacity: 1;
+            position: relative;
+            margin-top: auto;
+
+            .collectionLinearView-docBtn, .collectionLinearView-docBtn-scalable {
+                position:relative;
+                margin:auto;
+                margin-left: 3px;
+                transform-origin: center 80%;
+            }
+            .collectionLinearView-docBtn-scalable:hover {
+                transform: scale(1.15);
+            }
+
+            .collectionLinearView-round-button {
+                width: 18px;
+                height: 18px;
+                border-radius: 18px;
+                font-size: 15px;
+            }
+            
+            .collectionLinearView-round-button:hover {
+                transform: scale(1.15);
+            }
+        }
+    }
+}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
new file mode 100644
index 000000000..9191bf822
--- /dev/null
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -0,0 +1,106 @@
+import { action, IReactionDisposer, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, HeightSym, WidthSym } from '../../../new_fields/Doc';
+import { makeInterface } from '../../../new_fields/Schema';
+import { BoolCast, NumCast, StrCast } from '../../../new_fields/Types';
+import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils } from '../../../Utils';
+import { DragManager } from '../../util/DragManager';
+import { Transform } from '../../util/Transform';
+import "./CollectionLinearView.scss";
+import { CollectionViewType } from './CollectionView';
+import { CollectionSubView } from './CollectionSubView';
+import { DocumentView } from '../nodes/DocumentView';
+import { documentSchema } from '../../../new_fields/documentSchemas';
+import { Id } from '../../../new_fields/FieldSymbols';
+
+
+type LinearDocument = makeInterface<[typeof documentSchema,]>;
+const LinearDocument = makeInterface(documentSchema);
+
+@observer
+export class CollectionLinearView extends CollectionSubView(LinearDocument) {
+    @observable public addMenuToggle = React.createRef<HTMLInputElement>();
+    private _dropDisposer?: DragManager.DragDropDisposer;
+    private _widthDisposer?: IReactionDisposer;
+
+    componentWillUnmount() {
+        this._dropDisposer && this._dropDisposer();
+        this._widthDisposer && this._widthDisposer();
+    }
+
+    componentDidMount() {
+        // is there any reason this needs to exist? -syip.  yes, it handles autoHeight for stacking views (masonry isn't yet supported).
+        this._widthDisposer = reaction(() => NumCast(this.props.Document.height, 0) + this.childDocs.length + (this.props.Document.isExpanded ? 1 : 0),
+            () => this.props.Document.width = 5 + (this.props.Document.isExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
+            { fireImmediately: true }
+        );
+    }
+    protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
+        this._dropDisposer && this._dropDisposer();
+        if (ele) {
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+        }
+    }
+
+    public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
+
+    dimension = () => NumCast(this.props.Document.height); // 2 * the padding
+    getTransform = (ele: React.RefObject<HTMLDivElement>) => () => {
+        if (!ele.current) return Transform.Identity();
+        const { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current);
+        return new Transform(-translateX, -translateY, 1 / scale);
+    }
+
+    render() {
+        const guid = Utils.GenerateGuid();
+        return <div className="collectionLinearView-outer">
+            <div className="collectionLinearView" ref={this.createDropTarget} >
+                <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.isExpanded)} ref={this.addMenuToggle}
+                    onChange={action((e: any) => this.props.Document.isExpanded = this.addMenuToggle.current!.checked)} />
+                <label htmlFor={`${guid}`} style={{ marginTop: "auto", marginBottom: "auto", background: StrCast(this.props.Document.backgroundColor, "black") === StrCast(this.props.Document.color, "white") ? "black" : StrCast(this.props.Document.backgroundColor, "black") }} title="Close Menu"><p>+</p></label>
+
+                <div className="collectionLinearView-content" style={{ height: this.dimension(), width: NumCast(this.props.Document.width, 25) }}>
+                    {this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => {
+                        const nested = pair.layout.viewType === CollectionViewType.Linear;
+                        const dref = React.createRef<HTMLDivElement>();
+                        const nativeWidth = NumCast(pair.layout.nativeWidth, this.dimension());
+                        const deltaSize = nativeWidth * .15 / 2;
+                        return <div className={`collectionLinearView-docBtn` + (pair.layout.onClick || pair.layout.onDragStart ? "-scalable" : "")} key={pair.layout[Id]} ref={dref}
+                            style={{
+                                width: nested ? pair.layout[WidthSym]() : this.dimension() - deltaSize,
+                                height: nested && pair.layout.isExpanded ? pair.layout[HeightSym]() : this.dimension() - deltaSize,
+                            }}  >
+                            <DocumentView
+                                Document={pair.layout}
+                                DataDoc={pair.data}
+                                LibraryPath={this.props.LibraryPath}
+                                addDocument={this.props.addDocument}
+                                moveDocument={this.props.moveDocument}
+                                addDocTab={this.props.addDocTab}
+                                pinToPres={emptyFunction}
+                                removeDocument={this.props.removeDocument}
+                                ruleProvider={undefined}
+                                onClick={undefined}
+                                ScreenToLocalTransform={this.getTransform(dref)}
+                                ContentScaling={returnOne}
+                                PanelWidth={nested ? pair.layout[WidthSym] : () => this.dimension()}// ugh - need to get rid of this inline function to avoid recomputing
+                                PanelHeight={nested ? pair.layout[HeightSym] : () => this.dimension()}
+                                renderDepth={this.props.renderDepth + 1}
+                                focus={emptyFunction}
+                                backgroundColor={returnEmptyString}
+                                parentActive={returnTrue}
+                                whenActiveChanged={emptyFunction}
+                                bringToFront={emptyFunction}
+                                ContainingCollectionView={undefined}
+                                ContainingCollectionDoc={undefined}
+                                zoomToScale={emptyFunction}
+                                getScale={returnOne}>
+                            </DocumentView>
+                        </div>;
+                    })}
+                </div>
+            </div>
+        </div>;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx
index d6261c7ee..6af7cce70 100644
--- a/src/client/views/collections/CollectionPivotView.tsx
+++ b/src/client/views/collections/CollectionPivotView.tsx
@@ -75,7 +75,6 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
                     Object.keys(Doc.GetProto(child)).forEach(key => child[key] instanceof Doc && facetValues.add((child[key] as Doc)[facet]?.toString() || "(null)"));
                     facetValues.add(child[facet]?.toString() || "(null)");
                 });
-                this.childDocs
 
                 const newFacetVals = facetValues.toArray().map(val => Docs.Create.TextDocument({ title: val.toString() }));
                 const newFacet = Docs.Create.FreeformDocument(newFacetVals, { title: facet, treeViewOpen: true, isFacetFilter: true });
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 022fc8f48..ce3aab579 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -4,39 +4,38 @@ import { faColumns, faCopy, faEllipsisV, faFingerprint, faImage, faProjectDiagra
 import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from "mobx-react";
 import * as React from 'react';
+import Lightbox from 'react-image-lightbox-with-rotate';
+import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
+import { DateField } from '../../../new_fields/DateField';
+import { Doc, DocListCast } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/FieldSymbols';
-import { StrCast, BoolCast, Cast } from '../../../new_fields/Types';
+import { listSpec } from '../../../new_fields/Schema';
+import { BoolCast, Cast, StrCast } from '../../../new_fields/Types';
+import { ImageField } from '../../../new_fields/URLField';
+import { TraceMobx } from '../../../new_fields/util';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { Utils } from '../../../Utils';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DocumentManager } from '../../util/DocumentManager';
+import { ImageUtils } from '../../util/Import & Export/ImageUtils';
+import { SelectionManager } from '../../util/SelectionManager';
+import CollectionMulticolumnView from '../CollectionMulticolumnView';
 import { ContextMenu } from "../ContextMenu";
+import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import { ScriptBox } from '../ScriptBox';
+import { Touchable } from '../Touchable';
 import { CollectionDockingView } from "./CollectionDockingView";
 import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { CollectionLinearView } from './CollectionLinearView';
+import { CollectionPivotView } from './CollectionPivotView';
 import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionStackingView } from './CollectionStackingView';
+import { CollectionStaffView } from './CollectionStaffView';
 import { CollectionTreeView } from "./CollectionTreeView";
+import './CollectionView.scss';
 import { CollectionViewBaseChrome } from './CollectionViewChromes';
-import { ImageUtils } from '../../util/Import & Export/ImageUtils';
-import { CollectionLinearView } from '../CollectionLinearView';
-import { CollectionStaffView } from './CollectionStaffView';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { ImageField } from '../../../new_fields/URLField';
-import { DocListCast } from '../../../new_fields/Doc';
-import Lightbox from 'react-image-lightbox-with-rotate';
-import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
 export const COLLECTION_BORDER_WIDTH = 2;
-import { DateField } from '../../../new_fields/DateField';
-import { Doc, } from '../../../new_fields/Doc';
-import { listSpec } from '../../../new_fields/Schema';
-import { DocumentManager } from '../../util/DocumentManager';
-import { SelectionManager } from '../../util/SelectionManager';
-import './CollectionView.scss';
-import { FieldViewProps, FieldView } from '../nodes/FieldView';
-import { Touchable } from '../Touchable';
-import { TraceMobx } from '../../../new_fields/util';
-import { Utils } from '../../../Utils';
-import { ScriptBox } from '../ScriptBox';
-import CollectionMulticolumnView from '../CollectionMulticolumnView';
-import { CollectionPivotView } from './CollectionPivotView';
 const path = require('path');
 library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
 
-- 
cgit v1.2.3-70-g09d2


From 810f6b9b2737f3617910e498f577499fcbf3ffab Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 17 Jan 2020 10:33:05 -0500
Subject: moved multilcolumnview into collections directory

---
 src/client/views/CollectionMulticolumnView.scss    |  23 --
 src/client/views/CollectionMulticolumnView.tsx     | 233 ---------------------
 .../collections/CollectionMulticolumnView.scss     |  23 ++
 .../collections/CollectionMulticolumnView.tsx      | 232 ++++++++++++++++++++
 src/client/views/collections/CollectionView.tsx    |   2 +-
 5 files changed, 256 insertions(+), 257 deletions(-)
 delete mode 100644 src/client/views/CollectionMulticolumnView.scss
 delete mode 100644 src/client/views/CollectionMulticolumnView.tsx
 create mode 100644 src/client/views/collections/CollectionMulticolumnView.scss
 create mode 100644 src/client/views/collections/CollectionMulticolumnView.tsx

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

diff --git a/src/client/views/CollectionMulticolumnView.scss b/src/client/views/CollectionMulticolumnView.scss
deleted file mode 100644
index 120603a0b..000000000
--- a/src/client/views/CollectionMulticolumnView.scss
+++ /dev/null
@@ -1,23 +0,0 @@
-.collectionMulticolumnView_contents {
-    display: flex;
-    width: 100%;
-    height: 100%;
-    overflow: hidden;
-
-    .fish {
-        display: flex;
-        flex-direction: column;
-
-        .display {
-            text-align: center;
-            height: 20px;
-        }
-
-    }
-
-    .resizer {
-        background: black;
-        cursor: ew-resize;
-    }
-
-}
\ No newline at end of file
diff --git a/src/client/views/CollectionMulticolumnView.tsx b/src/client/views/CollectionMulticolumnView.tsx
deleted file mode 100644
index 99242e8be..000000000
--- a/src/client/views/CollectionMulticolumnView.tsx
+++ /dev/null
@@ -1,233 +0,0 @@
-import { observer } from 'mobx-react';
-import { makeInterface } from '../../new_fields/Schema';
-import { documentSchema } from '../../new_fields/documentSchemas';
-import { CollectionSubView } from './collections/CollectionSubView';
-import { DragManager } from '../util/DragManager';
-import * as React from "react";
-import { Doc } from '../../new_fields/Doc';
-import { NumCast, StrCast } from '../../new_fields/Types';
-import { ContentFittingDocumentView } from './nodes/ContentFittingDocumentView';
-import { Utils } from '../../Utils';
-import "./collectionMulticolumnView.scss";
-import { computed } from 'mobx';
-
-type MulticolumnDocument = makeInterface<[typeof documentSchema]>;
-const MulticolumnDocument = makeInterface(documentSchema);
-
-interface Unresolved {
-    target: Doc;
-    magnitude: number;
-    unit: string;
-}
-
-interface Resolved {
-    target: Doc;
-    pixels: number;
-}
-
-interface LayoutData {
-    unresolved: Unresolved[];
-    numFixed: number;
-    numRatio: number;
-    starSum: number;
-}
-
-const resolvedUnits = ["*", "px"];
-const resizerWidth = 2;
-
-@observer
-export default class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocument) {
-    @computed
-    private get resolvedLayoutInformation(): LayoutData {
-        const unresolved: Unresolved[] = [];
-        let starSum = 0, numFixed = 0, numRatio = 0;
-        for (const target of this.childDocs) {
-            const unit = StrCast(target.widthUnit);
-            const magnitude = NumCast(target.widthMagnitude);
-            if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
-                if (unit === "*") {
-                    starSum += magnitude;
-                    numRatio++;
-                } else {
-                    numFixed++;
-                }
-                unresolved.push({ target, 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
-        }
-        return { unresolved, numRatio, numFixed, starSum };
-    }
-
-    /**
-     * This returns the total quantity, in pixels, that this
-     * view needs to reserve for child documents that have
-     * (with higher priority) requested a fixed pixel width.
-     * 
-     * If the underlying resolvedLayoutInformation returns null
-     * because we're waiting on promises to resolve, this value will be undefined as well.
-     */
-    @computed
-    private get totalFixedAllocation(): number | undefined {
-        return this.resolvedLayoutInformation?.unresolved.reduce(
-            (sum, { magnitude, unit }) => sum + (unit === "px" ? magnitude : 0), 0);
-    }
-
-    /**
-     * This returns the total quantity, in pixels, that this
-     * view needs to reserve for child documents that have
-     * (with lower priority) requested a certain relative proportion of the
-     * remaining pixel width not allocated for fixed widths.
-     * 
-     * If the underlying totalFixedAllocation returns undefined
-     * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
-     */
-    @computed
-    private get totalRatioAllocation(): number | undefined {
-        const layoutInfoLen = this.resolvedLayoutInformation?.unresolved.length;
-        if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined)
-            return this.props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1));
-    }
-
-    /**
-     * This returns the total quantity, in pixels, that
-     * 1* (relative / star unit) is worth. For example,
-     * if the configuration has three documents, with, respectively,
-     * widths of 2*, 2* and 1*, and the panel width returns 1000px,
-     * this accessor returns 1000 / (2 + 2 + 1), or 200px.
-     * Elsewhere, this is then multiplied by each relative-width
-     * document's (potentially decimal) * count to compute its actual width (400px, 400px and 200px).
-     * 
-     * If the underlying totalRatioAllocation or this.resolveLayoutInformation return undefined
-     * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
-     */
-    @computed
-    private get columnUnitLength(): number | undefined {
-        if (this.resolvedLayoutInformation && this.totalRatioAllocation !== undefined) {
-            return this.totalRatioAllocation / this.resolvedLayoutInformation.starSum;
-        }
-    }
-
-    @computed
-    private get contents(): JSX.Element[] | null {
-        const layout = this.resolvedLayoutInformation;
-        const columnUnitLength = this.columnUnitLength;
-        if (layout === null || columnUnitLength === undefined) {
-            return (null); // 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;
-            }
-            resolved.push({ pixels: width, ...remaining });
-        });
-        const collector: JSX.Element[] = [];
-        for (let i = 0; i < resolved.length; i++) {
-            const { target, pixels } = resolved[i];
-            collector.push(
-                <div className={"fish"}>
-                    <ContentFittingDocumentView
-                        {...this.props}
-                        key={Utils.GenerateGuid()}
-                        Document={target}
-                        DataDocument={undefined}
-                        PanelWidth={() => pixels}
-                        getTransform={this.props.ScreenToLocalTransform}
-                    />
-                    <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}
-                />
-            );
-        }
-        collector.pop(); // removes the final extraneous resize bar
-        return collector;
-    }
-
-    render(): JSX.Element {
-        return (
-            <div className={"collectionMulticolumnView_contents"}>
-                {this.contents}
-            </div>
-        );
-    }
-
-}
-
-interface SpacerProps {
-    width: number;
-    columnUnitLength: number;
-    toLeft?: Doc;
-    toRight?: Doc;
-}
-
-class ResizeBar extends React.Component<SpacerProps> {
-
-    private registerResizing = (e: React.PointerEvent<HTMLDivElement>) => {
-        e.stopPropagation();
-        e.preventDefault();
-        window.removeEventListener("pointermove", this.onPointerMove);
-        window.removeEventListener("pointerup", this.onPointerUp);
-        window.addEventListener("pointermove", this.onPointerMove);
-        window.addEventListener("pointerup", this.onPointerUp);
-    }
-
-    private onPointerMove = ({ movementX }: PointerEvent) => {
-        const { toLeft, toRight, columnUnitLength } = this.props;
-        const target = movementX > 0 ? toRight : toLeft;
-        if (target) {
-            const { widthUnit, widthMagnitude } = target;
-            if (widthUnit === "*") {
-                target.widthMagnitude = NumCast(widthMagnitude) - Math.abs(movementX) / columnUnitLength;
-            }
-        }
-    }
-
-    private get opacity() {
-        const { toLeft, toRight } = this.props;
-        if (toLeft && toRight) {
-            if (StrCast(toLeft.widthUnit) === "px" && StrCast(toRight.widthUnit) === "px") {
-                return 0;
-            }
-            return 0.4;
-        } else if (toLeft) {
-            if (StrCast(toLeft.widthUnit) === "px") {
-                return 0;
-            }
-            return 0.4;
-        } else if (toRight) {
-            if (StrCast(toRight.widthUnit) === "px") {
-                return 0;
-            }
-            return 0.4;
-        }
-        return 0;
-    }
-
-    private onPointerUp = () => {
-        window.removeEventListener("pointermove", this.onPointerMove);
-        window.removeEventListener("pointerup", this.onPointerUp);
-    }
-
-    render() {
-        return (
-            <div
-                className={"resizer"}
-                style={{
-                    width: this.props.width,
-                    opacity: this.opacity
-                }}
-                onPointerDown={this.registerResizing}
-            />
-        );
-    }
-
-}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionMulticolumnView.scss b/src/client/views/collections/CollectionMulticolumnView.scss
new file mode 100644
index 000000000..120603a0b
--- /dev/null
+++ b/src/client/views/collections/CollectionMulticolumnView.scss
@@ -0,0 +1,23 @@
+.collectionMulticolumnView_contents {
+    display: flex;
+    width: 100%;
+    height: 100%;
+    overflow: hidden;
+
+    .fish {
+        display: flex;
+        flex-direction: column;
+
+        .display {
+            text-align: center;
+            height: 20px;
+        }
+
+    }
+
+    .resizer {
+        background: black;
+        cursor: ew-resize;
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionMulticolumnView.tsx b/src/client/views/collections/CollectionMulticolumnView.tsx
new file mode 100644
index 000000000..19a7b1123
--- /dev/null
+++ b/src/client/views/collections/CollectionMulticolumnView.tsx
@@ -0,0 +1,232 @@
+import { observer } from 'mobx-react';
+import { makeInterface } from '../../../new_fields/Schema';
+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 { ContentFittingDocumentView } from './../nodes/ContentFittingDocumentView';
+import { Utils } from '../../../Utils';
+import "./collectionMulticolumnView.scss";
+import { computed } from 'mobx';
+
+type MulticolumnDocument = makeInterface<[typeof documentSchema]>;
+const MulticolumnDocument = makeInterface(documentSchema);
+
+interface Unresolved {
+    target: Doc;
+    magnitude: number;
+    unit: string;
+}
+
+interface Resolved {
+    target: Doc;
+    pixels: number;
+}
+
+interface LayoutData {
+    unresolved: Unresolved[];
+    numFixed: number;
+    numRatio: number;
+    starSum: number;
+}
+
+const resolvedUnits = ["*", "px"];
+const resizerWidth = 2;
+
+@observer
+export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocument) {
+    @computed
+    private get resolvedLayoutInformation(): LayoutData {
+        const unresolved: Unresolved[] = [];
+        let starSum = 0, numFixed = 0, numRatio = 0;
+        for (const target of this.childDocs) {
+            const unit = StrCast(target.widthUnit);
+            const magnitude = NumCast(target.widthMagnitude);
+            if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
+                if (unit === "*") {
+                    starSum += magnitude;
+                    numRatio++;
+                } else {
+                    numFixed++;
+                }
+                unresolved.push({ target, 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
+        }
+        return { unresolved, numRatio, numFixed, starSum };
+    }
+
+    /**
+     * This returns the total quantity, in pixels, that this
+     * view needs to reserve for child documents that have
+     * (with higher priority) requested a fixed pixel width.
+     * 
+     * If the underlying resolvedLayoutInformation returns null
+     * because we're waiting on promises to resolve, this value will be undefined as well.
+     */
+    @computed
+    private get totalFixedAllocation(): number | undefined {
+        return this.resolvedLayoutInformation?.unresolved.reduce(
+            (sum, { magnitude, unit }) => sum + (unit === "px" ? magnitude : 0), 0);
+    }
+
+    /**
+     * This returns the total quantity, in pixels, that this
+     * view needs to reserve for child documents that have
+     * (with lower priority) requested a certain relative proportion of the
+     * remaining pixel width not allocated for fixed widths.
+     * 
+     * If the underlying totalFixedAllocation returns undefined
+     * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
+     */
+    @computed
+    private get totalRatioAllocation(): number | undefined {
+        const layoutInfoLen = this.resolvedLayoutInformation?.unresolved.length;
+        if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined)
+            return this.props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1));
+    }
+
+    /**
+     * This returns the total quantity, in pixels, that
+     * 1* (relative / star unit) is worth. For example,
+     * if the configuration has three documents, with, respectively,
+     * widths of 2*, 2* and 1*, and the panel width returns 1000px,
+     * this accessor returns 1000 / (2 + 2 + 1), or 200px.
+     * Elsewhere, this is then multiplied by each relative-width
+     * document's (potentially decimal) * count to compute its actual width (400px, 400px and 200px).
+     * 
+     * If the underlying totalRatioAllocation or this.resolveLayoutInformation return undefined
+     * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
+     */
+    @computed
+    private get columnUnitLength(): number | undefined {
+        if (this.resolvedLayoutInformation && this.totalRatioAllocation !== undefined) {
+            return this.totalRatioAllocation / this.resolvedLayoutInformation.starSum;
+        }
+    }
+
+    @computed
+    private get contents(): JSX.Element[] | null {
+        const layout = this.resolvedLayoutInformation;
+        const columnUnitLength = this.columnUnitLength;
+        if (layout === null || columnUnitLength === undefined) {
+            return (null); // 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;
+            }
+            resolved.push({ pixels: width, ...remaining });
+        });
+        const collector: JSX.Element[] = [];
+        for (let i = 0; i < resolved.length; i++) {
+            const { target, pixels } = resolved[i];
+            collector.push(
+                <div className={"fish"}>
+                    <ContentFittingDocumentView
+                        {...this.props}
+                        key={Utils.GenerateGuid()}
+                        Document={target}
+                        DataDocument={undefined}
+                        PanelWidth={() => pixels}
+                        getTransform={this.props.ScreenToLocalTransform}
+                    />
+                    <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}
+                />
+            );
+        }
+        collector.pop(); // removes the final extraneous resize bar
+        return collector;
+    }
+
+    render(): JSX.Element {
+        return (
+            <div className={"collectionMulticolumnView_contents"}>
+                {this.contents}
+            </div>
+        );
+    }
+
+}
+
+interface SpacerProps {
+    width: number;
+    columnUnitLength: number;
+    toLeft?: Doc;
+    toRight?: Doc;
+}
+
+class ResizeBar extends React.Component<SpacerProps> {
+
+    private registerResizing = (e: React.PointerEvent<HTMLDivElement>) => {
+        e.stopPropagation();
+        e.preventDefault();
+        window.removeEventListener("pointermove", this.onPointerMove);
+        window.removeEventListener("pointerup", this.onPointerUp);
+        window.addEventListener("pointermove", this.onPointerMove);
+        window.addEventListener("pointerup", this.onPointerUp);
+    }
+
+    private onPointerMove = ({ movementX }: PointerEvent) => {
+        const { toLeft, toRight, columnUnitLength } = this.props;
+        const target = movementX > 0 ? toRight : toLeft;
+        if (target) {
+            const { widthUnit, widthMagnitude } = target;
+            if (widthUnit === "*") {
+                target.widthMagnitude = NumCast(widthMagnitude) - Math.abs(movementX) / columnUnitLength;
+            }
+        }
+    }
+
+    private get opacity() {
+        const { toLeft, toRight } = this.props;
+        if (toLeft && toRight) {
+            if (StrCast(toLeft.widthUnit) === "px" && StrCast(toRight.widthUnit) === "px") {
+                return 0;
+            }
+            return 0.4;
+        } else if (toLeft) {
+            if (StrCast(toLeft.widthUnit) === "px") {
+                return 0;
+            }
+            return 0.4;
+        } else if (toRight) {
+            if (StrCast(toRight.widthUnit) === "px") {
+                return 0;
+            }
+            return 0.4;
+        }
+        return 0;
+    }
+
+    private onPointerUp = () => {
+        window.removeEventListener("pointermove", this.onPointerMove);
+        window.removeEventListener("pointerup", this.onPointerUp);
+    }
+
+    render() {
+        return (
+            <div
+                className={"resizer"}
+                style={{
+                    width: this.props.width,
+                    opacity: this.opacity
+                }}
+                onPointerDown={this.registerResizing}
+            />
+        );
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index ce3aab579..4bd456233 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -19,7 +19,6 @@ import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
 import { SelectionManager } from '../../util/SelectionManager';
-import CollectionMulticolumnView from '../CollectionMulticolumnView';
 import { ContextMenu } from "../ContextMenu";
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import { ScriptBox } from '../ScriptBox';
@@ -28,6 +27,7 @@ import { CollectionDockingView } from "./CollectionDockingView";
 import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import { CollectionLinearView } from './CollectionLinearView';
+import { CollectionMulticolumnView } from './CollectionMulticolumnView';
 import { CollectionPivotView } from './CollectionPivotView';
 import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionStackingView } from './CollectionStackingView';
-- 
cgit v1.2.3-70-g09d2

