From 471dce0623a751aed34f1cf69e022049a45e56bd Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 4 Feb 2020 11:37:06 -0500
Subject: added defaults for mulitirow/col

---
 .../collectionFreeForm/CollectionFreeFormView.tsx         | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6453cfe17..63544a637 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -824,13 +824,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
     get doLayoutComputation() {
         const { newPool, computedElementData } = this.doInternalLayoutComputation;
-        Array.from(newPool.keys()).map(key => {
-            const lastPos = this._cachedPool.get(key); // last computed pos
-            const newPos = newPool.get(key);
-            if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex || newPos.width !== lastPos.width || newPos.height !== lastPos.height) {
-                runInAction(() => this._layoutPoolData.set(key, { transition: "transform 1s", ...newPos }));
-            }
-        });
+        runInAction(() =>
+            Array.from(newPool.keys()).map(key => {
+                const lastPos = this._cachedPool.get(key); // last computed pos
+                const newPos = newPool.get(key);
+                if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex || newPos.width !== lastPos.width || newPos.height !== lastPos.height) {
+                    this._layoutPoolData.set(key, newPos);
+                }
+            }));
         this._cachedPool.clear();
         Array.from(newPool.keys()).forEach(k => this._cachedPool.set(k, newPool.get(k)));
         this.childLayoutPairs.filter((pair, i) => this.isCurrent(pair.layout)).forEach(pair =>
-- 
cgit v1.2.3-70-g09d2


From 7eb6f1bcc35d852308e2c2ea466d9727d3ba10ef Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 4 Feb 2020 15:56:38 -0500
Subject: pivot view/time view working

---
 src/client/views/collections/CollectionSubView.tsx |  1 +
 .../views/collections/CollectionTimeView.scss      |  3 +
 .../views/collections/CollectionTimeView.tsx       | 61 ++++++++++----------
 .../CollectionFreeFormLayoutEngines.tsx            | 65 ++++++++++++++++------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +-
 5 files changed, 85 insertions(+), 49 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 9cdd48089..a2700e75a 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -43,6 +43,7 @@ export interface SubCollectionViewProps extends CollectionViewProps {
     children?: never | (() => JSX.Element[]) | React.ReactNode;
     isAnnotationOverlay?: boolean;
     annotationsKey: string;
+    layoutEngine?: () => string;
 }
 
 export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
diff --git a/src/client/views/collections/CollectionTimeView.scss b/src/client/views/collections/CollectionTimeView.scss
index 3aac2bc75..df5057b5b 100644
--- a/src/client/views/collections/CollectionTimeView.scss
+++ b/src/client/views/collections/CollectionTimeView.scss
@@ -8,6 +8,9 @@
     .collectionFreeform-customText {
         text-align: left;
     }
+    .collectionFreeform-customDiv {
+        position: absolute;
+    }
     .collectionTimeView-thumb {
         position: absolute;
         width: 30px;
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index c92ffdeea..f999067d3 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,6 +1,6 @@
 import { faEdit } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, IReactionDisposer, observable } from "mobx";
+import { action, computed, IReactionDisposer, observable, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Set } from "typescript-collections";
 import { Doc, DocListCast } from "../../../new_fields/Doc";
@@ -22,8 +22,10 @@ import { RichTextField } from "../../../new_fields/RichTextField";
 
 @observer
 export class CollectionTimeView extends CollectionSubView(doc => doc) {
+    _changing = false;
+    @observable _layoutEngine = "pivot";
+
     componentDidMount() {
-        this.props.Document._freeformLayoutEngine = "timeline";
         const childDetailed = this.props.Document.childDetailed; // bcz: needs to be here to make sure the childDetailed layout template has been loaded when the first item is clicked;
         if (!this.props.Document._facetCollection) {
             const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", _yMargin: 0, treeViewHideTitle: true });
@@ -190,14 +192,14 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         document.removeEventListener("pointermove", this.onMidUp);
     }
 
+    layoutEngine = () => this._layoutEngine;
     @computed get contents() {
         return <div className="collectionTimeView-innards" key="timeline" style={{ width: this.bodyPanelWidth() }}>
-            <CollectionFreeFormView  {...this.props} ScreenToLocalTransform={this.getTransform} PanelWidth={this.bodyPanelWidth} />
+            <CollectionFreeFormView  {...this.props} layoutEngine={this.layoutEngine} ScreenToLocalTransform={this.getTransform} PanelWidth={this.bodyPanelWidth} />
         </div>;
     }
-
-    _changing = false;
-    render() {
+    @computed get filterView() {
+        trace();
         const facetCollection = Cast(this.props.Document?._facetCollection, Doc, null);
         const flyout = (
             <div className="collectionTimeView-flyout" style={{ width: `${this._facetWidth}` }}>
@@ -208,6 +210,22 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
                 </label>)}
             </div>
         );
+        return <div className="collectionTimeView-treeView" style={{ width: `${this._facetWidth}px`, overflow: this._facetWidth < 15 ? "hidden" : undefined }}>
+            <div className="collectionTimeView-addFacet" style={{ width: `${this._facetWidth}px` }} onPointerDown={e => e.stopPropagation()}>
+                <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
+                    <div className="collectionTimeView-button">
+                        <span className="collectionTimeView-span">Facet Filters</span>
+                        <FontAwesomeIcon icon={faEdit} size={"lg"} />
+                    </div>
+                </Flyout>
+            </div>
+            <div className="collectionTimeView-tree" key="tree">
+                <CollectionTreeView {...this.props} Document={facetCollection} />
+            </div>
+        </div>;
+    }
+
+    render() {
         const newEditableViewProps = {
             GetValue: () => "",
             SetValue: (value: any) => {
@@ -230,23 +248,18 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
                 nonNumbers++;
             }
         });
-        const doTimeline = nonNumbers / this.childDocs.length < 0.1;
-        if (doTimeline !== (this.props.Document._freeformLayoutEngine === "timeline")) {
+        const doTimeline = nonNumbers / this.childDocs.length < 0.1 && this.props.PanelWidth() / this.props.PanelHeight() > 6;
+        if (doTimeline !== (this._layoutEngine === "timeline")) {
             if (!this._changing) {
                 this._changing = true;
-                setTimeout(() => {
-                    if (nonNumbers / this.childDocs.length > 0.1) {
-                        this.childDocs.map(child => child.isMinimized = false);
-                        this.props.Document._freeformLayoutEngine = "pivot";
-                    } else {
-                        this.props.Document._freeformLayoutEngine = "timeline";
-                    }
+                setTimeout(action(() => {
+                    this._layoutEngine = doTimeline ? "timeline" : "pivot";
                     this._changing = false;
-                }, 0);
+                }), 0);
             }
-            return (null);
         }
 
+        const facetCollection = Cast(this.props.Document?._facetCollection, Doc, null);
         return !facetCollection ? (null) :
             <div className={"collectionTimeView" + (doTimeline ? "" : "-pivot")} style={{ height: `calc(100%  - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
                 <div className={"pivotKeyEntry"}>
@@ -257,19 +270,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
                         <span title="library View Dragger" style={{ width: "5px", position: "absolute", top: "0" }} />
                     </div>
                 }
-                <div className="collectionTimeView-treeView" style={{ width: `${this._facetWidth}px`, overflow: this._facetWidth < 15 ? "hidden" : undefined }}>
-                    <div className="collectionTimeView-addFacet" style={{ width: `${this._facetWidth}px` }} onPointerDown={e => e.stopPropagation()}>
-                        <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
-                            <div className="collectionTimeView-button">
-                                <span className="collectionTimeView-span">Facet Filters</span>
-                                <FontAwesomeIcon icon={faEdit} size={"lg"} />
-                            </div>
-                        </Flyout>
-                    </div>
-                    <div className="collectionTimeView-tree" key="tree">
-                        <CollectionTreeView {...this.props} Document={facetCollection} />
-                    </div>
-                </div>
+                {this.filterView}
                 {this.contents}
                 {!this.props.isSelected() || !doTimeline ? (null) : <>
                     <div className="collectionTimeView-thumb-min collectionTimeView-thumb" key="min" onPointerDown={this.onMinDown} />
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 63bcc68e5..f35662dab 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -67,35 +67,59 @@ export function computePivotLayout(
     panelDim: number[],
     viewDefsToJSX: (views: any) => ViewDefResult[]
 ) {
-    console.log("PIVOT " + pivotDoc[HeightSym]());
     const fieldKey = "data";
     const pivotColumnGroups = new Map<FieldResult<Field>, Doc[]>();
     const fontSize = NumCast(pivotDoc[fieldKey + "-timelineFontSize"], panelDim[1] > 58 ? 20 : Math.max(7, panelDim[1] / 3));
 
-    let maxInColumn = 1;
     const pivotFieldKey = toLabel(pivotDoc.pivotField);
     for (const doc of childDocs) {
         const val = Field.toString(doc[pivotFieldKey] as Field);
         if (val) {
             !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, []);
             pivotColumnGroups.get(val)!.push(doc);
-            maxInColumn = Math.max(maxInColumn, pivotColumnGroups.get(val)?.length || 0);
         }
     }
+    if (pivotColumnGroups.size > 10) {
+        const arrayofKeys = Array.from(pivotColumnGroups.keys());
+        const sortedKeys = arrayofKeys.sort();
+        const clusterSize = Math.ceil(pivotColumnGroups.size / 10);
+        const numClusters = Math.ceil(sortedKeys.length / clusterSize);
+        for (let i = 0; i < numClusters; i++) {
+            for (let j = i * clusterSize + 1; j < Math.min(sortedKeys.length, (i + 1) * clusterSize); j++) {
+                pivotColumnGroups.get(sortedKeys[i * clusterSize])!.push(...pivotColumnGroups.get(sortedKeys[j])!);
+                pivotColumnGroups.delete(sortedKeys[j]);
+            }
+        }
+    }
+    let maxInColumn = Array.from(pivotColumnGroups.values()).reduce((p, s) => Math.max(p, s.length), 1);
 
     const colWidth = panelDim[0] / pivotColumnGroups.size;
     const colHeight = panelDim[1];
-    const pivotAxisWidth = Math.sqrt(colWidth * colHeight / maxInColumn);
-    const numCols = Math.max(Math.round(colWidth / pivotAxisWidth), 1);
+    let numCols = 0;
+    let bestArea = 0;
+    let pivotAxisWidth = 0;
+    for (let i = 1; i < 10; i++) {
+        const numInCol = Math.ceil(maxInColumn / i);
+        const hd = colHeight / numInCol;
+        const wd = colWidth / i;
+        const dim = Math.min(hd, wd);
+        const area = dim * dim * i * numInCol;
+        if (area > bestArea) {
+            bestArea = area;
+            numCols = i;
+            pivotAxisWidth = dim;
+        }
+    }
 
     const docMap = new Map<Doc, ViewDefBounds>();
-    const groupNames: PivotData[] = [];;
+    const groupNames: PivotData[] = [];
 
     const expander = 1.05;
     const gap = .15;
     let x = 0;
     let max_text = 60;
-    pivotColumnGroups.forEach((val, key) => {
+    Array.from(pivotColumnGroups.keys()).sort().forEach(key => {
+        const val = pivotColumnGroups.get(key)!;
         let y = 0;
         let xCount = 0;
         const text = toLabel(key);
@@ -132,6 +156,10 @@ export function computePivotLayout(
         x += pivotAxisWidth * (numCols * expander + gap);
     });
 
+    const maxColHeight = pivotAxisWidth * Math.ceil(maxInColumn / numCols) + pivotAxisWidth;
+    const dividers = Array.from(pivotColumnGroups.values()).map((pg, i) =>
+        ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap), y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight } as any));
+    groupNames.push(...dividers);
     return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
 }
 
@@ -161,12 +189,10 @@ export function computeTimelineLayout(
     }
 
     let minTime = Number.MAX_VALUE;
-    let maxTime = Number.MIN_VALUE;
+    let maxTime = -Number.MAX_VALUE;
     childDocs.map(doc => {
         const num = NumCast(doc[timelineFieldKey], Number(StrCast(doc[timelineFieldKey])));
-        if (Number.isNaN(num) || (minTimeReq && num < minTimeReq) || (maxTimeReq && num > maxTimeReq)) {
-            doc.isMinimized = true;
-        } else {
+        if (!(Number.isNaN(num) || (minTimeReq && num < minTimeReq) || (maxTimeReq && num > maxTimeReq))) {
             !pivotDateGroups.get(num) && pivotDateGroups.set(num, []);
             pivotDateGroups.get(num)!.push(doc);
             minTime = Math.min(num, minTime);
@@ -180,8 +206,14 @@ export function computeTimelineLayout(
             minTime = curTime - (maxTime - curTime);
         }
     }
-    pivotDoc[fieldKey + "-timelineMin"] = minTime = minTimeReq ? Math.min(minTimeReq, minTime) : minTime;
-    pivotDoc[fieldKey + "-timelineMax"] = maxTime = maxTimeReq ? Math.max(maxTimeReq, maxTime) : maxTime;
+    setTimeout(() => {
+        pivotDoc[fieldKey + "-timelineMin"] = minTime = minTimeReq ? Math.min(minTimeReq, minTime) : minTime;
+        pivotDoc[fieldKey + "-timelineMax"] = maxTime = maxTimeReq ? Math.max(maxTimeReq, maxTime) : maxTime;
+    }, 0);
+
+    if (maxTime === minTime) {
+        maxTime = minTime + 1;
+    }
 
     const arrayofKeys = Array.from(pivotDateGroups.keys());
     const sortedKeys = arrayofKeys.sort((n1, n2) => n1 - n2);
@@ -204,7 +236,6 @@ export function computeTimelineLayout(
             groupNames.push({ type: "text", text: curTime.toString(), x: (curTime - minTime) * scaling, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize });
         }
         const keyDocs = pivotDateGroups.get(key)!;
-        keyDocs.forEach(d => d.isMinimized = false);
         x += scaling * (key - prevKey);
         const stack = findStack(x, stacking);
         prevKey = key;
@@ -244,15 +275,15 @@ export function computeTimelineLayout(
 function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map<Doc, ViewDefBounds>,
     poolData: Map<string, PoolData>, viewDefsToJSX: (views: any) => ViewDefResult[], groupNames: PivotData[], minWidth: number, extras: PivotData[]) {
 
-    const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, height: gn.height }) as PivotData);
-    const docEles = childPairs.filter(d => !d.layout.isMinimized).map(pair => docMap.get(pair.layout) as PivotData);
+    const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as PivotData);
+    const docEles = childPairs.filter(d => docMap.get(d.layout)).map(pair => docMap.get(pair.layout) as PivotData);
     const aggBounds = aggregateBounds(docEles.concat(grpEles), 0, 0);
     aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x);
     const wscale = panelDim[0] / (aggBounds.r - aggBounds.x);
     let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale;
     if (Number.isNaN(scale)) scale = 1;
 
-    childPairs.filter(d => !d.layout.isMinimized).map(pair => {
+    childPairs.filter(d => docMap.get(d.layout)).map(pair => {
         const newPosRaw = docMap.get(pair.layout);
         if (newPosRaw) {
             const newPos = {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 63544a637..6b81eba7d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -816,7 +816,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @computed get doInternalLayoutComputation() {
         const newPool = new Map<string, any>();
-        switch (this.Document._freeformLayoutEngine) {
+        switch (this.props.layoutEngine?.()) {
             case "timeline": return { newPool, computedElementData: this.doTimelineLayout(newPool) };
             case "pivot": return { newPool, computedElementData: this.doPivotLayout(newPool) };
         }
@@ -839,7 +839,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 ele: <CollectionFreeFormDocumentView key={pair.layout[Id]}  {...this.getChildDocumentViewProps(pair.layout, pair.data)}
                     dataProvider={this.childDataProvider}
                     jitterRotation={NumCast(this.props.Document.jitterRotation)}
-                    fitToBox={this.props.fitToBox || this.Document._freeformLayoutEngine !== undefined} />,
+                    fitToBox={this.props.fitToBox || this.props.layoutEngine !== undefined} />,
                 bounds: this.childDataProvider(pair.layout)
             }));
 
-- 
cgit v1.2.3-70-g09d2


From ab95c12485c26e5e0e6dc48cef8abd63a9c1fb56 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 5 Feb 2020 15:05:32 -0500
Subject: several layout fixes for row/col stuff.  fixes to icons.  added
 buttons for buxton app.  made PresBox not search for its own contents when
 following a link

---
 src/client/util/DocumentManager.ts                 |  4 +-
 src/client/views/MainView.tsx                      |  4 +-
 .../views/collections/CollectionDockingView.tsx    |  4 +-
 src/client/views/collections/CollectionSubView.tsx |  6 ++-
 .../views/collections/CollectionTimeView.tsx       |  2 +-
 .../views/collections/CollectionTreeView.tsx       | 47 +++++++++++++++-------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../CollectionMulticolumnView.tsx                  |  5 +--
 .../CollectionMultirowView.tsx                     |  4 +-
 src/client/views/nodes/DocumentView.tsx            | 21 +++++-----
 .../authentication/models/current_user_utils.ts    |  9 ++---
 11 files changed, 67 insertions(+), 41 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 9fff8faa7..60bb25272 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -93,9 +93,9 @@ export class DocumentManager {
         const toReturn: DocumentView[] = [];
 
         DocumentManager.Instance.DocumentViews.map(view =>
-            view.props.Document === toFind && toReturn.push(view));
+            view.props.Document.presBox === undefined && view.props.Document === toFind && toReturn.push(view));
         DocumentManager.Instance.DocumentViews.map(view =>
-            view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view));
+            view.props.Document.presBox === undefined && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view));
 
         return toReturn;
     }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 0cc374cee..a839f9fd2 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,6 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import {
-    faStickyNote, faArrowDown, faBullseye, faFilter, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight,
+    faFileAlt, faStickyNote, faArrowDown, faBullseye, faFilter, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight,
     faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt, faPhone, faStamp, faClipboard
 } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -101,6 +101,8 @@ export class MainView extends React.Component {
             }
         }
 
+        library.add(faFileAlt);
+        library.add(faStickyNote);
         library.add(faFont);
         library.add(faExclamation);
         library.add(faPortrait);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 861a63503..9c67d8041 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -468,7 +468,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     onPointerDown={e => {
                         e.preventDefault();
                         e.stopPropagation();
-                        DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY);
+                        const dragData = new DragManager.DocumentDragData([doc]);
+                        dragData.dropAction = doc._dropAction;
+                        DragManager.StartDocumentDrag([dragSpan], dragData, e.clientX, e.clientY);
                     }}>
                     <FontAwesomeIcon icon="file" size="lg" />
                 </span>, dragSpan);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index a2700e75a..731b5b01d 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -128,11 +128,15 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             const filteredDocs = docFilters.length ? viewedDocs.filter(d => {
                 for (const key of Object.keys(clusters)) {
                     const cluster = clusters[key];
+                    const satisfiesFacetx = !Object.keys(cluster).some((inner) => {
+                        const modifier = cluster[inner];
+                        return ((modifier === "x") === Doc.matchFieldValue(d, key, inner));
+                    });
                     const satisfiesFacet = Object.keys(cluster).some(inner => {
                         const modifier = cluster[inner];
                         return (modifier === "x") !== Doc.matchFieldValue(d, key, inner);
                     });
-                    if (!satisfiesFacet) {
+                    if (!satisfiesFacet || !satisfiesFacetx) {
                         return false;
                     }
                 }
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 264423d72..896fb74f0 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -33,7 +33,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
             this.props.Document.excludeFields = new List<string>(["_facetCollection", "_docFilter"]);
 
             const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)";
-            const childText = "const alias = getAlias(this); Doc.ApplyTemplateTo(containingCollection.childDetailed, alias, 'layout_detailed'); useRightSplit(alias, shiftKey); ";
+            const childText = "const alias = getAlias(this); Doc.ApplyTemplateTo(containingCollection.childDetailed, alias, 'layout_detailView'); alias._dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']);  useRightSplit(alias, shiftKey); ";
             facetCollection.onCheckedClick = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "boolean", checked: "boolean", containingTreeView: Doc.name });
             this.props.Document.onChildClick = ScriptField.MakeScript(childText, { this: Doc.name, heading: "boolean", containingCollection: Doc.name, shiftKey: "boolean" });
             this.props.Document._facetCollection = facetCollection;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 9e3fb1d67..19900c48b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -640,27 +640,46 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                         d.captions = undefined;
                     });
                 });
-                const { TextDocument, ImageDocument, CarouselDocument } = Docs.Create;
+                const { TextDocument, ImageDocument, CarouselDocument, TreeDocument } = Docs.Create;
                 const { Document } = this.props;
                 const fallbackImg = "http://www.cs.brown.edu/~bcz/face.gif";
-                const detailedTemplate = `{ "doc": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "short_description" } } ] }, { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "year" } } ] },  { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "company" } } ] }  ] }, "selection":{"type":"text","anchor":1,"head":1},"storedMarks":[] }`;
+                const detailedTemplate = `{ "doc": { "type": "doc", "content": [  { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "year" } } ] },  { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "company" } } ] }  ] }, "selection":{"type":"text","anchor":1,"head":1},"storedMarks":[] }`;
 
                 const textDoc = TextDocument("", { title: "details", _autoHeight: true });
-                const detailedLayout = Docs.Create.StackingDocument([
+                const detailView = Docs.Create.StackingDocument([
                     CarouselDocument([], { title: "data", _height: 350, _itemIndex: 0, backgroundColor: "#9b9b9b3F" }),
                     textDoc,
-                ], { _chromeStatus: "disabled", title: "detailed layout stack" });
-                textDoc.data = new RichTextField(detailedTemplate, "short_description year company");
-                detailedLayout.isTemplateDoc = makeTemplate(detailedLayout);
-
-                const cardLayout = ImageDocument(fallbackImg, { title: "cardLayout", isTemplateDoc: true, isTemplateForField: "hero", }); // this acts like a template doc and a template field ... a little weird, but seems to work?
-                cardLayout.proto!.layout = ImageBox.LayoutString("hero");
-                cardLayout.showTitle = "title";
-                cardLayout.showTitleHover = "titlehover";
-
-                Document.childLayout = cardLayout;
-                Document.childDetailed = detailedLayout;
+                    TextDocument("", { title: "short_description", _autoHeight: true }),
+                    TreeDocument([], { title: "narratives", _height: 75, treeViewHideTitle: true })
+                ], { _chromeStatus: "disabled", _width: 300, _height: 300, _autoHeight: true, title: "detailView" });
+                textDoc.data = new RichTextField(detailedTemplate, "year company");
+                detailView.isTemplateDoc = makeTemplate(detailView);
+
+
+                const heroView = ImageDocument(fallbackImg, { title: "heroView", isTemplateDoc: true, isTemplateForField: "hero", }); // this acts like a template doc and a template field ... a little weird, but seems to work?
+                heroView.proto!.layout = ImageBox.LayoutString("hero");
+                heroView.showTitle = "title";
+                heroView.showTitleHover = "titlehover";
+                heroView._dropAction = "alias";
+                heroView.removeDropProperties = new List<string>(["dropAction"]);
+
+                Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data",
+                    Docs.Create.FontIconDocument({
+                        _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, _dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+                        dragFactory: heroView, removeDropProperties: new List<string>(["dropAction"]), title: "hero view", icon: "portrait"
+                    }));
+
+                Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data",
+                    Docs.Create.FontIconDocument({
+                        _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, _dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+                        dragFactory: detailView, removeDropProperties: new List<string>(["dropAction"]), title: "detail view", icon: "file-alt"
+                    }));
+
+
+                Document.childLayout = heroView;
+                Document.childDetailed = detailView;
                 Document._viewType = CollectionViewType.Time;
+                Document._forceActive = true;
                 Document.pivotField = "company";
             }
         });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6b81eba7d..54c45e43a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -434,7 +434,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         let y = this.Document._panY || 0;
         const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.isMinimized).map(pair => pair.layout);
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
-        if (!this.isAnnotationOverlay && docs.length) {
+        if (!this.isAnnotationOverlay && docs.length && this.childDataProvider(docs[0])) {
             PDFMenu.Instance.fadeOut(true);
             const minx = this.childDataProvider(docs[0]).x;//docs.length ? NumCast(docs[0].x) : 0;
             const miny = this.childDataProvider(docs[0]).y;//docs.length ? NumCast(docs[0].y) : 0;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 92b908ab6..7d8de0db4 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -5,7 +5,6 @@ import { Doc } from '../../../../new_fields/Doc';
 import { documentSchema } from '../../../../new_fields/documentSchemas';
 import { makeInterface } from '../../../../new_fields/Schema';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../new_fields/Types';
-import { Utils } from '../../../../Utils';
 import { DragManager } from '../../../util/DragManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
@@ -119,7 +118,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
     private get totalRatioAllocation(): number | undefined {
         const layoutInfoLen = this.resolvedLayoutInformation.widthSpecifiers.length;
         if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
-            return this.props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1));
+            return this.props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1)) - 2 * NumCast(this.props.Document._xMargin);
         }
     }
 
@@ -228,7 +227,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
         for (let i = 0; i < childLayoutPairs.length; i++) {
             const { layout } = childLayoutPairs[i];
             const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin));
-            const width = () => this.lookupPixels(layout) - 2 * NumCast(Document._xMargin);
+            const width = () => this.lookupPixels(layout);
             const height = () => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
             collector.push(
                 <div className={"document-wrapper"}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index d92ba9a9f..ff7c4998f 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -119,7 +119,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
     private get totalRatioAllocation(): number | undefined {
         const layoutInfoLen = this.resolvedLayoutInformation.heightSpecifiers.length;
         if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
-            return this.props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1));
+            return this.props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this.props.Document._yMargin);
         }
     }
 
@@ -228,7 +228,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
         for (let i = 0; i < childLayoutPairs.length; i++) {
             const { layout } = childLayoutPairs[i];
             const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin));
-            const height = () => this.lookupPixels(layout) - 2 * NumCast(Document._yMargin);
+            const height = () => this.lookupPixels(layout);
             const width = () => PanelWidth() - 2 * NumCast(Document._xMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
             collector.push(
                 <div
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1fb3dcb36..776d4b8c3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -483,9 +483,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (e.cancelBubble && this.active) {
             document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView)
         }
-        else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) {
+        else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.onClickHandler) && !this.Document.lockedPosition && !this.Document.inOverlay) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
-                if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
+                if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
                     document.removeEventListener("pointermove", this.onPointerMove);
                     document.removeEventListener("pointerup", this.onPointerUp);
                     this.startDragging(this._downX, this._downY, this.Document._dropAction ? this.Document._dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
@@ -518,7 +518,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     @undoBatch
-    deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); }
+    deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument?.(this.props.Document); }
 
     static makeNativeViewClicked = (doc: Doc) => {
         undoBatch(() => Doc.setNativeView(doc))();
@@ -597,7 +597,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 `Link from ${StrCast(de.complete.annoDragData.annotationDocument.title)}`);
         }
         if (de.complete.docDragData && de.complete.docDragData.applyAsTemplate) {
-            Doc.ApplyTemplateTo(de.complete.docDragData.draggedDocuments[0], this.props.Document, "layout_custom");
+            Doc.ApplyTemplateTo(de.complete.docDragData.draggedDocuments[0], this.props.Document, "layout_custom", undefined);
             e.stopPropagation();
         }
         if (de.complete.linkDragData) {
@@ -656,12 +656,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             if (custom) {
                 DocumentView.makeNativeViewClicked(this.props.Document);
 
-                let foundLayout: Opt<Doc> = Cast(Doc.UserDoc().iconView, Doc, null);
-                !foundLayout && DocListCast(Cast(CurrentUserUtils.UserDocument.expandingButtons, Doc, null)?.data)?.map(btnDoc => {
-                    if (StrCast(Cast(btnDoc?.dragFactory, Doc, null)?.title) === layout) {
-                        foundLayout = btnDoc.dragFactory as Doc;
-                    }
-                })
+                let foundLayout: Opt<Doc>;
+                DocListCast(Cast(Doc.UserDoc().expandingButtons, Doc, null)?.data)?.concat([Cast(Doc.UserDoc().iconView, Doc, null)]).
+                    map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc).forEach(tempDoc => {
+                        if (StrCast(tempDoc.title) === layout) {
+                            foundLayout = tempDoc;
+                        }
+                    })
                 DocumentView.
                     makeCustomViewClicked(this.props.Document, this.props.DataDoc, Docs.Create.StackingDocument, layout, foundLayout);
             } else {
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index a4af46a63..74db511df 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -53,14 +53,14 @@ export class CurrentUserUtils {
         doc.noteTypes = Docs.Create.TreeDocument(notes, { title: "Note Types", _height: 75 });
         doc.activePen = doc;
         const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
-            { title: "collection", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory))', drag: 'getCopy(this.dragFactory, true)', dragFactory: emptyCollection },
+            { title: "collection", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: emptyCollection },
             { title: "preview", icon: "expand", ignoreClick: true, drag: 'Docs.Create.DocumentDocument(ComputedField.MakeFunction("selectedDocs(this,true,[_last_])?.[0]"), { _width: 250, _height: 250, title: "container" })' },
             { title: "todo item", icon: "check", ignoreClick: true, drag: 'getCopy(this.dragFactory, true)', dragFactory: notes[notes.length - 1] },
             { title: "web page", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", {_width: 300, _height: 300, title: "New Webpage" })' },
             { title: "cat image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 200, title: "an image of a cat" })' },
             { title: "record", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` },
             { title: "clickable button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, title: "Button" })' },
-            { title: "presentation", icon: "tv", click: 'openOnRight(Doc.UserDoc().curPresentation = getCopy(this.dragFactory))', drag: `Doc.UserDoc().curPresentation = getCopy(this.dragFactory,true)`, dragFactory: emptyPresentation },
+            { title: "presentation", icon: "tv", click: 'openOnRight(Doc.UserDoc().curPresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().curPresentation = getCopy(this.dragFactory,true)`, dragFactory: emptyPresentation },
             { title: "import folder", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' },
             { title: "mobile view", icon: "phone", ignoreClick: true, drag: 'Doc.UserDoc().activeMobile' },
             { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.pen,  this)`, activePen: doc },
@@ -240,7 +240,7 @@ export class CurrentUserUtils {
             { _width: 400, _height: 300, title: "slide", _chromeStatus: "disabled", _autoHeight: true });
         slideTemplate.isTemplateDoc = makeTemplate(slideTemplate);
 
-        const iconDoc = Docs.Create.TextDocument("", { title: "icon", _width: 150, _height: 30, onClick: ScriptField.MakeScript("setNativeView(this)") });
+        const iconDoc = Docs.Create.TextDocument("", { title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onClick: ScriptField.MakeScript("setNativeView(this)") });
         Doc.GetProto(iconDoc).data = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', "");
         doc.isTemplateDoc = makeTemplate(iconDoc);
         doc.iconView = new PrefetchProxy(iconDoc);
@@ -250,8 +250,7 @@ export class CurrentUserUtils {
         doc.redoBtn = Docs.Create.FontIconDocument(
             { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, _dropAction: "alias", onClick: ScriptField.MakeScript("redo()"), removeDropProperties: new List<string>(["dropAction"]), title: "redo button", icon: "redo-alt" });
         doc.slidesBtn = Docs.Create.FontIconDocument(
-            { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, _dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: slideTemplate, removeDropProperties: new List<string>(["dropAction"]), title: "slide button", icon: "sticky-note" });
-
+            { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, _dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: slideTemplate, removeDropProperties: new List<string>(["dropAction"]), title: "presentation slide", icon: "sticky-note" });
         doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc, doc.slidesBtn as Doc], {
             title: "expanding buttons", _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0",
             backgroundColor: "black", preventTreeViewOpen: true, forceActive: true, lockedPosition: true,
-- 
cgit v1.2.3-70-g09d2


From 045233f2a8b8e79e6a3255ed594218929db1b042 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 6 Feb 2020 15:14:56 -0500
Subject: added pivoting into a pivot column

---
 src/client/views/collections/CollectionSubView.tsx |  6 +-
 .../views/collections/CollectionTimeView.tsx       | 14 +++-
 .../CollectionFreeFormLayoutEngines.tsx            | 87 ++++++++++++----------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 15 ++--
 src/client/views/nodes/PresBox.tsx                 |  2 +-
 src/new_fields/Doc.ts                              | 31 ++++----
 src/new_fields/util.ts                             |  2 +-
 7 files changed, 87 insertions(+), 70 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 731b5b01d..a2700e75a 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -128,15 +128,11 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             const filteredDocs = docFilters.length ? viewedDocs.filter(d => {
                 for (const key of Object.keys(clusters)) {
                     const cluster = clusters[key];
-                    const satisfiesFacetx = !Object.keys(cluster).some((inner) => {
-                        const modifier = cluster[inner];
-                        return ((modifier === "x") === Doc.matchFieldValue(d, key, inner));
-                    });
                     const satisfiesFacet = Object.keys(cluster).some(inner => {
                         const modifier = cluster[inner];
                         return (modifier === "x") !== Doc.matchFieldValue(d, key, inner);
                     });
-                    if (!satisfiesFacet || !satisfiesFacetx) {
+                    if (!satisfiesFacet) {
                         return false;
                     }
                 }
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index a9114026f..c0e18714e 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -20,6 +20,8 @@ import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
 import { RichTextField } from "../../../new_fields/RichTextField";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { Scripting } from "../../util/Scripting";
+import { ViewDefResult, ViewDefBounds } from "./collectionFreeForm/CollectionFreeFormLayoutEngines";
 
 @observer
 export class CollectionTimeView extends CollectionSubView(doc => doc) {
@@ -40,7 +42,11 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
             this.props.Document._facetCollection = facetCollection;
             this.props.Document._fitToBox = true;
         }
+        if (!this.props.Document.onViewDefClick) {
+            this.props.Document.onViewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" })
+        }
     }
+
     bodyPanelWidth = () => this.props.PanelWidth() - this._facetWidth;
     getTransform = () => this.props.ScreenToLocalTransform().translate(-this._facetWidth, 0);
 
@@ -299,4 +305,10 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
                 </>}
             </div>;
     }
-}
\ No newline at end of file
+}
+
+Scripting.addGlobal(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
+    console.log("filter down to key: " + pivotDoc._pivotField + " val:" + bounds.payload);
+    (bounds.payload as string[]).map(filterVal =>
+        Doc.setDocFilter(pivotDoc, StrCast(pivotDoc._pivotField), filterVal, "check"));
+});
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index d58925aab..884f3b353 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -1,29 +1,18 @@
-import { Doc, Field, FieldResult, WidthSym, HeightSym } from "../../../../new_fields/Doc";
-import { NumCast, StrCast, Cast, DateCast, BoolCast } from "../../../../new_fields/Types";
+import { Doc, Field, FieldResult } from "../../../../new_fields/Doc";
+import { NumCast, StrCast, Cast } from "../../../../new_fields/Types";
 import { ScriptBox } from "../../ScriptBox";
 import { CompileScript } from "../../../util/Scripting";
 import { ScriptField } from "../../../../new_fields/ScriptField";
 import { OverlayView, OverlayElementOptions } from "../../OverlayView";
 import { emptyFunction, aggregateBounds } from "../../../../Utils";
 import React = require("react");
-import { ObservableMap, runInAction } from "mobx";
 import { Id, ToString } from "../../../../new_fields/FieldSymbols";
 import { ObjectField } from "../../../../new_fields/ObjectField";
 import { RefField } from "../../../../new_fields/RefField";
 
-interface PivotData {
-    type: string;
-    text: string;
-    x: number;
-    y: number;
-    zIndex?: number;
-    width?: number;
-    height?: number;
-    fontSize: number;
-    color?: string;
-}
-
 export interface ViewDefBounds {
+    type: string;
+    text?: string;
     x: number;
     y: number;
     z?: number;
@@ -31,7 +20,10 @@ export interface ViewDefBounds {
     width?: number;
     height?: number;
     transition?: string;
+    fontSize?: number;
     highlight?: boolean;
+    color?: string;
+    payload: any;
 }
 
 export interface PoolData {
@@ -83,14 +75,14 @@ export function computePivotLayout(
     viewDefsToJSX: (views: any) => ViewDefResult[]
 ) {
     const fieldKey = "data";
-    const pivotColumnGroups = new Map<FieldResult<Field>, Doc[]>();
+    const pivotColumnGroups = new Map<FieldResult<Field>, { docs: Doc[], filters: string[] }>();
 
     const pivotFieldKey = toLabel(pivotDoc._pivotField);
     for (const doc of childDocs) {
         const val = Field.toString(doc[pivotFieldKey] as Field);
         if (val) {
-            !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, []);
-            pivotColumnGroups.get(val)!.push(doc);
+            !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val] });
+            pivotColumnGroups.get(val)!.docs.push(doc);
         }
     }
     if (pivotColumnGroups.size > 10) {
@@ -100,7 +92,10 @@ export function computePivotLayout(
         const numClusters = Math.ceil(sortedKeys.length / clusterSize);
         for (let i = 0; i < numClusters; i++) {
             for (let j = i * clusterSize + 1; j < Math.min(sortedKeys.length, (i + 1) * clusterSize); j++) {
-                pivotColumnGroups.get(sortedKeys[i * clusterSize])!.push(...pivotColumnGroups.get(sortedKeys[j])!);
+                const curgrp = pivotColumnGroups.get(sortedKeys[i * clusterSize])!;
+                const newgrp = pivotColumnGroups.get(sortedKeys[j])!;
+                curgrp.docs.push(...newgrp.docs);
+                curgrp.filters.push(...newgrp.filters);
                 pivotColumnGroups.delete(sortedKeys[j]);
             }
         }
@@ -109,7 +104,7 @@ export function computePivotLayout(
     const desc = `${fontSize}px ${getComputedStyle(document.body).fontFamily}`;
     const textlen = Array.from(pivotColumnGroups.keys()).map(c => getTextWidth(toLabel(c), desc)).reduce((p, c) => Math.max(p, c), 0 as number);
     const max_text = Math.min(Math.ceil(textlen / 120) * 28, panelDim[1] / 2);
-    let maxInColumn = Array.from(pivotColumnGroups.values()).reduce((p, s) => Math.max(p, s.length), 1);
+    let maxInColumn = Array.from(pivotColumnGroups.values()).reduce((p, s) => Math.max(p, s.docs.length), 1);
 
     const colWidth = panelDim[0] / pivotColumnGroups.size;
     const colHeight = panelDim[1] - max_text;
@@ -129,7 +124,7 @@ export function computePivotLayout(
     }
 
     const docMap = new Map<Doc, ViewDefBounds>();
-    const groupNames: PivotData[] = [];
+    const groupNames: ViewDefBounds[] = [];
 
     const expander = 1.05;
     const gap = .15;
@@ -146,9 +141,10 @@ export function computePivotLayout(
             y: pivotAxisWidth,
             width: pivotAxisWidth * expander * numCols,
             height: max_text,
-            fontSize
+            fontSize,
+            payload: val
         });
-        for (const doc of val) {
+        for (const doc of val.docs) {
             const layoutDoc = Doc.Layout(doc);
             let wid = pivotAxisWidth;
             let hgt = layoutDoc._nativeWidth ? (NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth)) * pivotAxisWidth : pivotAxisWidth;
@@ -157,10 +153,12 @@ export function computePivotLayout(
                 wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
             }
             docMap.set(doc, {
-                x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.length < numCols ? (numCols - val.length) * pivotAxisWidth / 2 : 0),
+                type: "doc",
+                x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.docs.length < numCols ? (numCols - val.docs.length) * pivotAxisWidth / 2 : 0),
                 y: -y + (pivotAxisWidth - hgt) / 2,
                 width: wid,
-                height: hgt
+                height: hgt,
+                payload: undefined
             });
             xCount++;
             if (xCount >= numCols) {
@@ -172,12 +170,15 @@ export function computePivotLayout(
     });
 
     const maxColHeight = pivotAxisWidth * expander * Math.ceil(maxInColumn / numCols);
-    const dividers = Array.from(pivotColumnGroups.values()).map((pg, i) =>
-        ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap), y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight } as any));
+    const dividers = Array.from(pivotColumnGroups.values()).map((pkey, i) =>
+        ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap), y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pkey.filters }));
     groupNames.push(...dividers);
     return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
 }
 
+function toNumber(val: FieldResult<Field>) {
+    return val === undefined ? undefined : NumCast(val, Number(StrCast(val)));
+}
 
 export function computeTimelineLayout(
     poolData: Map<string, PoolData>,
@@ -190,9 +191,9 @@ export function computeTimelineLayout(
     const fieldKey = "data";
     const pivotDateGroups = new Map<number, Doc[]>();
     const docMap = new Map<Doc, ViewDefBounds>();
-    const groupNames: PivotData[] = [];
+    const groupNames: ViewDefBounds[] = [];
     const timelineFieldKey = Field.toString(pivotDoc._pivotField as Field);
-    const curTime = Cast(pivotDoc[fieldKey + "-timelineCur"], "number", null);
+    const curTime = toNumber(pivotDoc[fieldKey + "-timelineCur"]);
     const curTimeSpan = Cast(pivotDoc[fieldKey + "-timelineSpan"], "number", null);
     const minTimeReq = curTime === undefined ? Cast(pivotDoc[fieldKey + "-timelineMinReq"], "number", null) : curTimeSpan && (curTime - curTimeSpan);
     const maxTimeReq = curTime === undefined ? Cast(pivotDoc[fieldKey + "-timelineMaxReq"], "number", null) : curTimeSpan && (curTime + curTimeSpan);
@@ -237,10 +238,10 @@ export function computeTimelineLayout(
     let prevKey = Math.floor(minTime);
 
     if (sortedKeys.length && scaling * (sortedKeys[0] - prevKey) > 25) {
-        groupNames.push({ type: "text", text: prevKey.toString(), x: x, y: 0, height: fontHeight, fontSize });
+        groupNames.push({ type: "text", text: prevKey.toString(), x: x, y: 0, height: fontHeight, fontSize, payload: undefined });
     }
     if (!sortedKeys.length && curTime !== undefined) {
-        groupNames.push({ type: "text", text: curTime.toString(), x: (curTime - minTime) * scaling, zIndex: 1000, color: "orange", y: 0, height: fontHeight, fontSize });
+        groupNames.push({ type: "text", text: curTime.toString(), x: (curTime - minTime) * scaling, zIndex: 1000, color: "orange", y: 0, height: fontHeight, fontSize, payload: undefined });
     }
 
     const pivotAxisWidth = NumCast(pivotDoc.pivotTimeWidth, panelDim[1] / 2.5);
@@ -248,21 +249,23 @@ export function computeTimelineLayout(
     let zind = 0;
     sortedKeys.forEach(key => {
         if (curTime !== undefined && curTime > prevKey && curTime <= key) {
-            groupNames.push({ type: "text", text: curTime.toString(), x: (curTime - minTime) * scaling, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize });
+            groupNames.push({ type: "text", text: curTime.toString(), x: (curTime - minTime) * scaling, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize, payload: key });
         }
         const keyDocs = pivotDateGroups.get(key)!;
         x += scaling * (key - prevKey);
         const stack = findStack(x, stacking);
         prevKey = key;
-        !stack && (curTime === undefined || Math.abs(x - (curTime - minTime) * scaling) > pivotAxisWidth) && groupNames.push({ type: "text", text: key.toString(), x: x, y: stack * 25, height: fontHeight, fontSize });
+        if (!stack && (curTime === undefined || Math.abs(x - (curTime - minTime) * scaling) > pivotAxisWidth)) {
+            groupNames.push({ type: "text", text: key.toString(), x: x, y: stack * 25, height: fontHeight, fontSize, payload: undefined });
+        }
         layoutDocsAtTime(keyDocs, key);
     });
-    if (sortedKeys.length && curTime > sortedKeys[sortedKeys.length - 1]) {
+    if (sortedKeys.length && curTime !== undefined && curTime > sortedKeys[sortedKeys.length - 1]) {
         x = (curTime - minTime) * scaling;
-        groupNames.push({ type: "text", text: curTime.toString(), x: x, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize });
+        groupNames.push({ type: "text", text: curTime.toString(), x: x, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize, payload: undefined });
     }
     if (Math.ceil(maxTime - minTime) * scaling > x + 25) {
-        groupNames.push({ type: "text", text: Math.ceil(maxTime).toString(), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize });
+        groupNames.push({ type: "text", text: Math.ceil(maxTime).toString(), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined });
     }
 
     const divider = { type: "div", color: "black", x: 0, y: 0, width: panelDim[0], height: 1 } as any;
@@ -279,8 +282,9 @@ export function computeTimelineLayout(
                 wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
             }
             docMap.set(doc, {
+                type: "doc",
                 x: x, y: -Math.sqrt(stack) * pivotAxisWidth / 2 - pivotAxisWidth + (pivotAxisWidth - hgt) / 2,
-                zIndex: (curTime === key ? 1000 : zind++), highlight: curTime === key, width: wid / (Math.max(stack, 1)), height: hgt
+                zIndex: (curTime === key ? 1000 : zind++), highlight: curTime === key, width: wid / (Math.max(stack, 1)), height: hgt, payload: undefined
             });
             stacking[stack] = x + pivotAxisWidth;
         });
@@ -288,10 +292,10 @@ export function computeTimelineLayout(
 }
 
 function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map<Doc, ViewDefBounds>,
-    poolData: Map<string, PoolData>, viewDefsToJSX: (views: any) => ViewDefResult[], groupNames: PivotData[], minWidth: number, extras: PivotData[]) {
+    poolData: Map<string, PoolData>, viewDefsToJSX: (views: any) => ViewDefResult[], groupNames: ViewDefBounds[], minWidth: number, extras: ViewDefBounds[]) {
 
-    const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as PivotData);
-    const docEles = childPairs.filter(d => docMap.get(d.layout)).map(pair => docMap.get(pair.layout) as PivotData);
+    const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
+    const docEles = childPairs.filter(d => docMap.get(d.layout)).map(pair => docMap.get(pair.layout) as ViewDefBounds);
     const aggBounds = aggregateBounds(docEles.concat(grpEles), 0, 0);
     aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x);
     const wscale = panelDim[0] / (aggBounds.r - aggBounds.x);
@@ -323,7 +327,8 @@ function normalizeResults(panelDim: number[], fontHeight: number, childPairs: {
             color: gname.color,
             width: gname.width === undefined ? undefined : gname.width * scale,
             height: Math.max(fontHeight, (gname.height || 0) * scale),
-            fontSize: gname.fontSize
+            fontSize: gname.fontSize,
+            payload: gname.payload
         }))))
     };
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 54c45e43a..a34779046 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -32,7 +32,7 @@ import { FormattedTextBox } from "../../nodes/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
 import PDFMenu from "../../pdf/PDFMenu";
 import { CollectionSubView } from "../CollectionSubView";
-import { computePivotLayout, ViewDefResult, computeTimelineLayout, PoolData } from "./CollectionFreeFormLayoutEngines";
+import { computePivotLayout, ViewDefResult, computeTimelineLayout, PoolData, ViewDefBounds } from "./CollectionFreeFormLayoutEngines";
 import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
 import "./CollectionFreeFormView.scss";
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
@@ -747,11 +747,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         };
     }
 
-    viewDefsToJSX = (views: any[]) => {
+    viewDefsToJSX = (views: ViewDefBounds[]) => {
         return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!);
     }
 
-    private viewDefToJSX(viewDef: any): Opt<ViewDefResult> {
+    onViewDefDivClick = (e: React.MouseEvent, payload: any) => {
+        (this.props.Document.onViewDefDivClick as ScriptField)?.script.run({ this: this.props.Document, payload });
+    }
+    private viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> {
         const x = Cast(viewDef.x, "number");
         const y = Cast(viewDef.y, "number");
         const z = Cast(viewDef.z, "number");
@@ -769,15 +772,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         style={{ width, height, color, fontSize, transform: `translate(${x}px, ${y}px)` }}>
                         {text}
                     </div>,
-                    bounds: { x: x!, y: y!, z, zIndex, width, height }
+                    bounds: viewDef
                 };
         } else if (viewDef.type === "div") {
             const backgroundColor = Cast(viewDef.color, "string");
             return [x, y].some(val => val === undefined) ? undefined :
                 {
-                    ele: <div className="collectionFreeform-customDiv" key={"div" + x + y + z}
+                    ele: <div className="collectionFreeform-customDiv" key={"div" + x + y + z} onClick={e => this.onViewDefDivClick(e, viewDef)}
                         style={{ width, height, backgroundColor, transform: `translate(${x}px, ${y}px)` }} />,
-                    bounds: { x: x!, y: y!, z, zIndex, width, height }
+                    bounds: viewDef
                 };
         }
     }
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index be5d414a5..6c4cbba12 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -360,7 +360,7 @@ export class PresBox extends React.Component<FieldViewProps> {
         const funcs: ContextMenuProps[] = [];
         funcs.push({ description: "Show as Slideshow", event: action(() => this.props.Document._slideshow = "slideshow"), icon: "asterisk" });
         funcs.push({ description: "Show as Timeline", event: action(() => this.props.Document._slideshow = "timeline"), icon: "asterisk" });
-        funcs.push({ description: "Show as List", event: action(() => this.props.Document._slideshow = "list"), icon: "asterisk" });
+        funcs.push({ description: "Show as List", event: action(() => this.props.Document._slideshow = undefined), icon: "asterisk" });
         ContextMenu.Instance.addItem({ description: "Presentation Funcs...", subitems: funcs, icon: "asterisk" });
     }
 
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 6ae02720c..92b1fb090 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -791,6 +791,21 @@ export namespace Doc {
         if (StrCast(doc.title).endsWith("_" + prevLayout)) doc.title = StrCast(doc.title).replace("_" + prevLayout, "");
         doc.layoutKey = deiconify || "layout";
     }
+    export function setDocFilter(container: Doc, key: string, value: any, modifiers?: string) {
+        const docFilters = Cast(container._docFilter, listSpec("string"), []);
+        for (let i = 0; i < docFilters.length; i += 3) {
+            if (docFilters[i] === key && docFilters[i + 1] === value) {
+                docFilters.splice(i, 3);
+                break;
+            }
+        }
+        if (modifiers !== undefined) {
+            docFilters.push(key);
+            docFilters.push(value);
+            docFilters.push(modifiers);
+            container._docFilter = new List<string>(docFilters);
+        }
+    }
 }
 
 Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${n})`; });
@@ -815,18 +830,4 @@ Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: bo
     const docs = DocListCast(Doc.UserDoc().SelectedDocs).filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.DOCUMENT && d.type !== DocumentType.KVP && (!excludeCollections || !Cast(d.data, listSpec(Doc), null)));
     return docs.length ? new List(docs) : prevValue;
 });
-Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: string) {
-    const docFilters = Cast(container._docFilter, listSpec("string"), []);
-    for (let i = 0; i < docFilters.length; i += 3) {
-        if (docFilters[i] === key && docFilters[i + 1] === value) {
-            docFilters.splice(i, 3);
-            break;
-        }
-    }
-    if (modifiers !== undefined) {
-        docFilters.push(key);
-        docFilters.push(value);
-        docFilters.push(modifiers);
-        container._docFilter = new List<string>(docFilters);
-    }
-});
\ No newline at end of file
+Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: string) { Doc.setDocFilter(container, key, value, modifiers); });
\ No newline at end of file
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 3495a934d..45ec676b2 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -101,7 +101,7 @@ export function makeEditable() {
 }
 
 let layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox",
-    "LODdisable", "dropAction", "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"];
+    "LODdisable", "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"];
 export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
     let prop = in_prop;
     if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" &&
-- 
cgit v1.2.3-70-g09d2


From 696d8c769424e4c5ff85147ecd60571a254527fd Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 6 Feb 2020 15:37:58 -0500
Subject: fixed indexing bug in pivot drill down

---
 src/client/views/collections/CollectionTimeView.tsx        |  2 +-
 .../collectionFreeForm/CollectionFreeFormLayoutEngines.tsx | 14 ++++++++++----
 .../collectionFreeForm/CollectionFreeFormView.tsx          |  2 +-
 3 files changed, 12 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index c0e18714e..fb36a1b0c 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -308,7 +308,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
 }
 
 Scripting.addGlobal(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
-    console.log("filter down to key: " + pivotDoc._pivotField + " val:" + bounds.payload);
+    pivotDoc._docFilter = new List();
     (bounds.payload as string[]).map(filterVal =>
         Doc.setDocFilter(pivotDoc, StrCast(pivotDoc._pivotField), filterVal, "check"));
 });
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 884f3b353..4ef579f27 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -66,6 +66,11 @@ function getTextWidth(text: string, font: string): number {
     return metrics.width;
 }
 
