From 0a39ac7f93f2035115aa929c77ad0e8b797f9fb1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 18 Jul 2019 01:20:58 -0400
Subject: stackingViewwithSections

---
 .../views/collections/CollectionStackingView.tsx   | 57 ++++++++++++++--------
 1 file changed, 37 insertions(+), 20 deletions(-)

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

diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 6d9e942c9..2f26846af 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, reaction, untracked, observable, r
 import { observer } from "mobx-react";
 import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
-import { BoolCast, NumCast, Cast } from "../../../new_fields/Types";
+import { BoolCast, NumCast, Cast, StrCast } from "../../../new_fields/Types";
 import { emptyFunction, Utils } from "../../../Utils";
 import { ContextMenu } from "../ContextMenu";
 import { CollectionSchemaPreview } from "./CollectionSchemaView";
@@ -15,6 +15,7 @@ import { DragManager } from "../../util/DragManager";
 import { DocumentType } from "../../documents/Documents";
 import { Transform } from "../../util/Transform";
 import { CursorProperty } from "csstype";
+import { string } from "prop-types";
 
 @observer
 export class CollectionStackingView extends CollectionSubView(doc => doc) {
@@ -104,10 +105,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return this.offsetTransform(doc, translate[0], translate[1]);
     }
 
-    @computed
-    get children() {
+    children(docs: Doc[]) {
         this._docXfs.length = 0;
-        return this.filteredChildren.map((d, i) => {
+        return docs.map((d, i) => {
             let layoutDoc = Doc.expandTemplateLayout(d, this.props.DataDoc);
             let width = () => d.nativeWidth ? Math.min(layoutDoc[WidthSym](), this.columnWidth) : this.columnWidth;
             let height = () => this.getDocHeight(layoutDoc);
@@ -219,28 +219,45 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             }
         });
     }
-    render() {
+    section(heading: string, docList: Doc[]) {
         let cols = this.singleColumn ? 1 : Math.max(1, Math.min(this.filteredChildren.length,
             Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))));
         let templatecols = "";
         for (let i = 0; i < cols; i++) templatecols += `${this.columnWidth}px `;
+        return <>
+            {heading ? <div key={`${heading}`} className="collectionStackingView-sectionHeader">{heading}</div> : (null)}
+            <div key={`${heading}-stack`} className={`collectionStackingView-masonry${this.singleColumn ? "Single" : "Grid"}`}
+                style={{
+                    padding: this.singleColumn ? `${this.yMargin}px ${this.xMargin}px ${this.yMargin}px ${this.xMargin}px` : `${this.yMargin}px ${this.xMargin}px`,
+                    margin: "auto",
+                    width: this.singleColumn ? undefined : `${cols * (this.columnWidth + this.gridGap) + 2 * this.xMargin - this.gridGap}px`,
+                    height: this.singleColumn ? "100%" : 'max-content',
+                    position: "relative",
+                    gridGap: this.gridGap,
+                    gridTemplateColumns: this.singleColumn ? undefined : templatecols,
+                    gridAutoRows: this.singleColumn ? undefined : `${this._gridSize}px`
+                }}
+            >
+                {this.children(docList)}
+                {this.singleColumn ? (null) : this.columnDragger}
+            </div></>;
+    }
+    render() {
+        let sectionFilter = StrCast(this.props.Document.sectionFilter);
+        let fields = new Map<object, Doc[]>();
+        sectionFilter && this.filteredChildren.map(d => {
+            if (!fields.has(d[sectionFilter] as object)) fields.set(d[sectionFilter] as object, [d]);
+            else fields.get(d[sectionFilter] as object)!.push(d);
+        });
         return (
             <div className="collectionStackingView" ref={this.createRef} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onWheel={(e: React.WheelEvent) => e.stopPropagation()} >
-                <div className={`collectionStackingView-masonry${this.singleColumn ? "Single" : "Grid"}`}
-                    style={{
-                        padding: this.singleColumn ? `${this.yMargin}px ${this.xMargin}px ${this.yMargin}px ${this.xMargin}px` : `${this.yMargin}px ${this.xMargin}px`,
-                        margin: "auto",
-                        width: this.singleColumn ? undefined : `${cols * (this.columnWidth + this.gridGap) + 2 * this.xMargin - this.gridGap}px`,
-                        height: "100%",
-                        position: "relative",
-                        gridGap: this.gridGap,
-                        gridTemplateColumns: this.singleColumn ? undefined : templatecols,
-                        gridAutoRows: this.singleColumn ? undefined : `${this._gridSize}px`
-                    }}
-                >
-                    {this.children}
-                    {this.singleColumn ? (null) : this.columnDragger}
-                </div>
+                {/* {sectionFilter as boolean ? [
+                    ["width > height", this.filteredChildren.filter(f => f[WidthSym]() >= 1 + f[HeightSym]())],
+                    ["width = height", this.filteredChildren.filter(f => Math.abs(f[WidthSym]() - f[HeightSym]()) < 1)],
+                    ["height > width", this.filteredChildren.filter(f => f[WidthSym]() + 1 <= f[HeightSym]())]]. */}
+                {sectionFilter ? Array.from(fields.entries()).
+                    map(section => this.section(section[0].toString(), section[1] as Doc[])) :
+                    this.section("", this.filteredChildren)}
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2


From 123027bf525bfb6d635cf2304d01490bccb4abff Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 18 Jul 2019 02:22:27 -0400
Subject: fixes for single column layouts not contained in a free form view.

---
 .../views/collections/CollectionBaseView.tsx       |  3 +--
 .../views/collections/CollectionStackingView.tsx   | 27 ++++++++++++++++------
 2 files changed, 21 insertions(+), 9 deletions(-)

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

diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index eba69b448..2603dc60b 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -78,7 +78,6 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
 
     @action.bound
     addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {
-        let self = this;
         var curPage = NumCast(this.props.Document.curPage, -1);
         Doc.GetProto(doc).page = curPage;
         if (curPage >= 0) {
@@ -146,7 +145,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         const viewtype = this.collectionViewType;
         return (
             <div id="collectionBaseView"
-                style={{ boxShadow: `#9c9396 ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}` }}
+                style={{ overflow: "auto", boxShadow: `#9c9396 ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}` }}
                 className={this.props.className || "collectionView-cont"}
                 onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
                 {viewtype !== undefined ? this.props.children(viewtype, props) : (null)}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 2f26846af..c42a423c1 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -34,9 +34,18 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
 
     componentDidMount() {
         this._heightDisposer = reaction(() => [this.yMargin, this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])],
-            () => this.singleColumn &&
-                (this.props.Document.height = this.filteredChildren.reduce((height, d, i) =>
-                    height + this.getDocHeight(d) + (i === this.filteredChildren.length - 1 ? this.yMargin : this.gridGap), this.yMargin))
+            () => {
+                if (this.singleColumn) {
+                    let sectionFilter = StrCast(this.props.Document.sectionFilter);
+                    let fields = new Map<object, Doc[]>();
+                    sectionFilter && this.filteredChildren.map(d => {
+                        if (!fields.has(d[sectionFilter] as object)) fields.set(d[sectionFilter] as object, [d]);
+                        else fields.get(d[sectionFilter] as object)!.push(d);
+                    });
+                    (this.props.Document.height = fields.size * 50 + this.filteredChildren.reduce((height, d, i) =>
+                        height + this.getDocHeight(d) + (i === this.filteredChildren.length - 1 ? this.yMargin : this.gridGap), this.yMargin));
+                }
+            }
             , { fireImmediately: true });
     }
     componentWillUnmount() {
@@ -112,10 +121,11 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             let width = () => d.nativeWidth ? Math.min(layoutDoc[WidthSym](), this.columnWidth) : this.columnWidth;
             let height = () => this.getDocHeight(layoutDoc);
             if (this.singleColumn) {
+                //have to add the height of all previous single column sections or the doc decorations will be in the wrong place.
                 let dxf = () => this.getSingleDocTransform(layoutDoc, i, width());
-                let rowHgtPcnt = height() / (this.props.Document[HeightSym]() - 2 * this.yMargin) * 100;
+                let rowHgtPcnt = height();
                 this._docXfs.push({ dxf: dxf, width: width, height: height });
-                return <div className="collectionStackingView-columnDoc" key={d[Id]} style={{ width: width(), marginTop: i === 0 ? 0 : this.gridGap, height: `${rowHgtPcnt}%` }} >
+                return <div className="collectionStackingView-columnDoc" key={d[Id]} style={{ width: width(), marginTop: i === 0 ? 0 : this.gridGap, height: `${rowHgtPcnt}` }} >
                     {this.getDisplayDoc(layoutDoc, d, dxf)}
                 </div>;
             } else {
@@ -231,7 +241,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     padding: this.singleColumn ? `${this.yMargin}px ${this.xMargin}px ${this.yMargin}px ${this.xMargin}px` : `${this.yMargin}px ${this.xMargin}px`,
                     margin: "auto",
                     width: this.singleColumn ? undefined : `${cols * (this.columnWidth + this.gridGap) + 2 * this.xMargin - this.gridGap}px`,
-                    height: this.singleColumn ? "100%" : 'max-content',
+                    height: 'max-content',
                     position: "relative",
                     gridGap: this.gridGap,
                     gridTemplateColumns: this.singleColumn ? undefined : templatecols,
@@ -250,7 +260,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             else fields.get(d[sectionFilter] as object)!.push(d);
         });
         return (
-            <div className="collectionStackingView" ref={this.createRef} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onWheel={(e: React.WheelEvent) => e.stopPropagation()} >
+            <div className="collectionStackingView"
+
+                style={{ height: this.props.Document[HeightSym]() }}
+                ref={this.createRef} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onWheel={(e: React.WheelEvent) => e.stopPropagation()} >
                 {/* {sectionFilter as boolean ? [
                     ["width > height", this.filteredChildren.filter(f => f[WidthSym]() >= 1 + f[HeightSym]())],
                     ["width = height", this.filteredChildren.filter(f => Math.abs(f[WidthSym]() - f[HeightSym]()) < 1)],
-- 
cgit v1.2.3-70-g09d2


From 922c418f1207c43150c499e075a9c1be34719b58 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 18 Jul 2019 17:36:37 -0400
Subject: fixes for stacking view and added detailedLayout

---
 src/client/views/collections/CollectionStackingView.scss |  5 +++--
 src/client/views/collections/CollectionStackingView.tsx  |  8 ++++----
 src/client/views/collections/CollectionView.tsx          |  3 ++-
 src/client/views/nodes/DocumentView.tsx                  | 13 ++++---------
 src/new_fields/Doc.ts                                    | 11 +++++++++--
 5 files changed, 22 insertions(+), 18 deletions(-)

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

diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index b6ad47813..7ebf5f77c 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -1,8 +1,9 @@
 @import "../globalCssVariables";
 .collectionStackingView {
-    overflow-y: auto;
     height: 100%;
-
+    width: 100%;
+    position: absolute;
+    overflow-y: auto;
     .collectionStackingView-docView-container {
         width: 45%;
         margin: 5% 2.5%;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index c42a423c1..4424cffe1 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -15,6 +15,7 @@ import { DragManager } from "../../util/DragManager";
 import { DocumentType } from "../../documents/Documents";
 import { Transform } from "../../util/Transform";
 import { CursorProperty } from "csstype";
+import { COLLECTION_BORDER_WIDTH } from "../../views/globalCssVariables.scss";
 import { string } from "prop-types";
 
 @observer
@@ -107,6 +108,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         let { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
         return this.offsetTransform(doc, translateX, translateY);
     }
+
     getSingleDocTransform(doc: Doc, ind: number, width: number) {
         let localY = this.filteredChildren.reduce((height, d, i) =>
             height + (i < ind ? this.getDocHeight(Doc.expandTemplateLayout(d, this.props.DataDoc)) + this.gridGap : 0), this.yMargin);
@@ -234,7 +236,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))));
         let templatecols = "";
         for (let i = 0; i < cols; i++) templatecols += `${this.columnWidth}px `;
-        return <>
+        return <div key={heading}>
             {heading ? <div key={`${heading}`} className="collectionStackingView-sectionHeader">{heading}</div> : (null)}
             <div key={`${heading}-stack`} className={`collectionStackingView-masonry${this.singleColumn ? "Single" : "Grid"}`}
                 style={{
@@ -250,7 +252,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             >
                 {this.children(docList)}
                 {this.singleColumn ? (null) : this.columnDragger}
-            </div></>;
+            </div></div>;
     }
     render() {
         let sectionFilter = StrCast(this.props.Document.sectionFilter);
@@ -261,8 +263,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         });
         return (
             <div className="collectionStackingView"
-
-                style={{ height: this.props.Document[HeightSym]() }}
                 ref={this.createRef} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onWheel={(e: React.WheelEvent) => e.stopPropagation()} >
                 {/* {sectionFilter as boolean ? [
                     ["width > height", this.filteredChildren.filter(f => f[WidthSym]() >= 1 + f[HeightSym]())],
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 377a46535..1b85a0cdb 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -46,6 +46,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
 
     get isAnnotationOverlay() { return this.props.fieldExt ? true : false; }
 
+    static _applyCount: number = 0;
     onContextMenu = (e: React.MouseEvent): void => {
         if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             let subItems: ContextMenuProps[] = [];
@@ -62,7 +63,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
                     let otherdoc = new Doc();
                     otherdoc.width = 100;
                     otherdoc.height = 50;
-                    Doc.GetProto(otherdoc).title = "applied(" + this.props.Document.title + ")";
+                    Doc.GetProto(otherdoc).title = this.props.Document.title + "(..." + CollectionView._applyCount++ + ")"; // previously "applied"
                     Doc.GetProto(otherdoc).layout = Doc.MakeDelegate(this.props.Document);
                     Doc.GetProto(otherdoc).miniLayout = StrCast(this.props.Document.miniLayout);
                     Doc.GetProto(otherdoc).detailedLayout = Doc.GetProto(otherdoc).layout;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 899c47cb3..76b8658a5 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -291,7 +291,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (this._doubleTap && this.props.renderDepth) {
             let fullScreenAlias = Doc.MakeAlias(this.props.Document);
             fullScreenAlias.templates = new List<string>();
-            if (this.props.Document.layout === this.props.Document.miniLayout) fullScreenAlias.layout = this.props.Document.detailedLayout instanceof Doc ? this.props.Document.detailedLayout : StrCast(this.props.Document.detailedLayout);
+            if (this.props.Document.layout === this.props.Document.miniLayout) {
+                Doc.ToggleDetailLayout(fullScreenAlias);
+            }
             this.props.addDocTab(fullScreenAlias, this.dataDoc, "inTab");
             SelectionManager.DeselectAll();
             this.props.Document.libraryBrush = undefined;
@@ -557,14 +559,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }, icon: "search"
         });
         if (this.props.Document.detailedLayout && !this.props.Document.isTemplate) {
-            cm.addItem({
-                description: "Toggle detail", event: async () => {
-                    let d = this.props.Document;
-                    let miniLayout = await PromiseValue(d.miniLayout);
-                    let detailLayout = await PromiseValue(d.detailedLayout);
-                    d.layout !== miniLayout ? miniLayout && (d.layout = d.miniLayout) : detailLayout && (d.layout = detailLayout);
-                }, icon: "image"
-            });
+            cm.addItem({ description: "Toggle detail", event: () => Doc.ToggleDetailLayout(this.props.Document), icon: "image" });
         }
         cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
         cm.addItem({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 7180564ea..9a4b817d3 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -316,7 +316,7 @@ export namespace Doc {
         if (extensionDoc === undefined) {
             setTimeout(() => {
                 let docExtensionForField = new Doc(doc[Id] + fieldKey, true);
-                docExtensionForField.title = "Extension of " + doc.title + "'s field:" + fieldKey;
+                docExtensionForField.title = doc.title + ":" + fieldKey + ".ext";
                 docExtensionForField.extendsDoc = doc;
                 let proto: Doc | undefined = doc;
                 while (proto && !Doc.IsPrototype(proto)) {
@@ -351,7 +351,7 @@ export namespace Doc {
         if (expandedTemplateLayout === undefined && BoolCast(templateLayoutDoc.isTemplate)) {
             setTimeout(() => {
                 templateLayoutDoc["_expanded_" + dataDoc[Id]] = Doc.MakeDelegate(templateLayoutDoc);
-                (templateLayoutDoc["_expanded_" + dataDoc[Id]] as Doc).title = templateLayoutDoc.title + " applied to " + dataDoc.title;
+                (templateLayoutDoc["_expanded_" + dataDoc[Id]] as Doc).title = templateLayoutDoc.title + "[" + StrCast(dataDoc.title).match(/\.\.\.[0-9]*/) + "]";  // previously: "applied to"
                 (templateLayoutDoc["_expanded_" + dataDoc[Id]] as Doc).isExpandedTemplate = templateLayoutDoc;
             }, 0);
         }
@@ -420,4 +420,11 @@ export namespace Doc {
         fieldTemplate.showTitle = "title";
         setTimeout(() => fieldTemplate.proto = proto);
     }
+
+    export async function ToggleDetailLayout(d: Doc) {
+        let miniLayout = await PromiseValue(d.miniLayout);
+        let detailLayout = await PromiseValue(d.detailedLayout);
+        d.layout !== miniLayout ? miniLayout && (d.layout = d.miniLayout) : detailLayout && (d.layout = detailLayout);
+        if (d.layout === detailLayout) Doc.GetProto(d).nativeWidth = Doc.GetProto(d).nativeHeight = undefined;
+    }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From ae07ba8fb410752ea98702219247ce5f89d1758b Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 19 Jul 2019 14:42:39 -0400
Subject: fixes for templates and stacking views

---
 src/client/documents/Documents.ts                       | 3 ++-
 src/client/views/collections/CollectionStackingView.tsx | 5 +++--
 src/new_fields/Doc.ts                                   | 2 ++
 3 files changed, 7 insertions(+), 3 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 3c248760b..11df6c152 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -55,7 +55,8 @@ export enum DocumentType {
     ICON = "icon",
     IMPORT = "import",
     LINK = "link",
-    LINKDOC = "linkdoc"
+    LINKDOC = "linkdoc",
+    TEMPLATE = "template"
 }
 
 export interface DocumentOptions {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 4424cffe1..039bd7b3a 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -258,8 +258,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         let sectionFilter = StrCast(this.props.Document.sectionFilter);
         let fields = new Map<object, Doc[]>();
         sectionFilter && this.filteredChildren.map(d => {
-            if (!fields.has(d[sectionFilter] as object)) fields.set(d[sectionFilter] as object, [d]);
-            else fields.get(d[sectionFilter] as object)!.push(d);
+            let sectionValue = (d[sectionFilter] ? d[sectionFilter] : "-undefined-") as object;
+            if (!fields.has(sectionValue)) fields.set(sectionValue, [d]);
+            else fields.get(sectionValue)!.push(d);
         });
         return (
             <div className="collectionStackingView"
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 7bf1dec53..152a48e5f 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -11,6 +11,7 @@ import { ToScriptString, SelfProxy, Parent, OnUpdate, Self, HandleUpdate, Update
 import { scriptingGlobal } from "../client/util/Scripting";
 import { List } from "./List";
 import { string } from "prop-types";
+import { DocumentType } from "../client/documents/Documents";
 
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -422,6 +423,7 @@ export namespace Doc {
         fieldTemplate.nativeHeight = nh;
         fieldTemplate.isTemplate = true;
         fieldTemplate.showTitle = "title";
+        fieldTemplate.type = DocumentType.TEMPLATE;
         setTimeout(() => fieldTemplate.proto = proto);
     }
 
-- 
cgit v1.2.3-70-g09d2


From 5e9bcf2e35415fd0ab4dec4f0141511cd4d312d0 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 19 Jul 2019 16:03:09 -0400
Subject: split masonry collection view into its own view type.

---
 src/client/documents/Documents.ts                  |  4 ++
 .../views/collections/CollectionBaseView.tsx       |  3 +-
 .../views/collections/CollectionStackingView.tsx   | 58 +++++++---------------
 src/client/views/collections/CollectionView.tsx    | 10 ++--
 4 files changed, 32 insertions(+), 43 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 11df6c152..47ed33adf 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -410,6 +410,10 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Stacking });
         }
 
+        export function MasonryDocument(documents: Array<Doc>, options: DocumentOptions) {
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Masonry });
+        }
+
         export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id);
         }
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 2603dc60b..72faf52c4 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -18,7 +18,8 @@ export enum CollectionViewType {
     Schema,
     Docking,
     Tree,
-    Stacking
+    Stacking,
+    Masonry
 }
 
 export interface CollectionRenderProps {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 039bd7b3a..0e5f9a321 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -6,7 +6,6 @@ import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { BoolCast, NumCast, Cast, StrCast } from "../../../new_fields/Types";
 import { emptyFunction, Utils } from "../../../Utils";
-import { ContextMenu } from "../ContextMenu";
 import { CollectionSchemaPreview } from "./CollectionSchemaView";
 import "./CollectionStackingView.scss";
 import { CollectionSubView } from "./CollectionSubView";
@@ -15,16 +14,14 @@ import { DragManager } from "../../util/DragManager";
 import { DocumentType } from "../../documents/Documents";
 import { Transform } from "../../util/Transform";
 import { CursorProperty } from "csstype";
-import { COLLECTION_BORDER_WIDTH } from "../../views/globalCssVariables.scss";
-import { string } from "prop-types";
 
 @observer
 export class CollectionStackingView extends CollectionSubView(doc => doc) {
     _masonryGridRef: HTMLDivElement | null = null;
     _draggerRef = React.createRef<HTMLDivElement>();
     _heightDisposer?: IReactionDisposer;
-    _gridSize = 1;
     _docXfs: any[] = [];
+    _columnStart: number = 0;
     @observable private cursor: CursorProperty = "grab";
     @computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); }
     @computed get yMargin() { return NumCast(this.props.Document.yMargin, 2 * this.gridGap); }
