From 2b79008596351f6948d8de80c7887446d97b068c Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Fri, 15 May 2020 00:03:41 -0700
Subject: renamed new_fields to fields

---
 src/client/views/collections/CollectionViewChromes.tsx | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 62b03bbdc..5dc0b09ac 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -2,11 +2,11 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as React from "react";
-import { Doc, DocListCast } from "../../../new_fields/Doc";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { List } from "../../../new_fields/List";
-import { listSpec } from "../../../new_fields/Schema";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { Doc, DocListCast } from "../../../fields/Doc";
+import { Id } from "../../../fields/FieldSymbols";
+import { List } from "../../../fields/List";
+import { listSpec } from "../../../fields/Schema";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
 import { Utils, emptyFunction, setupMoveUpEvents } from "../../../Utils";
 import { DragManager } from "../../util/DragManager";
 import { undoBatch } from "../../util/UndoManager";
-- 
cgit v1.2.3-70-g09d2


From 21cd63dc9536deea17814231605ff22ea59e26b4 Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Mon, 18 May 2020 14:24:28 -0700
Subject: fixed some bugs with webbox and hacked some problems with cognitive
 services

---
 package.json                                                        | 2 +-
 src/client/cognitive_services/CognitiveServices.ts                  | 6 +++++-
 src/client/views/MainView.tsx                                       | 4 ++--
 src/client/views/collections/CollectionViewChromes.tsx              | 1 +
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 5 +++--
 src/client/views/nodes/WebBox.tsx                                   | 6 +++---
 6 files changed, 15 insertions(+), 9 deletions(-)

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

diff --git a/package.json b/package.json
index ef30a3385..926b63f66 100644
--- a/package.json
+++ b/package.json
@@ -263,4 +263,4 @@
         "xregexp": "^4.3.0",
         "youtube": "^0.1.0"
     }