+interface pivotColumn {
+    docs: Doc[],
+    filters: string[]
+}
+
 export function computePivotLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
@@ -75,7 +80,7 @@ export function computePivotLayout(
     viewDefsToJSX: (views: any) => ViewDefResult[]
 ) {
     const fieldKey = "data";
-    const pivotColumnGroups = new Map<FieldResult<Field>, { docs: Doc[], filters: string[] }>();
+    const pivotColumnGroups = new Map<FieldResult<Field>, pivotColumn>();
 
     const pivotFieldKey = toLabel(pivotDoc._pivotField);
     for (const doc of childDocs) {
@@ -129,7 +134,8 @@ export function computePivotLayout(
     const expander = 1.05;
     const gap = .15;
     let x = 0;
-    Array.from(pivotColumnGroups.keys()).sort().forEach(key => {
+    const sortedPivotKeys = Array.from(pivotColumnGroups.keys()).sort();
+    sortedPivotKeys.forEach(key => {
         const val = pivotColumnGroups.get(key)!;
         let y = 0;
         let xCount = 0;
@@ -170,8 +176,8 @@ export function computePivotLayout(
     });
 
     const maxColHeight = pivotAxisWidth * expander * Math.ceil(maxInColumn / numCols);
-    const dividers = Array.from(pivotColumnGroups.values()).map((pkey, i) =>
-        ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap), y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pkey.filters }));
+    const dividers = sortedPivotKeys.map((key, i) =>
+        ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap), y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters }));
     groupNames.push(...dividers);
     return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a34779046..1ac0d28f5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -778,7 +778,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             const backgroundColor = Cast(viewDef.color, "string");
             return [x, y].some(val => val === undefined) ? undefined :
                 {
-                    ele: <div className="collectionFreeform-customDiv" key={"div" + x + y + z} onClick={e => this.onViewDefDivClick(e, viewDef)}
+                    ele: <div className="collectionFreeform-customDiv" title={viewDef.payload.join(" ")} key={"div" + x + y + z} onClick={e => this.onViewDefDivClick(e, viewDef)}
                         style={{ width, height, backgroundColor, transform: `translate(${x}px, ${y}px)` }} />,
                     bounds: viewDef
                 };
