From ae51e87874a714fdb46d4093fee513787b413ed8 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <abdullah_ahmed@brown.edu>
Date: Fri, 19 Jul 2019 13:11:10 -0400
Subject: can sort columns by asc, desc

---
 src/client/views/EditableView.tsx | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 989fb1be9..42faedf9d 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -31,6 +31,7 @@ export interface EditableProps {
     oneLine?: boolean;
     editing?: boolean;
     onClick?: (e: React.MouseEvent) => boolean;
+    isEditingCallback?:  (isEditing: boolean) => void;
 }
 
 /**
@@ -47,6 +48,11 @@ export class EditableView extends React.Component<EditableProps> {
         this._editing = this.props.editing ? true : false;
     }
 
+    @action
+    componentWillReceiveProps(nextProps: EditableProps) {
+        this._editing = nextProps.editing ? true : false;
+    }
+
     @action
     onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
         if (e.key === "Tab") {
@@ -55,13 +61,16 @@ export class EditableView extends React.Component<EditableProps> {
             if (!e.ctrlKey) {
                 if (this.props.SetValue(e.currentTarget.value, e.shiftKey)) {
                     this._editing = false;
+                    this.props.isEditingCallback && this.props.isEditingCallback(false);
                 }
             } else if (this.props.OnFillDown) {
                 this.props.OnFillDown(e.currentTarget.value);
                 this._editing = false;
+                this.props.isEditingCallback && this.props.isEditingCallback(false);
             }
         } else if (e.key === "Escape") {
             this._editing = false;
+            this.props.isEditingCallback && this.props.isEditingCallback(false);
         }
     }
 
@@ -69,6 +78,7 @@ export class EditableView extends React.Component<EditableProps> {
     onClick = (e: React.MouseEvent) => {
         if (!this.props.onClick || !this.props.onClick(e)) {
             this._editing = true;
+            this.props.isEditingCallback && this.props.isEditingCallback(true);
         }
         e.stopPropagation();
     }
@@ -85,7 +95,7 @@ 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}
+                onBlur={action(() => {this._editing = false; this.props.isEditingCallback && this.props.isEditingCallback(false);})} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
                 style={{ display: this.props.display, fontSize: this.props.fontSize }} />;
         } else {
             return (
-- 
cgit v1.2.3-70-g09d2


From 5da4de27c45e4f40c88b710be64f0129e9be7088 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <abdullah_ahmed@brown.edu>
Date: Fri, 19 Jul 2019 16:21:19 -0400
Subject: focused cell styling + click editable view changes focuses on that
 cell

---
 src/client/views/EditableView.tsx                  |  2 +-
 .../views/collections/CollectionSchemaCells.tsx    |  1 +
 .../views/collections/CollectionSchemaHeaders.tsx  | 29 ----------------------
 .../views/collections/CollectionSchemaView.scss    |  7 +++++-
 4 files changed, 8 insertions(+), 31 deletions(-)

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

diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 42faedf9d..9471ae98a 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -79,7 +79,7 @@ export class EditableView extends React.Component<EditableProps> {
         if (!this.props.onClick || !this.props.onClick(e)) {
             this._editing = true;
             this.props.isEditingCallback && this.props.isEditingCallback(true);
-        }
+        } 
         e.stopPropagation();
     }
 
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 0307bb83e..31a9baa11 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -74,6 +74,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
         document.addEventListener("keydown", this.onKeyDown);
         this._isEditing = isEditing;
         this.props.setIsEditing(isEditing);
+        this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
     }
 
     @action
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index 5f8037fbf..df078eca5 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -56,38 +56,14 @@ export class CollectionSchemaHeader extends React.Component<HeaderProps> {
 
 
 export interface AddColumnHeaderProps {
-    // possibleKeys: string[];
-    // existingKeys: string[];
-    // onSelect: (oldKey: string, newKey: string, addnew: boolean) => void;
-    // setIsEditing: (isEditing: boolean) => void;
     createColumn: () => void;
 }
 
 @observer
 export class CollectionSchemaAddColumnHeader extends React.Component<AddColumnHeaderProps> {
     render() {
-        let addButton = <button><FontAwesomeIcon icon="plus" size="sm" /></button>;
         return (
             <button onClick={() => this.props.createColumn()}><FontAwesomeIcon icon="plus" size="sm" /></button>
-            // <div className="collectionSchemaView-header-addColumn" >
-            //     <CollectionSchemaColumnMenu
-            //         keyValue=""
-            //         possibleKeys={this.props.possibleKeys}
-            //         existingKeys={this.props.existingKeys}
-            //         keyType={ColumnType.Any}
-            //         typeConst={true}
-            //         menuButtonContent={addButton}
-            //         addNew={true}
-            //         onSelect={this.props.onSelect}
-            //         setIsEditing={this.props.setIsEditing}
-            //         deleteColumn={action(emptyFunction)}
-            //         onlyShowOptions={true}
-            //         setColumnType={action(emptyFunction)}
-            //         setColumnSort={action(emptyFunction)}
-            //         removeColumnSort={action(emptyFunction)}
-            //         anchorPoint={anchorPoints.TOP_RIGHT}
-            //     />
-            // </div>
         );
     }
 }
@@ -115,8 +91,6 @@ export interface ColumnMenuProps {
 export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps> {
     @observable private _isOpen: boolean = false;
     @observable private _node : HTMLDivElement | null = null;
-    // @observable private _node = React.createRef<HTMLDivElement>();
-    @observable private _test = "test";
 
     componentDidMount() {
         document.addEventListener("pointerdown", this.detectClick);
@@ -168,9 +142,6 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
                     <button title="String" className={this.props.keyType === ColumnType.String ? "active" : ""} onClick={() => this.props.setColumnType(this.props.keyValue, ColumnType.String)}>
                         <FontAwesomeIcon icon={"font"} size="sm" />
                         </button>
-                    {/* <button title="Boolean" className={this.props.keyType === ColumnType.Boolean ? "active" : ""} onClick={() => this.props.setColumnType(this.props.keyValue, ColumnType.Boolean)}>
-                        <FontAwesomeIcon icon={"toggle-on"} size="sm" />
-                        </button> */}
                     <button title="Checkbox" className={this.props.keyType === ColumnType.Boolean ? "active" : ""} onClick={() => this.props.setColumnType(this.props.keyValue, ColumnType.Boolean)}>
                         <FontAwesomeIcon icon={"check-square"} size="sm" />
                         </button>
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index 47947829e..045994751 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -126,13 +126,14 @@
 
     .rt-tr {
         width: 100%;
+        height: $MAX_ROW_HEIGHT;
     }
 
     .rt-td {
         border-width: 1px;
         border-right-color: $intermediate-color;
         max-height: $MAX_ROW_HEIGHT;
-        padding: 3px 7px;
+        padding: 0;
         font-size: 13px;
         text-align: center;
 
@@ -289,6 +290,10 @@
     height: 100%;
     padding: 4px;
 
+    &:focus {
+        outline: none;
+    }
+
     &.focused {
         // background-color: yellowgreen;
         border: 2px solid yellowgreen;
-- 
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/EditableView.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


From 40b7197fb9b4748a63845bb664fa9ab02ad6915a Mon Sep 17 00:00:00 2001
From: Sam Wilkins <abdullah_ahmed@brown.edu>
Date: Tue, 23 Jul 2019 22:07:55 -0400
Subject: minor styling

---
 src/client/views/EditableView.tsx                  | 13 ++--
 .../views/collections/CollectionSchemaHeaders.tsx  | 38 +++++------
 .../views/collections/CollectionSchemaView.scss    | 29 ++++++--
 .../views/collections/CollectionSchemaView.tsx     | 79 +++++++++++-----------
 4 files changed, 92 insertions(+), 67 deletions(-)

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

diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 5cbecf2c9..31e4557be 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -39,7 +39,7 @@ export interface EditableProps {
     oneLine?: boolean;
     editing?: boolean;
     onClick?: (e: React.MouseEvent) => boolean;
-    isEditingCallback?:  (isEditing: boolean) => void;
+    isEditingCallback?: (isEditing: boolean) => void;
 }
 
 /**
@@ -58,7 +58,12 @@ export class EditableView extends React.Component<EditableProps> {
 
     @action
     componentWillReceiveProps(nextProps: EditableProps) {
-        this._editing = nextProps.editing ? true : false;
+        // this is done because when autosuggest is turned on, the suggestions are passed in as a prop,
+        // so when the suggestions are passed in, and no editing prop is passed in, it used to set it
+        // to false. this will no longer do so -syip
+        if (nextProps.editing && nextProps.editing !== this._editing) {
+            this._editing = nextProps.editing;
+        }
     }
 
     @action
@@ -88,7 +93,7 @@ export class EditableView extends React.Component<EditableProps> {
         if (!this.props.onClick || !this.props.onClick(e)) {
             this._editing = true;
             this.props.isEditingCallback && this.props.isEditingCallback(true);
-        } 
+        }
         e.stopPropagation();
     }
 
@@ -119,7 +124,7 @@ export class EditableView extends React.Component<EditableProps> {
                     }}
                 />
                 : <input className="editableView-input" defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus
-                    onBlur={action(() => {this._editing = false; this.props.isEditingCallback && this.props.isEditingCallback(false);})} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
+                    onBlur={action(() => { this._editing = false; this.props.isEditingCallback && this.props.isEditingCallback(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();
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index 3a57cd306..d1d0674c4 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -30,7 +30,7 @@ export interface HeaderProps {
 export class CollectionSchemaHeader extends React.Component<HeaderProps> {
     render() {
         let icon: IconProp = this.props.keyType === ColumnType.Number ? "hashtag" : this.props.keyType === ColumnType.String ? "font" :
-            this.props.keyType === ColumnType.Boolean ? "check-square" : this.props.keyType ===  ColumnType.Doc ? "file" : "align-justify";
+            this.props.keyType === ColumnType.Boolean ? "check-square" : this.props.keyType === ColumnType.Doc ? "file" : "align-justify";
 
         return (
             <div className="collectionSchemaView-header" >
@@ -64,7 +64,7 @@ export interface AddColumnHeaderProps {
 export class CollectionSchemaAddColumnHeader extends React.Component<AddColumnHeaderProps> {
     render() {
         return (
-            <button onClick={() => this.props.createColumn()}><FontAwesomeIcon icon="plus" size="sm" /></button>
+            <button className="add-column" onClick={() => this.props.createColumn()}><FontAwesomeIcon icon="plus" size="sm" /></button>
         );
     }
 }
@@ -91,7 +91,7 @@ export interface ColumnMenuProps {
 @observer
 export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps> {
     @observable private _isOpen: boolean = false;
-    @observable private _node : HTMLDivElement | null = null;
+    @observable private _node: HTMLDivElement | null = null;
 
     componentDidMount() {
         document.addEventListener("pointerdown", this.detectClick);
@@ -109,7 +109,7 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
         }
     }
 
-    @action 
+    @action
     toggleIsOpen = (): void => {
         this._isOpen = !this._isOpen;
         this.props.setIsEditing(this._isOpen);
@@ -133,19 +133,19 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
         return (
             <div className="collectionSchema-headerMenu-group">
                 <label>Column type:</label>
-                <div className="columnMenu-types"> 
+                <div className="columnMenu-types">
                     <button title="Any" className={this.props.keyType === ColumnType.Any ? "active" : ""} onClick={() => this.props.setColumnType(this.props.keyValue, ColumnType.Any)}>
                         <FontAwesomeIcon icon={"align-justify"} size="sm" />
-                        </button>
+                    </button>
                     <button title="Number" className={this.props.keyType === ColumnType.Number ? "active" : ""} onClick={() => this.props.setColumnType(this.props.keyValue, ColumnType.Number)}>
                         <FontAwesomeIcon icon={"hashtag"} size="sm" />
-                        </button>
+                    </button>
                     <button title="String" className={this.props.keyType === ColumnType.String ? "active" : ""} onClick={() => this.props.setColumnType(this.props.keyValue, ColumnType.String)}>
                         <FontAwesomeIcon icon={"font"} size="sm" />
-                        </button>
+                    </button>
                     <button title="Checkbox" className={this.props.keyType === ColumnType.Boolean ? "active" : ""} onClick={() => this.props.setColumnType(this.props.keyValue, ColumnType.Boolean)}>
                         <FontAwesomeIcon icon={"check-square"} size="sm" />
-                        </button>
+                    </button>
                     <button title="Document" className={this.props.keyType === ColumnType.Doc ? "active" : ""} onClick={() => this.props.setColumnType(this.props.keyValue, ColumnType.Doc)}>
                         <FontAwesomeIcon icon={"file"} size="sm" />
                     </button>
@@ -183,13 +183,13 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
                     />
                 </div>
                 {this.props.onlyShowOptions ? <></> :
-                <>
-                    {this.renderTypes()}
-                    {this.renderSorting()}
-                    <div className="collectionSchema-headerMenu-group">
-                        <button onClick={() => this.props.deleteColumn(this.props.keyValue)}>Delete Column</button>
-                    </div>
-                </>
+                    <>
+                        {this.renderTypes()}
+                        {this.renderSorting()}
+                        <div className="collectionSchema-headerMenu-group">
+                            <button onClick={() => this.props.deleteColumn(this.props.keyValue)}>Delete Column</button>
+                        </div>
+                    </>
                 }
             </div>
         );
@@ -225,7 +225,7 @@ class KeysDropdown extends React.Component<KeysDropdownProps> {
 
     @action setSearchTerm = (value: string): void => { this._searchTerm = value; };
     @action setKey = (key: string): void => { this._key = key; };
-    @action setIsOpen = (isOpen: boolean): void => {this._isOpen = isOpen;};
+    @action setIsOpen = (isOpen: boolean): void => { this._isOpen = isOpen; };
 
     @action
     onSelect = (key: string): void => {
@@ -253,7 +253,7 @@ class KeysDropdown extends React.Component<KeysDropdownProps> {
         }
     }
 
-    @action 
+    @action
     onPointerEnter = (e: React.PointerEvent): void => {
         this._canClose = false;
     }
@@ -278,7 +278,7 @@ class KeysDropdown extends React.Component<KeysDropdownProps> {
         if (!exactFound && this._searchTerm !== "" && this.props.canAddNew) {
             options.push(<div key={""} className="key-option"
                 onClick={() => { this.onSelect(this._searchTerm); this.setSearchTerm(""); }}>
-                    Create "{this._searchTerm}" key</div>);
+                Create "{this._searchTerm}" key</div>);
         }
 
         return options;
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index 4b20e8241..38d14c2de 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -57,7 +57,7 @@
 .ReactTable {
     width: 100%;
     height: 100%;
-    background: $light-color;
+    background: white;
     box-sizing: border-box;
     border: none !important;
 
@@ -76,6 +76,7 @@
             font-size: 12px;
             height: 30px;
             border: 1px solid $intermediate-color;
+            box-shadow: none;
         }
 
         .rt-resizable-header {
@@ -101,6 +102,7 @@
         // max-height: $MAX_ROW_HEIGHT;
         font-size: 13px;
         text-align: center;
+        background-color: $light-color-secondary;
         
         &:last-child {
             overflow: visible;
@@ -114,6 +116,7 @@
     .rt-tr-group {
         direction: ltr;
         flex: 0 1 auto;
+        min-height: 30px;
         // max-height: $MAX_ROW_HEIGHT;
         // for sub comp
 
@@ -134,6 +137,7 @@
 
     .rt-tr {
         width: 100%;
+        min-height: 30px;
         // height: $MAX_ROW_HEIGHT;
     }
 
@@ -144,6 +148,7 @@
         padding: 0;
         font-size: 13px;
         text-align: center;
+        white-space: normal;
 
         .imageBox-cont {
             position: relative;
@@ -190,7 +195,9 @@
 
 .collectionSchemaView-header {
     height: 100%;
-    color: $intermediate-color;
+    color: gray;
+    letter-spacing: 2px;
+    text-transform: uppercase;
 
     .collectionSchema-header-menu {
         height: 100%;
@@ -204,9 +211,18 @@
                 margin-right: 4px;
             }
         }
+
+        div[class*="css"] {
+            width: 100%;
+            height: 100%;
+        }
     }
 }
 
+button.add-column {
+    width: 28px;
+}
+
 .collectionSchema-header-menuOptions {
     color: black;
     width: 175px;
@@ -265,6 +281,7 @@
 
 .collectionSchema-row {
     // height: $MAX_ROW_HEIGHT;
+    height: 100%;
 
     &.row-focused {
         background-color: $light-color-secondary;
@@ -317,7 +334,7 @@
     p {
         width: 100%;
         height: 100%;
-        word-wrap: break-word;
+        // word-wrap: break-word;
     }
 }
 
@@ -337,5 +354,9 @@
 
 .sub {
     padding: 20px;
-    background-color: red;
+    background-color: $intermediate-color;
+}
+
+.collectionSchemaView-expander {
+    height: 100%;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index febf95dc7..0e2d37a9e 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -1,6 +1,6 @@
 import React = require("react");
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
+import { faCog, faPlus, faSortUp, faSortDown } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable, trace, untracked } from "mobx";
 import { observer } from "mobx-react";
@@ -40,8 +40,7 @@ import { ImageBox } from "../nodes/ImageBox";
 import { ComputedField } from "../../../new_fields/ScriptField";
 
 
-library.add(faCog);
-library.add(faPlus);
+library.add(faCog, faPlus, faSortUp, faSortDown);
 // bcz: need to add drag and drop of rows and columns.  This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
 
 export enum ColumnType {
@@ -67,7 +66,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
 
     @observable previewScript: string = "";
     @observable previewDoc: Doc | undefined = this.childDocs.length ? this.childDocs[0] : undefined;
-    @observable private _node : HTMLDivElement | null = null;
+    @observable private _node: HTMLDivElement | null = null;
     @observable private _focusedTable: Doc = this.props.Document;
 
     @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); }
@@ -192,7 +191,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     @computed
     get schemaTable() {
         return (
-            <SchemaTable 
+            <SchemaTable
                 Document={this.props.Document} // child doc
                 PanelHeight={this.props.PanelHeight}
                 PanelWidth={this.props.PanelWidth}
@@ -253,7 +252,7 @@ export interface SchemaTableProps {
     ScreenToLocalTransform: () => Transform;
     // CreateDropTarget: (ele: HTMLDivElement)=> void; // super createdriotarget
     active: () => boolean;
-    onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined)=> void;
+    onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
     isSelected: () => boolean;
     isFocused: (document: Doc) => boolean;
@@ -267,16 +266,16 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
 
     @observable _headerIsEditing: boolean = false;
     @observable _cellIsEditing: boolean = false;
-    @observable _focusedCell: {row: number, col: number} = {row: 0, col: 0};
-    @observable _sortedColumns: Map<string, {id: string, desc: boolean}> = new Map();
+    @observable _focusedCell: { row: number, col: number } = { row: 0, col: 0 };
+    @observable _sortedColumns: Map<string, { id: string, desc: boolean }> = new Map();
     @observable _openCollections: Array<Doc> = [];
-    @observable private _node : HTMLDivElement | null = null;
+    @observable private _node: HTMLDivElement | null = null;
 
     @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(); }
     @computed get columns() { return Cast(this.props.Document.schemaColumns, listSpec("string"), []); }
-    set columns(columns: string[]) {this.props.Document.schemaColumns = new List<string>(columns); }
+    set columns(columns: string[]) { this.props.Document.schemaColumns = new List<string>(columns); }
     @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
     @computed get tableColumns(): Column<Doc>[] {
         let possibleKeys = this.documentKeys.filter(key => this.columns.findIndex(existingKey => existingKey.toUpperCase() === key.toUpperCase()) === -1);
@@ -294,8 +293,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
                     width: 45,
                     Expander: (rowInfo) => {
                         if (rowInfo.original.type === "collection") {
-                            if (rowInfo.isExpanded) return <div onClick={() => this.onCloseCollection(rowInfo.original)}>-</div>;
-                            if (!rowInfo.isExpanded) return <div onClick={() => this.onExpandCollection(rowInfo.original)}>+</div>;
+                            if (rowInfo.isExpanded) return <div className="collectionSchemaView-expander" onClick={() => this.onCloseCollection(rowInfo.original)}><FontAwesomeIcon icon={"sort-up"} size="sm" /></div>;
+                            if (!rowInfo.isExpanded) return <div className="collectionSchemaView-expander" onClick={() => this.onExpandCollection(rowInfo.original)}><FontAwesomeIcon icon={"sort-down"} size="sm" /></div>;
                         } else {
                             return null;
                         }
@@ -303,10 +302,10 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
                 }
             );
         }
-        
+
         let cols = this.columns.map(col => {
 
-            let header = <CollectionSchemaHeader 
+            let header = <CollectionSchemaHeader
                 keyValue={col}
                 possibleKeys={possibleKeys}
                 existingKeys={this.columns}
@@ -339,7 +338,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
                         ContainingCollection: this.props.ContainingCollectionView,
                         Document: this.props.Document,
                         fieldKey: this.props.fieldKey,
-                        renderDepth: this.props.renderDepth, 
+                        renderDepth: this.props.renderDepth,
                         addDocTab: this.props.addDocTab,
                         moveDocument: this.props.moveDocument,
                         setIsEditing: this.setCellIsEditing,
@@ -347,11 +346,11 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
                     };
 
                     let colType = this.getColumnType(col);
-                    if (colType === ColumnType.Number) return <CollectionSchemaNumberCell {...props}/>;
-                    if (colType === ColumnType.String) return <CollectionSchemaStringCell {...props}/>;
+                    if (colType === ColumnType.Number) return <CollectionSchemaNumberCell {...props} />;
+                    if (colType === ColumnType.String) return <CollectionSchemaStringCell {...props} />;
                     if (colType === ColumnType.Boolean) return <CollectionSchemaCheckboxCell {...props} />;
                     if (colType === ColumnType.Doc) return <CollectionSchemaDocCell {...props} />;
-                    return <CollectionSchemaCell {...props}/>;
+                    return <CollectionSchemaCell {...props} />;
                 },
                 minWidth: 200,
             };
@@ -363,7 +362,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
             accessor: (doc: Doc) => 0,
             id: "add",
             Cell: (rowProps: CellInfo) => <></>,
-            width: 45,
+            width: 28,
             resizable: false
         });
         return columns;
@@ -397,7 +396,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
     }
 
     tableMoveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => {
-        console.log("SCHEMA MOVE"); 
+        console.log("SCHEMA MOVE");
         this.props.moveDocument(d, target, addDoc);
     }
 
@@ -410,7 +409,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
             ScreenToLocalTransform: this.props.ScreenToLocalTransform,
             addDoc: this.tableAddDoc,
             moveDoc: this.tableMoveDoc,
-            rowInfo, 
+            rowInfo,
             rowFocused: !this._headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document)
         };
     }
@@ -505,7 +504,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
     }
 
     @action
-    createColumn = () => {  
+    createColumn = () => {
         let index = 0;
         let found = this.columns.findIndex(col => col.toUpperCase() === "New field".toUpperCase()) > -1;
         if (!found) {
@@ -513,7 +512,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
             return;
         }
         while (found) {
-            index ++;
+            index++;
             found = this.columns.findIndex(col => col.toUpperCase() === ("New field (" + index + ")").toUpperCase()) > -1;
         }
         this.columns.push("New field (" + index + ")");
@@ -562,7 +561,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
         if (!typesDoc) {
             let newTypesDoc = new Doc();
             newTypesDoc[key] = type;
-            this.props.Document.schemaColumnTypes  = newTypesDoc;
+            this.props.Document.schemaColumnTypes = newTypesDoc;
             return;
         } else {
             typesDoc[key] = type;
@@ -588,13 +587,13 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
 
     @action
     setColumnSort = (column: string, descending: boolean) => {
-        this._sortedColumns.set(column, {id: column, desc: descending});
+        this._sortedColumns.set(column, { id: column, desc: descending });
     }
 
     @action
     removeColumnSort = (column: string) => {
         this._sortedColumns.delete(column);
-    } 
+    }
 
     get documentKeys() {
         const docs = DocListCast(this.props.Document[this.props.fieldKey]);
@@ -619,27 +618,27 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
         let expanded = {};
         expandedRowsList.forEach(row => expanded[row] = true);
 
-        return <ReactTable 
-            style={{ position: "relative", float: "left", width: `calc(100% - ${previewWidth}px` }} 
-            data={this.props.childDocs} 
-            page={0} 
-            pageSize={this.props.childDocs.length} 
+        return <ReactTable
+            style={{ position: "relative", float: "left", width: `calc(100% - ${previewWidth}px` }}
+            data={this.props.childDocs}
+            page={0}
+            pageSize={this.props.childDocs.length}
             showPagination={false}
             columns={this.tableColumns}
             getTrProps={this.getTrProps}
             sortable={false}
             TrComponent={MovableRow}
-            sorted={Array.from(this._sortedColumns.values())}    
-            expanded={expanded}       
-            SubComponent={hasCollectionChild ? 
+            sorted={Array.from(this._sortedColumns.values())}
+            expanded={expanded}
+            SubComponent={hasCollectionChild ?
                 row => {
-                    if (row.original.type === "collection") { 
+                    if (row.original.type === "collection") {
                         let childDocs = DocListCast(row.original[this.props.fieldKey]);
-                        return <div className="sub"><SchemaTable {...this.props} Document={row.original} childDocs={childDocs}/></div>;
+                        return <div className="sub"><SchemaTable {...this.props} Document={row.original} childDocs={childDocs} /></div>;
                     }
                 }
-             : undefined}
-            
+                : undefined}
+
         />;
     }
 
@@ -685,7 +684,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
 
 interface CollectionSchemaPreviewProps {
     Document?: Doc;
-    DataDocument?: Doc; 
+    DataDocument?: Doc;
     childDocs?: Doc[];
     renderDepth: number;
     fitToBox?: boolean;
@@ -762,7 +761,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
         return undefined;
     }
 
-    
+
     render() {
         let input = this.props.previewScript === undefined ? (null) :
             <div ref={this.createTarget}><input className="collectionSchemaView-input" value={this.props.previewScript} onChange={this.onPreviewScriptChange}
-- 
cgit v1.2.3-70-g09d2


From 828fdd80ab8af26f6e2ef521e05a71e7e500bf8c Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Wed, 24 Jul 2019 17:18:15 -0400
Subject: view specsss

---
 package.json                                       |   1 +
 src/client/views/EditableView.tsx                  |   2 +-
 src/client/views/MainView.tsx                      |   4 +-
 .../views/collections/CollectionStackingView.tsx   |   9 +-
 .../CollectionStackingViewFieldColumn.tsx          |  44 +++-
 src/client/views/collections/CollectionSubView.tsx |  15 +-
 .../views/collections/CollectionViewChromes.scss   | 113 ++++++++-
 .../views/collections/CollectionViewChromes.tsx    | 261 ++++++++++++++++-----
 src/client/views/collections/KeyRestrictionRow.tsx |  45 ++++
 9 files changed, 428 insertions(+), 66 deletions(-)
 create mode 100644 src/client/views/collections/KeyRestrictionRow.tsx

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

diff --git a/package.json b/package.json
index 4a15cbb2f..37052fde3 100644
--- a/package.json
+++ b/package.json
@@ -136,6 +136,7 @@
         "image-size": "^0.7.4",
         "imagesloaded": "^4.1.4",
         "jquery-awesome-cursor": "^0.3.1",
+        "js-datepicker": "^4.6.6",
         "jsonwebtoken": "^8.5.0",
         "jsx-to-string": "^1.4.0",
         "lodash": "^4.17.11",
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index a5bb40243..bd00d47b9 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -116,7 +116,7 @@ export class EditableView extends React.Component<EditableProps> {
             return (
                 <div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`}
                     style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
-                    onClick={this.onClick} >
+                    onClick={this.onClick}>
                     <span style={{ fontStyle: this.props.fontStyle }}>{this.props.contents}</span>
                 </div>
             );
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 94a4835a1..69602deed 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,5 +1,5 @@
 import { IconName, library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowDown, faCloudUploadAlt, faArrowUp, faClone, faCheck, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faPortrait, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt, faCat } from '@fortawesome/free-solid-svg-icons';
+import { faArrowDown, faCaretUp, faLongArrowAltRight, faCloudUploadAlt, faArrowUp, faClone, faCheck, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faPortrait, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt, faCat } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, runInAction, reaction, trace } from 'mobx';
 import { observer } from 'mobx-react';
@@ -127,7 +127,9 @@ export class MainView extends React.Component {
         library.add(faCut);
         library.add(faCommentAlt);
         library.add(faThumbtack);
+        library.add(faLongArrowAltRight);
         library.add(faCheck);
+        library.add(faCaretUp);
         library.add(faArrowDown);
         library.add(faArrowUp);
         library.add(faCloudUploadAlt);
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index e4e6f92e3..a78a47ffb 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -249,6 +249,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return false;
     }
 
+    sortFunc = (a: [SchemaHeaderField, Doc[]], b: [SchemaHeaderField, Doc[]]): 1 | -1 => {
+        let descending = BoolCast(this.props.Document.stackingHeadersSortDescending);
+        let firstEntry = descending ? b : a;
+        let secondEntry = descending ? a : b;
+        return firstEntry[0].heading > secondEntry[0].heading ? 1 : -1;
+    }
+
     render() {
         let headings = Array.from(this.Sections.keys());
         let editableViewProps = {
@@ -264,7 +271,7 @@ 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()).sort((a, b) => a[0].toString() > b[0].toString() ? 1 : -1).
+                {this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc).
                     map(section => this.section(section[0], section[1] as Doc[])) :
                     this.section(undefined, this.filteredChildren)}
                 {this.props.Document.sectionFilter ?
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index fe24c63c7..582adc418 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -16,6 +16,8 @@ import "./CollectionStackingView.scss";
 import { Docs } from "../../documents/Documents";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { CompileScript } from "../../util/Scripting";
 
 
 interface CSVFieldColumnProps {
@@ -35,6 +37,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
 
     private _dropRef: HTMLDivElement | null = null;
     private dropDisposer?: DragManager.DragDropDisposer;
+    private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();
 
     @observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading;
 
@@ -182,6 +185,43 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         }
     }
 
+    startDrag = (e: PointerEvent) => {
+        let alias = Doc.MakeAlias(this.props.parent.props.Document);
+        let key = StrCast(this.props.parent.props.Document.sectionFilter);
+        let value = this.getValue(this._heading);
+        value = typeof value === "string" ? `"${value}"` : value;
+        let script = `return doc.${key} === ${value}`;
+        let compiled = CompileScript(script, { params: { doc: Doc.name } });
+        if (compiled.compiled) {
+            let scriptField = new ScriptField(compiled);
+            alias.viewSpecScript = scriptField;
+            let dragData = new DragManager.DocumentDragData([alias], [alias.proto]);
+            DragManager.StartDocumentDrag([this._headerRef.current!], dragData, e.clientX, e.clientY);
+        }
+
+        e.stopPropagation();
+        document.removeEventListener("pointermove", this.startDrag);
+        document.removeEventListener("pointerup", this.pointerUp);
+    }
+
+    pointerUp = (e: PointerEvent) => {
+        e.stopPropagation();
+        e.preventDefault();
+
+        document.removeEventListener("pointermove", this.startDrag);
+        document.removeEventListener("pointerup", this.pointerUp);
+    }
+
+    headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
+        e.stopPropagation();
+        e.preventDefault();
+
+        document.removeEventListener("pointermove", this.startDrag);
+        document.addEventListener("pointermove", this.startDrag);
+        document.removeEventListener("pointerup", this.pointerUp);
+        document.addEventListener("pointerup", this.pointerUp);
+    }
+
     render() {
         let cols = this.props.cols();
         let key = StrCast(this.props.parent.props.Document.sectionFilter);
@@ -199,11 +239,11 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
             oneLine: true
         }
         let headingView = this.props.headingObject ?
-            <div key={heading} className="collectionStackingView-sectionHeader"
+            <div key={heading} className="collectionStackingView-sectionHeader" ref={this._headerRef}
                 style={{ width: (style.columnWidth) / (uniqueHeadings.length + 1) }}>
                 {/* 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"
+                <div className="collectionStackingView-sectionHeader-subCont" onPointerDown={this.headerDown}
                     title={evContents === `No ${key} value` ?
                         `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""}
                     style={{
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 2ddefb3c0..24c8adb35 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -21,6 +21,8 @@ import { CollectionView } from "./CollectionView";
 import React = require("react");
 import { MainView } from "../MainView";
 import { Utils } from "../../../Utils";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { CompileScript } from "../../util/Scripting";
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -54,7 +56,18 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             let self = this;
             //TODO tfs: This might not be what we want?
             //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue)
-            return DocListCast(this.extensionDoc[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey]);
+            let docs = DocListCast(this.extensionDoc[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey]);
+            let viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
+            if (viewSpecScript) {
+                let script = viewSpecScript.script;
+                docs = docs.filter(d => {
+                    let res = script.run({ doc: d });
+                    if (res.success) {
+                        return res.result;
+                    }
+                });
+            }
+            return docs;
         }
         get childDocList() {
             //TODO tfs: This might not be what we want?
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index e5cb1b546..3103cd309 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -1,12 +1,120 @@
 @import "../globalCssVariables";
+@import '~js-datepicker/dist/datepicker.min.css';
+
+.collectionViewBaseChrome {
+    display: flex;
+
+    .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;
+    }
+
+    .collectionViewBaseChrome-viewPicker:active {
+        outline-color: black;
+    }
+
+    .collectionViewBaseChrome-collapse {
+        margin-right: 10px;
+        transition: all .5s;
+    }
+
+    .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 {
+            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-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 {
     display: grid;
-    grid-template-columns: 1fr 1fr;
+    grid-template-columns: 1fr auto;
     padding-bottom: 10px;
     border-bottom: .5px solid lightgrey;
     margin: 10px;
 
+    .collectionStackingViewChrome-cont {
+        display: flex;
+        justify-content: space-between;
+    }
+
+    .collectionStackingViewChrome-sort {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+
+        .collectionStackingViewChrome-sortIcon {
+            transition: transform .5s;
+            margin-left: 10px;
+        }
+    }
+
+    button:hover {
+        transform: scale(1);
+    }
+
+
     .collectionStackingViewChrome-sectionFilter-cont {
         justify-self: right;
         display: flex;
@@ -21,10 +129,9 @@
 
         .collectionStackingViewChrome-sectionFilter {
             color: white;
-            width: fit-content;
+            width: 100px;
             text-align: center;
             background: rgb(238, 238, 238);
-            height: 37px;
 
             .editable-view-input,
             input,
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 52fee26bf..2edac384d 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -3,55 +3,189 @@ import { CollectionView } from "./CollectionView";
 import "./CollectionViewChromes.scss";
 import { CollectionViewType } from "./CollectionBaseView";
 import { undoBatch } from "../../util/UndoManager";
-import { action, observable, runInAction, computed } from "mobx";
+import { action, observable, runInAction, computed, IObservable, IObservableValue } 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";
+import { StrCast, NumCast, BoolCast, Cast } from "../../../new_fields/Types";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Utils } from "../../../Utils";
+import KeyRestrictionRow from "./KeyRestrictionRow";
+import { CompileScript } from "../../util/Scripting";
+import { ScriptField } from "../../../new_fields/ScriptField";
+const datepicker = require('js-datepicker');
 
 interface CollectionViewChromeProps {
     CollectionView: CollectionView;
 }
 
+let stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
+
+@observer
 class CollectionViewBaseChrome extends React.Component<CollectionViewChromeProps> {
+    @observable private _viewSpecsOpen: boolean = false;
+    @observable private _dateWithinValue: string = "";
+    @observable private _dateValue: Date = new Date();
+    @observable private _keyRestrictions: [JSX.Element, string][] = [];
+    @computed private get filterValue() { return Cast(this.props.CollectionView.props.Document.viewSpecScript, ScriptField); }
+
+    private _picker: any;
+    private _datePickerElGuid = Utils.GenerateGuid();
+
+    componentDidMount = () => {
+        this._picker = datepicker("#" + this._datePickerElGuid, {
+            disabler: (date: Date) => date > new Date(),
+            onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date),
+            dateSelected: new Date()
+        });
+
+        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)} />, ""]);
+        });
+    }
+
     @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;
+        this.props.CollectionView.props.Document.viewType = parseInt(e.target.selectedOptions[0].value);
+    }
+
+    @action
+    openViewSpecs = (e: React.SyntheticEvent) => {
+        this._viewSpecsOpen = true;
+
+        //@ts-ignore
+        if (!e.target.classList[0].startsWith("qs")) {
+            this.closeDatePicker();
+        }
+
+        e.stopPropagation();
+        document.removeEventListener("pointerdown", this.closeViewSpecs);
+        document.addEventListener("pointerdown", this.closeViewSpecs);
+    }
+
+    @action closeViewSpecs = () => { this._viewSpecsOpen = false; document.removeEventListener("pointerdown", this.closeViewSpecs); };
+
+    @action
+    openDatePicker = (e: React.PointerEvent) => {
+        this.openViewSpecs(e);
+        if (this._picker) {
+            this._picker.alwaysShow = true;
+            this._picker.show();
+            // TODO: calendar is offset when zoomed in/out
+            // this._picker.calendar.style.position = "absolute";
+            // let transform = this.props.CollectionView.props.ScreenToLocalTransform();
+            // let x = parseInt(this._picker.calendar.style.left) / transform.Scale;
+            // let y = parseInt(this._picker.calendar.style.top) / transform.Scale;
+            // this._picker.calendar.style.left = x;
+            // this._picker.calendar.style.top = y;
+
+            e.stopPropagation();
         }
     }
 
+    @action
+    addKeyRestriction = (e: React.MouseEvent) => {
+        let index = this._keyRestrictions.length;
+        this._keyRestrictions.push([<KeyRestrictionRow key={Utils.GenerateGuid()} contains={true} script={(value: string) => runInAction(() => this._keyRestrictions[index][1] = value)} />, ""]);
+
+        this.openViewSpecs(e);
+    }
+
+    @action
+    applyFilter = (e: React.MouseEvent) => {
+        this.openViewSpecs(e);
+
+        let keyRestrictionScript = `${this._keyRestrictions.map(i => i[1])
+            .reduce((acc: string, value: string, i: number) => value ? `${acc} && ${value}` : acc)}`;
+        let yearOffset = this._dateWithinValue[1] === 'y' ? 1 : 0;
+        let monthOffset = this._dateWithinValue[1] === 'm' ? parseInt(this._dateWithinValue[0]) : 0;
+        let weekOffset = this._dateWithinValue[1] === 'w' ? parseInt(this._dateWithinValue[0]) : 0;
+        let dayOffset = (this._dateWithinValue[1] === 'd' ? parseInt(this._dateWithinValue[0]) : 0) + weekOffset * 7;
+        let lowerBound = new Date(this._dateValue.getFullYear() - yearOffset, this._dateValue.getMonth() - monthOffset, this._dateValue.getDate() - dayOffset);
+        let upperBound = new Date(this._dateValue.getFullYear() + yearOffset, this._dateValue.getMonth() + monthOffset, this._dateValue.getDate() + dayOffset + 1);
+        let dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`;
+        let fullScript = `return ${dateRestrictionScript} && ${keyRestrictionScript}`;
+        let compiled = CompileScript(fullScript, { params: { doc: Doc.name } });
+        if (compiled.compiled) {
+            this.props.CollectionView.props.Document.viewSpecScript = new ScriptField(compiled);
+        }
+    }
+
+    @action
+    closeDatePicker = () => {
+        if (this._picker) {
+            this._picker.alwaysShow = false;
+            this._picker.hide();
+        }
+        document.removeEventListener("pointerdown", this.closeDatePicker);
+    }
+
     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>
+                <button className="collectionViewBaseChrome-collapse" title="Collapse collection chrome">
+                    <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>
+                        <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>
+                        </div>
+                    </div>
+                </div>
             </div>
         )
     }
@@ -62,6 +196,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
     @observable private _currentKey: string = "";
     @observable private suggestions: string[] = [];
 
+    @computed private get descending() { return BoolCast(this.props.CollectionView.props.Document.stackingHeadersSortDescending); }
     @computed get sectionFilter() { return StrCast(this.props.CollectionView.props.Document.sectionFilter); }
 
     getKeySuggestions = async (value: string): Promise<string[]> => {
@@ -109,41 +244,53 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
         return true;
     }
 
+    @action toggleSort = () => { this.props.CollectionView.props.Document.stackingHeadersSortDescending = !this.props.CollectionView.props.Document.stackingHeadersSortDescending; }
     @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 className="collectionStackingViewChrome-cont">
+                    <button className="collectionStackingViewChrome-sort" onClick={this.toggleSort}>
+                        <div className="collectionStackingViewChrome-sortLabel">
+                            Sort
+                        </div>
+                        <div className="collectionStackingViewChrome-sortIcon" style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}>
+                            <FontAwesomeIcon icon="caret-up" size="2x" color="white" />
+                        </div>
+                    </button>
+                    <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
+                                        }
+                                    }}
+                                oneLine
+                                SetValue={this.setValue}
+                                contents={this.sectionFilter ? this.sectionFilter : "N/A"}
+                            />
+                        </div>
                     </div>
                 </div>
             </div>
diff --git a/src/client/views/collections/KeyRestrictionRow.tsx b/src/client/views/collections/KeyRestrictionRow.tsx
new file mode 100644
index 000000000..8051a8359
--- /dev/null
+++ b/src/client/views/collections/KeyRestrictionRow.tsx
@@ -0,0 +1,45 @@
+import * as React from "react";
+import { observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { PastelSchemaPalette } from "../../../new_fields/SchemaHeaderField";
+
+interface IKeyRestrictionProps {
+    contains: boolean;
+    script: (value: string) => void;
+}
+
+@observer
+export default class KeyRestrictionRow extends React.Component<IKeyRestrictionProps> {
+    @observable private _key = "";
+    @observable private _value = "";
+    @observable private _contains = this.props.contains;
+
+    render() {
+        if (this._key && this._value) {
+            let parsedValue: string | number = `"${this._value}"`;
+            let parsed = parseInt(this._value);
+            if (!isNaN(parsed)) {
+                parsedValue = parsed;
+            }
+            let scriptText = `(doc.${this._key} ${this._contains ? "===" : "!=="} ${parsedValue})`;
+            this.props.script(scriptText);
+        }
+        return (
+            <div className="collectionViewBaseChrome-viewSpecsMenu-row">
+                <input className="collectionViewBaseChrome-viewSpecsMenu-rowLeft"
+                    value={this._key}
+                    onChange={(e) => runInAction(() => this._key = e.target.value)}
+                    placeholder="KEY" />
+                <button className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
+                    style={{ background: PastelSchemaPalette.get(this._contains ? "green" : "red") }}
+                    onClick={() => runInAction(() => this._contains = !this._contains)}>
+                    {this._contains ? "CONTAINS" : "DOES NOT CONTAIN"}
+                </button>
+                <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
+                    value={this._value}
+                    onChange={(e) => runInAction(() => this._value = e.target.value)}
+                    placeholder="VALUE" />
+            </div>
+        )
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From c360dc0adb468ae3aaa1c2d943606993d01a5a52 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 27 Jul 2019 11:08:44 -0400
Subject: fixed handling of keyboard events to avoid triggering global key
 events. made BoolCast default to false. fixed templating to deal with
 conflict between field fields and template fields

---
 src/client/util/DocumentManager.ts                 | 16 +++++++--------
 src/client/views/EditableView.tsx                  |  3 +++
 src/client/views/MainView.tsx                      |  1 -
 src/client/views/MetadataEntryMenu.tsx             |  1 +
 .../views/collections/CollectionSchemaView.tsx     |  2 +-
 .../views/collections/CollectionStackingView.tsx   |  3 ++-
 .../CollectionFreeFormLinkView.tsx                 |  8 ++++----
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  8 ++++----
 src/client/views/nodes/FormattedTextBox.tsx        |  4 ++--
 src/client/views/nodes/IconBox.tsx                 |  2 +-
 src/client/views/nodes/KeyValueBox.tsx             |  3 ++-
 src/client/views/nodes/LinkMenuItem.tsx            |  2 +-
 src/client/views/pdf/Page.tsx                      | 22 ++++++++------------
 .../views/presentationview/PresentationElement.tsx | 24 ++++++++++------------
 .../views/presentationview/PresentationView.tsx    |  2 +-
 src/new_fields/Doc.ts                              | 19 +++++++++--------
 src/new_fields/Types.ts                            |  2 +-
 18 files changed, 62 insertions(+), 62 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 262194a40..32f728c71 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,16 +1,14 @@
-import { computed, observable, action } from 'mobx';
-import { DocumentView } from '../views/nodes/DocumentView';
-import { Doc, DocListCast, Opt } from '../../new_fields/Doc';
-import { FieldValue, Cast, NumCast, BoolCast, StrCast } from '../../new_fields/Types';
-import { listSpec } from '../../new_fields/Schema';
-import { undoBatch, UndoManager } from './UndoManager';
+import { action, computed, observable } from 'mobx';
+import { Doc } from '../../new_fields/Doc';
+import { Id } from '../../new_fields/FieldSymbols';
+import { BoolCast, Cast, NumCast } from '../../new_fields/Types';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { CollectionView } from '../views/collections/CollectionView';
 import { CollectionPDFView } from '../views/collections/CollectionPDFView';
 import { CollectionVideoView } from '../views/collections/CollectionVideoView';
-import { Id } from '../../new_fields/FieldSymbols';
+import { CollectionView } from '../views/collections/CollectionView';
+import { DocumentView } from '../views/nodes/DocumentView';
 import { LinkManager } from './LinkManager';
-import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
+import { undoBatch, UndoManager } from './UndoManager';
 
 
 export class DocumentManager {
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index c66a92f48..7cabebddd 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -50,8 +50,10 @@ export class EditableView extends React.Component<EditableProps> {
     @action
     onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
         if (e.key === "Tab") {
+            e.stopPropagation();
             this.props.OnTab && this.props.OnTab();
         } else if (e.key === "Enter") {
+            e.stopPropagation();
             if (!e.ctrlKey) {
                 if (this.props.SetValue(e.currentTarget.value, e.shiftKey)) {
                     this._editing = false;
@@ -61,6 +63,7 @@ export class EditableView extends React.Component<EditableProps> {
                 this._editing = false;
             }
         } else if (e.key === "Escape") {
+            e.stopPropagation();
             this._editing = false;
         }
     }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index bfb50bc75..8b7eb8856 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -7,7 +7,6 @@ import "normalize.css";
 import * as React from 'react';
 import { SketchPicker } from 'react-color';
 import Measure from 'react-measure';
-import * as request from 'request';
 import { Doc, DocListCast, Opt, HeightSym } from '../../new_fields/Doc';
 import { Id } from '../../new_fields/FieldSymbols';
 import { InkTool } from '../../new_fields/InkField';
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index bd5a307b3..c50e02f0d 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -76,6 +76,7 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
 
     onValueKeyDown = async (e: React.KeyboardEvent) => {
         if (e.key === "Enter") {
+            e.stopPropagation();
             const script = KeyValueBox.CompileKVPScript(this._currentValue);
             if (!script) return;
             let doc = this.props.docs;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 8ce3b40e5..0f67c47aa 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -11,7 +11,7 @@ import { Doc, DocListCast, DocListCastAsync, Field } from "../../../new_fields/D
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
-import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
+import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { Docs } from "../../documents/Documents";
 import { Gateway } from "../../northstar/manager/Gateway";
 import { SetupDrag, DragManager } from "../../util/DragManager";
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 3da13bff5..d5a7b90da 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -53,7 +53,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                         let xhgt = height + this.getDocHeight(d) + (i === this.filteredChildren.length - 1 ? this.yMargin : this.gridGap);
                         return xhgt;
                     }, this.yMargin);
-                    this.layoutDoc.height = hgt * (this.props as any).ContentScaling();
+                    (this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc)
+                        .height = hgt * (this.props as any).ContentScaling();
                 }
             }, { fireImmediately: true });
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index b546d1b78..6af87b138 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -21,10 +21,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         if (e.button === 0 && !InkingControl.Instance.selectedTool) {
             let a = this.props.A;
             let b = this.props.B;
-            let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : a[WidthSym]() / 2);
-            let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : a[HeightSym]() / 2);
-            let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : b[WidthSym]() / 2);
-            let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : b[HeightSym]() / 2);
+            let x1 = NumCast(a.x) + (BoolCast(a.isMinimized) ? 5 : a[WidthSym]() / 2);
+            let y1 = NumCast(a.y) + (BoolCast(a.isMinimized) ? 5 : a[HeightSym]() / 2);
+            let x2 = NumCast(b.x) + (BoolCast(b.isMinimized) ? 5 : b[WidthSym]() / 2);
+            let y2 = NumCast(b.y) + (BoolCast(b.isMinimized) ? 5 : b[HeightSym]() / 2);
             // this.props.LinkDocs.map(l => {
             //     let width = l[WidthSym]();
             //     l.x = (x1 + x2) / 2 - width / 2;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f83d02271..eaad43c10 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -471,7 +471,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             if (!(doc instanceof Doc)) return prev;
             var page = NumCast(doc.page, -1);
             if ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1) {
-                let minim = BoolCast(doc.isMinimized, false);
+                let minim = BoolCast(doc.isMinimized);
                 if (minim === undefined || !minim) {
                     const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) : {};
                     state = pos.state === undefined ? state : pos.state;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3152d6a2a..bc2af2ae8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -275,7 +275,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
                 if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) {
                     if (isMinimized === undefined) {
-                        isMinimized = BoolCast(maximizedDoc.isMinimized, false);
+                        isMinimized = BoolCast(maximizedDoc.isMinimized);
                     }
                     maximizedDoc.willMaximize = isMinimized;
                     maximizedDoc.isMinimized = false;
@@ -308,7 +308,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
             SelectionManager.SelectDoc(this, e.ctrlKey);
             let isExpander = (e.target as any).id === "isExpander";
-            if (BoolCast(this.props.Document.isButton, false) || isExpander) {
+            if (BoolCast(this.props.Document.isButton) || isExpander) {
                 SelectionManager.DeselectAll();
                 let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs);
                 let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs);
@@ -326,7 +326,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         maxLocation = this.props.Document.maximizeLocation = (ctrlKey ? maxLocation : (maxLocation === "inPlace" || !maxLocation ? "inTab" : "inPlace"));
                         if (!maxLocation || maxLocation === "inPlace") {
                             let hadView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedDocs[0], this.props.ContainingCollectionView);
-                            let wasMinimized = !hadView && expandedDocs.reduce((min, d) => !min && !BoolCast(d.IsMinimized, false), false);
+                            let wasMinimized = !hadView && expandedDocs.reduce((min, d) => !min && !BoolCast(d.IsMinimized), false);
                             expandedDocs.forEach(maxDoc => Doc.GetProto(maxDoc).isMinimized = false);
                             let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedDocs[0], this.props.ContainingCollectionView);
                             if (!hasView) {
@@ -409,7 +409,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @undoBatch
     makeBtnClicked = (): void => {
         let doc = Doc.GetProto(this.props.Document);
-        doc.isButton = !BoolCast(doc.isButton, false);
+        doc.isButton = !BoolCast(doc.isButton);
         if (doc.isButton) {
             if (!doc.nativeWidth) {
                 doc.nativeWidth = this.props.Document[WidthSym]();
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 0a79677e2..bd0fd8301 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -459,8 +459,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     specificContextMenu = (e: React.MouseEvent): void => {
         let subitems: ContextMenuProps[] = [];
         subitems.push({
-            description: BoolCast(this.props.Document.autoHeight, false) ? "Manual Height" : "Auto Height",
-            event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight, false)), icon: "expand-arrows-alt"
+            description: BoolCast(this.props.Document.autoHeight) ? "Manual Height" : "Auto Height",
+            event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight)), icon: "expand-arrows-alt"
         });
         ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems });
     }
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index d6ab2a34a..6f50033a4 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -64,7 +64,7 @@ export class IconBox extends React.Component<FieldViewProps> {
         let hideLabel = BoolCast(this.props.Document.hideLabel);
         let maxDocs = DocListCast(this.props.Document.maximizedDocs);
         let firstDoc = maxDocs.length ? maxDocs[0] : undefined;
-        let label = hideLabel ? "" : (firstDoc && labelField && !BoolCast(this.props.Document.useOwnTitle, false) ? firstDoc[labelField] : this.props.Document.title);
+        let label = hideLabel ? "" : (firstDoc && labelField && !BoolCast(this.props.Document.useOwnTitle) ? firstDoc[labelField] : this.props.Document.title);
         return (
             <div className="iconBox-container" onContextMenu={this.specificContextMenu}>
                 {this.minimizedIcon}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 9fc0f2080..77824b4ff 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -46,6 +46,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     @action
     onEnterKey = (e: React.KeyboardEvent): void => {
         if (e.key === 'Enter') {
+            e.stopPropagation();
             if (this._keyInput && this._valueInput && this.fieldDocToLayout) {
                 if (KeyValueBox.SetField(this.fieldDocToLayout, this._keyInput, this._valueInput)) {
                     this._keyInput = "";
@@ -153,7 +154,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
                     <input style={{ width: "100%" }} type="text" value={this._keyInput} placeholder="Key" onChange={this.keyChanged} />
                 </td>
                 <td className="keyValueBox-td-value" style={{ width: `${this.splitPercentage}%` }}>
-                    <input style={{ width: "100%" }} type="text" value={this._valueInput} placeholder="Value" onChange={this.valueChanged} onKeyPress={this.onEnterKey} />
+                    <input style={{ width: "100%" }} type="text" value={this._valueInput} placeholder="Value" onChange={this.valueChanged} onKeyDown={this.onEnterKey} />
                 </td>
             </tr>
         )
diff --git a/src/client/views/nodes/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx
index a0c37a719..d4c92c9f2 100644
--- a/src/client/views/nodes/LinkMenuItem.tsx
+++ b/src/client/views/nodes/LinkMenuItem.tsx
@@ -7,7 +7,7 @@ import { undoBatch } from "../../util/UndoManager";
 import './LinkMenu.scss';
 import React = require("react");
 import { Doc } from '../../../new_fields/Doc';
-import { StrCast, Cast, BoolCast, FieldValue, NumCast } from '../../../new_fields/Types';
+import { StrCast, Cast, FieldValue, NumCast } from '../../../new_fields/Types';
 import { observable, action } from 'mobx';
 import { LinkManager } from '../../util/LinkManager';
 import { DragLinkAsDocument } from '../../util/DragManager';
diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx
index c9d442fe5..a6864e0f3 100644
--- a/src/client/views/pdf/Page.tsx
+++ b/src/client/views/pdf/Page.tsx
@@ -1,22 +1,18 @@
+import { action, IReactionDisposer, observable } from "mobx";
 import { observer } from "mobx-react";
-import React = require("react");
-import { observable, action, runInAction, IReactionDisposer, reaction } from "mobx";
 import * as Pdfjs from "pdfjs-dist";
-import { Opt, Doc, FieldResult, Field, DocListCast, WidthSym, HeightSym, DocListCastAsync } from "../../../new_fields/Doc";
-import "./PDFViewer.scss";
 import "pdfjs-dist/web/pdf_viewer.css";
-import { PDFBox } from "../nodes/PDFBox";
-import { DragManager } from "../../util/DragManager";
-import { Docs, DocUtils } from "../../documents/Documents";
+import { Doc, DocListCastAsync, Opt, WidthSym } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
-import { emptyFunction } from "../../../Utils";
-import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
 import { listSpec } from "../../../new_fields/Schema";
-import { menuBar } from "prosemirror-menu";
-import { AnnotationTypes, PDFViewer, scale } from "./PDFViewer";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { Docs, DocUtils } from "../../documents/Documents";
+import { DragManager } from "../../util/DragManager";
+import { PDFBox } from "../nodes/PDFBox";
 import PDFMenu from "./PDFMenu";
-import { UndoManager } from "../../util/UndoManager";
-import { copy } from "typescript-collections/dist/lib/arrays";
+import { scale } from "./PDFViewer";
+import "./PDFViewer.scss";
+import React = require("react");
 
 
 interface IPageProps {
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index 329630875..36f1178f1 100644
--- a/src/client/views/presentationview/PresentationElement.tsx
+++ b/src/client/views/presentationview/PresentationElement.tsx
@@ -1,21 +1,19 @@
-import { observer } from "mobx-react";
-import React = require("react");
-import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
-import { NumCast, BoolCast, StrCast, Cast } from "../../../new_fields/Types";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { observable, action, computed, runInAction } from "mobx";
-import "./PresentationView.scss";
-import { Utils } from "../../../Utils";
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faFile as fileSolid, faFileDownload, faLocationArrow, faArrowUp, faSearch } from '@fortawesome/free-solid-svg-icons';
 import { faFile as fileRegular } from '@fortawesome/free-regular-svg-icons';
+import { faArrowUp, faFile as fileSolid, faFileDownload, faLocationArrow, faSearch } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { Doc } from "../../../new_fields/Doc";
+import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
-import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { Utils } from "../../../Utils";
+import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
 import { SelectionManager } from "../../util/SelectionManager";
-import { indexOf } from "typescript-collections/dist/lib/arrays";
-import { map } from "bluebird";
+import "./PresentationView.scss";
+import React = require("react");
 
 library.add(faArrowUp);
 library.add(fileSolid);
diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx
index f2fef7f16..e25725275 100644
--- a/src/client/views/presentationview/PresentationView.tsx
+++ b/src/client/views/presentationview/PresentationView.tsx
@@ -171,7 +171,7 @@ export class PresentationView extends React.Component<PresViewProps>  {
 
 
         //storing the presentation status,ie. whether it was stopped or playing
-        let presStatusBackUp = BoolCast(this.curPresentation.presStatus, null);
+        let presStatusBackUp = BoolCast(this.curPresentation.presStatus);
         runInAction(() => this.presStatus = presStatusBackUp);
     }
 
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index dbb0dc505..b5708e97b 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -455,7 +455,7 @@ export namespace Doc {
         return otherdoc;
     }
 
-    export function MakeTemplate(fieldTemplate: Doc, metaKey: string, proto: Doc) {
+    export function MakeTemplate(fieldTemplate: Doc, metaKey: string, templateDataDoc: Doc) {
         // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
         let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
         let fieldLayoutDoc = fieldTemplate;
@@ -466,21 +466,24 @@ export namespace Doc {
         if (backgroundLayout) {
             backgroundLayout = backgroundLayout.replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
         }
-        let nw = Cast(fieldTemplate.nativeWidth, "number");
-        let nh = Cast(fieldTemplate.nativeHeight, "number");
 
         let layoutDelegate = fieldTemplate.layout instanceof Doc ? fieldLayoutDoc : fieldTemplate;
         layoutDelegate.layout = layout;
 
-        fieldTemplate.title = metaKey;
         fieldTemplate.templateField = metaKey;
+        fieldTemplate.title = metaKey;
+        fieldTemplate.isTemplate = true;
         fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout;
         fieldTemplate.backgroundLayout = backgroundLayout;
-        fieldTemplate.nativeWidth = nw;
-        fieldTemplate.nativeHeight = nh;
-        fieldTemplate.isTemplate = true;
+        /* move certain layout properties from the original data doc to the template layout to avoid
+           inheriting them from the template's data doc which may also define these fields for its own use.
+        */
+        fieldTemplate.ignoreAspect = BoolCast(fieldTemplate.ignoreAspect);
+        fieldTemplate.singleColumn = BoolCast(fieldTemplate.singleColumn);
+        fieldTemplate.nativeWidth = Cast(fieldTemplate.nativeWidth, "number");
+        fieldTemplate.nativeHeight = Cast(fieldTemplate.nativeHeight, "number");
         fieldTemplate.showTitle = "title";
-        setTimeout(() => fieldTemplate.proto = proto);
+        setTimeout(() => fieldTemplate.proto = templateDataDoc);
     }
 
     export async function ToggleDetailLayout(d: Doc) {
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
index f8a4a30b4..565ae2ee3 100644
--- a/src/new_fields/Types.ts
+++ b/src/new_fields/Types.ts
@@ -78,7 +78,7 @@ export function StrCast(field: FieldResult, defaultVal: string | null = "") {
     return Cast(field, "string", defaultVal);
 }
 
-export function BoolCast(field: FieldResult, defaultVal: boolean | null = null) {
+export function BoolCast(field: FieldResult, defaultVal: boolean | null = false) {
     return Cast(field, "boolean", defaultVal);
 }
 export function DateCast(field: FieldResult) {
-- 
cgit v1.2.3-70-g09d2


From 8f4e803171bbf355e584fad6daa40d498253087f Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Sat, 27 Jul 2019 22:27:23 -0400
Subject: can script in any schema cell

---
 src/client/views/EditableView.tsx                  |  2 +-
 .../views/collections/CollectionSchemaCells.tsx    | 26 ++++---
 .../views/collections/CollectionSchemaView.tsx     | 82 +++++++++++++++++++++-
 3 files changed, 96 insertions(+), 14 deletions(-)

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

diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 373b63282..c3612fee9 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -134,7 +134,7 @@ export class EditableView extends React.Component<EditableProps> {
             return (
                 <div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`}
                     style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
-                    onClick={this.onClick} >
+                    onClick={this.onClick}>
                     <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize }}>{this.props.contents}</span>
                 </div>
             );
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index c91f1017b..e00142c91 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -24,7 +24,7 @@ import { SelectionManager } from "../../util/SelectionManager";
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faExpand } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { SchemaHeaderField, RandomPastel } from "../../../new_fields/SchemaHeaderField";
+import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 
 library.add(faExpand);
 
@@ -44,6 +44,8 @@ export interface CellProps {
     setIsEditing: (isEditing: boolean) => void;
     isEditable: boolean;
     setPreviewDoc: (doc: Doc) => void;
+    setComputed: (script: string, doc: Doc, field: string, row: number, col: number) => boolean;
+    getField: (row: number, col?: number) => void;
 }
 
 @observer
@@ -84,9 +86,11 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
         this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
     }
 
-    applyToDoc = (doc: Doc, run: (args?: { [name: string]: any }) => any) => {
-        const res = run({ this: doc });
+    applyToDoc = (doc: Doc, row: number, col: number, run: (args?: { [name: string]: any }) => any) => {
+        const res = run({ this: doc, $r: row, $c: col, $: (r: number = 0, c: number = 0) => this.props.getField(r + row, c + col) });
         if (!res.success) return false;
+        // doc[this.props.fieldKey] = res.result;
+        // return true;
         doc[this.props.rowProps.column.id as string] = res.result;
         return true;
     }
@@ -176,7 +180,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
         if (this.props.isFocused && !this.props.isEditable) className += " inactive";
 
         let doc = FieldValue(Cast(field, Doc));
-        if (type === "document") console.log("doc", typeof field);
         let fieldIsDoc = (type === "document" && typeof field === "object") || (typeof field === "object" && doc);
         let docExpander = (
             <div className="collectionSchemaView-cellContents-docExpander" onPointerDown={this.expandDoc} >
@@ -203,22 +206,25 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
                             }
                             }
                             SetValue={(value: string) => {
-                                let script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name } });
+                                if (value.startsWith(":=")) {
+                                    return this.props.setComputed(value.substring(2), props.Document, this.props.rowProps.column.id!, this.props.row, this.props.col);
+                                }
+                                let script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
                                 if (!script.compiled) {
                                     return false;
                                 }
-                                return this.applyToDoc(props.Document, script.run);
+                                return this.applyToDoc(props.Document, this.props.row, this.props.col, script.run);
                             }}
                             OnFillDown={async (value: string) => {
-                                let script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name } });
+                                let script = CompileScript(value, { requiredType: type, 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 => this.applyToDoc(doc, run));
-                            }} />
+                                val && val.forEach((doc, i) => this.applyToDoc(doc, i, this.props.col, run));
+                            }}
+                        />
                     </div >
                     {fieldIsDoc ? docExpander : null}
                 </div>
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index d504f9799..42843ad30 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -321,13 +321,12 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
         }
 
         let cols = this.columns.map(col => {
-
             let header = <CollectionSchemaHeader
                 keyValue={col}
                 possibleKeys={possibleKeys}
                 existingKeys={this.columns.map(c => c.heading)}
                 keyType={this.getColumnType(col)}
-                typeConst={col.type !== undefined || columnTypes.get(col.heading) !== undefined}
+                typeConst={columnTypes.get(col.heading) !== undefined}
                 onSelect={this.changeColumns}
                 setIsEditing={this.setHeaderIsEditing}
                 deleteColumn={this.deleteColumn}
@@ -360,7 +359,9 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
                         moveDocument: this.props.moveDocument,
                         setIsEditing: this.setCellIsEditing,
                         isEditable: isEditable,
-                        setPreviewDoc: this.props.setPreviewDoc
+                        setPreviewDoc: this.props.setPreviewDoc,
+                        setComputed: this.setComputed,
+                        getField: this.getField,
                     };
 
                     let colType = this.getColumnType(col);
@@ -772,6 +773,81 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
         }
     }
 
+    getField = (row: number, col?: number) => {
+        // const docs = DocListCast(this.props.Document[this.props.fieldKey]);
+
+        let cdoc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
+        const docs = DocListCast(cdoc[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].heading;
+            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}].heading];
+            }
+            return ${script}`;
+        const compiled = CompileScript(script, { params: { this: Doc.name }, capturedVariables: { doc: this.props.Document, key: this.props.fieldKey }, typecheck: true, transformer: this.createTransformer(row, col) });
+        if (compiled.compiled) {
+            doc[field] = new ComputedField(compiled);
+            return true;
+        }
+        return false;
+    }
+
     render() {
         // if (SelectionManager.SelectedDocuments().length > 0) console.log(StrCast(SelectionManager.SelectedDocuments()[0].Document.title));
         // if (DocumentManager.Instance.getDocumentView(this.props.Document)) console.log(StrCast(this.props.Document.title), SelectionManager.IsSelected(DocumentManager.Instance.getDocumentView(this.props.Document)!))
-- 
cgit v1.2.3-70-g09d2

