From b4ef60552f226e4022fac5092ea66f1b685bb08c Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Mon, 22 Jul 2019 18:50:26 -0400
Subject: Started adding schema functionality

---
 .../views/collections/CollectionSchemaView.tsx     | 92 ++++++++++++++++++++--
 1 file changed, 84 insertions(+), 8 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index f72b1aa07..479fe2b0a 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -15,7 +15,7 @@ import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../new_field
 import { Docs } from "../../documents/Documents";
 import { Gateway } from "../../northstar/manager/Gateway";
 import { SetupDrag, DragManager } from "../../util/DragManager";
-import { CompileScript } from "../../util/Scripting";
+import { CompileScript, ts, Transformer } from "../../util/Scripting";
 import { Transform } from "../../util/Transform";
 import { COLLECTION_BORDER_WIDTH, MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss';
 import { ContextMenu } from "../ContextMenu";
@@ -31,6 +31,7 @@ import { CollectionVideoView } from "./CollectionVideoView";
 import { CollectionView } from "./CollectionView";
 import { undoBatch } from "../../util/UndoManager";
 import { timesSeries } from "async";
+import { ComputedField } from "../../../new_fields/ScriptField";
 
 
 library.add(faCog);
@@ -97,6 +98,78 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
         return this.props.Document;
     }
 