-}
+}
\ No newline at end of file
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 8c63ae906..570d135c4 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -45,7 +45,11 @@ export enum Confidence {
 export namespace CognitiveServices {
 
     const ExecuteQuery = async <D>(service: Service, manager: APIManager<D>, data: D): Promise<any> => {
-        const apiKey = process.env[service.toUpperCase()];
+        let apiKey = process.env[service.toUpperCase()];
+        // A HACK FOR A DEMO VIDEO - syip2
+        if (service === "handwriting") {
+            apiKey = "61088486d76c4b12ba578775a5f55422";
+        }
         if (!apiKey) {
             console.log(`No API key found for ${service}: ensure index.ts has access to a .env file in your root directory.`);
             return undefined;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 9bc08de3e..d03bbc8d9 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -599,8 +599,8 @@ export class MainView extends React.Component {
     @computed get snapLines() {
         return <div className="mainView-snapLines">
             <svg style={{ width: "100%", height: "100%" }}>
-                {SnappingManager.horizSnapLines().map(l => <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
-                {SnappingManager.vertSnapLines().map(l => <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
+                {/* {SnappingManager.horizSnapLines().map(l => <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
+                {SnappingManager.vertSnapLines().map(l => <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)} */}
             </svg>
         </div>;
     }
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 62b03bbdc..548956d11 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -371,6 +371,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
         this.suggestions = [];
     }
 
+    @action
     setValue = (value: string) => {
         this.props.CollectionView.props.Document._pivotField = value;
         return true;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c6bae66a4..c3a77841e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -438,9 +438,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 console.log("end");
                 if (this._inkToTextStartX && this._inkToTextStartY) {
                     const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
-                    const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === "text" && s.color);
+                    const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === "rtf" && s.color);
                     const sets = setDocs.map((sd) => {
-                        return Cast(sd.data, RichTextField)?.Text as string;
+                        return Cast(sd.text, RichTextField)?.Text as string;
                     });
                     if (sets.length && sets[0]) {
                         this._wordPalette.clear();
@@ -476,6 +476,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                         }
                     });
 
+                    console.log(this._wordPalette)
                     CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => {
                         console.log(results);
                         const wordResults = results.filter((r: any) => r.category === "inkWord");
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 6d4cc1a07..04878cf35 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -77,7 +77,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
 
         this.setURL();
 
-        this._iframeRef.current!.setAttribute("enable-annotation", "true");
+        this._iframeRef.current?.setAttribute("enable-annotation", "true");
 
         document.addEventListener("pointerup", this.onLongPressUp);
         document.addEventListener("pointermove", this.onLongPressMove);
@@ -101,8 +101,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         this._reactionDisposer?.();
         document.removeEventListener("pointerup", this.onLongPressUp);
         document.removeEventListener("pointermove", this.onLongPressMove);
-        this._iframeRef.current!.contentDocument?.removeEventListener('pointerdown', this.iframedown);
-        this._iframeRef.current!.contentDocument?.removeEventListener('scroll', this.iframeScrolled);
+        this._iframeRef.current?.contentDocument?.removeEventListener('pointerdown', this.iframedown);
+        this._iframeRef.current?.contentDocument?.removeEventListener('scroll', this.iframeScrolled);
     }
 
     @action
-- 
cgit v1.2.3-70-g09d2


From 895e84fe6999a39eb9553db68b378aee5c1864a3 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 29 May 2020 01:29:02 -0400
Subject: moved frame animation into freeform chrome.   fixed marquees of docs
 at non-zero frame.  fixed pile view to use its own _pileLayoutEngine key to
 avoid leaving colleciton in an animated state.  added toggle for snap linew
 views.  cached transforms in CollectionFreeformView for performance.

---
 src/client/views/MainView.tsx                      |   6 +-
 .../views/collections/CollectionPileView.tsx       |   8 +-
 .../views/collections/CollectionViewChromes.scss   |  60 +++++-
 .../views/collections/CollectionViewChromes.tsx    | 201 +++++++++++++--------
 .../collectionFreeForm/CollectionFreeFormView.scss |  26 ---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  68 +++----
 .../collections/collectionFreeForm/MarqueeView.tsx |  12 +-
 src/client/views/nodes/DocumentView.tsx            |   9 +-
 src/client/views/nodes/PresBox.tsx                 |   3 -
 9 files changed, 223 insertions(+), 170 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 958ba0ce3..a0a6adab7 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -534,10 +534,10 @@ export class MainView extends React.Component {
     }
 
     @computed get snapLines() {
-        return <div className="mainView-snapLines">
+        return !Doc.UserDoc().showSnapLines ? (null) : <div className="mainView-snapLines">
             <svg style={{ width: "100%", height: "100%" }}>
-                {/* {SnappingManager.horizSnapLines().map(l => <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
-                {SnappingManager.vertSnapLines().map(l => <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)} */}
+                {SnappingManager.horizSnapLines().map(l => <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
+                {SnappingManager.vertSnapLines().map(l => <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
             </svg>
         </div>;
     }
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 020a87b2e..fc48e0327 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -23,7 +23,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
     @observable _childClickedScript: Opt<ScriptField>;
     componentDidMount() {
         if (this.layoutEngine() !== "pass" && this.layoutEngine() !== "starburst") {
-            this.Document._layoutEngine = "pass";
+            this.Document._pileLayoutEngine = "pass";
         }
         this._originalChrome = StrCast(this.layoutDoc._chromeStatus);
         this.layoutDoc._chromeStatus = "disabled";
@@ -34,7 +34,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
         this.layoutDoc._chromeStatus = this._originalChrome;
     }
 
-    layoutEngine = () => StrCast(this.Document._layoutEngine);
+    layoutEngine = () => StrCast(this.Document._pileLayoutEngine);
 
     @computed get contents() {
         return <div className="collectionPileView-innards" style={{ pointerEvents: this.layoutEngine() === "starburst" ? undefined : "none" }} >
@@ -53,7 +53,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
             Doc.pileup(this.childDocs);
             this.layoutDoc._panX = 0;
             this.layoutDoc._panY = -10;
-            this.props.Document._layoutEngine = 'pass';
+            this.props.Document._pileLayoutEngine = 'pass';
         } else {
             const defaultSize = 25;
             this.layoutDoc._overflow = 'visible';
@@ -67,7 +67,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
             }
             this.layoutDoc._panX = this.layoutDoc._panY = 0;
             this.layoutDoc._width = this.layoutDoc._height = defaultSize;
-            this.props.Document._layoutEngine = 'starburst';
+            this.props.Document._pileLayoutEngine = 'starburst';
         }
     });
 
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index e4581eb46..03bd9a01a 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -8,10 +8,12 @@
     z-index: 9001;
     transition: top .5s;
     background: lightgrey;
+    transform-origin: top left;
 
     .collectionViewChrome {
         display: flex;
-        padding-bottom: 10px;
+        padding-bottom: 1px;
+        height:32px;
         border-bottom: .5px solid rgb(180, 180, 180);
         overflow: hidden;
 
@@ -21,12 +23,12 @@
             .collectionViewBaseChrome-viewPicker {
                 font-size: 75%;
                 //text-transform: uppercase;
-                letter-spacing: 2px;
+                //letter-spacing: 2px;
                 background: rgb(238, 238, 238);
                 color: grey;
                 outline-color: black;
                 border: none;
-                padding: 12px 10px 11px 10px;
+                //padding: 12px 10px 11px 10px;
             }
 
             .collectionViewBaseChrome-viewPicker:active {
@@ -47,6 +49,7 @@
             .collectionViewBaseChrome-cmdPicker {
                 margin-left: 3px;
                 margin-right: 0px;
+                font-size: 75%;
                 background: rgb(238, 238, 238);
                 border: none;
                 color: grey;
@@ -56,6 +59,7 @@
                 background-color: gray;
                 display: flex;
                 flex-direction: row;
+                height:30px;
                 .commandEntry-drop {
                     color:white;
                     width:25px;
@@ -95,8 +99,8 @@
                     margin: auto;
                     background: gray;
                     color: white;
-                    width: 40px;
-                    height: 40px;
+                    width: 30px;
+                    height: 30px;
                     align-items: center;
                     justify-content: center;
                 }
@@ -195,8 +199,7 @@
             .collectionTreeViewChrome-pivotField-label {
                 vertical-align: center;
                 padding-left: 10px;
-                padding-top: 10px;
-                padding-bottom: 10px;
+                margin:auto;
             }
 
             .collectionStackingViewChrome-pivotField,
@@ -204,14 +207,15 @@
                 color: white;
                 width:100%;
                 min-width: 100px;
-                text-align: center;
+                display: flex;
+                align-items: center;
                 background: rgb(238, 238, 238);
 
                 .editable-view-input,
                 input,
                 .editableView-container-editing-oneLine,
                 .editableView-container-editing {
-                    padding: 12px 10px 11px 10px;
+                    margin:auto;
                     border: 0px;
                     color: grey;
                     text-align: center;
@@ -235,6 +239,44 @@
     }
 }
 
+.collectionFreeFormViewChrome-cont {
+    width: 60px;
+    display: flex;
+    position: relative;
+    align-items: center;
+    .fwdKeyframe, .numKeyframe, .backKeyframe {
+        cursor: pointer;
+        position: absolute;
+        width: 20;
+        height: 30;
+        bottom: 0;
+        background: gray;
+        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;
+        }
+    }
+}
+
 .collectionSchemaViewChrome-cont {
     display: flex;
     font-size: 10.5px;
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 8c4d53d63..29a3e559a 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -15,6 +15,7 @@ import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss";
 import { CollectionViewType } from "./CollectionView";
 import { CollectionView } from "./CollectionView";
 import "./CollectionViewChromes.scss";
+import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
 const datepicker = require('js-datepicker');
 
 interface CollectionViewChromeProps {
@@ -44,7 +45,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
         initialize: emptyFunction,
     };
     _narrativeCommand = {
-        params: ["target", "source"], title: "=> click clicked open view",
+        params: ["target", "source"], title: "=> child click view",
         script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])",
         immediate: (source: Doc[]) => this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]),
         initialize: emptyFunction,
@@ -83,22 +84,16 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     private _viewRef = React.createRef<HTMLInputElement>();
     @observable private _currentKey: string = "";
 
-    componentDidMount = () => {
-        runInAction(() => {
-            // chrome status is one of disabled, collapsed, or visible. this determines initial state from document
-            const chromeStatus = this.props.CollectionView.props.Document._chromeStatus;
-            if (chromeStatus) {
-                if (chromeStatus === "disabled") {
-                    throw new Error("how did you get here, if chrome status is 'disabled' on a collection, a chrome shouldn't even be instantiated!");
-                }
-                else if (chromeStatus === "collapsed") {
-                    if (this.props.collapse) {
-                        this.props.collapse(true);
-                    }
-                }
-            }
-        });
-    }
+    componentDidMount = action(() => {
+        // chrome status is one of disabled, collapsed, or visible. this determines initial state from document
+        switch (this.props.CollectionView.props.Document._chromeStatus) {
+            case "disabled":
+                throw new Error("how did you get here, if chrome status is 'disabled' on a collection, a chrome shouldn't even be instantiated!");
+            case "collapsed":
+                this.props.collapse?.(true);
+                break;
+        }
+    })
 
     @undoBatch
     viewChanged = (e: React.ChangeEvent) => {
@@ -197,10 +192,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
         }
     }
 
-    subChrome = () => {
+    @computed get subChrome() {
         const collapsed = this.document._chromeStatus !== "enabled";
         if (collapsed) return null;
         switch (this.props.type) {
+            case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} 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} />);
@@ -236,7 +232,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
             const vtype = this.props.CollectionView.collectionViewType;
             const c = {
                 params: ["target"], title: vtype,
-                script: `this.target._viewType = ${StrCast(this.props.CollectionView.props.Document._viewType)}`,
+                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,
             };
@@ -254,14 +250,61 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
         }, emptyFunction, emptyFunction);
     }
 
+    @computed get templateChrome() {
+        const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
+        return <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} style={{ display: collapsed ? "none" : undefined }}>
+            <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 => ["invalid", "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 collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
+        const scale = Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform().Scale);
         return (
             <div className="collectionViewChrome-cont" style={{
                 top: collapsed ? -70 : 0, height: collapsed ? 0 : undefined,
-                transform: collapsed ? "" : `scale(${Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform().Scale)})`,
-                transformOrigin: "top left",
-                width: `${this.props.PanelWidth() / Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform().Scale)}px`
+                transform: collapsed ? "" : `scale(${scale})`,
+                width: `${this.props.PanelWidth() / scale}px`
             }}>
                 <div className="collectionViewChrome" style={{ border: "unset", pointerEvents: collapsed ? "none" : undefined }}>
                     <div className="collectionViewBaseChrome">
@@ -276,58 +319,71 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                             title="Collapse collection chrome" onClick={this.toggleCollapse}>
                             <FontAwesomeIcon icon="caret-up" size="2x" />
                         </button>
-                        <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"></FontAwesomeIcon>
-                                </div>
-                                <select
-                                    className="collectionViewBaseChrome-viewPicker"
-                                    onPointerDown={stopPropagation}
-                                    onChange={this.viewChanged}
-                                    value={StrCast(this.props.CollectionView.props.Document._viewType)}>
-                                    {Object.values(CollectionViewType).map(type => ["invalid", "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>
+                        {this.viewModes}
                         <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}>
                             <div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.toggleViewSpecs} >
                                 <FontAwesomeIcon icon="filter" size="2x" />
                             </div>
                         </div>
-                        <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} style={{ display: collapsed ? "none" : undefined }}>
-                            <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={""}>{""}</option>
-                                    {this._buttonizableCommands.map(cmd =>
-                                        <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>{cmd.title}</option>
-                                    )}
-                                </select>
-                            </div>
-                        </div>
+                        {this.templateChrome}
                     </div>
-                    {this.subChrome()}
+                    {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>;
+    }
+}
+
 @observer
 export class CollectionStackingViewChrome extends React.Component<CollectionViewChromeProps> {
     @observable private _currentKey: string = "";
@@ -474,19 +530,20 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionViewCh
 @observer
 export class CollectionTreeViewChrome extends React.Component<CollectionViewChromeProps> {
 
-    get dataExtension() {
-        return this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "_ext"] as Doc;
+    get sortAscending() {
+        return this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"];
     }
-    @computed private get descending() {
-        return this.dataExtension && Cast(this.dataExtension.sortAscending, "boolean", null);
+    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.dataExtension) {
-            if (this.dataExtension.sortAscending) this.dataExtension.sortAscending = undefined;
-            else if (this.dataExtension.sortAscending === undefined) this.dataExtension.sortAscending = false;
-            else this.dataExtension.sortAscending = true;
-        }
+        if (this.sortAscending) this.sortAscending = undefined;
+        else if (this.sortAscending === undefined) this.sortAscending = false;
+        else this.sortAscending = true;
     }
 
     render() {
@@ -496,7 +553,7 @@ export class CollectionTreeViewChrome extends React.Component<CollectionViewChro
                     <div className="collectionTreeViewChrome-sortLabel">
                         Sort
                         </div>
-                    <div className="collectionTreeViewChrome-sortIcon" style={{ transform: `rotate(${this.descending === undefined ? "90" : this.descending ? "180" : "0"}deg)` }}>
+                    <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>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 5478a1c4a..d9011c9d3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -41,32 +41,6 @@
     // touch action none means that the browser will handle none of the touch actions. this allows us to implement our own actions.
     touch-action: none;
 
-    .fwdKeyframe, .numKeyframe, .backKeyframe{
-        cursor: pointer;
-        position: absolute;
-        width: 20;
-        height:20;
-        bottom:0;
-        background:gray;
-    }
-    .backKeyframe {
-        right:45;
-        svg {
-            display:block;
-            margin:auto;
-        }
-    }
-    .numKeyframe {
-        right:25;
-        text-align:center;
-    }
-    .fwdKeyframe {
-        right:5;
-        svg {
-            display:block;
-            margin:auto;
-        }
-    }
 
     .collectionfreeformview-placeholder {
         background: gray;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index cb1228fed..c753a703d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,7 +1,7 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye, faEyeSlash } from "@fortawesome/free-regular-svg-icons";
 import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, _allowStateChangesInsideComputed } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, _allowStateChangesInsideComputed, trace } from "mobx";
 import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
 import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../fields/Doc";
@@ -118,12 +118,28 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
         this.Document.scale || 1)
 
-    private centeringShiftX = () => !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling / this.contentScaling : 0;  // shift so pan position is at center of window for non-overlay collections
-    private centeringShiftY = () => !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling / this.contentScaling : 0;// shift so pan position is at center of window for non-overlay collections
-    private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
-    private getTransformOverlay = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1);
-    private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
-    private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
+    @computed get cachedCenteringShiftX(): number {
+        return !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling / this.contentScaling : 0;  // shift so pan position is at center of window for non-overlay collections
+    }
+    @computed get cachedCenteringShiftY(): number {
+        return !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling / this.contentScaling : 0;// shift so pan position is at center of window for non-overlay collections
+    }
+    @computed get cachedGetLocalTransform(): Transform {
+        return Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
+    }
+    @computed get cachedGetContainerTransform(): Transform {
+        return this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
+    }
+    @computed get cachedGetTransform(): Transform {
+        return this.getTransformOverlay().translate(- this.cachedCenteringShiftX, - this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform);
+    }
+
+    private centeringShiftX = () => this.cachedCenteringShiftX;
+    private centeringShiftY = () => this.cachedCenteringShiftY;
+    private getTransform = () => this.cachedGetTransform.copy();
+    private getLocalTransform = () => this.cachedGetLocalTransform.copy();
+    private getContainerTransform = () => this.cachedGetContainerTransform.copy();
+    private getTransformOverlay = () => this.getContainerTransform().translate(1, 1);
     private addLiveTextBox = (newBox: Doc) => {
         FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
         this.addDocument(newBox);
@@ -162,30 +178,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         return retVal;
     })
 
