From 07d71ea914c5bae3136b6c911c38b3373afcd5a7 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 14 Jul 2020 19:32:56 -0400
Subject: cleaned up InkOptionsMenu

---
 src/client/views/MainView.tsx | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 2ed528836..41efe246c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,4 +1,5 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
+
 import {
     faTasks, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
     faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
@@ -6,7 +7,8 @@ import {
     faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
     faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle,
     faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
-    faHeading, faRulerCombined, faFillDrip, faUnlink
+    faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
+    faPaintRoller, faBars, faBrush, faShapes, faEllipsisH
 } from '@fortawesome/free-solid-svg-icons';
 import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -147,7 +149,8 @@ export class MainView extends React.Component {
             faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
             faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
             faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
-            faHeading, faRulerCombined, faFillDrip, faUnlink);
+            faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
+            faPaintRoller, faBars, faBrush, faShapes, faEllipsisH);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
-- 
cgit v1.2.3-70-g09d2


From 6c96bd6c8ef06e1ecb75ca39a575155dafcc26f1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 14 Jul 2020 21:56:48 -0400
Subject: moving collectionViewChrome to CollectionMenu

---
 src/client/views/MainView.tsx                    |   2 +
 src/client/views/collections/CollectionMenu.scss |  12 +
 src/client/views/collections/CollectionMenu.tsx  | 331 +++++++++++++++++++++++
 src/client/views/collections/CollectionView.tsx  |   2 +
 4 files changed, 347 insertions(+)
 create mode 100644 src/client/views/collections/CollectionMenu.scss
 create mode 100644 src/client/views/collections/CollectionMenu.tsx

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 41efe246c..428fb835b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -66,6 +66,7 @@ import { LinkCreatedBox } from './nodes/LinkCreatedBox';
 import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
 import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
 import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager';