@@ -33,24 +30,25 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @computed get columnWidth() { return this.singleColumn ? (this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin) : Math.min(this.props.PanelWidth() - 2 * this.xMargin, NumCast(this.props.Document.columnWidth, 250)); }
     @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized); }
 
+    @computed get Sections() {
+        let sectionFilter = StrCast(this.props.Document.sectionFilter);
+        let fields = new Map<object, Doc[]>();
+        sectionFilter && this.filteredChildren.map(d => {
+            let sectionValue = (d[sectionFilter] ? d[sectionFilter] : "-undefined-") as object;
+            if (!fields.has(sectionValue)) fields.set(sectionValue, [d]);
+            else fields.get(sectionValue)!.push(d);
+        });
+        return fields;
+    }
     componentDidMount() {
         this._heightDisposer = reaction(() => [this.yMargin, this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])],
-            () => {
-                if (this.singleColumn) {
-                    let sectionFilter = StrCast(this.props.Document.sectionFilter);
-                    let fields = new Map<object, Doc[]>();
-                    sectionFilter && this.filteredChildren.map(d => {
-                        if (!fields.has(d[sectionFilter] as object)) fields.set(d[sectionFilter] as object, [d]);
-                        else fields.get(d[sectionFilter] as object)!.push(d);
-                    });
-                    (this.props.Document.height = fields.size * 50 + this.filteredChildren.reduce((height, d, i) =>
-                        height + this.getDocHeight(d) + (i === this.filteredChildren.length - 1 ? this.yMargin : this.gridGap), this.yMargin));
-                }
-            }
+            () => this.singleColumn &&
+                (this.props.Document.height = this.Sections.size * 50 + this.filteredChildren.reduce((height, d, i) =>
+                    height + this.getDocHeight(d) + (i === this.filteredChildren.length - 1 ? this.yMargin : this.gridGap), this.yMargin))
             , { fireImmediately: true });
     }
     componentWillUnmount() {
-        if (this._heightDisposer) this._heightDisposer();
+        this._heightDisposer && this._heightDisposer();
     }
 
     @action