-- 
cgit v1.2.3-70-g09d2


From 8597134b6ada1e57ae08d49e24c00d11f728ba90 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 6 Feb 2020 16:04:04 -0500
Subject: restored isBackground for documents that don't want to respond to
 anything.

---
 src/client/util/DragManager.ts                                       | 4 ++--
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx  | 1 +
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx            | 1 +
 src/client/views/nodes/ImageBox.scss                                 | 5 ++---
 src/client/views/nodes/ImageBox.tsx                                  | 3 ++-
 5 files changed, 8 insertions(+), 6 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index b0dd90947..e572f0fcb 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -15,7 +15,7 @@ import { listSpec } from "../../new_fields/Schema";
 import { Scripting } from "./Scripting";
 import { convertDropDataToButtons } from "./DropConverter";
 
-export type dropActionType = "alias" | "copy" | "move" | undefined;
+export type dropActionType = "alias" | "copy" | undefined;
 export function SetupDrag(
     _reference: React.RefObject<HTMLElement>,
     docFunc: () => Doc | Promise<Doc> | undefined,
@@ -198,7 +198,7 @@ export namespace DragManager {
             );
             e.docDragData?.droppedDocuments.forEach((drop: Doc, i: number) =>
                 Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []).map(prop => {
-                    drop[prop] = "move";
+                    drop[prop] = undefined;
                 })
             );
         };
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1ac0d28f5..f2c4cda34 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -126,6 +126,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
+        if (this.props.Document.isBackground) return false;
         const xf = this.getTransform();
         const xfo = this.getTransformOverlay();
         const [xp, yp] = xf.transformPoint(de.x, de.y);
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 2a11267d4..f484b6115 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -88,6 +88,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 width: this.width,
                 height: this.height,
                 zIndex: this.ZInd,