+import CollectionMenu from './collections/CollectionMenu';
 
 @observer
 export class MainView extends React.Component {
@@ -612,6 +613,7 @@ export class MainView extends React.Component {
             <GoogleAuthenticationManager />
             <HypothesisAuthenticationManager />
             <DocumentDecorations />
+            <CollectionMenu />
             <InkOptionsMenu />
             <FormatShapePane />
             <RichTextMenu key="rich" />
diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss
new file mode 100644
index 000000000..6280670d8
--- /dev/null
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -0,0 +1,12 @@
+.antimodeMenu-button {
+    padding:0;
+    width:40px;
+    display: flex;
+    svg {
+        margin:auto;
+    }
+}
+.collectionViewChrome-cont {
+    position:relative;
+    display:inline-flex;
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
new file mode 100644
index 000000000..d79ffffa1
--- /dev/null
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -0,0 +1,331 @@
+import React = require("react");
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable, reaction, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { Doc, DocListCast } from "../../../fields/Doc";
+import { BoolCast, Cast, StrCast, NumCast } from "../../../fields/Types";
+import AntimodeMenu from "../AntimodeMenu";
+import "./CollectionMenu.scss";
+import { undoBatch } from "../../util/UndoManager";
+import { CollectionViewType, CollectionView } from "./CollectionView";
+import { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils";
+import { CollectionGridViewChrome } from "./CollectionViewChromes";
+import { DragManager } from "../../util/DragManager";
+import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
+import { List } from "../../../fields/List";
+import { SelectionManager } from "../../util/SelectionManager";
+
+@observer
+export default class CollectionMenu extends AntimodeMenu {
+    static Instance: CollectionMenu;
+
+    @observable SelectedCollection: CollectionView | undefined;
+
+    constructor(props: Readonly<{}>) {
+        super(props);
+        CollectionMenu.Instance = this;
+        this._canFade = false; // don't let the inking menu fade away
+        this.Pinned = Cast(Doc.UserDoc()["menuCollections-pinned"], "boolean", true);
+    }
+
+    @action
+    toggleMenuPin = (e: React.MouseEvent) => {
+        Doc.UserDoc()["menuCollections-pinned"] = this.Pinned = !this.Pinned;
+    }
+
+    @computed get aButton() {
+        return <div className="btn-draw" key="draw">
+            <button className="antimodeMenu-button" key="abutton" onClick={() => alert("clicked")} style={{ fontSize: "20" }}>
+                <FontAwesomeIcon icon="palette" size="lg" />
+            </button>
+        </div>;
+    }
+
+    render() {
+        return this.getElement([
+            this.aButton,
+            !this.SelectedCollection ? <></> : <CollectionViewBaseChrome CollectionView={this.SelectedCollection} type={StrCast(this.SelectedCollection.props.Document._viewType) as CollectionViewType} />,
+            <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "" }}>
+                <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
+            </button>
+        ]);
+    }
+}
+
+interface CollectionViewChromeProps {
+    CollectionView: CollectionView;
+    type: CollectionViewType;
+    collapse?: (value: boolean) => any;
+}
+
+const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
+
+@observer
+export class CollectionViewBaseChrome extends React.Component<CollectionViewChromeProps> {
+    //(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
+
+    get target() { return this.props.CollectionView.props.Document; }
+    _templateCommand = {
+        params: ["target", "source"], title: "=> item view",
+        script: "this.target.childLayout = getDocTemplate(this.source?.[0])",
+        immediate: undoBatch((source: Doc[]) => source.length && (this.target.childLayout = Doc.getDocTemplate(source?.[0]))),
+        initialize: emptyFunction,
+    };
+    _narrativeCommand = {
+        params: ["target", "source"], title: "=> child click view",
+        script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])",
+        immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))),
+        initialize: emptyFunction,
+    };
+    _contentCommand = {
+        params: ["target", "source"], title: "=> clear content",
+        script: "getProto(this.target).data = copyField(this.source);",
+        immediate: undoBatch((source: Doc[]) => Doc.GetProto(this.target).data = new List<Doc>(source)), // Doc.aliasDocs(source),
+        initialize: emptyFunction,
+    };
+    _viewCommand = {
+        params: ["target"], title: "=> reset view",
+        script: "this.target._panX = this.restoredPanX; this.target._panY = this.restoredPanY; this.target.scale = this.restoredScale;",
+        immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target.scale = 1; }),
+        initialize: (button: Doc) => { button.restoredPanX = this.target._panX; button.restoredPanY = this.target._panY; button.restoredScale = this.target.scale; },
+    };
+    _clusterCommand = {
+        params: ["target"], title: "=> fit content",
+        script: "this.target._fitToBox = !this.target._fitToBox;",
+        immediate: undoBatch((source: Doc[]) => this.target._fitToBox = !this.target._fitToBox),
+        initialize: emptyFunction
+    };
+    _fitContentCommand = {
+        params: ["target"], title: "=> toggle clusters",
+        script: "this.target.useClusters = !this.target.useClusters;",
+        immediate: undoBatch((source: Doc[]) => this.target.useClusters = !this.target.useClusters),
+        initialize: emptyFunction
+    };
+
+    _freeform_commands = [this._viewCommand, this._fitContentCommand, this._clusterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand];
+    _stacking_commands = [this._contentCommand, this._templateCommand];
+    _masonry_commands = [this._contentCommand, this._templateCommand];
+    _schema_commands = [this._templateCommand, this._narrativeCommand];
+    _tree_commands = [];
+    private get _buttonizableCommands() {
+        switch (this.props.type) {
+            case CollectionViewType.Tree: return this._tree_commands;
+            case CollectionViewType.Schema: return this._schema_commands;
+            case CollectionViewType.Stacking: return this._stacking_commands;
+            case CollectionViewType.Masonry: return this._stacking_commands;
+            case CollectionViewType.Freeform: return this._freeform_commands;
+            case CollectionViewType.Time: return this._freeform_commands;
+            case CollectionViewType.Carousel: return this._freeform_commands;
+            case CollectionViewType.Carousel3D: return this._freeform_commands;
+        }
+        return [];
+    }
+    private _picker: any;
+    private _commandRef = React.createRef<HTMLInputElement>();
+    private _viewRef = React.createRef<HTMLInputElement>();
+    @observable private _currentKey: string = "";
+
+    componentDidMount = action(() => {
+        this._currentKey = this._currentKey || (this._buttonizableCommands.length ? this._buttonizableCommands[0]?.title : "");
+    });
+
+    @undoBatch
+    viewChanged = (e: React.ChangeEvent) => {
+        //@ts-ignore
+        this.document._viewType = e.target.selectedOptions[0].value;
+    }
+
+    commandChanged = (e: React.ChangeEvent) => {
+        //@ts-ignore
+        runInAction(() => this._currentKey = e.target.selectedOptions[0].value);
+    }
+
+    @action
+    toggleViewSpecs = (e: React.SyntheticEvent) => {
+        this.document._facetWidth = this.document._facetWidth ? 0 : 200;
+        e.stopPropagation();
+    }
+
+    @action closeViewSpecs = () => {
+        this.document._facetWidth = 0;
+    }
+
+
+    @computed get subChrome() {
+        switch (this.props.type) {
+            case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+            // case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
+            // case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
+            // case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
+            // case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
+            // case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
+            // case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+            default: return null;
+        }
+    }
+
+    private get document() {
+        return this.props.CollectionView.props.Document;
+    }
+
+    private dropDisposer?: DragManager.DragDropDisposer;
+    protected createDropTarget = (ele: HTMLDivElement) => {
+        this.dropDisposer?.();
+        if (ele) {
+            this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.document);
+        }
+    }
+
+    @undoBatch
+    @action
+    protected drop(e: Event, de: DragManager.DropEvent): boolean {
+        const docDragData = de.complete.docDragData;
+        if (docDragData?.draggedDocuments.length) {
+            this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(docDragData.draggedDocuments || []));
+            e.stopPropagation();
+        }
+        return true;
+    }
+
+    dragViewDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, (e, down, delta) => {
+            const vtype = this.props.CollectionView.collectionViewType;
+            const c = {
+                params: ["target"], title: vtype,
+                script: `this.target._viewType = '${StrCast(this.props.CollectionView.props.Document._viewType)}'`,
+                immediate: (source: Doc[]) => this.props.CollectionView.props.Document._viewType = Doc.getDocTemplate(source?.[0]),
+                initialize: emptyFunction,
+            };
+            DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title),
+                { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY);
+            return true;
+        }, emptyFunction, emptyFunction);
+    }
+    dragCommandDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, (e, down, delta) => {
+            this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c =>
+                DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title,
+                    { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY));
+            return true;
+        }, emptyFunction, () => {
+            this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate([]));
+        });
+    }
+
+    @computed get templateChrome() {
+        return <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} >
+            <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
+                <div className="commandEntry-drop">
+                    <FontAwesomeIcon icon="bullseye" size="2x" />
+                </div>
+                <select
+                    className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}>
+                    <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={"empty"} value={""} />
+                    {this._buttonizableCommands.map(cmd =>
+                        <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>{cmd.title}</option>
+                    )}
+                </select>
+            </div>
+        </div>;
+    }
+
+    @computed get viewModes() {
+        const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
+        return <div className="collectionViewBaseChrome-viewModes" style={{ display: collapsed ? "none" : undefined }}>
+            <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._viewRef} onPointerDown={this.dragViewDown}>
+                <div className="commandEntry-drop">
+                    <FontAwesomeIcon icon="bullseye" size="2x" />
+                </div>
+                <select
+                    className="collectionViewBaseChrome-viewPicker"
+                    onPointerDown={stopPropagation}
+                    onChange={this.viewChanged}
+                    value={StrCast(this.props.CollectionView.props.Document._viewType)}>
+                    {Object.values(CollectionViewType).map(type => [CollectionViewType.Invalid, CollectionViewType.Docking].includes(type) ? (null) : (
+                        <option
+                            key={Utils.GenerateGuid()}
+                            className="collectionViewBaseChrome-viewOption"
+                            onPointerDown={stopPropagation}
+                            value={type}>
+                            {type[0].toUpperCase() + type.substring(1)}
+                        </option>
+                    ))}
+                </select>
+            </div>
+        </div>;
+    }
+
+    render() {
+        const scale = Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform()?.Scale);
+        return (
+            <div className="collectionViewChrome-cont" style={{
+                top: 0,
+                transform: `scale(${scale})`,
+                width: "100%"
+            }}>
+                <div className="collectionViewChrome" style={{ border: "unset" }}>
+                    <div className="collectionViewBaseChrome">
+                        {this.viewModes}
+                        <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: "grid" }}>
+                            <div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.toggleViewSpecs} >
+                                <FontAwesomeIcon icon="filter" size="2x" />
+                            </div>
+                        </div>
+                        {this.templateChrome}
+                    </div>
+                    {this.subChrome}
+                </div>
+            </div>
+        );
+    }
+}
+
+@observer
+export class CollectionFreeFormViewChrome extends React.Component<CollectionViewChromeProps> {
+
+    get Document() { return this.props.CollectionView.props.Document; }
+    @computed get dataField() {
+        return this.props.CollectionView.props.Document[Doc.LayoutFieldKey(this.props.CollectionView.props.Document)];
+    }
+    @computed get childDocs() {
+        return DocListCast(this.dataField);
+    }
+    @undoBatch
+    @action
+    nextKeyframe = (): void => {
+        const currentFrame = NumCast(this.Document.currentFrame);
+        if (currentFrame === undefined) {
+            this.Document.currentFrame = 0;
+            CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
+        }
+        CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0);
+        this.Document.currentFrame = Math.max(0, (currentFrame || 0) + 1);
+        this.Document.lastFrame = Math.max(NumCast(this.Document.currentFrame), NumCast(this.Document.lastFrame));
+    }
+    @undoBatch
+    @action
+    prevKeyframe = (): void => {
+        const currentFrame = NumCast(this.Document.currentFrame);
+        if (currentFrame === undefined) {
+            this.Document.currentFrame = 0;
+            CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
+        }
+        CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
+        this.Document.currentFrame = Math.max(0, (currentFrame || 0) - 1);
+    }
+    render() {
+        return this.Document.isAnnotationOverlay ? (null) :
+            <div className="collectionFreeFormViewChrome-cont">
+                <div key="back" title="back frame" className="backKeyframe" onClick={this.prevKeyframe}>
+                    <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
+                </div>
+                <div key="num" title="toggle view all" className="numKeyframe" style={{ backgroundColor: this.Document.editing ? "#759c75" : "#c56565" }}
+                    onClick={action(() => this.Document.editing = !this.Document.editing)} >
+                    {NumCast(this.Document.currentFrame)}
+                </div>
+                <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={this.nextKeyframe}>
+                    <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
+                </div>
+            </div>;
+    }
+}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index cbd1ac9af..e18fe319e 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -48,6 +48,7 @@ import { CollectionTimeView } from './CollectionTimeView';
 import { CollectionTreeView } from "./CollectionTreeView";
 import './CollectionView.scss';
 import { CollectionViewBaseChrome } from './CollectionViewChromes';
