From 0bd7f4f85be56de4326f0671453fc5e5e917a5d0 Mon Sep 17 00:00:00 2001
From: Lionel Han <47760119+IGoByJoe@users.noreply.github.com>
Date: Sun, 7 Jun 2020 21:22:25 -0700
Subject: pull current work in

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 80ee2a65d..8b0c72f5e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -597,7 +597,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
         // bcz: theres should be a better way of doing these than referencing these static instances directly
         MarqueeOptionsMenu.Instance?.fadeOut(true);// I think it makes sense for the marquee menu to go away when panned. -syip2
-        PDFMenu.Instance.fadeOut(true);
+        // PDFMenu.Instance.fadeOut(true);
 
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
         this.setPan((this.Document._panX || 0) - dx, (this.Document._panY || 0) - dy, undefined, true);
-- 
cgit v1.2.3-70-g09d2


From 639be55eaff422e1e982cf41d09edd9e96f014a4 Mon Sep 17 00:00:00 2001
From: yunahi <60233430+yunahi@users.noreply.github.com>
Date: Thu, 18 Jun 2020 14:10:34 +0900
Subject: updated menu

---
 package-lock.json                                  |  44 +++--
 src/client/documents/Documents.ts                  |  10 +-
 src/client/util/CurrentUserUtils.ts                |   4 +
 src/client/util/InteractionUtils.tsx               |  64 +++++--
 src/client/views/DocumentDecorations.scss          |  50 +++--
 src/client/views/DocumentDecorations.tsx           |  81 +++++++-
 src/client/views/GestureOverlay.tsx                |  21 ++-
 src/client/views/InkingStroke.tsx                  |  41 ++++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   4 +-
 .../collectionFreeForm/InkOptionsMenu.tsx          | 203 +++++++++++++++++++--
 src/fields/InkField.ts                             |   4 +-
 11 files changed, 459 insertions(+), 67 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index c956fd3e9..cd80ecc5e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2832,7 +2832,8 @@
                         },
                         "ansi-regex": {
                             "version": "2.1.1",
-                            "bundled": true
+                            "bundled": true,
+                            "optional": true
                         },
                         "aproba": {
                             "version": "1.2.0",
@@ -2850,11 +2851,13 @@
                         },
                         "balanced-match": {
                             "version": "1.0.0",
-                            "bundled": true
+                            "bundled": true,
+                            "optional": true
                         },
                         "brace-expansion": {
                             "version": "1.1.11",
                             "bundled": true,
+                            "optional": true,
                             "requires": {
                                 "balanced-match": "^1.0.0",
                                 "concat-map": "0.0.1"
@@ -2867,15 +2870,18 @@
                         },
                         "code-point-at": {
                             "version": "1.1.0",
-                            "bundled": true
+                            "bundled": true,
+                            "optional": true
                         },
                         "concat-map": {
                             "version": "0.0.1",
-                            "bundled": true
+                            "bundled": true,
+                            "optional": true
                         },
                         "console-control-strings": {
                             "version": "1.1.0",
-                            "bundled": true
+                            "bundled": true,
+                            "optional": true
                         },
                         "core-util-is": {
                             "version": "1.0.2",
@@ -2978,7 +2984,8 @@
                         },
                         "inherits": {
                             "version": "2.0.4",
-                            "bundled": true
+                            "bundled": true,
+                            "optional": true
                         },
                         "ini": {
                             "version": "1.3.5",
@@ -2988,6 +2995,7 @@
                         "is-fullwidth-code-point": {
                             "version": "1.0.0",
                             "bundled": true,
+                            "optional": true,
                             "requires": {
                                 "number-is-nan": "^1.0.0"
                             }
@@ -3000,17 +3008,20 @@
                         "minimatch": {
                             "version": "3.0.4",
                             "bundled": true,
+                            "optional": true,
                             "requires": {
                                 "brace-expansion": "^1.1.7"
                             }
                         },
                         "minimist": {
                             "version": "1.2.5",
-                            "bundled": true
+                            "bundled": true,
+                            "optional": true
                         },
                         "minipass": {
                             "version": "2.9.0",
                             "bundled": true,
+                            "optional": true,
                             "requires": {
                                 "safe-buffer": "^5.1.2",
                                 "yallist": "^3.0.0"
@@ -3027,6 +3038,7 @@
                         "mkdirp": {
                             "version": "0.5.3",
                             "bundled": true,
+                            "optional": true,
                             "requires": {
                                 "minimist": "^1.2.5"
                             }
@@ -3082,7 +3094,8 @@
                         },
                         "npm-normalize-package-bin": {
                             "version": "1.0.1",
-                            "bundled": true
+                            "bundled": true,
+                            "optional": true
                         },
                         "npm-packlist": {
                             "version": "1.4.8",
@@ -3107,7 +3120,8 @@
                         },
                         "number-is-nan": {
                             "version": "1.0.1",
-                            "bundled": true
+                            "bundled": true,
+                            "optional": true
                         },
                         "object-assign": {
                             "version": "4.1.1",
@@ -3117,6 +3131,7 @@
                         "once": {
                             "version": "1.4.0",
                             "bundled": true,
+                            "optional": true,
                             "requires": {
                                 "wrappy": "1"
                             }
@@ -3185,7 +3200,8 @@
                         },
                         "safe-buffer": {
                             "version": "5.1.2",
-                            "bundled": true
+                            "bundled": true,
+                            "optional": true
                         },
                         "safer-buffer": {
                             "version": "2.1.2",
@@ -3215,6 +3231,7 @@
                         "string-width": {
                             "version": "1.0.2",
                             "bundled": true,
+                            "optional": true,
                             "requires": {
                                 "code-point-at": "^1.0.0",
                                 "is-fullwidth-code-point": "^1.0.0",
@@ -3232,6 +3249,7 @@
                         "strip-ansi": {
                             "version": "3.0.1",
                             "bundled": true,
+                            "optional": true,
                             "requires": {
                                 "ansi-regex": "^2.0.0"
                             }
@@ -3270,11 +3288,13 @@
                         },
                         "wrappy": {
                             "version": "1.0.2",
-                            "bundled": true
+                            "bundled": true,
+                            "optional": true
                         },
                         "yallist": {
                             "version": "3.1.1",
-                            "bundled": true
+                            "bundled": true,
+                            "optional": true
                         }
                     }
                 }
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 8d867348f..cbf97e8e8 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -37,7 +37,7 @@ import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
 import { QueryBox } from "../views/nodes/QueryBox";
 import { ColorBox } from "../views/nodes/ColorBox";
 import { DocHolderBox } from "../views/nodes/DocHolderBox";
-import { InkingStroke, ActiveInkColor, ActiveInkWidth, ActiveInkBezierApprox } from "../views/InkingStroke";
+import { InkingStroke, ActiveInkColor, ActiveInkWidth, ActiveInkBezierApprox, ActiveFillColor, ActiveArrowStart, ActiveArrowEnd, ActiveDash } from "../views/InkingStroke";
 import { InkField } from "../../fields/InkField";
 import { RichTextField } from "../../fields/RichTextField";
 import { extname } from "path";
@@ -631,13 +631,17 @@ export namespace Docs {
             return doc;
         }
 
-        export function InkDocument(color: string, tool: string, strokeWidth: string, strokeBezier: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) {
+        export function InkDocument(color: string, tool: string, strokeWidth: string, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) {
             const I = new Doc();
             I.type = DocumentType.INK;
             I.layout = InkingStroke.LayoutString("data");
             I.color = color;
             I.strokeWidth = strokeWidth;
             I.strokeBezier = strokeBezier;
+            I.fillColor = fillColor;
+            I.arrowStart = arrowStart;
+            I.arrowEnd = arrowEnd;
+            I.dash = dash;
             I.tool = tool;
             I.title = "ink";
             I.x = options.x;
@@ -852,7 +856,7 @@ export namespace DocUtils {
             created = Docs.Create.AudioDocument((field).url.href, resolved);
             layout = AudioBox.LayoutString;
         } else if (field instanceof InkField) {
-            created = Docs.Create.InkDocument(ActiveInkColor(), Doc.GetSelectedTool(), ActiveInkWidth(), ActiveInkBezierApprox(), (field).inkData, resolved);
+            created = Docs.Create.InkDocument(ActiveInkColor(), Doc.GetSelectedTool(), ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), (field).inkData, resolved);
             layout = InkingStroke.LayoutString;
         } else if (field instanceof List && field[0] instanceof Doc) {
             created = Docs.Create.StackingDocument(DocListCast(field), resolved);
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index b0cea9947..3d0c0ef4a 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -691,6 +691,10 @@ export class CurrentUserUtils {
         doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)");
         doc.activeInkWidth = StrCast(doc.activeInkWidth, "1");
         doc.activeInkBezier = StrCast(doc.activeInkBezier, "");
+        doc.activeFillColor = StrCast(doc.activeFillColor, "none");
+        doc.activeArrowStart = StrCast(doc.activeArrowStart, "none");
+        doc.activeArrowEnd = StrCast(doc.activeArrowEnd, "none");
+        doc.activeDash = StrCast(doc.activeDash, "0");
         doc.fontSize = NumCast(doc.fontSize, 12);
         doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); // 
         doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); // 
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 81f9b9362..c6b96df45 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -1,6 +1,7 @@
 import React = require("react");
 import * as beziercurve from 'bezier-curve';
 import * as fitCurve from 'fit-curve';
+import InkOptionsMenu from "../views/collections/collectionFreeForm/InkOptionsMenu";
 
 export namespace InteractionUtils {
     export const MOUSETYPE = "mouse";
@@ -89,7 +90,8 @@ export namespace InteractionUtils {
         return myTouches;
     }
 
-    export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: string, bezier: string, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean) {
+
+    export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: string, bezier: string, fill: string, arrowStart: string, arrowEnd: string, dash: string, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean) {
         var pts = "";
         if (shape) {
             //if any of the shape are true
@@ -116,22 +118,58 @@ export namespace InteractionUtils {
             //in the middle of drawing
             pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X * scalex - left * scalex},${pt.Y * scaley - top * scaley} `, "");
         }
+        const dashArray = String(Number(width) * Number(dash));
+
         return (
-            <polyline
-                points={pts}
-                style={{
-                    filter: drawHalo ? "url(#dangerShine)" : undefined,
-                    fill: "none",
-                    pointerEvents: pevents as any,
-                    stroke: color ?? "rgb(0, 0, 0)",
-                    strokeWidth: parseInt(width),
-                    strokeLinejoin: "round",
-                    strokeLinecap: "round"
-                }}
-            />
+            <svg>
+                <defs>
+
+                    {/* {makeArrow()} */}
+                    <marker id="dot" orient="auto" overflow="visible">
+                        <circle r={1} fill="red" />
+                    </marker>
+                    <marker id="arrowHead" orient="auto" overflow="visible" refX="3" refY="1" markerWidth="10" markerHeight="7">
+                        {/* <rect width={strokeWidth} height={strokeWidth} transform='rotate(45)' fill="dodgerblue" /> */}
+                        <polygon points="3 0, 3 2, 0 1" fill="dodgerblue" />
+                    </marker>
+                    <marker id="arrowEnd" orient="auto" overflow="visible" refX="0" refY="1" markerWidth="10" markerHeight="7">
+                        {/* <rect width={strokeWidth} height={strokeWidth} transform='rotate(45)' fill="dodgerblue" /> */}
+                        <polygon points="0 0, 3 1, 0 2" fill={"#" + color} />
+                    </marker>
+                </defs>
+
+                <polyline
+                    points={pts}
+                    style={{
+                        filter: drawHalo ? "url(#dangerShine)" : undefined,
+                        fill: fill,
+                        pointerEvents: pevents as any,
+                        stroke: color ?? "rgb(0, 0, 0)",
+                        strokeWidth: parseInt(width),
+                        strokeLinejoin: "round",
+                        strokeLinecap: "round",
+                        strokeDasharray: dashArray
+                    }}
+                    markerStart={arrowStart}
+                    markerEnd={arrowEnd}
+                />
+            </svg>
+
         );
     }
 
+    // export function makeArrow() {
+    //     return (
+    //         InkOptionsMenu.Instance.getColors().map(color => {
+    //             const id1 = "arrowHeadTest" + color;
+    //             console.log(color);
+    //             <marker id={id1} orient="auto" overflow="visible" refX="0" refY="1" markerWidth="10" markerHeight="7">
+    //                 <polygon points="0 0, 3 1, 0 2" fill={"#" + color} />
+    //             </marker>;
+    //         })
+    //     );
+    // }
+
     export function makePolygon(shape: string, points: { X: number, Y: number }[]) {
         if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y + 1 === points[0].Y) {
             //pointer is up (first and last points are the same)
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 0a96b058b..cf5871bda 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -22,11 +22,20 @@ $linkGap : 3px;
     }
 
 
+    .documentDecorations-rotation {
+        pointer-events: auto;
+        // cursor: grabbing;
+        cursor: ns-resize;
+        width: 10px;
+        height: 10px;
+    }
+
     .documentDecorations-resizer {
         pointer-events: auto;
         background: $alt-accent;
         opacity: 0.1;
     }
+
     .documentDecorations-resizer:hover {
         opacity: 1;
     }
@@ -87,14 +96,17 @@ $linkGap : 3px;
         background: unset;
         opacity: 1;
     }
+
     #documentDecorations-topLeftResizer {
-        border-left:  2px solid;
-        border-top:  solid 2px;
+        border-left: 2px solid;
+        border-top: solid 2px;
     }
+
     #documentDecorations-bottomRightResizer {
-        border-right:  2px solid;
-        border-bottom:  solid 2px;
+        border-right: 2px solid;
+        border-bottom: solid 2px;
     }
+
     #documentDecorations-topLeftResizer:hover,
     #documentDecorations-bottomRightResizer:hover {
         opacity: 1;
@@ -110,14 +122,17 @@ $linkGap : 3px;
         background: unset;
         opacity: 1;
     }
+
     #documentDecorations-topRightResizer {
-        border-right:  2px solid;
-        border-top:  2px solid;
+        border-right: 2px solid;
+        border-top: 2px solid;
     }
+
     #documentDecorations-bottomLeftResizer {
-        border-left:  2px solid;
-        border-bottom:  2px solid;
+        border-left: 2px solid;
+        border-bottom: 2px solid;
     }
+
     #documentDecorations-topRightResizer:hover,
     #documentDecorations-bottomLeftResizer:hover {
         cursor: nesw-resize;
@@ -139,10 +154,11 @@ $linkGap : 3px;
         width: 25px;
         height: calc(100% + 8px); // 8px for the height of the top resizer bar
         grid-column-start: 2;
-        grid-column-end : 2;
+        grid-column-end: 2;
         pointer-events: all;
         padding-left: 5px;
     }
+
     .documentDecorations-title {
         opacity: 1;
         grid-column-start: 3;
@@ -150,14 +166,16 @@ $linkGap : 3px;
         pointer-events: auto;
         overflow: hidden;
         text-align: center;
-        display: flex;    
+        display: flex;
         border-bottom: solid 1px;
-        margin-left:10px;
+        margin-left: 10px;
         width: calc(100% - 10px);
     }
+
     .focus-visible {
-        margin-left:0px;
+        margin-left: 0px;
     }
+
     .publishBox {
         width: 20px;
         height: 22px;
@@ -194,8 +212,9 @@ $linkGap : 3px;
     width: 8px;
     height: $MINIMIZED_ICON_SIZE;
     max-height: 20px;
-    > svg {
-        margin:0;
+
+    >svg {
+        margin: 0;
     }
 }
 
@@ -332,7 +351,8 @@ $linkGap : 3px;
     padding: 2px 12px;
     list-style: none;
 
-    .templateToggle, .chromeToggle {
+    .templateToggle,
+    .chromeToggle {
         text-align: left;
     }
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6ca7331d6..8cb0c83ed 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -17,11 +17,13 @@ import { DocumentButtonBar } from './DocumentButtonBar';
 import './DocumentDecorations.scss';
 import { DocumentView } from "./nodes/DocumentView";
 import React = require("react");
-import { Id } from '../../fields/FieldSymbols';
+import { Id, Copy, Update } from '../../fields/FieldSymbols';
 import e = require('express');
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { SnappingManager } from '../util/SnappingManager';
 import { HtmlField } from '../../fields/HtmlField';
+import { InkData, InkField, InkTool } from "../../fields/InkField";
+import { update } from 'serializr';
 
 library.add(faCaretUp);
 library.add(faObjectGroup);
@@ -50,6 +52,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     private _resizeUndo?: UndoManager.Batch;
     private _offX = 0; _offY = 0;  // offset from click pt to inner edge of resize border
     private _snapX = 0; _snapY = 0; // last snapped location of resize border
+    private _prevX = 0;
+    private _prevY = 0;
+    private _centerPoints: { X: number, Y: number }[] = [];
+
     @observable private _accumulatedTitle = "";
     @observable private _titleControlString: string = "#title";
     @observable private _edtingTitle = false;
@@ -237,6 +243,77 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         return false;
     }
 
+    @action
+    onRotateDown = (e: React.PointerEvent): void => {
+        setupMoveUpEvents(this, e, this.onRotateMove, this.onRotateUp, (e) => { });
+        this._prevX = e.clientX;
+        this._prevY = e.clientY;
+        SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
+            const doc = Document(element.rootDoc);
+            if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
+                const ink = Cast(doc.data, InkField)?.inkData;
+                if (ink) {
+                    const xs = ink.map(p => p.X);
+                    const ys = ink.map(p => p.Y);
+                    const left = Math.min(...xs);
+                    const top = Math.min(...ys);
+                    const right = Math.max(...xs);
+                    const bottom = Math.max(...ys);
+                    this._centerPoints.push({ X: ((right - left) / 2) + left, Y: ((bottom - top) / 2) + bottom });
+                }
+            }
+        }));
+
+    }
+    @action
+    onRotateMove = (e: PointerEvent, down: number[]): boolean => {
+        // const distance = Math.sqrt((this._prevY - e.clientY) * (this._prevY - e.clientY) + (this._prevX - e.clientX) * (this._prevX - e.clientX));
+        const distance = Math.abs(this._prevY - e.clientY);
+        var angle = 0;
+        //think of a better condition later...
+        // if ((down[0] < e.clientX && this._prevY < e.clientY) || (down[0] > e.clientX && this._prevY > e.clientY)) {
+        if (e.clientY > this._prevY) {
+            angle = distance * (Math.PI / 180);
+            // } else if ((down[0] < e.clientX && this._prevY > e.clientY) || (down[0] > e.clientX && this._prevY <= e.clientY)) {
+        } else if (e.clientY < this._prevY) {
+            angle = - distance * (Math.PI / 180);
+        }
+        this._prevX = e.clientX;
+        this._prevY = e.clientY;
+        var index = 0;
+        SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
+            const doc = Document(element.rootDoc);
+            if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
+                const ink = Cast(doc.data, InkField)?.inkData;
+                if (ink) {
+                    const newPoints: { X: number, Y: number }[] = [];
+                    for (var i = 0; i < ink.length; i++) {
+                        const newX = Math.cos(angle) * (ink[i].X - this._centerPoints[index].X) - Math.sin(angle) * (ink[i].Y - this._centerPoints[index].Y) + this._centerPoints[index].X;
+                        const newY = Math.sin(angle) * (ink[i].X - this._centerPoints[index].X) + Math.cos(angle) * (ink[i].Y - this._centerPoints[index].Y) + this._centerPoints[index].Y;
+                        newPoints.push({ X: newX, Y: newY });
+                    }
+                    doc.data = new InkField(newPoints);
+                    const xs = newPoints.map(p => p.X);
+                    const ys = newPoints.map(p => p.Y);
+                    const left = Math.min(...xs);
+                    const top = Math.min(...ys);
+                    const right = Math.max(...xs);
+                    const bottom = Math.max(...ys);
+                    doc._height = bottom - top;
+                    doc._width = right - left;
+                }
+                index++;
+            }
+        }));
+        return false;
+    }
+
+    onRotateUp = (e: PointerEvent) => {
+        this._centerPoints = [];
+    }
+
+
+
     _initialAutoHeight = false;
     _dragHeights = new Map<Doc, number>();
     @action
@@ -521,6 +598,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 <div className="documentDecorations-closeButton" title="Open Document in Tab" onPointerDown={this.onMaximizeDown}>
                     {SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
                 </div>
+                <div id="documentDecorations-rotation" className="documentDecorations-rotation"
+                    onPointerDown={this.onRotateDown}> ⟲ </div>
                 <div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer"
                     onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
                 <div id="documentDecorations-topResizer" className="documentDecorations-resizer"
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index d239a1d6f..5b6cbc372 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -18,7 +18,7 @@ import { LinkManager } from "../util/LinkManager";
 import { Scripting } from "../util/Scripting";
 import { Transform } from "../util/Transform";
 import "./GestureOverlay.scss";
-import { ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from "./InkingStroke";
+import { ActiveInkBezierApprox, ActiveArrowStart, ActiveArrowEnd, ActiveFillColor, ActiveInkColor, ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth, SetActiveFillColor, SetActiveArrowStart, SetActiveArrowEnd, ActiveDash, SetActiveDash } from "./InkingStroke";
 import { DocumentView } from "./nodes/DocumentView";
 import { RadialMenu } from "./nodes/RadialMenu";
 import HorizontalPalette from "./Palette";
@@ -32,6 +32,10 @@ export default class GestureOverlay extends Touchable {
     @observable public InkShape: string = "";
     @observable public SavedColor?: string;
     @observable public SavedWidth?: string;
+    @observable public SavedFill?: string;
+    @observable public SavedArrowStart: string = "none";
+    @observable public SavedArrowEnd: string = "none";
+    @observable public SavedDash: String = "0";
     @observable public Tool: ToolglassTools = ToolglassTools.None;
 
     @observable private _thumbX?: number;
@@ -802,6 +806,7 @@ export default class GestureOverlay extends Touchable {
     }
 
     @computed get elements() {
+
         const B = this.svgBounds;
         return [
             this.props.children,
@@ -809,11 +814,11 @@ export default class GestureOverlay extends Touchable {
             [this._strokes.map(l => {
                 const b = this.getBounds(l);
                 return <svg key={b.left} width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
-                    {InteractionUtils.CreatePolyline(l, b.left, b.top, ActiveInkColor(), ActiveInkWidth(), ActiveInkBezierApprox(), 1, 1, this.InkShape)}
+                    {InteractionUtils.CreatePolyline(l, b.left, b.top, ActiveInkColor(), ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", false)}
                 </svg>;
             }),
             this._points.length <= 1 ? (null) : <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
-                {InteractionUtils.CreatePolyline(this._points, B.left, B.top, ActiveInkColor(), ActiveInkWidth(), ActiveInkBezierApprox(), 1, 1, this.InkShape)}
+                {InteractionUtils.CreatePolyline(this._points, B.left, B.top, ActiveInkColor(), ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", false)}
             </svg>]
         ];
     }
@@ -901,12 +906,20 @@ Scripting.addGlobal("GestureOverlay", GestureOverlay);
 Scripting.addGlobal(function setToolglass(tool: any) {
     runInAction(() => GestureOverlay.Instance.Tool = tool);
 });
-Scripting.addGlobal(function setPen(width: any, color: any) {
+Scripting.addGlobal(function setPen(width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) {
     runInAction(() => {
         GestureOverlay.Instance.SavedColor = ActiveInkColor();
         SetActiveInkColor(color);
         GestureOverlay.Instance.SavedWidth = ActiveInkWidth();
         SetActiveInkWidth(width);
+        GestureOverlay.Instance.SavedFill = ActiveFillColor();
+        SetActiveFillColor(fill);
+        GestureOverlay.Instance.SavedArrowStart = ActiveArrowStart();
+        SetActiveArrowStart(arrowStart);
+        GestureOverlay.Instance.SavedArrowEnd = ActiveArrowEnd();
+        SetActiveArrowStart(arrowEnd);
+        GestureOverlay.Instance.SavedDash = ActiveDash();
+        SetActiveDash(dash);
     });
 });
 Scripting.addGlobal(function resetPen() {
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index a7650163f..324665aea 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -58,11 +58,13 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         const points = InteractionUtils.CreatePolyline(data, left, top,
             strokeColor,
             strokeWidth.toString(),
-            StrCast(this.layoutDoc.strokeBezier, ActiveInkBezierApprox()), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5);
+            StrCast(this.layoutDoc.strokeBezier, ActiveInkBezierApprox()), StrCast(this.layoutDoc.fillColor, ActiveFillColor()), StrCast(this.layoutDoc.arrowStart, ActiveArrowStart()), StrCast(this.layoutDoc.arrowEnd, ActiveArrowEnd()), StrCast(this.layoutDoc.dash, ActiveDash()), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5);
         const hpoints = InteractionUtils.CreatePolyline(data, left, top,
             this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent",
+            // strokeColor,
             (strokeWidth + 15).toString(),
-            StrCast(this.layoutDoc.strokeBezier, ActiveInkBezierApprox()), scaleX, scaleY, "", this.props.active() ? "visiblestroke" : "none", false);
+            StrCast(this.layoutDoc.strokeBezier, ActiveInkBezierApprox()), StrCast(this.layoutDoc.fillColor, ActiveFillColor()), StrCast(this.layoutDoc.arrowStart, ActiveArrowStart()), StrCast(this.layoutDoc.arrowEnd, ActiveArrowEnd()), StrCast(this.layoutDoc.dash, ActiveDash()), scaleX, scaleY, "", this.props.active() ? "visiblestroke" : "none", false);
+        console.log("#" + strokeColor);
         return (
             <svg className="inkingStroke"
                 width={width}
@@ -70,7 +72,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                 style={{
                     pointerEvents: this.props.Document.isInkMask ? "all" : "none",
                     transform: this.props.Document.isInkMask ? "translate(2500px, 2500px)" : undefined,
-                    mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset"
+                    mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
+                    overflow: "visible",
                 }}
                 onContextMenu={() => {
                     ContextMenu.Instance.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" });
@@ -93,6 +96,18 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                             <feMergeNode in="SourceGraphic"></feMergeNode>
                         </feMerge>
                     </filter>
+                    {/* <marker id="arrow" markerWidth="10" markerHeight="10" refX="0" refY="3" orient="auto" markerUnits="strokeWidth">
+                        <path d="M0,0 L0,6 L9,3 z" fill="black" />
+                    </marker> */}
+                    {/* <marker id="arrowHead" orient="auto" overflow="visible" refX="10" refY="3.5" markerWidth="10" markerHeight="7">
+                        <polygon points="10 0, 10 7, 0 3.5" fill="dodgerblue" />
+                    </marker>
+                    <marker id="arrowEnd" orient="auto" overflow="visible" refX="0" refY="3.5" markerWidth="10" markerHeight="7">
+                        <polygon points="0 0, 10 3.5, 0 7" fill="dodgerblue" />
+                    </marker> */}
+                    {/* <marker id="dot" orient="auto" overflow="visible">
+                        <circle r={strokeWidth} fill={"#" + strokeColor} />
+                    </marker> */}
                 </defs>
                 {hpoints}
                 {points}
@@ -105,17 +120,33 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
 export function SetActiveInkWidth(width: string): void { !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); }
 export function SetActiveBezierApprox(bezier: string): void { ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? "" : bezier); }
 export function SetActiveInkColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeInkColor = value); }
+export function SetActiveFillColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeFillColor = value); }
+export function SetActiveArrowStart(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); }
+export function SetActiveArrowEnd(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); }
+export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); }
 export function ActiveInkPen(): Doc { return Cast(Doc.UserDoc().activeInkPen, Doc, null); }
 export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, "black"); }
+export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, "none"); }
+export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, "none"); }
+export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, "none"); }
+export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, "0"); }
 export function ActiveInkWidth(): string { return StrCast(ActiveInkPen()?.activeInkWidth, "1"); }
 export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); }
-Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any) {
+Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) {
     Doc.SetSelectedTool(pen ? InkTool.Highlighter : InkTool.None);
     SetActiveInkWidth(width);
     SetActiveInkColor(color);
+    SetActiveFillColor(fill);
+    SetActiveArrowStart(arrowStart);
+    SetActiveArrowEnd(arrowEnd);
+    SetActiveDash(dash);
 });
 Scripting.addGlobal(function activateEraser(pen: any) { return Doc.SetSelectedTool(pen ? InkTool.Eraser : InkTool.None); });
 Scripting.addGlobal(function activateStamp(pen: any) { return Doc.SetSelectedTool(pen ? InkTool.Stamp : InkTool.None); });
 Scripting.addGlobal(function deactivateInk() { return Doc.SetSelectedTool(InkTool.None); });
 Scripting.addGlobal(function setInkWidth(width: any) { return SetActiveInkWidth(width); });
-Scripting.addGlobal(function setInkColor(color: any) { return SetActiveInkColor(color); });
\ No newline at end of file
+Scripting.addGlobal(function setInkColor(color: any) { return SetActiveInkColor(color); });
+Scripting.addGlobal(function setFillColor(fill: any) { return SetActiveFillColor(fill); });
+Scripting.addGlobal(function setActiveArrowStart(arrowStart: any) { return SetActiveArrowStart(arrowStart); });
+Scripting.addGlobal(function setActiveArrowEnd(arrowEnd: any) { return SetActiveArrowStart(arrowEnd); });
+Scripting.addGlobal(function setActiveDash(dash: any) { return SetActiveDash(dash); });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 678ad2a53..b97ac286c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -45,7 +45,7 @@ import React = require("react");
 import { CollectionViewType } from "../CollectionView";
 import { Timeline } from "../../animationtimeline/Timeline";
 import { SnappingManager } from "../../../util/SnappingManager";
-import { InkingStroke, ActiveInkColor, ActiveInkWidth, ActiveInkBezierApprox } from "../../InkingStroke";
+import { InkingStroke, ActiveArrowStart, ActiveArrowEnd, ActiveInkColor, ActiveFillColor, ActiveInkWidth, ActiveInkBezierApprox, ActiveDash } from "../../InkingStroke";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -456,7 +456,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             case GestureUtils.Gestures.Stroke:
                 const points = ge.points;
                 const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
-                const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), Doc.GetSelectedTool(), ActiveInkWidth(), ActiveInkBezierApprox(), points, { title: "ink stroke", x: B.x, y: B.y, _width: B.width, _height: B.height });
+                const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), Doc.GetSelectedTool(), ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), points, { title: "ink stroke", x: B.x, y: B.y, _width: B.width, _height: B.height });
                 this.addDocument(inkDoc);
                 e.stopPropagation();
                 break;
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
index 5a27f74e5..96a9cbbdb 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
@@ -3,25 +3,39 @@ import AntimodeMenu from "../../AntimodeMenu";
 import { observer } from "mobx-react";
 import { observable, action, computed } from "mobx";
 import "./InkOptionsMenu.scss";
-import { ActiveInkColor, ActiveInkBezierApprox, SetActiveInkWidth, SetActiveInkColor, SetActiveBezierApprox } from "../../InkingStroke";
+import { ActiveInkColor, ActiveInkBezierApprox, ActiveFillColor, ActiveArrowStart, ActiveArrowEnd, SetActiveInkWidth, SetActiveInkColor, SetActiveBezierApprox, SetActiveFillColor, SetActiveArrowStart, SetActiveArrowEnd, ActiveDash, SetActiveDash } from "../../InkingStroke";
 import { Scripting } from "../../../util/Scripting";
 import { InkTool } from "../../../../fields/InkField";
 import { ColorState } from "react-color";
 import { Utils } from "../../../../Utils";
 import GestureOverlay from "../../GestureOverlay";
 import { Doc } from "../../../../fields/Doc";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { DocumentView } from "../../../views/nodes/DocumentView";
+import { Document } from "../../../../fields/documentSchemas";
+import { DocumentType } from "../../../documents/DocumentTypes";
 
 @observer
 export default class InkOptionsMenu extends AntimodeMenu {
     static Instance: InkOptionsMenu;
 
-    private _palette = ["D0021B", "F5A623", "F8E71C", "8B572A", "7ED321", "417505", "9013FE", "4A90E2", "50E3C2", "B8E986", "000000", "4A4A4A", "9B9B9B", "FFFFFF"];
+    private _palette = ["D0021B", "F5A623", "F8E71C", "8B572A", "7ED321", "417505", "9013FE", "4A90E2", "50E3C2", "B8E986", "000000", "4A4A4A", "9B9B9B", "FFFFFF", "none"];
     private _width = ["1", "5", "10", "100", "200", "300"];
-    private _buttons = ["circle", "triangle", "rectangle", "arrow", "line"];
-    private _icons = ["O", "∆", "ロ", "➜", "-"];
+    // private _buttons = ["circle", "triangle", "rectangle", "arrow", "line"];
+    // private _icons = ["O", "∆", "ロ", "➜", "-"];
+    private _buttons = ["circle", "triangle", "rectangle", "line", ""];
+    private _icons = ["O", "∆", "ロ", "-", " "];
+    //arrowStart and arrowEnd must match and defs must exist in Inking Stroke
+    private _arrowStart = ["url(#arrowHead)", "url(#arrowHead)", "url(#dot)", "url(#dot)", "none"];
+    private _arrowEnd = ["none", "url(#arrowEnd)", "none", "url(#dot)", "none"];
+    private _arrowIcons = ["➡︎", "↔︎", "o-", "o-o", " "];
 
     @observable _colorBtn = false;
     @observable _widthBtn = false;
+    @observable _fillBtn = false;
+    @observable _arrowBtn = false;
+    @observable _dashBtn = false;
+    @observable _shapeBtn = false;
 
     constructor(props: Readonly<{}>) {
         super(props);
@@ -29,18 +43,103 @@ export default class InkOptionsMenu extends AntimodeMenu {
         this._canFade = false; // don't let the inking menu fade away
     }
 
+    getColors = () => {
+        return this._palette;
+    }
+
+    @action
+    changeArrow = (arrowStart: string, arrowEnd: string) => {
+        SetActiveArrowStart(arrowStart);
+        SetActiveArrowEnd(arrowEnd);
+    }
+
     @action
-    changeColor = (color: string) => {
+    changeColor = (color: string, type: string) => {
         const col: ColorState = {
             hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: "" }, hsv: { a: 0, h: 0, s: 0, v: 0, source: "" },
             rgb: { a: 0, r: 0, b: 0, g: 0, source: "" }, oldHue: 0, source: "",
         };
-        SetActiveInkColor(Utils.colorString(col));
+        if (type === "color") {
+            SetActiveInkColor(Utils.colorString(col));
+        } else if (type === "fill") {
+            SetActiveFillColor(Utils.colorString(col));
+        }
     }
 
+    @action
+    editProperties = (value: any, field: string) => {
+        SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
+            const doc = Document(element.rootDoc);
+            if (doc.type === DocumentType.INK) {
+                switch (field) {
+                    case "width":
+                        doc.strokeWidth = Number(value);
+                        break;
+                    case "color":
+                        doc.color = String(value);
+                        break;
+                    case "fill":
+                        doc.fillColor = String(value);
+                        break;
+                    case "bezier":
+                        // doc.strokeBezier === 300 ? doc.strokeBezier = 0 : doc.strokeBezier = 300;
+                        break;
+                    case "arrowStart":
+                        doc.arrowStart = String(value);
+                        break;
+                    case "arrowEnd":
+                        doc.arrowEnd = String(value);
+                        break;
+                    case "dash":
+                        doc.dash = Number(value);
+                    default:
+                        break;
+                }
+            }
+        }));
+    }
+
+
     @action
     changeBezier = (e: React.PointerEvent): void => {
         SetActiveBezierApprox(!ActiveInkBezierApprox() ? "300" : "");
+        this.editProperties(0, "bezier");
+    }
+    @action
+    changeDash = (e: React.PointerEvent): void => {
+        SetActiveDash(ActiveDash() === "0" ? "2" : "0");
+        this.editProperties(ActiveDash(), "dash");
+    }
+
+    @computed get arrowPicker() {
+        var currIcon;
+        for (var i = 0; i < this._arrowStart.length; i++) {
+            if (this._arrowStart[i] === ActiveArrowStart() && this._arrowEnd[i] === ActiveArrowEnd()) {
+                currIcon = this._arrowIcons[i];
+            }
+        }
+        var arrowPicker = <button
+            className="antimodeMenu-button"
+            key="arrow"
+            onPointerDown={action(e => this._arrowBtn = !this._arrowBtn)}
+            style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}>
+            {currIcon}
+        </button>;
+        if (this._arrowBtn) {
+            arrowPicker = <div className="btn2-group" key="arrows">
+                {arrowPicker}
+                {this._arrowStart.map((arrowStart, i) => {
+                    return <button
+                        className="antimodeMenu-button"
+                        key={arrowStart}
+                        onPointerDown={action(() => { SetActiveArrowStart(arrowStart); SetActiveArrowEnd(this._arrowEnd[i]); this.editProperties(arrowStart, "arrowStart"), this.editProperties(this._arrowEnd[i], "arrowEnd"); this._arrowBtn = false; })}
+                        style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}>
+                        {this._arrowIcons[i]}
+                    </button>;
+                })}
+            </div>;
+        }
+        return arrowPicker;
     }
 
     @computed get widthPicker() {
@@ -58,7 +157,7 @@ export default class InkOptionsMenu extends AntimodeMenu {
                     return <button
                         className="antimodeMenu-button"
                         key={wid}
-                        onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; })}
+                        onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })}
                         style={{ backgroundColor: this._widthBtn ? "121212" : "" }}>
                         {wid}
                     </button>;
@@ -68,6 +167,8 @@ export default class InkOptionsMenu extends AntimodeMenu {
         return widthPicker;
     }
 
+
+
     @computed get colorPicker() {
         var colorPicker = <button
             className="antimodeMenu-button"
@@ -84,7 +185,7 @@ export default class InkOptionsMenu extends AntimodeMenu {
                     return <button
                         className="antimodeMenu-button"
                         key={color}
-                        onPointerDown={action(() => { this.changeColor(color); this._colorBtn = false; })}
+                        onPointerDown={action(() => { this.changeColor(color, "color"); this._colorBtn = false; this.editProperties(color, "color"); })}
                         style={{ backgroundColor: this._colorBtn ? "121212" : "" }}>
                         <div className="color-preview" style={{ backgroundColor: color }}></div>
                     </button>;
@@ -94,6 +195,35 @@ export default class InkOptionsMenu extends AntimodeMenu {
         return colorPicker;
     }
 
+    @computed get fillPicker() {
+        var fillPicker = <button
+            className="antimodeMenu-button"
+            key="fill"
+            title="fillChanger"
+            onPointerDown={action(e => this._fillBtn = !this._fillBtn)}
+            style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
+            <div className="color-preview" style={{ backgroundColor: ActiveFillColor() ?? "121212" }}></div>
+        </button>;
+        if (this._fillBtn) {
+            fillPicker = <div className="btn-group" key="fill">
+                {fillPicker}
+                {this._palette.map(color => {
+                    return <button
+                        className="antimodeMenu-button"
+                        key={color}
+                        onPointerDown={action(() => { this.changeColor(color, "fill"); this._fillBtn = false; this.editProperties(color, "fill"); })}
+                        style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
+                        <div className="color-preview" style={{ backgroundColor: color }}></div>
+                    </button>;
+                })}
+
+            </div>;
+        }
+        return fillPicker;
+    }
+
+
+
     @computed get shapeButtons() {
         return <>
             {this._buttons.map((btn, i) => <button
@@ -103,28 +233,79 @@ export default class InkOptionsMenu extends AntimodeMenu {
                 onPointerDown={action(e => GestureOverlay.Instance.InkShape = btn)}
                 style={{ backgroundColor: btn === GestureOverlay.Instance.InkShape ? "121212" : "" }}>
                 {this._icons[i]}
-            </button>)},
+            </button>)}
         </>;
     }
 
+    @computed get shapePicker() {
+        var currIcon;
+        if (GestureOverlay.Instance.InkShape === "") {
+            currIcon = "S";
+        } else {
+            for (var i = 0; i < this._icons.length; i++) {
+                if (GestureOverlay.Instance.InkShape === this._buttons[i]) {
+                    currIcon = this._icons[i];
+                }
+            }
+        }
+        var shapePicker = <button
+            className="antimodeMenu-button"
+            key="shape"
+            onPointerDown={action(e => this._shapeBtn = !this._shapeBtn)}
+            style={{ backgroundColor: this._widthBtn ? "121212" : "" }}>
+            {currIcon}
+        </button>;
+        if (this._shapeBtn) {
+            shapePicker = <div className="btn2-group" key="shape">
+                {shapePicker}
+                {this._buttons.map((btn, i) => {
+                    return <button
+                        className="antimodeMenu-button"
+                        title={`Draw ${btn}`}
+                        key={btn}
+                        onPointerDown={action((e) => { GestureOverlay.Instance.InkShape = btn; this._shapeBtn = false; })}
+                        style={{ backgroundColor: this._shapeBtn ? "121212" : "" }}>
+                        {this._icons[i]}
+                    </button>;
+                })}
+            </div>;
+        }
+        return shapePicker;
+    }
+
     @computed get bezierButton() {
         return <button
             className="antimodeMenu-button"
             title="Bezier changer"
             key="bezier"
             onPointerDown={e => this.changeBezier(e)}
-            style={ { backgroundColor:ActiveInkBezierApprox() ? "121212":"" } }>
+            style={{ backgroundColor: ActiveInkBezierApprox() ? "121212" : "" }}>
             B
         </button>;
     }
 
+    @computed get dashButton() {
+        return <button
+            className="antimodeMenu-button"
+            title="dash changer"
+            key="dash"
+            onPointerDown={e => this.changeDash(e)}
+            style={{ backgroundColor: ActiveDash() !== "0" ? "121212" : "" }}>
+            ...
+        </button>;
+    }
+
     render() {
         const buttons = [
             <button className="antimodeMenu-button" title="Drag" key="drag" onPointerDown={e => this.dragStart(e)}>  ✜  </button>,
-            this.shapeButtons,
+            this.shapePicker,
+            // this.shapeButtons,
             this.bezierButton,
             this.widthPicker,
             this.colorPicker,
+            this.fillPicker,
+            this.arrowPicker,
+            this.dashButton
         ];
         return this.getElement(buttons);
     }
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index 51a5768bf..7cfd74cc4 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -1,7 +1,7 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, custom, createSimpleSchema, list, object, map } from "serializr";
 import { ObjectField } from "./ObjectField";
-import { Copy, ToScriptString, ToString } from "./FieldSymbols";
+import { Copy, ToScriptString, ToString, Update } from "./FieldSymbols";
 
 export enum InkTool {
     None = "none",
@@ -31,6 +31,8 @@ const strokeDataSchema = createSimpleSchema({
 export class InkField extends ObjectField {
     @serializable(list(object(strokeDataSchema)))
     readonly inkData: InkData;
+    // inkData: InkData;
+
 
     constructor(data: InkData) {
         super();
-- 
cgit v1.2.3-70-g09d2


From 738a56b3c18c06d8290346373eb60a125120caaf Mon Sep 17 00:00:00 2001
From: Lionel Han <47760119+IGoByJoe@users.noreply.github.com>
Date: Thu, 18 Jun 2020 15:02:50 -0700
Subject: hopefully fixed new ink

---
 src/client/util/InteractionUtils.tsx               |  4 +-
 src/client/views/InkingStroke.tsx                  |  4 +-
 .../views/collections/CollectionDockingView.tsx    |  2 +-
 src/client/views/collections/CollectionView.tsx    |  4 +-
 .../views/collections/CollectionViewChromes.tsx    |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  6 +--
 src/client/views/nodes/DocumentView.tsx            | 44 +++++++++++-----------
 src/mobile/MobileInterface.tsx                     | 16 +++++---
 src/pen-gestures/ndollar.ts                        |  2 +-
 9 files changed, 44 insertions(+), 40 deletions(-)

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

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index aeb0f670d..3b42fcaee 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -96,9 +96,9 @@ export namespace InteractionUtils {
         }
         else if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y === points[0].Y) {
             //pointer is up (first and last points are the same)
-            points.pop();
+            //points.pop();
             const newPoints = points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]);
-
+            newPoints.pop();
             const bezierCurves = fitCurve(newPoints, parseInt(bezier));
             for (var i = 0; i < bezierCurves.length; i++) {
                 for (var t = 0; t < 1; t += 0.01) {
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 7e3bd1c17..5129ef65d 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -68,8 +68,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                     mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset"
                 }}
                 onContextMenu={() => {
-                    ContextMenu.Instance.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" });
-                    ContextMenu.Instance.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" });
+                    ContextMenu.Instance?.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" });
+                    ContextMenu.Instance?.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" });
                 }}
             ><defs>
                     <filter id="dangerShine">
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index c2e297f61..657e2a08b 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -790,7 +790,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         if (this._mainCont && this._mainCont.children) {
             const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont.children[0].firstChild as HTMLElement);
             const scale = Utils.GetScreenTransform(this._mainCont).scale;
-            return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale);
+            return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale);
         }
         return Transform.Identity();
     }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a700d0dfb..0a84e6f72 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -283,10 +283,10 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             !existingOnClick && ContextMenu.Instance?.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
 
             if (!Doc.UserDoc().noviceMode) {
-                const more = ContextMenu.Instance.findByDescription("More...");
+                const more = ContextMenu.Instance?.findByDescription("More...");
                 const moreItems = more && "subitems" in more ? more.subitems : [];
                 moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) });
-                !more && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+                !more && ContextMenu.Instance?.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
             }
         }
     }
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 48810f1e9..ec908f034 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -300,7 +300,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
 
     render() {
         const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
-        const scale = Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform().Scale);
+        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,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 82d6dee73..bed871f8d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1199,13 +1199,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     onContextMenu = (e: React.MouseEvent) => {
         if (this.props.annotationsKey) return;
 
-        ContextMenu.Instance.addItem({
+        ContextMenu.Instance?.addItem({
             description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => {
                 this._timelineVisible = !this._timelineVisible;
             }), icon: this._timelineVisible ? faEyeSlash : faEye
         });
 
-        const options = ContextMenu.Instance.findByDescription("Options...");
+        const options = ContextMenu.Instance?.findByDescription("Options...");
         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[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
@@ -1246,7 +1246,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             }
         });
         optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
-        ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
+        ContextMenu.Instance?.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
 
     }
     @observable _timelineVisible = false;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e027b6a0f..2f868d558 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -708,40 +708,40 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     onContextMenu = async (e: React.MouseEvent | Touch): Promise<void> => {
         // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
         if (!(e instanceof Touch)) {
-            if (e.button === 0 && !e.ctrlKey) {
-                e.preventDefault();
+            if (e?.button === 0 && !e.ctrlKey) {
+                e?.preventDefault();
                 return;
             }
-            e.persist();
+            e?.persist();
             e?.stopPropagation();
 
-            if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 ||
-                e.isDefaultPrevented()) {
-                e.preventDefault();
+            if (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3 ||
+                e?.isDefaultPrevented()) {
+                e?.preventDefault();
                 return;
             }
-            e.preventDefault();
+            e?.preventDefault();
         }
 
         const cm = ContextMenu.Instance;
 
         const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
         Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) =>
-            cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
+            cm?.addItem({ description: label, event: () => customScripts[i]?.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
         this.props.contextMenuItems?.().forEach(item =>
-            cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
+            cm?.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
 
-        let options = cm.findByDescription("Options...");
+        let options = cm?.findByDescription("Options...");
         const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
         const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
         optionItems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
         templateDoc && optionItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
         if (!options) {
             options = { description: "Options...", subitems: optionItems, icon: "compass" };
-            cm.addItem(options);
+            cm?.addItem(options);
         }
 
-        const existingOnClick = cm.findByDescription("OnClick...");
+        const existingOnClick = cm?.findByDescription("OnClick...");
         const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
         onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
         onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.props.Document.layoutKey}")`), icon: "window-restore" });
@@ -750,17 +750,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link on Right", event: this.toggleFollowOnRight, icon: "concierge-bell" });
         onClicks.push({ description: this.Document.isLinkButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
         onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
-        !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+        !existingOnClick && cm?.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
 
         const funcs: ContextMenuProps[] = [];
         if (this.Document.onDragStart) {
             funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
             funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
             funcs.push({ description: "Drag Document", icon: "edit", event: () => this.Document.onDragStart = undefined });
-            cm.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
+            cm?.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
         }
 
-        const more = cm.findByDescription("More...");
+        const more = cm?.findByDescription("More...");
         const moreItems = more && "subitems" in more ? more.subitems : [];
         if (!Doc.UserDoc().noviceMode) {
             moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
@@ -789,26 +789,26 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
         moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
         moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" });
-        !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
-        cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
+        !more && cm?.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+        cm?.moveAfter(cm?.findByDescription("More...")!, cm?.findByDescription("OnClick...")!);
 
-        const help = cm.findByDescription("Help...");
+        const help = cm?.findByDescription("Help...");
         const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
         helpItems.push({
             description: "Keyboard Shortcuts       Ctrl+/",
             event: () => this.props.addDocTab(Docs.Create.PdfDocument("http://localhost:1050/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "onRight"),
             icon: "keyboard"
         });
-        cm.addItem({ description: "Help...", subitems: helpItems, icon: "question" });
+        cm?.addItem({ description: "Help...", subitems: helpItems, icon: "question" });
 
-        const existingAcls = cm.findByDescription("Privacy...");
+        const existingAcls = cm?.findByDescription("Privacy...");
         const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
         aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" });
         aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
         aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" });
         aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
         aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
-        !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
+        !existingAcls && cm?.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
 
         // const recommender_subitems: ContextMenuProps[] = [];
 
@@ -869,7 +869,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 // DocumentViews should stop propagation of this event
                 e.stopPropagation();
             }
-            ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
+            ContextMenu.Instance?.displayMenu(e.pageX - 15, e.pageY - 15);
             !SelectionManager.IsSelected(this, true) && SelectionManager.SelectDoc(this, false);
         });
         const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc()["tabs-button-library"] as Doc).sourcePanel as Doc) ? "" : d.title), "");
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 4000bee85..3a81e5de1 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -708,6 +708,10 @@ export class MobileInterface extends React.Component {
         e.stopPropagation();
     }
 
+    uploadToCurrentCollection = (doc: Doc) => {
+
+    }
+
     render() {
         return (
             <div className="mobileInterface-container" onDragOver={this.onDragOver}>
@@ -717,13 +721,13 @@ export class MobileInterface extends React.Component {
                 </div>
                 {this.switchMenuView()}
                 {this.inkMenu()}
+                <div className="docButtonContainer">
+                    {this.pinToPresentation()}
+                    {this.downloadDocument()}
+                    {this.drawInk()}
+                    {this.uploadAudioButton()}
+                </div>
                 <GestureOverlay>
-                    <div className="docButtonContainer">
-                        {this.pinToPresentation()}
-                        {this.downloadDocument()}
-                        {this.drawInk()}
-                        {this.uploadAudioButton()}
-                    </div>
                     {this.displayWorkspaces()}
                     {this.renderDefaultContent()}
                 </GestureOverlay>
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index 9d42035d1..ecd8df3e7 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -552,7 +552,7 @@ function Distance(p1: any, p2: any) // distance between two points
 }
 function CalcStartUnitVector(points: any, index: any) // start angle from points[0] to points[index] normalized as a unit vector
 {
-    const v = new Point(points[index].X - points[0].X, points[index].Y - points[0].Y);
+    const v = new Point(points[index]?.X - points[0]?.X, points[index]?.Y - points[0]?.Y);
     const len = Math.sqrt(v.X * v.X + v.Y * v.Y);
     return new Point(v.X / len, v.Y / len);
 }
-- 
cgit v1.2.3-70-g09d2


From e7372931b9d28c141aaec9552041b5644c2f415a Mon Sep 17 00:00:00 2001
From: geireann <60007097+geireann@users.noreply.github.com>
Date: Fri, 26 Jun 2020 03:37:08 +0800
Subject: Merge branch 'master' into mobile_revision_direct

And changes to UI of record and image upload
---
 src/client/util/CurrentUserUtils.ts                |  12 +-
 src/client/util/DragManager.ts                     |   2 +-
 src/client/views/DocumentButtonBar.tsx             |  63 +-----
 src/client/views/DocumentDecorations.tsx           |   2 +-
 src/client/views/GlobalKeyHandler.ts               |   2 +
 src/client/views/MainView.tsx                      |  11 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   4 +-
 src/client/views/globalCssVariables.scss           |   1 +
 src/client/views/linking/LinkMenu.scss             |   4 +
 src/client/views/linking/LinkMenu.tsx              |  37 +--
 src/client/views/nodes/AudioBox.scss               |  71 +-----
 src/client/views/nodes/AudioBox.tsx                |   2 +-
 src/client/views/nodes/DocumentLinksButton.scss    |  38 ++++
 src/client/views/nodes/DocumentLinksButton.tsx     | 104 +++++++++
 src/client/views/nodes/DocumentView.tsx            |  28 ++-
 src/client/views/nodes/LinkAnchorBox.tsx           |   8 +
 src/client/views/nodes/LinkDocPreview.tsx          | 107 +++++++++
 .../views/nodes/formattedText/RichTextMenu.tsx     |   9 +-
 src/mobile/AudioUpload.scss                        |  41 +++-
 src/mobile/AudioUpload.tsx                         | 249 +++++++--------------
 src/mobile/ImageUpload.scss                        |  61 ++---
 src/mobile/ImageUpload.tsx                         |  66 +++---
 src/mobile/MobileInterface.tsx                     |  29 ++-
 src/mobile/MobileMenu.scss                         |   1 +
 24 files changed, 549 insertions(+), 403 deletions(-)
 create mode 100644 src/client/views/nodes/DocumentLinksButton.scss
 create mode 100644 src/client/views/nodes/DocumentLinksButton.tsx
 create mode 100644 src/client/views/nodes/LinkDocPreview.tsx

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index efdc943fa..823a99436 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -474,12 +474,12 @@ export class CurrentUserUtils {
 
     static setupMobileButtons(doc?: Doc, buttons?: string[]) {
         const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [
-            { title: "WORKSPACES", icon: "folder-open", click: 'openWorkspaces()', backgroundColor: "#ffd6d6", info: "Access your Workspaces from your mobile, and navigate through all of your documents. " },
-            { title: "RECORD", icon: "microphone", click: 'openMobileAudio()', backgroundColor: "#ffbfbf", info: "Use your phone to record and upload audio onto Dash Web." },
-            { title: "UPLOAD", icon: "upload", click: 'uploadImageMobile()', backgroundColor: "#ff9e9e", info: "Upload images or videos from your mobile device so they can be accessed on Dash Web." },
-            { title: "PRESENTATION", icon: "desktop", click: 'openMobilePresentation()', backgroundColor: "#ff8080", info: "Use your phone as a remote for you presentation." },
-            { title: "MOBILE UPLOAD", icon: "mobile", click: 'switchToMobileUploads()', backgroundColor: "#ff7373", info: "Access the collection of your mobile uploads." },
-            { title: "SETTINGS", icon: "cog", click: 'openMobileSettings()', backgroundColor: "#ff5e5e", info: "Change your password, log out, or manage your account security." }
+            { title: "WORKSPACES", icon: "bars", click: 'openMobileWorkspaces()', backgroundColor: "lightgrey", info: "Access your Workspaces from your mobile, and navigate through all of your documents. " },
+            { title: "UPLOAD", icon: "upload", click: 'uploadImageMobile()', backgroundColor: "lightgrey", info: "Upload files from your mobile device so they can be accessed on Dash Web." },
+            { title: "MOBILE UPLOAD", icon: "mobile", click: 'switchToMobileUploads()', backgroundColor: "lightgrey", info: "Access the collection of your mobile uploads." },
+            { title: "RECORD", icon: "microphone", click: 'openMobileAudio()', backgroundColor: "lightgrey", info: "Use your phone to record, dictate and then upload audio onto Dash Web." },
+            { title: "PRESENTATION", icon: "desktop", click: 'openMobilePresentation()', backgroundColor: "lightgrey", info: "Use your phone as a remote for you presentation." },
+            { title: "SETTINGS", icon: "cog", click: 'openMobileSettings()', backgroundColor: "lightgrey", info: "Change your password, log out, or manage your account security." }
         ];
         return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data =>
             this.mobileButton({
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 91bc51101..417ddf989 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -353,7 +353,7 @@ export namespace DragManager {
             const dragElement = ele.parentNode === dragDiv ? ele : ele.cloneNode(true) as HTMLElement;
             const rect = ele.getBoundingClientRect();
             const scaleX = rect.width / ele.offsetWidth,
-                scaleY = rect.height / ele.offsetHeight;
+                scaleY = ele.offsetHeight ? rect.height / ele.offsetHeight : scaleX;
             elesCont.left = Math.min(rect.left, elesCont.left);
             elesCont.top = Math.min(rect.top, elesCont.top);
             elesCont.right = Math.max(rect.right, elesCont.right);
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 62a95116f..fcaea0f0b 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,28 +1,28 @@
 import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowAltCircleDown, faPhotoVideo, faArrowAltCircleUp, faArrowAltCircleRight, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons';
+import { faArrowAltCircleDown, faArrowAltCircleRight, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faPhotoVideo, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast } from "../../fields/Doc";
 import { RichTextField } from '../../fields/RichTextField';
-import { NumCast, StrCast, Cast } from "../../fields/Types";
+import { Cast, NumCast } from "../../fields/Types";
 import { emptyFunction, setupMoveUpEvents } from "../../Utils";
+import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
+import { Docs, DocUtils } from '../documents/Documents';
+import { DragManager } from '../util/DragManager';
 import { UndoManager } from "../util/UndoManager";
 import { CollectionDockingView, DockedFrameRenderer } from './collections/CollectionDockingView';
 import { ParentDocSelector } from './collections/ParentDocumentSelector';
 import './collections/ParentDocumentSelector.scss';
 import './DocumentButtonBar.scss';
-import { LinkMenu } from "./linking/LinkMenu";
+import { MetadataEntryMenu } from './MetadataEntryMenu';
 import { DocumentView } from './nodes/DocumentView';
 import { GoogleRef } from "./nodes/formattedText/FormattedTextBox";
 import { TemplateMenu } from "./TemplateMenu";
 import { Template, Templates } from "./Templates";
 import React = require("react");
-import { DragManager } from '../util/DragManager';
-import { MetadataEntryMenu } from './MetadataEntryMenu';
-import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
-import { Docs } from '../documents/Documents';
+import { DocumentLinksButton } from './nodes/DocumentLinksButton';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -113,39 +113,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
 
     get view0() { return this.props.views()?.[0]; }
 
-    @action
-    onLinkButtonMoved = (e: PointerEvent) => {
-        if (this._linkButton.current !== null) {
-            const linkDrag = UndoManager.StartBatch("Drag Link");
-            this.view0 && DragManager.StartLinkDrag(this._linkButton.current, this.view0.props.Document, e.pageX, e.pageY, {
-                dragComplete: dropEv => {
-                    const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
-                    if (this.view0 && linkDoc) {
-                        !linkDoc.linkRelationship && (Doc.GetProto(linkDoc).linkRelationship = "hyperlink");
-
-                        // we want to allow specific views to handle the link creation in their own way (e.g., rich text makes text hyperlinks)
-                        // the dragged view can regiser a linkDropCallback to be notified that the link was made and to update their data structures
-                        // however, the dropped document isn't so accessible.  What we do is set the newly created link document on the documentView
-                        // The documentView passes a function prop returning this link doc to its descendants who can react to changes to it.
-                        dropEv.linkDragData?.linkDropCallback?.(dropEv.linkDragData);
-                        runInAction(() => this.view0!._link = linkDoc);
-                        setTimeout(action(() => this.view0!._link = undefined), 0);
-                    }
-                    linkDrag?.end();
-                },
-                hideSource: false
-            });
-            return true;
-        }
-        return false;
-    }
-
-
-    onLinkButtonDown = (e: React.PointerEvent): void => {
-        setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, emptyFunction);
-    }
-
-
     @computed
     get considerGoogleDocsPush() {
         const targetDoc = this.view0?.props.Document;
@@ -235,20 +202,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         </div>;
     }
 
-    @computed
-    get linkButton() {
-        const view0 = this.view0;
-        const linkCount = view0 && DocListCast(view0.props.Document.links).length;
-        return !view0 ? (null) : <div title="Drag(create link) Tap(view links)" className="documentButtonBar-linkFlyout" ref={this._linkButton}>
-            <Flyout anchorPoint={anchorPoints.LEFT_TOP}
-                content={<LinkMenu docView={view0} addDocTab={view0.props.addDocTab} changeFlyout={emptyFunction} />}>
-                <div className={"documentButtonBar-linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >
-                    {linkCount ? linkCount : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />}
-                </div>
-            </Flyout>
-        </div>;
-    }
-
     @computed
     get metadataButton() {
         const view0 = this.view0;
@@ -318,7 +271,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         const considerPush = isText && this.considerGoogleDocsPush;
         return <div className="documentButtonBar">
             <div className="documentButtonBar-button">
-                {this.linkButton}
+                <DocumentLinksButton View={this.view0!} AlwaysOn={true} />
             </div>
             <div className="documentButtonBar-button">
                 {this.templateButton}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index beb6155ca..d7b0ab7a9 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -552,7 +552,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}></div>
 
             </div >
-            <div className="link-button-container" style={{ left: bounds.x - this._resizeBorderWidth / 2, top: bounds.b + this._resizeBorderWidth / 2 }}>
+            <div className="link-button-container" style={{ left: bounds.x - this._resizeBorderWidth / 2 + 10, top: bounds.b + this._resizeBorderWidth / 2 }}>
                 <DocumentButtonBar views={SelectionManager.SelectedDocuments} />
             </div>
         </div >
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 27755737e..7bc8cf6a7 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -19,6 +19,7 @@ import { MarqueeView } from "./collections/collectionFreeForm/MarqueeView";
 import { DocumentDecorations } from "./DocumentDecorations";
 import { MainView } from "./MainView";
 import { DocumentView } from "./nodes/DocumentView";
+import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
 
 const modifiers = ["control", "meta", "shift", "alt"];
 type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -77,6 +78,7 @@ export default class KeyManager {
                 // MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI
                 break;
             case "escape":
+                DocumentLinksButton.StartLink = undefined;
                 const main = MainView.Instance;
                 Doc.SetSelectedTool(InkTool.None);
                 if (main.isPointerDown) {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 97953452d..4e2c067ac 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -5,7 +5,7 @@ import {
     faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
     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,
-    faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faQuoteLeft
+    faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faQuoteLeft, faSortAmountDown
 } from '@fortawesome/free-solid-svg-icons';
 import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -55,6 +55,9 @@ import { TimelineMenu } from './animationtimeline/TimelineMenu';
 import { SnappingManager } from '../util/SnappingManager';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { DocumentManager } from '../util/DocumentManager';
+import { DocumentLinksButton } from './nodes/DocumentLinksButton';
+import { LinkMenu } from './linking/LinkMenu';
+import { LinkDocPreview } from './nodes/LinkDocPreview';
 
 @observer
 export class MainView extends React.Component {
@@ -135,7 +138,7 @@ export class MainView extends React.Component {
             faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
             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, faQuoteLeft);
+            faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faQuoteLeft, faSortAmountDown);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
@@ -578,6 +581,10 @@ export class MainView extends React.Component {
                 {this.mainContent}
             </GestureOverlay>
             <PreviewCursor />
+            {DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
+            {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors}
+                linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href}
+                addDocTab={LinkDocPreview.LinkInfo.addDocTab} /> : (null)}
             <ContextMenu />
             <RadialMenu />
             <PDFMenu />
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index dc62c48b7..42aa0f58e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -47,6 +47,7 @@ import { Timeline } from "../../animationtimeline/Timeline";
 import { SnappingManager } from "../../../util/SnappingManager";
 import { ActiveInkColor, ActiveInkWidth, ActiveInkBezierApprox } from "../../InkingStroke";
 import { DocumentType } from "../../../documents/DocumentTypes";
+import { DocumentLinksButton } from "../../nodes/DocumentLinksButton";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -245,7 +246,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             // const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
             // this.props.addDocument(source);
             // linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed
-           return false;
+            return false;
         } else {
             const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
             this.props.addDocument(source);
@@ -589,6 +590,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     onClick = (e: React.MouseEvent) => {
         if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
             if (Date.now() - this._lastTap < 300) {
+                runInAction(() => DocumentLinksButton.StartLink = undefined);
                 const docpt = this.getTransform().transformPoint(e.clientX, e.clientY);
                 this.scaleAtPt(docpt, 1);
                 e.stopPropagation();
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
index 4e85fe0ca..3e54d001b 100644
--- a/src/client/views/globalCssVariables.scss
+++ b/src/client/views/globalCssVariables.scss
@@ -12,6 +12,7 @@ $lighter-alt-accent: rgb(207, 220, 240);
 $darker-alt-accent: rgb(178, 206, 248);
 $intermediate-color: #9c9396;
 $dark-color: #121721;
+$link-color: lightBlue;
 $antimodemenu-height: 35px;
 // fonts
 $sans-serif: "Noto Sans",
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index 7dee22f66..c372e7098 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -8,6 +8,10 @@
 .linkMenu-list {
     max-height: 200px;
     overflow-y: scroll;
+    position: absolute;
+    z-index: 10;
+    background: $link-color;
+    min-width: 150px
 }
 
 .linkMenu-group {
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 56f40ad69..0fcc0f0b9 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -4,11 +4,12 @@ import { DocumentView } from "../nodes/DocumentView";
 import { LinkEditor } from "./LinkEditor";
 import './LinkMenu.scss';
 import React = require("react");
-import { Doc } from "../../../fields/Doc";
+import { Doc, Opt } from "../../../fields/Doc";
 import { LinkManager } from "../../util/LinkManager";
 import { LinkMenuGroup } from "./LinkMenuGroup";
 import { faTrash } from '@fortawesome/free-solid-svg-icons';
 import { library } from "@fortawesome/fontawesome-svg-core";
+import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
 
 library.add(faTrash);
 
@@ -16,16 +17,29 @@ interface Props {
     docView: DocumentView;
     changeFlyout: () => void;
     addDocTab: (document: Doc, where: string) => boolean;
+    location: number[];
 }
 
 @observer
 export class LinkMenu extends React.Component<Props> {
 
     @observable private _editingLink?: Doc;
+    @observable private _linkMenuRef: Opt<HTMLDivElement | null>;
 
+    @action
+    onClick = (e: PointerEvent) => {
+        if (!Array.from(this._linkMenuRef?.getElementsByTagName((e.target as HTMLElement).tagName) || []).includes(e.target as any)) {
+            DocumentLinksButton.EditLink = undefined;
+        }
+    }
     @action
     componentDidMount() {
         this._editingLink = undefined;
+        document.addEventListener("pointerdown", this.onClick);
+    }
+
+    componentWillUnmount() {
+        document.removeEventListener("pointerdown", this.onClick);
     }
 
     clearAllLinks = () => {
@@ -56,20 +70,11 @@ export class LinkMenu extends React.Component<Props> {
     render() {
         const sourceDoc = this.props.docView.props.Document;
         const groups: Map<string, Doc[]> = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc);
-        if (this._editingLink === undefined) {
-            return (
-                <div className="linkMenu">
-                    {/* <button className="linkEditor-button linkEditor-clearButton" onClick={() => this.clearAllLinks()} title="Clear all links"><FontAwesomeIcon icon="trash" size="sm" /></button> */}
-                    {/* <input id="linkMenu-searchBar" type="text" placeholder="Search..."></input> */}
-                    <div className="linkMenu-list">
-                        {this.renderAllGroups(groups)}
-                    </div>
-                </div>
-            );
-        } else {
-            return (
-                <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)}></LinkEditor>
-            );
-        }
+        return <div className="linkMenu-list" ref={(r) => this._linkMenuRef = r} style={{ left: this.props.location[0], top: this.props.location[1] }}>
+            {!this._editingLink ?
+                this.renderAllGroups(groups) :
+                <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)} />
+            }
+        </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index 43dd698ad..b5d113ca0 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -175,76 +175,29 @@
 
 
 @media only screen and (max-device-width: 480px) {
-
-    .audiobox-container,
-    .audiobox-container-interactive {
-        width: max-content;
-        /* align-self: center; */
-        left: 50%;
-        top: 40%;
-        transform: translate(-50%, 0);
-        height: 100px;
-        position: absolute;
-        display: flex;
-        pointer-events: all;
-        /* cursor: default; */
-    }
-
-    .audiobox-container .audiobox-buttons,
-    .audiobox-container-interactive .audiobox-buttons {
-        display: flex;
-        width: 500px;
-        /* left: 50%; */
-        /* transform: translate(-50%,0); */
-        align-items: center;
-    }
-
     .audiobox-dictation {
+        font-size: 5em;
+        display: flex;
         width: 100;
-        height: 100;
-        font-size: 50;
-        /* background-color: white; */
+        justify-content: center;
+        flex-direction: column;
+        align-items: center;
     }
 
-    .audiobox-container .audiobox-record-interactive,
-    .audiobox-container-interactive .audiobox-record-interactive {
-        width: 95%;
-        user-select: none;
-        font-size: 50px;
-        font-weight: 100;
-        left: 5%;
+    .audiobox-container .audiobox-record,
+    .audiobox-container-interactive .audiobox-record {
+        pointer-events: all;
+        font-size: 3em;
+        width: 100%;
         height: 100%;
         position: relative;
+        pointer-events: none;
     }
 
     .audiobox-container .audiobox-controls .audiobox-player .audiobox-playhead,
     .audiobox-container .audiobox-controls .audiobox-player .audiobox-dictation,
     .audiobox-container-interactive .audiobox-controls .audiobox-player .audiobox-playhead,
     .audiobox-container-interactive .audiobox-controls .audiobox-player .audiobox-dictation {
-        position: relative;
-        align-self: center;
-        width: 50px;
-        height: max-content;
-        padding: 0;
-    }
-
-    .audiobox-container .audiobox-controls .audiobox-player,
-    .audiobox-container-interactive .audiobox-controls .audiobox-player {
-        margin-top: auto;
-        margin-bottom: auto;
-        width: 100%;
-        height: 100%;
-        position: relative;
-        padding-right: 5px;
-        display: flex;
-    }
-
-    .audiobox-container .audiobox-controls,
-    .audiobox-container-interactive .audiobox-controls {
-        width: 500px;
-        height: 100%;
-        position: relative;
-        display: flex;
-        padding-left: 2px;
+        width: 70px;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index cb0582d87..d5288fff6 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -193,7 +193,7 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
         const newDoc = Docs.Create.TextDocument("", {
             title: "", _chromeStatus: "disabled",
             x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document._height) + 10,
-            _width: NumCast(this.props.Document._width), _height: 3 * NumCast(this.props.Document._height)
+            _width: NumCast(this.props.Document._width), _height: 2 * NumCast(this.props.Document._height)
         });
         Doc.GetProto(newDoc).recordingSource = this.dataDoc;
         Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.props.fieldKey}-recordingStart"]`);
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
new file mode 100644
index 000000000..484f8c469
--- /dev/null
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -0,0 +1,38 @@
+@import "../globalCssVariables.scss";
+
+
+.documentLinksButton,
+.documentLinksButton-endLink,
+.documentLinksButton-startLink {
+    height: 20px;
+    width: 20px;
+    position: absolute;
+    border-radius: 50%;
+    opacity: 0.9;
+    pointer-events: auto;
+    color: black;
+    text-transform: uppercase;
+    letter-spacing: 2px;
+    font-size: 75%;
+    transition: transform 0.2s;
+    text-align: center;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    &:hover {
+        background: deepskyblue;
+        transform: scale(1.05);
+        cursor: default;
+    }
+}
+.documentLinksButton {
+    background-color: $link-color;
+}
+.documentLinksButton-endLink {
+    border: red solid 2px;
+}
+.documentLinksButton-startLink {
+    border: red solid 2px;
+    background-color: rgba(255, 192, 203, 0.5);
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
new file mode 100644
index 000000000..f35b48ce4
--- /dev/null
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -0,0 +1,104 @@
+import { action, computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { Doc, DocListCast } from "../../../fields/Doc";
+import { emptyFunction, setupMoveUpEvents, returnFalse } from "../../../Utils";
+import { DragManager } from "../../util/DragManager";
+import { UndoManager } from "../../util/UndoManager";
+import './DocumentLinksButton.scss';
+import { DocumentView } from "./DocumentView";
+import React = require("react");
+import { DocUtils } from "../../documents/Documents";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { LinkDocPreview } from "./LinkDocPreview";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
+
+interface DocumentLinksButtonProps {
+    View: DocumentView;
+    Offset?: number[];
+    AlwaysOn?: boolean;
+}
+@observer
+export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
+    private _linkButton = React.createRef<HTMLDivElement>();
+
+    @action
+    onLinkButtonMoved = (e: PointerEvent) => {
+        if (this._linkButton.current !== null) {
+            const linkDrag = UndoManager.StartBatch("Drag Link");
+            this.props.View && DragManager.StartLinkDrag(this._linkButton.current, this.props.View.props.Document, e.pageX, e.pageY, {
+                dragComplete: dropEv => {
+                    const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
+                    if (this.props.View && linkDoc) {
+                        !linkDoc.linkRelationship && (Doc.GetProto(linkDoc).linkRelationship = "hyperlink");
+
+                        // we want to allow specific views to handle the link creation in their own way (e.g., rich text makes text hyperlinks)
+                        // the dragged view can regiser a linkDropCallback to be notified that the link was made and to update their data structures
+                        // however, the dropped document isn't so accessible.  What we do is set the newly created link document on the documentView
+                        // The documentView passes a function prop returning this link doc to its descendants who can react to changes to it.
+                        dropEv.linkDragData?.linkDropCallback?.(dropEv.linkDragData);
+                        runInAction(() => this.props.View._link = linkDoc);
+                        setTimeout(action(() => this.props.View._link = undefined), 0);
+                    }
+                    linkDrag?.end();
+                },
+                hideSource: false
+            });
+            return true;
+        }
+        return false;
+    }
+
+    @observable static StartLink: DocumentView | undefined;
+    onLinkButtonDown = (e: React.PointerEvent): void => {
+        setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
+            if (doubleTap) {
+                DocumentLinksButton.StartLink = this.props.View;
+            } else {
+                DocumentLinksButton.EditLink = this.props.View;
+                DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY];
+            }
+        }));
+    }
+    completeLink = (e: React.PointerEvent): void => {
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e, doubleTap) => {
+            if (doubleTap) {
+                if (DocumentLinksButton.StartLink === this.props.View) {
+                    DocumentLinksButton.StartLink = undefined;
+                } else {
+                    DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
+                        DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+                }
+            }
+        }));
+    }
+
+    @observable
+    public static EditLink: DocumentView | undefined;
+    public static EditLinkLoc: number[] = [0, 0];
+
+    @computed
+    get linkButton() {
+        const links = DocListCast(this.props.View.props.Document.links);
+        return (!links.length || links[0].hidden) && !this.props.AlwaysOn ? (null) :
+            <div title="Drag(create link) Tap(view links)" ref={this._linkButton} style={{ position: "absolute", left: this.props.Offset?.[0] }}>
+                <div className={"documentLinksButton"} style={{ backgroundColor: DocumentLinksButton.StartLink ? "transparent" : "" }}
+                    onPointerDown={this.onLinkButtonDown}
+                    onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
+                    onPointerEnter={action(e => LinkDocPreview.LinkInfo = {
+                        addDocTab: this.props.View.props.addDocTab,
+                        linkSrc: this.props.View.props.Document,
+                        linkDoc: links[0],
+                        Location: [e.clientX, e.clientY + 20]
+                    })} >
+                    {links.length ? links.length : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />}
+                </div>
+                {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"} onPointerDown={this.completeLink} /> : (null)}
+                {DocumentLinksButton.StartLink === this.props.View ? <div className={"documentLinksButton-startLink"} /> : (null)}
+            </div>;
+    }
+    render() {
+        return this.linkButton;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d31ec8c1a..afd28534e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -42,6 +42,7 @@ import { LinkAnchorBox } from './LinkAnchorBox';
 import { RadialMenu } from './RadialMenu';
 import React = require("react");
 import { MobileInterface } from '../../../mobile/MobileInterface';
+import { DocumentLinksButton } from './DocumentLinksButton';
 
 library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
     fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
@@ -585,10 +586,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     toggleLinkButtonBehavior = (): void => {
         if (this.Document.isLinkButton || this.layoutDoc.onClick || this.Document.ignoreClick) {
             this.Document.isLinkButton = false;
+            const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
+            first && (first.hidden = false);
             this.Document.ignoreClick = false;
             this.layoutDoc.onClick = undefined;
         } else {
             this.Document.isLinkButton = true;
+            const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
+            first && (first.hidden = true);
             this.Document.followLinkZoom = false;
             this.Document.followLinkLocation = undefined;
         }
@@ -598,8 +603,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     toggleFollowInPlace = (): void => {
         if (this.Document.isLinkButton) {
             this.Document.isLinkButton = false;
+            const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
+            first && (first.hidden = false);
         } else {
             this.Document.isLinkButton = true;
+            const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
+            first && (first.hidden = true);
             this.Document.followLinkZoom = true;
             this.Document.followLinkLocation = "inPlace";
         }
@@ -609,6 +618,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     toggleFollowOnRight = (): void => {
         if (this.Document.isLinkButton) {
             this.Document.isLinkButton = false;
+            const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
+            first && (first.hidden = false);
         } else {
             this.Document.isLinkButton = true;
             this.Document.followLinkZoom = false;
@@ -737,10 +748,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
         const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
         templateDoc && optionItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
-        if (!options) {
-            options = { description: "Options...", subitems: optionItems, icon: "compass" };
-            cm?.addItem(options);
-        }
+        optionItems.push({ description: "Toggle Show Each Link Dot", event: () => this.layoutDoc.showLinks = !this.layoutDoc.showLinks, icon: "eye" });
+        !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
 
         const existingOnClick = cm?.findByDescription("OnClick...");
         const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
@@ -1039,7 +1048,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 select={this.select}
                 onClick={this.onClickHandler}
                 layoutKey={this.finalLayoutKey} />
-            {this.anchors}
+            {this.layoutDoc.showLinks ? this.anchors : (null)}
+            {this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.props.dontRegisterView ? (null) : <DocumentLinksButton View={this} Offset={[-15, 0]} />}
         </div>
         );
     }
@@ -1063,7 +1073,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     anchorPanelHeight = () => this.props.PanelHeight() || 1;
     @computed get anchors() {
         TraceMobx();
-        return this.layoutDoc.presBox ? (null) : DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
+        return this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.layoutDoc.presBox || this.props.dontRegisterView ? (null) : DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
             <DocumentView {...this.props} key={i + 1}
                 Document={d}
                 ContainingCollectionView={this.props.ContainingCollectionView}
@@ -1071,6 +1081,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 PanelWidth={this.anchorPanelWidth}
                 PanelHeight={this.anchorPanelHeight}
                 ContentScaling={returnOne}
+                dontRegisterView={false}
                 forcedBackgroundColor={returnTransparent}
                 removeDocument={this.hideLinkAnchor}
                 pointerEvents={false}
@@ -1194,7 +1205,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 color: StrCast(this.layoutDoc.color, "inherit"),
                 outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
                 border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
-                boxShadow: this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined,
+                boxShadow: this.Document.isLinkButton && !this.props.dontRegisterView && this.props.forcedBackgroundColor?.(this.Document) !== "transparent" ?
+                    StrCast(this.props.Document._linkButtonShadow, "lightblue 0em 0em 1em") :
+                    this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" :
+                        undefined,
                 background: finalColor,
                 opacity: finalOpacity,
                 fontFamily: StrCast(this.Document._fontFamily, "inherit"),
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 2b64cdab6..2bcc6168b 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -18,6 +18,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { SelectionManager } from "../../util/SelectionManager";
 import { TraceMobx } from "../../../fields/util";
 import { Id } from "../../../fields/FieldSymbols";
+import { LinkDocPreview } from "./LinkDocPreview";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -132,6 +133,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
         );
         const small = this.props.PanelWidth() <= 1;
         return <div className={`linkAnchorBox-cont${small ? "-small" : ""} ${this.rootDoc[Id]}`}
+            onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
+            onPointerEnter={action(e => LinkDocPreview.LinkInfo = {
+                addDocTab: this.props.addDocTab,
+                linkSrc: this.props.ContainingCollectionDoc!,
+                linkDoc: this.rootDoc,
+                Location: [e.clientX, e.clientY + 20]
+            })}
             onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle} onContextMenu={this.specificContextMenu}
             ref={this._ref} style={{
                 background: c,
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
new file mode 100644
index 000000000..126dc240a
--- /dev/null
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -0,0 +1,107 @@
+import { action, computed, observable, runInAction } from 'mobx';
+import { observer } from "mobx-react";
+import wiki from "wikijs";
+import { Doc, DocCastAsync, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { Cast, FieldValue, NumCast } from "../../../fields/Types";
+import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero } from "../../../Utils";
+import { Docs } from "../../documents/Documents";
+import { DocumentManager } from "../../util/DocumentManager";
+import { Transform } from "../../util/Transform";
+import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
+import React = require("react");
+import { DocumentView } from './DocumentView';
+
+interface Props {
+    linkDoc?: Doc;
+    linkSrc?: Doc;
+    href?: string;
+    backgroundColor: (doc: Doc) => string;
+    addDocTab: (document: Doc, where: string) => boolean;
+    location: number[];
+}
+@observer
+export class LinkDocPreview extends React.Component<Props> {
+    @observable public static LinkInfo: Opt<{ linkDoc?: Doc; addDocTab: (document: Doc, where: string) => boolean, linkSrc: Doc; href?: string; Location: number[] }>;
+    @observable _targetDoc: Opt<Doc>;
+    @observable _toolTipText = "";
+
+    componentDidUpdate() { this.updatePreview() }
+    componentDidMount() { this.updatePreview() }
+    async updatePreview() {
+        const linkDoc = this.props.linkDoc;
+        const linkSrc = this.props.linkSrc;
+        if (this.props.href) {
+            if (this.props.href.startsWith("https://en.wikipedia.org/wiki/")) {
+                wiki().page(this.props.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500))));
+            } else {
+                runInAction(() => this._toolTipText = "external => " + this.props.href);
+            }
+        } else if (linkDoc && linkSrc) {
+            const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), linkSrc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
+            const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
+            runInAction(() => {
+                this._toolTipText = "";
+                this._targetDoc = target;
+                if (anchor !== this._targetDoc && anchor && this._targetDoc) {
+                    this._targetDoc._scrollY = NumCast(anchor?.y);
+                }
+            });
+        }
+    }
+    pointerDown = (e: React.PointerEvent) => {
+        if (this.props.linkDoc && this.props.linkSrc) {
+            DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.linkSrc,
+                (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+        } else if (this.props.href) {
+            this.props.addDocTab(Docs.Create.WebDocument(this.props.href, { title: this.props.href, _width: 200, _height: 400, UseCors: true }), "onRight");
+        }
+    }
+    width = () => Math.min(350, NumCast(this._targetDoc?.[WidthSym](), 350));
+    height = () => Math.min(350, NumCast(this._targetDoc?.[HeightSym](), 350));
+    @computed get targetDocView() {
+        return !this._targetDoc ?
+            <div style={{ pointerEvents: "all", maxWidth: 350, maxHeight: 250, width: "100%", height: "100%", overflow: "hidden" }}>
+                <div style={{ width: "100%", height: "100%", textOverflow: "ellipsis", }} onPointerDown={this.pointerDown}>
+                    {this._toolTipText}
+                </div>
+            </div> :
+            <ContentFittingDocumentView
+                Document={this._targetDoc}
+                LibraryPath={emptyPath}
+                fitToBox={true}
+                backgroundColor={this.props.backgroundColor}
+                moveDocument={returnFalse}
+                rootSelected={returnFalse}
+                ScreenToLocalTransform={Transform.Identity}
+                parentActive={returnFalse}
+                addDocument={returnFalse}
+                removeDocument={returnFalse}
+                addDocTab={returnFalse}
+                pinToPres={returnFalse}
+                dontRegisterView={true}
+                docFilters={returnEmptyFilter}
+                ContainingCollectionDoc={undefined}
+                ContainingCollectionView={undefined}
+                renderDepth={0}
+                PanelWidth={this.width}
+                PanelHeight={this.height}
+                focus={emptyFunction}
+                whenActiveChanged={returnFalse}
+                bringToFront={returnFalse}
+                ContentScaling={returnOne}
+                NativeWidth={returnZero}
+                NativeHeight={returnZero}
+            />;
+    }
+
+    render() {
+        return <div className="linkDocPreview"
+            style={{
+                position: "absolute", left: this.props.location[0],
+                top: this.props.location[1], width: this.width(), height: this.height(),
+                boxShadow: "black 2px 2px 1em"
+            }}>
+            {this.targetDocView}
+        </div>;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 5e32b3ef8..839943aac 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -11,7 +11,7 @@ import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { Doc } from "../../../../fields/Doc";
 import { DarkPastelSchemaPalette, PastelSchemaPalette } from '../../../../fields/SchemaHeaderField';
-import { Cast, StrCast } from "../../../../fields/Types";
+import { Cast, StrCast, BoolCast } from "../../../../fields/Types";
 import { unimplementedFunction, Utils } from "../../../../Utils";
 import { DocServer } from "../../../DocServer";
 import { LinkManager } from "../../../util/LinkManager";
@@ -72,7 +72,7 @@ export default class RichTextMenu extends AntimodeMenu {
         super(props);
         RichTextMenu.Instance = this;
         this._canFade = false;
-        this.Pinned = true;
+        this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]);
 
         this.fontSizeOptions = [
             { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
@@ -147,7 +147,6 @@ export default class RichTextMenu extends AntimodeMenu {
         this.updateFromDash(view, lastState, this.editorProps);
     }
 
-
     public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => {
         if (this.view) {
             const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId });
@@ -750,7 +749,7 @@ export default class RichTextMenu extends AntimodeMenu {
 
     @action
     toggleMenuPin = (e: React.MouseEvent) => {
-        this.Pinned = !this.Pinned;
+        Doc.UserDoc()["menuRichText-pinned"] = this.Pinned = !this.Pinned;
         if (!this.Pinned) {
             this.fadeOut(true);
         }
@@ -779,7 +778,7 @@ export default class RichTextMenu extends AntimodeMenu {
             this.createHighlighterButton(),
             this.createLinkButton(),
             this.createBrushButton(),
-            this.createButton("indent", "Summarize", undefined, this.insertSummarizer),
+            this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
             this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
         ]}</div>;
 
diff --git a/src/mobile/AudioUpload.scss b/src/mobile/AudioUpload.scss
index 9fe442e55..6e64d9e2e 100644
--- a/src/mobile/AudioUpload.scss
+++ b/src/mobile/AudioUpload.scss
@@ -5,8 +5,45 @@
     justify-content: center;
     flex-direction: column;
     align-items: center;
-    max-width: 400px;
-    min-width: 400px;
+    margin-top: 10px;
+    height: 400px;
+    width: 600px;
+}
+
+.upload_label {
+    position: relative;
+    font-weight: 700;
+    color: black;
+    background-color: rgba(0, 0, 0, 0);
+    border: solid 3px black;
+    margin: 10px;
+    font-size: 30;
+    height: 70px;
+    width: 60%;
+    display: inline-flex;
+    font-family: sans-serif;
+    text-transform: uppercase;
+    justify-content: center;
+    flex-direction: column;
+    border-radius: 10px;
+}
+
+.restart_label {
+    position: relative;
+    font-weight: 700;
+    color: black;
+    background-color: rgba(0, 0, 0, 0);
+    border: solid 3px black;
+    margin: 10px;
+    font-size: 30;
+    height: 70px;
+    width: 60%;
+    display: inline-flex;
+    font-family: sans-serif;
+    text-transform: uppercase;
+    justify-content: center;
+    flex-direction: column;
+    border-radius: 10px;
 }
 
 .audio-upload {
diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx
index 7ea11ee84..f859b0ba3 100644
--- a/src/mobile/AudioUpload.tsx
+++ b/src/mobile/AudioUpload.tsx
@@ -5,11 +5,11 @@ import "./ImageUpload.scss";
 import React = require('react');
 import { DocServer } from '../client/DocServer';
 import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
-import { Utils, emptyPath, returnFalse, emptyFunction, returnOne, returnZero, returnTrue } from '../Utils';
+import { observable, action, computed } from 'mobx';
+import { Utils, emptyPath, returnFalse, emptyFunction, returnOne, returnZero, returnTrue, returnEmptyFilter } from '../Utils';
 import { Networking } from '../client/Network';
 import { Doc, Opt } from '../fields/Doc';
-import { Cast } from '../fields/Types';
+import { Cast, FieldValue } from '../fields/Types';
 import { listSpec } from '../fields/Schema';
 import { List } from '../fields/List';
 import { Scripting } from '../client/util/Scripting';
@@ -38,185 +38,100 @@ export class AudioUpload extends React.Component {
     @observable status: string = "";
     @observable nm: string = "Choose files";
     @observable process: string = "";
+    @observable public _audioCol: Doc = FieldValue(Cast(Docs.Create.FreeformDocument([Cast(Docs.Create.AudioDocument(nullAudio, { title: "mobile audio", _width: 500, _height: 100 }), Doc) as Doc], { title: "mobile audio", _fitToBox: true, boxShadow: "0 0" }), Doc)) as Doc;
 
-    onClick = async () => {
-        try {
-            await Docs.Prototypes.initialize();
-            const imgPrev = document.getElementById("img_preview");
-            const slab1 = document.getElementById("slab1");
-            if (slab1) {
-                slab1.style.opacity = "1";
-            }
-            if (imgPrev) {
-                const files: FileList | null = inputRef.current!.files;
-                const slab2 = document.getElementById("slab2");
-                if (slab2) {
-                    slab2.style.opacity = "1";
-                }
-                if (files && files.length !== 0) {
-                    this.process = "Uploading Files";
-                    for (let index = 0; index < files.length; ++index) {
-                        const file = files[index];
-                        const res = await Networking.UploadFilesToServer(file);
-                        const slab3 = document.getElementById("slab3");
-                        if (slab3) {
-                            slab3.style.opacity = "1";
-                        }
-                        res.map(async ({ result }) => {
-                            const name = file.name;
-                            if (result instanceof Error) {
-                                return;
-                            }
-                            const path = Utils.prepend(result.accessPaths.agnostic.client);
-                            let doc = null;
-                            console.log("type: " + file.type);
-                            if (file.type === "video/mp4") {
-                                doc = Docs.Create.VideoDocument(path, { _nativeWidth: 200, _width: 200, title: name });
-                            } else if (file.type === "application/pdf") {
-                                doc = Docs.Create.PdfDocument(path, { _width: 200, title: name });
-                            } else {
-                                doc = Docs.Create.ImageDocument(path, { _nativeWidth: 200, _width: 200, title: name });
-                            }
-                            const slab4 = document.getElementById("slab4");
-                            if (slab4) {
-                                slab4.style.opacity = "1";
-                            }
-                            const res = await rp.get(Utils.prepend("/getUserDocumentId"));
-                            if (!res) {
-                                throw new Error("No user id returned");
-                            }
-                            const field = await DocServer.GetRefField(res);
-                            let pending: Opt<Doc>;
-                            if (field instanceof Doc) {
-                                pending = await Cast(field.mobileUpload, Doc);
-                            }
-                            if (pending) {
-                                const data = await Cast(pending.data, listSpec(Doc));
-                                if (data) {
-                                    data.push(doc);
-                                } else {
-                                    pending.data = new List([doc]);
-                                }
-                                this.status = "finished";
-                                const slab5 = document.getElementById("slab5");
-                                if (slab5) {
-                                    slab5.style.opacity = "1";
-                                }
-                                this.process = "File " + (index + 1).toString() + " Uploaded";
-                                const slab6 = document.getElementById("slab6");
-                                if (slab6) {
-                                    slab6.style.opacity = "1";
-                                }
-                                const slab7 = document.getElementById("slab7");
-                                if (slab7) {
-                                    slab7.style.opacity = "1";
-                                }
-
-                            }
-                        });
-                    }
-                } else {
-                    this.process = "No file selected";
-                }
-                setTimeout(this.clearUpload, 3000);
-            }
-        } catch (error) {
-            this.error = JSON.stringify(error);
-        }
-    }
-
-    // Updates label after a files is selected (so user knows a file is uploaded)
-    inputLabel = async () => {
-        const files: FileList | null = inputRef.current!.files;
-        await files;
-        if (files && files.length === 1) {
-            console.log(files);
-            this.nm = files[0].name;
-        } else if (files && files.length > 1) {
-            console.log(files.length);
-            this.nm = files.length.toString() + " files selected";
-        }
-    }
 
     @action
     clearUpload = () => {
-        const slab1 = document.getElementById("slab1");
-        if (slab1) {
-            slab1.style.opacity = "0.4";
-        }
-        const slab2 = document.getElementById("slab2");
-        if (slab2) {
-            slab2.style.opacity = "0.4";
-        }
-        const slab3 = document.getElementById("slab3");
-        if (slab3) {
-            slab3.style.opacity = "0.4";
+        for (let i = 1; i < 8; i++) {
+            this.setOpacity(i, "0.2");
         }
-        const slab4 = document.getElementById("slab4");
-        if (slab4) {
-            slab4.style.opacity = "0.4";
-        }
-        const slab5 = document.getElementById("slab5");
-        if (slab5) {
-            slab5.style.opacity = "0.4";
-        }
-        const slab6 = document.getElementById("slab6");
-        if (slab6) {
-            slab6.style.opacity = "0.4";
-        }
-        const slab7 = document.getElementById("slab7");
-        if (slab7) {
-            slab7.style.
-                opacity = "0.4";
-        }
-        this.nm = "Choose files";
-
-        if (inputRef.current) {
-            inputRef.current.value = "";
-        }
-        this.process = "";
-        console.log(inputRef.current!.files);
+        this._audioCol = FieldValue(Cast(Docs.Create.FreeformDocument([Cast(Docs.Create.AudioDocument(nullAudio, { title: "mobile audio", _width: 500, _height: 100 }), Doc) as Doc], { title: "mobile audio", _fitToBox: true, boxShadow: "0 0" }), Doc)) as Doc;
     }
 
-
+    closeUpload = () => {
+        this.clearUpload();
+        MobileInterface.Instance.toggleAudio();
+    }
 
     private get uploadInterface() {
-        const audioDoc = Cast(Docs.Create.AudioDocument(nullAudio, { title: "mobile audio" }), Doc) as Doc;
-
         return (
-            <div className="imgupload_cont">
-                <div className="closeUpload" onClick={MobileInterface.Instance.toggleAudio}>
+            <>
+                <div className="closeUpload" onClick={() => this.closeUpload()}>
                     <FontAwesomeIcon icon="window-close" size={"lg"} />
                 </div>
-                <DocumentView
-                    Document={audioDoc}
-                    DataDoc={undefined}
-                    LibraryPath={emptyPath}
-                    addDocument={returnFalse}
-                    addDocTab={returnFalse}
-                    pinToPres={emptyFunction}
-                    rootSelected={returnFalse}
-                    removeDocument={undefined}
-                    onClick={undefined}
-                    ScreenToLocalTransform={Transform.Identity}
-                    ContentScaling={returnOne}
-                    PanelWidth={() => 1000}
-                    PanelHeight={() => 1000}
-                    NativeHeight={returnZero}
-                    NativeWidth={returnZero}
-                    renderDepth={0}
-                    focus={emptyFunction}
-                    backgroundColor={() => "white"}
-                    parentActive={returnTrue}
-                    whenActiveChanged={emptyFunction}
-                    bringToFront={emptyFunction}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
-                />
-            </div>
+                <FontAwesomeIcon icon="microphone" size="lg" style={{ fontSize: "130" }} />
+                <div className="audioUpload_cont">
+                    <DocumentView
+                        Document={this._audioCol}
+                        DataDoc={undefined}
+                        LibraryPath={emptyPath}
+                        addDocument={returnFalse}
+                        addDocTab={returnFalse}
+                        pinToPres={emptyFunction}
+                        rootSelected={returnFalse}
+                        removeDocument={undefined}
+                        docFilters={returnEmptyFilter}
+                        onClick={undefined}
+                        ScreenToLocalTransform={Transform.Identity}
+                        ContentScaling={returnOne}
+                        PanelWidth={() => 600}
+                        PanelHeight={() => 400}
+                        NativeHeight={returnZero}
+                        NativeWidth={returnZero}
+                        renderDepth={0}
+                        focus={emptyFunction}
+                        backgroundColor={() => "rgba(0,0,0,0)"}
+                        parentActive={returnTrue}
+                        whenActiveChanged={emptyFunction}
+                        bringToFront={emptyFunction}
+                        ContainingCollectionView={undefined}
+                        ContainingCollectionDoc={undefined}
+                    />
+                </div>
+                <div className="restart_label" onClick={this.clearUpload}>
+                    Restart
+                </div>
+                <div className="upload_label" onClick={this.uploadAudio}>
+                    Upload
+                </div>
+                <div className="loadingImage">
+                    <div className="loadingSlab" id="slab01" />
+                    <div className="loadingSlab" id="slab02" />
+                    <div className="loadingSlab" id="slab03" />
+                    <div className="loadingSlab" id="slab04" />
+                    <div className="loadingSlab" id="slab05" />
+                    <div className="loadingSlab" id="slab06" />
+                    <div className="loadingSlab" id="slab07" />
+                </div>
+            </>
         );
     }
 
+    setOpacity = (i: number, o: string) => {
+        const slab = document.getElementById("slab0" + i);
+        if (slab) {
+            console.log(slab?.id);
+            slab.style.opacity = o;
+        }
+    }
+
+    // Pushing the audio doc onto Dash Web through the right side bar
+    uploadAudio = () => {
+        console.log("uploading");
+        const audioRightSidebar = Cast(Doc.UserDoc().rightSidebarCollection, Doc) as Doc;
+        const audioDoc = this._audioCol;
+        const data = Cast(audioRightSidebar.data, listSpec(Doc));
+        for (let i = 1; i < 8; i++) {
+            setTimeout(() => this.setOpacity(i, "1"), i * 200);
+        }
+        if (data) {
+            data.push(audioDoc);
+        }
+
+        setTimeout(this.clearUpload, 3000);
+    }
+
     @observable private dialogueBoxOpacity = 1;
     @observable private overlayOpacity = 0.4;
 
diff --git a/src/mobile/ImageUpload.scss b/src/mobile/ImageUpload.scss
index b64aac338..890258918 100644
--- a/src/mobile/ImageUpload.scss
+++ b/src/mobile/ImageUpload.scss
@@ -9,13 +9,19 @@
     min-width: 400px;
 
     .upload_label {
-        font-size: 3em;
         font-weight: 700;
-        color: white;
-        background-color: black;
-        display: inline-block;
-        margin: 10;
-        width: 100%;
+        color: black;
+        background-color: rgba(0, 0, 0, 0);
+        border: solid 3px black;
+        margin: 10px;
+        font-size: 30;
+        height: 70px;
+        width: 80%;
+        display: flex;
+        font-family: sans-serif;
+        text-transform: uppercase;
+        justify-content: center;
+        flex-direction: column;
         border-radius: 10px;
     }
 
@@ -26,10 +32,6 @@
         direction: ltr;
     }
 
-    .upload_label:hover {
-        background-color: darkred;
-    }
-
     .button_file {
         text-align: center;
         height: 50%;
@@ -49,20 +51,31 @@
     }
 
     .inputfile+label {
-        font-size: 3em;
         font-weight: 700;
-        color: white;
-        background-color: black;
-        display: inline-block;
+        color: black;
+        background-color: rgba(0, 0, 0, 0);
+        border: solid 3px black;
         margin: 10px;
+        font-size: 30;
+        height: 70px;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
         margin-top: 30px;
-        width: 100%;
+        width: 80%;
+        display: flex;
+        font-family: sans-serif;
+        text-transform: uppercase;
+        justify-content: center;
+        flex-direction: column;
         border-radius: 10px;
     }
 
-    .inputfile:focus+label,
-    .inputfile+label:hover {
-        background-color: darkred;
+    .inputfile.active+label {
+        font-style: italic;
+        color: black;
+        background-color: lightgreen;
+        border: solid 3px darkgreen;
     }
 
     .status {
@@ -71,16 +84,6 @@
 
 }
 
-.backgroundUpload {
-    height: 100vh;
-    top: 0;
-    z-index: 999;
-    width: 100vw;
-    position: absolute;
-    background-color: lightgrey;
-    opacity: 0.4;
-}
-
 .image-upload {
     top: 100%;
     opacity: 0;
@@ -129,7 +132,7 @@
     height: 30px;
     margin: 10;
     border-radius: 20px;
-    opacity: 0.3;
+    opacity: 0.2;
     background-color: black;
     transition: all 2s, opacity 1.5s;
 }
\ No newline at end of file
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index b712d52cc..6a5a2dd5b 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -43,21 +43,18 @@ export class Uploader extends React.Component<ImageUploadProps> {
             await Docs.Prototypes.initialize();
             const imgPrev = document.getElementById("img_preview");
             // Slab 1
-            const slab1 = document.getElementById("slab1");
-            if (slab1) slab1.style.opacity = "1";
+            this.setOpacity(1, "1");
             if (imgPrev) {
                 const files: FileList | null = inputRef.current!.files;
                 // Slab 2
-                const slab2 = document.getElementById("slab2");
-                if (slab2) slab2.style.opacity = "1";
+                this.setOpacity(2, "1");
                 if (files && files.length !== 0) {
                     this.process = "Uploading Files";
                     for (let index = 0; index < files.length; ++index) {
                         const file = files[index];
                         const res = await Networking.UploadFilesToServer(file);
                         // Slab 3
-                        const slab3 = document.getElementById("slab3");
-                        if (slab3) slab3.style.opacity = "1";
+                        this.setOpacity(3, "1");
                         res.map(async ({ result }) => {
                             const name = file.name;
                             if (result instanceof Error) {
@@ -74,8 +71,7 @@ export class Uploader extends React.Component<ImageUploadProps> {
                                 doc = Docs.Create.ImageDocument(path, { _nativeWidth: 400, _width: 400, title: name });
                             }
                             // Slab 4
-                            const slab4 = document.getElementById("slab4");
-                            if (slab4) slab4.style.opacity = "1";
+                            this.setOpacity(4, "1");
                             const res = await rp.get(Utils.prepend("/getUserDocumentId"));
                             if (!res) {
                                 throw new Error("No user id returned");
@@ -94,13 +90,15 @@ export class Uploader extends React.Component<ImageUploadProps> {
                                 if (data) data.push(doc);
                                 else pending.data = new List([doc]);
                                 this.status = "finished";
-                                const slab5 = document.getElementById("slab5");
-                                if (slab5) slab5.style.opacity = "1";
+                                this.setOpacity(5, "1"); // Slab 5
                                 this.process = "File " + (index + 1).toString() + " Uploaded";
-                                const slab6 = document.getElementById("slab6");
-                                if (slab6) slab6.style.opacity = "1";
-                                const slab7 = document.getElementById("slab7");
-                                if (slab7) slab7.style.opacity = "1";
+                                this.setOpacity(6, "1"); // Slab 6
+                                this.setOpacity(7, "1"); // Slab 7
+                            }
+                            console.log("i: " + index + 1);
+                            console.log("l: " + files.length);
+                            if ((index + 1) === files.length) {
+                                this.process = "Uploads Completed";
                             }
                         });
                     }
@@ -129,20 +127,9 @@ export class Uploader extends React.Component<ImageUploadProps> {
 
     @action
     clearUpload = () => {
-        const slab1 = document.getElementById("slab1");
-        if (slab1) slab1.style.opacity = "0.4";
-        const slab2 = document.getElementById("slab2");
-        if (slab2) slab2.style.opacity = "0.4";
-        const slab3 = document.getElementById("slab3");
-        if (slab3) slab3.style.opacity = "0.4";
-        const slab4 = document.getElementById("slab4");
-        if (slab4) slab4.style.opacity = "0.4";
-        const slab5 = document.getElementById("slab5");
-        if (slab5) slab5.style.opacity = "0.4";
-        const slab6 = document.getElementById("slab6");
-        if (slab6) slab6.style.opacity = "0.4";
-        const slab7 = document.getElementById("slab7");
-        if (slab7) slab7.style.opacity = "0.4";
+        for (let i = 1; i < 8; i++) {
+            this.setOpacity(i, "0.2");
+        }
         this.nm = "Choose files";
 
         if (inputRef.current) {
@@ -152,24 +139,33 @@ export class Uploader extends React.Component<ImageUploadProps> {
         console.log(inputRef.current!.files);
     }
 
+    closeUpload = () => {
+        this.clearUpload();
+        MobileInterface.Instance.toggleUpload();
+    }
+
+    setOpacity = (i: number, o: string) => {
+        const slab = document.getElementById("slab" + i);
+        if (slab) {
+            console.log(slab?.id);
+            slab.style.opacity = o;
+        }
+    }
 
 
     private get uploadInterface() {
         return (
             <div className="imgupload_cont">
-                <div className="closeUpload" onClick={MobileInterface.Instance.toggleUpload}>
+                <div className="closeUpload" onClick={() => this.closeUpload()}>
                     <FontAwesomeIcon icon="window-close" size={"lg"} />
                 </div>
-                <input type="file" accept="application/pdf, video/*,image/*" className="inputFile" id="input_image_file" ref={inputRef} onChange={this.inputLabel} multiple></input>
+                <FontAwesomeIcon icon="upload" size="lg" style={{ fontSize: "130" }} />
+                <input type="file" accept="application/pdf, video/*,image/*" className={`inputFile ${this.nm !== "Choose files" ? "active" : ""}`} id="input_image_file" ref={inputRef} onChange={this.inputLabel} multiple></input>
                 <label className="file" id="label" htmlFor="input_image_file">{this.nm}</label>
                 <div className="upload_label" onClick={this.onClick}>
-                    <FontAwesomeIcon icon="upload" size="sm" />
-                    &nbsp; &nbsp; Upload
+                    Upload
                 </div>
-                {/* <div onClick={this.onClick} className="upload_button">Upload</div> */}
                 <img id="img_preview" src=""></img>
-                {/* <p>{this.status}</p>
-                <p>{this.error}</p> */}
                 <div className="loadingImage">
                     <div className="loadingSlab" id="slab1" />
                     <div className="loadingSlab" id="slab2" />
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index a1719c015..5894326ab 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -1,7 +1,7 @@
 import * as React from "react";
 import { library } from '@fortawesome/fontawesome-svg-core';
 import {
-    faTasks, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
+    faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, 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,
     faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
     faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
@@ -43,8 +43,9 @@ import { AudioUpload } from "./AudioUpload";
 import { Cast, FieldValue } from '../fields/Types';
 import { CollectionView } from '../client/views/collections/CollectionView';
 import { InkingStroke } from '../client/views/InkingStroke';
+import RichTextMenu from "../client/views/nodes/formattedText/RichTextMenu";
 
-library.add(faTasks, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
+library.add(faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
     faTerminal, faToggleOn, fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
     faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
     faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
@@ -387,13 +388,13 @@ export class MobileInterface extends React.Component {
                 return (
                     <div
                         className="item"
-                        key={index}
-                        onClick={() => this.handleClick(doc)}>
-                        <div className="item-title"> {doc.title} </div>
-                        <div className="item-type">{doc.type}</div>
-                        <FontAwesomeIcon className="right" icon="angle-right" size="lg" />
+                        key={index}>
+                        <div className="item-title" onClick={() => this.handleClick(doc)}> {doc.title} </div>
+                        <div className="item-type" onClick={() => this.handleClick(doc)}>{doc.type}</div>
+                        <FontAwesomeIcon onClick={() => this.handleClick(doc)} className="right" icon="angle-right" size="lg" style={{ display: `${doc.type === "collection" ? "block" : "none"}` }} />
                         <FontAwesomeIcon className="open" onClick={() => this.openFromSidebar(doc)} icon="external-link-alt" size="lg" />
-                    </div>);
+                    </div>
+                );
             }
         });
 
@@ -440,7 +441,7 @@ export class MobileInterface extends React.Component {
     }
 
     /**
-     * Handles the Create New Workspace button in the menu
+     * Handles the Create New Workspace button in the menu (taken from MainView.tsx)
      */
     @action
     createNewWorkspace = async (id?: string) => {
@@ -759,12 +760,7 @@ export class MobileInterface extends React.Component {
             toggle = this.toggleUpload;
         }
         return (
-            <div>
-                <div className="closeUpload" onClick={toggle}>
-                    <FontAwesomeIcon icon="window-close" size={"lg"} />
-                </div>
-                <Uploader Document={doc} />
-            </div>
+            <Uploader Document={doc} />
         );
     }
 
@@ -815,6 +811,7 @@ export class MobileInterface extends React.Component {
                 {this.switchMenuView()}
                 {this.inkMenu()}
                 <GestureOverlay>
+                    <div style={{ display: "none" }}><RichTextMenu key="rich" /></div>
                     <div className="docButtonContainer">
                         {this.pinToPresentation()}
                         {this.downloadDocument()}
@@ -843,6 +840,6 @@ Scripting.addGlobal(function openMobilePresentation() { return MobileInterface.I
 Scripting.addGlobal(function toggleMobileSidebar() { return MobileInterface.Instance.toggleSidebar(); });
 Scripting.addGlobal(function openMobileAudio() { return MobileInterface.Instance.toggleAudio(); });
 Scripting.addGlobal(function openMobileSettings() { return SettingsManager.Instance.open(); });
-Scripting.addGlobal(function openWorkspaces() { return MobileInterface.Instance.openWorkspaces(); });
+Scripting.addGlobal(function openMobileWorkspaces() { return MobileInterface.Instance.openWorkspaces(); });
 Scripting.addGlobal(function uploadImageMobile() { return MobileInterface.Instance.toggleUpload(); });
 Scripting.addGlobal(function switchToMobileUploads() { return MobileInterface.Instance.switchToMobileUploads(); });
diff --git a/src/mobile/MobileMenu.scss b/src/mobile/MobileMenu.scss
index 53bc48034..c499e6d1e 100644
--- a/src/mobile/MobileMenu.scss
+++ b/src/mobile/MobileMenu.scss
@@ -176,6 +176,7 @@ body {
 
 .open {
     right: 20px;
+    font-size: 35;
     position: absolute;
 }
 
-- 
cgit v1.2.3-70-g09d2


From 9d92f68750c943d82baa99dc26c29cdd58417e56 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 25 Jun 2020 21:35:10 -0400
Subject: fixed loading of pdf annotations.  fixed copy pasting of pdf excerpts

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +--
 src/client/views/pdf/Annotation.tsx                |  5 ++--
 src/client/views/pdf/PDFViewer.tsx                 | 35 ++++++++--------------
 3 files changed, 17 insertions(+), 27 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 936174b52..6632291c2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1383,9 +1383,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             onPointerDown={this.onPointerDown}
             onPointerMove={this.onCursorMove}
             onDrop={this.onExternalDrop.bind(this)}
-            onDragOver={e => {
-                e.preventDefault();
-            }}
+            onDragOver={e => e.preventDefault()}
             onContextMenu={this.onContextMenu}
             style={{
                 pointerEvents: this.backgroundEvents ? "all" : undefined,
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index cb6a15f36..d29b638e6 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -8,7 +8,6 @@ import { Cast, FieldValue, NumCast, StrCast } from "../../../fields/Types";
 import { DocumentManager } from "../../util/DocumentManager";
 import PDFMenu from "./PDFMenu";
 import "./Annotation.scss";
-import { DocumentView } from "../nodes/DocumentView";
 
 interface IAnnotationProps {
     anno: Doc;
@@ -19,7 +18,9 @@ interface IAnnotationProps {
     fieldKey: string;
 }
 
-export default class Annotation extends React.Component<IAnnotationProps> {
+@observer
+export default
+    class Annotation extends React.Component<IAnnotationProps> {
     render() {
         return DocListCast(this.props.anno.annotations).map(a => (
             <RegionAnnotation {...this.props} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />));
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 50ffd57fe..91d0feafe 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -80,7 +80,6 @@ interface IViewerProps {
 export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocument>(PdfDocument) {
     static _annotationStyle: any = addStyleSheet();
     @observable private _pageSizes: { width: number, height: number }[] = [];
-    @observable private _annotations: Doc[] = [];
     @observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
     @observable private _script: CompiledScript = CompileScript("return true") as CompiledScript;
     @observable private Index: number = -1;
@@ -112,17 +111,14 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     private _downY: number = 0;
     private _coverPath: any;
     private _viewerIsSetup = false;
+    private _lastSearch: string = "";
 
     @computed get allAnnotations() {
-        return DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]).filter(
-            anno => this._script.run({ this: anno }, console.log, true).result);
+        return DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]).
+            filter(anno => this._script.run({ this: anno }, console.log, true).result);
     }
+    @computed get nonDocAnnotations() { return this.allAnnotations.filter(a => a.annotations); }
 
-    @computed get nonDocAnnotations() {
-        return this._annotations.filter(anno => this._script.run({ this: anno }, console.log, true).result);
-    }
-
-    _lastSearch: string = "";
     componentDidMount = async () => {
         // change the address to be the file address of the PNG version of each page
         // file address of the pdf
@@ -148,7 +144,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         }
         runInAction(() => this._showWaiting = this._showCover = true);
         this.props.startupLive && this.setupPdfJsViewer();
-        this._mainCont.current!.scrollTop = this.layoutDoc._scrollTop || 0;
+        this._mainCont.current && (this._mainCont.current.scrollTop = this.layoutDoc._scrollTop || 0);
         this._searchReactionDisposer = reaction(() => this.Document.searchMatch, search => {
             if (search) {
                 this.search(Doc.SearchQuery(), true);
@@ -231,25 +227,19 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         await this.initialLoad();
 
         this._scrollTopReactionDisposer = reaction(() => Cast(this.layoutDoc._scrollTop, "number", null),
-            (stop) => (stop !== undefined && this.layoutDoc._scrollY === undefined) && (this._mainCont.current!.scrollTop = stop), { fireImmediately: true });
-
-        this._annotationReactionDisposer = reaction(
-            () => DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]),
-            annotations => annotations?.length && (this._annotations = annotations),
+            (stop) => (stop !== undefined && this.layoutDoc._scrollY === undefined && this._mainCont.current) && (this._mainCont.current.scrollTop = stop),
             { fireImmediately: true });
 
         this._filterReactionDisposer = reaction(
-            () => ({ scriptField: Cast(this.Document.filterScript, ScriptField), annos: this._annotations.slice() }),
-            action(({ scriptField, annos }: { scriptField: FieldResult<ScriptField>, annos: Doc[] }) => {
+            () => Cast(this.Document.filterScript, ScriptField),
+            action(scriptField => {
                 const oldScript = this._script.originalScript;
-                this._script = scriptField && scriptField.script.compiled ? scriptField.script : CompileScript("return true") as CompiledScript;
+                this._script = scriptField?.script.compiled ? scriptField.script : CompileScript("return true") as CompiledScript;
                 if (this._script.originalScript !== oldScript) {
                     this.Index = -1;
                 }
-                annos.forEach(d => d.opacity = this._script.run({ this: d }, console.log, 1).result ? 1 : 0);
             }),
-            { fireImmediately: true }
-        );
+            { fireImmediately: true });
 
         this.createPdfViewer();
     }
@@ -656,8 +646,9 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     @computed get annotationLayer() {
         TraceMobx();
         return <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.Document._nativeHeight), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
-            {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) =>
-                <Annotation {...this.props} focus={this.props.focus} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)}
+            {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
+                <Annotation {...this.props} focus={this.props.focus} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)
+            }
         </div>;
     }
     overlayTransform = () => this.scrollXf().scale(1 / this._zoomed);
-- 
cgit v1.2.3-70-g09d2


From 55bdee9e73513118c242f1665150dc7698eac50e Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 28 Jun 2020 00:16:38 -0400
Subject: renamed some fields scale=>_viewScale, transtion=>dataTransition,
 panTransformType=>_viewTransform.  fixed animation frames to not break acls
 (sort of).

---
 src/client/documents/Documents.ts                  |  4 +--
 src/client/util/DocumentManager.ts                 |  2 +-
 src/client/views/MainView.tsx                      |  2 +-
 src/client/views/animationtimeline/Timeline.tsx    |  4 +--
 .../collectionFreeForm/CollectionFreeFormView.scss |  7 +----
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 27 ++++++++---------
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 34 +++++++++++++---------
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/PresBox.tsx                 |  4 +--
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 +--
 src/client/views/pdf/PDFViewer.tsx                 |  2 +-
 src/fields/Doc.ts                                  |  2 +-
 12 files changed, 46 insertions(+), 48 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b175c018e..e8801df90 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -92,7 +92,7 @@ export interface DocumentOptions {
     label?: string; // short form of title for use as an icon label
     style?: string;
     page?: number;
-    scale?: number;
+    _viewScale?: number;
     isDisplayPanel?: boolean; // whether the panel functions as GoldenLayout "stack" used to display documents
     forceActive?: boolean;
     layout?: string | Doc; // default layout string for a document
@@ -228,7 +228,7 @@ export namespace Docs {
             }],
             [DocumentType.COL, {
                 layout: { view: CollectionView, dataField: defaultDataKey },
-                options: { _panX: 0, _panY: 0, scale: 1 } // , _width: 500, _height: 500 }
+                options: { _panX: 0, _panY: 0, _viewScale: 1 } // , _width: 500, _height: 500 }
             }],
             [DocumentType.KVP, {
                 layout: { view: KeyValueBox, dataField: defaultDataKey },
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 40d6b039a..fb5d1717e 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -170,7 +170,7 @@ export class DocumentManager {
                 const targetDocContextView = getFirstDocView(targetDocContext);
                 targetDocContext._scrollY = 0;  // this will force PDFs to activate and load their annotations / allow scrolling
                 if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first..
-                    targetDocContext.panTransformType = "Ease";
+                    targetDocContext._viewTransition = "transform 500ms";
                     targetDocContextView.props.focus(targetDocContextView.props.Document, willZoom);
 
                     // now find the target document within the context
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d7e7db592..68b81ab4f 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -83,7 +83,7 @@ export class MainView extends React.Component {
 
 
     componentDidMount() {
-        DocServer.setPlaygroundFields(["panTransformType", "transition", "_panX", "_panY", "scale", "_viewType"]); // can play with these fields on someone else's
+        DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType"]); // can play with these fields on someone else's
 
         const tag = document.createElement('script');
 
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index ab984f727..f54bd3aff 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -288,13 +288,13 @@ export class Timeline extends React.Component<FieldViewProps> {
     resetView(doc: Doc) {
         doc._panX = doc._customOriginX ?? 0;
         doc._panY = doc._customOriginY ?? 0;
-        doc.scale = doc._customOriginScale ?? 1;
+        doc._viewScale = doc._customOriginScale ?? 1;
     }
 
     setView(doc: Doc) {
         doc._customOriginX = doc._panX;
         doc._customOriginY = doc._panY;
-        doc._customOriginScale = doc.scale;
+        doc._customOriginScale = doc._viewScale;
     }
     /**
      * zooming mechanism (increment and spacing changes)
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index d9011c9d3..92aee3776 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,7 +1,6 @@
 @import "../../globalCssVariables";
 
-.collectionfreeformview-none,
-.collectionfreeformview-ease {
+.collectionfreeformview-none {
     position: inherit;
     top: 0;
     left: 0;
@@ -22,10 +21,6 @@
     }
 }
 
-.collectionfreeformview-ease {
-    transition: transform 500ms;
-}
-
 .collectionfreeformview-none {
     touch-action: none;
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2e543f137..5135c4ae4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -63,7 +63,7 @@ export const panZoomSchema = createSchema({
     fitToBox: "boolean",
     _xPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
     _yPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
-    panTransformType: "string",
+    _viewTransition: "string",
     scrollHeight: "number",
     fitX: "number",
     fitY: "number",
@@ -110,9 +110,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @computed get nativeWidth() { return this.fitToContent ? 0 : NumCast(this.Document._nativeWidth, this.props.NativeWidth()); }
     @computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight, this.props.NativeHeight()); }
     private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
-    private get scaleFieldKey() { return this.props.scaleField || "scale"; }
+    private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; }
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
-    private easing = () => this.props.Document.panTransformType === "Ease";
     private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document._panX || 0;
     private panY = () => this.fitToContent ? (this.contentBounds.y + this.contentBounds.b) / 2 : this.Document._panY || 0;
     private zoomScaling = () => (this.fitToContentScaling / this.parentScaling) * (this.fitToContent ?
@@ -832,7 +831,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             }
         }
         if (!this.layoutDoc._lockedTransform || this.Document.inOverlay) {
-            this.Document.panTransformType = panType;
+            this.Document._viewTransition = panType;
             const scale = this.getLocalTransform().inverse().Scale;
             const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
             const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY));
@@ -860,7 +859,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     scaleAtPt(docpt: number[], scale: number) {
         const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
-        this.Document.panTransformType = "Ease";
+        this.Document._viewTransition = "transform 500ms";
         this.layoutDoc[this.scaleFieldKey] = scale;
         const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
         const scrDelta = { x: screenXY[0] - newScreenXY[0], y: screenXY[1] - newScreenXY[1] };
@@ -905,14 +904,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY };
             HistoryUtil.pushState(newState);
 
-            const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document[this.scaleFieldKey], pt: this.Document.panTransformType };
+            const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document[this.scaleFieldKey], pt: this.Document._viewTransition };
 
             // if (!willZoom && DocumentView._focusHack.length) {
             //     Doc.BrushDoc(this.props.Document);
             //     !doc.z && NumCast(this.layoutDoc.scale) < 1 && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1);
             // } else {
             if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
-                if (!doc.z) this.setPan(newPanX, newPanY, "Ease", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+                if (!doc.z) this.setPan(newPanX, newPanY, "transform 500ms", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
             }
             Doc.BrushDoc(this.props.Document);
             this.props.focus(this.props.Document);
@@ -925,7 +924,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     this.Document._panX = savedState.px;
                     this.Document._panY = savedState.py;
                     this.Document[this.scaleFieldKey] = savedState.s;
-                    this.Document.panTransformType = savedState.pt;
+                    this.Document._viewTransition = savedState.pt;
                 }
             }, 500);
         }
@@ -1011,7 +1010,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const { z, color, zIndex } = params.pair.layout;
         return {
             x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
-            transition: StrCast(layoutDoc.transition), opacity: this.Document.editing ? 1 : Cast(opacity, "number", null),
+            transition: StrCast(layoutDoc.dataTransition), opacity: this.Document.editing ? 1 : Cast(opacity, "number", null),
             width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: ""
         };
     }
@@ -1333,9 +1332,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     nudge = action((x: number, y: number) => {
         if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform) { // bcz: this isn't ideal, but want to try it out...
             this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(),
-                NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "Ease", true);
+                NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "transform 500ms", true);
             this._nudgeTime = Date.now();
-            setTimeout(() => (Date.now() - this._nudgeTime >= 500) && (this.Document.panTransformType = undefined), 500);
+            setTimeout(() => (Date.now() - this._nudgeTime >= 500) && (this.Document._viewTransition = undefined), 500);
             return true;
         }
         return false;
@@ -1354,8 +1353,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             <CollectionFreeFormViewPannableContents
                 centeringShiftX={this.centeringShiftX}
                 centeringShiftY={this.centeringShiftY}
-                easing={this.easing}
-                transition={Cast(this.layoutDoc.transition, "string", null)}
+                transition={Cast(this.layoutDoc._viewTransition, "string", null)}
                 viewDefDivClick={this.props.viewDefDivClick}
                 zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
                 {this.children}
@@ -1434,7 +1432,6 @@ interface CollectionFreeFormViewPannableContentsProps {
     panX: () => number;
     panY: () => number;
     zoomScaling: () => number;
-    easing: () => boolean;
     viewDefDivClick?: ScriptField;
     children: () => JSX.Element[];
     transition?: string;
@@ -1443,7 +1440,7 @@ interface CollectionFreeFormViewPannableContentsProps {
 @observer
 class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{
     render() {
-        const freeformclass = "collectionfreeformview" + (this.props.viewDefDivClick ? "-viewDef" : (this.props.easing() ? "-ease" : "-none"));
+        const freeformclass = "collectionfreeformview" + (this.props.viewDefDivClick ? "-viewDef" : "-none");
         const cenx = this.props.centeringShiftX();
         const ceny = this.props.centeringShiftY();
         const panx = -this.props.panX();
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 404d69730..090beba0b 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -22,7 +22,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     zIndex?: number;
     highlight?: boolean;
     jitterRotation: number;
-    transition?: string;
+    dataTransition?: string;
     fitToBox?: boolean;
     replica: string;
 }
@@ -60,10 +60,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             const someView = Cast(this.props.Document.someView, Doc);
             const minimap = Cast(this.props.Document.minimap, Doc);
             if (someView instanceof Doc && minimap instanceof Doc) {
-                const x = (NumCast(someView._panX) - NumCast(someView._width) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitX) - NumCast(minimap.fitW) / 2)) / NumCast(minimap.fitW) * NumCast(minimap._width) - NumCast(minimap._width) / 2;
-                const y = (NumCast(someView._panY) - NumCast(someView._height) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap._height) - NumCast(minimap._height) / 2;
-                const w = NumCast(someView._width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width);
-                const h = NumCast(someView._height) / NumCast(someView.scale) / NumCast(minimap.fitH) * NumCast(minimap.height);
+                const x = (NumCast(someView._panX) - NumCast(someView._width) / 2 / NumCast(someView._viewScale) - (NumCast(minimap.fitX) - NumCast(minimap.fitW) / 2)) / NumCast(minimap.fitW) * NumCast(minimap._width) - NumCast(minimap._width) / 2;
+                const y = (NumCast(someView._panY) - NumCast(someView._height) / 2 / NumCast(someView._viewScale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap._height) - NumCast(minimap._height) / 2;
+                const w = NumCast(someView._width) / NumCast(someView._viewScale) / NumCast(minimap.fitW) * NumCast(minimap.width);
+                const h = NumCast(someView._height) / NumCast(someView._viewScale) / NumCast(minimap.fitH) * NumCast(minimap.height);
                 return { x: x, y: y, width: w, height: h };
             }
         }
@@ -81,9 +81,15 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     public static setValues(time: number, d: Doc, x?: number, y?: number, opacity?: number) {
         const timecode = Math.round(time);
-        Cast(d["x-indexed"], listSpec("number"), [])[timecode] = x as any as number;
-        Cast(d["y-indexed"], listSpec("number"), [])[timecode] = y as any as number;
-        Cast(d["opacity-indexed"], listSpec("number"), null)[timecode] = opacity as any as number;
+        const xindexed = Cast(d["x-indexed"], listSpec("number"), []).slice();
+        const yindexed = Cast(d["y-indexed"], listSpec("number"), []).slice();
+        const oindexed = Cast(d["opacity-indexed"], listSpec("number"), []).slice();
+        xindexed[timecode] = x as any as number;
+        yindexed[timecode] = y as any as number;
+        oindexed[timecode] = opacity as any as number;
+        d["x-indexed"] = new List<number>(xindexed);
+        d["y-indexed"] = new List<number>(yindexed);
+        d["opacity-indexed"] = new List<number>(oindexed);
     }
     public static updateKeyframe(docs: Doc[], time: number) {
         const timecode = Math.round(time);
@@ -94,14 +100,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             xindexed?.length <= timecode + 1 && xindexed.push(undefined as any as number);
             yindexed?.length <= timecode + 1 && yindexed.push(undefined as any as number);
             opacityindexed?.length <= timecode + 1 && opacityindexed.push(undefined as any as number);
-            doc.transition = "all 1s";
+            doc.dataTransition = "all 1s";
         });
-        setTimeout(() => docs.forEach(doc => doc.transition = "inherit"), 1010);
+        setTimeout(() => docs.forEach(doc => doc.dataTransition = "inherit"), 1010);
     }
 
     public static gotoKeyframe(docs: Doc[]) {
-        docs.forEach(doc => doc.transition = "all 1s");
-        setTimeout(() => docs.forEach(doc => doc.transition = "inherit"), 1010);
+        docs.forEach(doc => doc.dataTransition = "all 1s");
+        setTimeout(() => docs.forEach(doc => doc.dataTransition = "inherit"), 1010);
     }
 
     public static setupKeyframes(docs: Doc[], timecode: number, progressivize: boolean = false) {
@@ -119,7 +125,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             doc.x = ComputedField.MakeInterpolated("x", "activeFrame");
             doc.y = ComputedField.MakeInterpolated("y", "activeFrame");
             doc.opacity = ComputedField.MakeInterpolated("opacity", "activeFrame");
-            doc.transition = "inherit";
+            doc.dataTransition = "inherit";
         });
     }
 
@@ -150,7 +156,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 borderRadius: StrCast(Doc.Layout(this.layoutDoc).borderRounding),
                 outline: this.Highlight ? "orange solid 2px" : "",
                 transform: this.transform,
-                transition: this.props.transition ? this.props.transition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.transition),
+                transition: this.props.transition ? this.props.transition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition),
                 width: this.props.Document.isInkMask ? 5000 : this.width,
                 height: this.props.Document.isInkMask ? 5000 : this.height,
                 zIndex: this.ZInd,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 22917f18b..342bbc606 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1202,7 +1202,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             style={{
                 transformOrigin: this._animateScalingTo ? "center center" : undefined,
                 transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
-                transition: !this._animateScalingTo ? StrCast(this.Document.transition) : this._animateScalingTo < 1 ? "transform 0.5s ease-in" : "transform 0.5s ease-out",
+                transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : this._animateScalingTo < 1 ? "transform 0.5s ease-in" : "transform 0.5s ease-out",
                 pointerEvents: this.ignorePointerEvents ? "none" : undefined,
                 color: StrCast(this.layoutDoc.color, "inherit"),
                 outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index dbc879920..8818d375e 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -62,8 +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) {
-            presTargetDoc.transition = "all 1s";
-            setTimeout(() => presTargetDoc.transition = undefined, 1010);
+            presTargetDoc._viewTransition = "all 1s";
+            setTimeout(() => presTargetDoc._viewTransition = undefined, 1010);
             presTargetDoc.currentFrame = curFrame + 1;
         }
         else if (this.childDocs[this.itemIndex + 1] !== undefined) {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 135ab8cbc..7bc1078d7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1221,7 +1221,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     tryUpdateHeight(limitHeight?: number) {
         let scrollHeight = this._ref.current?.scrollHeight;
         if (this.props.renderDepth && this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight && scrollHeight) {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
-            scrollHeight = scrollHeight * NumCast(this.layoutDoc.scale, 1);
+            scrollHeight = scrollHeight * NumCast(this.layoutDoc._viewScale, 1);
             if (limitHeight && scrollHeight > limitHeight) {
                 scrollHeight = limitHeight;
                 this.layoutDoc.limitHeight = undefined;
@@ -1250,7 +1250,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); }
     render() {
         TraceMobx();
-        const scale = this.props.ContentScaling() * NumCast(this.layoutDoc.scale, 1);
+        const scale = this.props.ContentScaling() * NumCast(this.layoutDoc._viewScale, 1);
         const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
         const interactive = Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc.isBackground;
         if (this.props.isSelected()) {
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index bd74e0dd0..d8130ce63 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -433,7 +433,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         this._downX = e.clientX;
         this._downY = e.clientY;
         addStyleSheetRule(PDFViewer._annotationStyle, "pdfAnnotation", { "pointer-events": "none" });
-        if ((this.Document.scale || 1) !== 1) return;
+        if ((this.Document._viewScale || 1) !== 1) return;
         if ((e.button !== 0 || e.altKey) && this.active(true)) {
             this._setPreviewCursor?.(e.clientX, e.clientY, true);
             //e.stopPropagation();
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 8c8720179..231ee88e3 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -973,7 +973,7 @@ export namespace Doc {
     export function toggleNativeDimensions(layoutDoc: Doc, contentScale: number, panelWidth: number, panelHeight: number) {
         runInAction(() => {
             if (layoutDoc._nativeWidth || layoutDoc._nativeHeight) {
-                layoutDoc.scale = NumCast(layoutDoc.scale, 1) * contentScale;
+                layoutDoc._viewScale = NumCast(layoutDoc._viewScale, 1) * contentScale;
                 layoutDoc._nativeWidth = undefined;
                 layoutDoc._nativeHeight = undefined;
             }
-- 
cgit v1.2.3-70-g09d2


From 29f9cedd937697c56ce3b400931b26c82a23721d Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 29 Jun 2020 00:21:07 -0400
Subject: changed a lot of collection keys to start with "_" -- particularly
 for Stacking and Schema views.  Also added filtering to facet filters. 
 enabled facet filter for sidebar library.

---
 .../apis/google_docs/GooglePhotosClientUtils.ts    |  1 -
 src/client/documents/Documents.ts                  | 40 +++++------
 src/client/util/CurrentUserUtils.ts                | 24 +++----
 .../util/Import & Export/DirectoryImportBox.tsx    |  2 +-
 src/client/views/MainView.tsx                      | 18 ++---
 .../collections/CollectionMasonryViewFieldRow.tsx  | 10 +--
 .../views/collections/CollectionSchemaView.tsx     | 16 +++--
 .../views/collections/CollectionStackingView.tsx   | 77 +++++++++++-----------
 .../CollectionStackingViewFieldColumn.tsx          | 18 ++---
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../views/collections/CollectionTreeView.tsx       |  4 +-
 src/client/views/collections/CollectionView.tsx    |  8 +--
 .../views/collections/CollectionViewChromes.tsx    |  8 ++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  1 -
 src/client/views/nodes/KeyValueBox.tsx             |  4 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |  4 +-
 .../views/nodes/formattedText/RichTextRules.ts     |  4 +-
 src/client/views/pdf/PDFViewer.tsx                 |  2 +-
 src/fields/documentSchemas.ts                      | 10 ++-
 src/fields/util.ts                                 |  2 +-
 src/scraping/buxton/scraper.py                     |  3 +-
 22 files changed, 131 insertions(+), 131 deletions(-)

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

diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index a604c7de1..13bfb3a91 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -130,7 +130,6 @@ export namespace GooglePhotos {
             const uploads = await Transactions.WriteMediaItemsToServer(response);
             const children = uploads.map((upload: Transactions.UploadInformation) => {
                 const document = Docs.Create.ImageDocument(Utils.fileUrl(upload.fileNames.clean));
-                document.fillColumn = true;
                 document.contentSize = upload.contentSize;
                 return document;
             });
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f71984ca6..b70971c2d 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -62,7 +62,7 @@ export interface DocumentOptions {
     _dimUnit?: string; // "px" or "*" (default = "*")
     _fitWidth?: boolean;
     _fitToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents
-    _LODdisable?: boolean;
+    _freeformLOD?: boolean; // whether to use LOD to render a freeform document
     _showTitleHover?: string; // 
     _showTitle?: string; // which field to display in the title area.  leave empty to have no title
     _showCaption?: string; // which field to display in the caption area.  leave empty to have no caption
@@ -99,7 +99,7 @@ export interface DocumentOptions {
     childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox or Buxton layout in tree view)
     childLayoutString?: string; // template string for collection to use to render its children
     hideFilterView?: boolean; // whether to hide the filter popout on collections
-    hideHeadings?: boolean; // whether stacking view column headings should be hidden
+    _columnsHideIfEmpty?: boolean; // whether stacking view column headings should be hidden
     isTemplateForField?: string; // the field key for which the containing document is a rendering template
     isTemplateDoc?: boolean;
     targetScriptKey?: string; // where to write a template script (used by collections with click templates which need to target onClick, onDoubleClick, etc)
@@ -119,7 +119,7 @@ export interface DocumentOptions {
     defaultBackgroundColor?: string;
     isBackground?: boolean;
     isLinkButton?: boolean;
-    columnWidth?: number;
+    _columnWidth?: number;
     _fontSize?: number;
     _fontFamily?: string;
     curPage?: number;
@@ -137,7 +137,8 @@ export interface DocumentOptions {
     "onCheckedClick-rawScript"?: string; // onChecked script in raw text form
     "onCheckedClick-params"?: List<string>; // parameter list for onChecked treeview functions
     _pivotField?: string; // field key used to determine headings for sections in stacking, masonry, pivot views
-    schemaColumns?: List<SchemaHeaderField>;
+    _columnHeaders?: List<SchemaHeaderField>; // headers for stacking views
+    _schemaHeaders?: List<SchemaHeaderField>; // headers for schema view
     dockingConfig?: string;
     annotationOn?: Doc;
     removeDropProperties?: List<string>; // list of properties that should be removed from a document when it is dropped.  e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document
@@ -428,8 +429,7 @@ export namespace Docs {
             const parent = TreeDocument([loading], {
                 title: "The Buxton Collection",
                 _width: 400,
-                _height: 400,
-                _LODdisable: true
+                _height: 400
             });
             const parentProto = Doc.GetProto(parent);
             const { _socket } = DocServer;
@@ -465,7 +465,7 @@ export namespace Docs {
                         return imageDoc;
                     });
                     // the main document we create
-                    const doc = StackingDocument(deviceImages, { title, _LODdisable: true, hero: new ImageField(constructed[0].url) });
+                    const doc = StackingDocument(deviceImages, { title, hero: new ImageField(constructed[0].url) });
                     doc.nameAliases = new List<string>([title.toLowerCase()]);
                     // add the parsed attributes to this main document
                     Doc.Get.FromJson({ data: device, appendToExisting: { targetDoc: Doc.GetProto(doc) } });
@@ -698,15 +698,15 @@ export namespace Docs {
         }
 
         export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Freeform }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Freeform }, id);
         }
 
         export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Pile }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", ...options, _viewType: CollectionViewType.Pile }, id);
         }
 
         export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Linear }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", ...options, _viewType: CollectionViewType.Linear }, id);
         }
 
         export function MapDocument(documents: Array<Doc>, options: DocumentOptions = {}) {
@@ -714,35 +714,35 @@ export namespace Docs {
         }
 
         export function CarouselDocument(documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Carousel });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Carousel });
         }
 
         export function Carousel3DDocument(documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Carousel3D });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Carousel3D });
         }
 
-        export function SchemaDocument(schemaColumns: SchemaHeaderField[], documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List(schemaColumns.length ? schemaColumns : [new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Schema });
+        export function SchemaDocument(schemaHeaders: SchemaHeaderField[], documents: Array<Doc>, options: DocumentOptions) {
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", _schemaHeaders: schemaHeaders.length ? new List(schemaHeaders) : undefined, ...options, _viewType: CollectionViewType.Schema });
         }
 
         export function TreeDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Tree }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Tree }, id);
         }
 
         export function StackingDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Stacking }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Stacking }, id);
         }
 
         export function MulticolumnDocument(documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Multicolumn });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Multicolumn });
         }
         export function MultirowDocument(documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Multirow });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Multirow });
         }
 
 
         export function MasonryDocument(documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Masonry });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Masonry });
         }
 
         export function LabelDocument(options?: DocumentOptions) {
@@ -1054,7 +1054,7 @@ export namespace DocUtils {
             });
         });
         if (x !== undefined && y !== undefined) {
-            const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: x - 55, y: y - 55, _width: 110, _height: 100, _LODdisable: true });
+            const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: x - 55, y: y - 55, _width: 110, _height: 100 });
             newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55;
             newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
             newCollection._width = newCollection._height = 110;
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 2b0b2c738..d0ca0e57e 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -43,7 +43,7 @@ export class CurrentUserUtils {
             const queryTemplate = Docs.Create.MulticolumnDocument(
                 [
                     Docs.Create.QueryDocument({ title: "query", _height: 200 }),
-                    Docs.Create.FreeformDocument([], { title: "data", _height: 100, _LODdisable: true })
+                    Docs.Create.FreeformDocument([], { title: "data", _height: 100 })
                 ],
                 { _width: 400, _height: 300, title: "queryView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true }
             );
@@ -136,9 +136,9 @@ export class CurrentUserUtils {
         if (doc["template-button-switch"] === undefined) {
             const { FreeformDocument, MulticolumnDocument, TextDocument } = Docs.Create;
 
-            const yes = FreeformDocument([], { title: "yes", _height: 35, _width: 50, _LODdisable: true, _dimUnit: DimUnit.Pixel, _dimMagnitude: 40 });
+            const yes = FreeformDocument([], { title: "yes", _height: 35, _width: 50, _dimUnit: DimUnit.Pixel, _dimMagnitude: 40 });
             const name = TextDocument("name", { title: "name", _height: 35, _width: 70, _dimMagnitude: 1 });
-            const no = FreeformDocument([], { title: "no", _height: 100, _width: 100, _LODdisable: true });
+            const no = FreeformDocument([], { title: "no", _height: 100, _width: 100 });
             const labelTemplate = {
                 doc: {
                     type: "doc", content: [{
@@ -193,10 +193,10 @@ export class CurrentUserUtils {
 
             const shared = { _chromeStatus: "disabled", _autoHeight: true, _xMargin: 0 };
             const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: 12 };
-            const descriptionWrapperOpts = { title: "descriptions", _height: 300, columnWidth: -1, treeViewHideTitle: true, _pivotField: "title" };
+            const descriptionWrapperOpts = { title: "descriptions", _height: 300, _columnWidth: -1, treeViewHideTitle: true, _pivotField: "title" };
 
             const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts });
-            descriptionWrapper.sectionHeaders = new List<SchemaHeaderField>([
+            descriptionWrapper._columnHeaders = new List<SchemaHeaderField>([
                 new SchemaHeaderField("[A Short Description]", "dimGray", undefined, undefined, undefined, false),
                 new SchemaHeaderField("[Long Description]", "dimGray", undefined, undefined, undefined, true),
                 new SchemaHeaderField("[Details]", "dimGray", undefined, undefined, undefined, true),
@@ -225,7 +225,7 @@ export class CurrentUserUtils {
         if (doc["template-buttons"] === undefined) {
             doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument(requiredTypes, {
                 title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title",
-                _autoHeight: true, _width: 500, columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled",
+                _autoHeight: true, _width: 500, _columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled",
                 dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
             }));
         } else {
@@ -358,11 +358,11 @@ export class CurrentUserUtils {
     }[] {
         if (doc.emptyPresentation === undefined) {
             doc.emptyPresentation = Docs.Create.PresDocument(new List<Doc>(),
-                { title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias", _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" });
+                { title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias", _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" });
         }
         if (doc.emptyCollection === undefined) {
             doc.emptyCollection = Docs.Create.FreeformDocument([],
-                { _nativeWidth: undefined, _nativeHeight: undefined, _LODdisable: true, _width: 150, _height: 100, title: "freeform" });
+                { _nativeWidth: undefined, _nativeHeight: undefined, _width: 150, _height: 100, title: "freeform" });
         }
         if (doc.emptyDocHolder === undefined) {
             doc.emptyDocHolder = Docs.Create.DocumentDocument(
@@ -430,7 +430,7 @@ export class CurrentUserUtils {
         if (dragCreatorSet === undefined) {
             doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, {
                 title: "Basic Item Creators", _showTitle: "title", _xMargin: 0,
-                _autoHeight: true, _width: 500, columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled",
+                _autoHeight: true, _width: 500, _columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled",
                 dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
             }));
         } else {
@@ -493,7 +493,7 @@ export class CurrentUserUtils {
 
     static setupMobileDoc(userDoc: Doc) {
         return userDoc.activeMoble ?? Docs.Create.MasonryDocument(CurrentUserUtils.setupMobileButtons(userDoc), {
-            columnWidth: 100, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", _autoHeight: true, _yMargin: 5
+            _columnWidth: 100, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", _autoHeight: true, _yMargin: 5
         });
     }
 
@@ -655,7 +655,7 @@ export class CurrentUserUtils {
         // Finally, setup the list of buttons to display in the sidebar
         if (doc["tabs-buttons"] === undefined) {
             doc["tabs-buttons"] = new PrefetchProxy(Docs.Create.StackingDocument([searchBtn, libraryBtn, toolsBtn], {
-                _width: 500, _height: 80, boxShadow: "0 0", _pivotField: "title", hideHeadings: true, ignoreClick: true, _chromeStatus: "view-mode",
+                _width: 500, _height: 80, boxShadow: "0 0", _pivotField: "title", _columnsHideIfEmpty: true, ignoreClick: true, _chromeStatus: "view-mode",
                 title: "sidebar btn row stack", backgroundColor: "dimGray",
             }));
             (toolsBtn.onClick as ScriptField).script.run({ this: toolsBtn });
@@ -704,7 +704,7 @@ export class CurrentUserUtils {
         if (doc.activePresentation === undefined) {
             doc.activePresentation = Docs.Create.PresDocument(new List<Doc>(), {
                 title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias",
-                _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0"
+                _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0"
             });
         }
     }
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index af6c57e68..77f13e9f4 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -161,7 +161,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
                 importContainer = Docs.Create.SchemaDocument(headers, docs, options);
             }
             runInAction(() => this.phase = 'External: uploading files to Google Photos...');
-            importContainer.singleColumn = false;
+            importContainer._columnsStack = false;
             await GooglePhotos.Export.CollectionToAlbum({ collection: importContainer });
             Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer);
             !this.persistent && this.props.removeDocument && this.props.removeDocument(doc);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 68b81ab4f..8f5a31b6c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -206,7 +206,6 @@ export class MainView extends React.Component {
             _width: this._panelWidth * .7,
             _height: this._panelHeight,
             title: "Collection " + workspaceCount,
-            _LODdisable: true
         };
         const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
         const workspaceDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row");
@@ -354,15 +353,6 @@ export class MainView extends React.Component {
         }
     }
 
-    @action
-    pointerOverDragger = () => {
-        // if (this.flyoutWidth === 0) {
-        //     this.flyoutWidth = 250;
-        //     this.sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30;
-        //     this._flyoutTranslate = false;
-        // }
-    }
-
     @action
     pointerLeaveDragger = () => {
         if (!this._flyoutTranslate) {
@@ -375,13 +365,13 @@ export class MainView extends React.Component {
     onPointerMove = (e: PointerEvent) => {
         this.flyoutWidth = Math.max(e.clientX, 0);
         Math.abs(this.flyoutWidth - this._flyoutSizeOnDown) > 6 && (this._canClick = false);
-        this.sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30;
+        this.sidebarButtonsDoc._columnWidth = this.flyoutWidth / 3 - 30;
     }
     @action
     onPointerUp = (e: PointerEvent) => {
         if (Math.abs(e.clientX - this._flyoutSizeOnDown) < 4 && this._canClick) {
             this.flyoutWidth = this.flyoutWidth < 15 ? 250 : 0;
-            this.flyoutWidth && (this.sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30);
+            this.flyoutWidth && (this.sidebarButtonsDoc._columnWidth = this.flyoutWidth / 3 - 30);
         }
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
@@ -470,7 +460,7 @@ export class MainView extends React.Component {
             }} >
                 <div style={{ display: "contents", flexDirection: "row", position: "relative" }}>
                     <div className="mainView-flyoutContainer" onPointerLeave={this.pointerLeaveDragger} style={{ width: this.flyoutWidth }}>
-                        <div className="mainView-libraryHandle" onPointerDown={this.onPointerDown} onPointerOver={this.pointerOverDragger}
+                        <div className="mainView-libraryHandle" onPointerDown={this.onPointerDown}
                             style={{ backgroundColor: this.defaultBackgroundColors(sidebar) }}>
                             <span title="library View Dragger" style={{
                                 width: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "3vw",
@@ -497,7 +487,7 @@ export class MainView extends React.Component {
     public static expandFlyout = action(() => {
         MainView.Instance._flyoutTranslate = true;
         MainView.Instance.flyoutWidth = (MainView.Instance.flyoutWidth || 250);
-        MainView.Instance.sidebarButtonsDoc.columnWidth = MainView.Instance.flyoutWidth / 3 - 30;
+        MainView.Instance.sidebarButtonsDoc._columnWidth = MainView.Instance.flyoutWidth / 3 - 30;
     });
 
     @computed get expandButton() {
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index e0b53e762..627b22417 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -111,8 +111,8 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
         const key = StrCast(this.props.parent.props.Document._pivotField);
         const castedValue = this.getValue(value);
         if (castedValue) {
-            if (this.props.parent.sectionHeaders) {
-                if (this.props.parent.sectionHeaders.map(i => i.heading).indexOf(castedValue.toString()) > -1) {
+            if (this.props.parent.columnHeaders) {
+                if (this.props.parent.columnHeaders.map(i => i.heading).indexOf(castedValue.toString()) > -1) {
                     return false;
                 }
             }
@@ -151,9 +151,9 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
         this._createAliasSelected = false;
         const key = StrCast(this.props.parent.props.Document._pivotField);
         this.props.docList.forEach(d => d[key] = undefined);
-        if (this.props.parent.sectionHeaders && this.props.headingObject) {
-            const index = this.props.parent.sectionHeaders.indexOf(this.props.headingObject);
-            this.props.parent.sectionHeaders.splice(index, 1);
+        if (this.props.parent.columnHeaders && this.props.headingObject) {
+            const index = this.props.parent.columnHeaders.indexOf(this.props.headingObject);
+            this.props.parent.columnHeaders.splice(index, 1);
         }
     }));
 
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 56a2a517c..b780da97e 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -240,10 +240,10 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
     @computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH - this.previewWidth(); }
 
     @computed get columns() {
-        return Cast(this.props.Document.schemaColumns, listSpec(SchemaHeaderField), []);
+        return Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []);
     }
     set columns(columns: SchemaHeaderField[]) {
-        this.props.Document.schemaColumns = new List<SchemaHeaderField>(columns);
+        this.props.Document._schemaHeaders = new List<SchemaHeaderField>(columns);
     }
 
     @computed get childDocs() {
@@ -375,10 +375,12 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
     constructor(props: SchemaTableProps) {
         super(props);
         // convert old schema columns (list of strings) into new schema columns (list of schema header fields)
-        const oldSchemaColumns = Cast(this.props.Document.schemaColumns, listSpec("string"), []);
-        if (oldSchemaColumns && oldSchemaColumns.length && typeof oldSchemaColumns[0] !== "object") {
-            const newSchemaColumns = oldSchemaColumns.map(i => typeof i === "string" ? new SchemaHeaderField(i, "#f1efeb") : i);
-            this.props.Document.schemaColumns = new List<SchemaHeaderField>(newSchemaColumns);
+        const oldSchemaHeaders = Cast(this.props.Document._schemaHeaders, listSpec("string"), []);
+        if (oldSchemaHeaders?.length && typeof oldSchemaHeaders[0] !== "object") {
+            const newSchemaHeaders = oldSchemaHeaders.map(i => typeof i === "string" ? new SchemaHeaderField(i, "#f1efeb") : i);
+            this.props.Document._schemaHeaders = new List<SchemaHeaderField>(newSchemaHeaders);
+        } else if (this.props.Document._schemaHeaders === undefined) {
+            this.props.Document._schemaHeaders = new List<SchemaHeaderField>([new SchemaHeaderField("title", "#f1efeb")]);
         }
     }
 
@@ -740,7 +742,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
                 if(col === undefined) {
                     return (doc as any)[key][row + ${row}];
                 }
-                return (doc as any)[key][row + ${row}][(doc as any).schemaColumns[col + ${col}].heading];
+                return (doc as any)[key][row + ${row}][(doc as any)._schemaHeaders[col + ${col}].heading];
             }
             return ${script}`;
         const compiled = CompileScript(script, { params: { this: Doc.name }, capturedVariables: { doc: this.props.Document, key: this.props.fieldKey }, typecheck: false, transformer: this.createTransformer(row, col) });
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 4aab43b08..7bdf5e7df 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -43,27 +43,26 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
     @observable _heightMap = new Map<string, number>();
     @observable _cursor: CursorProperty = "grab";
     @observable _scroll = 0; // used to force the document decoration to update when scrolling
-    @computed get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
-    @computed get pivotField() { return StrCast(this.props.Document._pivotField); }
+    @computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField)); }
+    @computed get pivotField() { return StrCast(this.layoutDoc._pivotField); }
     @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout); }
-    @computed get xMargin() { return NumCast(this.props.Document._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); }
-    @computed get yMargin() { return Math.max(this.props.Document._showTitle && !this.props.Document._showTitleHover ? 30 : 0, NumCast(this.props.Document._yMargin, 0)); } // 2 * this.gridGap)); }
-    @computed get gridGap() { return NumCast(this.props.Document._gridGap, 10); }
-    @computed get isStackingView() { return BoolCast(this.props.Document.singleColumn, true); }
+    @computed get xMargin() { return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); }
+    @computed get yMargin() { return Math.max(this.layoutDoc._showTitle && !this.layoutDoc._showTitleHover ? 30 : 0, NumCast(this.layoutDoc._yMargin, 0)); } // 2 * this.gridGap)); }
+    @computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); }
+    @computed get isStackingView() { return BoolCast(this.layoutDoc._columnsStack, true); }
     @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
-    @computed get showAddAGroup() { return (this.pivotField && (this.props.Document._chromeStatus !== 'view-mode' && this.props.Document._chromeStatus !== 'disabled')); }
+    @computed get showAddAGroup() { return (this.pivotField && (this.layoutDoc._chromeStatus !== 'view-mode' && this.layoutDoc._chromeStatus !== 'disabled')); }
     @computed get columnWidth() {
-        TraceMobx();
-        return Math.min(this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin,
-            this.isStackingView ? Number.MAX_VALUE : this.props.Document.columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.props.Document.columnWidth, 250));
+        return Math.min(this.props.PanelWidth() / this.props.ContentScaling() - 2 * this.xMargin,
+            this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250));
     }
     @computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; }
 
     constructor(props: any) {
         super(props);
 
-        if (this.sectionHeaders === undefined) {
-            this.props.Document.sectionHeaders = new List<SchemaHeaderField>();
+        if (this.columnHeaders === undefined) {
+            this.layoutDoc._columnHeaders = new List<SchemaHeaderField>();
         }
     }
 
@@ -89,14 +88,14 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
     }
 
     get Sections() {
-        if (!this.pivotField || this.sectionHeaders instanceof Promise) return new Map<SchemaHeaderField, Doc[]>();
+        if (!this.pivotField || this.columnHeaders instanceof Promise) return new Map<SchemaHeaderField, Doc[]>();
 
-        if (this.sectionHeaders === undefined) {
-            setTimeout(() => this.props.Document.sectionHeaders = new List<SchemaHeaderField>(), 0);
+        if (this.columnHeaders === undefined) {
+            setTimeout(() => this.layoutDoc._columnHeaders = new List<SchemaHeaderField>(), 0);
             return new Map<SchemaHeaderField, Doc[]>();
         }
-        const sectionHeaders = Array.from(this.sectionHeaders);
-        const fields = new Map<SchemaHeaderField, Doc[]>(sectionHeaders.map(sh => [sh, []] as [SchemaHeaderField, []]));
+        const columnHeaders = Array.from(this.columnHeaders);
+        const fields = new Map<SchemaHeaderField, Doc[]>(columnHeaders.map(sh => [sh, []] as [SchemaHeaderField, []]));
         let changed = false;
         this.filteredChildren.map(d => {
             const sectionValue = (d[this.pivotField] ? d[this.pivotField] : `NO ${this.pivotField.toUpperCase()} VALUE`) as object;
@@ -105,26 +104,26 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
             const castedSectionValue = !isNaN(parsed) ? parsed : sectionValue;
 
             // look for if header exists already
-            const existingHeader = sectionHeaders.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`));
+            const existingHeader = columnHeaders.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`));
             if (existingHeader) {
                 fields.get(existingHeader)!.push(d);
             }
             else {
                 const newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`);
                 fields.set(newSchemaHeader, [d]);
-                sectionHeaders.push(newSchemaHeader);
+                columnHeaders.push(newSchemaHeader);
                 changed = true;
             }
         });
         // remove all empty columns if hideHeadings is set
-        if (this.props.Document.hideHeadings) {
+        if (this.layoutDoc._columnsHideIfEmpty) {
             Array.from(fields.keys()).filter(key => !fields.get(key)!.length).map(header => {
                 fields.delete(header);
-                sectionHeaders.splice(sectionHeaders.indexOf(header), 1);
+                columnHeaders.splice(columnHeaders.indexOf(header), 1);
                 changed = true;
             });
         }
-        changed && setTimeout(action(() => { if (this.sectionHeaders) { this.sectionHeaders.length = 0; this.sectionHeaders.push(...sectionHeaders); } }), 0);
+        changed && setTimeout(action(() => { if (this.columnHeaders) { this.columnHeaders.length = 0; this.columnHeaders.push(...columnHeaders); } }), 0);
         return fields;
     }
 
@@ -136,7 +135,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
         let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
         if (!layoutDoc._fitWidth && nw && nh) {
             const aspect = nw && nh ? nh / nw : 1;
-            if (!(this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid);
+            if (!(this.layoutDoc._columnsFill)) wid = Math.min(layoutDoc[WidthSym](), wid);
             return wid * aspect;
         }
         return layoutDoc._fitWidth ? wid * NumCast(layoutDoc.scrollHeight, nh) / (nw || 1) : layoutDoc[HeightSym]();
@@ -147,7 +146,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
         // reset section headers when a new filter is inputted
         this._pivotFieldDisposer = reaction(
             () => this.pivotField,
-            () => this.props.Document.sectionHeaders = new List()
+            () => this.layoutDoc._columnHeaders = new List()
         );
     }
     componentWillUnmount() {
@@ -211,7 +210,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
             fitToBox={false}
             dontRegisterView={this.props.dontRegisterView}
             rootSelected={this.rootSelected}
-            dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
+            dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
             onClick={this.onChildClickHandler}
             onDoubleClick={this.onChildDoubleClickHandler}
             ScreenToLocalTransform={dxf}
@@ -236,7 +235,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
         if (!d) return 0;
         const layoutDoc = Doc.Layout(d, this.props.ChildLayoutTemplate?.());
         const nw = NumCast(layoutDoc._nativeWidth);
-        return Math.min(nw && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
+        return Math.min(nw && !this.layoutDoc._columnsFill ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
     }
     getDocHeight(d?: Doc) {
         if (!d) return 0;
@@ -246,7 +245,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
         let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
         if (!layoutDoc._fitWidth && nw && nh) {
             const aspect = nw && nh ? nh / nw : 1;
-            if (!(this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid);
+            if (!(this.layoutDoc._columnsFill)) wid = Math.min(layoutDoc[WidthSym](), wid);
             return wid * aspect;
         }
         return layoutDoc._fitWidth ? !nh ? this.props.PanelHeight() - 2 * this.yMargin :
@@ -259,7 +258,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
     }
     @action
     onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
-        this.layoutDoc.columnWidth = Math.max(10, this.columnWidth + delta[0]);
+        this.layoutDoc._columnWidth = Math.max(10, this.columnWidth + delta[0]);
         return false;
     }
 
@@ -341,7 +340,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
                     this.refList.push(ref);
                     const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
                     this.observer = new _global.ResizeObserver(action((entries: any) => {
-                        if (this.props.Document._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
+                        if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
                             Doc.Layout(doc)._height = Math.min(1200, Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))));
                         }
                     }));
@@ -388,7 +387,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
                     this.refList.push(ref);
                     const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
                     this.observer = new _global.ResizeObserver(action((entries: any) => {
-                        if (this.props.Document._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
+                        if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
                             Doc.Layout(doc)._height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0);
                         }
                     }));
@@ -411,9 +410,9 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
 
     @action
     addGroup = (value: string) => {
-        if (value && this.sectionHeaders) {
+        if (value && this.columnHeaders) {
             const schemaHdrField = new SchemaHeaderField(value);
-            this.sectionHeaders.push(schemaHdrField);
+            this.columnHeaders.push(schemaHdrField);
             DocUtils.addFieldEnumerations(undefined, this.pivotField, [{ title: value, _backgroundColor: schemaHdrField.color }]);
             return true;
         }
@@ -421,22 +420,22 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
     }
 
     sortFunc = (a: [SchemaHeaderField, Doc[]], b: [SchemaHeaderField, Doc[]]): 1 | -1 => {
-        const descending = BoolCast(this.props.Document.stackingHeadersSortDescending);
+        const descending = StrCast(this.layoutDoc._columnsSort) === "descending";
         const firstEntry = descending ? b : a;
         const secondEntry = descending ? a : b;
         return firstEntry[0].heading > secondEntry[0].heading ? 1 : -1;
     }
 
     onToggle = (checked: Boolean) => {
-        this.props.Document._chromeStatus = checked ? "collapsed" : "view-mode";
+        this.layoutDoc._chromeStatus = checked ? "collapsed" : "view-mode";
     }
 
     onContextMenu = (e: React.MouseEvent): void => {
         // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
         if (!e.isPropagationStopped()) {
             const subItems: ContextMenuProps[] = [];
-            subItems.push({ description: `${this.props.Document.fillColumn ? "Variable Size" : "Autosize"} Column`, event: () => this.props.Document.fillColumn = !this.props.Document.fillColumn, icon: "plus" });
-            subItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
+            subItems.push({ description: `${this.layoutDoc._columnsFill ? "Variable Size" : "Autosize"} Column`, event: () => this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill, icon: "plus" });
+            subItems.push({ description: `${this.layoutDoc._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
             ContextMenu.Instance.addItem({ description: "Options...", subitems: subItems, icon: "eye" });
         }
     }
@@ -446,7 +445,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
         let sections = [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]];
         if (this.pivotField) {
             const entries = Array.from(this.Sections.entries());
-            sections = entries.sort(this.sortFunc);
+            sections = this.layoutDoc._columnsSort ? entries.sort(this.sortFunc) : entries;
         }
         return sections.map((section, i) => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1], i === 0));
     }
@@ -489,10 +488,10 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
                             style={{ width: !this.isStackingView ? "100%" : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
                             <EditableView {...editableViewProps} />
                         </div>}
-                    {this.props.Document._chromeStatus !== 'disabled' && this.props.isSelected() ? <Switch
+                    {this.layoutDoc._chromeStatus !== 'disabled' && this.props.isSelected() ? <Switch
                         onChange={this.onToggle}
                         onClick={this.onToggle}
-                        defaultChecked={this.props.Document._chromeStatus !== 'view-mode'}
+                        defaultChecked={this.layoutDoc._chromeStatus !== 'view-mode'}
                         checkedChildren="edit"
                         unCheckedChildren="view"
                     /> : null}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index b60ed853b..b147b089b 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -96,8 +96,8 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         const key = StrCast(this.props.parent.props.Document._pivotField);
         const castedValue = this.getValue(value);
         if (castedValue) {
-            if (this.props.parent.sectionHeaders) {
-                if (this.props.parent.sectionHeaders.map(i => i.heading).indexOf(castedValue.toString()) > -1) {
+            if (this.props.parent.columnHeaders) {
+                if (this.props.parent.columnHeaders.map(i => i.heading).indexOf(castedValue.toString()) > -1) {
                     return false;
                 }
             }
@@ -148,9 +148,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
     deleteColumn = () => {
         const key = StrCast(this.props.parent.props.Document._pivotField);
         this.props.docList.forEach(d => d[key] = undefined);
-        if (this.props.parent.sectionHeaders && this.props.headingObject) {
-            const index = this.props.parent.sectionHeaders.indexOf(this.props.headingObject);
-            this.props.parent.sectionHeaders.splice(index, 1);
+        if (this.props.parent.columnHeaders && this.props.headingObject) {
+            const index = this.props.parent.columnHeaders.indexOf(this.props.headingObject);
+            this.props.parent.columnHeaders.splice(index, 1);
         }
     }
 
@@ -168,7 +168,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
 
     startDrag = (e: PointerEvent, down: number[], delta: number[]) => {
         const alias = Doc.MakeAlias(this.props.parent.props.Document);
-        alias._width = this.props.parent.props.PanelWidth() / (Cast(this.props.parent.props.Document.sectionHeaders, listSpec(SchemaHeaderField))?.length || 1);
+        alias._width = this.props.parent.props.PanelWidth() / (Cast(this.props.parent.columnHeaders, listSpec(SchemaHeaderField))?.length || 1);
         alias._pivotField = undefined;
         const key = StrCast(this.props.parent.props.Document._pivotField);
         let value = this.getValue(this._heading);
@@ -259,8 +259,8 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                     }
                 }, icon: "compress-arrows-alt"
             }));
-        layoutItems.push({ description: ":freeform", event: () => this.props.parent.props.addDocument(Docs.Create.FreeformDocument([], { _width: 200, _height: 200, _LODdisable: true })), icon: "compress-arrows-alt" });
-        layoutItems.push({ description: ":carousel", event: () => this.props.parent.props.addDocument(Docs.Create.CarouselDocument([], { _width: 400, _height: 200, _LODdisable: true })), icon: "compress-arrows-alt" });
+        layoutItems.push({ description: ":freeform", event: () => this.props.parent.props.addDocument(Docs.Create.FreeformDocument([], { _width: 200, _height: 200 })), icon: "compress-arrows-alt" });
+        layoutItems.push({ description: ":carousel", event: () => this.props.parent.props.addDocument(Docs.Create.CarouselDocument([], { _width: 400, _height: 200 })), icon: "compress-arrows-alt" });
         layoutItems.push({ description: ":columns", event: () => this.props.parent.props.addDocument(Docs.Create.MulticolumnDocument([], { _width: 200, _height: 200 })), icon: "compress-arrows-alt" });
         layoutItems.push({ description: ":image", event: () => this.props.parent.props.addDocument(Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { _width: 200, _height: 200 })), icon: "compress-arrows-alt" });
 
@@ -359,7 +359,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                     background: this._background
                 }}
                 ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
-                {this.props.parent.Document.hideHeadings ? (null) : headingView}
+                {this.props.parent.Document._columnsHideIfEmpty ? (null) : headingView}
                 {
                     this.collapsed ? (null) :
                         <div>
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 3a13ac822..49480e759 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -341,7 +341,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                                     const rect = "getBoundingClientRect" in focusNode ? focusNode.getBoundingClientRect() : focusNode?.parentElement.getBoundingClientRect();
                                     const x = (rect?.x || 0);
                                     const y = NumCast(srcWeb._scrollTop) + (rect?.y || 0);
-                                    const anchor = Docs.Create.FreeformDocument([], { _LODdisable: true, _backgroundColor: "transparent", _width: 25, _height: 25, x, y, annotationOn: srcWeb });
+                                    const anchor = Docs.Create.FreeformDocument([], { _backgroundColor: "transparent", _width: 25, _height: 25, x, y, annotationOn: srcWeb });
                                     anchor.context = srcWeb;
                                     const key = Doc.LayoutFieldKey(srcWeb);
                                     Doc.AddDocToList(srcWeb, key + "-annotations", anchor);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index d54f4d6e6..f6f6fb7cc 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -176,7 +176,7 @@ class TreeView extends React.Component<TreeViewProps> {
         })}
         OnFillDown={undoBatch((value: string) => {
             Doc.SetInPlace(this.doc, key, value, false);
-            const doc = Docs.Create.FreeformDocument([], { title: "-", x: 0, y: 0, _width: 100, _height: 25, _LODdisable: true, templates: new List<string>([Templates.Title.Layout]) });
+            const doc = Docs.Create.FreeformDocument([], { title: "-", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
             Doc.SetInPlace(this.doc, "editTitle", undefined, false);
             Doc.SetInPlace(doc, "editTitle", "*", false);
             return this.props.addDocument(doc);
@@ -828,7 +828,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
                         SetValue={undoBatch((value: string) => Doc.SetInPlace(this.dataDoc, "title", value, false) || true)}
                         OnFillDown={undoBatch((value: string) => {
                             Doc.SetInPlace(this.dataDoc, "title", value, false);
-                            const doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, _LODdisable: true, templates: new List<string>([Templates.Title.Layout]) });
+                            const doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
                             Doc.SetInPlace(doc, "editTitle", "*", false);
                             this.addDoc(doc, childDocs.length ? childDocs[0] : undefined, true);
                         })} />}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 5c87bc483..f2ffe7835 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -207,8 +207,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             case CollectionViewType.Pile: { return (<CollectionPileView key="collview" {...props} />); }
             case CollectionViewType.Carousel: { return (<CollectionCarouselView key="collview" {...props} />); }
             case CollectionViewType.Carousel3D: { return (<CollectionCarousel3DView key="collview" {...props} />); }
-            case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} />); }
-            case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); }
+            case CollectionViewType.Stacking: { this.props.Document._columnsStack = true; return (<CollectionStackingView key="collview" {...props} />); }
+            case CollectionViewType.Masonry: { this.props.Document._columnsStack = false; return (<CollectionStackingView key="collview" {...props} />); }
             case CollectionViewType.Time: { return (<CollectionTimeView key="collview" {...props} />); }
             case CollectionViewType.Map: return (<CollectionMapView key="collview" {...props} />);
             case CollectionViewType.Grid: return (<CollectionGridView key="gridview" {...props} />);
@@ -360,7 +360,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         const facets = new Set<string>();
         this.childDocs.filter(child => child).forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
         Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.filter(child => child).forEach(child => Object.keys(child).forEach(key => facets.add(key)));
-        return Array.from(facets);
+        return Array.from(facets).filter(f => !f.startsWith("_") && !["proto", "zIndex", "isPrototype", "context", "text-noTemplate"].includes(f)).sort();
     }
 
     /**
@@ -534,7 +534,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                         Utils.CorsProxy(Cast(d.data, ImageField)!.url.href) : Cast(d.data, ImageField)!.url.href
                     :
                     ""))}
-            {!this.props.isSelected() || this.props.PanelHeight() < 100 || this.props.Document.hideFilterView ? (null) :
+            {(!this.props.isSelected() || this.props.Document.hideFilterView) && !this.props.Document.forceActive ? (null) :
                 <div className="collectionView-filterDragger" title="library View Dragger" onPointerDown={this.onPointerDown}
                     style={{ right: this.facetWidth() - 1, top: this.props.Document._viewType === CollectionViewType.Docking ? "25%" : "55%" }} />
             }
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 276bccede..ab0df88f8 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -406,7 +406,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
     @observable private _currentKey: string = "";
     @observable private suggestions: string[] = [];
 
-    @computed private get descending() { return BoolCast(this.props.CollectionView.props.Document.stackingHeadersSortDescending); }
+    @computed 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[]> => {
@@ -450,7 +450,11 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
         return true;
     }
 
-    @action toggleSort = () => { this.props.CollectionView.props.Document.stackingHeadersSortDescending = !this.props.CollectionView.props.Document.stackingHeadersSortDescending; };
+    @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() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5135c4ae4..ba20e9830 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1226,7 +1226,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
         if (!Doc.UserDoc().noviceMode) {
             optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
-            optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
+            optionItems.push({ description: `${this.Document._freeformLOD ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" });
             optionItems.push({
                 description: "Import document", icon: "upload", event: ({ x, y }) => {
                     const input = document.createElement("input");
@@ -1390,7 +1390,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 width: this.contentScaling ? `${100 / this.contentScaling}%` : "",
                 height: this.contentScaling ? `${100 / this.contentScaling}%` : this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
             }}>
-            {!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ?
+            {this.Document._freeformLOD && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ?
                 this.placeholder : this.marqueeView}
             <CollectionFreeFormOverlayView elements={this.elementFunc} />
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 099859109..97ed74c10 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -352,7 +352,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             backgroundColor: this.props.isAnnotationOverlay ? "#00000015" : isBackground ? "cyan" : undefined,
             _width: bounds.width,
             _height: bounds.height,
-            _LODdisable: true,
             title: "a nested collection",
         });
         selected.forEach(d => d.context = newCollection);
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index d375466c9..b732f5f83 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -169,8 +169,8 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
 
     getTemplate = async () => {
         const parent = Docs.Create.StackingDocument([], { _width: 800, _height: 800, title: "Template" });
-        parent.singleColumn = false;
-        parent.columnWidth = 100;
+        parent._columnsStack = false;
+        parent._columnWidth = 100;
         for (const row of this.rows.filter(row => row.isChecked)) {
             await this.createTemplateField(parent, row);
             row.uncheck();
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 8c16f4a1a..8718bf329 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -184,9 +184,9 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
         if (container) {
             const alias = Doc.MakeAlias(container.props.Document);
             alias.viewType = CollectionViewType.Time;
-            let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField));
+            let list = Cast(alias._columnHeaders, listSpec(SchemaHeaderField));
             if (!list) {
-                alias.schemaColumns = list = new List<SchemaHeaderField>();
+                alias._columnHeaders = list = new List<SchemaHeaderField>();
             }
             list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, "#f1efeb"));
             list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb"));
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index ba3230801..6a85e3b7c 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -275,7 +275,7 @@ export class RichTextRules {
                     if (!fieldKey) {
                         if (docid) {
                             DocServer.GetRefField(docid).then(docx => {
-                                const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true, }, docid);
+                                const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, }, docid);
                                 DocUtils.Publish(target, docid, returnFalse, returnFalse);
                                 DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal to");
                             });
@@ -305,7 +305,7 @@ export class RichTextRules {
                     if (!fieldKey && !docid) return state.tr;
                     docid && DocServer.GetRefField(docid).then(docx => {
                         if (!(docx instanceof Doc && docx)) {
-                            const docx = Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true }, docid);
+                            const docx = Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500 }, docid);
                             DocUtils.Publish(docx, docid, returnFalse, returnFalse);
                         }
                     });
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 5ed1e6239..372d01b9c 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -293,7 +293,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         let minY = Number.MAX_VALUE;
         if ((this._savedAnnotations.values()[0][0] as any).marqueeing) {
             const anno = this._savedAnnotations.values()[0][0];
-            const annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, _LODdisable: true, title: "Annotation on " + this.Document.title });
+            const annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, title: "Annotation on " + this.Document.title });
             if (anno.style.left) annoDoc.x = parseInt(anno.style.left);
             if (anno.style.top) annoDoc.y = parseInt(anno.style.top);
             if (anno.style.height) annoDoc._height = parseInt(anno.style.height);
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 40dadf5a8..97f62c9d4 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -2,6 +2,8 @@ import { makeInterface, createSchema, listSpec } from "./Schema";
 import { ScriptField } from "./ScriptField";
 import { Doc } from "./Doc";
 import { DateField } from "./DateField";
+import { SchemaHeaderField } from "./SchemaHeaderField";
+import { Schema } from "prosemirror-model";
 
 export const documentSchema = createSchema({
     // content properties
@@ -43,10 +45,16 @@ export const documentSchema = createSchema({
     _showTitleHover: "string",  // the showTitle should be shown only on hover
     _showAudio: "boolean",      // whether to show the audio record icon on documents
     _freeformLayoutEngine: "string",// the string ID for the layout engine to use to layout freeform view documents
-    _LODdisable: "boolean",     // whether to disbale LOD switching for CollectionFreeFormViews
+    _freeformLOD: "boolean",    // whether to enable LOD switching for CollectionFreeFormViews
     _pivotField: "string",      // specifies which field key should be used as the timeline/pivot axis
     _replacedChrome: "string",  // what the default chrome is replaced with. Currently only supports the value of 'replaced' for PresBox's.
     _chromeStatus: "string",    // determines the state of the collection chrome. values allowed are 'replaced', 'enabled', 'disabled', 'collapsed'
+    _columnsFill: "boolean",    // whether documents in a stacking view column should be sized to fill the column
+    _columnsSort: "string",     // how a document should be sorted "ascending", "descending", undefined (none)   
+    _columnsStack: "boolean",   // whether a stacking document stacks vertically (as opposed to masonry horizontal)
+    _columnsHideIfEmpty: "boolean",   // whether empty stacking view column headings should be hidden
+    _columnHeaders: listSpec(SchemaHeaderField), // header descriptions for stacking/masonry
+    _schemaHeaders: listSpec(SchemaHeaderField), // header descriptions for schema views
     _fontSize: "number",
     _fontFamily: "string",
     _sidebarWidthPercent: "string", // percent of text window width taken up by sidebar
diff --git a/src/fields/util.ts b/src/fields/util.ts
index c4affb2d7..2869ebed1 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -108,7 +108,7 @@ export function OVERRIDE_ACL(val: boolean) {
 }
 
 const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox",
-    "LODdisable", "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"];
+    "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"];
 export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
     let prop = in_prop;
     if (target[AclSym] && !_overrideAcl && !DocServer.PlaygroundFields.includes(in_prop.toString())) return true;
diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py
index ed122e544..312915e2d 100644
--- a/src/scraping/buxton/scraper.py
+++ b/src/scraping/buxton/scraper.py
@@ -91,14 +91,13 @@ def write_collection(parse_results, display_fields, storage_key, viewType):
             "zIndex": 2,
             "libraryBrush": False,
             "_viewType": viewType,
-            "_LODdisable": True
         },
         "__type": "Doc"
     }
 
     fields["proto"] = protofy(common_proto_id)
     fields[storage_key] = listify(proxify_guids(view_guids))
-    fields["schemaColumns"] = listify(display_fields)
+    fields["_columnHeaders"] = listify(display_fields)
     fields["author"] = "Bill Buxton"
     fields["creationDate"] = {
         "date": datetime.datetime.utcnow().microsecond,
-- 
cgit v1.2.3-70-g09d2


From cb8b5b3d9e735c88b9efdd9b4a444cac40fdedb2 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 29 Jun 2020 10:55:27 -0400
Subject: cleaned up undo handling on internal drag/drop

---
 src/client/util/CurrentUserUtils.ts                                   | 2 +-
 src/client/util/DragManager.ts                                        | 4 ++--
 src/client/views/collections/CollectionSubView.tsx                    | 1 -
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx   | 1 -
 4 files changed, 3 insertions(+), 5 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index d0ca0e57e..9c1881ef8 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -654,7 +654,7 @@ export class CurrentUserUtils {
 
         // Finally, setup the list of buttons to display in the sidebar
         if (doc["tabs-buttons"] === undefined) {
-            doc["tabs-buttons"] = new PrefetchProxy(Docs.Create.StackingDocument([searchBtn, libraryBtn, toolsBtn], {
+            doc["tabs-buttons"] = new PrefetchProxy(Docs.Create.StackingDocument([libraryBtn, searchBtn, toolsBtn], {
                 _width: 500, _height: 80, boxShadow: "0 0", _pivotField: "title", _columnsHideIfEmpty: true, ignoreClick: true, _chromeStatus: "view-mode",
                 title: "sidebar btn row stack", backgroundColor: "dimGray",
             }));
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 0db3963b2..2ceafff30 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -202,7 +202,6 @@ export namespace DragManager {
             dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(dropDoc);
             return dropDoc;
         };
-        const batch = UndoManager.StartBatch("dragging");
         const finishDrag = (e: DragCompleteEvent) => {
             const docDragData = e.docDragData;
             if (docDragData && !docDragData.droppedDocuments.length) {
@@ -216,7 +215,6 @@ export namespace DragManager {
                     const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps));
                     remProps.map(prop => drop[prop] = undefined);
                 });
-                batch.end();
             }
             return e;
         };
@@ -315,6 +313,7 @@ export namespace DragManager {
     export let docsBeingDragged: Doc[] = [];
     export let CanEmbed = false;
     export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) {
+        const batch = UndoManager.StartBatch("dragging");
         eles = eles.filter(e => e);
         CanEmbed = false;
         if (!dragDiv) {
@@ -449,6 +448,7 @@ export namespace DragManager {
             document.removeEventListener("pointermove", moveHandler, true);
             document.removeEventListener("pointerup", upHandler);
             SnappingManager.clearSnapLines();
+            batch.end();
         });
 
         AbortDrag = () => {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 49480e759..29144abaf 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -208,7 +208,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
 
         addDocument = (doc: Doc | Doc[]) => this.props.addDocument(doc);
 
-        @undoBatch
         @action
         protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
             const docDragData = de.complete.docDragData;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ba20e9830..546a4307c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -194,7 +194,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         return (pt => super.onExternalDrop(e, { x: pt[0], y: pt[1] }))(this.getTransform().transformPoint(e.pageX, e.pageY));
     }
 
-    @undoBatch
     @action
     internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) {
         if (!super.onInternalDrop(e, de)) return false;
-- 
cgit v1.2.3-70-g09d2


From 992a65ec6f828dda0adcd3cd121896f07f64af87 Mon Sep 17 00:00:00 2001
From: Lionel Han <47760119+IGoByJoe@users.noreply.github.com>
Date: Mon, 29 Jun 2020 14:21:57 -0700
Subject: minor fixes

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +-
 src/client/views/nodes/DocumentView.tsx                             | 6 +++---
 src/mobile/MobileInterface.tsx                                      | 1 -
 3 files changed, 4 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 42aa0f58e..a068e391e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1214,7 +1214,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         appearanceItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
         appearanceItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
         appearanceItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
-        !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
+        !appearance && ContextMenu.Instance?.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
 
         const options = ContextMenu.Instance?.findByDescription("Options...");
         const optionItems = options && "subitems" in options ? options.subitems : [];
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index afd28534e..0f46be345 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -749,7 +749,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
         templateDoc && optionItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
         optionItems.push({ description: "Toggle Show Each Link Dot", event: () => this.layoutDoc.showLinks = !this.layoutDoc.showLinks, icon: "eye" });
-        !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
+        !options && cm?.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
 
         const existingOnClick = cm?.findByDescription("OnClick...");
         const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
@@ -767,7 +767,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
             funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
             funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined });
-            cm.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
+            cm?.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
         }
 
         const more = cm?.findByDescription("More...");
@@ -806,7 +806,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
         helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("http://localhost:1050/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" });
         helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
-        cm.addItem({ description: "Help...", subitems: helpItems, icon: "question" });
+        cm?.addItem({ description: "Help...", subitems: helpItems, icon: "question" });
 
         const existingAcls = cm?.findByDescription("Privacy...");
         const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index e39349ff2..8cec03897 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -518,7 +518,6 @@ export class MobileInterface extends React.Component {
     inkMenu = () => {
         if (this._activeDoc._viewType === "docking") {
             if (this._ink) {
-                console.log("here");
                 return <div className="colorSelector">
                     <InkOptionsMenu />
                 </div>;
-- 
cgit v1.2.3-70-g09d2


From 1daf0a496175267d86f495bb24aa5140b6a9eeab Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 2 Jul 2020 14:05:33 -0400
Subject: small clanup in Mobile related code

---
 src/client/views/GestureOverlay.tsx                |  2 +-
 src/client/views/collections/CollectionView.tsx    | 21 ++++++++--------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 11 ++++----
 src/client/views/nodes/DocumentView.tsx            | 29 +++++++++++-----------
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 5 files changed, 33 insertions(+), 32 deletions(-)

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

diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 839ab09c7..487467b2b 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -264,7 +264,7 @@ export default class GestureOverlay extends Touchable {
     handleHandDown = async (e: React.TouchEvent) => {
         this._holdTimer && clearTimeout(this._holdTimer);
 
-        // this chunk of code helps us keep track of which touch events ar e associated with a hand event
+        // this chunk of code helps us keep track of which touch events are associated with a hand event
         // so that if a hand is held down, but a second hand is interacting with dash, the second hand's events
         // won't interfere with the first hand's events.
         const fingers = new Array<React.Touch>();
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 35196b634..300309882 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -231,12 +231,12 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
 
 
     setupViewTypes(category: string, func: (viewType: CollectionViewType) => Doc, addExtras: boolean) {
-        const existingVm = ContextMenu.Instance?.findByDescription(category);
+        const existingVm = ContextMenu.Instance.findByDescription(category);
         const subItems = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
 
         subItems.push({ description: "Freeform", event: () => func(CollectionViewType.Freeform), icon: "signature" });
         if (addExtras && CollectionView._safeMode) {
-            ContextMenu.Instance?.addItem({ description: "Test Freeform", event: () => func(CollectionViewType.Invalid), icon: "project-diagram" });
+            ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => func(CollectionViewType.Invalid), icon: "project-diagram" });
         }
         subItems.push({ description: "Schema", event: () => func(CollectionViewType.Schema), icon: "th-list" });
         subItems.push({ description: "Tree", event: () => func(CollectionViewType.Tree), icon: "tree" });
@@ -255,11 +255,12 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
         }
         addExtras && subItems.push({ description: "lightbox", event: action(() => this._isLightboxOpen = true), icon: "eye" });
-        !existingVm && ContextMenu.Instance?.addItem({ description: category, subitems: subItems, icon: "eye" });
+        !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: subItems, icon: "eye" });
     }
 
     onContextMenu = (e: React.MouseEvent): void => {
-        if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+        const cm = ContextMenu.Instance;
+        if (cm && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             this.setupViewTypes("Add a Perspective...", vtype => {
                 const newRendition = Doc.MakeAlias(this.props.Document);
                 newRendition._viewType = vtype;
@@ -267,7 +268,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                 return newRendition;
             }, false);
 
-            const existing = ContextMenu.Instance?.findByDescription("Options...");
+            const existing = cm.findByDescription("Options...");
             const layoutItems = existing && "subitems" in existing ? existing.subitems : [];
             layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
             if (this.props.Document.childLayout instanceof Doc) {
@@ -278,9 +279,9 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             }
             layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
-            !existing && ContextMenu.Instance?.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
+            !existing && cm.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
 
-            const existingOnClick = ContextMenu.Instance?.findByDescription("OnClick...");
+            const existingOnClick = cm.findByDescription("OnClick...");
             const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
             const funcs = [
                 { key: "onChildClick", name: "On Child Clicked" },
@@ -296,13 +297,13 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                     icon: "edit",
                     event: () => this.props.Document[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)),
                 }));
-            !existingOnClick && ContextMenu.Instance?.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+            !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
 
             if (!Doc.UserDoc().noviceMode) {
-                const more = ContextMenu.Instance?.findByDescription("More...");
+                const more = cm.findByDescription("More...");
                 const moreItems = more && "subitems" in more ? more.subitems : [];
                 moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) });
-                !more && ContextMenu.Instance?.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+                !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
             }
         }
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ae7213d0b..21b0473c9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -602,7 +602,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
         // bcz: theres should be a better way of doing these than referencing these static instances directly
         MarqueeOptionsMenu.Instance?.fadeOut(true);// I think it makes sense for the marquee menu to go away when panned. -syip2
-        // PDFMenu.Instance.fadeOut(true);
 
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
         this.setPan((this.Document._panX || 0) - dx, (this.Document._panY || 0) - dy, undefined, true);
@@ -1204,17 +1203,17 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     private thumbIdentifier?: number;
 
     onContextMenu = (e: React.MouseEvent) => {
-        if (this.props.annotationsKey) return;
+        if (this.props.annotationsKey || !ContextMenu.Instance) return;
 
-        const appearance = ContextMenu.Instance?.findByDescription("Appearance...");
+        const appearance = ContextMenu.Instance.findByDescription("Appearance...");
         const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
         appearanceItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
         appearanceItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
         appearanceItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
         appearanceItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
-        !appearance && ContextMenu.Instance?.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
+        !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
 
-        const options = ContextMenu.Instance?.findByDescription("Options...");
+        const options = ContextMenu.Instance.findByDescription("Options...");
         const optionItems = options && "subitems" in options ? options.subitems : [];
         !this.props.isAnnotationOverlay &&
             optionItems.push({ description: (this.showTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this.showTimeline = !this.showTimeline), icon: faEye });
@@ -1254,7 +1253,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 }
             });
         }
-        !options && ContextMenu.Instance?.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
+        !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
 
     }
     @observable showTimeline = false;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8d122baeb..732089068 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -736,21 +736,22 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
 
         const cm = ContextMenu.Instance;
+        if (!cm) return;
 
         const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
         Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) =>
-            cm?.addItem({ description: label, event: () => customScripts[i]?.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
+            cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
         this.props.contextMenuItems?.().forEach(item =>
-            cm?.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
+            cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
 
         const options = cm.findByDescription("Options...");
         const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
         const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
         templateDoc && optionItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
         optionItems.push({ description: "Toggle Show Each Link Dot", event: () => this.layoutDoc.showLinks = !this.layoutDoc.showLinks, icon: "eye" });
-        !options && cm?.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
+        !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
 
-        const existingOnClick = cm?.findByDescription("OnClick...");
+        const existingOnClick = cm.findByDescription("OnClick...");
         const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
         onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
         onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`), icon: "window-restore" });
@@ -759,17 +760,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link on Right", event: this.toggleFollowOnRight, icon: "concierge-bell" });
         onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
         onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
-        !existingOnClick && cm?.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+        !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
 
         const funcs: ContextMenuProps[] = [];
         if (this.layoutDoc.onDragStart) {
             funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
             funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
             funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined });
-            cm?.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
+            cm.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
         }
 
-        const more = cm?.findByDescription("More...");
+        const more = cm.findByDescription("More...");
         const moreItems = more && "subitems" in more ? more.subitems : [];
         if (!Doc.UserDoc().noviceMode) {
             moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
@@ -798,16 +799,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
         moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
         moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" });
-        !more && cm?.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
-        cm?.moveAfter(cm?.findByDescription("More...")!, cm?.findByDescription("OnClick...")!);
+        !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+        cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
 
-        const help = cm?.findByDescription("Help...");
+        const help = cm.findByDescription("Help...");
         const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
         helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("http://localhost:1050/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" });
         helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
-        cm?.addItem({ description: "Help...", subitems: helpItems, icon: "question" });
+        cm.addItem({ description: "Help...", subitems: helpItems, icon: "question" });
 
-        const existingAcls = cm?.findByDescription("Privacy...");
+        const existingAcls = cm.findByDescription("Privacy...");
         const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
         aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" });
         aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
@@ -815,7 +816,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" });
         aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
         aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
-        !existingAcls && cm?.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
+        !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
 
         // const recommender_subitems: ContextMenuProps[] = [];
 
@@ -876,7 +877,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 // DocumentViews should stop propagation of this event
                 e.stopPropagation();
             }
-            ContextMenu.Instance?.displayMenu(e.pageX - 15, e.pageY - 15);
+            cm.displayMenu(e.pageX - 15, e.pageY - 15);
             !SelectionManager.IsSelected(this, true) && SelectionManager.SelectDoc(this, false);
         });
         const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc()["tabs-button-library"] as Doc).sourcePanel as Doc) ? "" : d.title), "");
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 270fcb01c..b127cd024 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -190,7 +190,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 }
             }
             const state = this._editorView.state.apply(tx);
-            this._editorView?.updateState(state);
+            this._editorView.updateState(state);
             (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks);
 
             const tsel = this._editorView.state.selection.$from;
-- 
cgit v1.2.3-70-g09d2


From 37392f606d29547bed46ffcd0f0190ad38f51148 Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Fri, 3 Jul 2020 01:06:18 -0500
Subject: link created popup

---
 src/client/documents/Documents.ts                  |  8 ++++----
 src/client/views/MainView.tsx                      | 23 +++++++++++++---------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  5 +++++
 src/client/views/linking/LinkMenu.scss             |  9 ++++-----
 src/client/views/linking/LinkMenu.tsx              |  4 ++++
 src/client/views/linking/LinkMenuItem.tsx          | 11 ++++++++---
 src/client/views/nodes/DocumentLinksButton.tsx     | 14 +++++++++++--
 src/fields/Doc.ts                                  |  4 ++--
 8 files changed, 53 insertions(+), 25 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2e0323ede..0f434d616 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -868,6 +868,10 @@ export namespace DocUtils {
         DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline"));
     }
 
+    function stopLinkCreated() {
+        MainView.linkCreated = false;
+    }
+
     export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", id?: string) {
         const sv = DocumentManager.Instance.getDocumentView(source.doc);
         if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
@@ -880,10 +884,6 @@ export namespace DocUtils {
         Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)");
         Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)");
 
-        runInAction(() => { MainView.linkCreated = true; });
-        console.log("link created");
-        runInAction(() => { setTimeout("MainView.changeLinkCreated()", 2000); });
-
         return linkDoc;
     }
 
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 4898e114a..617cc19b1 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -84,6 +84,8 @@ export class MainView extends React.Component {
     public isPointerDown = false;
 
     @observable public static linkCreated: boolean = false;
+    @observable public static popupX: number = 600;
+    @observable public static popupY: number = 250;
 
 
     componentDidMount() {
@@ -614,15 +616,18 @@ export class MainView extends React.Component {
                 {this.mainContent}
             </GestureOverlay>
             <PreviewCursor />
-            {MainView.linkCreated ? <div className="mainView-linkCreated" style={{
-                border: "2px solid red", top: screenTop / 2,
-                left: screenLeft / 2, width: "50px", height: "20px", zIndex: 10000,
-                fontSize: "12px"
-            }}>
-                {/* <Fade in={MainView.linkCreated}> */}
-                link created!
-                {/* </Fade> */}
-            </div> : null}
+
+            <Fade in={MainView.linkCreated}>
+                <div style={{
+                    border: "1px solid rgb(111, 144, 175)", top: MainView.popupX ? MainView.popupX : 600
+                    , position: "absolute",
+                    left: MainView.popupY ? MainView.popupY : 250
+                    , width: "auto", height: "auto", zIndex: 10000,
+                    fontSize: "13px", whiteSpace: "nowrap", backgroundColor: "rgb(170, 205, 238)",
+                    padding: "5px", //fontWeight: "bold"
+                }}>Link Created</div>
+            </Fade>
+
             {DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
             {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors}
                 linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 546a4307c..39bfd8239 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -48,6 +48,7 @@ import { SnappingManager } from "../../../util/SnappingManager";
 import { InkingStroke, ActiveArrowStart, ActiveArrowEnd, ActiveInkColor, ActiveFillColor, ActiveInkWidth, ActiveInkBezierApprox, ActiveDash } from "../../InkingStroke";
 import { DocumentType } from "../../../documents/DocumentTypes";
 import { DocumentLinksButton } from "../../nodes/DocumentLinksButton";
+import { MainView } from "../../MainView";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -248,6 +249,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         } else {
             const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
             this.props.addDocument(source);
+            MainView.popupX = xp;
+            MainView.popupY = yp;
+            runInAction(() => { MainView.linkCreated = true; });
+            runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2500); });
             linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed
             e.stopPropagation();
             return true;
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index c372e7098..6468ccd3d 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -15,7 +15,7 @@
 }
 
 .linkMenu-group {
-    border-bottom: 0.5px solid lightgray; 
+    border-bottom: 0.5px solid lightgray;
     padding: 5px 0;
 
 
@@ -30,9 +30,11 @@
             p {
                 background-color: lightgray;
             }
+
             p.expand-one {
                 width: calc(100% - 26px);
             }
+
             .linkEditor-tableButton {
                 display: block;
             }
@@ -50,7 +52,4 @@
             display: none;
         }
     }
-}
-
-
-
+}
\ No newline at end of file
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index c36e7b67c..c672511ac 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -10,6 +10,7 @@ import { LinkMenuGroup } from "./LinkMenuGroup";
 import { faTrash } from '@fortawesome/free-solid-svg-icons';
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
+import { LinkDocPreview } from "../nodes/LinkDocPreview";
 
 library.add(faTrash);
 
@@ -28,6 +29,9 @@ export class LinkMenu extends React.Component<Props> {
 
     @action
     onClick = (e: PointerEvent) => {
+
+        LinkDocPreview.LinkInfo = undefined;
+
         if (this._linkMenuRef?.contains(e.target as any)) {
             DocumentLinksButton.EditLink = undefined;
         }
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 54dca261d..b11a173f3 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -97,6 +97,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         return (<div className="link-metadata">{mdRows}</div>);
     }
 
+    @action
     onLinkButtonDown = (e: React.PointerEvent): void => {
         this._downX = e.clientX;
         this._downY = e.clientY;
@@ -106,6 +107,10 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         document.addEventListener("pointermove", this.onLinkButtonMoved);
         document.removeEventListener("pointerup", this.onLinkButtonUp);
         document.addEventListener("pointerup", this.onLinkButtonUp);
+
+        //if (this._editRef && this._editRef.current?.contains(e.target as any)) {
+        LinkDocPreview.LinkInfo = undefined;
+        //}
     }
 
     onLinkButtonUp = (e: PointerEvent): void => {
@@ -168,14 +173,14 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                         }))}
                         onPointerDown={this.onLinkButtonDown}>
                         <p >{StrCast(this.props.destinationDoc.title)}</p>
-                        <div className="linkMenu-item-buttons">
+                        <div className="linkMenu-item-buttons" ref={this._editRef} >
                             {canExpand ? <div title="Show more" className="button" onPointerDown={e => this.toggleShowMore(e)}>
                                 <FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
 
                             {/* <div title="Edit link" className="button" ref={this._editRef} onPointerDown={this.onEdit}><FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div> */}
-                            <div title="Delete link" className="button" ref={this._editRef} onPointerDown={this.deleteLink}>
+                            <div title="Delete link" className="button" onPointerDown={this.deleteLink}>
                                 <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
-                            <div title="Follow link" className="button" onClick={e => this.followDefault()} onContextMenu={this.onContextMenu}>
+                            <div title="Follow link" className="button" onPointerDown={this.followDefault} onContextMenu={this.onContextMenu}>
                                 <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" />
                             </div>
                         </div>
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index d67a01d8b..95c2988da 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -77,6 +77,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         }
     }
 
+    @action
     completeLink = (e: React.PointerEvent): void => {
         setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e, doubleTap) => {
             if (doubleTap) {
@@ -88,12 +89,17 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                 } else {
                     DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
                         DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+                    runInAction(() => { MainView.linkCreated = true; });
+                    runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2500); });
+                    MainView.popupX = e.screenX;
+                    MainView.popupY = e.screenY;
                 }
             }
         }));
     }
 
-    finishLinkClick = () => {
+    @action
+    finishLinkClick = (e: React.MouseEvent) => {
         if (DocumentLinksButton.StartLink === this.props.View) {
             DocumentLinksButton.StartLink = undefined;
             // action((e: React.PointerEvent<HTMLDivElement>) => {
@@ -102,6 +108,10 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         } else {
             DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
                 DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+            runInAction(() => { MainView.linkCreated = true; });
+            runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2500); });
+            MainView.popupX = e.screenX;
+            MainView.popupY = e.screenY;
         }
     }
 
@@ -131,7 +141,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                 </div>
                 {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"}
                     style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }}
-                    onPointerDown={this.completeLink} onClick={this.finishLinkClick} /> : (null)}
+                    onPointerDown={this.completeLink} onClick={e => this.finishLinkClick(e)} /> : (null)}
                 {DocumentLinksButton.StartLink === this.props.View ? <div className={"documentLinksButton-startLink"}
                     style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }} /> : (null)}
             </div>;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index b4bb556c7..7aa1d528d 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -832,14 +832,14 @@ export namespace Doc {
         })(doc);
     }
     export function BrushDoc(doc: Doc) {
-        console.log("brushed");
+
         if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc;
         brushManager.BrushedDoc.set(doc, true);
         brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
         return doc;
     }
     export function UnBrushDoc(doc: Doc) {
-        console.log("unbrushed");
+
         if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc;
         brushManager.BrushedDoc.delete(doc);
         brushManager.BrushedDoc.delete(Doc.GetProto(doc));
-- 
cgit v1.2.3-70-g09d2


From c146d9a6165dcfa9ebd98868dfb741a26ede5a9f Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Fri, 3 Jul 2020 12:09:48 -0500
Subject: finished link created popups

---
 src/client/views/MainView.tsx                             | 15 +++++++++------
 .../collectionFreeForm/CollectionFreeFormView.tsx         |  4 ----
 src/client/views/nodes/DocumentLinksButton.tsx            |  6 +++---
 src/client/views/nodes/DocumentView.tsx                   | 12 ++++++++++++
 4 files changed, 24 insertions(+), 13 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index acbeb65cc..3d47d09c8 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -619,12 +619,15 @@ export class MainView extends React.Component {
 
             <Fade in={MainView.linkCreated}>
                 <div style={{
-                    border: "1px solid rgb(111, 144, 175)", top: MainView.popupX ? MainView.popupX : 600
-                    , position: "absolute",
-                    left: MainView.popupY ? MainView.popupY : 250
-                    , width: "auto", height: "auto", zIndex: 10000,
-                    fontSize: "13px", whiteSpace: "nowrap", backgroundColor: "rgb(170, 205, 238)",
-                    padding: "5px", //fontWeight: "bold"
+                    border: "1px solid rgb(100, 100, 100)",
+                    left: MainView.popupX ? MainView.popupX : 600,
+                    position: "absolute",
+                    top: MainView.popupY ? MainView.popupY : 250, width: "auto",
+                    height: "auto", zIndex: 10000, borderRadius: "13px",
+                    fontSize: "13px", whiteSpace: "nowrap",
+                    color: "rgb(100, 100, 100)", backgroundColor: "rgba(250, 250, 250, 0.85)",
+                    paddingTop: "6.5px", paddingBottom: "6.5px", fontWeight: "bold",
+                    paddingLeft: "9px", paddingRight: "9px"
                 }}>Link Created</div>
             </Fade>
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 25f724449..7f6d1d506 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -249,10 +249,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         } else {
             const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
             this.props.addDocument(source);
-            MainView.popupX = xp;
-            MainView.popupY = yp;
-            runInAction(() => { MainView.linkCreated = true; });
-            runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2500); });
             linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed
             e.stopPropagation();
             return true;
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 7faae68bf..6a6624aa0 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -109,9 +109,9 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
                 DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
             runInAction(() => { MainView.linkCreated = true; });
-            runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2500); });
-            MainView.popupX = e.screenX;
-            MainView.popupY = e.screenY;
+            runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2000); });
+            MainView.popupX = e.screenX - 25;
+            MainView.popupY = e.screenY - 140;
         }
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 66af29286..3a9e4fcf0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -42,6 +42,7 @@ import { RadialMenu } from './RadialMenu';
 import React = require("react");
 import { DocumentLinksButton } from './DocumentLinksButton';
 import { MobileInterface } from '../../../mobile/MobileInterface';
+import { MainView } from '../MainView';
 
 library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
     fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
@@ -641,12 +642,23 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             e.stopPropagation();
             de.complete.annoDragData.linkedToDoc = true;
 
+            runInAction(() => MainView.popupX = de.x);
+            runInAction(() => MainView.popupY = de.y);
+            runInAction(() => { MainView.linkCreated = true; });
+            runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2500); });
+
             DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link");
         }
         if (de.complete.linkDragData) {
             e.stopPropagation();
             // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
             // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
+            runInAction(() => MainView.popupX = de.x);
+            runInAction(() => MainView.popupY = de.y);
+
+            runInAction(() => { MainView.linkCreated = true; });
+            runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2000); });
+
             de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
                 (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument },
                     { doc: this.props.Document }, `link`)); // TODODO this is where in text links get passed