+    getField(row: number, col?: number) {
+        const docs = DocListCast(this.props.Document[this.props.fieldKey]);
+        row = row % docs.length;
+        while (row < 0) row += docs.length;
+        const columns = this.columns;
+        const doc = docs[row];
+        if (col === undefined) {
+            return doc;
+        }
+        if (col >= 0 && col < columns.length) {
+            const column = this.columns[col];
+            return doc[column];
+        }
+        return undefined;
+    }
+
+    createTransformer = (row: number, col: number): Transformer => {
+        const self = this;
+        const captures: { [name: string]: Field } = {};
+
+        const transformer: ts.TransformerFactory<ts.SourceFile> = context => {
+            return root => {
+                function visit(node: ts.Node) {
+                    node = ts.visitEachChild(node, visit, context);
+                    if (ts.isIdentifier(node)) {
+                        const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
+                        const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
+                        if (isntPropAccess && isntPropAssign) {
+                            if (node.text === "$r") {
+                                return ts.createNumericLiteral(row.toString());
+                            } else if (node.text === "$c") {
+                                return ts.createNumericLiteral(col.toString());
+                            } else if (node.text === "$") {
+                                if (ts.isCallExpression(node.parent)) {
+                                    captures.doc = self.props.Document;
+                                    captures.key = self.props.fieldKey;
+                                }
+                            }
+                        }
+                    }
+
+                    return node;
+                }
+                return ts.visitNode(root, visit);
+            };
+        };
+
+        const getVars = () => {
+            return { capturedVariables: captures };
+        };
+
+        return { transformer, getVars };
+    }
+
+    setComputed(script: string, doc: Doc, field: string, row: number, col: number): boolean {
+        script =
+            `const $ = (row:number, col?:number) => {
+                if(col === undefined) {
+                    return (doc as any)[key][row + ${row}];
+                }
+                return (doc as any)[key][row + ${row}][(doc as any).schemaColumns[col + ${col}]];
+            }
+            return ${script}`;
+        const compiled = CompileScript(script, { params: { this: Doc.name }, typecheck: true, transformer: this.createTransformer(row, col) });
+        if (compiled.compiled) {
+            doc[field] = new ComputedField(compiled);
+            return true;
+        }
+
+        return false;
+    }
+
     renderCell = (rowProps: CellInfo) => {
         let props: FieldViewProps = {
             Document: rowProps.original,
@@ -122,12 +195,13 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             (!this.props.CollectionView.props.isSelected() ? undefined :
                 SetupDrag(reference, () => props.Document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e));
         };
-        let applyToDoc = (doc: Doc, run: (args?: { [name: string]: any }) => any) => {
-            const res = run({ this: doc });
+        let applyToDoc = (doc: Doc, row: number, column: number, run: (args?: { [name: string]: any }) => any) => {
+            const res = run({ this: doc, $r: row, $c: column, $: (r: number = 0, c: number = 0) => this.getField(r + row, c + column) });
             if (!res.success) return false;
             doc[props.fieldKey] = res.result;
             return true;
         };
+        const colIndex = this.columns.indexOf(rowProps.column.id!);
         return (
             <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} key={props.Document[Id]} ref={reference}>
                 <EditableView
@@ -142,21 +216,23 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
                         return "";
                     }}
                     SetValue={(value: string) => {
-                        let script = CompileScript(value, { addReturn: true, params: { this: Doc.name } });
+                        if (value.startsWith(":=")) {
+                            return this.setComputed(value.substring(2), props.Document, rowProps.column.id!, rowProps.index, colIndex);
+                        }
+                        let script = CompileScript(value, { addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
                         if (!script.compiled) {
                             return false;
                         }
-                        return applyToDoc(props.Document, script.run);
+                        return applyToDoc(props.Document, rowProps.index, colIndex, script.run);
                     }}
                     OnFillDown={async (value: string) => {
-                        let script = CompileScript(value, { addReturn: true, params: { this: Doc.name } });
+                        let script = CompileScript(value, { addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
                         if (!script.compiled) {
                             return;
                         }
                         const run = script.run;
-                        //TODO This should be able to be refactored to compile the script once
                         const val = await DocListCastAsync(this.props.Document[this.props.fieldKey]);
-                        val && val.forEach(doc => applyToDoc(doc, run));
+                        val && val.forEach((doc, i) => applyToDoc(doc, i, colIndex, run));
                     }}>
                 </EditableView>
             </div >
-- 
cgit v1.2.3-70-g09d2


From db560a8c43f77b9342e55a48dd96f695813b32ef Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Thu, 25 Jul 2019 12:53:09 -0400
Subject: oof a lot of stuff here

---
 .../views/collections/CollectionSchemaView.scss    |   1 +
 .../views/collections/CollectionSchemaView.tsx     |   2 +
 .../views/collections/CollectionStackingView.tsx   |   5 +-
 .../views/collections/CollectionTreeView.scss      |  27 ++-
 .../views/collections/CollectionTreeView.tsx       |   2 +
 src/client/views/collections/CollectionView.tsx    |  18 +-
 .../views/collections/CollectionViewChromes.scss   | 258 +++++++++++----------
 .../views/collections/CollectionViewChromes.tsx    | 137 ++++++-----
 .../authentication/models/current_user_utils.ts    |   4 +
 9 files changed, 251 insertions(+), 203 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index 186e006f3..1e6d1fe99 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -12,6 +12,7 @@
     width: 100%;
     height: 100%;
     overflow: hidden;
+    transition: top 0.5s;
 
     .collectionSchemaView-cellContents {
         height: $MAX_ROW_HEIGHT;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 2cf50e551..bdcedea10 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -69,6 +69,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     @observable _newKeyName: string = "";
     @observable previewScript: string = "";
 
+    @computed get chromeCollapsed() { return this.props.chromeCollapsed; }
     @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); }
     @computed get previewHeight() { return () => this.props.PanelHeight() - 2 * this.borderWidth; }
     @computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH - this.previewWidth(); }
@@ -390,6 +391,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     render() {
         return (
             <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel}
+                style={{ top: this.chromeCollapsed ? 0 : "unset" }}
                 onDrop={(e: React.DragEvent) => this.onDrop(e, {})} onContextMenu={this.onContextMenu} ref={this.createTarget}>
                 {this.reactTable}
                 {this.dividerDragger}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 31ec80d76..b5c0fb3d6 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -21,8 +21,6 @@ import { List } from "../../../new_fields/List";
 import { EditableView } from "../EditableView";
 import { CollectionViewProps } from "./CollectionBaseView";
 
-let valuesCreated = 1;
-
 @observer
 export class CollectionStackingView extends CollectionSubView(doc => doc) {
     _masonryGridRef: HTMLDivElement | null = null;
@@ -33,7 +31,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     _columnStart: number = 0;
     @observable private cursor: CursorProperty = "grab";
     get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
-    get chromeCollapsed() { return this.props.chromeCollapsed; }
+    @computed get chromeCollapsed() { return this.props.chromeCollapsed; }
     @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); }
@@ -84,7 +82,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             () => StrCast(this.props.Document.sectionFilter),
             () => {
                 this.props.Document.sectionHeaders = new List();
-                valuesCreated = 1;
             }
         )
     }
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 5205f4313..db3652ff6 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -31,6 +31,7 @@
         margin-top: 4px;
         transform: scale(1.3, 1.3);
     }