@@ -98,7 +96,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return (nw && nh) ? wid * aspect : d[HeightSym]();
     }
 
-
     offsetTransform(doc: Doc, translateX: number, translateY: number) {
         let outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
         let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
@@ -133,7 +130,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             } else {
                 let dref = React.createRef<HTMLDivElement>();
                 let dxf = () => this.getDocTransform(layoutDoc, dref.current!);
-                let rowSpan = Math.ceil((height() + this.gridGap) / (this._gridSize + this.gridGap));
+                let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
                 this._docXfs.push({ dxf: dxf, width: width, height: height });
                 return <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={{ gridRowEnd: `span ${rowSpan}` }} >
                     {this.getDisplayDoc(layoutDoc, d, dxf)}
@@ -142,7 +139,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         });
     }
 
-    _columnStart: number = 0;
     columnDividerDown = (e: React.PointerEvent) => {
         e.stopPropagation();
         e.preventDefault();
@@ -156,7 +152,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         let dragPos = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0];
         let delta = dragPos - this._columnStart;
         this._columnStart = dragPos;
-
         this.props.Document.columnWidth = this.columnWidth + delta;
     }
 
@@ -172,14 +167,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             <FontAwesomeIcon icon={"arrows-alt-h"} />
         </div>;
     }
-    onContextMenu = (e: React.MouseEvent): void => {
-        if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
-            ContextMenu.Instance.addItem({
-                description: "Toggle multi-column",
-                event: () => this.props.Document.singleColumn = !BoolCast(this.props.Document.singleColumn, true), icon: "file-pdf"
-            });
-        }
-    }
 
     @undoBatch
     @action