-- 
cgit v1.2.3-70-g09d2


From d95136bc94270132711ab60f329c20419f52a91d Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 3 Jul 2020 17:26:11 -0400
Subject: remove imports

---
 src/client/views/collections/CollectionView.tsx    | 11 ++++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 35 ++++++++++------------
 2 files changed, 21 insertions(+), 25 deletions(-)

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

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 10ebe571b..26abd2529 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -12,26 +12,29 @@ import { AclAddonly, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field, Opt
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
+import { RichTextField } from '../../../fields/RichTextField';
 import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils, returnEmptyFilter } from '../../../Utils';
+import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
 import { InteractionUtils } from '../../util/InteractionUtils';
+import { UndoManager } from '../../util/UndoManager';
 import { ContextMenu } from "../ContextMenu";
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import { ScriptBox } from '../ScriptBox';
 import { Touchable } from '../Touchable';
-import { CollectionCarouselView } from './CollectionCarouselView';
 import { CollectionCarousel3DView } from './CollectionCarousel3DView';
+import { CollectionCarouselView } from './CollectionCarouselView';
 import { CollectionDockingView } from "./CollectionDockingView";
 import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { CollectionGridView } from './collectionGrid/CollectionGridView';
 import { CollectionLinearView } from './CollectionLinearView';
 import CollectionMapView from './CollectionMapView';
 import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