+
     .editableView-container {
         font-weight: bold;
     }
@@ -43,18 +44,20 @@
         display: inline;
     }
 
-    .editableView-input, .editableView-container-editing {
+    .editableView-input,
+    .editableView-container-editing {
         display: block;
         text-overflow: ellipsis;
         font-size: 24px;
         white-space: nowrap;
     }
 }
+
 .collectionTreeView-keyHeader {
     font-style: italic;
     font-size: 8pt;
     margin-left: 3px;
-    display:none;
+    display: none;
     background: lightgray;
 }
 
@@ -72,28 +75,31 @@
     // width:100%;//width: max-content;
 
 }
+
 .treeViewItem-openRight {
     display: none;
 }
 
 .treeViewItem-border {
-    display:inherit;
+    display: inherit;
     border-left: dashed 1px #00000042;
 }
 
 .treeViewItem-header:hover {
     .collectionTreeView-keyHeader {
-        display:inherit;
+        display: inherit;
     }
+
     .treeViewItem-openRight {
         display: inline-block;
-        height:13px;
-        margin-top:2px;
+        height: 13px;
+        margin-top: 2px;
         margin-left: 5px;
+
         // display: inline;
         svg {
-            display:block;
-            padding:0px;
+            display: block;
+            padding: 0px;
             margin: 0px;
         }
     }
@@ -101,14 +107,17 @@
 
 .treeViewItem-header {
     border: transparent 1px solid;
-    display:flex;
+    display: flex;
 }
+
 .treeViewItem-header-above {
     border-top: black 1px solid;
 }
+
 .treeViewItem-header-below {
     border-bottom: black 1px solid;
 }
+
 .treeViewItem-header-inside {
     border: black 1px solid;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index d05cc375e..cbf32c667 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -456,6 +456,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
     private treedropDisposer?: DragManager.DragDropDisposer;
     private _mainEle?: HTMLDivElement;
 
+    @computed get chromeCollapsed() { return this.props.chromeCollapsed; }
+
     protected createTreeDropTarget = (ele: HTMLDivElement) => {
         this.treedropDisposer && this.treedropDisposer();
         if (this._mainEle = ele) {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index e2f652908..1c6dd661d 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -18,7 +18,7 @@ import { CollectionTreeView } from "./CollectionTreeView";
 import { StrCast, PromiseValue } from '../../../new_fields/Types';
 import { DocumentType } from '../../documents/Documents';
 import { CollectionStackingViewChrome, CollectionViewBaseChrome } from './CollectionViewChromes';
-import { observable } from 'mobx';
+import { observable, action, runInAction } from 'mobx';
 export const COLLECTION_BORDER_WIDTH = 2;
 
 library.add(faTh);
@@ -37,11 +37,20 @@ export class CollectionView extends React.Component<FieldViewProps> {
 
     public static LayoutString(fieldStr: string = "data", fieldExt: string = "") { return FieldView.LayoutString(CollectionView, fieldStr, fieldExt); }
 
+    componentDidMount = () => {
+        // chrome status is one of disabled, collapsed, or visible. this determines initial state from document
+        let chromeStatus = this.props.Document.chromeStatus;
+        if (chromeStatus && (chromeStatus === "disabled" || chromeStatus === "collapsed")) {
+            runInAction(() => this._collapsed = true);
+        }
+    }
+
     private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
         let props = { ...this.props, ...renderProps };
         switch (this.isAnnotationOverlay ? CollectionViewType.Freeform : type) {
             case CollectionViewType.Schema: return (<CollectionSchemaView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />);
-            case CollectionViewType.Docking: return (<CollectionDockingView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />);
+            // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip
+            case CollectionViewType.Docking: return (<CollectionDockingView chromeCollapsed={true} key="collview" {...props} CollectionView={this} />);
             case CollectionViewType.Tree: return (<CollectionTreeView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />);
             case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />); }
             case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />); }
@@ -52,12 +61,15 @@ export class CollectionView extends React.Component<FieldViewProps> {
         return (null);
     }
 
+    @action
     private collapse = (value: boolean) => {
         this._collapsed = value;
+        this.props.Document.chromeStatus = value ? "collapsed" : "visible";
     }
 
     private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
-        if (this.isAnnotationOverlay || this.props.Document === CurrentUserUtils.UserDocument.sidebar) {
+        // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip
+        if (this.isAnnotationOverlay || this.props.Document.chromeStatus === "disabled" || type === CollectionViewType.Docking) {
             return [(null), this.SubViewHelper(type, renderProps)];
         }
         else {
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index d37228bde..0faca0607 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -1,167 +1,171 @@
 @import "../globalCssVariables";
 @import '~js-datepicker/dist/datepicker.min.css';
 
-.collectionViewChrome {
-    display: grid;
-    grid-template-columns: 1fr auto;
-    padding-bottom: 10px;
-    border-bottom: .5px solid lightgrey;
-    margin: 10px;
+.collectionViewChrome-cont {
     position: relative;
     z-index: 9001;
     transition: top .5s;
+    background: rgb(238, 238, 238);
+    padding: 10px;
 
-    .collectionViewBaseChrome {
-        display: flex;
+    .collectionViewChrome {
+        display: grid;
+        grid-template-columns: 1fr auto;
+        padding-bottom: 10px;
+        border-bottom: .5px solid lightgrey;
 
-        .collectionViewBaseChrome-viewPicker {
-            font-size: 75%;
-            text-transform: uppercase;
-            letter-spacing: 2px;
-            background: rgb(238, 238, 238);
-            color: grey;
-            outline-color: black;
-            border: none;
-            padding: 12px 10px 11px 10px;
-            margin-left: 50px;
-        }
-
-        .collectionViewBaseChrome-viewPicker:active {
-            outline-color: black;
-        }
-
-        .collectionViewBaseChrome-collapse {
-            transition: all .5s;
-            position: absolute;
-            width: 40px;
-        }
-
-        .collectionViewBaseChrome-viewSpecs {
-            margin-left: 10px;
-            display: grid;
+        .collectionViewBaseChrome {
+            display: flex;
 
-            .collectionViewBaseChrome-viewSpecsInput {
-                padding: 12px 10px 11px 10px;
-                border: 0px;
-                color: grey;
-                text-align: center;
+            .collectionViewBaseChrome-viewPicker {
+                font-size: 75%;
                 text-transform: uppercase;
                 letter-spacing: 2px;
-                outline-color: black;
-                font-size: 75%;
                 background: rgb(238, 238, 238);
-                height: 100%;
-                width: 150px;
+                color: grey;
+                outline-color: black;
+                border: none;
+                padding: 12px 10px 11px 10px;
+                margin-left: 50px;
+            }
+
+            .collectionViewBaseChrome-viewPicker:active {
+                outline-color: black;
             }
 
-            .collectionViewBaseChrome-viewSpecsMenu {
-                overflow: hidden;
-                transition: height .5s, display .5s;
+            .collectionViewBaseChrome-collapse {
+                transition: all .5s;
                 position: absolute;
-                top: 60px;
-                z-index: 100;
-                display: flex;
-                flex-direction: column;
-                background: rgb(238, 238, 238);
-                box-shadow: grey 2px 2px 4px;
+                width: 40px;
+            }
 
-                .qs-datepicker {
-                    left: unset;
-                    right: 0;
+            .collectionViewBaseChrome-viewSpecs {
+                margin-left: 10px;
+                display: grid;
+
+                .collectionViewBaseChrome-viewSpecsInput {
+                    padding: 12px 10px 11px 10px;
+                    border: 0px;
+                    color: grey;
+                    text-align: center;
+                    text-transform: uppercase;
+                    letter-spacing: 2px;
+                    outline-color: black;
+                    font-size: 75%;
+                    background: rgb(238, 238, 238);
+                    height: 100%;
+                    width: 150px;
                 }
 
-                .collectionViewBaseChrome-viewSpecsMenu-row {
-                    display: grid;
-                    grid-template-columns: 150px 200px 150px;
-                    margin-top: 10px;
-                    margin-right: 10px;
-
-                    .collectionViewBaseChrome-viewSpecsMenu-rowLeft,
-                    .collectionViewBaseChrome-viewSpecsMenu-rowMiddle,
-                    .collectionViewBaseChrome-viewSpecsMenu-rowRight {
-                        font-size: 75%;
-                        letter-spacing: 2px;
-                        color: grey;
-                        margin-left: 10px;
-                        padding: 5px;
-                        border: none;
-                        outline-color: black;
+                .collectionViewBaseChrome-viewSpecsMenu {
+                    overflow: hidden;
+                    transition: height .5s, display .5s;
+                    position: absolute;
+                    top: 60px;
+                    z-index: 100;
+                    display: flex;
+                    flex-direction: column;
+                    background: rgb(238, 238, 238);
+                    box-shadow: grey 2px 2px 4px;
+
+                    .qs-datepicker {
+                        left: unset;
+                        right: 0;
                     }
-                }
 
-                .collectionViewBaseChrome-viewSpecsMenu-lastRow {
-                    display: grid;
-                    grid-template-columns: 1fr 1fr;
-                    grid-gap: 10px;
-                    margin: 10px;
+                    .collectionViewBaseChrome-viewSpecsMenu-row {
+                        display: grid;
+                        grid-template-columns: 150px 200px 150px;
+                        margin-top: 10px;
+                        margin-right: 10px;
+
+                        .collectionViewBaseChrome-viewSpecsMenu-rowLeft,
+                        .collectionViewBaseChrome-viewSpecsMenu-rowMiddle,
+                        .collectionViewBaseChrome-viewSpecsMenu-rowRight {
+                            font-size: 75%;
+                            letter-spacing: 2px;
+                            color: grey;
+                            margin-left: 10px;
+                            padding: 5px;
+                            border: none;
+                            outline-color: black;
+                        }
+                    }
+
+                    .collectionViewBaseChrome-viewSpecsMenu-lastRow {
+                        display: grid;
+                        grid-template-columns: 1fr 1fr;
+                        grid-gap: 10px;
+                        margin: 10px;
+                    }
                 }
             }
         }
-    }
 
 
-    .collectionStackingViewChrome-cont {
-        display: flex;
-        justify-content: space-between;
-    }
+        .collectionStackingViewChrome-cont {
+            display: flex;
+            justify-content: space-between;
+        }
 
-    .collectionStackingViewChrome-sort {
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
+        .collectionStackingViewChrome-sort {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
 
-        .collectionStackingViewChrome-sortIcon {
-            transition: transform .5s;
-            margin-left: 10px;
+            .collectionStackingViewChrome-sortIcon {
+                transition: transform .5s;
+                margin-left: 10px;
+            }
         }
-    }
-
-    button:hover {
-        transform: scale(1);
-    }
 
+        button:hover {
+            transform: scale(1);
+        }
 
-    .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-cont {
+            justify-self: right;
+            display: flex;
+            font-size: 75%;
+            text-transform: uppercase;
+            letter-spacing: 2px;
 
-        .collectionStackingViewChrome-sectionFilter {
-            color: white;
-            width: 100px;
-            text-align: center;
-            background: rgb(238, 238, 238);
+            .collectionStackingViewChrome-sectionFilter-label {
+                vertical-align: center;
+                padding: 10px;
+            }
 
-            .editable-view-input,
-            input,
-            .editableView-container-editing-oneLine,
-            .editableView-container-editing {
-                padding: 12px 10px 11px 10px;
-                border: 0px;
-                color: grey;
+            .collectionStackingViewChrome-sectionFilter {
+                color: white;
+                width: 100px;
                 text-align: center;
-                text-transform: uppercase;
-                letter-spacing: 2px;
-                outline-color: black;
-                height: 100%;
-            }
+                background: rgb(238, 238, 238);
 
-            .react-autosuggest__container {
-                margin: 0;
-                color: grey;
-                padding: 0px;
+                .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;
+        .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
index 24d13d5cb..4129781f7 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -47,6 +47,20 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
         runInAction(() => {
             this._keyRestrictions.push([<KeyRestrictionRow key={Utils.GenerateGuid()} contains={true} script={(value: string) => runInAction(() => this._keyRestrictions[0][1] = value)} />, ""]);
             this._keyRestrictions.push([<KeyRestrictionRow key={Utils.GenerateGuid()} contains={false} script={(value: string) => runInAction(() => this._keyRestrictions[1][1] = value)} />, ""]);
+
+            // chrome status is one of disabled, collapsed, or visible. this determines initial state from document
+            let chromeStatus = this.props.CollectionView.props.Document.chromeStatus;
+            if (chromeStatus) {
+                if (chromeStatus === "disabled") {
+                    throw new Error("how did you get here, if chrome status is 'disabled' on a collection, a chrome shouldn't even be instantiated!");
+                }
+                else if (chromeStatus === "collapsed") {
+                    this._collapsed = true;
+                    if (this.props.collapse) {
+                        this.props.collapse(true);
+                    }
+                }
+            }
         });
     }
 
@@ -130,11 +144,12 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     @action
     toggleCollapse = () => {
         this._collapsed = !this._collapsed;
-        this.props.collapse(this._collapsed);
+        if (this.props.collapse) {
+            this.props.collapse(this._collapsed);
+        }
     }
 
     subChrome = () => {
-
         switch (this.props.type) {
             case CollectionViewType.Stacking: return (
                 <CollectionStackingViewChrome
@@ -148,71 +163,73 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
 
     render() {
         return (
-            <div className="collectionViewChrome" style={{ top: this._collapsed ? -100 : 0 }}>
-                <div className="collectionViewBaseChrome">
-                    <button className="collectionViewBaseChrome-collapse"
-                        style={{ top: this._collapsed ? 80 : 0, transform: `rotate(${this._collapsed ? 180 : 0}deg)` }}
-                        title="Collapse collection chrome" onClick={this.toggleCollapse}>
-                        <FontAwesomeIcon icon="caret-up" size="2x" />
-                    </button>
-                    <select
-                        className="collectionViewBaseChrome-viewPicker"
-                        onPointerDown={stopPropagation}
-                        onChange={this.viewChanged}
-                        value={NumCast(this.props.CollectionView.props.Document.viewType)}>
-                        <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="1">Freeform View</option>
-                        <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="2">Schema View</option>
-                        <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree View</option>
-                        <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking View</option>
-                        <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry View></option>
-                    </select>
-                    <div className="collectionViewBaseChrome-viewSpecs">
-                        <input className="collectionViewBaseChrome-viewSpecsInput"
-                            placeholder="Filter Documents"
-                            value={this.filterValue ? this.filterValue.script.originalScript : ""}
-                            onPointerDown={this.openViewSpecs} />
-                        <div className="collectionViewBaseChrome-viewSpecsMenu"
-                            onPointerDown={this.openViewSpecs}
-                            style={{
-                                height: this._viewSpecsOpen ? "fit-content" : "0px",
-                                overflow: this._viewSpecsOpen ? "initial" : "hidden"
-                            }}>
-                            {this._keyRestrictions.map(i => i[0])}
-                            <div className="collectionViewBaseChrome-viewSpecsMenu-row">
-                                <div className="collectionViewBaseChrome-viewSpecsMenu-rowLeft">
-                                    CREATED WITHIN:
-                            </div>
-                                <select className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
-                                    style={{ textTransform: "uppercase", textAlign: "center" }}
-                                    value={this._dateWithinValue}
-                                    onChange={(e) => runInAction(() => this._dateWithinValue = e.target.value)}>
-                                    <option value="1d">1 day of</option>
-                                    <option value="3d">3 days of</option>
-                                    <option value="1w">1 week of</option>
-                                    <option value="2w">2 weeks of</option>
-                                    <option value="1m">1 month of</option>
-                                    <option value="2m">2 months of</option>
-                                    <option value="6m">6 months of</option>
-                                    <option value="1y">1 year of</option>
-                                </select>
-                                <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
-                                    id={this._datePickerElGuid}
-                                    value={this._dateValue.toLocaleDateString()}
-                                    onPointerDown={this.openDatePicker}
-                                    placeholder="Value" />
+            <div className="collectionViewChrome-cont" style={{ top: this._collapsed ? -100 : 0 }}>
+                <div className="collectionViewChrome">
+                    <div className="collectionViewBaseChrome">
+                        <button className="collectionViewBaseChrome-collapse"
+                            style={{ top: this._collapsed ? 90 : 10, transform: `rotate(${this._collapsed ? 180 : 0}deg)` }}
+                            title="Collapse collection chrome" onClick={this.toggleCollapse}>
+                            <FontAwesomeIcon icon="caret-up" size="2x" />
+                        </button>
+                        <select
+                            className="collectionViewBaseChrome-viewPicker"
+                            onPointerDown={stopPropagation}
+                            onChange={this.viewChanged}
+                            value={NumCast(this.props.CollectionView.props.Document.viewType)}>
+                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="1">Freeform View</option>
+                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="2">Schema View</option>
+                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree View</option>
+                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking View</option>
+                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry View></option>
+                        </select>
+                        <div className="collectionViewBaseChrome-viewSpecs">
+                            <input className="collectionViewBaseChrome-viewSpecsInput"
+                                placeholder="Filter Documents"
+                                value={this.filterValue ? this.filterValue.script.originalScript : ""}
+                                onPointerDown={this.openViewSpecs} />
+                            <div className="collectionViewBaseChrome-viewSpecsMenu"
+                                onPointerDown={this.openViewSpecs}
+                                style={{
+                                    height: this._viewSpecsOpen ? "fit-content" : "0px",
+                                    overflow: this._viewSpecsOpen ? "initial" : "hidden"
+                                }}>
+                                {this._keyRestrictions.map(i => i[0])}
+                                <div className="collectionViewBaseChrome-viewSpecsMenu-row">
+                                    <div className="collectionViewBaseChrome-viewSpecsMenu-rowLeft">
+                                        CREATED WITHIN:
                             </div>
-                            <div className="collectionViewBaseChrome-viewSpecsMenu-lastRow">
-                                <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.addKeyRestriction}>
-                                    ADD KEY RESTRICTION
+                                    <select className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
+                                        style={{ textTransform: "uppercase", textAlign: "center" }}
+                                        value={this._dateWithinValue}
+                                        onChange={(e) => runInAction(() => this._dateWithinValue = e.target.value)}>
+                                        <option value="1d">1 day of</option>
+                                        <option value="3d">3 days of</option>
+                                        <option value="1w">1 week of</option>
+                                        <option value="2w">2 weeks of</option>
+                                        <option value="1m">1 month of</option>
+                                        <option value="2m">2 months of</option>
+                                        <option value="6m">6 months of</option>
+                                        <option value="1y">1 year of</option>
+                                    </select>
+                                    <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
+                                        id={this._datePickerElGuid}
+                                        value={this._dateValue.toLocaleDateString()}
+                                        onPointerDown={this.openDatePicker}
+                                        placeholder="Value" />
+                                </div>
+                                <div className="collectionViewBaseChrome-viewSpecsMenu-lastRow">
+                                    <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.addKeyRestriction}>
+                                        ADD KEY RESTRICTION
                             </button>
-                                <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.applyFilter}>
-                                    APPLY FILTER
+                                    <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.applyFilter}>
+                                        APPLY FILTER
                             </button>
+                                </div>
                             </div>
                         </div>
                     </div>
+                    {this.subChrome()}
                 </div>
-                {this.subChrome()}
             </div>
         )
     }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 1c52a3f11..41eb1aa0c 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -50,6 +50,7 @@ export class CurrentUserUtils {
         if (doc.workspaces === undefined) {
             const workspaces = Docs.Create.TreeDocument([], { title: "Workspaces", height: 100 });
             workspaces.excludeFromLibrary = true;
+            workspaces.chromeStatus = "disabled";
             workspaces.workspaceLibrary = true;
             workspaces.boxShadow = "0 0";
             doc.workspaces = workspaces;
@@ -57,6 +58,7 @@ export class CurrentUserUtils {
         if (doc.recentlyClosed === undefined) {
             const recentlyClosed = Docs.Create.TreeDocument([], { title: "Recently Closed", height: 75 });
             recentlyClosed.excludeFromLibrary = true;
+            recentlyClosed.chromeStatus = "disabled";
             recentlyClosed.boxShadow = "0 0";
             doc.recentlyClosed = recentlyClosed;
         }
@@ -65,11 +67,13 @@ export class CurrentUserUtils {
             sidebar.excludeFromLibrary = true;
             sidebar.gridGap = 5;
             sidebar.xMargin = 5;
+            sidebar.chromeStatus = "disabled";
             sidebar.yMargin = 5;
             Doc.GetProto(sidebar).backgroundColor = "#aca3a6";
             sidebar.boxShadow = "1 1 3";
             doc.sidebar = sidebar;
         }
+        doc.chromeStatus = "disabled";
         StrCast(doc.title).indexOf("@") !== -1 && (doc.title = StrCast(doc.title).split("@")[0] + "'s Library");
 
     }
-- 
cgit v1.2.3-70-g09d2