+                pointerEvents: this.props.Document.isBackground ? "none" : undefined
             }} >
 
             {!this.props.fitToBox ? <DocumentView {...this.props}
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 43f4a0ba9..7bbf4a368 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -8,7 +8,7 @@
     transform-origin: top left;
 
     .imageBox-fader {
-        pointer-events: all;
+        pointer-events: inherit;
     }
 }
 
@@ -34,13 +34,12 @@
     height: 100%;
     max-width: 100%;
     max-height: 100%;
-    pointer-events: none;
+    pointer-events: inherit;
     background: transparent;
 
     img {
         height: auto;
         width: 100%;
-        pointer-events: all;
     }
 }
 
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 66b0e5a15..1951fcc1a 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -416,7 +416,8 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
             style={{
                 transform: `scale(${this.props.ContentScaling()})`,
                 width: `${100 / this.props.ContentScaling()}%`,
-                height: `${100 / this.props.ContentScaling()}%`
+                height: `${100 / this.props.ContentScaling()}%`,
+                pointerEvents: this.props.Document.isBackground ? "none" : undefined
             }} >
             <CollectionFreeFormView {...this.props}
                 PanelHeight={this.props.PanelHeight}
-- 
cgit v1.2.3-70-g09d2


From 061ed9215af59b8bc41cb3f3e8661687209341cd Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 6 Feb 2020 18:33:07 -0500
Subject: quick fixes for pivot layout & pivoting down