@@ -43,12 +46,8 @@ import { CollectionStaffView } from './CollectionStaffView';
 import { SubCollectionViewProps } from './CollectionSubView';
 import { CollectionTimeView } from './CollectionTimeView';
 import { CollectionTreeView } from "./CollectionTreeView";
-import { CollectionGridView } from './collectionGrid/CollectionGridView';
 import './CollectionView.scss';
 import { CollectionViewBaseChrome } from './CollectionViewChromes';
-import { UndoManager } from '../../util/UndoManager';
-import { RichTextField } from '../../../fields/RichTextField';
-import { TextField } from '../../util/ProsemirrorCopy/prompt';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7f6d1d506..b81e400b3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,54 +1,51 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
-import { faEye, faEyeSlash } from "@fortawesome/free-regular-svg-icons";
+import { faEye } 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, trace } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
-import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../fields/Doc";
-import { documentSchema, collectionSchema } from "../../../../fields/documentSchemas";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../fields/Doc";
+import { collectionSchema, documentSchema } from "../../../../fields/documentSchemas";
 import { Id } from "../../../../fields/FieldSymbols";
-import { InkData, InkField, InkTool, PointData } from "../../../../fields/InkField";
+import { InkData, InkField, InkTool } from "../../../../fields/InkField";
 import { List } from "../../../../fields/List";
 import { RichTextField } from "../../../../fields/RichTextField";
