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.

---
 src/client/views/collections/CollectionBaseView.tsx | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

(limited to 'src/client/views/collections/CollectionBaseView.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)}
-- 
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/CollectionBaseView.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 3b740e1c1ac74e7aac45c136ec9df1b37e2dbc16 Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Tue, 23 Jul 2019 16:32:43 -0400
Subject: pretty ui for changing section fitler and added base for colletion
 chromes

---
 src/client/views/EditableView.scss                 |  10 +-
 src/client/views/EditableView.tsx                  |  30 +++-
 .../views/collections/CollectionBaseView.tsx       |   2 +-
 src/client/views/collections/CollectionView.tsx    |  34 ++++-
 .../views/collections/CollectionViewChromes.scss   |  54 ++++++++
 .../views/collections/CollectionViewChromes.tsx    | 152 +++++++++++++++++++++
 6 files changed, 268 insertions(+), 14 deletions(-)
 create mode 100644 src/client/views/collections/CollectionViewChromes.scss
 create mode 100644 src/client/views/collections/CollectionViewChromes.tsx

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

diff --git a/src/client/views/EditableView.scss b/src/client/views/EditableView.scss
index a5150cd66..19512362e 100644
--- a/src/client/views/EditableView.scss
+++ b/src/client/views/EditableView.scss
@@ -1,20 +1,24 @@
-.editableView-container-editing, .editableView-container-editing-oneLine {
+.editableView-container-editing,
+.editableView-container-editing-oneLine {
     overflow-wrap: break-word;
     word-wrap: break-word;
     hyphens: auto;
     overflow: hidden;
 }
+
 .editableView-container-editing-oneLine {
     span {
         white-space: nowrap;
         overflow: hidden;
         text-overflow: ellipsis;
-        display:block;
+        display: block;
     }
+
     input {
-        display:block;
+        display: block;
     }
 }
+
 .editableView-input {
     width: 100%;
     background: inherit;
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index f2cdffd38..a5bb40243 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -2,6 +2,7 @@ import React = require('react');
 import { observer } from 'mobx-react';
 import { observable, action, trace } from 'mobx';
 import "./EditableView.scss";
+import * as Autosuggest from 'react-autosuggest';
 
 export interface EditableProps {
     /**
@@ -28,6 +29,13 @@ export interface EditableProps {
     fontSize?: number;
     height?: number;
     display?: string;
+    autosuggestProps?: {
+        resetValue: () => void;
+        value: string,
+        onChange: (e: React.ChangeEvent, { newValue }: { newValue: string }) => void,
+        autosuggestProps: Autosuggest.AutosuggestProps<string>
+
+    };
     oneLine?: boolean;
     editing?: boolean;
     onClick?: (e: React.MouseEvent) => boolean;
@@ -85,10 +93,26 @@ export class EditableView extends React.Component<EditableProps> {
 
     render() {
         if (this._editing) {
-            return <input className="editableView-input" defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus
-                onBlur={action(() => this._editing = false)} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
-                style={{ display: this.props.display, fontSize: this.props.fontSize }} />;
+            return this.props.autosuggestProps
+                ? <Autosuggest
+                    {...this.props.autosuggestProps.autosuggestProps}
+                    inputProps={{
+                        className: "editableView-input",
+                        onKeyDown: this.onKeyDown,
+                        autoFocus: true,
+                        onBlur: action(() => this._editing = false),
+                        onPointerDown: this.stopPropagation,
+                        onClick: this.stopPropagation,
+                        onPointerUp: this.stopPropagation,
+                        value: this.props.autosuggestProps.value,
+                        onChange: this.props.autosuggestProps.onChange
+                    }}
+                />
+                : <input className="editableView-input" defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus
+                    onBlur={action(() => this._editing = false)} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
+                    style={{ display: this.props.display, fontSize: this.props.fontSize }} />;
         } else {
+            if (this.props.autosuggestProps) this.props.autosuggestProps.resetValue();
             return (
                 <div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`}
                     style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 72faf52c4..4cdbd8554 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -32,7 +32,7 @@ export interface CollectionRenderProps {
 
 export interface CollectionViewProps extends FieldViewProps {
     onContextMenu?: (e: React.MouseEvent) => void;
-    children: (type: CollectionViewType, props: CollectionRenderProps) => JSX.Element | JSX.Element[] | null;
+    children: (type: CollectionViewType, props: CollectionRenderProps) => JSX.Element | JSX.Element[] | null | (JSX.Element | null)[];
     className?: string;
     contentRef?: React.Ref<HTMLDivElement>;
 }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 045c8531e..ba9780c24 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -17,6 +17,7 @@ import { CollectionStackingView } from './CollectionStackingView';
 import { CollectionTreeView } from "./CollectionTreeView";
 import { StrCast, PromiseValue } from '../../../new_fields/Types';
 import { DocumentType } from '../../documents/Documents';
+import { CollectionStackingViewChrome } from './CollectionViewChromes';
 export const COLLECTION_BORDER_WIDTH = 2;
 
 library.add(faTh);
@@ -33,21 +34,40 @@ library.add(faImage);
 export class CollectionView extends React.Component<FieldViewProps> {
     public static LayoutString(fieldStr: string = "data", fieldExt: string = "") { return FieldView.LayoutString(CollectionView, fieldStr, fieldExt); }
 
-    private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
+    private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
         let props = { ...this.props, ...renderProps };
         switch (this.isAnnotationOverlay ? CollectionViewType.Freeform : type) {
-            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: { 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.Schema: return (<CollectionSchemaView key="collview" {...props} CollectionView={this} />);
+            case CollectionViewType.Docking: return (<CollectionDockingView key="collview" {...props} CollectionView={this} />);
+            case CollectionViewType.Tree: return (<CollectionTreeView key="collview" {...props} CollectionView={this} />);
+            case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} CollectionView={this} />); }
+            case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} CollectionView={this} />); }
             case CollectionViewType.Freeform:
             default:
-                return (<CollectionFreeFormView {...props} CollectionView={this} />);
+                return (<CollectionFreeFormView key="collview" {...props} CollectionView={this} />);
         }
         return (null);
     }
 
+    private Chrome = (type: CollectionViewType) => {
+        if (this.isAnnotationOverlay || this.props.Document === CurrentUserUtils.UserDocument.sidebar) {
+            return (null);
+        }
+
+        switch (type) {
+            case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this} />);
+            default:
+                return null;
+        }
+    }
+
+    private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
+        return [
+            this.Chrome(type),
+            this.SubViewHelper(type, renderProps)
+        ]
+    }
+
     get isAnnotationOverlay() { return this.props.fieldExt ? true : false; }
 
     static _applyCount: number = 0;
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
new file mode 100644
index 000000000..e5cb1b546
--- /dev/null
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -0,0 +1,54 @@
+@import "../globalCssVariables";
+
+.collectionStackingViewChrome {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    padding-bottom: 10px;
+    border-bottom: .5px solid lightgrey;
+    margin: 10px;
+
+    .collectionStackingViewChrome-sectionFilter-cont {
+        justify-self: right;
+        display: flex;
+        font-size: 75%;
+        text-transform: uppercase;
+        letter-spacing: 2px;
+
+        .collectionStackingViewChrome-sectionFilter-label {
+            vertical-align: center;
+            padding: 10px;
+        }
+
+        .collectionStackingViewChrome-sectionFilter {
+            color: white;
+            width: fit-content;
+            text-align: center;
+            background: rgb(238, 238, 238);
+            height: 37px;
+
+            .editable-view-input,
+            input,
+            .editableView-container-editing-oneLine,
+            .editableView-container-editing {
+                padding: 12px 10px 11px 10px;
+                border: 0px;
+                color: grey;
+                text-align: center;
+                text-transform: uppercase;
+                letter-spacing: 2px;
+                outline-color: black;
+                height: 100%;
+            }
+
+            .react-autosuggest__container {
+                margin: 0;
+                color: grey;
+                padding: 0px;
+            }
+        }
+    }
+
+    .collectionStackingViewChrome-sectionFilter:hover {
+        cursor: text;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
new file mode 100644
index 000000000..ca1480ff2
--- /dev/null
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -0,0 +1,152 @@
+import * as React from "react";
+import { CollectionView } from "./CollectionView";
+import "./CollectionViewChromes.scss";
+import { CollectionViewType } from "./CollectionBaseView";
+import { undoBatch } from "../../util/UndoManager";
+import { action, observable, runInAction, computed } from "mobx";
+import { observer } from "mobx-react";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { DocLike } from "../MetadataEntryMenu";
+const higflyout = require("@hig/flyout");
+export const Flyout = higflyout.default;
+import * as Autosuggest from 'react-autosuggest';
+import { EditableView } from "../EditableView";
+import { StrCast } from "../../../new_fields/Types";
+
+interface CollectionViewChromeProps {
+    CollectionView: CollectionView;
+}
+
+class CollectionViewBaseChrome extends React.Component<CollectionViewChromeProps> {
+    @undoBatch
+    viewChanged = (e: React.ChangeEvent) => {
+        //@ts-ignore
+        switch (e.target.selectedOptions[0].value) {
+            case "freeform":
+                this.props.CollectionView.props.Document.viewType = CollectionViewType.Freeform;
+                break;
+            case "schema":
+                this.props.CollectionView.props.Document.viewType = CollectionViewType.Schema;
+                break;
+            case "treeview":
+                this.props.CollectionView.props.Document.viewType = CollectionViewType.Tree;
+                break;
+            case "stacking":
+                this.props.CollectionView.props.Document.viewType = CollectionViewType.Stacking;
+                break;
+            case "masonry":
+                this.props.CollectionView.props.Document.viewType = CollectionViewType.Masonry;
+                break;
+            default:
+                break;
+        }
+    }
+
+    render() {
+        return (
+            <div className="collectionViewBaseChrome">
+                <select onChange={this.viewChanged}>
+                    <option value="freeform" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Freeform}>Freeform View</option>
+                    <option value="schema" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Schema}>Schema View</option>
+                    <option value="treeview" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Tree}>Tree View</option>
+                    <option value="stacking" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Stacking}>Stacking View</option>
+                    <option value="masonry" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Masonry}>Masonry View</option>
+                </select>
+            </div>
+        )
+    }
+}
+
+@observer
+export class CollectionStackingViewChrome extends React.Component<CollectionViewChromeProps> {
+    @observable private _currentKey: string = "";
+    @observable private suggestions: string[] = [];
+
+    @computed get sectionFilter() { return StrCast(this.props.CollectionView.props.Document.sectionFilter); }
+
+    getKeySuggestions = async (value: string): Promise<string[]> => {
+        value = value.toLowerCase();
+        let docs: Doc | Doc[] | Promise<Doc> | Promise<Doc[]> | (() => DocLike)
+            = () => DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldExt ? this.props.CollectionView.props.fieldExt : this.props.CollectionView.props.fieldKey]);
+        if (typeof docs === "function") {
+            docs = docs();
+        }
+        docs = await docs;
+        if (docs instanceof Doc) {
+            return Object.keys(docs).filter(key => key.toLowerCase().startsWith(value));
+        } else {
+            const keys = new Set<string>();
+            docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
+            return Array.from(keys).filter(key => key.toLowerCase().startsWith(value));
+        }
+    }
+
+    @action
+    onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => {
+        this._currentKey = newValue;
+    }
+
+    getSuggestionValue = (suggestion: string) => suggestion;
+
+    renderSuggestion = (suggestion: string) => {
+        return <p>{suggestion}</p>;
+    }
+
+    onSuggestionFetch = async ({ value }: { value: string }) => {
+        const sugg = await this.getKeySuggestions(value);
+        runInAction(() => {
+            this.suggestions = sugg;
+        });
+    }
+
+    @action
+    onSuggestionClear = () => {
+        this.suggestions = [];
+    }
+
+    setValue = (value: string) => {
+        this.props.CollectionView.props.Document.sectionFilter = value;
+        return true;
+    }
+
+    @action resetValue = () => { this._currentKey = this.sectionFilter; }
+
+    render() {
+        return (
+            <div className="collectionStackingViewChrome">
+                <CollectionViewBaseChrome CollectionView={this.props.CollectionView} />
+                <div className="collectionStackingViewChrome-sectionFilter-cont">
+                    <div className="collectionStackingViewChrome-sectionFilter-label">
+                        Group items by:
+                    </div>
+                    <div className="collectionStackingViewChrome-sectionFilter">
+                        <EditableView
+                            GetValue={() => this.sectionFilter}
+                            autosuggestProps={
+                                {
+                                    resetValue: this.resetValue,
+                                    value: this._currentKey,
+                                    onChange: this.onKeyChange,
+                                    autosuggestProps: {
+                                        inputProps:
+                                        {
+                                            value: this._currentKey,
+                                            onChange: this.onKeyChange
+                                        },
+                                        getSuggestionValue: this.getSuggestionValue,
+                                        suggestions: this.suggestions,
+                                        alwaysRenderSuggestions: true,
+                                        renderSuggestion: this.renderSuggestion,
+                                        onSuggestionsFetchRequested: this.onSuggestionFetch,
+                                        onSuggestionsClearRequested: this.onSuggestionClear
+                                    }
+                                }}
+                            SetValue={this.setValue}
+                            contents={this.sectionFilter ? this.sectionFilter : "N/A"}
+                        />
+                    </div>
+                </div>
+            </div>
+        )
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2