---
 .../collectionFreeForm/CollectionFreeFormLayoutEngines.tsx          | 6 +++---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 4 ++--
 src/server/authentication/models/current_user_utils.ts              | 2 +-
 3 files changed, 6 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 4ef579f27..e354ad0af 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -192,7 +192,7 @@ export function computeTimelineLayout(
     childDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
-    viewDefsToJSX: (views: any) => ViewDefResult[]
+    viewDefsToJSX: (views: ViewDefBounds) => ViewDefResult[]
 ) {
     const fieldKey = "data";
     const pivotDateGroups = new Map<number, Doc[]>();
@@ -274,7 +274,7 @@ export function computeTimelineLayout(
         groupNames.push({ type: "text", text: Math.ceil(maxTime).toString(), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined });
     }
 
-    const divider = { type: "div", color: "black", x: 0, y: 0, width: panelDim[0], height: 1 } as any;
+    const divider = { type: "div", color: "black", x: 0, y: 0, width: panelDim[0], height: 1, payload: undefined };
     return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]);
 
     function layoutDocsAtTime(keyDocs: Doc[], key: number) {
@@ -298,7 +298,7 @@ export function computeTimelineLayout(
 }
 
 function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map<Doc, ViewDefBounds>,
-    poolData: Map<string, PoolData>, viewDefsToJSX: (views: any) => ViewDefResult[], groupNames: ViewDefBounds[], minWidth: number, extras: ViewDefBounds[]) {
+    poolData: Map<string, PoolData>, viewDefsToJSX: (views: ViewDefBounds) => ViewDefResult[], groupNames: ViewDefBounds[], minWidth: number, extras: ViewDefBounds[]) {
 
     const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
     const docEles = childPairs.filter(d => docMap.get(d.layout)).map(pair => docMap.get(pair.layout) as ViewDefBounds);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f2c4cda34..ea3805b65 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -441,7 +441,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             const miny = this.childDataProvider(docs[0]).y;//docs.length ? NumCast(docs[0].y) : 0;
             const maxx = this.childDataProvider(docs[0]).width + minx;//docs.length ? NumCast(docs[0].width) + minx : minx;
             const maxy = this.childDataProvider(docs[0]).height + miny;//docs.length ? NumCast(docs[0].height) + miny : miny;
-            const ranges = docs.filter(doc => doc).reduce((range, doc) => {
+            const ranges = docs.filter(doc => doc).filter(doc => this.childDataProvider(doc)).reduce((range, doc) => {
                 const x = this.childDataProvider(doc).x;//NumCast(doc.x);
                 const y = this.childDataProvider(doc).y;//NumCast(doc.y);
                 const xe = this.childDataProvider(doc).width + x;//x + NumCast(layoutDoc.width);
@@ -779,7 +779,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             const backgroundColor = Cast(viewDef.color, "string");
             return [x, y].some(val => val === undefined) ? undefined :
                 {
-                    ele: <div className="collectionFreeform-customDiv" title={viewDef.payload.join(" ")} key={"div" + x + y + z} onClick={e => this.onViewDefDivClick(e, viewDef)}
+                    ele: <div className="collectionFreeform-customDiv" title={viewDef.payload?.join(" ")} key={"div" + x + y + z} onClick={e => this.onViewDefDivClick(e, viewDef)}
                         style={{ width, height, backgroundColor, transform: `translate(${x}px, ${y}px)` }} />,
                     bounds: viewDef
                 };
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index b23ab5e49..8b760db00 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -237,7 +237,7 @@ export class CurrentUserUtils {
                 Docs.Create.MulticolumnDocument([], { title: "images", _height: 200, _xMargin: 10, _yMargin: 10 }),
                 Docs.Create.TextDocument("", { title: "contents", _height: 100 })
             ],
-            { _width: 400, _height: 300, title: "slide", _chromeStatus: "disabled", _autoHeight: true });
+            { _width: 400, _height: 300, title: "slide", _chromeStatus: "disabled", backgroundColor: "lightGray", _autoHeight: true });
         slideTemplate.isTemplateDoc = makeTemplate(slideTemplate);
 
         const iconDoc = Docs.Create.TextDocument("", { title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onClick: ScriptField.MakeScript("setNativeView(this)") });
-- 
cgit v1.2.3-70-g09d2


From 9f82ece7c763ba4a054d86a715311e0280fcb79f Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 7 Feb 2020 01:22:48 -0500
Subject: sped up pivot viewer by not recreating docs when pivot changes.

---
 .../views/collections/CollectionStackingView.tsx   |  6 ++--
 .../CollectionStackingViewFieldColumn.tsx          |  2 +-
 src/client/views/collections/CollectionSubView.tsx | 29 +---------------
 .../views/collections/CollectionTimeView.scss      |  5 +++
 .../views/collections/CollectionTimeView.tsx       | 39 +++++++++++++++-------
 .../CollectionFreeFormLayoutEngines.tsx            | 20 ++++++-----
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 37 +++++++++++++++++---
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  1 +
 8 files changed, 83 insertions(+), 56 deletions(-)

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

diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 01a2f1042..e04f4e8a6 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -52,11 +52,11 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
     @computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; }
 
-    children(docs: Doc[]) {
+    children(docs: Doc[], columns?: number) {
         this._docXfs.length = 0;
         return docs.map((d, i) => {
             const height = () => this.getDocHeight(d);
-            const width = () => (this.widthScale ? this.widthScale : 1) * Math.min(d._nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
+            const width = () => (this.widthScale && !columns ? this.widthScale : 1) * Math.min(d._nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
             const dref = React.createRef<HTMLDivElement>();
             const dxf = () => this.getDocTransform(d, dref.current!);
             this._docXfs.push({ dxf: dxf, width: width, height: height });
@@ -381,7 +381,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return Math.min(this.props.Document[WidthSym]() / this.props.PanelWidth(), this.props.Document[HeightSym]() / this.props.PanelHeight());
     }
     @computed get widthScale() {
-        return StrCast(this.props.Document.title).includes("slide") ? this.heightScale : undefined;
+        return StrCast(this.props.Document.title).includes("slide") || true ? this.heightScale : undefined;
     }
     render() {
         TraceMobx();
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index b81b1f31d..21982f1ca 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -407,7 +407,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                                     gridTemplateColumns: singleColumn ? undefined : templatecols,
                                     gridAutoRows: singleColumn ? undefined : "0px"
                                 }}>
-                                {this.props.parent.children(this.props.docList)}
+                                {this.props.parent.children(this.props.docList, uniqueHeadings.length)}
                                 {singleColumn ? (null) : this.props.parent.columnDragger}
                             </div>
                             {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ?
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index a2700e75a..e0e99d635 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -111,34 +111,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         get childDocs() {
             const docs = DocListCast(this.dataField);
             const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
-            const viewedDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
-            const docFilters = Cast(this.props.Document._docFilter, listSpec("string"), []);
-            const clusters: { [key: string]: { [value: string]: string } } = {};
-            for (let i = 0; i < docFilters.length; i += 3) {
-                const [key, value, modifiers] = docFilters.slice(i, i + 3);
-                const cluster = clusters[key];
-                if (!cluster) {
-                    const child: { [value: string]: string } = {};
-                    child[value] = modifiers;
-                    clusters[key] = child;
-                } else {
-                    cluster[value] = modifiers;
-                }
-            }
-            const filteredDocs = docFilters.length ? viewedDocs.filter(d => {
-                for (const key of Object.keys(clusters)) {
-                    const cluster = clusters[key];
-                    const satisfiesFacet = Object.keys(cluster).some(inner => {
-                        const modifier = cluster[inner];
-                        return (modifier === "x") !== Doc.matchFieldValue(d, key, inner);
-                    });
-                    if (!satisfiesFacet) {
-                        return false;
-                    }
-                }
-                return true;
-            }) : viewedDocs;
-            return filteredDocs;
+            return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
         }
 
         @action
diff --git a/src/client/views/collections/CollectionTimeView.scss b/src/client/views/collections/CollectionTimeView.scss
index df5057b5b..a5ce73a92 100644
--- a/src/client/views/collections/CollectionTimeView.scss
+++ b/src/client/views/collections/CollectionTimeView.scss
@@ -5,6 +5,11 @@
     height: 100%;
     width: 100%;
     overflow: hidden;
+    .collectionTimeView-backBtn {
+        background: green;
+        display: inline;
+        margin-right: 20px;
+    }
     .collectionFreeform-customText {
         text-align: left;
     }
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index fb36a1b0c..0c1f93829 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,27 +1,27 @@
 import { faEdit } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, IReactionDisposer, observable, trace } from "mobx";
+import { action, computed, observable, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Set } from "typescript-collections";
-import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Field } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
+import { RichTextField } from "../../../new_fields/RichTextField";
 import { listSpec } from "../../../new_fields/Schema";
 import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
-import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { Docs } from "../../documents/Documents";
+import { Scripting } from "../../util/Scripting";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
 import { EditableView } from "../EditableView";
 import { anchorPoints, Flyout } from "../TemplateMenu";
+import { ViewDefBounds } from "./collectionFreeForm/CollectionFreeFormLayoutEngines";
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
-import "./CollectionTimeView.scss";
 import { CollectionSubView } from "./CollectionSubView";
-import { CollectionTreeView } from "./CollectionTreeView";
+import "./CollectionTimeView.scss";
 import React = require("react");
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { RichTextField } from "../../../new_fields/RichTextField";
-import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { Scripting } from "../../util/Scripting";
-import { ViewDefResult, ViewDefBounds } from "./collectionFreeForm/CollectionFreeFormLayoutEngines";
+import { CollectionTreeView } from "./CollectionTreeView";
+import { ObjectField } from "../../../new_fields/ObjectField";
 
 @observer
 export class CollectionTimeView extends CollectionSubView(doc => doc) {
@@ -289,7 +289,19 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
             <div className={"collectionTimeView" + (doTimeline ? "" : "-pivot")} onContextMenu={this.specificMenu}
                 style={{ height: `calc(100%  - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
                 <div className={"pivotKeyEntry"}>
-                    <EditableView {...newEditableViewProps} menuCallback={this.menuCallback} />
+                    <button className="collectionTimeView-backBtn" style={{ width: 50, height: 20, background: "green" }}
+                        onClick={action(() => {
+                            let pfilterIndex = NumCast(this.props.Document._pfilterIndex);
+                            if (pfilterIndex > 0) {
+                                this.props.Document._docFilter = ObjectField.MakeCopy(this.props.Document["_pfilter" + --pfilterIndex] as ObjectField);
+                                this.props.Document._pfilterIndex = pfilterIndex;
+                            } else {
+                                this.props.Document._docFilter = new List([]);
+                            }
+                        })}>
+                        back
+                    </button>
+                    <EditableView {...newEditableViewProps} display={"inline"} menuCallback={this.menuCallback} />
                 </div>
                 {!this.props.isSelected() || this.props.PanelHeight() < 100 ? (null) :
                     <div className="collectionTimeView-dragger" key="dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this._facetWidth}px, 0px)` }} >
@@ -308,6 +320,9 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
 }
 
 Scripting.addGlobal(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
+    let pfilterIndex = NumCast(pivotDoc._pfilterIndex);
+    pivotDoc["_pfilter" + pfilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilter as ObjectField);
+    pivotDoc._pfilterIndex = ++pfilterIndex;
     pivotDoc._docFilter = new List();
     (bounds.payload as string[]).map(filterVal =>
         Doc.setDocFilter(pivotDoc, StrCast(pivotDoc._pivotField), filterVal, "check"));
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index e354ad0af..95f7794bb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -36,7 +36,6 @@ export interface PoolData {
     color?: string,
     transition?: string,
     highlight?: boolean,
-    state?: any
 }
 
 export interface ViewDefResult {
@@ -71,19 +70,21 @@ interface pivotColumn {
     filters: string[]
 }
 
+
 export function computePivotLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
     childDocs: Doc[],
+    filterDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
-    viewDefsToJSX: (views: any) => ViewDefResult[]
+    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
     const fieldKey = "data";
     const pivotColumnGroups = new Map<FieldResult<Field>, pivotColumn>();
 
     const pivotFieldKey = toLabel(pivotDoc._pivotField);
-    for (const doc of childDocs) {
+    for (const doc of filterDocs) {
         const val = Field.toString(doc[pivotFieldKey] as Field);
         if (val) {
             !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val] });
@@ -179,7 +180,7 @@ export function computePivotLayout(
     const dividers = sortedPivotKeys.map((key, i) =>
         ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap), y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters }));
     groupNames.push(...dividers);
-    return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
+    return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, [], childDocs.filter(c => !filterDocs.includes(c)));
 }
 
 function toNumber(val: FieldResult<Field>) {
@@ -190,9 +191,10 @@ export function computeTimelineLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
     childDocs: Doc[],
+    filterDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
-    viewDefsToJSX: (views: ViewDefBounds) => ViewDefResult[]
+    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
     const fieldKey = "data";
     const pivotDateGroups = new Map<number, Doc[]>();
@@ -212,7 +214,7 @@ export function computeTimelineLayout(
 
     let minTime = Number.MAX_VALUE;
     let maxTime = -Number.MAX_VALUE;
-    childDocs.map(doc => {
+    filterDocs.map(doc => {
         const num = NumCast(doc[timelineFieldKey], Number(StrCast(doc[timelineFieldKey])));
         if (!(Number.isNaN(num) || (minTimeReq && num < minTimeReq) || (maxTimeReq && num > maxTimeReq))) {
             !pivotDateGroups.get(num) && pivotDateGroups.set(num, []);
@@ -275,7 +277,7 @@ export function computeTimelineLayout(
     }
 
     const divider = { type: "div", color: "black", x: 0, y: 0, width: panelDim[0], height: 1, payload: undefined };
-    return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]);
+    return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider], childDocs.filter(c => !filterDocs.includes(c)));
 
     function layoutDocsAtTime(keyDocs: Doc[], key: number) {
         keyDocs.forEach(doc => {
@@ -298,7 +300,8 @@ export function computeTimelineLayout(
 }
 
 function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map<Doc, ViewDefBounds>,
-    poolData: Map<string, PoolData>, viewDefsToJSX: (views: ViewDefBounds) => ViewDefResult[], groupNames: ViewDefBounds[], minWidth: number, extras: ViewDefBounds[]) {
+    poolData: Map<string, PoolData>, viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], groupNames: ViewDefBounds[], minWidth: number, extras: ViewDefBounds[],
+    extraDocs: Doc[]) {
 
     const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
     const docEles = childPairs.filter(d => docMap.get(d.layout)).map(pair => docMap.get(pair.layout) as ViewDefBounds);
@@ -323,6 +326,7 @@ function normalizeResults(panelDim: number[], fontHeight: number, childPairs: {
             poolData.set(pair.layout[Id], { transition: "transform 1s", ...newPos });
         }
     });
+    extraDocs.map(ed => poolData.set(ed[Id], { x: 0, y: 0, zIndex: -99 }));
 
     return {
         elements: viewDefsToJSX(extras.concat(groupNames.map(gname => ({
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ea3805b65..2518a4a55 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -7,7 +7,7 @@ import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync, Field } f
 import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
 import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkTool, InkField, InkData } from "../../../../new_fields/InkField";
-import { createSchema, makeInterface } from "../../../../new_fields/Schema";
+import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Schema";
 import { ScriptField } from "../../../../new_fields/ScriptField";
 import { BoolCast, Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
@@ -794,12 +794,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }.bind(this));
 
     doTimelineLayout(poolData: Map<string, any>) {
-        return computeTimelineLayout(poolData, this.props.Document, this.childDocs,
+        return computeTimelineLayout(poolData, this.props.Document, this.childDocs, this.filterDocs,
             this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
     }
 
     doPivotLayout(poolData: Map<string, any>) {
-        return computePivotLayout(poolData, this.props.Document, this.childDocs,
+        return computePivotLayout(poolData, this.props.Document, this.childDocs, this.filterDocs,
             this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
     }
 
@@ -812,7 +812,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
         this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => {
             const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state });
-            state = pos.state === undefined ? state : pos.state;
             poolData.set(pair.layout[Id], pos);
         });
         return { elements: elements };
@@ -826,6 +825,36 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         }
         return { newPool, computedElementData: this.doFreeformLayout(newPool) };
     }
+
+    @computed get filterDocs() {
+        const docFilters = Cast(this.props.Document._docFilter, listSpec("string"), []);
+        const clusters: { [key: string]: { [value: string]: string } } = {};
+        for (let i = 0; i < docFilters.length; i += 3) {
+            const [key, value, modifiers] = docFilters.slice(i, i + 3);
+            const cluster = clusters[key];
+            if (!cluster) {
+                const child: { [value: string]: string } = {};
+                child[value] = modifiers;
+                clusters[key] = child;
+            } else {
+                cluster[value] = modifiers;
+            }
+        }
+        const filteredDocs = docFilters.length ? this.childDocs.filter(d => {
+            for (const key of Object.keys(clusters)) {
+                const cluster = clusters[key];
+                const satisfiesFacet = Object.keys(cluster).some(inner => {
+                    const modifier = cluster[inner];
+                    return (modifier === "x") !== Doc.matchFieldValue(d, key, inner);
+                });
+                if (!satisfiesFacet) {
+                    return false;
+                }
+            }
+            return true;
+        }) : this.childDocs;
+        return filteredDocs;
+    }
     get doLayoutComputation() {
         const { newPool, computedElementData } = this.doInternalLayoutComputation;
         runInAction(() =>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index f484b6115..3bceec45f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -88,6 +88,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 width: this.width,
                 height: this.height,
                 zIndex: this.ZInd,
+                display: this.ZInd === -99 ? "none" : undefined,
                 pointerEvents: this.props.Document.isBackground ? "none" : undefined
             }} >
 
-- 
cgit v1.2.3-70-g09d2