-import { createSchema, listSpec, makeInterface } from "../../../../fields/Schema";
-import { ScriptField, ComputedField } from "../../../../fields/ScriptField";
+import { createSchema, makeInterface } from "../../../../fields/Schema";
+import { ScriptField } from "../../../../fields/ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
 import { TraceMobx } from "../../../../fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { aggregateBounds, intersectRect, returnOne, Utils, returnZero, returnFalse, numberRange } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnFalse, returnOne, returnZero, Utils } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
 import { Docs, DocUtils } from "../../../documents/Documents";
+import { DocumentType } from "../../../documents/DocumentTypes";
 import { DocumentManager } from "../../../util/DocumentManager";
 import { DragManager, dropActionType } from "../../../util/DragManager";
 import { HistoryUtil } from "../../../util/History";
 import { InteractionUtils } from "../../../util/InteractionUtils";
 import { SelectionManager } from "../../../util/SelectionManager";
+import { SnappingManager } from "../../../util/SnappingManager";
 import { Transform } from "../../../util/Transform";
 import { undoBatch, UndoManager } from "../../../util/UndoManager";
 import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
+import { Timeline } from "../../animationtimeline/Timeline";
 import { ContextMenu } from "../../ContextMenu";
-import { ContextMenuProps } from "../../ContextMenuItem";
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth } from "../../InkingStroke";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
-import { DocumentViewProps, DocumentView } from "../../nodes/DocumentView";
+import { DocumentLinksButton } from "../../nodes/DocumentLinksButton";
+import { DocumentViewProps } from "../../nodes/DocumentView";
 import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
-import PDFMenu from "../../pdf/PDFMenu";
 import { CollectionDockingView } from "../CollectionDockingView";
 import { CollectionSubView } from "../CollectionSubView";
-import { computePivotLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult, computerStarburstLayout, computerPassLayout } from "./CollectionFreeFormLayoutEngines";
+import { CollectionViewType } from "../CollectionView";
+import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
 import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
 import "./CollectionFreeFormView.scss";
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
-import { CollectionViewType } from "../CollectionView";
-import { Timeline } from "../../animationtimeline/Timeline";
-import { SnappingManager } from "../../../util/SnappingManager";
-import { InkingStroke, ActiveArrowStart, ActiveArrowEnd, ActiveInkColor, ActiveFillColor, ActiveInkWidth, ActiveInkBezierApprox, ActiveDash } from "../../InkingStroke";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { DocumentLinksButton } from "../../nodes/DocumentLinksButton";
-import { MainView } from "../../MainView";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
-- 
cgit v1.2.3-70-g09d2