+import CollectionMenu from './CollectionMenu';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -549,6 +550,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             ChildLayoutTemplate: this.childLayoutTemplate,
             ChildLayoutString: this.childLayoutString,
         };
+        setTimeout(action(() => this.props.isSelected() && (CollectionMenu.Instance.SelectedCollection = this)), 0);
         const boxShadow = Doc.UserDoc().renderStyle === "comic" || this.props.Document.isBackground || this.collectionViewType === CollectionViewType.Linear ? undefined :
             `${Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`;
         return (<div className={"collectionView"} onContextMenu={this.onContextMenu}
-- 
cgit v1.2.3-70-g09d2


From 95429adb77402ce2a218c5585ceef4db0c8494db Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 14 Jul 2020 23:55:59 -0400
Subject: converted collectionChrome into a menu bar

---
 src/client/views/MainView.tsx                      |   6 +-
 .../views/collections/CollectionDockingView.tsx    |   2 +-
 src/client/views/collections/CollectionMenu.scss   | 475 ++++++++++++++++++++-
 src/client/views/collections/CollectionMenu.tsx    | 443 +++++++++++++++++--
 src/client/views/collections/CollectionView.tsx    |  11 +-
 5 files changed, 888 insertions(+), 49 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 428fb835b..95b2dfcdb 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -392,7 +392,7 @@ export class MainView extends React.Component {
             doc.dockingConfig ? this.openWorkspace(doc) :
                 CollectionDockingView.AddRightSplit(doc, libraryPath);
     }
-    sidebarScreenToLocal = () => new Transform(0, RichTextMenu.Instance.Pinned ? -35 : 0, 1);
+    sidebarScreenToLocal = () => new Transform(0, (RichTextMenu.Instance.Pinned ? -35 : 0) + (InkOptionsMenu.Instance.Pinned ? -35 : 0) + (CollectionMenu.Instance.Pinned ? -35 : 0), 1);
     mainContainerXf = () => this.sidebarScreenToLocal().translate(0, -this._buttonBarHeight);
 
     @computed get flyout() {
@@ -469,11 +469,13 @@ export class MainView extends React.Component {
 
     @computed get mainContent() {
         const sidebar = this.userDoc?.["tabs-panelContainer"];
+        const n = (RichTextMenu.Instance?.Pinned ? 1 : 0) + (InkOptionsMenu.Instance?.Pinned ? 1 : 0) + (CollectionMenu.Instance?.Pinned ? 1 : 0);
+        const height = `calc(100% - ${n * Number(ANTIMODEMENU_HEIGHT.replace("px", ""))}px)`;
         return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
             <div className="mainView-mainContent" style={{
                 color: this.darkScheme ? "rgb(205,205,205)" : "black",
                 //change to times 2 for both pinned
-                height: (RichTextMenu.Instance?.Pinned || InkOptionsMenu.Instance?.Pinned) ? (RichTextMenu.Instance?.Pinned && InkOptionsMenu.Instance?.Pinned) ? `calc(100% - 2*${ANTIMODEMENU_HEIGHT})` : `calc(100% - ${ANTIMODEMENU_HEIGHT})` : "100%",
+                height,
                 width: (FormatShapePane.Instance?.Pinned) ? `calc(100% - 200px)` : "100%"
             }} >
                 <div style={{ display: "contents", flexDirection: "row", position: "relative" }}>
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index f408e24a8..657296566 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -436,7 +436,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         const cur = this._containerRef.current;
 
         // bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed
-        this._goldenLayout && this._goldenLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
+        this._goldenLayout?.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
     }
 
     @action
diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss
index 6280670d8..c1a3f9c09 100644
--- a/src/client/views/collections/CollectionMenu.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -1,12 +1,471 @@
-.antimodeMenu-button {
-    padding:0;
-    width:40px;
+@import "../globalCssVariables";
+
+
+.collectionMenu-cont {
+    position:relative;
+    display:inline-flex;
+    width: 100%;
+    opacity: 0.9;
+    z-index: 9001;
+    transition: top .5s;
+    background: #121721;
+    color: white;
+    transform-origin: top left;
+
+    .antimodeMenu-button {
+        padding:0;
+        width:40px;
+        display: flex;
+        svg {
+            margin:auto;
+        }
+    }
+
+    .collectionViewChrome {
+        display: flex;
+        padding-bottom: 1px;
+        height: 32px;
+        border-bottom: .5px solid rgb(180, 180, 180);
+        overflow: visible;
+        z-index: 9001;
+
+        .collectionViewBaseChrome {
+            display: flex;
+
+            .collectionViewBaseChrome-viewPicker {
+                font-size: 75%;
+                //text-transform: uppercase;
+                //letter-spacing: 2px;
+                background: #121721;
+                color: white;
+                outline-color: black;
+                border: none;
+                //padding: 12px 10px 11px 10px;
+            }
+
+            .collectionViewBaseChrome-viewPicker:active {
+                outline-color: black;
+            }
+
+            .collectionViewBaseChrome-button {
+                font-size: 75%;
+                text-transform: uppercase;
+                letter-spacing: 2px;
+                background: rgb(238, 238, 238);
+                color: purple;
+                outline-color: black;
+                border: none;
+                padding: 12px 10px 11px 10px;
+                margin-left: 10px;
+            }
+
+            .collectionViewBaseChrome-cmdPicker {
+                margin-left: 3px;
+                margin-right: 0px;
+                font-size: 75%;
+                background: #121721;
+                border: none;
+                color: white;
+            }
+
+            .commandEntry-outerDiv {
+                pointer-events: all;
+                background-color: #121721;
+                display: flex;
+                flex-direction: row;
+                height: 30px;
+
+                .commandEntry-drop {
+                    color: white;
+                    width: 30px;
+                    margin-top: auto;
+                    margin-bottom: auto;
+                }
+            }
+
+            .collectionViewBaseChrome-collapse {
+                transition: all .5s, opacity 0.3s;
+                position: absolute;
+                width: 40px;
+                transform-origin: top left;
+                pointer-events: all;
+                // margin-top: 10px;
+            }
+
+            @media only screen and (max-device-width: 480px) {
+                .collectionViewBaseChrome-collapse {
+                    display: none;
+                }
+            }
+
+            .collectionViewBaseChrome-template,
+            .collectionViewBaseChrome-viewModes {
+                display: grid;
+                background: rgb(238, 238, 238);
+                color: grey;
+                margin-top: auto;
+                margin-bottom: auto;
+            }
+            .collectionViewBaseChrome-viewSpecs {
+                margin-left: 5px;
+                display: grid;
+
+                .collectionViewBaseChrome-filterIcon {
+                    position: relative;
+                    display: flex;
+                    margin: auto;
+                    background: #121721;
+                    color: white;
+                    width: 30px;
+                    height: 30px;
+                    align-items: center;
+                    justify-content: center;
+                }
+
+                .collectionViewBaseChrome-viewSpecsInput {
+                    padding: 12px 10px 11px 10px;
+                    border: 0px;
+                    color: grey;
+                    text-align: center;
+                    letter-spacing: 2px;
+                    outline-color: black;
+                    font-size: 75%;
+                    background: rgb(238, 238, 238);
+                    height: 100%;
+                    width: 75px;
+                }
+
+                .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 1fr;
+                        grid-gap: 10px;
+                        margin: 10px;
+                    }
+                }
+            }
+        }
+
+        .collectionStackingViewChrome-cont,
+        .collectionTreeViewChrome-cont,
+        .collection3DCarouselViewChrome-cont {
+            display: flex;
+            justify-content: space-between;
+        }
+
+        .collectionGridViewChrome-cont {
+            display: flex;
+            margin-left: 10;
+
+            .collectionGridViewChrome-viewPicker {
+                font-size: 75%;
+                //text-transform: uppercase;
+                //letter-spacing: 2px;
+                background: #121721;
+                color: white;
+                outline-color: black;
+                border: none;
+                //padding: 12px 10px 11px 10px;
+            }
+
+            .collectionGridViewChrome-viewPicker:active {
+                outline-color: black;
+            }
+
+            .grid-control {
+                align-self: center;
+                display: flex;
+                flex-direction: row;
+                margin-right: 5px;
+
+                .grid-icon {
+                    margin-right: 5px;
+                    align-self: center;
+                }
+
+                .flexLabel {
+                    margin-bottom: 0;
+                }
+            }
+
+            .collectionGridViewChrome-entryBox {
+                width: 50%;
+            }
+        }
+
+
+        .collectionStackingViewChrome-sort,
+        .collectionTreeViewChrome-sort {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+
+            .collectionStackingViewChrome-sortIcon,
+            .collectionTreeViewChrome-sortIcon {
+                transition: transform .5s;
+                margin-left: 10px;
+            }
+        }
+
+        button:hover {
+            transform: scale(1);
+        }
+
+
+        .collectionStackingViewChrome-pivotField-cont,
+        .collectionTreeViewChrome-pivotField-cont,
+        .collection3DCarouselViewChrome-scrollSpeed-cont {
+            justify-self: right;
+            display: flex;
+            font-size: 75%;
+            letter-spacing: 2px;
+
+            .collectionStackingViewChrome-pivotField-label,
+            .collectionTreeViewChrome-pivotField-label,
+            .collection3DCarouselViewChrome-scrollSpeed-label {
+                vertical-align: center;
+                padding-left: 10px;
+                margin: auto;
+            }
+
+            .collectionStackingViewChrome-pivotField,
+            .collectionTreeViewChrome-pivotField,
+            .collection3DCarouselViewChrome-scrollSpeed {
+                color: white;
+                width: 100%;
+                min-width: 100px;
+                display: flex;
+                align-items: center;
+                background: rgb(238, 238, 238);
+
+                .editable-view-input,
+                input,
+                .editableView-container-editing-oneLine,
+                .editableView-container-editing {
+                    margin: auto;
+                    border: 0px;
+                    color: grey !important;
+                    text-align: center;
+                    letter-spacing: 2px;
+                    outline-color: black;
+                    height: 100%;
+                }
+
+                .react-autosuggest__container {
+                    margin: 0;
+                    color: grey;
+                    padding: 0px;
+                }
+            }
+        }
+
+        .collectionStackingViewChrome-pivotField:hover,
+        .collectionTreeViewChrome-pivotField:hover,
+        .collection3DCarouselViewChrome-scrollSpeed:hover {
+            cursor: text;
+        }
+
+    }
+}
+
+.collectionFreeFormMenu-cont {
+    width: 60px;
     display: flex;
-    svg {
-        margin:auto;
+    position: relative;
+    align-items: center;
+
+    .fwdKeyframe,
+    .numKeyframe,
+    .backKeyframe {
+        cursor: pointer;
+        position: absolute;
+        width: 20;
+        height: 30;
+        bottom: 0;
+        background: #121721;
+        display: flex;
+        align-items: center;
+        color: white;
+    }
+
+    .backKeyframe {
+        left: 0;
+
+        svg {
+            display: block;
+            margin: auto;
+        }
+    }
+
+    .numKeyframe {
+        left: 20;
+        display: flex;
+        flex-direction: column;
+        padding: 5px;
+    }
+
+    .fwdKeyframe {
+        left: 40;
+
+        svg {
+            display: block;
+            margin: auto;
+        }
     }
 }
-.collectionViewChrome-cont {
-    position:relative;
-    display:inline-flex;
+
+.collectionSchemaViewChrome-cont {
+    display: flex;
+    font-size: 10.5px;
+
+    .collectionSchemaViewChrome-toggle {
+        display: flex;
+        margin-left: 10px;
+    }
+
+    .collectionSchemaViewChrome-label {
+        text-transform: uppercase;
+        letter-spacing: 2px;
+        margin-right: 5px;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+    }
+
+    .collectionSchemaViewChrome-toggler {
+        width: 100px;
+        height: 41px;
+        background-color: black;
+        position: relative;
+    }
+
+    .collectionSchemaViewChrome-togglerButton {
+        width: 47px;
+        height: 35px;
+        background-color: $light-color-secondary;
+        // position: absolute;
+        transition: all 0.5s ease;
+        // top: 3px;
+        margin-top: 3px;
+        color: gray;
+        letter-spacing: 2px;
+        text-transform: uppercase;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        text-align: center;
+
+        &.on {
+            margin-left: 3px;
+        }
+
+        &.off {
+            margin-left: 50px;
+        }
+    }
+}
+
+
+.commandEntry-outerDiv {
+    display: flex;
+    flex-direction: column;
+    height: 40px;
+}
+
+.commandEntry-inputArea {
+    display: flex;
+    flex-direction: row;
+    width: 150px;
+    margin: auto auto auto auto;
+}
+
+.react-autosuggest__container {
+    position: relative;
+    width: 100%;
+    margin-left: 5px;
+    margin-right: 5px;
+}
+
+.react-autosuggest__input {
+    border: 1px solid #aaa;
+    border-radius: 4px;
+    width: 100%;
+}
+
+.react-autosuggest__input--focused {
+    outline: none;
+}
+
+.react-autosuggest__input--open {
+    border-bottom-left-radius: 0;
+    border-bottom-right-radius: 0;
+}
+
+.react-autosuggest__suggestions-container {
+    display: none;
+}
+
+.react-autosuggest__suggestions-container--open {
+    display: block;
+    position: fixed;
+    overflow-y: auto;
+    max-height: 400px;
+    width: 180px;
+    border: 1px solid #aaa;
+    background-color: #fff;
+    font-family: Helvetica, sans-serif;
+    font-weight: 300;
+    font-size: 16px;
+    border-bottom-left-radius: 4px;
+    border-bottom-right-radius: 4px;
+    z-index: 2;
+}
+
+.react-autosuggest__suggestions-list {
+    margin: 0;
+    padding: 0;
+    list-style-type: none;
+}
+
+.react-autosuggest__suggestion {
+    cursor: pointer;
+    padding: 10px 20px;
+}
+
+.react-autosuggest__suggestion--highlighted {
+    background-color: #ddd;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index d79ffffa1..1644bc726 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1,19 +1,20 @@
 import React = require("react");
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, reaction, runInAction } from "mobx";
+import { action, computed, observable, reaction, runInAction, Lambda } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt } from "../../../fields/Doc";
 import { BoolCast, Cast, StrCast, NumCast } from "../../../fields/Types";
 import AntimodeMenu from "../AntimodeMenu";
 import "./CollectionMenu.scss";
 import { undoBatch } from "../../util/UndoManager";
-import { CollectionViewType, CollectionView } from "./CollectionView";
+import { CollectionViewType, CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView";
 import { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils";
-import { CollectionGridViewChrome } from "./CollectionViewChromes";
 import { DragManager } from "../../util/DragManager";
 import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
 import { List } from "../../../fields/List";
-import { SelectionManager } from "../../util/SelectionManager";
+import { EditableView } from "../EditableView";
+import { Id } from "../../../fields/FieldSymbols";
+import { listSpec } from "../../../fields/Schema";
 
 @observer
 export default class CollectionMenu extends AntimodeMenu {
@@ -33,19 +34,10 @@ export default class CollectionMenu extends AntimodeMenu {
         Doc.UserDoc()["menuCollections-pinned"] = this.Pinned = !this.Pinned;
     }
 
-    @computed get aButton() {
-        return <div className="btn-draw" key="draw">
-            <button className="antimodeMenu-button" key="abutton" onClick={() => alert("clicked")} style={{ fontSize: "20" }}>
-                <FontAwesomeIcon icon="palette" size="lg" />
-            </button>
-        </div>;
-    }
-
     render() {
         return this.getElement([
-            this.aButton,
             !this.SelectedCollection ? <></> : <CollectionViewBaseChrome CollectionView={this.SelectedCollection} type={StrCast(this.SelectedCollection.props.Document._viewType) as CollectionViewType} />,
-            <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "" }}>
+            <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: "#121721" }}>
                 <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
             </button>
         ]);
@@ -55,7 +47,6 @@ export default class CollectionMenu extends AntimodeMenu {
 interface CollectionViewChromeProps {
     CollectionView: CollectionView;
     type: CollectionViewType;
-    collapse?: (value: boolean) => any;
 }
 
 const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
@@ -154,12 +145,12 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     @computed get subChrome() {
         switch (this.props.type) {
             case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
-            // case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
-            // case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
-            // case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
-            // case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
-            // case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
-            // case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+            case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+            case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+            case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+            case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+            case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+            case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
             default: return null;
         }
     }
@@ -230,8 +221,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     }
 
     @computed get viewModes() {
-        const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
-        return <div className="collectionViewBaseChrome-viewModes" style={{ display: collapsed ? "none" : undefined }}>
+        return <div className="collectionViewBaseChrome-viewModes" >
             <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._viewRef} onPointerDown={this.dragViewDown}>
                 <div className="commandEntry-drop">
                     <FontAwesomeIcon icon="bullseye" size="2x" />
@@ -258,7 +248,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     render() {
         const scale = Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform()?.Scale);
         return (
-            <div className="collectionViewChrome-cont" style={{
+            <div className="collectionMenu-cont" style={{
                 top: 0,
                 transform: `scale(${scale})`,
                 width: "100%"
@@ -293,7 +283,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
     @undoBatch
     @action
     nextKeyframe = (): void => {
-        const currentFrame = NumCast(this.Document.currentFrame);
+        const currentFrame = Cast(this.Document.currentFrame, "number", null);
         if (currentFrame === undefined) {
             this.Document.currentFrame = 0;
             CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
@@ -305,7 +295,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
     @undoBatch
     @action
     prevKeyframe = (): void => {
-        const currentFrame = NumCast(this.Document.currentFrame);
+        const currentFrame = Cast(this.Document.currentFrame, "number", null);
         if (currentFrame === undefined) {
             this.Document.currentFrame = 0;
             CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
@@ -315,7 +305,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
     }
     render() {
         return this.Document.isAnnotationOverlay ? (null) :
-            <div className="collectionFreeFormViewChrome-cont">
+            <div className="collectionFreeFormMenu-cont">
                 <div key="back" title="back frame" className="backKeyframe" onClick={this.prevKeyframe}>
                     <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
                 </div>
@@ -329,3 +319,400 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
             </div>;
     }
 }
+@observer
+export class CollectionStackingViewChrome extends React.Component<CollectionViewChromeProps> {
+    @observable private _currentKey: string = "";
+    @observable private suggestions: string[] = [];
+
+    @computed private get descending() { return StrCast(this.props.CollectionView.props.Document._columnsSort) === "descending"; }
+    @computed get pivotField() { return StrCast(this.props.CollectionView.props.Document._pivotField); }
+
+    getKeySuggestions = async (value: string): Promise<string[]> => {
+        value = value.toLowerCase();
+        const docs = DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey]);
+        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 = [];
+    }
+
+    @action
+    setValue = (value: string) => {
+        this.props.CollectionView.props.Document._pivotField = value;
+        return true;
+    }
+
+    @action toggleSort = () => {
+        this.props.CollectionView.props.Document._columnsSort =
+            this.props.CollectionView.props.Document._columnsSort === "descending" ? "ascending" :
+                this.props.CollectionView.props.Document._columnsSort === "ascending" ? undefined : "descending";
+    }
+    @action resetValue = () => { this._currentKey = this.pivotField; };
+
+    render() {
+        return (
+            <div className="collectionStackingViewChrome-cont">
+                <div className="collectionStackingViewChrome-pivotField-cont">
+                    <div className="collectionStackingViewChrome-pivotField-label">
+                        GROUP BY:
+                    </div>
+                    <div className="collectionStackingViewChrome-sortIcon" onClick={this.toggleSort} style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}>
+                        <FontAwesomeIcon icon="caret-up" size="2x" color="white" />
+                    </div>
+                    <div className="collectionStackingViewChrome-pivotField">
+                        <EditableView
+                            GetValue={() => this.pivotField}
+                            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.pivotField ? this.pivotField : "N/A"}
+                        />
+                    </div>
+                </div>
+            </div>
+        );
+    }
+}
+
+
+@observer
+export class CollectionSchemaViewChrome extends React.Component<CollectionViewChromeProps> {
+    // private _textwrapAllRows: boolean = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+
+    @undoBatch
+    togglePreview = () => {
+        const dividerWidth = 4;
+        const borderWidth = Number(COLLECTION_BORDER_WIDTH);
+        const panelWidth = this.props.CollectionView.props.PanelWidth();
+        const previewWidth = NumCast(this.props.CollectionView.props.Document.schemaPreviewWidth);
+        const tableWidth = panelWidth - 2 * borderWidth - dividerWidth - previewWidth;
+        this.props.CollectionView.props.Document.schemaPreviewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0;
+    }
+
+    @undoBatch
+    @action
+    toggleTextwrap = async () => {
+        const textwrappedRows = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []);
+        if (textwrappedRows.length) {
+            this.props.CollectionView.props.Document.textwrappedSchemaRows = new List<string>([]);
+        } else {
+            const docs = DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey]);
+            const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]);
+            this.props.CollectionView.props.Document.textwrappedSchemaRows = new List<string>(allRows);
+        }
+    }
+
+
+    render() {
+        const previewWidth = NumCast(this.props.CollectionView.props.Document.schemaPreviewWidth);
+        const textWrapped = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+
+        return (
+            <div className="collectionSchemaViewChrome-cont">
+                <div className="collectionSchemaViewChrome-toggle">
+                    <div className="collectionSchemaViewChrome-label">Show Preview: </div>
+                    <div className="collectionSchemaViewChrome-toggler" onClick={this.togglePreview}>
+                        <div className={"collectionSchemaViewChrome-togglerButton" + (previewWidth !== 0 ? " on" : " off")}>
+                            {previewWidth !== 0 ? "on" : "off"}
+                        </div>
+                    </div>
+                </div>
+            </div >
+        );
+    }
+}
+
+@observer
+export class CollectionTreeViewChrome extends React.Component<CollectionViewChromeProps> {
+
+    get sortAscending() {
+        return this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"];
+    }
+    set sortAscending(value) {
+        this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"] = value;
+    }
+    @computed private get ascending() {
+        return Cast(this.sortAscending, "boolean", null);
+    }
+
+    @action toggleSort = () => {
+        if (this.sortAscending) this.sortAscending = undefined;
+        else if (this.sortAscending === undefined) this.sortAscending = false;
+        else this.sortAscending = true;
+    }
+
+    render() {
+        return (
+            <div className="collectionTreeViewChrome-cont">
+                <button className="collectionTreeViewChrome-sort" onClick={this.toggleSort}>
+                    <div className="collectionTreeViewChrome-sortLabel">
+                        Sort
+                        </div>
+                    <div className="collectionTreeViewChrome-sortIcon" style={{ transform: `rotate(${this.ascending === undefined ? "90" : this.ascending ? "180" : "0"}deg)` }}>
+                        <FontAwesomeIcon icon="caret-up" size="2x" color="white" />
+                    </div>
+                </button>
+            </div>
+        );
+    }
+}
+
+// Enter scroll speed for 3D Carousel 
+@observer
+export class Collection3DCarouselViewChrome extends React.Component<CollectionViewChromeProps> {
+    @computed get scrollSpeed() {
+        return this.props.CollectionView.props.Document._autoScrollSpeed;
+    }
+
+    @action
+    setValue = (value: string) => {
+        const numValue = Number(StrCast(value));
+        if (numValue > 0) {
+            this.props.CollectionView.props.Document._autoScrollSpeed = numValue;
+            return true;
+        }
+        return false;
+    }
+
+    render() {
+        return (
+            <div className="collection3DCarouselViewChrome-cont">
+                <div className="collection3DCarouselViewChrome-scrollSpeed-cont">
+                    <div className="collectionStackingViewChrome-scrollSpeed-label">
+                        AUTOSCROLL SPEED:
+                    </div>
+                    <div className="collection3DCarouselViewChrome-scrollSpeed">
+                        <EditableView
+                            GetValue={() => StrCast(this.scrollSpeed)}
+                            oneLine
+                            SetValue={this.setValue}
+                            contents={this.scrollSpeed ? this.scrollSpeed : 1000} />
+                    </div>
+                </div>
+            </div>
+        );
+    }
+}
+
+/**
+ * Chrome for grid view.
+ */
+@observer
+export class CollectionGridViewChrome extends React.Component<CollectionViewChromeProps> {
+
+    private clicked: boolean = false;
+    private entered: boolean = false;
+    private decrementLimitReached: boolean = false;
+    @observable private resize = false;
+    private resizeListenerDisposer: Opt<Lambda>;
+
+    componentDidMount() {
+
+        runInAction(() => this.resize = this.props.CollectionView.props.PanelWidth() < 700);
+
+        // listener to reduce text on chrome resize (panel resize)
+        this.resizeListenerDisposer = computed(() => this.props.CollectionView.props.PanelWidth()).observe(({ newValue }) => {
+            runInAction(() => this.resize = newValue < 700);
+        });
+    }
+
+    componentWillUnmount() {
+        this.resizeListenerDisposer?.();
+    }
+
+    get numCols() { return NumCast(this.props.CollectionView.props.Document.gridNumCols, 10); }
+
+    /**
+     * Sets the value of `numCols` on the grid's Document to the value entered.
+     */
+    @undoBatch
+    onNumColsEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
+        if (e.key === "Enter" || e.key === "Tab") {
+            if (e.currentTarget.valueAsNumber > 0) {
+                this.props.CollectionView.props.Document.gridNumCols = e.currentTarget.valueAsNumber;
+            }
+
+        }
+    }
+
+    /**
+     * Sets the value of `rowHeight` on the grid's Document to the value entered.
+     */
+    // @undoBatch
+    // onRowHeightEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
+    //     if (e.key === "Enter" || e.key === "Tab") {
+    //         if (e.currentTarget.valueAsNumber > 0 && this.props.CollectionView.props.Document.rowHeight as number !== e.currentTarget.valueAsNumber) {
+    //             this.props.CollectionView.props.Document.rowHeight = e.currentTarget.valueAsNumber;
+    //         }
+    //     }
+    // }
+
+    /**
+     * Sets whether the grid is flexible or not on the grid's Document.
+     */
+    @undoBatch
+    toggleFlex = () => {
+        this.props.CollectionView.props.Document.gridFlex = !BoolCast(this.props.CollectionView.props.Document.gridFlex, true);
+    }
+
+    /**
+     * Increments the value of numCols on button click
+     */
+    onIncrementButtonClick = () => {
+        this.clicked = true;
+        this.entered && (this.props.CollectionView.props.Document.gridNumCols as number)--;
+        undoBatch(() => this.props.CollectionView.props.Document.gridNumCols = this.numCols + 1)();
+        this.entered = false;
+    }
+
+    /**
+     * Decrements the value of numCols on button click
+     */
+    onDecrementButtonClick = () => {
+        this.clicked = true;
+        if (!this.decrementLimitReached) {
+            this.entered && (this.props.CollectionView.props.Document.gridNumCols as number)++;
+            undoBatch(() => this.props.CollectionView.props.Document.gridNumCols = this.numCols - 1)();
+        }
+        this.entered = false;
+    }
+
+    /**
+     * Increments the value of numCols on button hover
+     */
+    incrementValue = () => {
+        this.entered = true;
+        if (!this.clicked && !this.decrementLimitReached) {
+            this.props.CollectionView.props.Document.gridNumCols = this.numCols + 1;
+        }
+        this.decrementLimitReached = false;
+        this.clicked = false;
+    }
+
+    /**
+     * Decrements the value of numCols on button hover
+     */
+    decrementValue = () => {
+        this.entered = true;
+        if (!this.clicked) {
+            if (this.numCols !== 1) {
+                this.props.CollectionView.props.Document.gridNumCols = this.numCols - 1;
+            }
+            else {
+                this.decrementLimitReached = true;
+            }
+        }
+
+        this.clicked = false;
+    }
+
+    /**
+     * Toggles the value of preventCollision
+     */
+    toggleCollisions = () => {
+        this.props.CollectionView.props.Document.gridPreventCollision = !this.props.CollectionView.props.Document.gridPreventCollision;
+    }
+
+    /**
+     * Changes the value of the compactType
+     */
+    changeCompactType = (e: React.ChangeEvent<HTMLSelectElement>) => {
+        // need to change startCompaction so that this operation will be undoable.
+        this.props.CollectionView.props.Document.gridStartCompaction = e.target.selectedOptions[0].value;
+    }
+
+    render() {
+        return (
+            <div className="collectionGridViewChrome-cont" >
+                <span className="grid-control" style={{ width: this.resize ? "25%" : "30%" }}>
+                    <span className="grid-icon">
+                        <FontAwesomeIcon icon="columns" size="1x" />
+                    </span>
+                    <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.numCols.toString()} onKeyDown={this.onNumColsEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
+                    <input className="columnButton" onClick={this.onIncrementButtonClick} onMouseEnter={this.incrementValue} onMouseLeave={this.decrementValue} type="button" value="↑" />
+                    <input className="columnButton" style={{ marginRight: 5 }} onClick={this.onDecrementButtonClick} onMouseEnter={this.decrementValue} onMouseLeave={this.incrementValue} type="button" value="↓" />
+                </span>
+                {/* <span className="grid-control">
+                    <span className="grid-icon">
+                        <FontAwesomeIcon icon="text-height" size="1x" />
+                    </span>
+                    <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.props.CollectionView.props.Document.rowHeight as string} onKeyDown={this.onRowHeightEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
+                </span> */}
+                <span className="grid-control" style={{ width: this.resize ? "12%" : "20%" }}>
+                    <input type="checkbox" style={{ marginRight: 5 }} onChange={this.toggleCollisions} checked={!this.props.CollectionView.props.Document.gridPreventCollision} />
+                    <label className="flexLabel">{this.resize ? "Coll" : "Collisions"}</label>
+                </span>
+
+                <select className="collectionGridViewChrome-viewPicker"
+                    style={{ marginRight: 5 }}
+                    onPointerDown={stopPropagation}
+                    onChange={this.changeCompactType}
+                    value={StrCast(this.props.CollectionView.props.Document.gridStartCompaction, StrCast(this.props.CollectionView.props.Document.gridCompaction))}>
+                    {["vertical", "horizontal", "none"].map(type =>
+                        <option className="collectionGridViewChrome-viewOption"
+                            onPointerDown={stopPropagation}
+                            value={type}>
+                            {this.resize ? type[0].toUpperCase() + type.substring(1) : "Compact: " + type}
+                        </option>
+                    )}
+                </select>
+
+                <span className="grid-control" style={{ width: this.resize ? "12%" : "20%" }}>
+                    <input style={{ marginRight: 5 }} type="checkbox" onChange={this.toggleFlex}
+                        checked={BoolCast(this.props.CollectionView.props.Document.gridFlex, true)} />
+                    <label className="flexLabel">{this.resize ? "Flex" : "Flexible"}</label>
+                </span>
+
+                <button onClick={() => this.props.CollectionView.props.Document.gridResetLayout = true}>
+                    {!this.resize ? "Reset" :
+                        <FontAwesomeIcon icon="redo-alt" size="1x" />}
+                </button>
+
+            </div>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index e18fe319e..6cd6a827f 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -47,7 +47,6 @@ import { SubCollectionViewProps } from './CollectionSubView';
 import { CollectionTimeView } from './CollectionTimeView';
 import { CollectionTreeView } from "./CollectionTreeView";
 import './CollectionView.scss';
-import { CollectionViewBaseChrome } from './CollectionViewChromes';
 import CollectionMenu from './CollectionMenu';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
@@ -235,16 +234,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         }
     }
 
-    @action
-    private collapse = (value: boolean) => {
-        this.props.Document._chromeStatus = value ? "collapsed" : "enabled";
-    }
-
     private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
-        // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip
-        const chrome = this.props.Document._chromeStatus === "disabled" || this.props.Document._chromeStatus === "replaced" || type === CollectionViewType.Docking ? (null) :
-            <CollectionViewBaseChrome key="chrome" CollectionView={this} PanelWidth={this.bodyPanelWidth} type={type} collapse={this.collapse} />;
-        return <>{chrome} {this.SubViewHelper(type, renderProps)}</>;
+        return this.SubViewHelper(type, renderProps);
     }
 
 
-- 
cgit v1.2.3-70-g09d2