-    @undoBatch
-    @action
-    nextKeyframe = (): void => {
-        const currentFrame = 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 = 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);
-    }
-
     private selectDocuments = (docs: Doc[]) => {
         SelectionManager.DeselectAll();
         docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
@@ -1219,6 +1211,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
 
         optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
+        optionItems.push({ description: "toggle snap line display", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" });
         optionItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
         optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
         optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
@@ -1397,18 +1390,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             {!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ?
                 this.placeholder : this.marqueeView}
             <CollectionFreeFormOverlayView elements={this.elementFunc} />
-            {this.isAnnotationOverlay || !this.props.isSelected() || this.props.Document._viewType === CollectionViewType.Pile ? (null) :
-                <>
-                    <div key="back" className="backKeyframe" onClick={this.prevKeyframe}>
-                        <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
-                    </div>
-                    <div key="num" 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" className="fwdKeyframe" onClick={this.nextKeyframe}>
-                        <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
-                    </div>
-                </>}
+
             <div className={"pullpane-indicator"}
                 style={{
                     display: this._pullDirection ? "block" : "none",
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index ed70ac9e8..cdfeeaa6b 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -357,10 +357,14 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         const selected = this.marqueeSelect(false);
         if (e instanceof KeyboardEvent ? e.key === "c" : true) {
             selected.map(action(d => {
-                //this.props.removeDocument(d);
-                d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
-                d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
-                d.displayTimecode = undefined;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+                const dx = NumCast(d.x);
+                const dy = NumCast(d.y);
+                delete d.x;
+                delete d.y;
+                delete d.activeFrame;
+                delete d.displayTimecode;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+                d.x = dx - bounds.left - bounds.width / 2;
+                d.y = dy - bounds.top - bounds.height / 2;
                 return d;
             }));
             this.props.removeDocument(selected);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 00d19752f..2de7b3460 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -965,9 +965,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return this.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
     }
     childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
-    panelWidth = () => this.props.PanelWidth();
-    panelHeight = () => this.props.PanelHeight();
-    screenToLocalTransform = () => this.props.ScreenToLocalTransform();
     @computed get contents() {
         TraceMobx();
         return (<>
@@ -988,10 +985,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 addDocument={this.props.addDocument}
                 removeDocument={this.props.removeDocument}
                 moveDocument={this.props.moveDocument}
-                ScreenToLocalTransform={this.screenToLocalTransform}
+                ScreenToLocalTransform={this.props.ScreenToLocalTransform}
                 renderDepth={this.props.renderDepth}
-                PanelWidth={this.panelWidth}
-                PanelHeight={this.panelHeight}
+                PanelWidth={this.props.PanelWidth}
+                PanelHeight={this.props.PanelHeight}
                 ignoreAutoHeight={this.props.ignoreAutoHeight}
                 focus={this.props.focus}
                 parentActive={this.props.parentActive}
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 56a543f46..9f1e99c77 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -62,11 +62,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
         const lastFrame = Cast(presTargetDoc.lastFrame, "number", null);
         const curFrame = NumCast(presTargetDoc.currentFrame);
         if (lastFrame !== undefined && curFrame < lastFrame) {
-            //const docs = DocListCast(presTargetDoc[Doc.LayoutFieldKey(presTargetDoc)]);
             presTargetDoc.transition = "all 1s";
             setTimeout(() => presTargetDoc.transition = undefined, 1010);
-            // docs.forEach(doc => doc.transition = "all 1s");
-            // setTimeout(() => docs.forEach(doc => doc.transition = undefined), 1010);
             presTargetDoc.currentFrame = curFrame + 1;
         }
         else if (this.childDocs[this.itemIndex + 1] !== undefined) {
-- 
cgit v1.2.3-70-g09d2