@@ -247,7 +234,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     position: "relative",
                     gridGap: this.gridGap,
                     gridTemplateColumns: this.singleColumn ? undefined : templatecols,
-                    gridAutoRows: this.singleColumn ? undefined : `${this._gridSize}px`
+                    gridAutoRows: this.singleColumn ? undefined : "0px"
                 }}
             >
                 {this.children(docList)}
@@ -255,21 +242,14 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             </div></div>;
     }
     render() {
-        let sectionFilter = StrCast(this.props.Document.sectionFilter);
-        let fields = new Map<object, Doc[]>();
-        sectionFilter && this.filteredChildren.map(d => {
-            let sectionValue = (d[sectionFilter] ? d[sectionFilter] : "-undefined-") as object;
-            if (!fields.has(sectionValue)) fields.set(sectionValue, [d]);
-            else fields.get(sectionValue)!.push(d);
-        });
         return (
             <div className="collectionStackingView"
-                ref={this.createRef} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onWheel={(e: React.WheelEvent) => e.stopPropagation()} >
+                ref={this.createRef} onDrop={this.onDrop.bind(this)} onWheel={(e: React.WheelEvent) => e.stopPropagation()} >
                 {/* {sectionFilter as boolean ? [
                     ["width > height", this.filteredChildren.filter(f => f[WidthSym]() >= 1 + f[HeightSym]())],
                     ["width = height", this.filteredChildren.filter(f => Math.abs(f[WidthSym]() - f[HeightSym]()) < 1)],
                     ["height > width", this.filteredChildren.filter(f => f[WidthSym]() + 1 <= f[HeightSym]())]]. */}
-                {sectionFilter ? Array.from(fields.entries()).
+                {this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).
                     map(section => this.section(section[0].toString(), section[1] as Doc[])) :
                     this.section("", this.filteredChildren)}
             </div>
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 7e9d35d3d..045c8531e 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,5 +1,5 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faProjectDiagram, faSignature, faSquare, faTh, faImage, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
+import { faProjectDiagram, faSignature, faColumns, faSquare, faTh, faImage, faThList, faTree, faEllipsisV } from '@fortawesome/free-solid-svg-icons';
 import { observer } from "mobx-react";
 import * as React from 'react';
 import { Doc, DocListCast, WidthSym, HeightSym } from '../../../new_fields/Doc';
@@ -25,6 +25,8 @@ library.add(faSquare);
 library.add(faProjectDiagram);
 library.add(faSignature);
 library.add(faThList);
+library.add(faColumns);
+library.add(faEllipsisV);
 library.add(faImage);
 
 @observer
@@ -37,7 +39,8 @@ export class CollectionView extends React.Component<FieldViewProps> {
             case CollectionViewType.Schema: return (<CollectionSchemaView {...props} CollectionView={this} />);
             case CollectionViewType.Docking: return (<CollectionDockingView {...props} CollectionView={this} />);
             case CollectionViewType.Tree: return (<CollectionTreeView {...props} CollectionView={this} />);
-            case CollectionViewType.Stacking: return (<CollectionStackingView {...props} CollectionView={this} />);
+            case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView {...props} CollectionView={this} />); }
+            case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView {...props} CollectionView={this} />); }
             case CollectionViewType.Freeform:
             default:
                 return (<CollectionFreeFormView {...props} CollectionView={this} />);
@@ -57,7 +60,8 @@ export class CollectionView extends React.Component<FieldViewProps> {
             }
             subItems.push({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema), icon: "th-list" });
             subItems.push({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" });
-            subItems.push({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "th-list" });
+            subItems.push({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "ellipsis-v" });
+            subItems.push({ description: "Masonry", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Masonry), icon: "columns" });
             ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems });
             ContextMenu.Instance.addItem({
                 description: "Apply Template", event: undoBatch(() => {
-- 
cgit v1.2.3-70-g09d2


From 6f8f9bd2eaba36dc64e7335dee53a372542bad46 Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Mon, 22 Jul 2019 17:17:09 -0400
Subject: basic stuff working, column headers don tmatch add group button

---
 src/client/views/MetadataEntryMenu.tsx             |   1 +
 .../views/collections/CollectionStackingView.scss  |  55 ++++--
 .../views/collections/CollectionStackingView.tsx   | 113 +++++-------
 .../CollectionStackingViewFieldColumn.tsx          | 202 +++++++++++++++++++++
 src/client/views/nodes/DocumentView.tsx            |   3 -
 5 files changed, 286 insertions(+), 88 deletions(-)
 create mode 100644 src/client/views/collections/CollectionStackingViewFieldColumn.tsx

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

diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index bd5a307b3..7fce3dd0c 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -74,6 +74,7 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
         this.userModified = e.target.value.trim() !== "";
     }
 
+    @action
     onValueKeyDown = async (e: React.KeyboardEvent) => {
         if (e.key === "Enter") {
             const script = KeyValueBox.CompileKVPScript(this._currentValue);
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 7ebf5f77c..3e389225a 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -1,9 +1,12 @@
 @import "../globalCssVariables";
+
 .collectionStackingView {
     height: 100%;
     width: 100%;
     position: absolute;
+    display: flex;
     overflow-y: auto;
+
     .collectionStackingView-docView-container {
         width: 45%;
         margin: 5% 2.5%;
@@ -18,21 +21,23 @@
         align-items: center;
     }
 
-    .collectionStackingView-masonrySingle, .collectionStackingView-masonryGrid {
-        width:100%;
-        height:100%;
+    .collectionStackingView-masonrySingle,
+    .collectionStackingView-masonryGrid {
+        width: 100%;
+        height: 100%;
         position: absolute;
-        display:grid;
+        display: grid;
         top: 0;
         left: 0;
         width: 100%;
         position: absolute;
     }
+
     .collectionStackingView-masonrySingle {
-        width:100%;
-        height:100%;
+        width: 100%;
+        height: 100%;
         position: absolute;
-        display:flex;
+        display: flex;
         flex-direction: column;
         top: 0;
         left: 0;
@@ -52,34 +57,50 @@
     }
 
     .collectionStackingView-columnDragger {
-        width: 15; 
-        height: 15; 
+        width: 15;
+        height: 15;
         position: absolute;
         margin-left: -5;
     }
 
-    .collectionStackingView-columnDoc{
+    .collectionStackingView-columnDoc {
         display: inline-block;
     }
 
-    .collectionStackingView-columnDoc, 
+    .collectionStackingView-columnDoc,
     .collectionStackingView-masonryDoc {
         margin-left: auto;
         margin-right: auto;
     }
-    
+
     .collectionStackingView-masonryDoc {
         transform-origin: top left;
         grid-column-end: span 1;
         height: 100%;
     }
-    .collectionStackingView-sectionHeader {    
-        width: 90%;
+
+    .collectionStackingView-sectionHeader {
         background: gray;
         text-align: center;
-        margin-left: 5%;
-        margin-right: 5%;
-        color: white;
+        margin-left: 10px;
+        margin-right: 10px;
         margin-top: 10px;
+        color: $light-color;
+        text-transform: uppercase;
+        letter-spacing: 2px;
+        padding: 10px;
+
+        .editableView-input {
+            color: black;
+        }
+    }
+
+    .collectionStackingView-addDocumentButton,
+    .collectionStackingView-addGroupButton {
+        display: inline-block;
+        margin: 0 10px;
+        overflow: hidden;
+        width: 90%;
+        color: lightgrey;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 0e5f9a321..0ddd5528b 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -14,6 +14,7 @@ import { DragManager } from "../../util/DragManager";
 import { DocumentType } from "../../documents/Documents";
 import { Transform } from "../../util/Transform";
 import { CursorProperty } from "csstype";
+import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
 
 @observer
 export class CollectionStackingView extends CollectionSubView(doc => doc) {
@@ -35,8 +36,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         let fields = new Map<object, Doc[]>();
         sectionFilter && this.filteredChildren.map(d => {
             let sectionValue = (d[sectionFilter] ? d[sectionFilter] : "-undefined-") as object;
-            if (!fields.has(sectionValue)) fields.set(sectionValue, [d]);
-            else fields.get(sectionValue)!.push(d);
+            let parsed = parseInt(sectionValue.toString());
+            let castedSectionValue: any = sectionValue;
+            if (!isNaN(parsed)) {
+                castedSectionValue = parsed;
+            }
+            if (!fields.has(castedSectionValue)) fields.set(castedSectionValue, [d]);
+            else fields.get(castedSectionValue)!.push(d);
         });
         return fields;
     }
@@ -66,7 +72,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
 
     getDisplayDoc(layoutDoc: Doc, d: Doc, dxf: () => Transform) {
         let resolvedDataDoc = !this.props.Document.isTemplate && this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined;
-        let width = () => d.nativeWidth ? Math.min(layoutDoc[WidthSym](), this.columnWidth) : this.columnWidth;
+        let headings = Array.from(this.Sections.keys());
+        let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
+        let width = () => (d.nativeWidth ? Math.min(layoutDoc[WidthSym](), this.columnWidth) : this.columnWidth) / (uniqueHeadings.length + 1);
         let height = () => this.getDocHeight(layoutDoc);
         let finalDxf = () => dxf().scale(this.columnWidth / layoutDoc[WidthSym]());
         return <CollectionSchemaPreview
@@ -96,49 +104,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return (nw && nh) ? wid * aspect : d[HeightSym]();
     }
 
-    offsetTransform(doc: Doc, translateX: number, translateY: number) {
-        let outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
-        let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
-        return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]).scale(NumCast(doc.width, 1) / this.columnWidth);
-    }
-    getDocTransform(doc: Doc, dref: HTMLDivElement) {
-        let { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
-        return this.offsetTransform(doc, translateX, translateY);
-    }
-
-    getSingleDocTransform(doc: Doc, ind: number, width: number) {
-        let localY = this.filteredChildren.reduce((height, d, i) =>
-            height + (i < ind ? this.getDocHeight(Doc.expandTemplateLayout(d, this.props.DataDoc)) + this.gridGap : 0), this.yMargin);
-        let translate = this.props.ScreenToLocalTransform().inverse().transformPoint((this.props.PanelWidth() - width) / 2, localY);
-        return this.offsetTransform(doc, translate[0], translate[1]);
-    }
-
-    children(docs: Doc[]) {
-        this._docXfs.length = 0;
-        return docs.map((d, i) => {
-            let layoutDoc = Doc.expandTemplateLayout(d, this.props.DataDoc);
-            let width = () => d.nativeWidth ? Math.min(layoutDoc[WidthSym](), this.columnWidth) : this.columnWidth;
-            let height = () => this.getDocHeight(layoutDoc);
-            if (this.singleColumn) {
-                //have to add the height of all previous single column sections or the doc decorations will be in the wrong place.
-                let dxf = () => this.getSingleDocTransform(layoutDoc, i, width());
-                let rowHgtPcnt = height();
-                this._docXfs.push({ dxf: dxf, width: width, height: height });
-                return <div className="collectionStackingView-columnDoc" key={d[Id]} style={{ width: width(), marginTop: i === 0 ? 0 : this.gridGap, height: `${rowHgtPcnt}` }} >
-                    {this.getDisplayDoc(layoutDoc, d, dxf)}
-                </div>;
-            } else {
-                let dref = React.createRef<HTMLDivElement>();
-                let dxf = () => this.getDocTransform(layoutDoc, dref.current!);
-                let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
-                this._docXfs.push({ dxf: dxf, width: width, height: height });
-                return <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={{ gridRowEnd: `span ${rowSpan}` }} >
-                    {this.getDisplayDoc(layoutDoc, d, dxf)}
-                </div>;
-            }
-        });
-    }
-
     columnDividerDown = (e: React.PointerEvent) => {
         e.stopPropagation();
         e.preventDefault();
@@ -218,30 +183,37 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             }
         });
     }
-    section(heading: string, docList: Doc[]) {
-        let cols = this.singleColumn ? 1 : Math.max(1, Math.min(this.filteredChildren.length,
+    section = (heading: string, docList: Doc[]) => {
+        let key = StrCast(this.props.Document.sectionFilter);
+        let types = docList.map(d => typeof d[key]);
+        let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
+        if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
+            type = types[0];
+        }
+        let parsed = parseInt(heading);
+        if (!isNaN(parsed)) {
+            heading = parsed.toString();
+        }
+        let cols = () => this.singleColumn ? 1 : Math.max(1, Math.min(this.filteredChildren.length,
             Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))));
-        let templatecols = "";
-        for (let i = 0; i < cols; i++) templatecols += `${this.columnWidth}px `;
-        return <div key={heading}>
-            {heading ? <div key={`${heading}`} className="collectionStackingView-sectionHeader">{heading}</div> : (null)}
-            <div key={`${heading}-stack`} className={`collectionStackingView-masonry${this.singleColumn ? "Single" : "Grid"}`}
-                style={{
-                    padding: this.singleColumn ? `${this.yMargin}px ${this.xMargin}px ${this.yMargin}px ${this.xMargin}px` : `${this.yMargin}px ${this.xMargin}px`,
-                    margin: "auto",
-                    width: this.singleColumn ? undefined : `${cols * (this.columnWidth + this.gridGap) + 2 * this.xMargin - this.gridGap}px`,
-                    height: 'max-content',
-                    position: "relative",
-                    gridGap: this.gridGap,
-                    gridTemplateColumns: this.singleColumn ? undefined : templatecols,
-                    gridAutoRows: this.singleColumn ? undefined : "0px"
-                }}
-            >
-                {this.children(docList)}
-                {this.singleColumn ? (null) : this.columnDragger}
-            </div></div>;
+        return <CollectionStackingViewFieldColumn
+            cols={cols}
+            headings={() => Array.from(this.Sections.keys())}
+            heading={heading}
+            docList={docList}
+            parent={this}
+            type={type}
+            createDropTarget={this.createDropTarget} />;
+    }
+
+    @action
+    addGroup = () => {
+
     }
+
     render() {
+        let headings = Array.from(this.Sections.keys());
+        let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
         return (
             <div className="collectionStackingView"
                 ref={this.createRef} onDrop={this.onDrop.bind(this)} onWheel={(e: React.WheelEvent) => e.stopPropagation()} >
@@ -249,9 +221,14 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     ["width > height", this.filteredChildren.filter(f => f[WidthSym]() >= 1 + f[HeightSym]())],
                     ["width = height", this.filteredChildren.filter(f => Math.abs(f[WidthSym]() - f[HeightSym]()) < 1)],
                     ["height > width", this.filteredChildren.filter(f => f[WidthSym]() + 1 <= f[HeightSym]())]]. */}
-                {this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).
+                {this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).sort((a, b) => a[0].toString() > b[0].toString() ? 1 : -1).
                     map(section => this.section(section[0].toString(), section[1] as Doc[])) :
                     this.section("", this.filteredChildren)}
+                {this.props.Document.sectionFilter ?
+                    <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
+                        style={{ width: this.columnWidth / (uniqueHeadings.length + 1), marginTop: 10 }}>
+                        <button style={{ width: "100%" }} onClick={this.addGroup}>+ Add a Group</button>
+                    </div> : null}
             </div>
         );
     }
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
new file mode 100644
index 000000000..9f64a4e93
--- /dev/null
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -0,0 +1,202 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import { number } from "prop-types";
+import { Doc, WidthSym } from "../../../new_fields/Doc";
+import { CollectionStackingView } from "./CollectionStackingView";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { Utils } from "../../../Utils";
+import { NumCast, StrCast } from "../../../new_fields/Types";
+import { EditableView } from "../EditableView";
+import { action, observable } from "mobx";
+import { undoBatch } from "../../util/UndoManager";
+import { DragManager } from "../../util/DragManager";
+import { DocumentManager } from "../../util/DocumentManager";
+import { SelectionManager } from "../../util/SelectionManager";
+import "./CollectionStackingView.scss";
+import { Docs } from "../../documents/Documents";
+
+
+interface CSVFieldColumnProps {
+    cols: () => number;
+    headings: () => object[];
+    heading: string;
+    docList: Doc[];
+    parent: CollectionStackingView;
+    type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined;
+    createDropTarget: (ele: HTMLDivElement) => void;
+}
+
+@observer
+export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldColumnProps> {
+    @observable private _background = "white";
+
+    private _dropRef: HTMLDivElement | null = null;
+    private dropDisposer?: DragManager.DragDropDisposer;
+
+    createColumnDropRef = (ele: HTMLDivElement | null) => {
+        this._dropRef = ele;
+        this.dropDisposer && this.dropDisposer();
+        if (ele) {
+            this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.columnDrop.bind(this) } });
+        }
+    }
+
+    @undoBatch
+    @action
+    columnDrop = (e: Event, de: DragManager.DropEvent) => {
+        if (de.data instanceof DragManager.DocumentDragData) {
+            let key = StrCast(this.props.parent.props.Document.sectionFilter);
+            let castedValue = this.getValue(this.props.heading);
+            if (castedValue) {
+                de.data.droppedDocuments.forEach(d => d[key] = castedValue);
+            }
+            this.props.parent.drop(e, de);
+            e.stopPropagation();
+        }
+    }
+
+    children(docs: Doc[]) {
+        let style = this.props.parent;
+        this.props.parent._docXfs.length = 0;
+        return docs.map((d, i) => {
+            let layoutDoc = Doc.expandTemplateLayout(d, this.props.parent.props.DataDoc);
+            let headings = this.props.headings();
+            let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
+            let width = () => (d.nativeWidth ? Math.min(layoutDoc[WidthSym](), style.columnWidth) : style.columnWidth) / (uniqueHeadings.length + 1);
+            let height = () => this.props.parent.getDocHeight(layoutDoc);
+            if (style.singleColumn) {
+                let dxf;
+                let dref = React.createRef<HTMLDivElement>();
+                if (uniqueHeadings.length > 0) {
+                    dxf = () => this.getDocTransform(layoutDoc, dref.current!);
+                    this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height });
+                }
+                else {
+                    //have to add the height of all previous single column sections or the doc decorations will be in the wrong place.
+                    dxf = () => this.getSingleDocTransform(layoutDoc, i, width());
+                    this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height });
+                }
+                let rowHgtPcnt = height();
+                return <div className="collectionStackingView-columnDoc" key={d[Id]} ref={dref} style={{ width: width(), marginTop: i === 0 ? 0 : style.gridGap, height: `${rowHgtPcnt}` }} >
+                    {this.props.parent.getDisplayDoc(layoutDoc, d, dxf)}
+                </div>;
+            } else {
+                let dref = React.createRef<HTMLDivElement>();
+                let dxf = () => this.getDocTransform(layoutDoc, dref.current!);
+                let rowSpan = Math.ceil((height() + style.gridGap) / style.gridGap);
+                this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height });
+                return <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={{ gridRowEnd: `span ${rowSpan}` }} >
+                    {this.props.parent.getDisplayDoc(layoutDoc, d, dxf)}
+                </div>;
+            }
+        });
+    }
+
+    getSingleDocTransform(doc: Doc, ind: number, width: number) {
+        let localY = this.props.parent.filteredChildren.reduce((height, d, i) =>
+            height + (i < ind ? this.props.parent.getDocHeight(Doc.expandTemplateLayout(d, this.props.parent.props.DataDoc)) + this.props.parent.gridGap : 0), this.props.parent.yMargin);
+        let translate = this.props.parent.props.ScreenToLocalTransform().inverse().transformPoint((this.props.parent.props.PanelWidth() - width) / 2, localY);
+        return this.offsetTransform(doc, translate[0], translate[1]);
+    }
+
+    offsetTransform(doc: Doc, translateX: number, translateY: number) {
+        let outerXf = Utils.GetScreenTransform(this.props.parent._masonryGridRef!);
+        let offset = this.props.parent.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
+        return this.props.parent.props.ScreenToLocalTransform().translate(offset[0], offset[1]).scale(NumCast(doc.width, 1) / this.props.parent.columnWidth);
+    }
+
+    getDocTransform(doc: Doc, dref: HTMLDivElement) {
+        let { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
+        return this.offsetTransform(doc, translateX, translateY);
+    }
+
+    getValue = (value: string): any => {
+        let parsed = parseInt(value);
+        if (!isNaN(parsed)) {
+            return parsed;
+        }
+        if (value.toLowerCase().indexOf("true") > -1) {
+            return true;
+        }
+        if (value.toLowerCase().indexOf("false") > -1) {
+            return false;
+        }
+        return value;
+    }
+
+    headingChanged = (value: string, shiftDown?: boolean) => {
+        let key = StrCast(this.props.parent.props.Document.sectionFilter);
+        let castedValue = this.getValue(value);
+        if (castedValue) {
+            this.props.docList.forEach(d => d[key] = castedValue);
+            return true;
+        }
+        return false;
+    }
+
+    @action
+    pointerEntered = () => {
+        if (SelectionManager.GetIsDragging()) {
+            this._background = "#b4b4b4";
+        }
+    }
+
+    @action
+    pointerLeave = () => {
+        this._background = "white";
+    }
+
+    @action
+    addDocument = () => {
+        let key = StrCast(this.props.parent.props.Document.sectionFilter);
+        let newDoc = Docs.Create.TextDocument({ height: 18, title: "new text document" });
+        newDoc[key] = this.getValue(this.props.heading);
+        this.props.parent.props.addDocument(newDoc);
+    }
+
+    render() {
+        let cols = this.props.cols();
+        let templatecols = "";
+        let headings = this.props.headings();
+        let heading = this.props.heading;
+        let style = this.props.parent;
+        let singleColumn = style.singleColumn;
+        let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
+        let editableViewProps = {
+            GetValue: () => heading,
+            SetValue: this.headingChanged,
+            contents: heading,
+        }
+        let headingView = heading ?
+            <div key={heading} className="collectionStackingView-sectionHeader"
+                style={{ width: (style.columnWidth) / (uniqueHeadings.length + 1) }}>
+                <EditableView {...editableViewProps} />
+            </div> : (null);
+        for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth}px `;
+        return (
+            <div key={heading} style={{ width: `${100 / (uniqueHeadings.length + 1)}%`, background: this._background }}
+                ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
+                {headingView}
+                <div key={`${heading}-stack`} className={`collectionStackingView-masonry${singleColumn ? "Single" : "Grid"}`}
+                    style={{
+                        padding: singleColumn ? `${style.yMargin}px ${0}px ${style.yMargin}px ${0}px` : `${style.yMargin}px ${0}px`,
+                        margin: "auto",
+                        width: singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
+                        height: 'max-content',
+                        position: "relative",
+                        gridGap: style.gridGap,
+                        gridTemplateColumns: singleColumn ? undefined : templatecols,
+                        gridAutoRows: singleColumn ? undefined : "0px"
+                    }}
+                >
+                    {this.children(this.props.docList)}
+                    {singleColumn ? (null) : this.props.parent.columnDragger}
+                </div>
+                <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton"
+                    style={{ width: style.columnWidth / (uniqueHeadings.length + 1) }}>
+                    <button style={{ width: "100%" }} onClick={this.addDocument}>+ New</button>
+                </div>
+            </div>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f4052c2c3..0d766cded 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -565,10 +565,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (this.props.Document.detailedLayout && !this.props.Document.isTemplate) {
             cm.addItem({ description: "Toggle detail", event: () => Doc.ToggleDetailLayout(this.props.Document), icon: "image" });
         }
-<<<<<<< HEAD
-=======
         cm.addItem({ description: "Add Repl", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
->>>>>>> fc1dbb1327d10bd1832d33a87d18cff1e836ecfb
         cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
         cm.addItem({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
         cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
-- 
cgit v1.2.3-70-g09d2


From de9b03ca6c7116949952bc428e8d9257959ad974 Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Tue, 23 Jul 2019 13:58:45 -0400
Subject: implemented most of the board functionality

---
 .../views/collections/CollectionStackingView.scss  |  99 +++++++++++++++---
 .../views/collections/CollectionStackingView.tsx   | 115 ++++++++++++++-------
 .../CollectionStackingViewFieldColumn.tsx          |  64 ++++++++++--
 src/new_fields/SchemaHeaderField.ts                |  46 +++++++++
 4 files changed, 266 insertions(+), 58 deletions(-)
 create mode 100644 src/new_fields/SchemaHeaderField.ts

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

diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 3e389225a..e0ced8af4 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -6,6 +6,7 @@
     position: absolute;
     display: flex;
     overflow-y: auto;
+    flex-wrap: wrap;
 
     .collectionStackingView-docView-container {
         width: 45%;
@@ -67,12 +68,6 @@
         display: inline-block;
     }
 
-    .collectionStackingView-columnDoc,
-    .collectionStackingView-masonryDoc {
-        margin-left: auto;
-        margin-right: auto;
-    }
-
     .collectionStackingView-masonryDoc {
         transform-origin: top left;
         grid-column-end: span 1;
@@ -80,27 +75,103 @@
     }
 
     .collectionStackingView-sectionHeader {
-        background: gray;
         text-align: center;
-        margin-left: 10px;
-        margin-right: 10px;
+        margin-left: 5px;
+        margin-right: 5px;
         margin-top: 10px;
-        color: $light-color;
-        text-transform: uppercase;
-        letter-spacing: 2px;
-        padding: 10px;
+        overflow: hidden;
 
         .editableView-input {
             color: black;
         }
+
+        .editableView-input:hover,
+        .editableView-container-editing:hover,
+        .editableView-container-editing-oneLine:hover {
+            cursor: text
+        }
+
+        .collectionStackingView-sectionHeader-subCont {
+            outline: none;
+            border: 0px;
+            color: $light-color;
+            text-transform: uppercase;
+            letter-spacing: 2px;
+            font-size: 75%;
+            transition: transform 0.2s;
+            position: relative;
+
+            .editableView-container-editing-oneLine,
+            .editableView-container-editing {
+                color: grey;
+                padding: 10px;
+            }
+
+            .editableView-input:hover,
+            .editableView-container-editing:hover,
+            .editableView-container-editing-oneLine:hover {
+                cursor: text
+            }
+
+            .editableView-input {
+                padding: 12px 10px 11px 10px;
+                border: 0px;
+                color: grey;
+                // font-size: 75%;
+                text-align: center;
+                text-transform: uppercase;
+                letter-spacing: 2px;
+                outline-color: black;
+            }
+        }
+
+        .collectionStackingView-sectionDelete {
+            position: absolute;
+            right: 0;
+            top: 0;
+            height: 100%;
+        }
     }
 
     .collectionStackingView-addDocumentButton,
     .collectionStackingView-addGroupButton {
         display: inline-block;
-        margin: 0 10px;
+        margin: 0 5px;
         overflow: hidden;
         width: 90%;
         color: lightgrey;
+        overflow: ellipses;
+    }
+
+    .collectionStackingView-addGroupButton {
+        background: rgb(238, 238, 238);
+        font-size: 75%;
+        text-align: center;
+        text-transform: uppercase;
+        letter-spacing: 2px;
+        height: fit-content;
+
+        .editableView-container-editing-oneLine,
+        .editableView-container-editing {
+            color: grey;
+            padding: 10px;
+        }
+
+        .editableView-input:hover,
+        .editableView-container-editing:hover,
+        .editableView-container-editing-oneLine:hover {
+            cursor: text
+        }
+
+        .editableView-input {
+            padding: 12px 10px 11px 10px;
+            border: 0px;
+            color: grey;
+            // font-size: 75%;
+            text-align: center;
+            text-transform: uppercase;
+            letter-spacing: 2px;
+            outline-color: black;
+        }
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 0ddd5528b..e4e6f92e3 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -2,9 +2,9 @@ import React = require("react");
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, IReactionDisposer, reaction, untracked, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
+import { Doc, HeightSym, WidthSym, DocListCast } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
-import { BoolCast, NumCast, Cast, StrCast } from "../../../new_fields/Types";
+import { BoolCast, NumCast, Cast, StrCast, FieldValue } from "../../../new_fields/Types";
 import { emptyFunction, Utils } from "../../../Utils";
 import { CollectionSchemaPreview } from "./CollectionSchemaView";
 import "./CollectionStackingView.scss";
@@ -15,46 +15,80 @@ import { DocumentType } from "../../documents/Documents";
 import { Transform } from "../../util/Transform";
 import { CursorProperty } from "csstype";
 import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
+import { listSpec } from "../../../new_fields/Schema";
+import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { List } from "../../../new_fields/List";
+import { EditableView } from "../EditableView";
+
+let valuesCreated = 1;
 
 @observer
 export class CollectionStackingView extends CollectionSubView(doc => doc) {
     _masonryGridRef: HTMLDivElement | null = null;
     _draggerRef = React.createRef<HTMLDivElement>();
     _heightDisposer?: IReactionDisposer;
+    _sectionFilterDisposer?: IReactionDisposer;
     _docXfs: any[] = [];
     _columnStart: number = 0;
     @observable private cursor: CursorProperty = "grab";
+    get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
     @computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); }
     @computed get yMargin() { return NumCast(this.props.Document.yMargin, 2 * this.gridGap); }
     @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); }
     @computed get singleColumn() { return BoolCast(this.props.Document.singleColumn, true); }
     @computed get columnWidth() { return this.singleColumn ? (this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin) : Math.min(this.props.PanelWidth() - 2 * this.xMargin, NumCast(this.props.Document.columnWidth, 250)); }
     @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized); }
-
-    @computed get Sections() {
+    get Sections() {
         let sectionFilter = StrCast(this.props.Document.sectionFilter);
-        let fields = new Map<object, Doc[]>();
-        sectionFilter && this.filteredChildren.map(d => {
-            let sectionValue = (d[sectionFilter] ? d[sectionFilter] : "-undefined-") as object;
-            let parsed = parseInt(sectionValue.toString());
-            let castedSectionValue: any = sectionValue;
-            if (!isNaN(parsed)) {
-                castedSectionValue = parsed;
-            }
-            if (!fields.has(castedSectionValue)) fields.set(castedSectionValue, [d]);
-            else fields.get(castedSectionValue)!.push(d);
-        });
+        let sectionHeaders = this.sectionHeaders;
+        if (!sectionHeaders) {
+            this.props.Document.sectionHeaders = sectionHeaders = new List();
+        }
+        let fields = new Map<SchemaHeaderField, Doc[]>(sectionHeaders.map(sh => [sh, []]));
+        if (sectionFilter) {
+            this.filteredChildren.map(d => {
+                let sectionValue = (d[sectionFilter] ? d[sectionFilter] : `No ${sectionFilter} value`) as object;
+                // the next five lines ensures that floating point rounding errors don't create more than one section -syip
+                let parsed = parseInt(sectionValue.toString());
+                let castedSectionValue: any = sectionValue;
+                if (!isNaN(parsed)) {
+                    castedSectionValue = parsed;
+                }
+
+                // look for if header exists already
+                let existingHeader = sectionHeaders!.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `No ${sectionFilter} value`));
+                if (existingHeader) {
+                    fields.get(existingHeader)!.push(d);
+                }
+                else {
+                    let newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `No ${sectionFilter} value`);
+                    fields.set(newSchemaHeader, [d]);
+                    sectionHeaders!.push(newSchemaHeader);
+                }
+            });
+        }
         return fields;
     }
     componentDidMount() {
-        this._heightDisposer = reaction(() => [this.yMargin, this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])],
-            () => this.singleColumn &&
-                (this.props.Document.height = this.Sections.size * 50 + this.filteredChildren.reduce((height, d, i) =>
-                    height + this.getDocHeight(d) + (i === this.filteredChildren.length - 1 ? this.yMargin : this.gridGap), this.yMargin))
-            , { fireImmediately: true });
+        // is there any reason this needs to exist? -syip
+        // this._heightDisposer = reaction(() => [this.yMargin, this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])],
+        //     () => this.singleColumn &&
+        //         (this.props.Document.height = this.Sections.size * 50 + this.filteredChildren.reduce((height, d, i) =>
+        //             height + this.getDocHeight(d) + (i === this.filteredChildren.length - 1 ? this.yMargin : this.gridGap), this.yMargin))
+        //     , { fireImmediately: true });
+
+        // reset section headers when a new filter is inputted
+        this._sectionFilterDisposer = reaction(
+            () => StrCast(this.props.Document.sectionFilter),
+            () => {
+                this.props.Document.sectionHeaders = new List();
+                valuesCreated = 1;
+            }
+        )
     }
     componentWillUnmount() {
         this._heightDisposer && this._heightDisposer();
+        this._sectionFilterDisposer && this._sectionFilterDisposer();
     }
 
     @action
@@ -73,8 +107,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     getDisplayDoc(layoutDoc: Doc, d: Doc, dxf: () => Transform) {
         let resolvedDataDoc = !this.props.Document.isTemplate && this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined;
         let headings = Array.from(this.Sections.keys());
-        let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
-        let width = () => (d.nativeWidth ? Math.min(layoutDoc[WidthSym](), this.columnWidth) : this.columnWidth) / (uniqueHeadings.length + 1);
+        // let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
+        let width = () => (d.nativeWidth ? Math.min(layoutDoc[WidthSym](), this.columnWidth) : this.columnWidth) / (headings.length + 1);
         let height = () => this.getDocHeight(layoutDoc);
         let finalDxf = () => dxf().scale(this.columnWidth / layoutDoc[WidthSym]());
         return <CollectionSchemaPreview
@@ -183,23 +217,21 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             }
         });
     }
-    section = (heading: string, docList: Doc[]) => {
+    section = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
         let key = StrCast(this.props.Document.sectionFilter);
-        let types = docList.map(d => typeof d[key]);
         let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
+        let types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]);
         if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
             type = types[0];
         }
-        let parsed = parseInt(heading);
-        if (!isNaN(parsed)) {
-            heading = parsed.toString();
-        }
         let cols = () => this.singleColumn ? 1 : Math.max(1, Math.min(this.filteredChildren.length,
             Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))));
         return <CollectionStackingViewFieldColumn
+            key={heading ? heading.heading : ""}
             cols={cols}
             headings={() => Array.from(this.Sections.keys())}
-            heading={heading}
+            heading={heading ? heading.heading : ""}
+            headingObject={heading}
             docList={docList}
             parent={this}
             type={type}
@@ -207,13 +239,24 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
 
     @action
-    addGroup = () => {
-
+    addGroup = (value: string) => {
+        if (value) {
+            if (this.sectionHeaders) {
+                this.sectionHeaders.push(new SchemaHeaderField(value));
+                return true;
+            }
+        }
+        return false;
     }
 
     render() {
         let headings = Array.from(this.Sections.keys());
-        let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
+        let editableViewProps = {
+            GetValue: () => "",
+            SetValue: this.addGroup,
+            contents: "+ Add a Group"
+        }
+        // let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
         return (
             <div className="collectionStackingView"
                 ref={this.createRef} onDrop={this.onDrop.bind(this)} onWheel={(e: React.WheelEvent) => e.stopPropagation()} >
@@ -222,12 +265,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     ["width = height", this.filteredChildren.filter(f => Math.abs(f[WidthSym]() - f[HeightSym]()) < 1)],
                     ["height > width", this.filteredChildren.filter(f => f[WidthSym]() + 1 <= f[HeightSym]())]]. */}
                 {this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).sort((a, b) => a[0].toString() > b[0].toString() ? 1 : -1).
-                    map(section => this.section(section[0].toString(), section[1] as Doc[])) :
-                    this.section("", this.filteredChildren)}
+                    map(section => this.section(section[0], section[1] as Doc[])) :
+                    this.section(undefined, this.filteredChildren)}
                 {this.props.Document.sectionFilter ?
                     <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
-                        style={{ width: this.columnWidth / (uniqueHeadings.length + 1), marginTop: 10 }}>
-                        <button style={{ width: "100%" }} onClick={this.addGroup}>+ Add a Group</button>
+                        style={{ width: (this.columnWidth / (headings.length + 1)) - 10, marginTop: 10 }}>
+                        <EditableView {...editableViewProps} />
                     </div> : null}
             </div>
         );
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 9f64a4e93..fe24c63c7 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -7,19 +7,22 @@ import { Id } from "../../../new_fields/FieldSymbols";
 import { Utils } from "../../../Utils";
 import { NumCast, StrCast } from "../../../new_fields/Types";
 import { EditableView } from "../EditableView";
-import { action, observable } from "mobx";
+import { action, observable, computed } from "mobx";
 import { undoBatch } from "../../util/UndoManager";
 import { DragManager } from "../../util/DragManager";
 import { DocumentManager } from "../../util/DocumentManager";
 import { SelectionManager } from "../../util/SelectionManager";
 import "./CollectionStackingView.scss";
 import { Docs } from "../../documents/Documents";
+import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 
 
 interface CSVFieldColumnProps {
     cols: () => number;
     headings: () => object[];
     heading: string;
+    headingObject: SchemaHeaderField | undefined;
     docList: Doc[];
     parent: CollectionStackingView;
     type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined;
@@ -33,6 +36,8 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
     private _dropRef: HTMLDivElement | null = null;
     private dropDisposer?: DragManager.DragDropDisposer;
 
+    @observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading;
+
     createColumnDropRef = (ele: HTMLDivElement | null) => {
         this._dropRef = ele;
         this.dropDisposer && this.dropDisposer();
@@ -46,10 +51,13 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
     columnDrop = (e: Event, de: DragManager.DropEvent) => {
         if (de.data instanceof DragManager.DocumentDragData) {
             let key = StrCast(this.props.parent.props.Document.sectionFilter);
-            let castedValue = this.getValue(this.props.heading);
+            let castedValue = this.getValue(this._heading);
             if (castedValue) {
                 de.data.droppedDocuments.forEach(d => d[key] = castedValue);
             }
+            else {
+                de.data.droppedDocuments.forEach(d => d[key] = undefined);
+            }
             this.props.parent.drop(e, de);
             e.stopPropagation();
         }
@@ -124,11 +132,21 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         return value;
     }
 
+    @action
     headingChanged = (value: string, shiftDown?: boolean) => {
         let key = StrCast(this.props.parent.props.Document.sectionFilter);
         let castedValue = this.getValue(value);
         if (castedValue) {
+            if (this.props.parent.sectionHeaders) {
+                if (this.props.parent.sectionHeaders.map(i => i.heading).indexOf(castedValue.toString()) > -1) {
+                    return false;
+                }
+            }
             this.props.docList.forEach(d => d[key] = castedValue);
+            if (this.props.headingObject) {
+                this.props.headingObject.setHeading(castedValue.toString());
+                this._heading = this.props.headingObject.heading;
+            }
             return true;
         }
         return false;
@@ -154,23 +172,53 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         this.props.parent.props.addDocument(newDoc);
     }
 
+    @action
+    deleteColumn = () => {
+        let key = StrCast(this.props.parent.props.Document.sectionFilter);
+        this.props.docList.forEach(d => d[key] = undefined);
+        if (this.props.parent.sectionHeaders && this.props.headingObject) {
+            let index = this.props.parent.sectionHeaders.indexOf(this.props.headingObject);
+            this.props.parent.sectionHeaders.splice(index, 1);
+        }
+    }
+
     render() {
         let cols = this.props.cols();
+        let key = StrCast(this.props.parent.props.Document.sectionFilter);
         let templatecols = "";
         let headings = this.props.headings();
-        let heading = this.props.heading;
+        let heading = this._heading;
         let style = this.props.parent;
         let singleColumn = style.singleColumn;
         let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
+        let evContents = heading ? heading : this.props.type && this.props.type === "number" ? "0" : `No ${key} value`;
         let editableViewProps = {
-            GetValue: () => heading,
+            GetValue: () => evContents,
             SetValue: this.headingChanged,
-            contents: heading,
+            contents: evContents,
+            oneLine: true
         }
-        let headingView = heading ?
+        let headingView = this.props.headingObject ?
             <div key={heading} className="collectionStackingView-sectionHeader"
                 style={{ width: (style.columnWidth) / (uniqueHeadings.length + 1) }}>
-                <EditableView {...editableViewProps} />
+                {/* the default bucket (no key value) has a tooltip that describes what it is.
+                    Further, it does not have a color and cannot be deleted. */}
+                <div className="collectionStackingView-sectionHeader-subCont"
+                    title={evContents === `No ${key} value` ?
+                        `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""}
+                    style={{
+                        width: "100%",
+                        background: this.props.headingObject && evContents !== `No ${key} value` ?
+                            this.props.headingObject.color : "lightgrey",
+                        color: "grey"
+                    }}>
+                    <EditableView {...editableViewProps} />
+                    {evContents === `No ${key} value` ?
+                        (null) :
+                        <button className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}>
+                            <FontAwesomeIcon icon="trash" />
+                        </button>}
+                </div>
             </div> : (null);
         for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth}px `;
         return (
@@ -180,7 +228,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                 <div key={`${heading}-stack`} className={`collectionStackingView-masonry${singleColumn ? "Single" : "Grid"}`}
                     style={{
                         padding: singleColumn ? `${style.yMargin}px ${0}px ${style.yMargin}px ${0}px` : `${style.yMargin}px ${0}px`,
-                        margin: "auto",
+                        margin: "auto 5px",
                         width: singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
                         height: 'max-content',
                         position: "relative",
diff --git a/src/new_fields/SchemaHeaderField.ts b/src/new_fields/SchemaHeaderField.ts
new file mode 100644
index 000000000..284de3023
--- /dev/null
+++ b/src/new_fields/SchemaHeaderField.ts
@@ -0,0 +1,46 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, createSimpleSchema, primitive } from "serializr";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString, OnUpdate } from "./FieldSymbols";
+import { scriptingGlobal, Scripting } from "../client/util/Scripting";
+
+export const PastelSchemaPalette = new Map<string, string>([
+    ["purple", "#f5b5fc"],
+    ["green", "#96F7D2"],
+    ["yellow", "#F0F696"],
+    ["red", "#FCB1B1"]
+])
+
+@scriptingGlobal
+@Deserializable("schemaheader")
+export class SchemaHeaderField extends ObjectField {
+    @serializable(primitive())
+    heading: string;
+    color: string;
+
+    constructor(heading: string = "", color: string = Array.from(PastelSchemaPalette.values())[Math.floor(Math.random() * 4)]) {
+        super();
+
+        this.heading = heading;
+        this.color = color;
+    }
+
+    setHeading(heading: string) {
+        this.heading = heading;
+        this[OnUpdate]();
+    }
+
+    setColor(color: string) {
+        this.color = color;
+        this[OnUpdate]();
+    }
+
+    [Copy]() {
+        return new SchemaHeaderField(this.heading, this.color);
+    }
+
+    [ToScriptString]() {
+        return `invalid`;
+    }
+}
+
-- 
cgit v1.2.3-70-g09d2

