From 2c565fd81daca02cabb9598c699cedb7611c3841 Mon Sep 17 00:00:00 2001
From: ljungster <parkerljung@gmail.com>
Date: Sat, 12 Mar 2022 07:44:01 -0500
Subject: attempting to add note-taking

I think this has something to do with the view not being rendered in novice mode. Assuming this is an issue in CollectionMenu.tsx.

Essentially what I did was add a note-taking view wherever I found a stacking view (via global search)
---
 src/client/views/nodes/trails/PresBox.tsx | 1 +
 1 file changed, 1 insertion(+)

(limited to 'src/client/views/nodes/trails/PresBox.tsx')

diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 14d6e8be6..b08955b48 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -113,6 +113,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
     @computed get activeItem() { return Cast(this.childDocs[NumCast(this.rootDoc._itemIndex)], Doc, null); }
     @computed get targetDoc() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); }
     @computed get scrollable(): boolean {
+      //TODO: likely do NOT have to update this for note-taking view, but still worth putting here
         if (this.targetDoc.type === DocumentType.PDF || this.targetDoc.type === DocumentType.WEB || this.targetDoc.type === DocumentType.RTF || this.targetDoc._viewType === CollectionViewType.Stacking) return true;
         else return false;
     }
-- 
cgit v1.2.3-70-g09d2


From 724c1c1228aa601fdbbed93dde68a5a9fa9c5ae9 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 20 Jul 2022 09:12:11 -0400
Subject: added a 'guest' login mode.  added ability for mainView to be any
 doc, not just a docking collection.

---
 src/client/DocServer.ts                            |   4 +-
 src/client/util/CurrentUserUtils.ts                |  18 +-
 src/client/util/GroupManager.tsx                   |   9 +-
 src/client/util/SettingsManager.tsx                |   3 +-
 src/client/util/SharingManager.tsx                 |   3 +-
 src/client/util/TrackMovements.ts                  | 141 ++++++++-------
 src/client/views/ContextMenu.scss                  |  22 +--
 src/client/views/ContextMenuItem.tsx               |  84 +++++----
 src/client/views/DashboardView.tsx                 |   2 +-
 src/client/views/GlobalKeyHandler.ts               |   4 +-
 src/client/views/Main.tsx                          |  51 +++---
 src/client/views/MainView.tsx                      |  53 ++++--
 src/client/views/PropertiesDocContextSelector.tsx  |   2 +-
 .../views/collections/CollectionDockingView.tsx    |  70 ++++----
 src/client/views/collections/CollectionView.tsx    |   2 +-
 src/client/views/collections/TabDocView.tsx        |  15 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |  48 ++---
 src/client/views/nodes/PDFBox.tsx                  |   5 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   2 +-
 src/client/views/nodes/trails/PresBox.tsx          |   2 +-
 src/client/views/topbar/TopBar.tsx                 |   2 +-
 src/fields/util.ts                                 |  47 +++--
 src/mobile/MobileInkOverlay.tsx                    | 119 ++++++-------
 src/mobile/MobileMain.tsx                          |   5 +-
 src/server/ApiManagers/UserManager.ts              |  77 ++++----
 src/server/RouteManager.ts                         |  58 +++---
 src/server/authentication/AuthenticationManager.ts |  31 ++--
 src/server/authentication/DashUserModel.ts         | 106 ++++++-----
 src/server/index.ts                                | 108 +++++-------
 src/server/websocket.ts                            | 194 +++++++++++----------
 views/login.pug                                    |   4 +-
 31 files changed, 682 insertions(+), 609 deletions(-)

(limited to 'src/client/views/nodes/trails/PresBox.tsx')

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 1b0ba6bc3..5a34fcf11 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -77,7 +77,7 @@ export namespace DocServer {
     }
 
     export function getFieldWriteMode(field: string) {
-        return fieldWriteModes[field] || WriteMode.Default;
+        return Doc.CurrentUserEmail === 'guest' ? WriteMode.LiveReadonly : fieldWriteModes[field] || WriteMode.Default;
     }
 
     export function registerDocWithCachedUpdate(doc: Doc, field: string, oldValue: any) {
@@ -178,7 +178,7 @@ export namespace DocServer {
                 _isReadOnly = true;
                 _CreateField = field => (_cache[field[Id]] = field);
                 _UpdateField = emptyFunction;
-                _RespondToUpdate = emptyFunction; // bcz: option: don't clear RespondToUpdate to continue to receive updates as others change the DB
+                // _RespondToUpdate = emptyFunction; // bcz: option: don't clear RespondToUpdate to continue to receive updates as others change the DB
             }
         }
 
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 6c80cf0f4..bbf2ff3f9 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,6 +1,7 @@
 import { reaction } from "mobx";
 import * as rp from 'request-promise';
 import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
+import { Id } from "../../fields/FieldSymbols";
 import { List } from "../../fields/List";
 import { PrefetchProxy } from "../../fields/Proxy";
 import { RichTextField } from "../../fields/RichTextField";
@@ -14,6 +15,7 @@ import { DocServer } from "../DocServer";
 import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents";
 import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
 import { TreeViewType } from "../views/collections/CollectionTreeView";
+import { DashboardView } from "../views/DashboardView";
 import { Colors } from "../views/global/globalEnums";
 import { MainView } from "../views/MainView";
 import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox";
@@ -762,8 +764,6 @@ export class CurrentUserUtils {
 
             doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates", system: true });
         }
-        // this is equivalent to using PrefetchProxies to make sure all the childClickFuncs have been retrieved.
-        PromiseValue(Cast(doc["clickFuncs-child"], Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
 
         if (doc.clickFuncs === undefined) {
             const onClick = Docs.Create.ScriptingDocument(undefined, {
@@ -789,7 +789,6 @@ export class CurrentUserUtils {
             }, "onCheckedClick");
             doc.clickFuncs = Docs.Create.TreeDocument([onClick, onChildClick, onDoubleClick, onCheckedClick], { title: "onClick funcs", system: true });
         }
-        PromiseValue(Cast(doc.clickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
 
         return doc.clickFuncs as Doc;
     }
@@ -886,13 +885,20 @@ export class CurrentUserUtils {
     public static async loadUserDocument(id: string) {
         await rp.get(Utils.prepend("/getUserDocumentIds")).then(ids => {
             const { userDocumentId, sharingDocumentId, linkDatabaseId } = JSON.parse(ids);
-            if (userDocumentId !== "guest") {
+            if (userDocumentId) {
                 return DocServer.GetRefField(userDocumentId).then(async field => {
                     Docs.newAccount = !(field instanceof Doc);
                     await Docs.Prototypes.initialize();
                     const userDoc = Docs.newAccount ? new Doc(userDocumentId, true) : field as Doc;
-                    Docs.newAccount &&(userDoc.activePage = "home");
-                    return this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId);
+                    this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId);
+                    if (Docs.newAccount) {
+                        if (Doc.CurrentUserEmail === "guest") {
+                            DashboardView.createNewDashboard(undefined, "guest dashboard");
+                        } else {
+                            userDoc.activePage = "home";
+                        }
+                    }
+                    return userDoc;
                 });
             } else {
                 throw new Error("There should be a user id! Why does Dash think there isn't one?");
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 59334f6a2..c8b784390 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -14,6 +14,7 @@ import { GroupMemberView } from './GroupMemberView';
 import { SharingManager, User } from './SharingManager';
 import { listSpec } from '../../fields/Schema';
 import { DateField } from '../../fields/DateField';
+import { Id } from '../../fields/FieldSymbols';
 
 /**
  * Interface for options for the react-select component
@@ -49,9 +50,11 @@ export class GroupManager extends React.Component<{}> {
      * Fetches the list of users stored on the database.
      */
     populateUsers = async () => {
-        const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
-        const raw = JSON.parse(userList) as User[];
-        raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email)));
+        if (Doc.UserDoc()[Id] !== '__guest__') {
+            const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
+            const raw = JSON.parse(userList) as User[];
+            raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email)));
+        }
     };
 
     /**
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 12d1793af..cf143c5e8 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -4,6 +4,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ColorState, SketchPicker } from 'react-color';
 import { Doc } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
 import { BoolCast, Cast, StrCast } from '../../fields/Types';
 import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
@@ -81,7 +82,7 @@ export class SettingsManager extends React.Component<{}> {
         if (this.playgroundMode) {
             DocServer.Control.makeReadOnly();
             addStyleSheetRule(SettingsManager._settingsStyle, 'topbar-inner-container', { background: 'red !important' });
-        } else DocServer.Control.makeEditable();
+        } else Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable();
     });
 
     @undoBatch
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 793027ea1..895bd3374 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -23,6 +23,7 @@ import { GroupMemberView } from './GroupMemberView';
 import { SelectionManager } from './SelectionManager';
 import './SharingManager.scss';
 import { LinkManager } from './LinkManager';
+import { Id } from '../../fields/FieldSymbols';
 
 export interface User {
     email: string;
@@ -136,7 +137,7 @@ export class SharingManager extends React.Component<{}> {
      * Populates the list of validated users (this.users) by adding registered users which have a sharingDocument.
      */
     populateUsers = async () => {
-        if (!this.populating) {
+        if (!this.populating && Doc.UserDoc()[Id] !== '__guest__') {
             this.populating = true;
             const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
             const raw = JSON.parse(userList) as User[];
diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts
index d512e4802..4a2ccd706 100644
--- a/src/client/util/TrackMovements.ts
+++ b/src/client/util/TrackMovements.ts
@@ -1,27 +1,26 @@
-import { IReactionDisposer, observable, observe, reaction } from "mobx";
-import { NumCast } from "../../fields/Types";
-import { Doc, DocListCast } from "../../fields/Doc";
-import { CollectionDockingView } from "../views/collections/CollectionDockingView";
-import { Id } from "../../fields/FieldSymbols";
+import { IReactionDisposer, observable, observe, reaction } from 'mobx';
+import { NumCast } from '../../fields/Types';
+import { Doc, DocListCast } from '../../fields/Doc';
+import { CollectionDockingView } from '../views/collections/CollectionDockingView';
+import { Id } from '../../fields/FieldSymbols';
 
 export type Movement = {
-    time: number,
-    panX: number,
-    panY: number,
-    scale: number,
-    docId: string,
-}
+    time: number;
+    panX: number;
+    panY: number;
+    scale: number;
+    docId: string;
+};
 
 export type Presentation = {
-    movements: Movement[] | null,
-    totalTime: number,
-    meta: Object | Object[],
-}
+    movements: Movement[] | null;
+    totalTime: number;
+    meta: Object | Object[];
+};
 
 export class TrackMovements {
-
     private static get NULL_PRESENTATION(): Presentation {
-        return { movements: null, meta: {}, totalTime: -1, }
+        return { movements: null, meta: {}, totalTime: -1 };
     }
 
     // instance variables
@@ -32,16 +31,17 @@ export class TrackMovements {
     private recordingFFViews: Map<string, IReactionDisposer> | null;
     private tabChangeDisposeFunc: IReactionDisposer | null;
 
-
     // create static instance and getter for global use
     @observable static _instance: TrackMovements;
-    static get Instance(): TrackMovements { return TrackMovements._instance }
+    static get Instance(): TrackMovements {
+        return TrackMovements._instance;
+    }
     constructor() {
         // init the global instance
         TrackMovements._instance = this;
 
         // init the instance variables
-        this.currentPresentation = TrackMovements.NULL_PRESENTATION
+        this.currentPresentation = TrackMovements.NULL_PRESENTATION;
         this.tracking = false;
         this.absoluteStart = -1;
 
@@ -52,28 +52,37 @@ export class TrackMovements {
 
     // little helper :)
     private get nullPresentation(): boolean {
-        return this.currentPresentation.movements === null
+        return this.currentPresentation.movements === null;
     }
 
     private addRecordingFFView(doc: Doc, key: string = doc[Id]): void {
         // console.info('adding dispose func : docId', key, 'doc', doc);
 
-        if (this.recordingFFViews === null) { console.warn('addFFView on null RecordingApi'); return; }
-        if (this.recordingFFViews.has(key)) { console.warn('addFFView : key already in map'); return; }
+        if (this.recordingFFViews === null) {
+            console.warn('addFFView on null RecordingApi');
+            return;
+        }
+        if (this.recordingFFViews.has(key)) {
+            console.warn('addFFView : key already in map');
+            return;
+        }
 
         const disposeFunc = reaction(
-            () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0)}),
-            (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovement(res.x, res.y, key, res.scale),
+            () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0) }),
+            res => res.x !== -1 && res.y !== -1 && this.tracking && this.trackMovement(res.x, res.y, key, res.scale)
         );
         this.recordingFFViews?.set(key, disposeFunc);
     }
 
     private removeRecordingFFView = (key: string) => {
         // console.info('removing dispose func : docId', key);
-        if (this.recordingFFViews === null) { console.warn('removeFFView on null RecordingApi'); return; }
+        if (this.recordingFFViews === null) {
+            console.warn('removeFFView on null RecordingApi');
+            return;
+        }
         this.recordingFFViews.get(key)?.();
         this.recordingFFViews.delete(key);
-    }
+    };
 
     // in the case where only one tab was changed (updates not across dashboards), set only one to true
     private updateRecordingFFViewsFromTabs = (tabbedDocs: Doc[], onlyOne = false) => {
@@ -86,7 +95,6 @@ export class TrackMovements {
             if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc[Id]);
         }
 
-
         // new tab was added - need to add it
         if (tabbedFFViews.size > this.recordingFFViews.size) {
             for (const DashDoc of tabbedDocs) {
@@ -103,13 +111,13 @@ export class TrackMovements {
         // tab was removed - need to remove it from recordingFFViews
         else if (tabbedFFViews.size < this.recordingFFViews.size) {
             for (const [key] of this.recordingFFViews) {
-                if (!tabbedFFViews.has(key)) { 
+                if (!tabbedFFViews.has(key)) {
                     this.removeRecordingFFView(key);
                     if (onlyOne) return;
-                } 
+                }
             }
         }
-    }
+    };
 
     private initTabTracker = () => {
         if (this.recordingFFViews === null) {
@@ -117,18 +125,19 @@ export class TrackMovements {
         }
 
         // init the dispose funcs on the page
-        const docList = DocListCast(CollectionDockingView.Instance.props.Document.data);
+        const docList = DocListCast(CollectionDockingView.Instance?.props.Document.data);
         this.updateRecordingFFViewsFromTabs(docList);
 
         // create a reaction to monitor changes in tabs
-        this.tabChangeDisposeFunc =
-            reaction(() => CollectionDockingView.Instance.props.Document.data,
-                (change) => {
-                    // TODO: consider changing between dashboards
-                    // console.info('change in tabs', change);
-                    this.updateRecordingFFViewsFromTabs(DocListCast(change), true);
-                });
-    }
+        this.tabChangeDisposeFunc = reaction(
+            () => CollectionDockingView.Instance?.props.Document.data,
+            change => {
+                // TODO: consider changing between dashboards
+                // console.info('change in tabs', change);
+                this.updateRecordingFFViewsFromTabs(DocListCast(change), true);
+            }
+        );
+    };
 
     start = (meta?: Object) => {
         this.initTabTracker();
@@ -142,12 +151,12 @@ export class TrackMovements {
         this.absoluteStart = startDate.getTime();
 
         // (2) assign meta content if it exists
-        this.currentPresentation.meta = meta || {}
+        this.currentPresentation.meta = meta || {};
         // (3) assign start date to currentPresenation
-        this.currentPresentation.movements = []
+        this.currentPresentation.movements = [];
         // (4) set tracking true to allow trackMovements
-        this.tracking = true
-    }
+        this.tracking = true;
+    };
 
     /* stops the video and returns the presentatation; if no presentation, returns undefined */
     yieldPresentation(clearData: boolean = true): Presentation | null {
@@ -157,7 +166,7 @@ export class TrackMovements {
         // set the previus recording view to the play view
         // this.playFFView = this.recordingFFView;
 
-        // ensure we add the endTime now that they are done recording    
+        // ensure we add the endTime now that they are done recording
         const cpy = { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart };
 
         // reset the current presentation
@@ -169,10 +178,10 @@ export class TrackMovements {
 
     finish = (): void => {
         // make is tracking false
-        this.tracking = false
+        this.tracking = false;
         // reset the RecordingApi instance
         this.clear();
-    }
+    };
 
     private clear = (): void => {
         // clear the disposeFunc if we are done (not tracking)
@@ -187,44 +196,46 @@ export class TrackMovements {
         }
 
         // clear presenation data
-        this.currentPresentation = TrackMovements.NULL_PRESENTATION
+        this.currentPresentation = TrackMovements.NULL_PRESENTATION;
         // clear absoluteStart
-        this.absoluteStart = -1
-    }
+        this.absoluteStart = -1;
+    };
 
     removeAllRecordingFFViews = () => {
-        if (this.recordingFFViews === null) { console.warn('removeAllFFViews on null RecordingApi'); return; }
+        if (this.recordingFFViews === null) {
+            console.warn('removeAllFFViews on null RecordingApi');
+            return;
+        }
 
         for (const [id, disposeFunc] of this.recordingFFViews) {
             // console.info('calling dispose func : docId', id);
             disposeFunc();
             this.recordingFFViews.delete(id);
         }
-    }
+    };
 
     private trackMovement = (panX: number, panY: number, docId: string, scale: number = 0) => {
         // ensure we are recording to track
         if (!this.tracking) {
-            console.error('[recordingApi.ts] trackMovements(): tracking is false')
+            console.error('[recordingApi.ts] trackMovements(): tracking is false');
             return;
         }
         // check to see if the presetation is init - if not, we are between segments
         // TODO: make this more clear - tracking should be "live tracking", not always true when the recording api being used (between start and yieldPres)
-        // bacuse tracking should be false inbetween segments high key 
+        // bacuse tracking should be false inbetween segments high key
         if (this.nullPresentation) {
-            console.warn('[recordingApi.ts] trackMovements(): trying to store movemetns between segments')
+            console.warn('[recordingApi.ts] trackMovements(): trying to store movemetns between segments');
             return;
         }
 
         // get the time
-        const time = new Date().getTime() - this.absoluteStart
+        const time = new Date().getTime() - this.absoluteStart;
         // make new movement object
-        const movement: Movement = { time, panX, panY, scale, docId }
+        const movement: Movement = { time, panX, panY, scale, docId };
 
         // add that movement to the current presentation data's movement array
-        this.currentPresentation.movements && this.currentPresentation.movements.push(movement)
-    }
-
+        this.currentPresentation.movements && this.currentPresentation.movements.push(movement);
+    };
 
     // method that concatenates an array of presentatations into one
     public concatPresentations = (presentations: Presentation[]): Presentation => {
@@ -233,13 +244,15 @@ export class TrackMovements {
         let sumTime = 0;
         let combinedMetas: any[] = [];
 
-        presentations.forEach((presentation) => {
+        presentations.forEach(presentation => {
             const { movements, totalTime, meta } = presentation;
 
             // update movements if they had one
             if (movements) {
                 // add the summed time to the movements
-                const addedTimeMovements = movements.map(move => { return { ...move, time: move.time + sumTime } });
+                const addedTimeMovements = movements.map(move => {
+                    return { ...move, time: move.time + sumTime };
+                });
                 // concat the movements already in the combined presentation with these new ones
                 combinedMovements.push(...addedTimeMovements);
             }
@@ -252,6 +265,6 @@ export class TrackMovements {
         });
 
         // return the combined presentation with the updated total summed time
-		return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas };
-    }
+        return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas };
+    };
 }
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index ea24dbf6d..1e6a377de 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -1,10 +1,10 @@
-@import "global/globalCssVariables";
+@import 'global/globalCssVariables';
 
 .contextMenu-cont {
     position: absolute;
     display: flex;
     z-index: 100000;
-    box-shadow: 0px 3px 4px rgba(0,0,0,30%);
+    box-shadow: 0px 3px 4px rgba(0, 0, 0, 30%);
     flex-direction: column;
     background: whitesmoke;
     border-radius: 3px;
@@ -28,9 +28,9 @@
     position: absolute;
     display: flex;
     z-index: 1000;
-    box-shadow: #AAAAAA .2vw .2vw .4vw;
+    box-shadow: #aaaaaa 0.2vw 0.2vw 0.4vw;
     flex-direction: column;
-    border: 1px solid #BBBBBBBB;
+    border: 1px solid #bbbbbbbb;
     border-radius: 15px;
     padding-top: 10px;
     padding-bottom: 10px;
@@ -49,7 +49,7 @@
     -moz-user-select: none;
     -ms-user-select: none;
     user-select: none;
-    transition: all .1s;
+    transition: all 0.1s;
     border-style: none;
     // padding: 10px 0px 10px 0px;
     white-space: nowrap;
@@ -58,7 +58,7 @@
     text-transform: uppercase;
     padding-right: 30px;
 
-    .icon-background {
+    .contextMenu-item-icon-background {
         pointer-events: all;
         background-color: transparent;
         width: 35px;
@@ -78,7 +78,7 @@
     -moz-user-select: none;
     -ms-user-select: none;
     user-select: none;
-    transition: all .1s;
+    transition: all 0.1s;
     border-style: none;
     // padding: 10px 0px 10px 0px;
     white-space: nowrap;
@@ -89,7 +89,7 @@
 }
 
 .contextMenu-item:hover {
-    border-width: .11px;
+    border-width: 0.11px;
     border-style: none;
     border-color: $medium-gray; //  rgb(187, 186, 186);
     border-bottom-style: solid;
@@ -116,8 +116,8 @@
     -moz-user-select: none;
     -ms-user-select: none;
     user-select: none;
-    transition: all .1s;
-    border-width: .11px;
+    transition: all 0.1s;
+    border-width: 0.11px;
     border-style: none;
     border-color: $medium-gray; //  rgb(187, 186, 186);
     // padding: 10px 0px 10px 0px;
@@ -152,4 +152,4 @@
     padding-left: 10px;
     border: solid black 1px;
     border-radius: 5px;
-}
\ No newline at end of file
+}
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 30073e21f..dc9c2eb6c 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -1,9 +1,9 @@
-import React = require("react");
-import { observable, action, runInAction } from "mobx";
-import { observer } from "mobx-react";
+import React = require('react');
+import { observable, action, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { UndoManager } from "../util/UndoManager";
+import { UndoManager } from '../util/UndoManager';
 
 export interface OriginalMenuProps {
     description: string;
@@ -37,7 +37,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
     }
 
     handleEvent = async (e: React.MouseEvent<HTMLDivElement>) => {
-        if ("event" in this.props) {
+        if ('event' in this.props) {
             this.props.closeMenu?.();
             let batch: UndoManager.Batch | undefined;
             if (this.props.undoable !== false) {
@@ -46,7 +46,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
             await this.props.event({ x: e.clientX, y: e.clientY });
             batch?.end();
         }
-    }
+    };
 
     currentTimeout?: any;
     static readonly timeout = 300;
@@ -62,8 +62,11 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
         }
         this._overPosY = e.clientY;
         this._overPosX = e.clientX;
-        this.currentTimeout = setTimeout(action(() => this.overItem = true), ContextMenuItem.timeout);
-    }
+        this.currentTimeout = setTimeout(
+            action(() => (this.overItem = true)),
+            ContextMenuItem.timeout
+        );
+    };
 
     onPointerLeave = () => {
         if (this.currentTimeout) {
@@ -73,57 +76,68 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
         if (!this.overItem) {
             return;
         }
-        this.currentTimeout = setTimeout(action(() => this.overItem = false), ContextMenuItem.timeout);
-    }
+        this.currentTimeout = setTimeout(
+            action(() => (this.overItem = false)),
+            ContextMenuItem.timeout
+        );
+    };
 
     render() {
-        if ("event" in this.props) {
+        if ('event' in this.props) {
             return (
-                <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} onPointerDown={this.handleEvent}>
+                <div className={'contextMenu-item' + (this.props.selected ? ' contextMenu-itemSelected' : '')} onPointerDown={this.handleEvent}>
                     {this.props.icon ? (
-                        <span className="icon-background">
+                        <span className="contextMenu-item-icon-background">
                             <FontAwesomeIcon icon={this.props.icon} size="sm" />
                         </span>
                     ) : null}
-                    <div className="contextMenu-description">
-                        {this.props.description.replace(":", "")}
-                    </div>
+                    <div className="contextMenu-description">{this.props.description.replace(':', '')}</div>
                 </div>
             );
-        } else if ("subitems" in this.props) {
-            const where = !this.overItem ? "" : this._overPosY < window.innerHeight / 3 ? "flex-start" : this._overPosY > window.innerHeight * 2 / 3 ? "flex-end" : "center";
-            const marginTop = !this.overItem ? "" : this._overPosY < window.innerHeight / 3 ? "20px" : this._overPosY > window.innerHeight * 2 / 3 ? "-20px" : "";
+        } else if ('subitems' in this.props) {
+            const where = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? 'flex-start' : this._overPosY > (window.innerHeight * 2) / 3 ? 'flex-end' : 'center';
+            const marginTop = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? '20px' : this._overPosY > (window.innerHeight * 2) / 3 ? '-20px' : '';
 
             // here
-            const submenu = !this.overItem ? (null) :
-                <div className="contextMenu-subMenu-cont"
+            const submenu = !this.overItem ? null : (
+                <div
+                    className="contextMenu-subMenu-cont"
                     style={{
-                        marginLeft: window.innerHeight - this._overPosX - 50 > 0 ? "90%" : "20%", marginTop
+                        marginLeft: window.innerHeight - this._overPosX - 50 > 0 ? '90%' : '20%',
+                        marginTop,
                     }}>
-                    {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
-                </div>;
+                    {this._items.map(prop => (
+                        <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />
+                    ))}
+                </div>
+            );
             if (!(this.props as SubmenuProps).noexpand) {
-                return <div className="contextMenu-inlineMenu">
-                    {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
-                </div>;
+                return (
+                    <div className="contextMenu-inlineMenu">
+                        {this._items.map(prop => (
+                            <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />
+                        ))}
+                    </div>
+                );
             }
             return (
-                <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")}
-                    style={{ alignItems: where, borderTop: this.props.addDivider ? "solid 1px" : undefined }}
-                    onMouseLeave={this.onPointerLeave} onMouseEnter={this.onPointerEnter}>
+                <div
+                    className={'contextMenu-item' + (this.props.selected ? ' contextMenu-itemSelected' : '')}
+                    style={{ alignItems: where, borderTop: this.props.addDivider ? 'solid 1px' : undefined }}
+                    onMouseLeave={this.onPointerLeave}
+                    onMouseEnter={this.onPointerEnter}>
                     {this.props.icon ? (
-                        <span className="icon-background" onMouseEnter={this.onPointerLeave} style={{ alignItems: "center", alignSelf: "center" }}>
+                        <span className="contextMenu-item-icon-background" onMouseEnter={this.onPointerLeave} style={{ alignItems: 'center', alignSelf: 'center' }}>
                             <FontAwesomeIcon icon={this.props.icon} size="sm" />
                         </span>
                     ) : null}
-                    <div className="contextMenu-description" onMouseEnter={this.onPointerEnter}
-                        style={{ alignItems: "center", alignSelf: "center" }} >
+                    <div className="contextMenu-description" onMouseEnter={this.onPointerEnter} style={{ alignItems: 'center', alignSelf: 'center' }}>
                         {this.props.description}
-                        <FontAwesomeIcon icon={"angle-right"} size="lg" style={{ position: "absolute", right: "10px" }} />
+                        <FontAwesomeIcon icon={'angle-right'} size="lg" style={{ position: 'absolute', right: '10px' }} />
                     </div>
                     {submenu}
                 </div>
             );
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 526a32f5a..9ea9128bd 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -238,7 +238,7 @@ export class DashboardView extends React.Component {
             } else if (doc.readOnly) {
                 DocServer.Control.makeReadOnly();
             } else {
-                DocServer.Control.makeEditable();
+                Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable();
             }
         }
 
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 73e0c9933..85f579975 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -116,8 +116,8 @@ export class KeyManager {
                 var doDeselect = true;
                 if (SnappingManager.GetIsDragging()) {
                     DragManager.AbortDrag();
-                } else if (CollectionDockingView.Instance.HasFullScreen) {
-                    CollectionDockingView.Instance.CloseFullScreen();
+                } else if (CollectionDockingView.Instance?.HasFullScreen) {
+                    CollectionDockingView.Instance?.CloseFullScreen();
                 } else if (CollectionStackedTimeline.SelectingRegion) {
                     CollectionStackedTimeline.SelectingRegion = undefined;
                     doDeselect = false;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index e998f1fb9..4cb1183d0 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -5,6 +5,8 @@
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { AssignAllExtensions } from '../../extensions/General/Extensions';
+import { Utils } from '../../Utils';
+import { DocServer } from '../DocServer';
 import { Docs } from '../documents/Documents';
 import { CurrentUserUtils } from '../util/CurrentUserUtils';
 import { LinkManager } from '../util/LinkManager'; // this must come before importing Docs and CurrentUserUtils
@@ -19,31 +21,28 @@ AssignAllExtensions();
     MainView.Live = window.location.search.includes('live');
     window.location.search.includes('safe') && CollectionView.SetSafeMode(true);
     const info = await CurrentUserUtils.loadCurrentUser();
-    if (info.id !== '__guest__') {
-        // a guest will not have an id registered
-        await CurrentUserUtils.loadUserDocument(info.id);
-    } else {
-        await Docs.Prototypes.initialize();
+    if (info.email === 'guest') DocServer.Control.makeReadOnly();
+    await CurrentUserUtils.loadUserDocument(info.id);
+    setTimeout(() => {
+        document.getElementById('root')!.addEventListener(
+            'wheel',
+            event => {
+                if (event.ctrlKey) {
+                    event.preventDefault();
+                }
+            },
+            true
+        );
+        const startload = (document as any).startLoad;
+        const loading = Date.now() - (startload ? Number(startload) : Date.now() - 3000);
+        console.log('Loading Time = ' + loading);
+        const d = new Date();
+        d.setTime(d.getTime() + 100 * 24 * 60 * 60 * 1000);
+        const expires = 'expires=' + d.toUTCString();
+        document.cookie = `loadtime=${loading};${expires};path=/`;
         new LinkManager();
-    }
-    document.getElementById('root')!.addEventListener(
-        'wheel',
-        event => {
-            if (event.ctrlKey) {
-                event.preventDefault();
-            }
-        },
-        true
-    );
-    const startload = (document as any).startLoad;
-    const loading = Date.now() - (startload ? Number(startload) : Date.now() - 3000);
-    console.log('Loading Time = ' + loading);
-    const d = new Date();
-    d.setTime(d.getTime() + 100 * 24 * 60 * 60 * 1000);
-    const expires = 'expires=' + d.toUTCString();
-    document.cookie = `loadtime=${loading};${expires};path=/`;
-    new LinkManager();
-    new TrackMovements();
-    new ReplayMovements();
-    ReactDOM.render(<MainView />, document.getElementById('root'));
+        new TrackMovements();
+        new ReplayMovements();
+        ReactDOM.render(<MainView />, document.getElementById('root'));
+    }, 0);
 })();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b61cd3409..7e032af5e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -3,7 +3,7 @@ import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons';
 import * as far from '@fortawesome/free-regular-svg-icons';
 import * as fa from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, configure, observable, reaction } from 'mobx';
+import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
@@ -77,12 +77,15 @@ export class MainView extends React.Component {
     @observable private _panelContent: string = 'none';
     @observable private _sidebarContent: any = Doc.MyLeftSidebarPanel;
     @observable private _leftMenuFlyoutWidth: number = 0;
+    @computed get _hideUI() {
+        return this.mainDoc && this.mainDoc._viewType !== CollectionViewType.Docking;
+    }
 
     @computed private get dashboardTabHeight() {
-        return 27;
+        return this._hideUI ? 0 : 27;
     } //  27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js
     @computed private get topOfDashUI() {
-        return Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', ''));
+        return this._hideUI ? 0 : Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', ''));
     }
     @computed private get topOfHeaderBarDoc() {
         return this.topOfDashUI;
@@ -105,7 +108,12 @@ export class MainView extends React.Component {
     @computed private get colorScheme() {
         return StrCast(Doc.ActiveDashboard?.colorScheme);
     }
+    @observable mainDoc: Opt<Doc>;
     @computed private get mainContainer() {
+        if (window.location.pathname.startsWith('/doc/')) {
+            DocServer.GetRefField(window.location.pathname.substring('/doc/'.length)).then(main => runInAction(() => (this.mainDoc = main as Doc)));
+            return this.mainDoc;
+        }
         return this.userDoc ? Doc.ActiveDashboard : Doc.GuestDashboard;
     }
     @computed private get headerBarDoc() {
@@ -116,10 +124,10 @@ export class MainView extends React.Component {
     }
 
     headerBarDocWidth = () => this.mainDocViewWidth();
-    headerBarDocHeight = () => SettingsManager.headerBarHeight ?? 0;
-    topMenuHeight = () => 35;
+    headerBarDocHeight = () => (this._hideUI ? 0 : SettingsManager.headerBarHeight ?? 0);
+    topMenuHeight = () => (this._hideUI ? 0 : 35);
     topMenuWidth = returnZero; // value is ignored ...
-    leftMenuWidth = () => Number(LEFT_MENU_WIDTH.replace('px', ''));
+    leftMenuWidth = () => (this._hideUI ? 0 : Number(LEFT_MENU_WIDTH.replace('px', '')));
     leftMenuHeight = () => this._dashUIHeight;
     leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth;
     leftMenuFlyoutHeight = () => this._dashUIHeight;
@@ -212,7 +220,8 @@ export class MainView extends React.Component {
             const pathname = window.location.pathname.substr(1).split('/');
             if (pathname.length > 1 && pathname[0] === 'doc') {
                 Doc.MainDocId = pathname[1];
-                !this.userDoc && DocServer.GetRefField(pathname[1]).then(action(field => field instanceof Doc && (Doc.GuestTarget = field)));
+                //!this.userDoc &&
+                DocServer.GetRefField(pathname[1]).then(action(field => field instanceof Doc && (Doc.GuestTarget = field)));
             }
         }
 
@@ -455,7 +464,11 @@ export class MainView extends React.Component {
         AudioBox.Enabled = true;
         const targets = document.elementsFromPoint(e.x, e.y);
         if (targets.length) {
-            const targClass = targets[0].className.toString();
+            let targClass = targets[0].className.toString();
+            for (let i = 0; i < targets.length - 1; i++) {
+                if (typeof targets[i].className === 'object') targClass = targets[i + 1].className.toString();
+                else break;
+            }
             !targClass.includes('contextMenu') && ContextMenu.Instance.closeMenu();
             !['timeline-menu-desc', 'timeline-menu-item', 'timeline-menu-input'].includes(targClass) && TimelineMenu.Instance.closeMenu();
         }
@@ -517,7 +530,7 @@ export class MainView extends React.Component {
         return () => (this._exploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
     }
     headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1);
-
+    mainScreenToLocalXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.topOfMainDocContent, 1);
     @computed get headerBarDocView() {
         return (
             <div className="mainView-headerBar" style={{ height: this.headerBarDocHeight() }}>
@@ -557,7 +570,7 @@ export class MainView extends React.Component {
     @computed get mainDocView() {
         return (
             <>
-                {this.headerBarDocView}
+                {this._hideUI ? null : this.headerBarDocView}
                 <DocumentView
                     key="main"
                     Document={this.mainContainer!}
@@ -566,11 +579,11 @@ export class MainView extends React.Component {
                     addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
                     docViewPath={returnEmptyDoclist}
-                    styleProvider={undefined}
+                    styleProvider={this._hideUI ? DefaultStyleProvider : undefined}
                     rootSelected={returnTrue}
                     isContentActive={returnTrue}
                     removeDocument={undefined}
-                    ScreenToLocalTransform={Transform.Identity}
+                    ScreenToLocalTransform={this._hideUI ? this.mainScreenToLocalXf : Transform.Identity}
                     PanelWidth={this.mainDocViewWidth}
                     PanelHeight={this.mainDocViewHeight}
                     focus={DocUtils.DefaultFocus}
@@ -582,7 +595,7 @@ export class MainView extends React.Component {
                     ContainingCollectionView={undefined}
                     ContainingCollectionDoc={undefined}
                     suppressSetHeight={true}
-                    renderDepth={-1}
+                    renderDepth={this._hideUI ? 0 : -1}
                 />
             </>
         );
@@ -750,7 +763,7 @@ export class MainView extends React.Component {
         const width = this.propertiesWidth() + leftMenuFlyoutWidth;
         return (
             <>
-                {this.leftMenuPanel}
+                {this._hideUI ? null : this.leftMenuPanel}
                 <div key="inner" className={`mainView-innerContent${this.colorScheme}`}>
                     {this.flyout}
                     <div className="mainView-libraryHandle" style={{ left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? 'none' : undefined }} onPointerDown={this.onFlyoutPointerDown}>
@@ -759,9 +772,11 @@ export class MainView extends React.Component {
                     <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)` }}>
                         {this.dockingContent}
 
-                        <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1 }}>
-                            <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? 'chevron-left' : 'chevron-right'} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" />
-                        </div>
+                        {this._hideUI ? null : (
+                            <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1 }}>
+                                <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? 'chevron-left' : 'chevron-right'} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" />
+                            </div>
+                        )}
                         <div className="properties-container" style={{ width: this.propertiesWidth() }}>
                             {this.propertiesWidth() < 10 ? null : <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={this.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
                         </div>
@@ -962,7 +977,7 @@ export class MainView extends React.Component {
                 <GoogleAuthenticationManager />
                 <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfHeaderBarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
                 <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
-                <TopBar />
+                {this._hideUI ? null : <TopBar />}
                 {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
                 {DocumentLinksButton.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocumentLinksButton.LinkEditorDocView = undefined))} docView={DocumentLinksButton.LinkEditorDocView} /> : null}
                 {LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : null}
@@ -973,7 +988,7 @@ export class MainView extends React.Component {
                         default:
                             return (
                                 <>
-                                    <div style={{ position: 'relative', display: LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 1999 }}>
+                                    <div style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 1999 }}>
                                         <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
                                     </div>
                                     {this.mainDashboardArea}
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index 0f63ebc1d..9d89ee036 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -40,7 +40,7 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
                 .keys()
         );
         return doclayouts
-            .filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document))
+            .filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance?.props.Document))
             .filter(doc => !Doc.IsSystem(doc))
             .map(doc => ({ col: doc, target }));
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 42f9bb981..ed9054107 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -29,7 +29,7 @@ const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
 export class CollectionDockingView extends CollectionSubView() {
-    @observable public static Instance: CollectionDockingView;
+    @observable public static Instance: CollectionDockingView | undefined;
     public static makeDocumentConfig(document: Doc, panelName?: string, width?: number) {
         return {
             type: 'react-component',
@@ -103,12 +103,14 @@ export class CollectionDockingView extends CollectionSubView() {
 
     @undoBatch
     public static CloseSplit(document: Opt<Doc>, panelName?: string): boolean {
-        const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tab => (panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document));
-        if (tab) {
-            const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
-            if (j !== -1) {
-                tab.header.parent.contentItems[j].remove();
-                return CollectionDockingView.Instance.layoutChanged();
+        if (CollectionDockingView.Instance) {
+            const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tab => (panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document));
+            if (tab) {
+                const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
+                if (j !== -1) {
+                    tab.header.parent.contentItems[j].remove();
+                    return CollectionDockingView.Instance.layoutChanged();
+                }
             }
         }
 
@@ -119,19 +121,21 @@ export class CollectionDockingView extends CollectionSubView() {
     public static OpenFullScreen(doc: Doc) {
         SelectionManager.DeselectAll();
         const instance = CollectionDockingView.Instance;
-        if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === 'layout') {
-            return DashboardView.openDashboard(doc);
+        if (instance) {
+            if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === 'layout') {
+                return DashboardView.openDashboard(doc);
+            }
+            const newItemStackConfig = {
+                type: 'stack',
+                content: [CollectionDockingView.makeDocumentConfig(Doc.MakeAlias(doc))],
+            };
+            const docconfig = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
+            instance._goldenLayout.root.contentItems[0].addChild(docconfig);
+            docconfig.callDownwards('_$init');
+            instance._goldenLayout._$maximiseItem(docconfig);
+            instance._goldenLayout.emit('stateChanged');
+            instance.stateChanged();
         }
-        const newItemStackConfig = {
-            type: 'stack',
-            content: [CollectionDockingView.makeDocumentConfig(Doc.MakeAlias(doc))],
-        };
-        const docconfig = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
-        instance._goldenLayout.root.contentItems[0].addChild(docconfig);
-        docconfig.callDownwards('_$init');
-        instance._goldenLayout._$maximiseItem(docconfig);
-        instance._goldenLayout.emit('stateChanged');
-        instance.stateChanged();
         return true;
     }
 
@@ -146,21 +150,23 @@ export class CollectionDockingView extends CollectionSubView() {
             const newContentItem = stack.layoutManager.createContentItem(newConfig, instance._goldenLayout);
             stack.addChild(newContentItem.contentItems[0], undefined);
             stack.contentItems[activeContentItemIndex].remove();
-            return CollectionDockingView.Instance.layoutChanged();
+            return instance.layoutChanged();
         }
-        const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tab => tab.contentItem.config.props.panelName === panelName);
+        const tab = Array.from(instance.tabMap.keys()).find(tab => tab.contentItem.config.props.panelName === panelName);
         if (tab) {
             tab.header.parent.addChild(newConfig, undefined);
             const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
             !addToSplit && j !== -1 && tab.header.parent.contentItems[j].remove();
-            return CollectionDockingView.Instance.layoutChanged();
+            return instance.layoutChanged();
         }
         return CollectionDockingView.AddSplit(document, panelName, stack, panelName);
     }
 
     @undoBatch
     public static ToggleSplit(doc: Doc, location: string, stack?: any, panelName?: string) {
-        return Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex(tab => tab.DashDoc === doc) !== -1 ? CollectionDockingView.CloseSplit(doc) : CollectionDockingView.AddSplit(doc, location, stack, panelName);
+        return CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex(tab => tab.DashDoc === doc) !== -1
+            ? CollectionDockingView.CloseSplit(doc)
+            : CollectionDockingView.AddSplit(doc, location, stack, panelName);
     }
 
     //
@@ -170,7 +176,7 @@ export class CollectionDockingView extends CollectionSubView() {
     @action
     public static AddSplit(document: Doc, pullSide: string, stack?: any, panelName?: string) {
         if (document._viewType === CollectionViewType.Docking) return DashboardView.openDashboard(document);
-
+        if (!CollectionDockingView.Instance) return false;
         const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document);
         if (tab) {
             tab.header.parent.setActiveContentItem(tab.contentItem);
@@ -453,13 +459,15 @@ export class CollectionDockingView extends CollectionSubView() {
             Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc);
             Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
         }
-        const dview = CollectionDockingView.Instance.props.Document;
-        const fieldKey = CollectionDockingView.Instance.props.fieldKey;
-        Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc);
-        this.tabMap.delete(tab);
-        tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
-        tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
-        this.stateChanged();
+        if (CollectionDockingView.Instance) {
+            const dview = CollectionDockingView.Instance.props.Document;
+            const fieldKey = CollectionDockingView.Instance.props.fieldKey;
+            Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc);
+            this.tabMap.delete(tab);
+            tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
+            tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
+            this.stateChanged();
+        }
     };
     tabCreated = (tab: any) => {
         this.tabMap.add(tab);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 2ab5f6247..f0cb23eab 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -225,7 +225,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
                         },
                     })
                 );
-                DocListCast(Cast(Doc.UserDoc()['clickFuncs-child'], Doc, null).data).forEach(childClick =>
+                DocListCast(Cast(Doc.UserDoc()['clickFuncs-child'], Doc, null)?.data).forEach(childClick =>
                     onClicks.push({
                         description: `Set child ${childClick.title}`,
                         icon: 'edit',
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index b8aaea622..2d08b1c09 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -302,6 +302,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array
             });
         if (
+            CollectionDockingView.Instance &&
             !Array.from(CollectionDockingView.Instance.tabMap)
                 .map(d => d.DashDoc)
                 .includes(curPres)
@@ -420,7 +421,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     ScreenToLocalTransform = () => {
         this._forceInvalidateScreenToLocal;
         const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
-        return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY);
+        return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY) ?? Transform.Identity();
     };
     PanelWidth = () => this._panelWidth;
     PanelHeight = () => this._panelHeight;
@@ -449,9 +450,9 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     PanelWidth={this.PanelWidth}
                     PanelHeight={this.PanelHeight}
                     styleProvider={DefaultStyleProvider}
-                    docFilters={CollectionDockingView.Instance.childDocFilters}
-                    docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters}
-                    searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
+                    docFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
+                    docRangeFilters={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
+                    searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
                     addDocument={undefined}
                     removeDocument={this.remDocTab}
                     addDocTab={this.addDocTab}
@@ -624,9 +625,9 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
                     styleProvider={TabMinimapView.miniStyleProvider}
                     addDocTab={this.props.addDocTab}
                     pinToPres={TabDocView.PinDoc}
-                    docFilters={CollectionDockingView.Instance.childDocFilters}
-                    docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters}
-                    searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
+                    docFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
+                    docRangeFilters={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
+                    searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
                     fitContentsToBox={returnTrue}
                 />
                 <div className="miniOverlay" onPointerDown={this.miniDown}>
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index bf4c029b2..c4b42301e 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Autocomplete, GoogleMap, GoogleMapProps, Marker } from '@react-google-maps/api';
-import { action, computed, IReactionDisposer, observable, ObservableMap } from 'mobx';
+import { action, computed, IReactionDisposer, observable, ObservableMap, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Opt, WidthSym } from '../../../../fields/Doc';
@@ -12,6 +12,7 @@ import { emptyFunction, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Uti
 import { Docs } from '../../../documents/Documents';
 import { DragManager } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
+import { UndoManager } from '../../../util/UndoManager';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
@@ -368,23 +369,27 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         setupMoveUpEvents(
             this,
             e,
-            (e, down, delta) => {
-                const localDelta = this.props
-                    .ScreenToLocalTransform()
-                    .scale(this.props.scaling?.() || 1)
-                    .transformDirection(delta[0], delta[1]);
-                const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']);
-                const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
-                const ratio = (curNativeWidth + localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth;
-                if (ratio >= 1) {
-                    this.layoutDoc.nativeWidth = nativeWidth * ratio;
-                    this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0];
-                    this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
-                }
-                return false;
-            },
+            (e, down, delta) =>
+                runInAction(() => {
+                    const localDelta = this.props
+                        .ScreenToLocalTransform()
+                        .scale(this.props.scaling?.() || 1)
+                        .transformDirection(delta[0], delta[1]);
+                    const fullWidth = this.layoutDoc[WidthSym]();
+                    const mapWidth = fullWidth - this.sidebarWidth();
+                    if (this.sidebarWidth() + localDelta[0] > 0) {
+                        this._showSidebar = true;
+                        this.layoutDoc._width = fullWidth + localDelta[0];
+                        this.layoutDoc._sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%';
+                    } else {
+                        this._showSidebar = false;
+                        this.layoutDoc._width = mapWidth;
+                        this.layoutDoc._sidebarWidthPercent = '0%';
+                    }
+                    return false;
+                }),
             emptyFunction,
-            this.toggleSidebar
+            () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map')
         );
     };
 
@@ -470,13 +475,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     // TODO: Adding highlight box layer to Maps
     @action
     toggleSidebar = () => {
+        //1.2 * w * ? = .2 * w      .2/1.2
         const prevWidth = this.sidebarWidth();
-        this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%';
-        this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
+        this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? `${(100 * 0.2) / 1.2}%` : '0%') !== '0%';
+        this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
     };
 
     sidebarDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false);
+        setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
     };
     sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
         const bounds = this._ref.current!.getBoundingClientRect();
@@ -605,7 +611,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     {this.annotationLayer}
                     <GoogleMap mapContainerStyle={mapContainerStyle} onZoomChanged={this.zoomChanged} onCenterChanged={this.centered} onLoad={this.loadHandler} options={mapOptions}>
                         <Autocomplete onLoad={this.setSearchBox} onPlaceChanged={this.handlePlaceChanged}>
-                            <input className="mapBox-input" ref={this.inputRef} type="text" placeholder="Enter location" />
+                            <input className="mapBox-input" ref={this.inputRef} type="text" onKeyDown={e => e.stopPropagation()} placeholder="Enter location" />
                         </Autocomplete>
 
                         {this.renderMarkers()}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 5b9d90780..f41f6a1ad 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -13,20 +13,19 @@ import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { KeyCodes } from '../../util/KeyCodes';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import { Colors } from '../global/globalEnums';
 import { CreateImage } from '../nodes/WebBoxRenderer';
-import { AnchorMenu } from '../pdf/AnchorMenu';
 import { PDFViewer } from '../pdf/PDFViewer';
 import { SidebarAnnos } from '../SidebarAnnos';
 import { FieldView, FieldViewProps } from './FieldView';
 import { ImageBox } from './ImageBox';
-import './PDFBox.scss';
 import { VideoBox } from './VideoBox';
+import './PDFBox.scss';
 import React = require('react');
-import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 
 @observer
 export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index cfaa428f9..e945920da 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -640,7 +640,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
     };
     sidebarDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false);
+        setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
     };
     sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
         const bounds = this._ref.current!.getBoundingClientRect();
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 292c187e4..6e5eb3300 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -399,7 +399,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const presCollection = Cast(this.layoutDoc.presCollection, Doc, null);
         const collectionDocView = presCollection ? DocumentManager.Instance.getDocumentView(presCollection) : undefined;
         const includesDoc: boolean = DocListCast(presCollection?.data).includes(targetDoc);
-        const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === srcContext);
+        const tab = CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === srcContext);
         this.turnOffEdit();
         // Handles the setting of presCollection
         if (includesDoc) {
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 0bf1575fb..3fc0a237e 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -26,7 +26,7 @@ import './TopBar.scss';
 @observer
 export class TopBar extends React.Component {
     navigateToHome = () => {
-        CollectionDockingView.Instance.CaptureThumbnail()?.then(() => {
+        CollectionDockingView.Instance?.CaptureThumbnail()?.then(() => {
             Doc.ActivePage = 'home';
             DashboardView.closeActiveDashboard(); // bcz: if we do this, we need some other way to keep track, for user convenience, of the last dashboard in use
         });
diff --git a/src/fields/util.ts b/src/fields/util.ts
index cbb560114..41e723119 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,41 +1,40 @@
+import { action, observable, runInAction, trace } from 'mobx';
+import { computedFn } from 'mobx-utils';
+import { DocServer } from '../client/DocServer';
+import { SerializationHelper } from '../client/util/SerializationHelper';
 import { UndoManager } from '../client/util/UndoManager';
+import { returnZero } from '../Utils';
+import CursorField from './CursorField';
 import {
-    Doc,
-    FieldResult,
-    UpdatingFromServer,
-    LayoutSym,
-    AclPrivate,
+    AclAdmin,
+    AclAugment,
     AclEdit,
+    AclPrivate,
     AclReadonly,
-    AclAugment,
+    AclSelfEdit,
     AclSym,
+    AclUnset,
     DataSym,
+    Doc,
     DocListCast,
-    AclAdmin,
-    HeightSym,
-    WidthSym,
-    updateCachedAcls,
-    AclUnset,
     DocListCastAsync,
+    FieldResult,
     ForceServerWrite,
+    HeightSym,
     Initializing,
-    AclSelfEdit,
+    LayoutSym,
+    updateCachedAcls,
+    UpdatingFromServer,
+    WidthSym,
 } from './Doc';
-import { SerializationHelper } from '../client/util/SerializationHelper';
-import { ProxyField, PrefetchProxy } from './Proxy';
-import { RefField } from './RefField';
+import { Id, OnUpdate, Parent, Self, SelfProxy, Update } from './FieldSymbols';
+import { List } from './List';
 import { ObjectField } from './ObjectField';
-import { action, observable, runInAction, trace } from 'mobx';
-import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from './FieldSymbols';
-import { DocServer } from '../client/DocServer';
+import { PrefetchProxy, ProxyField } from './Proxy';
+import { RefField } from './RefField';
+import { RichTextField } from './RichTextField';
 import { ComputedField } from './ScriptField';
 import { ScriptCast, StrCast } from './Types';
-import { returnZero } from '../Utils';
-import CursorField from './CursorField';
-import { List } from './List';
-import { SnappingManager } from '../client/util/SnappingManager';
-import { computedFn } from 'mobx-utils';
-import { RichTextField } from './RichTextField';
 
 function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx
index d668d134e..6415099fd 100644
--- a/src/mobile/MobileInkOverlay.tsx
+++ b/src/mobile/MobileInkOverlay.tsx
@@ -1,13 +1,12 @@
 import React = require('react');
-import { observer } from "mobx-react";
-import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "../server/Message";
-import { observable, action } from "mobx";
-import { GestureUtils } from "../pen-gestures/GestureUtils";
-import "./MobileInkOverlay.scss";
-import { DragManager } from "../client/util/DragManager";
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
 import { DocServer } from '../client/DocServer';
+import { DragManager } from '../client/util/DragManager';
 import { Doc } from '../fields/Doc';
-
+import { GestureUtils } from '../pen-gestures/GestureUtils';
+import { GestureContent, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent } from '../server/Message';
+import './MobileInkOverlay.scss';
 
 @observer
 export default class MobileInkOverlay extends React.Component {
@@ -18,7 +17,7 @@ export default class MobileInkOverlay extends React.Component {
     @observable private _height: number = 0;
     @observable private _x: number = -300;
     @observable private _y: number = -300;
-    @observable private _text: string = "";
+    @observable private _text: string = '';
 
     @observable private _offsetX: number = 0;
     @observable private _offsetY: number = 0;
@@ -49,7 +48,7 @@ export default class MobileInkOverlay extends React.Component {
         this._scale = scaledSize.scale;
         this._x = 300; // TODO: center on screen
         this._y = 25; // TODO: center on screen
-        this._text = text ? text : "";
+        this._text = text ? text : '';
     }
 
     @action
@@ -64,31 +63,29 @@ export default class MobileInkOverlay extends React.Component {
         // TODO: figure out why strokes drawn in corner of mobile interface dont get inserted
 
         const { points, bounds } = content;
-        console.log("received points", points, bounds);
+        console.log('received points', points, bounds);
 
         const B = {
-            right: (bounds.right * this._scale) + this._x,
-            left: (bounds.left * this._scale) + this._x, // TODO: scale
-            bottom: (bounds.bottom * this._scale) + this._y,
-            top: (bounds.top * this._scale) + this._y, // TODO: scale
+            right: bounds.right * this._scale + this._x,
+            left: bounds.left * this._scale + this._x, // TODO: scale
+            bottom: bounds.bottom * this._scale + this._y,
+            top: bounds.top * this._scale + this._y, // TODO: scale
             width: bounds.width * this._scale,
             height: bounds.height * this._scale,
         };
 
         const target = document.elementFromPoint(this._x + 10, this._y + 10);
         target?.dispatchEvent(
-            new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
-                {
-                    bubbles: true,
-                    detail: {
-                        points: points,
-                        gesture: GestureUtils.Gestures.Stroke,
-                        bounds: B
-                    }
-                }
-            )
+            new CustomEvent<GestureUtils.GestureEvent>('dashOnGesture', {
+                bubbles: true,
+                detail: {
+                    points: points,
+                    gesture: GestureUtils.Gestures.Stroke,
+                    bounds: B,
+                },
+            })
         );
-    }
+    };
 
     uploadDocument = async (content: MobileDocumentUploadContent) => {
         const { docId } = content;
@@ -100,36 +97,34 @@ export default class MobileInkOverlay extends React.Component {
             const complete = new DragManager.DragCompleteEvent(false, dragData);
 
             if (target) {
-                console.log("dispatching upload doc!!!!", target, doc);
+                console.log('dispatching upload doc!!!!', target, doc);
                 target.dispatchEvent(
-                    new CustomEvent<DragManager.DropEvent>("dashOnDrop",
-                        {
-                            bubbles: true,
-                            detail: {
-                                x: this._x,
-                                y: this._y,
-                                complete: complete,
-                                altKey: false,
-                                metaKey: false,
-                                ctrlKey: false,
-                                shiftKey: false,
-                                embedKey: false
-                            }
-                        }
-                    )
+                    new CustomEvent<DragManager.DropEvent>('dashOnDrop', {
+                        bubbles: true,
+                        detail: {
+                            x: this._x,
+                            y: this._y,
+                            complete: complete,
+                            altKey: false,
+                            metaKey: false,
+                            ctrlKey: false,
+                            shiftKey: false,
+                            embedKey: false,
+                        },
+                    })
                 );
             } else {
-                alert("TARGET IS UNDEFINED");
+                alert('TARGET IS UNDEFINED');
             }
         }
-    }
+    };
 
     @action
     dragStart = (e: React.PointerEvent) => {
-        document.removeEventListener("pointermove", this.dragging);
-        document.removeEventListener("pointerup", this.dragEnd);
-        document.addEventListener("pointermove", this.dragging);
-        document.addEventListener("pointerup", this.dragEnd);
+        document.removeEventListener('pointermove', this.dragging);
+        document.removeEventListener('pointerup', this.dragEnd);
+        document.addEventListener('pointermove', this.dragging);
+        document.addEventListener('pointerup', this.dragEnd);
 
         this._isDragging = true;
         this._offsetX = e.pageX - this._mainCont.current!.getBoundingClientRect().left;
@@ -137,7 +132,7 @@ export default class MobileInkOverlay extends React.Component {
 
         e.preventDefault();
         e.stopPropagation();
-    }
+    };
 
     @action
     dragging = (e: PointerEvent) => {
@@ -150,41 +145,39 @@ export default class MobileInkOverlay extends React.Component {
 
         e.preventDefault();
         e.stopPropagation();
-    }
+    };
 
     @action
     dragEnd = (e: PointerEvent) => {
-        document.removeEventListener("pointermove", this.dragging);
-        document.removeEventListener("pointerup", this.dragEnd);
+        document.removeEventListener('pointermove', this.dragging);
+        document.removeEventListener('pointerup', this.dragEnd);
 
         this._isDragging = false;
 
         e.preventDefault();
         e.stopPropagation();
-    }
+    };
 
     render() {
-
         return (
-            <div className="mobileInkOverlay"
+            <div
+                className="mobileInkOverlay"
                 style={{
                     width: this._width,
                     height: this._height,
-                    position: "absolute",
+                    position: 'absolute',
                     transform: `translate(${this._x}px, ${this._y}px)`,
                     zIndex: 30000,
-                    pointerEvents: "none",
-                    borderStyle: this._isDragging ? "solid" : "dashed",
-                }
-                }
-                ref={this._mainCont}
-            >
+                    pointerEvents: 'none',
+                    borderStyle: this._isDragging ? 'solid' : 'dashed',
+                }}
+                ref={this._mainCont}>
                 <p>{this._text}</p>
                 <div className="mobileInkOverlay-border top" onPointerDown={this.dragStart}></div>
                 <div className="mobileInkOverlay-border bottom" onPointerDown={this.dragStart}></div>
                 <div className="mobileInkOverlay-border left" onPointerDown={this.dragStart}></div>
                 <div className="mobileInkOverlay-border right" onPointerDown={this.dragStart}></div>
-            </div >
+            </div>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/mobile/MobileMain.tsx b/src/mobile/MobileMain.tsx
index f85f05f53..6cbf86f77 100644
--- a/src/mobile/MobileMain.tsx
+++ b/src/mobile/MobileMain.tsx
@@ -12,10 +12,7 @@ AssignAllExtensions();
     const info = await CurrentUserUtils.loadCurrentUser();
     DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email + ' (mobile)');
     await Docs.Prototypes.initialize();
-    if (info.id !== '__guest__') {
-        // a guest will not have an id registered
-        await CurrentUserUtils.loadUserDocument(info.id);
-    }
+    await CurrentUserUtils.loadUserDocument(info.id);
     document.getElementById('root')!.addEventListener(
         'wheel',
         event => {
diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts
index 7be8a1e9f..53e55c1c3 100644
--- a/src/server/ApiManagers/UserManager.ts
+++ b/src/server/ApiManagers/UserManager.ts
@@ -1,10 +1,10 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method } from "../RouteManager";
-import { Database } from "../database";
-import { msToTime } from "../ActionUtilities";
-import * as bcrypt from "bcrypt-nodejs";
-import { Opt } from "../../fields/Doc";
-import { WebSocket } from "../websocket";
+import ApiManager, { Registration } from './ApiManager';
+import { Method } from '../RouteManager';
+import { Database } from '../database';
+import { msToTime } from '../ActionUtilities';
+import * as bcrypt from 'bcrypt-nodejs';
+import { Opt } from '../../fields/Doc';
+import { WebSocket } from '../websocket';
 
 export const timeMap: { [id: string]: number } = {};
 interface ActivityUnit {
@@ -13,28 +13,26 @@ interface ActivityUnit {
 }
 
 export default class UserManager extends ApiManager {
-
     protected initialize(register: Registration): void {
-
         register({
             method: Method.GET,
-            subscription: "/getUsers",
+            subscription: '/getUsers',
             secureHandler: async ({ res }) => {
-                const cursor = await Database.Instance.query({}, { email: 1, linkDatabaseId: 1, sharingDocumentId: 1 }, "users");
+                const cursor = await Database.Instance.query({}, { email: 1, linkDatabaseId: 1, sharingDocumentId: 1 }, 'users');
                 const results = await cursor.toArray();
                 res.send(results.map((user: any) => ({ email: user.email, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId })));
-            }
+            },
         });
 
         register({
             method: Method.POST,
-            subscription: "/setCacheDocumentIds",
+            subscription: '/setCacheDocumentIds',
             secureHandler: async ({ user, req, res }) => {
                 const result: any = {};
                 user.cacheDocumentIds = req.body.cacheDocumentIds;
                 user.save(err => {
                     if (err) {
-                        result.error = [{ msg: "Error while caching documents" }];
+                        result.error = [{ msg: 'Error while caching documents' }];
                     }
                 });
 
@@ -42,32 +40,35 @@ export default class UserManager extends ApiManager {
                 //     console.log(e);
                 // });
                 res.send(result);
-            }
+            },
         });
 
         register({
             method: Method.GET,
-            subscription: "/getUserDocumentIds",
-            secureHandler: ({ res, user }) => res.send({ userDocumentId: user.userDocumentId, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId })
+            subscription: '/getUserDocumentIds',
+            secureHandler: ({ res, user }) => res.send({ userDocumentId: user.userDocumentId, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId }),
+            publicHandler: ({ res }) => res.send({ userDocumentId: '__guest__', linkDatabaseId: 3, sharingDocumentId: 2 }),
         });
 
         register({
             method: Method.GET,
-            subscription: "/getSharingDocumentId",
-            secureHandler: ({ res, user }) => res.send(user.sharingDocumentId)
+            subscription: '/getSharingDocumentId',
+            secureHandler: ({ res, user }) => res.send(user.sharingDocumentId),
+            publicHandler: ({ res }) => res.send(2),
         });
 
         register({
             method: Method.GET,
-            subscription: "/getLinkDatabaseId",
-            secureHandler: ({ res, user }) => res.send(user.linkDatabaseId)
+            subscription: '/getLinkDatabaseId',
+            secureHandler: ({ res, user }) => res.send(user.linkDatabaseId),
+            publicHandler: ({ res }) => res.send(3),
         });
 
         register({
             method: Method.GET,
-            subscription: "/getCurrentUser",
+            subscription: '/getCurrentUser',
             secureHandler: ({ res, user: { _id, email, cacheDocumentIds } }) => res.send(JSON.stringify({ id: _id, email, cacheDocumentIds })),
-            publicHandler: ({ res }) => res.send(JSON.stringify({ id: "__guest__", email: "" }))
+            publicHandler: ({ res }) => res.send(JSON.stringify({ id: '__guest__', email: 'guest' })),
         });
 
         register({
@@ -80,7 +81,7 @@ export default class UserManager extends ApiManager {
                 const validated = await new Promise<Opt<boolean>>(resolve => {
                     bcrypt.compare(curr_pass, user.password, (err, passwords_match) => {
                         if (err || !passwords_match) {
-                            result.error = [{ msg: "Incorrect current password" }];
+                            result.error = [{ msg: 'Incorrect current password' }];
                             res.send(result);
                             resolve(undefined);
                         } else {
@@ -93,10 +94,10 @@ export default class UserManager extends ApiManager {
                     return;
                 }
 
-                req.assert("new_pass", "Password must be at least 4 characters long").len({ min: 4 });
-                req.assert("new_confirm", "Passwords do not match").equals(new_pass);
+                req.assert('new_pass', 'Password must be at least 4 characters long').len({ min: 4 });
+                req.assert('new_confirm', 'Passwords do not match').equals(new_pass);
                 if (curr_pass === new_pass) {
-                    result.error = [{ msg: "Current and new password are the same" }];
+                    result.error = [{ msg: 'Current and new password are the same' }];
                 }
                 // was there error in validating new passwords?
                 if (req.validationErrors()) {
@@ -113,17 +114,17 @@ export default class UserManager extends ApiManager {
 
                 user.save(err => {
                     if (err) {
-                        result.error = [{ msg: "Error while saving new password" }];
+                        result.error = [{ msg: 'Error while saving new password' }];
                     }
                 });
 
                 res.send(result);
-            }
+            },
         });
 
         register({
             method: Method.GET,
-            subscription: "/activity",
+            subscription: '/activity',
             secureHandler: ({ res }) => {
                 const now = Date.now();
 
@@ -135,25 +136,23 @@ export default class UserManager extends ApiManager {
                     const socketPair = Array.from(WebSocket.socketMap).find(pair => pair[1] === user);
                     if (socketPair && !socketPair[0].disconnected) {
                         const duration = now - time;
-                        const target = (duration / 1000) < (60 * 5) ? activeTimes : inactiveTimes;
+                        const target = duration / 1000 < 60 * 5 ? activeTimes : inactiveTimes;
                         target.push({ user, duration });
                     }
                 }
 
-                const process = (target: { user: string, duration: number }[]) => {
+                const process = (target: { user: string; duration: number }[]) => {
                     const comparator = (first: ActivityUnit, second: ActivityUnit) => first.duration - second.duration;
                     const sorted = target.sort(comparator);
                     return sorted.map(({ user, duration }) => `${user} (${msToTime(duration)})`);
                 };
 
-                res.render("user_activity.pug", {
-                    title: "User Activity",
+                res.render('user_activity.pug', {
+                    title: 'User Activity',
                     active: process(activeTimes),
-                    inactive: process(inactiveTimes)
+                    inactive: process(inactiveTimes),
                 });
-            }
+            },
         });
-
     }
-
-}
\ No newline at end of file
+}
diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts
index aa9bfcfa7..5683cd539 100644
--- a/src/server/RouteManager.ts
+++ b/src/server/RouteManager.ts
@@ -1,12 +1,12 @@
 import { cyan, green, red } from 'colors';
 import { Express, Request, Response } from 'express';
-import { AdminPriviliges } from ".";
-import { DashUserModel } from "./authentication/DashUserModel";
-import RouteSubscriber from "./RouteSubscriber";
+import { AdminPriviliges } from '.';
+import { DashUserModel } from './authentication/DashUserModel';
+import RouteSubscriber from './RouteSubscriber';
 
 export enum Method {
     GET,
-    POST
+    POST,
 }
 
 export interface CoreArguments {
@@ -33,13 +33,13 @@ const registered = new Map<string, Set<Method>>();
 
 enum RegistrationError {
     Malformed,
-    Duplicate
+    Duplicate,
 }
 
 export default class RouteManager {
     private server: Express;
     private _isRelease: boolean;
-    private failedRegistrations: { route: string, reason: RegistrationError }[] = [];
+    private failedRegistrations: { route: string; reason: RegistrationError }[] = [];
 
     public get isRelease() {
         return this._isRelease;
@@ -74,39 +74,42 @@ export default class RouteManager {
             }
             process.exit(1);
         } else {
-            console.log(green("all server routes have been successfully registered:"));
-            Array.from(registered.keys()).sort().forEach(route => console.log(cyan(route)));
+            console.log(green('all server routes have been successfully registered:'));
+            Array.from(registered.keys())
+                .sort()
+                .forEach(route => console.log(cyan(route)));
             console.log();
         }
-    }
+    };
 
     static routes: string[] = [];
     /**
-     * 
-     * @param initializer 
+     *
+     * @param initializer
      */
     addSupervisedRoute = (initializer: RouteInitializer): void => {
         const { method, subscription, secureHandler, publicHandler, errorHandler, requireAdminInRelease: requireAdmin } = initializer;
 
-        typeof (initializer.subscription) === "string" && RouteManager.routes.push(initializer.subscription);
+        typeof initializer.subscription === 'string' && RouteManager.routes.push(initializer.subscription);
         initializer.subscription instanceof RouteSubscriber && RouteManager.routes.push(initializer.subscription.root);
-        initializer.subscription instanceof Array && initializer.subscription.map(sub => {
-            typeof (sub) === "string" && RouteManager.routes.push(sub);
-            sub instanceof RouteSubscriber && RouteManager.routes.push(sub.root);
-        });
+        initializer.subscription instanceof Array &&
+            initializer.subscription.map(sub => {
+                typeof sub === 'string' && RouteManager.routes.push(sub);
+                sub instanceof RouteSubscriber && RouteManager.routes.push(sub.root);
+            });
         const isRelease = this._isRelease;
         const supervised = async (req: Request, res: Response) => {
             let user = req.user as Partial<DashUserModel> | undefined;
             const { originalUrl: target } = req;
-            if (process.env.DB === "MEM" && !user) {
-                user = { id: "guest", email: "", userDocumentId: "guestDocId" };
+            if (process.env.DB === 'MEM' && !user) {
+                user = { id: 'guest', email: 'guest', userDocumentId: '__guest__' };
             }
             const core = { req, res, isRelease };
             const tryExecute = async (toExecute: (args: any) => any | Promise<any>, args: any) => {
                 try {
                     await toExecute(args);
                 } catch (e) {
-                    console.log(red(target), user && ("email" in user) ? "<user logged out>" : undefined);
+                    console.log(red(target), user && 'email' in user ? '<user logged out>' : undefined);
                     if (errorHandler) {
                         errorHandler({ ...core, error: e });
                     } else {
@@ -119,7 +122,7 @@ export default class RouteManager {
                     if (AdminPriviliges.get(user.id)) {
                         AdminPriviliges.delete(user.id);
                     } else {
-                        return res.redirect(`/admin/${req.originalUrl.substring(1).replace("/", ":")}`);
+                        return res.redirect(`/admin/${req.originalUrl.substring(1).replace('/', ':')}`);
                     }
                 }
                 await tryExecute(secureHandler, { ...core, user });
@@ -128,10 +131,10 @@ export default class RouteManager {
                 if (publicHandler) {
                     await tryExecute(publicHandler, core);
                     if (!res.headersSent) {
-                        res.redirect("/login");
+                        // res.redirect("/login");
                     }
                 } else {
-                    res.redirect("/login");
+                    res.redirect('/login');
                 }
             }
             setTimeout(() => {
@@ -144,7 +147,7 @@ export default class RouteManager {
         };
         const subscribe = (subscriber: RouteSubscriber | string) => {
             let route: string;
-            if (typeof subscriber === "string") {
+            if (typeof subscriber === 'string') {
                 route = subscriber;
             } else {
                 route = subscriber.build;
@@ -152,7 +155,7 @@ export default class RouteManager {
             if (!/^\/$|^\/[A-Za-z\*]+(\/\:[A-Za-z?_\*]+)*$/g.test(route)) {
                 this.failedRegistrations.push({
                     reason: RegistrationError.Malformed,
-                    route
+                    route,
                 });
             } else {
                 const existing = registered.get(route);
@@ -160,7 +163,7 @@ export default class RouteManager {
                     if (existing.has(method)) {
                         this.failedRegistrations.push({
                             reason: RegistrationError.Duplicate,
-                            route
+                            route,
                         });
                         return;
                     }
@@ -184,15 +187,14 @@ export default class RouteManager {
         } else {
             subscribe(subscription);
         }
-    }
-
+    };
 }
 
 export const STATUS = {
     OK: 200,
     BAD_REQUEST: 400,
     EXECUTION_ERROR: 500,
-    PERMISSION_DENIED: 403
+    PERMISSION_DENIED: 403,
 };
 
 export function _error(res: Response, message: string, error?: any) {
diff --git a/src/server/authentication/AuthenticationManager.ts b/src/server/authentication/AuthenticationManager.ts
index 3622be4c5..52d876e95 100644
--- a/src/server/authentication/AuthenticationManager.ts
+++ b/src/server/authentication/AuthenticationManager.ts
@@ -1,4 +1,4 @@
-import { default as User, DashUserModel } from './DashUserModel';
+import { default as User, DashUserModel, initializeGuest } from './DashUserModel';
 import { Request, Response, NextFunction } from 'express';
 import * as passport from 'passport';
 import { IVerifyOptions } from 'passport-local';
@@ -30,6 +30,7 @@ export let getSignup = (req: Request, res: Response) => {
  * Create a new local account.
  */
 export let postSignup = (req: Request, res: Response, next: NextFunction) => {
+    const email = req.body.email as String;
     req.assert('email', 'Email is not valid').isEmail();
     req.assert('password', 'Password must be at least 4 characters long').len({ min: 4 });
     req.assert('confirmPassword', 'Passwords do not match').equals(req.body.password);
@@ -41,15 +42,14 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => {
         return res.redirect('/signup');
     }
 
-    const email = req.body.email as String;
     const password = req.body.password;
 
     const model = {
         email,
         password,
-        userDocumentId: Utils.GenerateGuid(),
-        sharingDocumentId: Utils.GenerateGuid(),
-        linkDatabaseId: Utils.GenerateGuid(),
+        userDocumentId: email === 'guest' ? '__guest__' : Utils.GenerateGuid(),
+        sharingDocumentId: email === 'guest' ? 2 : Utils.GenerateGuid(),
+        linkDatabaseId: email === 'guest' ? 3 : Utils.GenerateGuid(),
         cacheDocumentIds: '',
     } as Partial<DashUserModel>;
 
@@ -106,18 +106,22 @@ export let getLogin = (req: Request, res: Response) => {
  * On failure, redirect to signup page
  */
 export let postLogin = (req: Request, res: Response, next: NextFunction) => {
-    req.assert('email', 'Email is not valid').isEmail();
-    req.assert('password', 'Password cannot be blank').notEmpty();
-    req.sanitize('email').normalizeEmail({ gmail_remove_dots: false });
-
-    const errors = req.validationErrors();
+    if (req.body.email === '') {
+        User.findOne({ email: 'guest' }, (err: any, user: DashUserModel) => !user && initializeGuest());
+        req.body.email = 'guest';
+        req.body.password = 'guest';
+    } else {
+        req.assert('email', 'Email is not valid').isEmail();
+        req.assert('password', 'Password cannot be blank').notEmpty();
+        req.sanitize('email').normalizeEmail({ gmail_remove_dots: false });
+    }
 
-    if (errors) {
+    if (req.validationErrors()) {
         req.flash('errors', 'Unable to login at this time. Please try again.');
         return res.redirect('/signup');
     }
 
-    passport.authenticate('local', (err: Error, user: DashUserModel, _info: IVerifyOptions) => {
+    const callback = (err: Error, user: DashUserModel, _info: IVerifyOptions) => {
         if (err) {
             next(err);
             return;
@@ -132,7 +136,8 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => {
             }
             tryRedirectToTarget(req, res);
         });
-    })(req, res, next);
+    };
+    setTimeout(() => passport.authenticate('local', callback)(req, res, next), 500);
 };
 
 /**
diff --git a/src/server/authentication/DashUserModel.ts b/src/server/authentication/DashUserModel.ts
index bee28b96d..a1883beab 100644
--- a/src/server/authentication/DashUserModel.ts
+++ b/src/server/authentication/DashUserModel.ts
@@ -1,13 +1,13 @@
 //@ts-ignore
-import * as bcrypt from "bcrypt-nodejs";
+import * as bcrypt from 'bcrypt-nodejs';
 //@ts-ignore
 import * as mongoose from 'mongoose';
 
 export type DashUserModel = mongoose.Document & {
-    email: String,
-    password: string,
-    passwordResetToken?: string,
-    passwordResetExpires?: Date,
+    email: String;
+    password: string;
+    passwordResetToken?: string;
+    passwordResetExpires?: Date;
 
     userDocumentId: string;
     sharingDocumentId: string;
@@ -15,66 +15,74 @@ export type DashUserModel = mongoose.Document & {
     cacheDocumentIds: string;
 
     profile: {
-        name: string,
-        gender: string,
-        location: string,
-        website: string,
-        picture: string
-    },
+        name: string;
+        gender: string;
+        location: string;
+        website: string;
+        picture: string;
+    };
 
-    comparePassword: comparePasswordFunction,
+    comparePassword: comparePasswordFunction;
 };
 
 type comparePasswordFunction = (candidatePassword: string, cb: (err: any, isMatch: any) => {}) => void;
 
 export type AuthToken = {
-    accessToken: string,
-    kind: string
+    accessToken: string;
+    kind: string;
 };
 
-const userSchema = new mongoose.Schema({
-    email: String,
-    password: String,
-    passwordResetToken: String,
-    passwordResetExpires: Date,
+const userSchema = new mongoose.Schema(
+    {
+        email: String,
+        password: String,
+        passwordResetToken: String,
+        passwordResetExpires: Date,
 
-    userDocumentId: String,    // id that identifies a document which hosts all of a user's account data
-    sharingDocumentId: String, // id that identifies a document that stores documents shared to a user, their user color, and any additional info needed to communicate between users
-    linkDatabaseId: String,
-    cacheDocumentIds: String, // set of document ids to retreive on startup
+        userDocumentId: String, // id that identifies a document which hosts all of a user's account data
+        sharingDocumentId: String, // id that identifies a document that stores documents shared to a user, their user color, and any additional info needed to communicate between users
+        linkDatabaseId: String,
+        cacheDocumentIds: String, // set of document ids to retreive on startup
 
-    facebook: String,
-    twitter: String,
-    google: String,
+        facebook: String,
+        twitter: String,
+        google: String,
 
-    profile: {
-        name: String,
-        gender: String,
-        location: String,
-        website: String,
-        picture: String
-    }
-}, { timestamps: true });
+        profile: {
+            name: String,
+            gender: String,
+            location: String,
+            website: String,
+            picture: String,
+        },
+    },
+    { timestamps: true }
+);
 
 /**
  * Password hash middleware.
  */
-userSchema.pre("save", function save(next) {
+userSchema.pre('save', function save(next) {
     const user = this as DashUserModel;
-    if (!user.isModified("password")) {
+    if (!user.isModified('password')) {
         return next();
     }
     bcrypt.genSalt(10, (err: any, salt: string) => {
         if (err) {
             return next(err);
         }
-        bcrypt.hash(user.password, salt, () => void {}, (err: mongoose.Error, hash: string) => {
-            if (err) {
-                return next(err);
+        bcrypt.hash(
+            user.password,
+            salt,
+            () => void {},
+            (err: mongoose.Error, hash: string) => {
+                if (err) {
+                    return next(err);
+                }
+                user.password = hash;
+                next();
             }
-            user.password = hash;
-            next();
-        });
+        );
     });
 });
 
@@ -88,5 +96,15 @@ const comparePassword: comparePasswordFunction = function (this: DashUserModel,
 
 userSchema.methods.comparePassword = comparePassword;
 
-const User = mongoose.model("User", userSchema);
-export default User;
\ No newline at end of file
+const User = mongoose.model('User', userSchema);
+export function initializeGuest() {
+    new User({
+        email: 'guest',
+        password: 'guest',
+        userDocumentId: '__guest__',
+        sharingDocumentId: '2',
+        linkDatabaseId: '3',
+        cacheDocumentIds: '',
+    }).save();
+}
+export default User;
diff --git a/src/server/index.ts b/src/server/index.ts
index f8c32103b..6e6bde3cb 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,35 +1,35 @@
 require('dotenv').config();
-import { yellow } from "colors";
+import { yellow } from 'colors';
 import * as mobileDetect from 'mobile-detect';
 import * as path from 'path';
 import * as qs from 'query-string';
-import { log_execution } from "./ActionUtilities";
-import DeleteManager from "./ApiManagers/DeleteManager";
+import { log_execution } from './ActionUtilities';
+import DeleteManager from './ApiManagers/DeleteManager';
 import DownloadManager from './ApiManagers/DownloadManager';
-import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager";
-import GooglePhotosManager from "./ApiManagers/GooglePhotosManager";
-import PDFManager from "./ApiManagers/PDFManager";
+import GeneralGoogleManager from './ApiManagers/GeneralGoogleManager';
+import GooglePhotosManager from './ApiManagers/GooglePhotosManager';
+import PDFManager from './ApiManagers/PDFManager';
 import { SearchManager } from './ApiManagers/SearchManager';
-import SessionManager from "./ApiManagers/SessionManager";
-import UploadManager from "./ApiManagers/UploadManager";
+import SessionManager from './ApiManagers/SessionManager';
+import UploadManager from './ApiManagers/UploadManager';
 import UserManager from './ApiManagers/UserManager';
 import UtilManager from './ApiManagers/UtilManager';
 import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader';
-import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils";
-import { DashSessionAgent } from "./DashSession/DashSessionAgent";
-import { AppliedSessionAgent } from "./DashSession/Session/agents/applied_session_agent";
+import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils';
+import { DashSessionAgent } from './DashSession/DashSessionAgent';
+import { AppliedSessionAgent } from './DashSession/Session/agents/applied_session_agent';
 import { DashUploadUtils } from './DashUploadUtils';
 import { Database } from './database';
-import { Logger } from "./ProcessFactory";
+import { Logger } from './ProcessFactory';
 import RouteManager, { Method, PublicHandler } from './RouteManager';
 import RouteSubscriber from './RouteSubscriber';
 import initializeServer, { resolvedPorts } from './server_Initialization';
 
 export const AdminPriviliges: Map<string, boolean> = new Map();
-export const onWindows = process.platform === "win32";
+export const onWindows = process.platform === 'win32';
 export let sessionAgent: AppliedSessionAgent;
-export const publicDirectory = path.resolve(__dirname, "public");
-export const filesDirectory = path.resolve(publicDirectory, "files");
+export const publicDirectory = path.resolve(__dirname, 'public');
+export const filesDirectory = path.resolve(publicDirectory, 'files');
 
 /**
  * These are the functions run before the server starts
@@ -43,11 +43,11 @@ async function preliminaryFunctions() {
     await GoogleCredentialsLoader.loadCredentials();
     SSL.loadCredentials();
     GoogleApiServerUtils.processProjectCredentials();
-    if (process.env.DB !== "MEM") {
+    if (process.env.DB !== 'MEM') {
         await log_execution({
-            startMessage: "attempting to initialize mongodb connection",
-            endMessage: "connection outcome determined",
-            action: Database.tryInitializeConnection
+            startMessage: 'attempting to initialize mongodb connection',
+            endMessage: 'connection outcome determined',
+            action: Database.tryInitializeConnection,
         });
     }
 }
@@ -56,27 +56,16 @@ async function preliminaryFunctions() {
  * Either clustered together as an API manager
  * or individually referenced below, by the completion
  * of this function's execution, all routes will
- * be registered on the server 
+ * be registered on the server
  * @param router the instance of the route manager
  * that will manage the registration of new routes
  * with the server
  */
 function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: RouteManager) {
-    const managers = [
-        new SessionManager(),
-        new UserManager(),
-        new UploadManager(),
-        new DownloadManager(),
-        new SearchManager(),
-        new PDFManager(),
-        new DeleteManager(),
-        new UtilManager(),
-        new GeneralGoogleManager(),
-        new GooglePhotosManager(),
-    ];
+    const managers = [new SessionManager(), new UserManager(), new UploadManager(), new DownloadManager(), new SearchManager(), new PDFManager(), new DeleteManager(), new UtilManager(), new GeneralGoogleManager(), new GooglePhotosManager()];
 
     // initialize API Managers
-    console.log(yellow("\nregistering server routes..."));
+    console.log(yellow('\nregistering server routes...'));
     managers.forEach(manager => manager.register(addSupervisedRoute));
 
     /**
@@ -84,88 +73,87 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
      */
     addSupervisedRoute({
         method: Method.GET,
-        subscription: "/",
-        secureHandler: ({ res }) => res.redirect("/home")
+        subscription: '/',
+        secureHandler: ({ res }) => res.redirect('/home'),
     });
 
-
     addSupervisedRoute({
         method: Method.GET,
-        subscription: "/serverHeartbeat",
-        secureHandler: ({ res }) => res.send(true)
+        subscription: '/serverHeartbeat',
+        secureHandler: ({ res }) => res.send(true),
     });
 
     addSupervisedRoute({
         method: Method.GET,
-        subscription: "/resolvedPorts",
-        secureHandler: ({ res }) => res.send(resolvedPorts)
+        subscription: '/resolvedPorts',
+        secureHandler: ({ res }) => res.send(resolvedPorts),
+        publicHandler: ({ res }) => res.send(resolvedPorts),
     });
 
     const serve: PublicHandler = ({ req, res }) => {
-        const detector = new mobileDetect(req.headers['user-agent'] || "");
+        const detector = new mobileDetect(req.headers['user-agent'] || '');
         const filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html';
         res.sendFile(path.join(__dirname, '../../deploy/' + filename));
     };
 
     /**
-     * Serves a simple password input box for any 
+     * Serves a simple password input box for any
      */
     addSupervisedRoute({
         method: Method.GET,
-        subscription: new RouteSubscriber("admin").add("previous_target"),
+        subscription: new RouteSubscriber('admin').add('previous_target'),
         secureHandler: ({ res, isRelease }) => {
             const { PASSWORD } = process.env;
             if (!(isRelease && PASSWORD)) {
-                return res.redirect("/home");
+                return res.redirect('/home');
             }
-            res.render("admin.pug", { title: "Enter Administrator Password" });
-        }
+            res.render('admin.pug', { title: 'Enter Administrator Password' });
+        },
     });
 
     addSupervisedRoute({
         method: Method.POST,
-        subscription: new RouteSubscriber("admin").add("previous_target"),
+        subscription: new RouteSubscriber('admin').add('previous_target'),
         secureHandler: async ({ req, res, isRelease, user: { id } }) => {
             const { PASSWORD } = process.env;
             if (!(isRelease && PASSWORD)) {
-                return res.redirect("/home");
+                return res.redirect('/home');
             }
             const { password } = req.body;
             const { previous_target } = req.params;
             let redirect: string;
             if (password === PASSWORD) {
                 AdminPriviliges.set(id, true);
-                redirect = `/${previous_target.replace(":", "/")}`;
+                redirect = `/${previous_target.replace(':', '/')}`;
             } else {
                 redirect = `/admin/${previous_target}`;
             }
             res.redirect(redirect);
-        }
+        },
     });
 
     addSupervisedRoute({
         method: Method.GET,
-        subscription: ["/home", new RouteSubscriber("doc").add("docId")],
+        subscription: ['/home', new RouteSubscriber('doc').add('docId')],
         secureHandler: serve,
         publicHandler: ({ req, res, ...remaining }) => {
             const { originalUrl: target } = req;
-            const sharing = qs.parse(qs.extract(req.originalUrl), { sort: false }).sharing === "true";
-            const docAccess = target.startsWith("/doc/");
+            const sharing = qs.parse(qs.extract(req.originalUrl), { sort: false }).sharing === 'true';
+            const docAccess = target.startsWith('/doc/');
             // since this is the public handler, there's no meaning of '/home' to speak of
             // since there's no user logged in, so the only viable operation
             // for a guest is to look at a shared document
-            if (sharing && docAccess) {
+            if (docAccess) {
                 serve({ req, res, ...remaining });
             } else {
-                res.redirect("/login");
+                res.redirect('/login');
             }
-        }
+        },
     });
 
     logRegistrationOutcome();
 }
 
-
 /**
  * This function can be used in two different ways. If not in release mode,
  * this is simply the logic that is invoked to start the server. In release mode,
@@ -174,9 +162,9 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
  */
 export async function launchServer() {
     await log_execution({
-        startMessage: "\nstarting execution of preliminary functions",
-        endMessage: "completed preliminary functions\n",
-        action: preliminaryFunctions
+        startMessage: '\nstarting execution of preliminary functions',
+        endMessage: 'completed preliminary functions\n',
+        action: preliminaryFunctions,
     });
     await initializeServer(routeSetter);
 }
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index 1b7f5919f..9b91a35a6 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -1,24 +1,24 @@
-import * as express from "express";
-import { blue, green } from "colors";
-import { createServer, Server } from "https";
-import { networkInterfaces } from "os";
+import { blue } from 'colors';
+import * as express from 'express';
+import { createServer, Server } from 'https';
+import { networkInterfaces } from 'os';
 import * as sio from 'socket.io';
-import { Socket } from "socket.io";
-import { Utils } from "../Utils";
+import { Socket } from 'socket.io';
+import { Opt } from '../fields/Doc';
+import { Utils } from '../Utils';
 import { logPort } from './ActionUtilities';
-import { timeMap } from "./ApiManagers/UserManager";
-import { GoogleCredentialsLoader, SSL } from "./apis/google/CredentialsLoader";
-import YoutubeApi from "./apis/youtube/youtubeApiSample";
-import { Client } from "./Client";
-import { Database } from "./database";
-import { DocumentsCollection } from "./IDatabase";
-import { Diff, GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, Transferable, Types, UpdateMobileInkOverlayPositionContent, YoutubeQueryInput, YoutubeQueryTypes } from "./Message";
-import { Search } from "./Search";
+import { timeMap } from './ApiManagers/UserManager';
+import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader';
+import YoutubeApi from './apis/youtube/youtubeApiSample';
+import { initializeGuest } from './authentication/DashUserModel';
+import { Client } from './Client';
+import { Database } from './database';
+import { DocumentsCollection } from './IDatabase';
+import { Diff, GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, Transferable, Types, UpdateMobileInkOverlayPositionContent, YoutubeQueryInput, YoutubeQueryTypes } from './Message';
+import { Search } from './Search';
 import { resolvedPorts } from './server_Initialization';
-import { Opt } from "../fields/Doc";
 
 export namespace WebSocket {
-
     export let _socket: Socket;
     const clients: { [key: string]: Client } = {};
     export const socketMap = new Map<SocketIO.Socket, string>();
@@ -32,14 +32,14 @@ export namespace WebSocket {
                 resolvedPorts.socket = Number(socketPort);
             }
             let socketEndpoint: Opt<Server>;
-            await new Promise<void>(resolve => socketEndpoint = createServer(SSL.Credentials, app).listen(resolvedPorts.socket, resolve));
+            await new Promise<void>(resolve => (socketEndpoint = createServer(SSL.Credentials, app).listen(resolvedPorts.socket, resolve)));
             io = sio(socketEndpoint!, SSL.Credentials as any);
         } else {
             io = sio().listen(resolvedPorts.socket);
         }
-        logPort("websocket", resolvedPorts.socket);
+        logPort('websocket', resolvedPorts.socket);
 
-        io.on("connection", function (socket: Socket) {
+        io.on('connection', function (socket: Socket) {
             _socket = socket;
             socket.use((_packet, next) => {
                 const userEmail = socketMap.get(socket);
@@ -70,14 +70,14 @@ export namespace WebSocket {
                     socket.join(room);
                     console.log('Client ID ' + socket.id + ' created room ' + room);
                     socket.emit('created', room, socket.id);
-
                 } else if (numClients === 1) {
                     console.log('Client ID ' + socket.id + ' joined room ' + room);
                     socket.in(room).emit('join', room);
                     socket.join(room);
                     socket.emit('joined', room, socket.id);
                     socket.in(room).emit('ready');
-                } else { // max two clients
+                } else {
+                    // max two clients
                     socket.emit('full', room);
                 }
             });
@@ -97,10 +97,10 @@ export namespace WebSocket {
                 console.log('received bye');
             });
 
-            Utils.Emit(socket, MessageStore.Foo, "handshooken");
+            Utils.Emit(socket, MessageStore.Foo, 'handshooken');
 
             Utils.AddServerHandler(socket, MessageStore.Bar, guid => barReceived(socket, guid));
-            Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args));
+            Utils.AddServerHandler(socket, MessageStore.SetField, args => setField(socket, args));
             Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField);
             Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields);
             if (isRelease) {
@@ -126,26 +126,26 @@ export namespace WebSocket {
              */
 
             disconnect = () => {
-                socket.broadcast.emit("connection_terminated", Date.now());
+                socket.broadcast.emit('connection_terminated', Date.now());
                 socket.disconnect(true);
             };
         });
     }
 
     function processGesturePoints(socket: Socket, content: GestureContent) {
-        socket.broadcast.emit("receiveGesturePoints", content);
+        socket.broadcast.emit('receiveGesturePoints', content);
     }
 
     function processOverlayTrigger(socket: Socket, content: MobileInkOverlayContent) {
-        socket.broadcast.emit("receiveOverlayTrigger", content);
+        socket.broadcast.emit('receiveOverlayTrigger', content);
     }
 
     function processUpdateOverlayPosition(socket: Socket, content: UpdateMobileInkOverlayPositionContent) {
-        socket.broadcast.emit("receiveUpdateOverlayPosition", content);
+        socket.broadcast.emit('receiveUpdateOverlayPosition', content);
     }
 
     function processMobileDocumentUpload(socket: Socket, content: MobileDocumentUploadContent) {
-        socket.broadcast.emit("receiveMobileDocumentUpload", content);
+        socket.broadcast.emit('receiveMobileDocumentUpload', content);
     }
 
     function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any[]) => void]) {
@@ -165,27 +165,22 @@ export namespace WebSocket {
         const target: string[] = [];
         onlyFields && target.push(DocumentsCollection);
         await Database.Instance.dropSchema(...target);
-        if (process.env.DISABLE_SEARCH !== "true") {
+        if (process.env.DISABLE_SEARCH !== 'true') {
             await Search.clear();
         }
+        initializeGuest();
     }
 
     function barReceived(socket: SocketIO.Socket, userEmail: string) {
         clients[userEmail] = new Client(userEmail.toString());
         const currentdate = new Date();
-        const datetime = currentdate.getDate() + "/"
-            + (currentdate.getMonth() + 1) + "/"
-            + currentdate.getFullYear() + " @ "
-            + currentdate.getHours() + ":"
-            + currentdate.getMinutes() + ":"
-            + currentdate.getSeconds();
+        const datetime = currentdate.getDate() + '/' + (currentdate.getMonth() + 1) + '/' + currentdate.getFullYear() + ' @ ' + currentdate.getHours() + ':' + currentdate.getMinutes() + ':' + currentdate.getSeconds();
         console.log(blue(`user ${userEmail} has connected to the web socket at: ${datetime}`));
-        socketMap.set(socket, userEmail + " at " + datetime);
+        socketMap.set(socket, userEmail + ' at ' + datetime);
     }
 
     function getField([id, callback]: [string, (result?: Transferable) => void]) {
-        Database.Instance.getDocument(id, (result?: Transferable) =>
-            callback(result ? result : undefined));
+        Database.Instance.getDocument(id, (result?: Transferable) => callback(result ? result : undefined));
     }
 
     function getFields([ids, callback]: [string[], (result: Transferable[]) => void]) {
@@ -193,9 +188,9 @@ export namespace WebSocket {
     }
 
     function setField(socket: Socket, newValue: Transferable) {
-        Database.Instance.update(newValue.id, newValue, () =>
-            socket.broadcast.emit(MessageStore.SetField.Message, newValue));  // broadcast set value to all other clients
-        if (newValue.type === Types.Text) {  // if the newValue has sring type, then it's suitable for searching -- pass it to SOLR
+        Database.Instance.update(newValue.id, newValue, () => socket.broadcast.emit(MessageStore.SetField.Message, newValue)); // broadcast set value to all other clients
+        if (newValue.type === Types.Text) {
+            // if the newValue has sring type, then it's suitable for searching -- pass it to SOLR
             Search.updateDocument({ id: newValue.id, data: { set: (newValue as any).data } });
         }
     }
@@ -213,34 +208,36 @@ export namespace WebSocket {
         Database.Instance.getDocuments(ids, callback);
     }
 
-    const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = {
-        "number": "_n",
-        "string": "_t",
-        "boolean": "_b",
-        "image": ["_t", "url"],
-        "video": ["_t", "url"],
-        "pdf": ["_t", "url"],
-        "audio": ["_t", "url"],
-        "web": ["_t", "url"],
-        "map": ["_t", "url"],
-        "script": ["_t", value => value.script.originalScript],
-        "RichTextField": ["_t", value => value.Text],
-        "date": ["_d", value => new Date(value.date).toISOString()],
-        "proxy": ["_i", "fieldId"],
-        "list": ["_l", list => {
-            const results = [];
-            for (const value of list.fields) {
-                const term = ToSearchTerm(value);
-                if (term) {
-                    results.push(term.value);
+    const suffixMap: { [type: string]: string | [string, string | ((json: any) => any)] } = {
+        number: '_n',
+        string: '_t',
+        boolean: '_b',
+        image: ['_t', 'url'],
+        video: ['_t', 'url'],
+        pdf: ['_t', 'url'],
+        audio: ['_t', 'url'],
+        web: ['_t', 'url'],
+        map: ['_t', 'url'],
+        script: ['_t', value => value.script.originalScript],
+        RichTextField: ['_t', value => value.Text],
+        date: ['_d', value => new Date(value.date).toISOString()],
+        proxy: ['_i', 'fieldId'],
+        list: [
+            '_l',
+            list => {
+                const results = [];
+                for (const value of list.fields) {
+                    const term = ToSearchTerm(value);
+                    if (term) {
+                        results.push(term.value);
+                    }
                 }
-            }
-            return results.length ? results : null;
-        }]
+                return results.length ? results : null;
+            },
+        ],
     };
 
-    function ToSearchTerm(val: any): { suffix: string, value: any } | undefined {
-
+    function ToSearchTerm(val: any): { suffix: string; value: any } | undefined {
         if (val === null || val === undefined) {
             return;
         }
@@ -252,69 +249,79 @@ export namespace WebSocket {
         }
         if (Array.isArray(suffix)) {
             const accessor = suffix[1];
-            if (typeof accessor === "function") {
+            if (typeof accessor === 'function') {
                 val = accessor(val);
             } else {
                 val = val[accessor];
-
             }
             suffix = suffix[0];
-
         }
         return { suffix, value: val };
     }
 
     function getSuffix(value: string | [string, any]): string {
-        return typeof value === "string" ? value : value[0];
+        return typeof value === 'string' ? value : value[0];
     }
 
     function addToListField(socket: Socket, diff: Diff, curListItems?: Transferable): void {
-        diff.diff.$set = diff.diff.$addToSet; delete diff.diff.$addToSet;// convert add to set to a query of the current fields, and then a set of the composition of the new fields with the old ones
+        diff.diff.$set = diff.diff.$addToSet;
+        delete diff.diff.$addToSet; // convert add to set to a query of the current fields, and then a set of the composition of the new fields with the old ones
         const updatefield = Array.from(Object.keys(diff.diff.$set))[0];
         const newListItems = diff.diff.$set[updatefield]?.fields;
         if (!newListItems) {
-            console.log("Error: addToListField - no new list items");
+            console.log('Error: addToListField - no new list items');
             return;
         }
-        const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields.filter((item: any) => item !== undefined) || [];
-        diff.diff.$set[updatefield].fields = [...curList, ...newListItems];//, ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))];
+        const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((item: any) => item !== undefined) || [];
+        diff.diff.$set[updatefield].fields = [...curList, ...newListItems]; //, ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))];
         const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length;
         delete diff.diff.length;
-        Database.Instance.update(diff.id, diff.diff,
+        Database.Instance.update(
+            diff.id,
+            diff.diff,
             () => {
                 if (sendBack) {
-                    console.log("Warning: list modified during update. Composite list is being returned.");
+                    console.log('Warning: list modified during update. Composite list is being returned.');
                     const id = socket.id;
-                    socket.id = "";
+                    socket.id = '';
                     socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
                     socket.id = id;
                 } else socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
                 dispatchNextOp(diff.id);
-            }, false);
+            },
+            false
+        );
     }
 
     function remFromListField(socket: Socket, diff: Diff, curListItems?: Transferable): void {
-        diff.diff.$set = diff.diff.$remFromSet; delete diff.diff.$remFromSet;
+        diff.diff.$set = diff.diff.$remFromSet;
+        delete diff.diff.$remFromSet;
         const updatefield = Array.from(Object.keys(diff.diff.$set))[0];
         const remListItems = diff.diff.$set[updatefield].fields;
-        const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields.filter((f: any) => f !== null) || [];
-        diff.diff.$set[updatefield].fields = curList?.filter((curItem: any) => !remListItems.some((remItem: any) => remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : remItem === curItem));
+        const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((f: any) => f !== null) || [];
+        diff.diff.$set[updatefield].fields = curList?.filter(
+            (curItem: any) => !remListItems.some((remItem: any) => (remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : remItem === curItem))
+        );
         const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length;
         delete diff.diff.length;
-        Database.Instance.update(diff.id, diff.diff,
+        Database.Instance.update(
+            diff.id,
+            diff.diff,
             () => {
                 if (sendBack) {
-                    console.log("SEND BACK");
+                    console.log('SEND BACK');
                     const id = socket.id;
-                    socket.id = "";
+                    socket.id = '';
                     socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
                     socket.id = id;
                 } else socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
                 dispatchNextOp(diff.id);
-            }, false);
+            },
+            false
+        );
     }
 
-    const pendingOps = new Map<string, { diff: Diff, socket: Socket }[]>();
+    const pendingOps = new Map<string, { diff: Diff; socket: Socket }[]>();
 
     function dispatchNextOp(id: string) {
         const next = pendingOps.get(id)!.shift();
@@ -341,7 +348,7 @@ export namespace WebSocket {
     function UpdateField(socket: Socket, diff: Diff) {
         if (CurUser !== socketMap.get(socket)) {
             CurUser = socketMap.get(socket);
-            console.log("Switch User: " + CurUser);
+            console.log('Switch User: ' + CurUser);
         }
         if (pendingOps.has(diff.id)) {
             pendingOps.get(diff.id)!.push({ diff, socket });
@@ -357,24 +364,25 @@ export namespace WebSocket {
         return GetRefFieldLocal([diff.id, (result?: Transferable) => SetField(socket, diff, result)]);
     }
     function SetField(socket: Socket, diff: Diff, curListItems?: Transferable) {
-        Database.Instance.update(diff.id, diff.diff,
-            () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false);
+        Database.Instance.update(diff.id, diff.diff, () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false);
         const docfield = diff.diff.$set || diff.diff.$unset;
         if (docfield) {
             const update: any = { id: diff.id };
             let dynfield = false;
             for (let key in docfield) {
-                if (!key.startsWith("fields.")) continue;
+                if (!key.startsWith('fields.')) continue;
                 dynfield = true;
                 const val = docfield[key];
                 key = key.substring(7);
-                Object.values(suffixMap).forEach(suf => { update[key + getSuffix(suf)] = { set: null }; });
+                Object.values(suffixMap).forEach(suf => {
+                    update[key + getSuffix(suf)] = { set: null };
+                });
                 const term = ToSearchTerm(val);
                 if (term !== undefined) {
                     const { suffix, value } = term;
                     update[key + suffix] = { set: value };
                     if (key.endsWith('lastModified')) {
-                        update["lastModified" + suffix] = value;
+                        update['lastModified' + suffix] = value;
                     }
                 }
             }
@@ -403,6 +411,4 @@ export namespace WebSocket {
     function CreateField(newValue: any) {
         Database.Instance.insert(newValue);
     }
-
 }
-
diff --git a/views/login.pug b/views/login.pug
index 98816e9c8..71c6933be 100644
--- a/views/login.pug
+++ b/views/login.pug
@@ -15,10 +15,10 @@ block content
                 h3.auth_header Log In
                 .form-group
                     .col-sm-7
-                    input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus, required)
+                    input.form-control(type='email', name='email', id='email', placeholder='Email (or blank for "guest")', autofocus)
                 .form-group
                     .col-sm-7
-                    input.form-control(type='password', name='password', id='password', placeholder='Password', required)
+                    input.form-control(type='password', name='password', id='password', placeholder='Password')
                 .form-group
                     .col-sm-offset-3.col-sm-7
                     button.btn.btn-success(id='submit', type='submit')
-- 
cgit v1.2.3-70-g09d2


From e48286293fb37e8af5c448c65c1926e813e8ee1f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 3 Aug 2022 09:27:21 -0400
Subject: from last

---
 src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx | 6 +++---
 src/client/views/nodes/trails/PresBox.tsx          | 1 -
 src/client/views/nodes/trails/PresElementBox.tsx   | 1 -
 3 files changed, 3 insertions(+), 5 deletions(-)

(limited to 'src/client/views/nodes/trails/PresBox.tsx')

diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index 697f8f47f..00bedafbe 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -42,9 +42,9 @@ export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & Vi
     };
 
     _stack: CollectionStackingView | CollectionNoteTakingView | null | undefined;
-    childFitWidth = (doc:Doc) => doc.type === DocumentType.RTF;
-    addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.AddDocToList(this.props.place, "data", d), true as boolean);
-    removeDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.RemoveDocFromList(this.props.place, "data", d), true as boolean);
+    childFitWidth = (doc: Doc) => doc.type === DocumentType.RTF;
+    addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.AddDocToList(this.props.place, 'data', d), true as boolean);
+    removeDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.RemoveDocFromList(this.props.place, 'data', d), true as boolean);
     render() {
         return (
             <InfoWindow anchor={this.props.markerMap[this.props.place[Id]]} onCloseClick={this.handleInfoWindowClose}>
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index dc01d8305..6e5eb3300 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -144,7 +144,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return Cast(this.activeItem?.presentationTargetDoc, Doc, null);
     }
     @computed get scrollable(): boolean {
-      //TODO: likely do NOT have to update this for note-taking view, but still worth putting here
         if (this.targetDoc.type === DocumentType.PDF || this.targetDoc.type === DocumentType.WEB || this.targetDoc.type === DocumentType.RTF || this.targetDoc._viewType === CollectionViewType.Stacking) return true;
         else return false;
     }
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 5ac506f21..0cf15d297 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -202,7 +202,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     /**
      * Function to drag and drop the pres element to a diferent location
      */
-    //TODO: this is what you need to look into
     startDrag = (e: PointerEvent) => {
         const miniView: boolean = this.toolbarWidth <= 100;
         const activeItem = this.rootDoc;
-- 
cgit v1.2.3-70-g09d2


From 513dcaa2d8c86f1cb5236ce89062cace6f418d1b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 10 Aug 2022 14:52:04 -0400
Subject: cleaned up pinning documents with an activeFrame index (and special
 case of isInkMask).  removed text-color from pres boxes which was breaking
 opacity when docs had an appearFrame > 0.

---
 src/client/views/DocumentButtonBar.tsx             |  18 +-
 src/client/views/collections/CollectionMenu.tsx    |  25 ++-
 src/client/views/collections/TabDocView.tsx        |  15 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 208 ++++++++++++---------
 src/client/views/nodes/DocumentView.tsx            |   4 +-
 src/client/views/nodes/trails/PresBox.tsx          |  23 +--
 6 files changed, 163 insertions(+), 130 deletions(-)

(limited to 'src/client/views/nodes/trails/PresBox.tsx')

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 1f8550ad6..40fc8dae6 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -5,7 +5,7 @@ import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc } from '../../fields/Doc';
 import { RichTextField } from '../../fields/RichTextField';
-import { Cast, NumCast } from '../../fields/Types';
+import { Cast, DocCast, NumCast } from '../../fields/Types';
 import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
@@ -238,15 +238,13 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                 <div
                     className="documentButtonBar-icon"
                     style={{ color: 'white' }}
-                    onClick={e =>
-                        TabDocView.PinDoc(
-                            this.props
-                                .views()
-                                .filter(v => v)
-                                .map(dv => dv!.rootDoc),
-                            { pinDocView: true }
-                        )
-                    }>
+                    onClick={e => {
+                        const docs = this.props
+                            .views()
+                            .filter(v => v)
+                            .map(dv => dv!.rootDoc);
+                        TabDocView.PinDoc(docs, { pinDocView: true, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
+                    }}>
                     <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
                 </div>
             </Tooltip>
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index cfbcec2d6..8432619de 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -769,6 +769,21 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
         return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any) ? true : false;
     }
 
+    public static gotoKeyFrame(doc: Doc, newFrame: number) {
+        if (!doc) {
+            return;
+        }
+        const dataField = doc[Doc.LayoutFieldKey(doc)];
+        const childDocs = DocListCast(dataField);
+        const currentFrame = Cast(doc._currentFrame, 'number', null);
+        if (currentFrame === undefined) {
+            doc._currentFrame = 0;
+            CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
+        }
+        CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0);
+        doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
+    }
+
     @undoBatch
     @action
     nextKeyframe = (): void => {
@@ -1569,13 +1584,5 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
     }
 }
 ScriptingGlobals.add(function gotoFrame(doc: any, newFrame: any) {
-    const dataField = doc[Doc.LayoutFieldKey(doc)];
-    const childDocs = DocListCast(dataField);
-    const currentFrame = Cast(doc._currentFrame, 'number', null);
-    if (currentFrame === undefined) {
-        doc._currentFrame = 0;
-        CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
-    }
-    CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0);
-    doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
+    CollectionFreeFormViewChrome.gotoKeyFrame(doc, newFrame);
 });
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index ec291e72c..f30faab79 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -288,14 +288,15 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     pinDoc.presEndTime = NumCast(doc.clipEnd, duration);
                 }
                 //save position
-                if (pinProps?.setPosition || pinDoc.isInkMask) {
-                    pinDoc.setPosition = true;
-                    pinDoc.y = doc.y;
-                    pinDoc.x = doc.x;
-                    pinDoc.presHideAfter = true;
-                    pinDoc.presHideBefore = true;
+                if (pinProps?.activeFrame !== undefined) {
+                    pinDoc.presActiveFrame = pinProps?.activeFrame;
                     pinDoc.title = doc.title + ' (move)';
-                    pinDoc.presMovement = PresMovement.None;
+                    pinDoc.presMovement = PresMovement.Pan;
+                    if (pinDoc.isInkMask) {
+                        pinDoc.presHideAfter = true;
+                        pinDoc.presHideBefore = true;
+                        pinDoc.presMovement = PresMovement.None;
+                    }
                 }
                 if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
                 PresBox.Instance?._selectedArray.clear();
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 284584a3d..e154e8445 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,26 +1,26 @@
-import { action, computed, observable, trace } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, Opt } from "../../../fields/Doc";
-import { List } from "../../../fields/List";
-import { listSpec } from "../../../fields/Schema";
-import { ComputedField } from "../../../fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { TraceMobx } from "../../../fields/util";
-import { DashColor, numberRange, OmitKeys } from "../../../Utils";
-import { DocumentManager } from "../../util/DocumentManager";
-import { SelectionManager } from "../../util/SelectionManager";
-import { Transform } from "../../util/Transform";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
-import { DocComponent } from "../DocComponent";
-import { InkingStroke } from "../InkingStroke";
-import { StyleProp } from "../StyleProvider";
-import "./CollectionFreeFormDocumentView.scss";
-import { DocumentView, DocumentViewProps } from "./DocumentView";
-import React = require("react");
+import { action, computed, observable, trace } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, Opt } from '../../../fields/Doc';
+import { List } from '../../../fields/List';
+import { listSpec } from '../../../fields/Schema';
+import { ComputedField } from '../../../fields/ScriptField';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { TraceMobx } from '../../../fields/util';
+import { DashColor, numberRange, OmitKeys } from '../../../Utils';
+import { DocumentManager } from '../../util/DocumentManager';
+import { SelectionManager } from '../../util/SelectionManager';
+import { Transform } from '../../util/Transform';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
+import { DocComponent } from '../DocComponent';
+import { InkingStroke } from '../InkingStroke';
+import { StyleProp } from '../StyleProvider';
+import './CollectionFreeFormDocumentView.scss';
+import { DocumentView, DocumentViewProps } from './DocumentView';
+import React = require('react');
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
-    dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined;
-    sizeProvider?: (doc: Doc, replica: string) => { width: number, height: number } | undefined;
+    dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined;
+    sizeProvider?: (doc: Doc, replica: string) => { width: number; height: number } | undefined;
     renderCutoffProvider: (doc: Doc) => boolean;
     zIndex?: number;
     highlight?: boolean;
@@ -32,29 +32,51 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
 
 @observer
 export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps>() {
-    public static animFields = ["_height", "_width", "x", "y", "_scrollTop", "opacity"];  // fields that are configured to be animatable using animation frames
+    public static animFields = ['_height', '_width', 'x', 'y', '_scrollTop', 'opacity']; // fields that are configured to be animatable using animation frames
     @observable _animPos: number[] | undefined = undefined;
     @observable _contentView: DocumentView | undefined | null;
-    get displayName() { return "CollectionFreeFormDocumentView(" + this.rootDoc.title + ")"; } // this makes mobx trace() statements more descriptive
-    get maskCentering() { return this.props.Document.isInkMask ? InkingStroke.MaskDim / 2 : 0; }
-    get transform() { return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`; }
-    get X() { return this.dataProvider ? this.dataProvider.x : NumCast(this.Document.x); }
-    get Y() { return this.dataProvider ? this.dataProvider.y : NumCast(this.Document.y); }
-    get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : NumCast(this.Document.zIndex); }
-    get Opacity() { return this.dataProvider ? this.dataProvider.opacity : undefined; }
-    get Highlight() { return this.dataProvider?.highlight; }
-    @computed get ShowTitle() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as (Opt<string>); }
-    @computed get dataProvider() { return this.props.dataProvider?.(this.props.Document, this.props.replica); }
-    @computed get sizeProvider() { return this.props.sizeProvider?.(this.props.Document, this.props.replica); }
+    get displayName() {
+        return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')';
+    } // this makes mobx trace() statements more descriptive
+    get maskCentering() {
+        return this.props.Document.isInkMask ? InkingStroke.MaskDim / 2 : 0;
+    }
+    get transform() {
+        return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`;
+    }
+    get X() {
+        return this.dataProvider ? this.dataProvider.x : NumCast(this.Document.x);
+    }
+    get Y() {
+        return this.dataProvider ? this.dataProvider.y : NumCast(this.Document.y);
+    }
+    get ZInd() {
+        return this.dataProvider ? this.dataProvider.zIndex : NumCast(this.Document.zIndex);
+    }
+    get Opacity() {
+        return this.dataProvider ? this.dataProvider.opacity : undefined;
+    }
+    get Highlight() {
+        return this.dataProvider?.highlight;
+    }
+    @computed get ShowTitle() {
+        return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as Opt<string>;
+    }
+    @computed get dataProvider() {
+        return this.props.dataProvider?.(this.props.Document, this.props.replica);
+    }
+    @computed get sizeProvider() {
+        return this.props.sizeProvider?.(this.props.Document, this.props.replica);
+    }
 
     styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
         if (property === StyleProp.Opacity && doc === this.layoutDoc) return this.Opacity; // only change the opacity for this specific document, not its children
         return this.props.styleProvider?.(doc, props, property);
-    }
+    };
 
     public static getValues(doc: Doc, time: number) {
         return CollectionFreeFormDocumentView.animFields.reduce((p, val) => {
-            p[val] = Cast(`${val}-indexed`, listSpec("number"), [NumCast(doc[val])]).reduce((p, v, i) => (i <= Math.round(time) && v !== undefined) || p === undefined ? v : p, undefined as any as number);
+            p[val] = Cast(`${val}-indexed`, listSpec('number'), [NumCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number);
             return p;
         }, {} as { [val: string]: Opt<number> });
     }
@@ -62,35 +84,43 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     public static setValues(time: number, d: Doc, vals: { [val: string]: Opt<number> }) {
         const timecode = Math.round(time);
         Object.keys(vals).forEach(val => {
-            const findexed = Cast(d[`${val}-indexed`], listSpec("number"), []).slice();
+            const findexed = Cast(d[`${val}-indexed`], listSpec('number'), []).slice();
             findexed[timecode] = vals[val] as any as number;
             d[`${val}-indexed`] = new List<number>(findexed);
         });
-        d.appearFrame && (d["text-color"] =
-            d.appearFrame === timecode + 1 ? "red" :
-                d.appearFrame < timecode + 1 ? "grey" : "black");
     }
 
     public static updateKeyframe(docs: Doc[], time: number, targetDoc?: Doc) {
         const timecode = Math.round(time);
-        docs.forEach(action(doc => {
-            doc._viewTransition = doc.dataTransition = "all 1s";
-            doc["text-color"] =
-                !doc.appearFrame || !targetDoc ? "black" :
-                    doc.appearFrame === timecode + 1 ? StrCast(targetDoc["pres-text-color"]) :
-                        doc.appearFrame < timecode + 1 ? StrCast(targetDoc["pres-text-viewed-color"]) :
-                            "black";
-            CollectionFreeFormDocumentView.animFields.forEach(val => {
-                const findexed = Cast(doc[`${val}-indexed`], listSpec("number"), null);
-                findexed?.length <= timecode + 1 && findexed.push(undefined as any as number);
-            });
-        }));
-        setTimeout(() => docs.forEach(doc => { doc._viewTransition = undefined; doc.dataTransition = "inherit"; }), 1010);
+        docs.forEach(
+            action(doc => {
+                doc._viewTransition = doc.dataTransition = 'all 1s';
+                CollectionFreeFormDocumentView.animFields.forEach(val => {
+                    const findexed = Cast(doc[`${val}-indexed`], listSpec('number'), null);
+                    findexed?.length <= timecode + 1 && findexed.push(undefined as any as number);
+                });
+            })
+        );
+        setTimeout(
+            () =>
+                docs.forEach(doc => {
+                    doc._viewTransition = undefined;
+                    doc.dataTransition = 'inherit';
+                }),
+            1010
+        );
     }
 
     public static gotoKeyframe(docs: Doc[]) {
-        docs.forEach(doc => doc._viewTransition = doc.dataTransition = "all 1s");
-        setTimeout(() => docs.forEach(doc => { doc._viewTransition = undefined; doc.dataTransition = "inherit"; }), 1010);
+        docs.forEach(doc => (doc._viewTransition = doc.dataTransition = 'all 1s'));
+        setTimeout(
+            () =>
+                docs.forEach(doc => {
+                    doc._viewTransition = undefined;
+                    doc.dataTransition = 'inherit';
+                }),
+            1010
+        );
     }
 
     public static setupZoom(doc: Doc, targDoc: Doc) {
@@ -102,21 +132,22 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         height.push(NumCast(targDoc._height));
         top.push(NumCast(targDoc._height) / -2);
         left.push(NumCast(targDoc._width) / -2);
-        doc["viewfinder-width-indexed"] = width;
-        doc["viewfinder-height-indexed"] = height;
-        doc["viewfinder-top-indexed"] = top;
-        doc["viewfinder-left-indexed"] = left;
+        doc['viewfinder-width-indexed'] = width;
+        doc['viewfinder-height-indexed'] = height;
+        doc['viewfinder-top-indexed'] = top;
+        doc['viewfinder-left-indexed'] = left;
     }
 
     public static setupKeyframes(docs: Doc[], currTimecode: number, makeAppear: boolean = false) {
         docs.forEach(doc => {
             if (doc.appearFrame === undefined) doc.appearFrame = currTimecode;
-            if (!doc["opacity-indexed"]) { // opacity is unlike other fields because it's value should not be undefined before it appears to enable it to fade-in
-                doc["opacity-indexed"] = new List<number>(numberRange(currTimecode + 1).map(t => !doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1));
+            if (!doc['opacity-indexed']) {
+                // opacity is unlike other fields because it's value should not be undefined before it appears to enable it to fade-in
+                doc['opacity-indexed'] = new List<number>(numberRange(currTimecode + 1).map(t => (!doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1)));
             }
-            CollectionFreeFormDocumentView.animFields.forEach(val => doc[val] = ComputedField.MakeInterpolated(val, "activeFrame", doc, currTimecode));
-            doc.activeFrame = ComputedField.MakeFunction("self.context?._currentFrame||0");
-            doc.dataTransition = "inherit";
+            CollectionFreeFormDocumentView.animFields.forEach(val => (doc[val] = ComputedField.MakeInterpolated(val, 'activeFrame', doc, currTimecode)));
+            doc.activeFrame = ComputedField.MakeFunction('self.context?._currentFrame||0');
+            doc.dataTransition = 'inherit';
         });
     }
 
@@ -131,7 +162,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 topDoc.x = spt[0];
                 topDoc.y = spt[1];
                 this.props.removeDocument?.(topDoc);
-                this.props.addDocTab(topDoc, "inParent");
+                this.props.addDocTab(topDoc, 'inParent');
             } else {
                 const spt = this.screenToLocalTransform().inverse().transformPoint(0, 0);
                 const fpt = screenXf.transformPoint(spt[0], spt[1]);
@@ -141,14 +172,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             }
             setTimeout(() => SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(topDoc, container)!, false), 0);
         }
-    }
+    };
 
     nudge = (x: number, y: number) => {
         this.props.Document.x = NumCast(this.props.Document.x) + x;
         this.props.Document.y = NumCast(this.props.Document.y) + y;
-    }
-    panelWidth = () => (this.sizeProvider?.width || this.props.PanelWidth?.());
-    panelHeight = () => (this.sizeProvider?.height || this.props.PanelHeight?.());
+    };
+    panelWidth = () => this.sizeProvider?.width || this.props.PanelWidth?.();
+    panelHeight = () => this.sizeProvider?.height || this.props.PanelHeight?.();
     screenToLocalTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y);
     focusDoc = (doc: Doc) => this.props.focus(doc);
     returnThis = () => this;
@@ -163,24 +194,27 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             PanelHeight: this.panelHeight,
         };
         const background = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const mixBlendMode = StrCast(this.layoutDoc.mixBlendMode) as any || (typeof background === "string" && background && (!background.startsWith("linear") && DashColor(background).alpha() !== 1) ? "multiply" : undefined);
-        return <div className={"collectionFreeFormDocumentView-container"}
-            style={{
-                outline: this.Highlight ? "orange solid 2px" : "",
-                width: this.panelWidth(),
-                height: this.panelHeight(),
-                transform: this.transform,
-                transformOrigin: '50% 50%',
-                transition: this.dataProvider?.transition ?? (this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition)),
-                zIndex: this.ZInd,
-                mixBlendMode: mixBlendMode,
-                display: this.ZInd === -99 ? "none" : undefined
-            }} >
-            {this.props.renderCutoffProvider(this.props.Document) ?
-                <div style={{ position: "absolute", width: this.panelWidth(), height: this.panelHeight(), background: "lightGreen" }} />
-                :
-                <DocumentView {...divProps} ref={action((r: DocumentView | null) => this._contentView = r)} />
-            }
-        </div>;
+        const mixBlendMode = (StrCast(this.layoutDoc.mixBlendMode) as any) || (typeof background === 'string' && background && !background.startsWith('linear') && DashColor(background).alpha() !== 1 ? 'multiply' : undefined);
+        return (
+            <div
+                className={'collectionFreeFormDocumentView-container'}
+                style={{
+                    outline: this.Highlight ? 'orange solid 2px' : '',
+                    width: this.panelWidth(),
+                    height: this.panelHeight(),
+                    transform: this.transform,
+                    transformOrigin: '50% 50%',
+                    transition: this.dataProvider?.transition ?? (this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition)),
+                    zIndex: this.ZInd,
+                    mixBlendMode: mixBlendMode,
+                    display: this.ZInd === -99 ? 'none' : undefined,
+                }}>
+                {this.props.renderCutoffProvider(this.props.Document) ? (
+                    <div style={{ position: 'absolute', width: this.panelWidth(), height: this.panelHeight(), background: 'lightGreen' }} />
+                ) : (
+                    <DocumentView {...divProps} ref={action((r: DocumentView | null) => (this._contentView = r))} />
+                )}
+            </div>
+        );
     }
 }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f87581875..f9ef85595 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1640,8 +1640,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     render() {
         TraceMobx();
-        const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
-        const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
+        const xshift = () => (this.props.Document.isInkMask && !this.props.LayoutTemplateString && !this.props.LayoutTemplate?.() ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
+        const yshift = () => (this.props.Document.isInkMask && !this.props.LayoutTemplateString && !this.props.LayoutTemplate?.() ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
         const isPresTreeElement: boolean = this.props.treeViewDoc?.type === DocumentType.PRES;
         const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
         return (
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 6e5eb3300..3bbdce1e4 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -29,10 +29,11 @@ import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentVie
 import { FieldView, FieldViewProps } from '../FieldView';
 import './PresBox.scss';
 import { PresEffect, PresMovement, PresStatus } from './PresEnums';
+import { CollectionFreeFormViewChrome } from '../../collections/CollectionMenu';
 
 export interface PinProps {
     audioRange?: boolean;
-    setPosition?: boolean;
+    activeFrame?: number;
     hidePresBox?: boolean;
     pinWithView?: PinViewProps;
     pinDocView?: boolean; // whether the current view specs of the document should be saved the pinned document
@@ -509,7 +510,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.childDocs.forEach((doc, index) => {
             const curDoc = Cast(doc, Doc, null);
             const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null);
-            if (tagDoc) tagDoc.opacity = 1;
+            //if (tagDoc) tagDoc.opacity = 1;
             const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, tagDoc);
             const curInd: number = itemIndexes.indexOf(index);
             if (tagDoc === this.layoutDoc.presCollection) {
@@ -519,7 +520,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 } else if (curDoc.presHideBefore) {
                     if (index > this.itemIndex) {
                         tagDoc.opacity = 0;
-                    } else if (!curDoc.presHideAfter) {
+                    } else if (index === this.itemIndex || !curDoc.presHideAfter) {
                         tagDoc.opacity = 1;
                     }
                 }
@@ -527,7 +528,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 } else if (curDoc.presHideAfter) {
                     if (index < this.itemIndex) {
                         tagDoc.opacity = 0;
-                    } else if (!curDoc.presHideBefore) {
+                    } else if (index === this.itemIndex || !curDoc.presHideBefore) {
                         tagDoc.opacity = 1;
                     }
                 }
@@ -821,17 +822,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0);
         else this.updateCurrentPresentation(context);
 
-        if (this.activeItem.setPosition && this.activeItem.y !== undefined && this.activeItem.x !== undefined && this.targetDoc.x !== undefined && this.targetDoc.y !== undefined) {
-            const timer = (ms: number) => new Promise(res => (this._presTimer = setTimeout(res, ms)));
-            const time = 10;
-            const ydiff = NumCast(this.activeItem.y) - NumCast(this.targetDoc.y);
-            const xdiff = NumCast(this.activeItem.x) - NumCast(this.targetDoc.x);
-
-            for (let i = 0; i < time; i++) {
-                this.targetDoc.x = NumCast(this.targetDoc.x) + xdiff / time;
-                this.targetDoc.y = NumCast(this.targetDoc.y) + ydiff / time;
-                await timer(0.1);
-            }
+        if (this.activeItem.presActiveFrame !== undefined) {
+            const context = DocCast(DocCast(this.activeItem.presentationTargetDoc).context);
+            context && CollectionFreeFormViewChrome.gotoKeyFrame(context, NumCast(this.activeItem.presActiveFrame));
         }
     };
 
-- 
cgit v1.2.3-70-g09d2


From 081632757af3c2ec2e4482c1e5fe710c8ee6cad8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 11 Aug 2022 19:01:37 -0400
Subject: added better ui and backend support for mask ink strokes. added frame
 #'s ui for seeing current animation frame.

---
 src/client/documents/Documents.ts                  | 19 ++++++++--
 src/client/util/CurrentUserUtils.ts                |  6 ++--
 src/client/util/DragManager.ts                     | 14 ++++----
 src/client/views/InkingStroke.tsx                  | 28 +++++++++++----
 src/client/views/MainView.tsx                      |  1 +
 src/client/views/collections/CollectionMenu.tsx    |  2 +-
 src/client/views/collections/TabDocView.tsx        | 10 +++---
 .../CollectionFreeFormLayoutEngines.tsx            |  1 +
 .../collectionFreeForm/CollectionFreeFormView.scss | 23 +++++++++----
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  9 +++--
 src/client/views/global/globalCssVariables.scss    | 13 +++----
 .../views/global/globalCssVariables.scss.d.ts      |  6 ++--
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  7 ++--
 src/client/views/nodes/DocumentView.tsx            |  6 ++--
 src/client/views/nodes/button/FontIconBox.tsx      | 34 +++++++++++++-----
 src/client/views/nodes/trails/PresBox.tsx          | 36 +++++++++++--------
 src/fields/InkField.ts                             | 40 +++++++++++-----------
 17 files changed, 162 insertions(+), 93 deletions(-)

(limited to 'src/client/views/nodes/trails/PresBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c7ea04839..cf9ed43e1 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -31,7 +31,7 @@ import { CollectionView } from '../views/collections/CollectionView';
 import { ContextMenu } from '../views/ContextMenu';
 import { ContextMenuProps } from '../views/ContextMenuItem';
 import { DFLT_IMAGE_NATIVE_DIM } from '../views/global/globalCssVariables.scss';
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke } from '../views/InkingStroke';
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke';
 import { AudioBox } from '../views/nodes/AudioBox';
 import { FontIconBox } from '../views/nodes/button/FontIconBox';
 import { ColorBox } from '../views/nodes/ColorBox';
@@ -918,9 +918,22 @@ export namespace Docs {
             return linkDoc;
         }
 
-        export function InkDocument(color: string, tool: string, strokeWidth: number, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: PointData[], options: DocumentOptions = {}) {
+        export function InkDocument(
+            color: string,
+            tool: string,
+            strokeWidth: number,
+            strokeBezier: string,
+            fillColor: string,
+            arrowStart: string,
+            arrowEnd: string,
+            dash: string,
+            points: PointData[],
+            isInkMask: boolean,
+            options: DocumentOptions = {}
+        ) {
             const I = new Doc();
             I[Initializing] = true;
+            I.isInkMask = isInkMask;
             I.type = DocumentType.INK;
             I.layout = InkingStroke.LayoutString('data');
             I.color = color;
@@ -1424,7 +1437,7 @@ export namespace DocUtils {
             created = Docs.Create.RecordingDocument(field.url.href, resolved);
             layout = RecordingBox.LayoutString;
         } else if (field instanceof InkField) {
-            created = Docs.Create.InkDocument(ActiveInkColor(), Doc.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), field.inkData, resolved);
+            created = Docs.Create.InkDocument(ActiveInkColor(), Doc.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), field.inkData, ActiveIsInkMask(), 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 d19874720..dcf4a71c8 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -645,8 +645,9 @@ export class CurrentUserUtils {
             // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} },
             { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle",                      scripts: {onClick:'{ return setActiveTool("circle", _readOnly_);}'} },
             // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveTool("square")' },
-            { title: "Line",   toolTip: "Line (Ctrl+Shift+L)",   btnType: ButtonType.ToggleButton, icon: "minus",                       scripts: {onClick: '{ return setActiveTool("line", _readOnly_);}' }},
-            { title: "Fill",   toolTip: "Fill color",            btnType: ButtonType.ColorButton,  icon: "fill-drip",ignoreClick: true, scripts: {script: "{ return setFillColor(value, _readOnly_);}"} },
+            { title: "Line",   toolTip: "Line (Ctrl+Shift+L)",   btnType: ButtonType.ToggleButton, icon: "minus",                       scripts: {onClick:'{ return setActiveTool("line", _readOnly_);}' }},
+            { title: "Mask",   toolTip: "Mask",                  btnType: ButtonType.ToggleButton, icon: "user-circle",                 scripts: {onClick:'{ return setIsInkMask(_readOnly_);}'} },
+            { title: "Fill",   toolTip: "Fill color",            btnType: ButtonType.ColorButton,  icon: "fill-drip",ignoreClick: true, scripts: {script: '{ return setFillColor(value, _readOnly_);}'} },
             { title: "Width",  toolTip: "Stroke width",          btnType: ButtonType.NumberButton,                   ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1},
             { title: "Color",  toolTip: "Stroke color",          btnType: ButtonType.ColorButton,  icon: "pen",      ignoreClick: true, scripts: {script: '{ return setStrokeColor(value, _readOnly_);}'} },
         ];
@@ -674,6 +675,7 @@ export class CurrentUserUtils {
                     CollectionViewType.Grid, CollectionViewType.NoteTaking]),
               title: "Perspective",                    toolTip: "View",                 btnType: ButtonType.DropdownList,                                                                                    ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
             { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,         funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
+            { title: "Num",     icon: "",           toolTip: "Frame Number",         btnType: ButtonType.TextButton,          funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement().currentFrame.toString()'}, width: 20,  scripts: {}},
             { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
             { title: "Fill",    icon: "fill-drip",     toolTip: "Background Fill Color",btnType: ButtonType.ColorButton,         funcs: {hidden: '!SelectionManager_selectedDocType()'},                     ignoreClick: true, width: 20,  scripts: { script:  'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected
             { title: "Header",  icon: "heading",       toolTip: "Header Color",         btnType: ButtonType.ColorButton,         funcs: {hidden: '!SelectionManager_selectedDocType()'},                     ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'}},
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index ccd94c56e..d781a87ab 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -377,14 +377,14 @@ export namespace DragManager {
             }
             const rect = ele.getBoundingClientRect();
             const scaleX = rect.width / (ele.offsetWidth || rect.width);
-            const scaleY = ele.offsetHeight ? rect.height / (ele.offsetHeight || rect.height) : scaleX;
+            const scaleY = scaleX; //ele.offsetHeight ? rect.height / (ele.offsetHeight || rect.height) : 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);
             elesCont.bottom = Math.max(rect.bottom, elesCont.bottom);
-            xs.push(rect.left);
-            ys.push(rect.top);
+            xs.push(rect.left + (options?.offsetX || 0));
+            ys.push(rect.top + (options?.offsetY || 0));
             scaleXs.push(scaleX);
             scaleYs.push(scaleY);
             Object.assign(dragElement.style, {
@@ -401,9 +401,9 @@ export namespace DragManager {
                 transformOrigin: '0 0',
                 width: `${rect.width / scaleX}px`,
                 height: `${rect.height / scaleY}px`,
-                transform: `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0)}px) scale(${scaleX}, ${scaleY})`,
+                transform: `translate(${xs[0]}px, ${ys[0]}px) scale(${scaleX}, ${scaleY})`,
             });
-            dragLabel.style.transform = `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0) - 20}px)`;
+            dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`;
 
             if (docsToDrag.length) {
                 const pdfBox = dragElement.getElementsByTagName('canvas');
@@ -543,8 +543,8 @@ export namespace DragManager {
             const moveVec = { x: x - lastPt.x, y: y - lastPt.y };
             lastPt = { x, y };
 
-            dragLabel.style.transform = `translate(${xs[0] + moveVec.x + (options?.offsetX || 0)}px, ${ys[0] + moveVec.y + (options?.offsetY || 0) - 20}px)`;
-            dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x) + (options?.offsetX || 0)}px, ${(ys[i] += moveVec.y) + (options?.offsetY || 0)}px)  scale(${scaleXs[i]}, ${scaleYs[i]})`));
+            dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x)}px, ${(ys[i] += moveVec.y)}px)  scale(${scaleXs[i]}, ${scaleYs[i]})`));
+            dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`;
         };
         const upHandler = (e: PointerEvent) => {
             clearTimeout(startWindowDragTimer);
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index e5de7a0c5..2671aea56 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -23,9 +23,9 @@
 import React = require('react');
 import { action, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, WidthSym } from '../../fields/Doc';
+import { Doc, HeightSym, WidthSym } from '../../fields/Doc';
 import { InkData, InkField, InkTool } from '../../fields/InkField';
-import { Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
 import { OmitKeys, returnFalse, setupMoveUpEvents } from '../../Utils';
 import { CognitiveServices } from '../cognitive_services/CognitiveServices';
@@ -42,12 +42,13 @@ import { InkTangentHandles } from './InkTangentHandles';
 import { DocComponentView } from './nodes/DocumentView';
 import { FieldView, FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { INK_MASK_SIZE } from './global/globalCssVariables.scss';
 import './InkStroke.scss';
 import Color = require('color');
 
 @observer
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
-    static readonly MaskDim = 50000; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big)
+    static readonly MaskDim = INK_MASK_SIZE; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big)
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(InkingStroke, fieldStr);
     }
@@ -353,9 +354,18 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const endMarker = StrCast(this.layoutDoc.strokeEndMarker);
         const markerScale = NumCast(this.layoutDoc.strokeMarkerScale, 1);
         const closed = InkingStroke.IsClosed(inkData);
-        const fillColor = StrCast(this.layoutDoc.fillColor, 'transparent');
+        const isInkMask = BoolCast(this.layoutDoc.isInkMask);
+        const fillColor = isInkMask ? '#aaaaaa' : StrCast(this.layoutDoc.fillColor, 'transparent');
         const strokeColor = !closed && fillColor && fillColor !== 'transparent' ? fillColor : StrCast(this.layoutDoc.color);
 
+        // bcz: Hack!! Not really sure why, but having fractional values for width/height of mask ink strokes causes the dragging clone (see DragManager) to be offset from where it should be.
+        if (isInkMask && (this.layoutDoc[WidthSym]() !== Math.round(this.layoutDoc[WidthSym]()) || this.layoutDoc[HeightSym]() !== Math.round(this.layoutDoc[HeightSym]()))) {
+            setTimeout(() => {
+                this.layoutDoc._width = Math.round(NumCast(this.layoutDoc[WidthSym]()));
+                this.layoutDoc._height = Math.round(NumCast(this.layoutDoc[HeightSym]()));
+            });
+        }
+
         // Visually renders the polygonal line made by the user.
         const inkLine = InteractionUtils.CreatePolyline(
             inkData,
@@ -430,7 +440,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                     className="inkStroke"
                     style={{
                         transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
-                        mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
+                        // mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
                         cursor: this.props.isSelected() ? 'default' : undefined,
                     }}
                     {...(!closed ? interactions : {})}>
@@ -467,7 +477,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                         className="inkStroke"
                         style={{
                             transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
-                            mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
+                            mixBlendMode: 'unset',
                             cursor: this.props.isSelected() ? 'default' : undefined,
                             position: 'absolute',
                         }}
@@ -489,6 +499,9 @@ export function SetActiveBezierApprox(bezier: string): void {
 export function SetActiveInkColor(value: string) {
     ActiveInkPen() && (ActiveInkPen().activeInkColor = value);
 }
+export function SetActiveIsInkMask(value: boolean) {
+    ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value);
+}
 export function SetActiveFillColor(value: string) {
     ActiveInkPen() && (ActiveInkPen().activeFillColor = value);
 }
@@ -513,6 +526,9 @@ export function ActiveInkColor(): string {
 export function ActiveFillColor(): string {
     return StrCast(ActiveInkPen()?.activeFillColor, '');
 }
+export function ActiveIsInkMask(): boolean {
+    return BoolCast(ActiveInkPen()?.activeIsInkMask, false);
+}
 export function ActiveArrowStart(): string {
     return StrCast(ActiveInkPen()?.activeArrowStart, '');
 }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index e96f65548..c166594e5 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -457,6 +457,7 @@ export class MainView extends React.Component {
                 fa.faVolumeDown,
                 fa.faSquareRootAlt,
                 fa.faVolumeMute,
+                fa.faUserCircle,
             ]
         );
         this.initAuthenticationRouters();
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 8432619de..eb55650e4 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1025,7 +1025,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
                                         <FontAwesomeIcon icon={'caret-left'} size={'lg'} />
                                     </div>
                                 </Tooltip>
-                                <Tooltip key="num" title={<div className="dash-tooltip">Toggle View All</div>} placement="bottom">
+                                <Tooltip key="num" title={<div className="dash-tooltip">Frame number</div>} placement="bottom">
                                     <div
                                         className="numKeyframe"
                                         style={{ color: this.props.docView.ComponentView?.getKeyFrameEditing?.() ? 'white' : 'black', backgroundColor: this.props.docView.ComponentView?.getKeyFrameEditing?.() ? '#5B9FDD' : '#AEDDF8' }}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index f30faab79..ff4b1e2ce 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -292,11 +292,11 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     pinDoc.presActiveFrame = pinProps?.activeFrame;
                     pinDoc.title = doc.title + ' (move)';
                     pinDoc.presMovement = PresMovement.Pan;
-                    if (pinDoc.isInkMask) {
-                        pinDoc.presHideAfter = true;
-                        pinDoc.presHideBefore = true;
-                        pinDoc.presMovement = PresMovement.None;
-                    }
+                }
+                if (pinDoc.isInkMask) {
+                    pinDoc.presHideAfter = true;
+                    pinDoc.presHideBefore = true;
+                    pinDoc.presMovement = PresMovement.None;
                 }
                 if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
                 PresBox.Instance?._selectedArray.clear();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 3d85d32a0..ee01c341b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -45,6 +45,7 @@ export interface PoolData {
 export interface ViewDefResult {
     ele: JSX.Element;
     bounds?: ViewDefBounds;
+    inkMask?: boolean;
 }
 function toLabel(target: FieldResult<Field>) {
     if (typeof target === 'number' || Number(target)) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 79e063f7f..010132aa5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,4 +1,4 @@
-@import "../../global/globalCssVariables";
+@import '../../global/globalCssVariables';
 
 .collectionfreeformview-none {
     position: inherit;
@@ -20,8 +20,19 @@
     pointer-events: none;
 }
 
+.collectionfreeformview-mask {
+    mix-blend-mode: multiply;
+    z-index: 5000;
+    width: $INK_MASK_SIZE;
+    height: $INK_MASK_SIZE;
+    transform: translate($INK_MASK_SIZE_HALF, $INK_MASK_SIZE_HALF);
+    background-color: rgba(0, 0, 0, 0.7);
+    pointer-events: none;
+    position: absolute;
+}
+
 .collectionfreeformview-viewdef {
-    >.collectionFreeFormDocumentView-container {
+    > .collectionFreeFormDocumentView-container {
         pointer-events: none;
 
         .contentFittingDocumentDocumentView-previewDoc {
@@ -210,13 +221,13 @@
         }
     }
 
-    .collectionfreeformview>.jsx-parser {
+    .collectionfreeformview > .jsx-parser {
         position: inherit;
         height: 100%;
         width: 100%;
     }
 
-    >.jsx-parser {
+    > .jsx-parser {
         z-index: 0;
     }
 
@@ -268,6 +279,6 @@
 
 .pullpane-indicator {
     z-index: 99999;
-    background-color: rgba($color: #000000, $alpha: .4);
+    background-color: rgba($color: #000000, $alpha: 0.4);
     position: absolute;
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 82b377dfa..f3074543b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -34,7 +34,7 @@ import { COLLECTION_BORDER_WIDTH } from '../../../views/global/globalCssVariable
 import { Timeline } from '../../animationtimeline/Timeline';
 import { ContextMenu } from '../../ContextMenu';
 import { GestureOverlay } from '../../GestureOverlay';
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
 import { LightboxView } from '../../LightboxView';
 import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
 import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from '../../nodes/DocumentView';
@@ -121,7 +121,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable ChildDrag: DocumentView | undefined; // child document view being dragged.  needed to update drop areas of groups when a group item is dragged.
 
     @computed get views() {
-        return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele);
+        const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask).map(ele => ele.ele);
+        const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && !ele.inkMask).map(ele => ele.ele);
+        if (viewsMask.length) renderableEles.push(<div className="collectionfreeformview-mask">{viewsMask}</div>);
+        return renderableEles;
     }
     @computed get fitToContentVals() {
         return {
@@ -579,6 +582,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     ActiveArrowEnd(),
                     ActiveDash(),
                     points,
+                    ActiveIsInkMask(),
                     {
                         title: 'ink stroke',
                         x: B.x - ActiveInkWidth() / 2,
@@ -1519,6 +1523,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 elements.push({
                     ele: this.getChildDocView(entry[1]),
                     bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica),
+                    inkMask: BoolCast(entry[1].pair.layout.isInkMask),
                 })
             );
 
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
index a14634bdc..c5968d9a7 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.scss
@@ -1,4 +1,4 @@
-@import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap");
+@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
 // colors
 $white: #ffffff;
 $off-white: #fdfdfd;
@@ -16,8 +16,10 @@ $yellow: #f5d747;
 
 $close-red: #e48282;
 
-$drop-shadow: "#32323215";
+$drop-shadow: '#32323215';
 
+$INK_MASK_SIZE: 50000;
+$INK_MASK_SIZE_HALF: -25000px;
 
 //padding
 $minimum-padding: 4px;
@@ -28,8 +30,7 @@ $large-padding: 32px;
 $icon-size: 28px;
 
 // fonts
-$sans-serif: "Roboto",
-sans-serif;
+$sans-serif: 'Roboto', sans-serif;
 $large-header: 16px;
 $body-text: 12px;
 $small-text: 9px;
@@ -54,7 +55,6 @@ $standard-border-radius: 3px;
 // shadow
 $standard-box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
 
-
 $dashboardselector-height: 32px;
 $mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc
 $docDecorations-zindex: 998; // then doc decorations appear over everything else
@@ -79,4 +79,5 @@ $TREE_BULLET_WIDTH: 20px;
     DFLT_IMAGE_NATIVE_DIM: $DFLT_IMAGE_NATIVE_DIM;
     LEFT_MENU_WIDTH: $LEFT_MENU_WIDTH;
     TREE_BULLET_WIDTH: $TREE_BULLET_WIDTH;
-}
\ No newline at end of file
+    INK_MASK_SIZE: $INK_MASK_SIZE;
+}
diff --git a/src/client/views/global/globalCssVariables.scss.d.ts b/src/client/views/global/globalCssVariables.scss.d.ts
index 59c2b3585..76259113c 100644
--- a/src/client/views/global/globalCssVariables.scss.d.ts
+++ b/src/client/views/global/globalCssVariables.scss.d.ts
@@ -1,6 +1,5 @@
-
 interface IGlobalScss {
-    contextMenuZindex: string;  // context menu shows up over everything
+    contextMenuZindex: string; // context menu shows up over everything
     SCHEMA_DIVIDER_WIDTH: string;
     COLLECTION_BORDER_WIDTH: string;
     MINIMIZED_ICON_SIZE: string;
@@ -11,7 +10,8 @@ interface IGlobalScss {
     DFLT_IMAGE_NATIVE_DIM: string;
     LEFT_MENU_WIDTH: string;
     TREE_BULLET_WIDTH: string;
+    INK_MASK_SIZE: number;
 }
 declare const globalCssVariables: IGlobalScss;
 
-export = globalCssVariables;
\ No newline at end of file
+export = globalCssVariables;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index e154e8445..78f351f4f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -38,11 +38,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     get displayName() {
         return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')';
     } // this makes mobx trace() statements more descriptive
-    get maskCentering() {
-        return this.props.Document.isInkMask ? InkingStroke.MaskDim / 2 : 0;
-    }
     get transform() {
-        return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`;
+        return `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`;
     }
     get X() {
         return this.dataProvider ? this.dataProvider.x : NumCast(this.Document.x);
@@ -194,7 +191,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             PanelHeight: this.panelHeight,
         };
         const background = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const mixBlendMode = (StrCast(this.layoutDoc.mixBlendMode) as any) || (typeof background === 'string' && background && !background.startsWith('linear') && DashColor(background).alpha() !== 1 ? 'multiply' : undefined);
+        const mixBlendMode = undefined; // (StrCast(this.layoutDoc.mixBlendMode) as any) || (typeof background === 'string' && background && !background.startsWith('linear') && DashColor(background).alpha() !== 1 ? 'multiply' : undefined);
         return (
             <div
                 className={'collectionFreeFormDocumentView-container'}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f9ef85595..bcc55eab4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1640,8 +1640,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     render() {
         TraceMobx();
-        const xshift = () => (this.props.Document.isInkMask && !this.props.LayoutTemplateString && !this.props.LayoutTemplate?.() ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
-        const yshift = () => (this.props.Document.isInkMask && !this.props.LayoutTemplateString && !this.props.LayoutTemplate?.() ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
+        const xshift = () => (Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
+        const yshift = () => (Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
         const isPresTreeElement: boolean = this.props.treeViewDoc?.type === DocumentType.PRES;
         const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
         return (
@@ -1652,7 +1652,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                         ref={this.ContentRef}
                         style={{
                             transition: this.props.dataTransition,
-                            position: this.props.Document.isInkMask ? 'absolute' : undefined,
+                            //position: this.props.Document.isInkMask ? 'absolute' : undefined,
                             transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
                             width: isButton || isPresTreeElement ? '100%' : xshift() ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`,
                             height:
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index d3b95e25a..cb68c1ac3 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -1,6 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
+import Color from 'color';
 import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -21,7 +22,7 @@ import { DocComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
 import { GestureOverlay } from '../../GestureOverlay';
 import { Colors } from '../../global/globalEnums';
-import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
+import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../../InkingStroke';
 import { InkTranscription } from '../../InkTranscription';
 import { StyleProp } from '../../StyleProvider';
 import { FieldView, FieldViewProps } from '.././FieldView';
@@ -498,8 +499,6 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 </div>
             );
 
-        const buttonText = StrCast(this.rootDoc.buttonText);
-
         // TODO:glr Add label of button type
         let button: JSX.Element | null = this.defaultButton;
 
@@ -508,7 +507,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 button = (
                     <div className={`menuButton ${this.type}`} style={{ color, backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}>
                         <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
-                        {buttonText ? <div className="button-text">{buttonText}</div> : null}
+                        {StrCast(this.rootDoc.buttonText) ? <div className="button-text">{StrCast(this.rootDoc.buttonText)}</div> : null}
                         {label}
                     </div>
                 );
@@ -563,11 +562,13 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 break;
         }
 
-        return !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? (
-            button
-        ) : button !== null ? (
-            <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>
-        ) : null;
+        const retval =
+            !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? (
+                button
+            ) : button !== null ? (
+                <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>
+            ) : null;
+        return retval;
     }
 }
 
@@ -860,6 +861,21 @@ ScriptingGlobals.add(function setActiveTool(tool: string, checkResult?: boolean)
     }
 });
 
+// toggle: Set overlay status of selected document
+ScriptingGlobals.add(function setIsInkMask(checkResult?: boolean) {
+    const selected = SelectionManager.Docs().lastElement();
+    if (checkResult) {
+        if (selected?.type === DocumentType.INK) {
+            return BoolCast(selected.isInkMask) ? Colors.MEDIUM_BLUE : 'transparent';
+        }
+        return ActiveIsInkMask() ? Colors.MEDIUM_BLUE : 'transparent';
+    }
+    SetActiveIsInkMask(!ActiveIsInkMask());
+    SelectionManager.Docs()
+        .filter(doc => doc.type === DocumentType.INK)
+        .map(doc => (doc.isInkMask = !doc.isInkMask));
+});
+
 // toggle: Set overlay status of selected document
 ScriptingGlobals.add(function setFillColor(color?: string, checkResult?: boolean) {
     const selected = SelectionManager.Docs().lastElement();
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 3bbdce1e4..627f35e71 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -313,10 +313,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     //it'll also execute the necessary actions if presentation is playing.
     public gotoDocument = action((index: number, from?: Doc, group?: boolean) => {
         Doc.UnBrushAllDocs();
+
         if (index >= 0 && index < this.childDocs.length) {
             this.rootDoc._itemIndex = index;
             const activeItem: Doc = this.activeItem;
             const targetDoc: Doc = this.targetDoc;
+            if (activeItem.presActiveFrame !== undefined) {
+                const context = DocCast(DocCast(activeItem.presentationTargetDoc).context);
+                context && CollectionFreeFormViewChrome.gotoKeyFrame(context, NumCast(activeItem.presActiveFrame));
+            }
             if (from?.mediaStopTriggerList && this.layoutDoc.presStatus !== PresStatus.Edit) {
                 DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia);
             }
@@ -516,20 +521,26 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             if (tagDoc === this.layoutDoc.presCollection) {
                 tagDoc.opacity = 1;
             } else {
-                if (itemIndexes.length > 1 && curDoc.presHideBefore && curInd !== 0) {
-                } else if (curDoc.presHideBefore) {
-                    if (index > this.itemIndex) {
-                        tagDoc.opacity = 0;
-                    } else if (index === this.itemIndex || !curDoc.presHideAfter) {
+                if (curDoc.presHideBefore) {
+                    if (itemIndexes.length > 1 && curInd !== 0) {
                         tagDoc.opacity = 1;
+                    } else {
+                        if (index > this.itemIndex) {
+                            tagDoc.opacity = 0;
+                        } else if (index === this.itemIndex || !curDoc.presHideAfter) {
+                            tagDoc.opacity = 1;
+                        }
                     }
                 }
-                if (itemIndexes.length > 1 && curDoc.presHideAfter && curInd !== itemIndexes.length - 1) {
-                } else if (curDoc.presHideAfter) {
-                    if (index < this.itemIndex) {
-                        tagDoc.opacity = 0;
-                    } else if (index === this.itemIndex || !curDoc.presHideBefore) {
+                if (curDoc.presHideAfter) {
+                    if (itemIndexes.length > 1 && curInd !== itemIndexes.length - 1) {
                         tagDoc.opacity = 1;
+                    } else {
+                        if (index < this.itemIndex) {
+                            tagDoc.opacity = 0;
+                        } else if (index === this.itemIndex || !curDoc.presHideBefore) {
+                            tagDoc.opacity = 1;
+                        }
                     }
                 }
             }
@@ -821,11 +832,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem);
         if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0);
         else this.updateCurrentPresentation(context);
-
-        if (this.activeItem.presActiveFrame !== undefined) {
-            const context = DocCast(DocCast(this.activeItem.presentationTargetDoc).context);
-            context && CollectionFreeFormViewChrome.gotoKeyFrame(context, NumCast(this.activeItem.presActiveFrame));
-        }
     };
 
     //Command click
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index 114d5fc2f..a074098c1 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -1,22 +1,21 @@
-import { Bezier } from "bezier-js";
-import { createSimpleSchema, list, object, serializable } from "serializr";
-import { ScriptingGlobals } from "../client/util/ScriptingGlobals";
-import { Deserializable } from "../client/util/SerializationHelper";
-import { Copy, ToScriptString, ToString } from "./FieldSymbols";
-import { ObjectField } from "./ObjectField";
+import { Bezier } from 'bezier-js';
+import { createSimpleSchema, list, object, serializable } from 'serializr';
+import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
+import { Deserializable } from '../client/util/SerializationHelper';
+import { Copy, ToScriptString, ToString } from './FieldSymbols';
+import { ObjectField } from './ObjectField';
 
 // Helps keep track of the current ink tool in use.
 export enum InkTool {
-    None = "none",
-    Pen = "pen",
-    Highlighter = "highlighter",
-    Eraser = "eraser",
-    Stamp = "stamp",
-    Write = "write",
-    PresentationPin = 'presentationpin'
+    None = 'none',
+    Pen = 'pen',
+    Highlighter = 'highlighter',
+    Eraser = 'eraser',
+    Stamp = 'stamp',
+    Write = 'write',
+    PresentationPin = 'presentationpin',
 }
 
-
 // Defines a point in an ink as a pair of x- and y-coordinates.
 export interface PointData {
     X: number;
@@ -54,15 +53,16 @@ export interface HandleLine {
 }
 
 const pointSchema = createSimpleSchema({
-    X: true, Y: true
+    X: true,
+    Y: true,
 });
 
 const strokeDataSchema = createSimpleSchema({
     pathData: list(object(pointSchema)),
-    "*": true
+    '*': true,
 });
 
-@Deserializable("ink")
+@Deserializable('ink')
 export class InkField extends ObjectField {
     @serializable(list(object(strokeDataSchema)))
     readonly inkData: InkData;
@@ -85,11 +85,11 @@ export class InkField extends ObjectField {
     }
 
     [ToScriptString]() {
-        return "new InkField([" + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}`) + "])";
+        return 'new InkField([' + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}}`) + '])';
     }
     [ToString]() {
-        return "InkField";
+        return 'InkField';
     }
 }
 
-ScriptingGlobals.add("InkField", InkField);
\ No newline at end of file
+ScriptingGlobals.add('InkField', InkField);
-- 
cgit v1.2.3-70-g09d2


From 5a425e5cf18115921ecb4e7cf931e65f45dab8e2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 18 Aug 2022 11:18:14 -0400
Subject: fixed up presboxelement to not reference presbox.instance.  fixing
 layout of presboxelement in treeview.  fixing "removing" an inkMask without
 removing it from the collection so that it can be turned on and off.

---
 src/client/util/CurrentUserUtils.ts                |   9 +-
 src/client/views/DocumentDecorations.tsx           |   6 +-
 src/client/views/PropertiesView.tsx                |   5 +-
 .../views/collections/CollectionTreeView.scss      |  11 +-
 src/client/views/collections/TabDocView.tsx        |   4 +-
 src/client/views/collections/TreeView.scss         |  17 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   1 +
 src/client/views/nodes/trails/PresBox.tsx          | 137 +++++++++------------
 src/client/views/nodes/trails/PresElementBox.tsx   |  73 ++++++-----
 9 files changed, 132 insertions(+), 131 deletions(-)

(limited to 'src/client/views/nodes/trails/PresBox.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index dcf4a71c8..f7d072d80 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -289,11 +289,12 @@ export class CurrentUserUtils {
             { toolTip: "Tap or drag to create a map",             title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, },
             { toolTip: "Tap or drag to create a screen grabber",  title: "Grab", icon: "photo-video",   dragFactory: doc.emptyScreengrab as Doc,  scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'},funcs: { hidden: 'IsNoviceMode()'} },
             { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc,      scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'},funcs: { hidden: 'IsNoviceMode()'}},
-            { toolTip: "Tap or drag to create a button",          title: "Button", icon: "bolt",        dragFactory: doc.emptyButton as Doc,      funcs: { hidden: 'IsNoviceMode()'} },
-            { toolTip: "Tap or drag to create a scripting box",   title: "Script", icon: "terminal",    dragFactory: doc.emptyScript as Doc,      funcs: { hidden: 'IsNoviceMode()'}},
+            { toolTip: "Tap or drag to create a button",          title: "Button", icon: "bolt",        dragFactory: doc.emptyButton as Doc,      funcs:   { hidden: 'IsNoviceMode()'} },
+            { toolTip: "Tap or drag to create a scripting box",   title: "Script", icon: "terminal",    dragFactory: doc.emptyScript as Doc,      funcs:   { hidden: 'IsNoviceMode()'}},
             { toolTip: "Tap or drag to create a data viz node",   title: "DataViz", icon: "file",       dragFactory: doc.emptyDataViz as Doc, },
-            { toolTip: "Tap or drag to create a data note",       title: "DataNote", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc,  scripts: {onClick: 'openOnRight(delegateDragFactory(this.dragFactory))', onDragStart: '{ return delegateDragFactory(this.dragFactory);}'},  },
-            { toolTip: "Toggle a Calculator REPL",                title: "repl", icon: "calculator",                                              scripts: {onClick: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' } },
+            { toolTip: "Tap or drag to create a bullet slide",    title: "PPT Slide", icon: "file",     dragFactory: doc.emptySlide as Doc,       funcs:   { hidden: 'IsNoviceMode()'}},
+            { toolTip: "Tap or drag to create a data note",       title: "DataNote", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc,scripts: { onClick: 'openOnRight(delegateDragFactory(this.dragFactory))', onDragStart: '{ return delegateDragFactory(this.dragFactory);}'},  },
+            { toolTip: "Toggle a Calculator REPL",                title: "repl", icon: "calculator",                                              scripts: { onClick: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' } },
           ].map(tuple => ({scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, ...tuple, }))
     }
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3544f74b4..6d1397395 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -203,7 +203,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 if (this._deleteAfterIconify) {
                     views.forEach(iconView => {
                         Doc.setNativeView(iconView.props.Document);
-                        iconView.props.removeDocument?.(iconView.props.Document);
+                        if (iconView.props.Document.isInkMask && iconView.props.Document.activeFrame !== undefined) {
+                            iconView.props.Document.opacity = 0; // bcz: hacky ... allows inkMaks to be "turned off" without removing them from the collection which allows them to function properly in a presenation.
+                        } else {
+                            iconView.props.removeDocument?.(iconView.props.Document);
+                        }
                     });
                     SelectionManager.DeselectAll();
                 }
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index ef0e057dc..33f17047b 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -15,7 +15,7 @@ import { ComputedField } from '../../fields/ScriptField';
 import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
 import { denormalizeEmail, GetEffectiveAcl, SharingPermissions } from '../../fields/util';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
-import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
+import { DocumentType } from '../documents/DocumentTypes';
 import { DocumentManager } from '../util/DocumentManager';
 import { LinkManager } from '../util/LinkManager';
 import { SelectionManager } from '../util/SelectionManager';
@@ -1614,9 +1614,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
             if (this.isPres) {
                 const selectedItem: boolean = PresBox.Instance?._selectedArray.size > 0;
                 const type = PresBox.Instance.activeItem?.type;
-                const viewType = PresBox.Instance.activeItem?._viewType;
-                const pannable: boolean = (type === DocumentType.COL && viewType === CollectionViewType.Freeform) || type === DocumentType.IMG;
-                const scrollable: boolean = type === DocumentType.PDF || type === DocumentType.WEB || type === DocumentType.RTF || viewType === CollectionViewType.Stacking || viewType === CollectionViewType.NoteTaking;
                 return (
                     <div className="propertiesView" style={{ width: this.props.width }}>
                         <div className="propertiesView-title" style={{ width: this.props.width }}>
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 93523a6cf..c0561e42c 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -1,6 +1,5 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables';
 
-  
 .collectionTreeView-container {
     transform-origin: top left;
     height: 100%;
@@ -28,7 +27,7 @@
         list-style: none;
         padding-left: $TREE_BULLET_WIDTH;
         margin-bottom: 1px; // otherwise vertical scrollbars may pop up for no apparent reason....
-        > .contentFittingDocumentView  {
+        > .contentFittingDocumentView {
             width: unset;
             height: unset;
         }
@@ -39,7 +38,7 @@
 
     .no-indent {
         padding-left: 0;
-        width: max-content;
+        //width: max-content;
     }
 
     .no-indent-outline {
@@ -85,7 +84,7 @@
     width: 100%;
     height: max-content;
     .contentFittingDocumentView {
-        display: block;  // makes titleBar take up full width of the treeView (flex doesn't for some reason)
+        display: block; // makes titleBar take up full width of the treeView (flex doesn't for some reason)
     }
 }
 
@@ -114,4 +113,4 @@
     padding-left: 3px;
     padding-right: 3px;
     padding-bottom: 2px;
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index ff4b1e2ce..e147f34d2 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -299,8 +299,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     pinDoc.presMovement = PresMovement.None;
                 }
                 if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
-                PresBox.Instance?._selectedArray.clear();
-                pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array
+                PresBox.Instance?.clearSelectedArray();
+                pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); //Update selected array
             });
             if (
                 CollectionDockingView.Instance &&
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index f587dbbf6..ce87e6f89 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables';
 
 .treeView-label {
     max-height: 1.5em;
@@ -21,7 +21,7 @@
     }
 
     .treeView-bulletIcons {
-        // width: $TREE_BULLET_WIDTH; 
+        // width: $TREE_BULLET_WIDTH;
         width: 100%;
         height: 100%;
 
@@ -101,6 +101,9 @@
 .treeView-border {
     display: flex;
     overflow: hidden;
+    > ul {
+        width: 100%;
+    }
 }
 
 .treeView-border {
@@ -118,7 +121,6 @@
     }
 
     .formattedTextBox-cont {
-
         .formattedTextbox-sidebar,
         .formattedTextbox-sidebar-inking {
             overflow: visible !important;
@@ -144,12 +146,12 @@
         pointer-events: all;
         cursor: pointer;
 
-        >svg {
+        > svg {
             margin-left: 0.25rem;
             margin-right: 0.25rem;
         }
 
-        >svg {
+        > svg {
             //display: none;
             opacity: 0;
             pointer-events: none;
@@ -176,8 +178,7 @@
     }
 
     .treeView-rightButtons {
-
-        >svg,
+        > svg,
         .styleProvider-treeView-icon {
             display: inherit;
             opacity: unset;
@@ -196,4 +197,4 @@
 
 .treeView-header-inside {
     border: black 1px solid;
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 45a5e30ff..0fd326091 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -251,6 +251,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     isCurrent(doc: Doc) {
+        if (doc.isInkMask && doc.opacity === 0) return false; // bcz: hacky --- allows inkMasks to be "turned off" in a presentation without removing them from the collection.  otherwise, they still render a gray background.. need to come back to this and fix.
         const dispTime = NumCast(doc._timecodeToShow, -1);
         const endTime = NumCast(doc._timecodeToHide, dispTime + 1.5);
         const curTime = NumCast(this.Document._currentTimecode, -1);
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 627f35e71..05e09361b 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1,7 +1,7 @@
 import React = require('react');
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { ColorState, SketchPicker } from 'react-color';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
@@ -20,6 +20,7 @@ import { SettingsManager } from '../../../util/SettingsManager';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
 import { MarqueeViewBounds } from '../../collections/collectionFreeForm';
+import { CollectionFreeFormViewChrome } from '../../collections/CollectionMenu';
 import { CollectionView } from '../../collections/CollectionView';
 import { TabDocView } from '../../collections/TabDocView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
@@ -29,7 +30,6 @@ import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentVie
 import { FieldView, FieldViewProps } from '../FieldView';
 import './PresBox.scss';
 import { PresEffect, PresMovement, PresStatus } from './PresEnums';
-import { CollectionFreeFormViewChrome } from '../../collections/CollectionMenu';
 
 export interface PinProps {
     audioRange?: boolean;
@@ -67,30 +67,31 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             delay: presDoc.presTransition,
             // when: this.layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc,
         };
+        //prettier-ignore
         switch (presDoc.presEffect) {
-            case PresEffect.Zoom:
-                return <Zoom {...effectProps}>{renderDoc}</Zoom>;
-            case PresEffect.Fade:
-                return <Fade {...effectProps}>{renderDoc}</Fade>;
-            case PresEffect.Flip:
-                return <Flip {...effectProps}>{renderDoc}</Flip>;
-            case PresEffect.Rotate:
-                return <Rotate {...effectProps}>{renderDoc}</Rotate>;
-            case PresEffect.Bounce:
-                return <Bounce {...effectProps}>{renderDoc}</Bounce>;
-            case PresEffect.Roll:
-                return <Roll {...effectProps}>{renderDoc}</Roll>;
-            case PresEffect.Lightspeed:
-                return <LightSpeed {...effectProps}>{renderDoc}</LightSpeed>;
-            case PresEffect.None:
-            default:
-                return renderDoc;
+            default: 
+            case PresEffect.None:       return renderDoc;
+            case PresEffect.Zoom:       return <Zoom {...effectProps}>{renderDoc}</Zoom>;
+            case PresEffect.Fade:       return <Fade {...effectProps}>{renderDoc}</Fade>;
+            case PresEffect.Flip:       return <Flip {...effectProps}>{renderDoc}</Flip>;
+            case PresEffect.Rotate:     return <Rotate {...effectProps}>{renderDoc}</Rotate>;
+            case PresEffect.Bounce:     return <Bounce {...effectProps}>{renderDoc}</Bounce>;
+            case PresEffect.Roll:       return <Roll {...effectProps}>{renderDoc}</Roll>;
+            case PresEffect.Lightspeed: return <LightSpeed {...effectProps}>{renderDoc}</LightSpeed>;
         }
     }
     public static EffectsProvider(layoutDoc: Doc, renderDoc: any) {
         return PresBox.Instance && layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc ? PresBox.renderEffectsDoc(renderDoc, layoutDoc, PresBox.Instance.childDocs[PresBox.Instance.itemIndex]) : renderDoc;
     }
 
+    private _disposers: { [name: string]: IReactionDisposer } = {};
+
+    constructor(props: any) {
+        super(props);
+        if ((Doc.ActivePresentation = this.rootDoc)) runInAction(() => (PresBox.Instance = this));
+        this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox
+    }
+
     @observable public static Instance: PresBox;
 
     @observable _isChildActive = false;
@@ -98,14 +99,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @observable _presTimer!: NodeJS.Timeout;
     @observable _presKeyEventsActive: boolean = false;
 
-    @observable _selectedArray: ObservableMap = new ObservableMap<Doc, any>();
     @observable _eleArray: HTMLElement[] = [];
     @observable _dragArray: HTMLElement[] = [];
     @observable _pathBoolean: boolean = false;
     @observable _expandBoolean: boolean = false;
 
-    private _disposers: { [name: string]: IReactionDisposer } = {};
-
     @observable static startMarquee: boolean = false; // onclick "+ new slide" in presentation mode, set as true, then when marquee selection finish, onPointerUp automatically triggers PinWithView
     @observable private transitionTools: boolean = false;
     @observable private newDocumentTools: boolean = false;
@@ -152,11 +150,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._viewType === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true;
         else return false;
     }
-    constructor(props: any) {
-        super(props);
-        if ((Doc.ActivePresentation = this.rootDoc)) runInAction(() => (PresBox.Instance = this));
-        this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox
-    }
     @computed get selectedDocumentView() {
         if (SelectionManager.Views().length) return SelectionManager.Views()[0];
         if (this._selectedArray.size) return DocumentManager.Instance.getDocumentView(this.rootDoc);
@@ -173,6 +166,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed get selectedDoc() {
         return this.selectedDocumentView?.rootDoc;
     }
+    _selectedArray = new ObservableSet<Doc>();
+    clearSelectedArray = () => this._selectedArray.clear();
+    addToSelectedArray = (doc: Doc) => this._selectedArray.add(doc);
+    removeFromSelectedArray = (doc: Doc) => this._selectedArray.delete(doc);
 
     _unmounting = false;
     @action
@@ -188,6 +185,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @action
     componentDidMount() {
+        this.props.setContentView?.(this);
         this._unmounting = false;
         this.rootDoc._forceRenderEngine = 'timeline';
         this.layoutDoc.presStatus = PresStatus.Edit;
@@ -344,8 +342,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             if (targetDoc?.lastFrame !== undefined) {
                 targetDoc._currentFrame = 0;
             }
-            if (!group) this._selectedArray.clear();
-            this.childDocs[index] && this._selectedArray.set(this.childDocs[index], undefined); //Update selected array
+            if (!group) this.clearSelectedArray();
+            this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); //Update selected array
             this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list
             this.onHideDocument(); //Handles hide after/before
         }
@@ -415,7 +413,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             this.layoutDoc.presCollection = srcContext;
         }
         const presStatus = this.rootDoc.presStatus;
-        const selViewCache = Array.from(this._selectedArray.keys());
+        const selViewCache = Array.from(this._selectedArray);
         const dragViewCache = Array.from(this._dragArray);
         const eleViewCache = Array.from(this._eleArray);
         const self = this;
@@ -423,8 +421,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const presDocView = DocumentManager.Instance.getDocumentView(self.rootDoc);
             if (presDocView) SelectionManager.SelectView(presDocView, false);
             self.rootDoc.presStatus = presStatus;
-            self._selectedArray.clear();
-            selViewCache.forEach(doc => self._selectedArray.set(doc, undefined));
+            self.clearSelectedArray();
+            selViewCache.forEach(doc => self.addToSelectedArray(doc));
             self._dragArray.splice(0, self._dragArray.length, ...dragViewCache);
             self._eleArray.splice(0, self._eleArray.length, ...eleViewCache);
         });
@@ -792,7 +790,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
      * Method to get the list of selected items in the order in which they have been selected
      */
     @computed get listOfSelected() {
-        const list = Array.from(this._selectedArray.keys()).map((doc: Doc, index: any) => {
+        return Array.from(this._selectedArray).map((doc: Doc, index: any) => {
             const curDoc = Cast(doc, Doc, null);
             const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null);
             if (curDoc && curDoc === this.activeItem)
@@ -816,7 +814,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     </div>
                 );
         });
-        return list;
     }
 
     @action
@@ -838,11 +835,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     multiSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => {
         if (!this._selectedArray.has(doc)) {
-            this._selectedArray.set(doc, undefined);
+            this.addToSelectedArray(doc);
             this._eleArray.push(ref);
             this._dragArray.push(drag);
         } else {
-            this._selectedArray.delete(doc);
+            this.removeFromSelectedArray(doc);
             this.removeFromArray(this._eleArray, doc);
             this.removeFromArray(this._dragArray, doc);
         }
@@ -858,11 +855,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     //Shift click
     @action
     shiftSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => {
-        this._selectedArray.clear();
+        this.clearSelectedArray();
         // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
         if (this.activeItem) {
             for (let i = Math.min(this.itemIndex, this.childDocs.indexOf(doc)); i <= Math.max(this.itemIndex, this.childDocs.indexOf(doc)); i++) {
-                this._selectedArray.set(this.childDocs[i], undefined);
+                this.addToSelectedArray(this.childDocs[i]);
                 this._eleArray.push(ref);
                 this._dragArray.push(drag);
             }
@@ -873,8 +870,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     //regular click
     @action
     regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean, selectPres = true) => {
-        this._selectedArray.clear();
-        this._selectedArray.set(doc, undefined);
+        this.clearSelectedArray();
+        this.addToSelectedArray(doc);
         this._eleArray.splice(0, this._eleArray.length, ref);
         this._dragArray.splice(0, this._dragArray.length, drag);
         focus && this.selectElement(doc);
@@ -903,10 +900,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 if (this.layoutDoc.presStatus === 'edit') {
                     undoBatch(
                         action(() => {
-                            for (const doc of Array.from(this._selectedArray.keys())) {
+                            for (const doc of this._selectedArray) {
                                 this.removeDocument(doc);
                             }
-                            this._selectedArray.clear();
+                            this.clearSelectedArray();
                             this._eleArray.length = 0;
                             this._dragArray.length = 0;
                         })
@@ -918,7 +915,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) {
                     this.updateMinimize();
                 } else if (this.layoutDoc.presStatus === 'edit') {
-                    this._selectedArray.clear();
+                    this.clearSelectedArray();
                     this._eleArray.length = this._dragArray.length = 0;
                 } else this.layoutDoc.presStatus = 'edit';
                 if (this._presTimer) clearTimeout(this._presTimer);
@@ -931,7 +928,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 if (e.shiftKey && this.itemIndex < this.childDocs.length - 1) {
                     // TODO: update to work properly
                     this.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) + 1;
-                    this._selectedArray.set(this.childDocs[this.rootDoc._itemIndex], undefined);
+                    this.addToSelectedArray(this.childDocs[this.rootDoc._itemIndex]);
                 } else {
                     this.next();
                     if (this._presTimer) {
@@ -948,7 +945,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 if (e.shiftKey && this.itemIndex !== 0) {
                     // TODO: update to work properly
                     this.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) - 1;
-                    this._selectedArray.set(this.childDocs[this.rootDoc._itemIndex], undefined);
+                    this.addToSelectedArray(this.childDocs[this.rootDoc._itemIndex]);
                 } else {
                     this.back();
                     if (this._presTimer) {
@@ -966,8 +963,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 break;
             case 'a':
                 if ((e.metaKey || e.altKey) && this.layoutDoc.presStatus === 'edit') {
-                    this._selectedArray.clear();
-                    this.childDocs.forEach(doc => this._selectedArray.set(doc, undefined));
+                    this.clearSelectedArray();
+                    this.childDocs.forEach(doc => this.addToSelectedArray(doc));
                     handled = true;
                 }
             default:
@@ -1115,7 +1112,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (change) timeInMS += change;
         if (timeInMS < 100) timeInMS = 100;
         if (timeInMS > 10000) timeInMS = 10000;
-        Array.from(this._selectedArray.keys()).forEach(doc => (doc.presTransition = timeInMS));
+        this._selectedArray.forEach(doc => (doc.presTransition = timeInMS));
     };
 
     // Converts seconds to ms and updates presTransition
@@ -1124,7 +1121,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (change) scale += change;
         if (scale < 0.01) scale = 0.01;
         if (scale > 1.5) scale = 1.5;
-        Array.from(this._selectedArray.keys()).forEach(doc => (doc.presZoom = scale));
+        this._selectedArray.forEach(doc => (doc.presZoom = scale));
     };
 
     // Converts seconds to ms and updates presDuration
@@ -1133,7 +1130,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (change) timeInMS += change;
         if (timeInMS < 100) timeInMS = 100;
         if (timeInMS > 20000) timeInMS = 20000;
-        Array.from(this._selectedArray.keys()).forEach(doc => (doc.presDuration = timeInMS));
+        this._selectedArray.forEach(doc => (doc.presDuration = timeInMS));
     };
 
     /**
@@ -1141,8 +1138,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
      */
     @undoBatch
     updateMovement = action((movement: any, all?: boolean) => {
-        const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys());
-        array.forEach(doc => {
+        (all ? this.childDocs : this._selectedArray).forEach(doc => {
             switch (movement) {
                 case PresMovement.Zoom: //Pan and zoom
                     doc.presMovement = PresMovement.Zoom;
@@ -1166,21 +1162,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     updateHideBefore = (activeItem: Doc) => {
         activeItem.presHideBefore = !activeItem.presHideBefore;
-        Array.from(this._selectedArray.keys()).forEach(doc => (doc.presHideBefore = activeItem.presHideBefore));
+        this._selectedArray.forEach(doc => (doc.presHideBefore = activeItem.presHideBefore));
     };
 
     @undoBatch
     @action
     updateHideAfter = (activeItem: Doc) => {
         activeItem.presHideAfter = !activeItem.presHideAfter;
-        Array.from(this._selectedArray.keys()).forEach(doc => (doc.presHideAfter = activeItem.presHideAfter));
+        this._selectedArray.forEach(doc => (doc.presHideAfter = activeItem.presHideAfter));
     };
 
     @undoBatch
     @action
     updateOpenDoc = (activeItem: Doc) => {
         activeItem.openDocument = !activeItem.openDocument;
-        Array.from(this._selectedArray.keys()).forEach(doc => {
+        this._selectedArray.forEach(doc => {
             doc.openDocument = activeItem.openDocument;
         });
     };
@@ -1188,8 +1184,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @undoBatch
     @action
     updateEffectDirection = (effect: any, all?: boolean) => {
-        const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys());
-        array.forEach(doc => {
+        (all ? this.childDocs : this._selectedArray).forEach(doc => {
             const tagDoc = doc; // Cast(doc.presentationTargetDoc, Doc, null);
             switch (effect) {
                 case PresEffect.Left:
@@ -1215,29 +1210,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @undoBatch
     @action
     updateEffect = (effect: any, all?: boolean) => {
-        const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys());
-        array.forEach(doc => {
+        (all ? this.childDocs : this._selectedArray).forEach(doc => {
             const tagDoc = doc; //Cast(doc.presentationTargetDoc, Doc, null);
+            //prettier-ignore
             switch (effect) {
-                case PresEffect.Bounce:
-                    tagDoc.presEffect = PresEffect.Bounce;
-                    break;
-                case PresEffect.Fade:
-                    tagDoc.presEffect = PresEffect.Fade;
-                    break;
-                case PresEffect.Flip:
-                    tagDoc.presEffect = PresEffect.Flip;
-                    break;
-                case PresEffect.Roll:
-                    tagDoc.presEffect = PresEffect.Roll;
-                    break;
-                case PresEffect.Rotate:
-                    tagDoc.presEffect = PresEffect.Rotate;
-                    break;
-                case PresEffect.None:
                 default:
-                    tagDoc.presEffect = PresEffect.None;
-                    break;
+                case PresEffect.None:   tagDoc.presEffect = PresEffect.None; break;
+                case PresEffect.Bounce: tagDoc.presEffect = PresEffect.Bounce; break;
+                case PresEffect.Fade:   tagDoc.presEffect = PresEffect.Fade; break;
+                case PresEffect.Flip:   tagDoc.presEffect = PresEffect.Flip; break;
+                case PresEffect.Roll:   tagDoc.presEffect = PresEffect.Roll; break;
+                case PresEffect.Rotate: tagDoc.presEffect = PresEffect.Rotate; break;
             }
         });
     };
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 0cf15d297..3af8cad9a 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -47,6 +47,13 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed get presStatus() {
         return this.presBox.presStatus;
     }
+    @computed get selectedArray() {
+        return this.presBoxView?._selectedArray;
+    }
+    @computed get presBoxView() {
+        const vpath = this.props.docViewPath();
+        return vpath.length > 1 ? (vpath[vpath.length - 2].ComponentView as PresBox) : undefined;
+    }
     @computed get presBox() {
         return (this.props.DocumentView?.().props.treeViewDoc ?? this.props.ContainingCollectionDoc)!;
     }
@@ -129,10 +136,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
             <div
                 className="presItem-groupSlide"
                 onClick={e => {
-                    console.log('Clicked on slide with index: ', ind);
                     e.stopPropagation();
                     e.preventDefault();
-                    PresBox.Instance.modifierSelect(doc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
+                    this.presBoxView?.modifierSelect(doc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
                     this.presExpandDocumentClick();
                 }}>
                 <div className="presItem-groupNum">{`${ind + 1}.`}</div>
@@ -181,15 +187,15 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         e.stopPropagation();
         e.preventDefault();
         if (element && !(e.ctrlKey || e.metaKey)) {
-            if (PresBox.Instance._selectedArray.has(this.rootDoc)) {
-                PresBox.Instance._selectedArray.size === 1 && PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false);
+            if (this.selectedArray?.has(this.rootDoc)) {
+                this.selectedArray.size === 1 && this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false);
                 setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);
             } else {
                 setupMoveUpEvents(
                     this,
                     e,
                     (e: PointerEvent) => {
-                        PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false);
+                        this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false);
                         return this.startDrag(e);
                     },
                     emptyFunction,
@@ -205,8 +211,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     startDrag = (e: PointerEvent) => {
         const miniView: boolean = this.toolbarWidth <= 100;
         const activeItem = this.rootDoc;
-        const dragArray = PresBox.Instance._dragArray;
-        const dragData = new DragManager.DocumentDragData(PresBox.Instance.sortArray());
+        const dragArray = this.presBoxView?._dragArray ?? [];
+        const dragData = new DragManager.DocumentDragData(this.presBoxView?.sortArray() ?? []);
         if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc);
         dragData.dropAction = 'move';
         dragData.treeViewDoc = this.props.docViewPath().lastElement()?.props.treeViewDoc;
@@ -221,7 +227,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         } else if (dragArray.length >= 1) {
             const doc = document.createElement('div');
             doc.className = 'presItem-multiDrag';
-            doc.innerText = 'Move ' + PresBox.Instance._selectedArray.size + ' slides';
+            doc.innerText = 'Move ' + this.selectedArray?.size + ' slides';
             doc.style.position = 'absolute';
             doc.style.top = e.clientY + 'px';
             doc.style.left = e.clientX - 50 + 'px';
@@ -286,10 +292,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @undoBatch
     removeItem = action((e: React.MouseEvent) => {
         e.stopPropagation();
-        this.props.removeDocument?.(this.rootDoc);
-        if (PresBox.Instance._selectedArray.has(this.rootDoc)) {
-            PresBox.Instance._selectedArray.delete(this.rootDoc);
+        if (this.indexInPres < (this.presBoxView?.itemIndex || 0)) {
+            this.presBox.itemIndex = (this.presBoxView?.itemIndex || 0) - 1;
         }
+        this.props.removeDocument?.(this.rootDoc);
+        this.presBoxView?.removeFromSelectedArray(this.rootDoc);
         this.removeAllRecordingInOverlay();
     });
 
@@ -441,7 +448,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @computed get mainItem() {
-        const isSelected: boolean = PresBox.Instance?._selectedArray.has(this.rootDoc);
+        const isSelected: boolean = this.selectedArray?.has(this.rootDoc) ? true : false;
         const toolbarWidth: number = this.toolbarWidth;
         const showMore: boolean = this.toolbarWidth >= 300;
         const miniView: boolean = this.toolbarWidth <= 110;
@@ -463,12 +470,12 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 onClick={e => {
                     e.stopPropagation();
                     e.preventDefault();
-                    PresBox.Instance.modifierSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
+                    this.presBoxView?.modifierSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
                     this.showRecording(activeItem);
                 }}
                 onDoubleClick={action(e => {
                     this.toggleProperties();
-                    PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true);
+                    this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true);
                 })}
                 onPointerOver={this.onPointerOver}
                 onPointerLeave={this.onPointerLeave}
@@ -542,21 +549,29 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 </Tooltip>
                             )}
 
-                            {/* {this.indexInPres === 0 ? (null) : <Tooltip title={<><div className="dash-tooltip">{activeItem.groupWithUp ? "Ungroup" : "Group with up"}</div></>}>
-                                          <div className="slideButton"
-                                                onClick={() => activeItem.groupWithUp = !activeItem.groupWithUp}
-                                                style={{
-                                                      zIndex: 1000 - this.indexInPres,
-                                                      fontWeight: 700,
-                                                      backgroundColor: activeItem.groupWithUp ? presColorBool ? presBoxColor : Colors.MEDIUM_BLUE : undefined,
-                                                      height: activeItem.groupWithUp ? 53 : 18,
-                                                      transform: activeItem.groupWithUp ? "translate(0, -17px)" : undefined
-                                                }}>
-                                                <div style={{ transform: activeItem.groupWithUp ? "rotate(180deg) translate(0, -17.5px)" : "rotate(0deg)" }}>
-                                                      <FontAwesomeIcon icon={"arrow-up"} onPointerDown={e => e.stopPropagation()} />
-                                                </div>
-                                          </div>
-                                    </Tooltip>} */}
+                            {this.indexInPres === 0 ? null : (
+                                <Tooltip
+                                    title={
+                                        <>
+                                            <div className="dash-tooltip">{activeItem.groupWithUp ? 'Ungroup' : 'Group with up'}</div>
+                                        </>
+                                    }>
+                                    <div
+                                        className="slideButton"
+                                        onClick={() => (activeItem.groupWithUp = !activeItem.groupWithUp)}
+                                        style={{
+                                            zIndex: 1000 - this.indexInPres,
+                                            fontWeight: 700,
+                                            backgroundColor: activeItem.groupWithUp ? (presColorBool ? presBoxColor : Colors.MEDIUM_BLUE) : undefined,
+                                            height: activeItem.groupWithUp ? 53 : 18,
+                                            transform: activeItem.groupWithUp ? 'translate(0, -17px)' : undefined,
+                                        }}>
+                                        <div style={{ transform: activeItem.groupWithUp ? 'rotate(180deg) translate(0, -17.5px)' : 'rotate(0deg)' }}>
+                                            <FontAwesomeIcon icon={'arrow-up'} onPointerDown={e => e.stopPropagation()} />
+                                        </div>
+                                    </div>
+                                </Tooltip>
+                            )}
                             <Tooltip
                                 title={
                                     <>
-- 
cgit v1.2.3-70-g09d2


From 542a057edbdd4661cfb5ef5d07058aa93b47a113 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 24 Aug 2022 11:16:46 -0400
Subject: cleaned up some of the pin with view code.  added a pin button to
 menu bar when tab is selected to avoid needing to use context menu

---
 src/client/util/CurrentUserUtils.ts                |   5 +-
 src/client/util/SelectionManager.ts                |   3 +
 src/client/views/collections/CollectionMenu.tsx    |  44 ---
 src/client/views/collections/TabDocView.tsx        |  39 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   3 +
 src/client/views/nodes/button/FontIconBox.tsx      |  96 ++----
 .../views/nodes/formattedText/RichTextMenu.tsx     |   4 +-
 src/client/views/nodes/trails/PresBox.tsx          | 358 +++++----------------
 8 files changed, 124 insertions(+), 428 deletions(-)

(limited to 'src/client/views/nodes/trails/PresBox.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 17d58595c..99a8c895f 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -267,7 +267,7 @@ export class CurrentUserUtils {
             {key: "Script",     creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
             // {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument(opts),         opts: { _width: 300, _height: 300 }},
             {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true,}},
-            {key: "Presentation",creator: Docs.Create.PresDocument,                         opts: { _width: 400, _height: 500, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, _chromeHidden: true, boxShadow: "0 0" }},
+            {key: "Presentation",creator: Docs.Create.PresDocument,                         opts: { _width: 400, _height: 500, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, treeViewHideTitle: true,  _chromeHidden: true, boxShadow: "0 0" }},
             {key: "Tab",        creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 500, _height: 800, _backgroundGridShow: true, }},
             {key: "Slide",      creator: opts => Docs.Create.TreeDocument([], opts),        opts: { _width: 300, _height: 200, _viewType: CollectionViewType.Tree, 
                                                                                                 treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, 
@@ -675,8 +675,9 @@ export class CurrentUserUtils {
                     CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map,
                     CollectionViewType.Grid, CollectionViewType.NoteTaking]),
               title: "Perspective",                    toolTip: "View",                 btnType: ButtonType.DropdownList,                                                                                    ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
+            { title: "Pin",     icon: "map-pin",       toolTip: "Pin View to Trail",    btnType: ButtonType.ClickButton,         funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "tab")'},                        width: 20,  scripts: { onClick: 'pinWithView(_readOnly_)'}},
             { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,         funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
-            { title: "Num",     icon: "",              toolTip: "Frame Number",         btnType: ButtonType.TextButton,          funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement().currentFrame.toString()'}, width: 20,  scripts: {}},
+            { title: "Num",     icon: "",              toolTip: "Frame Number",         btnType: ButtonType.TextButton,          funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement()?.currentFrame.toString()'}, width: 20,  scripts: {}},
             { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
             { title: "Fill",    icon: "fill-drip",     toolTip: "Background Fill Color",btnType: ButtonType.ColorButton,         funcs: {hidden: '!SelectionManager_selectedDocType()'},                     ignoreClick: true, width: 20,  scripts: { script:  'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected
             { title: "Header",  icon: "heading",       toolTip: "Header Color",         btnType: ButtonType.ColorButton,         funcs: {hidden: '!SelectionManager_selectedDocType()'},                     ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'}},
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 1c84af94a..7a555d5f8 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -101,6 +101,9 @@ export namespace SelectionManager {
     }
 }
 ScriptingGlobals.add(function SelectionManager_selectedDocType(docType?: DocumentType, colType?: CollectionViewType, checkContext?: boolean) {
+    if (colType === ('tab' as any)) {
+        return SelectionManager.Views().lastElement()?.props.renderDepth === 0;
+    }
     let selected = (sel => (checkContext ? DocCast(sel?.context) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
     return docType ? selected?.type === docType : colType ? selected?.viewType === colType : true;
 });
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index eb55650e4..0dc30e0fd 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -586,49 +586,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         );
     }
 
-    @undoBatch
-    @action
-    pinWithView = (targetDoc: Opt<Doc>) => {
-        if (targetDoc) {
-            TabDocView.PinDoc(targetDoc);
-            const presArray: Doc[] = PresBox.Instance?.sortArray();
-            const size: number = PresBox.Instance?._selectedArray.size;
-            const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
-            const activeDoc = presSelected ? PresBox.Instance?.childDocs[PresBox.Instance?.childDocs.indexOf(presSelected) + 1] : PresBox.Instance?.childDocs[PresBox.Instance?.childDocs.length - 1];
-            if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking || targetDoc._viewType === CollectionViewType.NoteTaking) {
-                const scroll = targetDoc._scrollTop;
-                activeDoc.presPinView = true;
-                activeDoc.presPinViewScroll = scroll;
-            } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG || targetDoc.type === DocumentType.MAP) {
-                const x = targetDoc._panX;
-                const y = targetDoc._panY;
-                const scale = targetDoc._viewScale;
-                activeDoc.presPinView = true;
-                activeDoc.presPinViewX = x;
-                activeDoc.presPinViewY = y;
-                activeDoc.presPinViewScale = scale;
-            } else if (targetDoc.type === DocumentType.VID) {
-                activeDoc.presPinView = true;
-            } else if (targetDoc.type === DocumentType.COMPARISON) {
-                const width = targetDoc._clipWidth;
-                activeDoc.presPinClipWidth = width;
-                activeDoc.presPinView = true;
-            }
-        }
-    };
-
-    @computed
-    get pinWithViewButton() {
-        const presPinWithViewIcon = <img src={`/assets/pinWithView.png`} style={{ margin: 'auto', width: 19 }} />;
-        return !this.selectedDoc ? null : (
-            <Tooltip title={<div className="dash-tooltip">{'Pin with current view'}</div>} placement="top">
-                <button className="antimodeMenu-button" style={{ justifyContent: 'center' }} onClick={() => this.pinWithView(this.selectedDoc)}>
-                    {presPinWithViewIcon}
-                </button>
-            </Tooltip>
-        );
-    }
-
     @undoBatch
     onAlias = () => {
         if (this.selectedDoc && this.selectedDocumentView) {
@@ -722,7 +679,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
                         {this.aliasButton}
                         {/* {this.pinButton} */}
                         {this.toggleOverlayButton}
-                        {this.pinWithViewButton}
                         <div className="collectionMenu-divider" key="divider2"></div>
                         {this.subChrome}
                         <div className="collectionMenu-divider" key="divider3"></div>
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index e147f34d2..73574bdb3 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -242,43 +242,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 const size: number = PresBox.Instance?._selectedArray.size;
                 const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
                 const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null);
-                // If pinWithView option set then update scale and x / y props of slide
-                if (pinProps?.pinWithView) {
-                    const viewProps = pinProps.pinWithView;
-                    pinDoc.presPinView = true;
-                    pinDoc.presPinViewX = viewProps.bounds.left + viewProps.bounds.width / 2;
-                    pinDoc.presPinViewY = viewProps.bounds.top + viewProps.bounds.height / 2;
-                    pinDoc.presPinViewScale = viewProps.scale;
-                    pinDoc.contentBounds = new List<number>([viewProps.bounds.left, viewProps.bounds.top, viewProps.bounds.left + viewProps.bounds.width, viewProps.bounds.top + viewProps.bounds.height]);
-                }
-                if (pinProps?.pinDocView) {
-                    const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(pinDoc.type as any) || pinDoc._viewType === CollectionViewType.Stacking;
-                    const pannable: boolean = (pinDoc.type === DocumentType.COL && doc._viewType === CollectionViewType.Freeform) || doc.type === DocumentType.IMG;
-                    if (scrollable) {
-                        const scroll = doc._scrollTop;
-                        pinDoc.presPinView = true;
-                        pinDoc.presPinViewScroll = scroll;
-                    } else if ([DocumentType.AUDIO, DocumentType.VID].includes(doc.type as any)) {
-                        pinDoc.presPinView = true;
-                        pinDoc.presStartTime = doc._currentTimecode;
-                        pinDoc.presEndTime = NumCast(doc._currentTimecode) + 0.1;
-                    } else if (pannable) {
-                        pinDoc.presPinView = true;
-                        pinDoc.presPinViewX = pinDoc._panX;
-                        pinDoc.presPinViewY = pinDoc._panY;
-                        pinDoc.presPinViewScale = pinDoc._viewScale;
-                        const pw = NumCast(pinProps.panelWidth);
-                        const ph = NumCast(pinProps.panelHeight);
-                        const ps = NumCast(pinDoc._viewScale);
-                        if (pw && ph && ps) {
-                            pinDoc.contentBounds = new List<number>([NumCast(pinDoc.panX) - pw / 2 / ps, NumCast(pinDoc.panY) - ph / 2 / ps, NumCast(pinDoc.panX) + pw / 2 / ps, NumCast(pinDoc.panY) + ph / 2 / ps]);
-                        }
-                    } else if (doc.type === DocumentType.COMPARISON) {
-                        const width = doc._clipWidth;
-                        pinDoc.presPinClipWidth = width;
-                        pinDoc.presPinView = true;
-                    }
-                }
+
+                PresBox.pinDocView(pinDoc, pinProps);
                 pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)');
                 Doc.AddDocToList(curPres, 'data', pinDoc, presSelected);
                 if (!pinProps?.audioRange && duration !== undefined) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 03beaf65e..052cbd3bb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -2213,3 +2213,6 @@ ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
 ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) {
     !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true);
 });
+ScriptingGlobals.add(function pinWithView(readOnly: boolean) {
+    !readOnly && SelectionManager.Views().forEach(view => TabDocView.PinDoc(view.rootDoc, { pinDocView: true, panelWidth: view.props.PanelWidth(), panelHeight: view.props.PanelHeight() }));
+});
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index fc5bf86f4..c72b5ca9b 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -254,16 +254,13 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
 
         const script = ScriptCast(this.rootDoc.script);
-        if (!script) {
-            return null;
-        }
 
         let noviceList: string[] = [];
         let text: string | undefined;
         let dropdown = true;
         let icon: IconProp = 'caret-down';
         try {
-            if (script.script.originalScript.startsWith('setView')) {
+            if (script?.script.originalScript.startsWith('setView')) {
                 const selected = SelectionManager.Docs().lastElement();
                 if (selected) {
                     if (StrCast(selected.type) === DocumentType.COL) {
@@ -279,7 +276,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                     text = 'User Default';
                 }
                 noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking];
-            } else if (script.script.originalScript.startsWith('setFont')) {
+            } else if (script?.script.originalScript.startsWith('setFont')) {
                 const editorView = RichTextMenu.Instance?.TextView?.EditorView;
                 text = StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
                 noviceList = ['Roboto', 'Times New Roman', 'Arial', 'Georgia', 'Comic Sans MS', 'Tahoma', 'Impact', 'Crimson Text'];
@@ -289,11 +286,9 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         }
 
         // Get items to place into the list
-        const list = this.buttonList.map(value => {
-            if (Doc.noviceMode && !noviceList.includes(value)) {
-                return;
-            }
-            return (
+        const list = this.buttonList
+            .filter(value => !Doc.noviceMode || noviceList.includes(value))
+            .map(value => (
                 <div
                     className="list-item"
                     key={`${value}`}
@@ -304,8 +299,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                     onClick={() => script.script.run({ value }).result}>
                     {value[0].toUpperCase() + value.slice(1)}
                 </div>
-            );
-        });
+            ));
 
         const label =
             !this.label || !FontIconBox.GetShowLabels() ? null : (
@@ -451,7 +445,6 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     @computed get defaultButton() {
         const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
         const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const active: string = StrCast(this.rootDoc.dropDownOpen);
         return (
             <div className={`menuButton ${this.type}`} onContextMenu={this.specificContextMenu} style={{ backgroundColor: 'transparent', borderBottomLeftRadius: this.dropdown ? 0 : undefined }}>
                 <div className="menuButton-wrap">
@@ -487,89 +480,50 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     render() {
         const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
         const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const label =
-            !this.label || !FontIconBox.GetShowLabels() ? null : (
-                <div className="fontIconBox-label" style={{ color, backgroundColor }}>
-                    {this.label}
-                </div>
-            );
-
-        const menuLabel =
+        const label = (noBackground: boolean = false) =>
             !this.label || !FontIconBox.GetShowLabels() ? null : (
-                <div className="fontIconBox-label" style={{ color, backgroundColor: 'transparent' }}>
+                <div className="fontIconBox-label" style={{ color, backgroundColor: noBackground ? 'transparent' : backgroundColor }}>
                     {this.label}
                 </div>
             );
-
         // TODO:glr Add label of button type
-        let button: JSX.Element | null = this.defaultButton;
+        let button: JSX.Element = this.defaultButton;
 
+        // prettier-ignore
         switch (this.type) {
-            case ButtonType.TextButton:
-                button = (
+            case ButtonType.DropdownList: return this.dropdownListButton;
+            case ButtonType.ColorButton: return this.colorButton; 
+            case ButtonType.NumberButton: return this.numberButton; 
+            case ButtonType.EditableText: return this.editableText;
+            case ButtonType.DropdownButton: button = this.dropdownButton; break;
+            case ButtonType.ToggleButton: button = this.toggleButton; break;
+            case ButtonType.TextButton: button = (
                     <div className={`menuButton ${this.type}`} style={{ color, backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}>
                         {this.Icon(color)}
                         {StrCast(this.rootDoc.buttonText) ? <div className="button-text">{StrCast(this.rootDoc.buttonText)}</div> : null}
-                        {label}
-                    </div>
-                );
-                // button = <TextButton {...buttonProps}></TextButton>
-                break;
-            case ButtonType.EditableText:
-                button = this.editableText;
-                break;
-            case ButtonType.NumberButton:
-                button = this.numberButton;
-                break;
-            case ButtonType.DropdownButton:
-                button = this.dropdownButton;
-                break;
-            case ButtonType.DropdownList:
-                button = this.dropdownListButton;
-                break;
-            case ButtonType.ColorButton:
-                button = this.colorButton;
-                break;
-            case ButtonType.ToolButton:
-                button = (
-                    <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ opacity: 1, backgroundColor, color }}>
-                        {this.Icon(color)}
-                        {label}
+                        {label()}
                     </div>
                 );
                 break;
-            case ButtonType.ToggleButton:
-                button = this.toggleButton;
-                // button = <ToggleButton {...buttonProps}></ToggleButton>
-                break;
             case ButtonType.ClickButton:
-                button = (
-                    <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ color, backgroundColor, opacity: 1 }}>
+            case ButtonType.ToolButton: button = (
+                    <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ backgroundColor, color, opacity: 1 }}>
                         {this.Icon(color)}
-                        {label}
+                        {label()}
                     </div>
                 );
                 break;
-            case ButtonType.MenuButton:
-                button = (
+            case ButtonType.MenuButton: button = (
                     <div className={`menuButton ${this.type}`} style={{ color, backgroundColor }}>
                         {this.Icon(color)}
-                        {menuLabel}
+                        {label(true)}
                         <FontIconBadge value={Cast(this.Document.badgeValue, 'string', null)} />
                     </div>
                 );
                 break;
-            default:
-                break;
         }
 
-        const retval =
-            !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? (
-                button
-            ) : button !== null ? (
-                <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>
-            ) : null;
-        return retval;
+        return !this.layoutDoc.toolTip ? button : <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>;
     }
 }
 
@@ -698,7 +652,7 @@ ScriptingGlobals.add(function setFontHighlight(color?: string, checkResult?: boo
 ScriptingGlobals.add(function setFontSize(size: string | number, checkResult?: boolean) {
     const editorView = RichTextMenu.Instance?.TextView?.EditorView;
     if (checkResult) {
-        return RichTextMenu.Instance.fontSize.replace('px', '');
+        return RichTextMenu.Instance?.fontSize.replace('px', '');
     }
     if (typeof size === 'number') size = size.toString();
     if (size && Number(size).toString() === size) size += 'px';
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 2a77210ae..0cbe60c0c 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -16,7 +16,7 @@ import { SelectionManager } from '../../../util/SelectionManager';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
 import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 import { FieldViewProps } from '../FieldView';
-import { FormattedTextBox, FormattedTextBoxProps } from './FormattedTextBox';
+import { FormattedTextBox } from './FormattedTextBox';
 import { updateBullets } from './ProsemirrorExampleTransfer';
 import './RichTextMenu.scss';
 import { schema } from './schema_rts';
@@ -29,7 +29,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     private _linkToRef = React.createRef<HTMLInputElement>();
 
     @observable public view?: EditorView;
-    public editorProps: (FieldViewProps & FormattedTextBoxProps) | undefined;
+    public editorProps: FieldViewProps | undefined;
 
     public _brushMap: Map<string, Set<Mark>> = new Map();
 
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 05e09361b..eb40089ec 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -358,20 +358,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (bestTarget) this._navTimer = PresBox.navigateToDoc(bestTarget, activeItem, false);
     };
 
+    static pinDataTypes(target: Doc) {
+        const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(target.type as any) || target._viewType === CollectionViewType.Stacking;
+        const pannable = [DocumentType.IMG].includes(target.type as any) || (target.type === DocumentType.COL && target._viewType === CollectionViewType.Freeform);
+        const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(target.type as any);
+        const clippable = [DocumentType.COMPARISON].includes(target.type as any);
+        return { scrollable, pannable, temporal, clippable };
+    }
     // navigates to the bestTarget document by making sure it is on screen,
     // then it applies the view specs stored in activeItem to
     @action
     static navigateToDoc(bestTarget: Doc, activeItem: Doc, jumpToDoc: boolean) {
-        if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) {
-            bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
-            bestTarget._scrollTop = activeItem.presPinViewScroll;
-        } else if (bestTarget.type === DocumentType.COMPARISON) {
-            bestTarget._clipWidth = activeItem.presPinClipWidth;
-        } else if ([DocumentType.AUDIO, DocumentType.VID].includes(bestTarget.type as any)) {
-            bestTarget._currentTimecode = activeItem.presStartTime;
-        } else {
+        bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
+        const { scrollable, pannable, temporal, clippable } = this.pinDataTypes(bestTarget);
+        if (clippable) bestTarget._clipWidth = activeItem.presPinClipWidth;
+        if (temporal) bestTarget._currentTimecode = activeItem.presStartTime;
+        if (scrollable) bestTarget._scrollTop = activeItem.presPinViewScroll;
+        if (pannable) {
             const contentBounds = Cast(activeItem.contentBounds, listSpec('number'));
-            bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
             if (contentBounds) {
                 bestTarget._panX = (contentBounds[0] + contentBounds[2]) / 2;
                 bestTarget._panY = (contentBounds[1] + contentBounds[3]) / 2;
@@ -388,6 +392,43 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return setTimeout(() => (bestTarget._viewTransition = undefined), activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510);
     }
 
+    /// copies values from the targetDoc (which is the prototype of the pinDoc) to
+    /// reserved fields on the pinDoc so that those values can be restored to the
+    /// target doc when navigating to it.
+    @action
+    static pinDocView(pinDoc: Doc, pinProps: PinProps | undefined) {
+        if (pinProps?.pinWithView) {
+            // If pinWithView option set then update scale and x / y props of slide
+            const bounds = pinProps.pinWithView.bounds;
+            pinDoc.presPinView = true;
+            pinDoc.presPinViewX = bounds.left + bounds.width / 2;
+            pinDoc.presPinViewY = bounds.top + bounds.height / 2;
+            pinDoc.presPinViewScale = pinProps.pinWithView.scale;
+            pinDoc.contentBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]);
+        }
+        if (pinProps?.pinDocView) {
+            const { scrollable, pannable, temporal, clippable } = this.pinDataTypes(pinDoc);
+            pinDoc.presPinView = (pinProps?.pinWithView ? true : false) || scrollable || temporal || pannable || clippable;
+
+            if (scrollable) pinDoc.presPinViewScroll = pinDoc._scrollTop;
+            else if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth;
+            else if (temporal) pinDoc.presEndTime = NumCast((pinDoc.presStartTime = pinDoc._currentTimecode)) + 0.1;
+            else if (pannable) {
+                const panX = NumCast(pinDoc._panX);
+                const panY = NumCast(pinDoc._panY);
+                const pw = NumCast(pinProps.panelWidth);
+                const ph = NumCast(pinProps.panelHeight);
+                const ps = NumCast(pinDoc._viewScale);
+                if (pw && ph && ps) {
+                    pinDoc.contentBounds = new List<number>([panX - pw / 2 / ps, panY - ph / 2 / ps, panX + pw / 2 / ps, panY + ph / 2 / ps]);
+                }
+                pinDoc.presPinViewX = panX;
+                pinDoc.presPinViewY = panY;
+                pinDoc.presPinViewScale = ps;
+            }
+        }
+    }
+
     /**
      * This method makes sure that cursor navigates to the element that
      * has the option open and last in the group.
@@ -1540,26 +1581,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
     }
 
-    @computed get effectDirection(): string {
-        let effect = '';
+    @computed get effectDirection() {
+        // prettier-ignore
         switch (this.activeItem.presEffectDirection) {
-            case 'left':
-                effect = 'Enter from left';
-                break;
-            case 'right':
-                effect = 'Enter from right';
-                break;
-            case 'top':
-                effect = 'Enter from top';
-                break;
-            case 'bottom':
-                effect = 'Enter from bottom';
-                break;
-            default:
-                effect = 'Enter from center';
-                break;
+            case 'left': return 'Enter from left'; 
+            case 'right': return 'Enter from right'; 
+            case 'top':  return'Enter from top';
+            case 'bottom': return 'Enter from bottom';
         }
-        return effect;
+        return 'Enter from center';
     }
 
     @undoBatch
@@ -1582,179 +1612,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         });
     };
 
-    @computed get presPinViewOptionsDropdown() {
-        const activeItem: Doc = this.activeItem;
-        const targetDoc: Doc = this.targetDoc;
-        const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: 'auto', width: 16, filter: 'invert(1)' }} />;
-        return (
-            <>
-                {this.panable || this.scrollable || this.targetDoc.type === DocumentType.COMPARISON ? 'Pinned view' : null}
-                <div className="ribbon-doubleButton">
-                    <Tooltip
-                        title={
-                            <>
-                                <div className="dash-tooltip">{activeItem.presPinView ? 'Turn off pin with view' : 'Turn on pin with view'}</div>
-                            </>
-                        }>
-                        <div
-                            className="ribbon-toggle"
-                            style={{ width: 20, padding: 0, backgroundColor: activeItem.presPinView ? Colors.LIGHT_BLUE : '' }}
-                            onClick={() => {
-                                activeItem.presPinView = !activeItem.presPinView;
-                                targetDoc.presPinView = activeItem.presPinView;
-                                if (activeItem.presPinView) {
-                                    if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking) {
-                                        const scroll = targetDoc._scrollTop;
-                                        activeItem.presPinView = true;
-                                        activeItem.presPinViewScroll = scroll;
-                                    } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) {
-                                        activeItem.presStartTime = targetDoc._currentTimecode;
-                                        activeItem.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1;
-                                    } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG) {
-                                        const x = targetDoc._panX;
-                                        const y = targetDoc._panY;
-                                        const scale = targetDoc._viewScale;
-                                        activeItem.presPinView = true;
-                                        activeItem.presPinViewX = x;
-                                        activeItem.presPinViewY = y;
-                                        activeItem.presPinViewScale = scale;
-                                    } else if (targetDoc.type === DocumentType.COMPARISON) {
-                                        const width = targetDoc._clipWidth;
-                                        activeItem.presPinClipWidth = width;
-                                        activeItem.presPinView = true;
-                                    }
-                                }
-                            }}>
-                            {presPinWithViewIcon}
-                        </div>
-                    </Tooltip>
-                    {activeItem.presPinView ? (
-                        <Tooltip
-                            title={
-                                <>
-                                    <div className="dash-tooltip">{'Update the pinned view with the view of the selected document'}</div>
-                                </>
-                            }>
-                            <div
-                                className="ribbon-button"
-                                onClick={() => {
-                                    if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) {
-                                        const scroll = targetDoc._scrollTop;
-                                        activeItem.presPinViewScroll = scroll;
-                                    } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) {
-                                        activeItem.presStartTime = targetDoc._currentTimecode;
-                                        activeItem.presStartTime = NumCast(targetDoc._currentTimecode) + 0.1;
-                                    } else if (targetDoc.type === DocumentType.COMPARISON) {
-                                        const clipWidth = targetDoc._clipWidth;
-                                        activeItem.presPinClipWidth = clipWidth;
-                                    } else {
-                                        const x = targetDoc._panX;
-                                        const y = targetDoc._panY;
-                                        const scale = targetDoc._viewScale;
-                                        activeItem.presPinViewX = x;
-                                        activeItem.presPinViewY = y;
-                                        activeItem.presPinViewScale = scale;
-                                    }
-                                }}>
-                                Update
-                            </div>
-                        </Tooltip>
-                    ) : null}
-                </div>
-            </>
-        );
-    }
-
-    @computed get panOptionsDropdown() {
-        const activeItem: Doc = this.activeItem;
-        const targetDoc: Doc = this.targetDoc;
-        return (
-            <>
-                {this.panable ? (
-                    <div style={{ display: activeItem.presPinView ? 'block' : 'none' }}>
-                        <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
-                            <div className="presBox-subheading">Pan X</div>
-                            <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
-                                <input
-                                    className="presBox-input"
-                                    style={{ textAlign: 'left', width: 50 }}
-                                    type="number"
-                                    value={NumCast(activeItem.presPinViewX)}
-                                    onKeyDown={e => e.stopPropagation()}
-                                    onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
-                                        const val = e.target.value;
-                                        activeItem.presPinViewX = Number(val);
-                                    })}
-                                />
-                            </div>
-                        </div>
-                        <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
-                            <div className="presBox-subheading">Pan Y</div>
-                            <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
-                                <input
-                                    className="presBox-input"
-                                    style={{ textAlign: 'left', width: 50 }}
-                                    type="number"
-                                    value={NumCast(activeItem.presPinViewY)}
-                                    onKeyDown={e => e.stopPropagation()}
-                                    onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
-                                        const val = e.target.value;
-                                        activeItem.presPinViewY = Number(val);
-                                    })}
-                                />
-                            </div>
-                        </div>
-                        <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
-                            <div className="presBox-subheading">Scale</div>
-                            <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
-                                <input
-                                    className="presBox-input"
-                                    style={{ textAlign: 'left', width: 50 }}
-                                    type="number"
-                                    value={NumCast(activeItem.presPinViewScale)}
-                                    onKeyDown={e => e.stopPropagation()}
-                                    onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
-                                        const val = e.target.value;
-                                        activeItem.presPinViewScale = Number(val);
-                                    })}
-                                />
-                            </div>
-                        </div>
-                    </div>
-                ) : null}
-            </>
-        );
-    }
-
-    @computed get scrollOptionsDropdown() {
-        const activeItem: Doc = this.activeItem;
-        const targetDoc: Doc = this.targetDoc;
-        return (
-            <>
-                {this.scrollable ? (
-                    <div style={{ display: activeItem.presPinView ? 'block' : 'none' }}>
-                        <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
-                            <div className="presBox-subheading">Scroll</div>
-                            <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
-                                <input
-                                    className="presBox-input"
-                                    style={{ textAlign: 'left', width: 50 }}
-                                    type="number"
-                                    value={NumCast(activeItem.presPinViewScroll)}
-                                    onKeyDown={e => e.stopPropagation()}
-                                    onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
-                                        const val = e.target.value;
-                                        activeItem.presPinViewScroll = Number(val);
-                                    })}
-                                />
-                            </div>
-                        </div>
-                    </div>
-                ) : null}
-            </>
-        );
-    }
-
     @computed get mediaStopSlides() {
         const activeItem: Doc = this.activeItem;
         const list = this.childDocs.map((doc, i) => {
@@ -2102,42 +1959,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     createTemplate = (layout: string, input?: string) => {
-        const activeItem: Doc = this.activeItem;
-        const targetDoc: Doc = this.targetDoc;
-        let x = 0;
-        let y = 0;
-        if (activeItem && targetDoc) {
-            x = NumCast(targetDoc.x);
-            y = NumCast(targetDoc.y) + NumCast(targetDoc._height) + 20;
-        }
-        let doc = undefined;
-        const title = Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, _fontSize: '24pt' });
-        const subtitle = Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, _fontSize: '16pt' });
-        const header = Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, _fontSize: '20pt' });
-        const contentTitle = Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, _fontSize: '24pt' });
-        const content = Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, _fontSize: '14pt' });
-        const content1 = Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, _fontSize: '14pt' });
-        const content2 = Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _fontSize: '14pt' });
+        const x = this.activeItem && this.targetDoc ? NumCast(this.targetDoc.x) : 0;
+        const y = this.activeItem && this.targetDoc ? NumCast(this.targetDoc.y) + NumCast(this.targetDoc._height) + 20 : 0;
+        const title = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, _fontSize: '24pt' });
+        const subtitle = () => Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, _fontSize: '16pt' });
+        const header = () => Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, _fontSize: '20pt' });
+        const contentTitle = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, _fontSize: '24pt' });
+        const content = () => Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, _fontSize: '14pt' });
+        const content1 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, _fontSize: '14pt' });
+        const content2 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _fontSize: '14pt' });
+        // prettier-ignore
         switch (layout) {
-            case 'blank':
-                doc = Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x: x, y: y });
-                break;
-            case 'title':
-                doc = Docs.Create.FreeformDocument([title, subtitle], { title: input ? input : 'Title slide', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y });
-                break;
-            case 'header':
-                doc = Docs.Create.FreeformDocument([header], { title: input ? input : 'Section header', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y });
-                break;
-            case 'content':
-                doc = Docs.Create.FreeformDocument([contentTitle, content], { title: input ? input : 'Title and content', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y });
-                break;
-            case 'twoColumns':
-                doc = Docs.Create.FreeformDocument([contentTitle, content1, content2], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y });
-                break;
-            default:
-                break;
+            case 'blank': return Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x, y });
+            case 'title': return Docs.Create.FreeformDocument([title(), subtitle()], { title: input ? input : 'Title slide', _width: 400, _height: 225, _fitContentsToBox: true, x, y });
+            case 'header': return Docs.Create.FreeformDocument([header()], { title: input ? input : 'Section header', _width: 400, _height: 225, _fitContentsToBox: true, x, y });
+            case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input ? input : 'Title and content', _width: 400, _height: 225, _fitContentsToBox: true, x, y });
+            case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _fitContentsToBox: true, x, y })
         }
-        return doc;
     };
 
     // Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view)
@@ -2201,42 +2039,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     /**
      * Returns the collection type as a string for headers
      */
-    @computed get stringType(): string {
-        const activeItem: Doc = this.activeItem;
-        const targetDoc: Doc = this.targetDoc;
-        let type: string = '';
-        if (activeItem) {
-            switch (targetDoc.type) {
-                case DocumentType.PDF:
-                    type = 'PDF';
-                    break;
-                case DocumentType.RTF:
-                    type = 'Text node';
-                    break;
-                case DocumentType.COL:
-                    type = 'Collection';
-                    break;
-                case DocumentType.AUDIO:
-                    type = 'Audio';
-                    break;
-                case DocumentType.VID:
-                    type = 'Video';
-                    break;
-                case DocumentType.IMG:
-                    type = 'Image';
-                    break;
-                case DocumentType.WEB:
-                    type = 'Web page';
-                    break;
-                case DocumentType.MAP:
-                    type = 'Map';
-                    break;
-                default:
-                    type = 'Other node';
-                    break;
+    @computed get stringType() {
+        if (this.activeItem) {
+            // prettier-ignore
+            switch (this.targetDoc.type) {
+                case DocumentType.PDF: return 'PDF';
+                case DocumentType.RTF: return 'Text node';
+                case DocumentType.COL: return 'Collection';
+                case DocumentType.AUDIO: return 'Audio';
+                case DocumentType.VID: return 'Video';
+                case DocumentType.IMG: return 'Image';
+                case DocumentType.WEB: return 'Web page';
+                case DocumentType.MAP: return 'Map';
+                default: return 'Other node';
             }
         }
-        return type;
+        return '';
     }
 
     @observable private openActiveColorPicker: boolean = false;
@@ -2845,10 +2663,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             case DocumentType.PDF || DocumentType.RTF || DocumentType.WEB:
                 this.updateList(activeItem.frameList);
                 break;
-            case DocumentType.COL:
-                break;
-            default:
-                break;
         }
     };
 
-- 
cgit v1.2.3-70-g09d2


From 9263422913f30b54922f3c0d7290e36d4a509455 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 26 Aug 2022 12:43:00 -0400
Subject: extending pres trails to allow ink animation without using keyframes.

---
 src/client/views/MainView.tsx                      |  2 +-
 src/client/views/collections/CollectionMenu.tsx    | 20 +++----
 src/client/views/collections/TabDocView.tsx        |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 -
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 10 ++--
 src/client/views/nodes/trails/PresBox.tsx          | 67 +++++++++++++++-------
 6 files changed, 61 insertions(+), 41 deletions(-)

(limited to 'src/client/views/nodes/trails/PresBox.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 06be4d194..515faa316 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -154,10 +154,10 @@ export class MainView extends React.Component {
         if (!MainView.Live) {
             DocServer.setPlaygroundFields([
                 'dataTransition',
+                'viewTransition',
                 'treeViewOpen',
                 'showSidebar',
                 'sidebarWidthPercent',
-                'viewTransition',
                 'panX',
                 'panY',
                 'fitWidth',
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 0dc30e0fd..6a0f69359 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -726,18 +726,16 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
     }
 
     public static gotoKeyFrame(doc: Doc, newFrame: number) {
-        if (!doc) {
-            return;
-        }
-        const dataField = doc[Doc.LayoutFieldKey(doc)];
-        const childDocs = DocListCast(dataField);
-        const currentFrame = Cast(doc._currentFrame, 'number', null);
-        if (currentFrame === undefined) {
-            doc._currentFrame = 0;
-            CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
+        if (doc) {
+            const childDocs = DocListCast(doc[Doc.LayoutFieldKey(doc)]);
+            const currentFrame = Cast(doc._currentFrame, 'number', null);
+            if (currentFrame === undefined) {
+                doc._currentFrame = 0;
+                CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
+            }
+            CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0);
+            doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
         }
-        CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0);
-        doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
     }
 
     @undoBatch
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 73574bdb3..f7b48adf6 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -243,7 +243,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
                 const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null);
 
-                PresBox.pinDocView(pinDoc, pinProps);
+                PresBox.pinDocView(pinDoc, pinProps, doc);
                 pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)');
                 Doc.AddDocToList(curPres, 'data', pinDoc, presSelected);
                 if (!pinProps?.audioRange && duration !== undefined) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c44b33ed0..210370d39 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -278,7 +278,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000), false); // get non-default values for everything else
                 vals.x = x + NumCast(pvals.x) - dropPos[0];
                 vals.y = y + NumCast(pvals.y) - dropPos[1];
-                vals._scrollTop = this.Document.editScrollProgressivize ? pvals._scrollTop : undefined;
                 CollectionFreeFormDocumentView.setValues(NumCast(this.Document._currentFrame), d, vals);
             } else {
                 d.x = x + NumCast(d.x) - dropPos[0];
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index efaecb8d2..82557d36b 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -90,14 +90,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     public static getValues(doc: Doc, time: number, fillIn: boolean = true) {
         return CollectionFreeFormDocumentView.animFields.reduce((p, val) => {
-            p[val.key] = Cast(`${val}-indexed`, listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number);
+            p[val.key] = Cast(doc[`${val.key}-indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number);
             return p;
         }, {} as { [val: string]: Opt<number> });
     }
 
     public static getStringValues(doc: Doc, time: number) {
         return CollectionFreeFormDocumentView.animStringFields.reduce((p, val) => {
-            p[val] = Cast(`${val}-indexed`, listSpec('string'), [StrCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as string);
+            p[val] = Cast(doc[`${val}-indexed`], listSpec('string'), [StrCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as string);
             return p;
         }, {} as { [val: string]: Opt<string> });
     }
@@ -140,8 +140,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         );
     }
 
-    public static gotoKeyframe(docs: Doc[]) {
-        docs.forEach(doc => (doc._viewTransition = doc.dataTransition = 'all 1s'));
+    public static gotoKeyframe(docs: Doc[], duration = 1000) {
+        docs.forEach(doc => (doc._viewTransition = doc.dataTransition = `all ${duration}ms`));
         setTimeout(
             () =>
                 docs.forEach(doc => {
@@ -226,7 +226,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             PanelWidth: this.panelWidth,
             PanelHeight: this.panelHeight,
         };
-        const mixBlendMode = undefined; // (StrCast(this.layoutDoc.mixBlendMode) as any) || (typeof background === 'string' && background && !background.startsWith('linear') && DashColor(background).alpha() !== 1 ? 'multiply' : undefined);
         return (
             <div
                 className={'collectionFreeFormDocumentView-container'}
@@ -238,7 +237,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     transformOrigin: '50% 50%',
                     transition: this.dataProvider?.transition ?? (this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition)),
                     zIndex: this.ZInd,
-                    mixBlendMode: mixBlendMode,
                     display: this.ZInd === -99 ? 'none' : undefined,
                 }}>
                 {this.props.renderCutoffProvider(this.props.Document) ? (
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index eb40089ec..f254eaba6 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -6,7 +6,7 @@ import { observer } from 'mobx-react';
 import { ColorState, SketchPicker } from 'react-color';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
 import { Doc, DocListCast, DocListCastAsync, FieldResult } from '../../../../fields/Doc';
-import { InkTool } from '../../../../fields/InkField';
+import { InkField, InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
@@ -19,8 +19,7 @@ import { SelectionManager } from '../../../util/SelectionManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
-import { MarqueeViewBounds } from '../../collections/collectionFreeForm';
-import { CollectionFreeFormViewChrome } from '../../collections/CollectionMenu';
+import { CollectionFreeFormView, MarqueeViewBounds } from '../../collections/collectionFreeForm';
 import { CollectionView } from '../../collections/CollectionView';
 import { TabDocView } from '../../collections/TabDocView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
@@ -30,6 +29,8 @@ import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentVie
 import { FieldView, FieldViewProps } from '../FieldView';
 import './PresBox.scss';
 import { PresEffect, PresMovement, PresStatus } from './PresEnums';
+import { Copy } from '../../../../fields/FieldSymbols';
+import { CollectionFreeFormViewChrome } from '../../collections/CollectionMenu';
 
 export interface PinProps {
     audioRange?: boolean;
@@ -317,8 +318,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const activeItem: Doc = this.activeItem;
             const targetDoc: Doc = this.targetDoc;
             if (activeItem.presActiveFrame !== undefined) {
+                const transTime = NumCast(activeItem.presDuration, 500);
                 const context = DocCast(DocCast(activeItem.presentationTargetDoc).context);
-                context && CollectionFreeFormViewChrome.gotoKeyFrame(context, NumCast(activeItem.presActiveFrame));
+                if (context) {
+                    const contextView = DocumentManager.Instance.getFirstDocumentView(context);
+                    if (contextView?.ComponentView) {
+                        CollectionFreeFormDocumentView.gotoKeyframe((contextView.ComponentView as CollectionFreeFormView).childDocs.slice(), transTime);
+                        context._currentFrame = NumCast(activeItem.presActiveFrame);
+                    }
+                }
             }
             if (from?.mediaStopTriggerList && this.layoutDoc.presStatus !== PresStatus.Edit) {
                 DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia);
@@ -353,8 +361,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     navigateToView = (targetDoc: Doc, activeItem: Doc) => {
         clearTimeout(this._navTimer);
         const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document;
-        if (bestTarget) console.log(bestTarget.title, bestTarget.type);
-        else console.log('no best target');
         if (bestTarget) this._navTimer = PresBox.navigateToDoc(bestTarget, activeItem, false);
     };
 
@@ -363,17 +369,29 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const pannable = [DocumentType.IMG].includes(target.type as any) || (target.type === DocumentType.COL && target._viewType === CollectionViewType.Freeform);
         const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(target.type as any);
         const clippable = [DocumentType.COMPARISON].includes(target.type as any);
-        return { scrollable, pannable, temporal, clippable };
+        const dataview = [DocumentType.INK].includes(target.type as any) && target.activeFrame === undefined;
+        return { scrollable, pannable, temporal, clippable, dataview };
     }
     // navigates to the bestTarget document by making sure it is on screen,
     // then it applies the view specs stored in activeItem to
     @action
     static navigateToDoc(bestTarget: Doc, activeItem: Doc, jumpToDoc: boolean) {
-        bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
-        const { scrollable, pannable, temporal, clippable } = this.pinDataTypes(bestTarget);
+        const transTime = NumCast(activeItem.presDuration, 500);
+        const presTransitionTime = `all ${transTime}ms`;
+        bestTarget._viewTransition = presTransitionTime;
+        const { scrollable, pannable, temporal, clippable, dataview } = this.pinDataTypes(bestTarget);
         if (clippable) bestTarget._clipWidth = activeItem.presPinClipWidth;
         if (temporal) bestTarget._currentTimecode = activeItem.presStartTime;
         if (scrollable) bestTarget._scrollTop = activeItem.presPinViewScroll;
+        if (dataview) {
+            bestTarget._dataTransition = presTransitionTime;
+            bestTarget.data = (activeItem.presData as any as InkField)[Copy]();
+            bestTarget.x = NumCast(activeItem.presX);
+            bestTarget.y = NumCast(activeItem.presY);
+            bestTarget.width = NumCast(activeItem.presWidth);
+            bestTarget.height = NumCast(activeItem.presHeight);
+        }
+
         if (pannable) {
             const contentBounds = Cast(activeItem.contentBounds, listSpec('number'));
             if (contentBounds) {
@@ -389,14 +407,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 bestTarget._viewScale = activeItem.presPinViewScale;
             }
         }
-        return setTimeout(() => (bestTarget._viewTransition = undefined), activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510);
+        return setTimeout(() => {
+            bestTarget._viewTransition = undefined;
+            if (dataview) bestTarget._dataTransition = undefined;
+        }, transTime + 10);
     }
 
     /// copies values from the targetDoc (which is the prototype of the pinDoc) to
     /// reserved fields on the pinDoc so that those values can be restored to the
     /// target doc when navigating to it.
     @action
-    static pinDocView(pinDoc: Doc, pinProps: PinProps | undefined) {
+    static pinDocView(pinDoc: Doc, pinProps: PinProps | undefined, targetDoc: Doc) {
         if (pinProps?.pinWithView) {
             // If pinWithView option set then update scale and x / y props of slide
             const bounds = pinProps.pinWithView.bounds;
@@ -407,13 +428,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             pinDoc.contentBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]);
         }
         if (pinProps?.pinDocView) {
-            const { scrollable, pannable, temporal, clippable } = this.pinDataTypes(pinDoc);
-            pinDoc.presPinView = (pinProps?.pinWithView ? true : false) || scrollable || temporal || pannable || clippable;
+            const { scrollable, pannable, temporal, clippable, dataview } = this.pinDataTypes(pinDoc);
+            pinDoc.presPinView = (pinProps?.pinWithView ? true : false) || scrollable || temporal || pannable || clippable || dataview || pinProps.activeFrame !== undefined;
 
             if (scrollable) pinDoc.presPinViewScroll = pinDoc._scrollTop;
-            else if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth;
-            else if (temporal) pinDoc.presEndTime = NumCast((pinDoc.presStartTime = pinDoc._currentTimecode)) + 0.1;
-            else if (pannable) {
+            if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth;
+            if (temporal) pinDoc.presEndTime = NumCast((pinDoc.presStartTime = pinDoc._currentTimecode)) + 0.1;
+            if (dataview) {
+                pinDoc.presData = (targetDoc.data as InkField)[Copy]();
+                pinDoc.presX = NumCast(targetDoc.x);
+                pinDoc.presY = NumCast(targetDoc.y);
+                pinDoc.presWidth = NumCast(targetDoc.width);
+                pinDoc.presHeight = NumCast(targetDoc.height);
+            }
+            if (pannable) {
                 const panX = NumCast(pinDoc._panX);
                 const panY = NumCast(pinDoc._panY);
                 const pw = NumCast(pinProps.panelWidth);
@@ -493,8 +521,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         // After navigating to the document, if it is added as a presPinView then it will
         // adjust the pan and scale to that of the pinView when it was added.
         if (activeItem.presPinView) {
-            console.log(targetDoc.title);
-            console.log('presPinView in PresBox.tsx:420');
             // if targetDoc is not displayed but one of its aliases is, then we need to modify that alias, not the original target
             this.navigateToView(targetDoc, activeItem);
         }
@@ -866,10 +892,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     //Regular click
     @action
     selectElement = async (doc: Doc) => {
-        const context = Cast(doc.context, Doc, null);
         this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem);
-        if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0);
-        else this.updateCurrentPresentation(context);
+        if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(DocCast(doc.context)), 0);
+        else this.updateCurrentPresentation(DocCast(doc.context));
     };
 
     //Command click
-- 
cgit v1.2.3-70-g09d2


From 7278aaa71a13f57cdc371bd771f5fcc6419707b7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 26 Aug 2022 13:25:23 -0400
Subject: added x,y,w,h view pinning for all documents.

---
 src/client/views/DocumentButtonBar.tsx           |  3 +-
 src/client/views/nodes/trails/PresBox.tsx        | 59 +++++++++++++++---------
 src/client/views/nodes/trails/PresElementBox.tsx | 15 +++++-
 3 files changed, 52 insertions(+), 25 deletions(-)

(limited to 'src/client/views/nodes/trails/PresBox.tsx')

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 40fc8dae6..76e2d64a2 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -26,6 +26,7 @@ import { DashFieldView } from './nodes/formattedText/DashFieldView';
 import { GoogleRef } from './nodes/formattedText/FormattedTextBox';
 import { TemplateMenu } from './TemplateMenu';
 import React = require('react');
+import { DocumentType } from '../documents/DocumentTypes';
 const higflyout = require('@hig/flyout');
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -243,7 +244,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                             .views()
                             .filter(v => v)
                             .map(dv => dv!.rootDoc);
-                        TabDocView.PinDoc(docs, { pinDocView: true, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
+                        TabDocView.PinDoc(docs, { pinDocView: !docs.some(doc => !e.shiftKey && doc.type === DocumentType.RTF), activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
                     }}>
                     <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
                 </div>
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index f254eaba6..eb6dad327 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -6,8 +6,10 @@ import { observer } from 'mobx-react';
 import { ColorState, SketchPicker } from 'react-color';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
 import { Doc, DocListCast, DocListCastAsync, FieldResult } from '../../../../fields/Doc';
+import { Copy } from '../../../../fields/FieldSymbols';
 import { InkField, InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
+import { ObjectField } from '../../../../fields/ObjectField';
 import { listSpec } from '../../../../fields/Schema';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents } from '../../../../Utils';
@@ -29,8 +31,6 @@ import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentVie
 import { FieldView, FieldViewProps } from '../FieldView';
 import './PresBox.scss';
 import { PresEffect, PresMovement, PresStatus } from './PresEnums';
-import { Copy } from '../../../../fields/FieldSymbols';
-import { CollectionFreeFormViewChrome } from '../../collections/CollectionMenu';
 
 export interface PinProps {
     audioRange?: boolean;
@@ -370,7 +370,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(target.type as any);
         const clippable = [DocumentType.COMPARISON].includes(target.type as any);
         const dataview = [DocumentType.INK].includes(target.type as any) && target.activeFrame === undefined;
-        return { scrollable, pannable, temporal, clippable, dataview };
+        const textview = [DocumentType.RTF].includes(target.type as any) && target.activeFrame === undefined;
+        return { scrollable, pannable, temporal, clippable, dataview, textview };
     }
     // navigates to the bestTarget document by making sure it is on screen,
     // then it applies the view specs stored in activeItem to
@@ -378,20 +379,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     static navigateToDoc(bestTarget: Doc, activeItem: Doc, jumpToDoc: boolean) {
         const transTime = NumCast(activeItem.presDuration, 500);
         const presTransitionTime = `all ${transTime}ms`;
+        const { scrollable, pannable, temporal, clippable, dataview, textview } = this.pinDataTypes(bestTarget);
         bestTarget._viewTransition = presTransitionTime;
-        const { scrollable, pannable, temporal, clippable, dataview } = this.pinDataTypes(bestTarget);
+        bestTarget._dataTransition = dataview || textview ? presTransitionTime : undefined;
+        bestTarget.x = NumCast(activeItem.presX);
+        bestTarget.y = NumCast(activeItem.presY);
+        bestTarget.width = NumCast(activeItem.presWidth);
+        bestTarget.height = NumCast(activeItem.presHeight);
         if (clippable) bestTarget._clipWidth = activeItem.presPinClipWidth;
         if (temporal) bestTarget._currentTimecode = activeItem.presStartTime;
         if (scrollable) bestTarget._scrollTop = activeItem.presPinViewScroll;
-        if (dataview) {
-            bestTarget._dataTransition = presTransitionTime;
-            bestTarget.data = (activeItem.presData as any as InkField)[Copy]();
-            bestTarget.x = NumCast(activeItem.presX);
-            bestTarget.y = NumCast(activeItem.presY);
-            bestTarget.width = NumCast(activeItem.presWidth);
-            bestTarget.height = NumCast(activeItem.presHeight);
-        }
-
+        if (dataview) Doc.GetProto(bestTarget).data = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData;
+        if (textview) Doc.GetProto(bestTarget).text = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData;
         if (pannable) {
             const contentBounds = Cast(activeItem.contentBounds, listSpec('number'));
             if (contentBounds) {
@@ -409,7 +408,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
         return setTimeout(() => {
             bestTarget._viewTransition = undefined;
-            if (dataview) bestTarget._dataTransition = undefined;
+            if (dataview || textview) bestTarget._dataTransition = undefined;
         }, transTime + 10);
     }
 
@@ -428,19 +427,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             pinDoc.contentBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]);
         }
         if (pinProps?.pinDocView) {
-            const { scrollable, pannable, temporal, clippable, dataview } = this.pinDataTypes(pinDoc);
-            pinDoc.presPinView = (pinProps?.pinWithView ? true : false) || scrollable || temporal || pannable || clippable || dataview || pinProps.activeFrame !== undefined;
+            const { scrollable, pannable, temporal, clippable, dataview, textview } = this.pinDataTypes(pinDoc);
+            pinDoc.presPinView = (pinProps?.pinWithView ? true : false) || scrollable || temporal || pannable || clippable || dataview || textview || pinProps.activeFrame !== undefined;
+            pinDoc.presX = NumCast(targetDoc.x);
+            pinDoc.presY = NumCast(targetDoc.y);
+            pinDoc.presWidth = NumCast(targetDoc.width);
+            pinDoc.presHeight = NumCast(targetDoc.height);
 
             if (scrollable) pinDoc.presPinViewScroll = pinDoc._scrollTop;
             if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth;
             if (temporal) pinDoc.presEndTime = NumCast((pinDoc.presStartTime = pinDoc._currentTimecode)) + 0.1;
-            if (dataview) {
-                pinDoc.presData = (targetDoc.data as InkField)[Copy]();
-                pinDoc.presX = NumCast(targetDoc.x);
-                pinDoc.presY = NumCast(targetDoc.y);
-                pinDoc.presWidth = NumCast(targetDoc.width);
-                pinDoc.presHeight = NumCast(targetDoc.height);
-            }
+            if (textview) pinDoc.presData = targetDoc.text instanceof ObjectField ? targetDoc.text[Copy]() : targetDoc.text;
+            if (dataview) pinDoc.presData = targetDoc.data instanceof ObjectField ? targetDoc.data[Copy]() : targetDoc.data;
             if (pannable) {
                 const panX = NumCast(pinDoc._panX);
                 const panY = NumCast(pinDoc._panY);
@@ -512,9 +510,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             // openInTab(targetDoc);
         } else if (curDoc.presMovement === PresMovement.Pan && targetDoc) {
             LightboxView.SetLightboxDoc(undefined);
+            const transTime = NumCast(activeItem.presDuration, 500);
+            const presTransitionTime = `all ${transTime}ms`;
+            targetDoc._dataTransition = presTransitionTime;
+            targetDoc.x = NumCast(activeItem.presX);
+            targetDoc.y = NumCast(activeItem.presY);
+            targetDoc.width = NumCast(activeItem.presWidth);
+            targetDoc.height = NumCast(activeItem.presHeight);
             await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right
         } else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) {
             LightboxView.SetLightboxDoc(undefined);
+
+            const transTime = NumCast(activeItem.presDuration, 500);
+            const presTransitionTime = `all ${transTime}ms`;
+            targetDoc._dataTransition = presTransitionTime;
+            targetDoc.x = NumCast(activeItem.presX);
+            targetDoc.y = NumCast(activeItem.presY);
+            targetDoc.width = NumCast(activeItem.presWidth);
+            targetDoc.height = NumCast(activeItem.presHeight);
             //awaiting jump so that new scale can be found, since jumping is async
             await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right
         }
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 91196ca21..38a87c34b 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -3,7 +3,7 @@ import { Tooltip } from '@material-ui/core';
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../../fields/Doc';
-import { Id } from '../../../../fields/FieldSymbols';
+import { Copy, Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
 import { Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
@@ -24,6 +24,8 @@ import { PresBox } from './PresBox';
 import './PresElementBox.scss';
 import { PresMovement } from './PresEnums';
 import React = require('react');
+import { InkField } from '../../../../fields/InkField';
+import { RichTextField } from '../../../../fields/RichTextField';
 /**
  * This class models the view a document added to presentation will have in the presentation.
  * It involves some functionality for its buttons and options.
@@ -320,6 +322,12 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
             case DocumentType.RTF:
                 const scroll = targetDoc._scrollTop;
                 activeItem.presPinViewScroll = scroll;
+                if (targetDoc.type === DocumentType.RTF) {
+                    activeItem.presData = targetDoc.text instanceof RichTextField ? targetDoc.text[Copy]() : targetDoc.text;
+                }
+                break;
+            case DocumentType.INK:
+                activeItem.presData = targetDoc.data instanceof InkField ? targetDoc.data[Copy]() : targetDoc.data;
                 break;
             case DocumentType.VID:
             case DocumentType.AUDIO:
@@ -337,6 +345,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 activeItem.presPinViewY = y;
                 activeItem.presPinViewScale = scale;
         }
+
+        activeItem.presX = NumCast(targetDoc.x);
+        activeItem.presY = NumCast(targetDoc.y);
+        activeItem.presWidth = NumCast(targetDoc.width);
+        activeItem.presHeight = NumCast(targetDoc.height);
     };
 
     @computed get recordingIsInOverlay() {
-- 
cgit v1.2.3-70-g09d2


From b1044d1d79c1e06769f74df514e12557426b67be Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 26 Aug 2022 16:16:21 -0400
Subject: trying to clean up transition times for presbox / jumptoDoc, etc.

---
 src/client/util/DocumentManager.ts                 |  18 +-
 .../views/collections/CollectionNoteTakingView.tsx |   2 +-
 .../views/collections/CollectionStackingView.tsx   |   5 +-
 src/client/views/collections/TabDocView.tsx        |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   5 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   1 +
 src/client/views/nodes/trails/PresBox.tsx          | 662 +++++++++------------
 src/client/views/nodes/trails/PresElementBox.tsx   |   9 -
 8 files changed, 287 insertions(+), 417 deletions(-)

(limited to 'src/client/views/nodes/trails/PresBox.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 52b643c04..2ca5d1095 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -154,7 +154,7 @@ export class DocumentManager {
         CollectionDockingView.AddSplit(doc, 'right');
         finished?.();
     };
-    public jumpToDocument = async (
+    public jumpToDocument = (
         targetDoc: Doc, // document to display
         willZoom: boolean, // whether to zoom doc to take up most of screen
         createViewFunc = DocumentManager.addView, // how to create a view of the doc if it doesn't exist
@@ -165,8 +165,8 @@ export class DocumentManager {
         finished?: () => void,
         originalTarget?: Doc,
         noSelect?: boolean,
-        presZoom?: number
-    ): Promise<void> => {
+        presZoomScale?: number
+    ): void => {
         originalTarget = originalTarget ?? targetDoc;
         const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
         const docView = getFirstDocView(targetDoc, originatingDoc);
@@ -207,7 +207,7 @@ export class DocumentManager {
             finished?.();
         };
         const annoContainerView = (!wasHidden || resolvedTarget !== annotatedDoc) && annotatedDoc && getFirstDocView(annotatedDoc);
-        const contextDocs = docContext.length ? await DocListCastAsync(docContext[0].data) : undefined;
+        const contextDocs = docContext.length ? DocListCast(docContext[0].data) : undefined;
         const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext.lastElement() : undefined;
         const targetDocContext = contextDoc || annotatedDoc;
         const targetDocContextView = (targetDocContext && getFirstDocView(targetDocContext)) || (wasHidden && annoContainerView); // if we have an annotation container and the target was hidden, then try again because we just un-hid the document above
@@ -218,7 +218,7 @@ export class DocumentManager {
                     annoContainerView.focus(targetDoc, {
                         originalTarget,
                         willZoom,
-                        scale: presZoom,
+                        scale: presZoomScale,
                         afterFocus: (didFocus: boolean) =>
                             new Promise<ViewAdjustment>(res => {
                                 focusAndFinish(true);
@@ -237,7 +237,7 @@ export class DocumentManager {
                 focusView.focus(originalTarget ?? targetDoc, {
                     originalTarget,
                     willZoom,
-                    scale: presZoom,
+                    scale: presZoomScale,
                     afterFocus: (didFocus: boolean) =>
                         new Promise<ViewAdjustment>(res => {
                             focusAndFinish(forceDidFocus || didFocus);
@@ -265,7 +265,9 @@ export class DocumentManager {
                         afterFocus: async () => {
                             targetDocContext._viewTransition = undefined;
                             if (targetDocContext.layoutKey === 'layout_icon') {
-                                targetDocContextView.iconify(() => this.jumpToDocument(resolvedTarget ?? targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoom));
+                                targetDocContextView.iconify(() =>
+                                    this.jumpToDocument(resolvedTarget ?? targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoomScale)
+                                );
                             }
                             return ViewAdjustment.doNothing;
                         },
@@ -309,7 +311,7 @@ export class DocumentManager {
                         const docContextView = this.getFirstDocumentView(docContext[0]);
                         if (docContextView) {
                             return docContextView.iconify(() =>
-                                this.jumpToDocument(targetDoc, willZoom, createViewFunc, docContext.slice(1, docContext.length), linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoom)
+                                this.jumpToDocument(targetDoc, willZoom, createViewFunc, docContext.slice(1, docContext.length), linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoomScale)
                             );
                         }
                     }
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 615141485..92c0bc341 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -201,7 +201,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
             const top = found.getBoundingClientRect().top;
             const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
             if (Math.floor(localTop[1]) !== 0) {
-                smoothScroll((focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
+                smoothScroll((focusSpeed = NumCast(doc.focusSpeed, 500)), this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
             }
         }
         const endFocus = async (moved: boolean) => (options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing);
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 71834607c..7f142727c 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -259,7 +259,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             const top = found.getBoundingClientRect().top;
             const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
             if (Math.floor(localTop[1]) !== 0) {
-                smoothScroll((focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
+                smoothScroll((focusSpeed = NumCast(doc.focusSpeed, 500)), this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
             }
         }
         const endFocus = async (moved: boolean) => options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing;
@@ -494,8 +494,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         super.onExternalDrop(e, {}, (docs: Doc[]) => {
             if (targInd === -1) {
                 this.addDocument(docs);
-            }
-            else {
+            } else {
                 const childDocs = this.childDocList;
                 if (childDocs) {
                     childDocs.splice(targInd, 0, ...docs);
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index f7b48adf6..49228a808 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -366,7 +366,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     focusFunc = (doc: Doc, options?: DocFocusOptions) => {
         const shrinkwrap = options?.originalTarget === this._document && this.view?.ComponentView?.shrinkWrap;
         if (shrinkwrap && this._document) {
-            const focusSpeed = 1000;
+            const focusSpeed = NumCast(this._document.focusSpeed, 500);
             shrinkwrap();
             this._document._viewTransition = `transform ${focusSpeed}ms`;
             setTimeout(
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 210370d39..ede113a9f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1146,7 +1146,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             }
             // focus on the document in the collection
             const didMove = !cantTransform && !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale);
-            const focusSpeed = options?.instant ? 0 : didMove ? (doc.focusSpeed !== undefined ? Number(doc.focusSpeed) : 500) : 0;
+            const focusSpeed = options?.instant ? 0 : didMove ? NumCast(doc.focusSpeed, 500) : 0;
             // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
             if (didMove) {
                 scale && (this.Document[this.scaleFieldKey] = scale);
@@ -1452,7 +1452,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     newPos.x !== lastPos.x ||
                     newPos.y !== lastPos.y ||
                     newPos.z !== lastPos.z ||
-                    newPos.zIndex !== lastPos.zIndex
+                    newPos.zIndex !== lastPos.zIndex ||
+                    newPos.transition !== lastPos.transition
                 ) {
                     this._layoutPoolData.set(entry[0], newPos);
                 }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 82557d36b..c07772b53 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -226,6 +226,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             PanelWidth: this.panelWidth,
             PanelHeight: this.panelHeight,
         };
+        console.log('WIdth = ' + this.panelWidth() + ' ' + this.dataProvider?.transition);
         return (
             <div
                 className={'collectionFreeFormDocumentView-container'}
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index eb6dad327..ade098917 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -7,7 +7,7 @@ import { ColorState, SketchPicker } from 'react-color';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
 import { Doc, DocListCast, DocListCastAsync, FieldResult } from '../../../../fields/Doc';
 import { Copy } from '../../../../fields/FieldSymbols';
-import { InkField, InkTool } from '../../../../fields/InkField';
+import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
 import { ObjectField } from '../../../../fields/ObjectField';
 import { listSpec } from '../../../../fields/Schema';
@@ -90,7 +90,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     constructor(props: any) {
         super(props);
         if ((Doc.ActivePresentation = this.rootDoc)) runInAction(() => (PresBox.Instance = this));
-        this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox
     }
 
     @observable public static Instance: PresBox;
@@ -127,12 +126,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @observable _treeViewMap: Map<Doc, number> = new Map();
 
     @computed get tagDocs() {
-        const tagDocs: Doc[] = [];
-        for (const doc of this.childDocs) {
-            const tagDoc = Cast(doc.presentationTargetDoc, Doc, null);
-            tagDocs.push(tagDoc);
-        }
-        return tagDocs;
+        return this.childDocs.map(doc => Cast(doc.presentationTargetDoc, Doc, null));
     }
     @computed get itemIndex() {
         return NumCast(this.rootDoc._itemIndex);
@@ -145,11 +139,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
     @computed get scrollable(): boolean {
         if (this.targetDoc.type === DocumentType.PDF || this.targetDoc.type === DocumentType.WEB || this.targetDoc.type === DocumentType.RTF || this.targetDoc._viewType === CollectionViewType.Stacking) return true;
-        else return false;
+        return false;
     }
     @computed get panable(): boolean {
         if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._viewType === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true;
-        else return false;
+        return false;
     }
     @computed get selectedDocumentView() {
         if (SelectionManager.Views().length) return SelectionManager.Views()[0];
@@ -202,8 +196,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @action
     updateCurrentPresentation = (pres?: Doc) => {
-        if (pres) Doc.ActivePresentation = pres;
-        else Doc.ActivePresentation = this.rootDoc;
+        Doc.ActivePresentation = pres ?? this.rootDoc;
         document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
         document.addEventListener('keydown', PresBox.keyEventsWrapper, true);
         this._presKeyEventsActive = true;
@@ -318,7 +311,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const activeItem: Doc = this.activeItem;
             const targetDoc: Doc = this.targetDoc;
             if (activeItem.presActiveFrame !== undefined) {
-                const transTime = NumCast(activeItem.presDuration, 500);
+                const transTime = NumCast(activeItem.presTransition, 500);
                 const context = DocCast(DocCast(activeItem.presentationTargetDoc).context);
                 if (context) {
                     const contextView = DocumentManager.Instance.getFirstDocumentView(context);
@@ -340,12 +333,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             }
             if (targetDoc) {
                 Doc.linkFollowHighlight(targetDoc.annotationOn instanceof Doc ? [targetDoc, targetDoc.annotationOn] : targetDoc);
-                targetDoc &&
-                    runInAction(() => {
-                        if (activeItem.presMovement === PresMovement.Jump) targetDoc.focusSpeed = 0;
-                        else targetDoc.focusSpeed = activeItem.presTransition ? activeItem.presTransition : 500;
-                    });
-                setTimeout(() => (targetDoc.focusSpeed = 500), this.activeItem.presTransition ? NumCast(this.activeItem.presTransition) + 10 : 510);
+                targetDoc && runInAction(() => (targetDoc.focusSpeed = activeItem.presMovement === PresMovement.Jump ? 0 : NumCast(activeItem.presTransition, 500)));
+                setTimeout(() => (targetDoc.focusSpeed = undefined), NumCast(targetDoc.focusSpeed) + 10);
             }
             if (targetDoc?.lastFrame !== undefined) {
                 targetDoc._currentFrame = 0;
@@ -356,14 +345,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             this.onHideDocument(); //Handles hide after/before
         }
     });
-
-    _navTimer!: NodeJS.Timeout;
-    navigateToView = (targetDoc: Doc, activeItem: Doc) => {
-        clearTimeout(this._navTimer);
-        const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document;
-        if (bestTarget) this._navTimer = PresBox.navigateToDoc(bestTarget, activeItem, false);
-    };
-
     static pinDataTypes(target: Doc) {
         const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(target.type as any) || target._viewType === CollectionViewType.Stacking;
         const pannable = [DocumentType.IMG].includes(target.type as any) || (target.type === DocumentType.COL && target._viewType === CollectionViewType.Freeform);
@@ -373,43 +354,29 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const textview = [DocumentType.RTF].includes(target.type as any) && target.activeFrame === undefined;
         return { scrollable, pannable, temporal, clippable, dataview, textview };
     }
-    // navigates to the bestTarget document by making sure it is on screen,
-    // then it applies the view specs stored in activeItem to
+
     @action
-    static navigateToDoc(bestTarget: Doc, activeItem: Doc, jumpToDoc: boolean) {
-        const transTime = NumCast(activeItem.presDuration, 500);
+    static restoreTargetDocView(bestTarget: Doc, activeItem: Doc, jumpToDoc: boolean) {
+        const transTime = NumCast(activeItem.presTransition, 500);
         const presTransitionTime = `all ${transTime}ms`;
         const { scrollable, pannable, temporal, clippable, dataview, textview } = this.pinDataTypes(bestTarget);
         bestTarget._viewTransition = presTransitionTime;
-        bestTarget._dataTransition = dataview || textview ? presTransitionTime : undefined;
-        bestTarget.x = NumCast(activeItem.presX);
-        bestTarget.y = NumCast(activeItem.presY);
-        bestTarget.width = NumCast(activeItem.presWidth);
-        bestTarget.height = NumCast(activeItem.presHeight);
         if (clippable) bestTarget._clipWidth = activeItem.presPinClipWidth;
         if (temporal) bestTarget._currentTimecode = activeItem.presStartTime;
         if (scrollable) bestTarget._scrollTop = activeItem.presPinViewScroll;
         if (dataview) Doc.GetProto(bestTarget).data = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData;
         if (textview) Doc.GetProto(bestTarget).text = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData;
         if (pannable) {
-            const contentBounds = Cast(activeItem.contentBounds, listSpec('number'));
+            bestTarget._panX = activeItem.presPinViewX;
+            bestTarget._panY = activeItem.presPinViewY;
+            bestTarget._viewScale = activeItem.presPinViewScale;
+            const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number'));
             if (contentBounds) {
-                bestTarget._panX = (contentBounds[0] + contentBounds[2]) / 2;
-                bestTarget._panY = (contentBounds[1] + contentBounds[3]) / 2;
                 const dv = DocumentManager.Instance.getDocumentView(bestTarget);
-                if (dv) {
-                    bestTarget._viewScale = Math.min(dv.props.PanelHeight() / (contentBounds[3] - contentBounds[1]), dv.props.PanelWidth() / (contentBounds[2] - contentBounds[0]));
-                }
-            } else {
-                bestTarget._panX = activeItem.presPinViewX;
-                bestTarget._panY = activeItem.presPinViewY;
-                bestTarget._viewScale = activeItem.presPinViewScale;
+                dv && (bestTarget._viewScale = Math.min(dv.props.PanelHeight() / (contentBounds[3] - contentBounds[1]), dv.props.PanelWidth() / (contentBounds[2] - contentBounds[0])));
             }
         }
-        return setTimeout(() => {
-            bestTarget._viewTransition = undefined;
-            if (dataview || textview) bestTarget._dataTransition = undefined;
-        }, transTime + 10);
+        return setTimeout(() => (bestTarget._viewTransition = undefined), transTime + 10);
     }
 
     /// copies values from the targetDoc (which is the prototype of the pinDoc) to
@@ -424,7 +391,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             pinDoc.presPinViewX = bounds.left + bounds.width / 2;
             pinDoc.presPinViewY = bounds.top + bounds.height / 2;
             pinDoc.presPinViewScale = pinProps.pinWithView.scale;
-            pinDoc.contentBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]);
+            pinDoc.presPinViewBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]);
         }
         if (pinProps?.pinDocView) {
             const { scrollable, pannable, temporal, clippable, dataview, textview } = this.pinDataTypes(pinDoc);
@@ -446,7 +413,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 const ph = NumCast(pinProps.panelHeight);
                 const ps = NumCast(pinDoc._viewScale);
                 if (pw && ph && ps) {
-                    pinDoc.contentBounds = new List<number>([panX - pw / 2 / ps, panY - ph / 2 / ps, panX + pw / 2 / ps, panY + ph / 2 / ps]);
+                    pinDoc.presPinViewBounds = new List<number>([panX - pw / 2 / ps, panY - ph / 2 / ps, panX + pw / 2 / ps, panY + ph / 2 / ps]);
                 }
                 pinDoc.presPinViewX = panX;
                 pinDoc.presPinViewY = panY;
@@ -455,6 +422,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
     }
 
+    _navTimer!: NodeJS.Timeout;
     /**
      * This method makes sure that cursor navigates to the element that
      * has the option open and last in the group.
@@ -504,38 +472,31 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 finished?.();
             }
         };
-        // If openDocument is selected then it should open the document for the user
-        if (activeItem.openDocument) {
-            LightboxView.SetLightboxDoc(targetDoc);
-            // openInTab(targetDoc);
-        } else if (curDoc.presMovement === PresMovement.Pan && targetDoc) {
-            LightboxView.SetLightboxDoc(undefined);
-            const transTime = NumCast(activeItem.presDuration, 500);
+        if (activeItem.presPinView && DocCast(targetDoc.context)?._currentFrame === undefined) {
+            const transTime = NumCast(activeItem.presTransition, 500);
             const presTransitionTime = `all ${transTime}ms`;
             targetDoc._dataTransition = presTransitionTime;
-            targetDoc.x = NumCast(activeItem.presX);
-            targetDoc.y = NumCast(activeItem.presY);
-            targetDoc.width = NumCast(activeItem.presWidth);
-            targetDoc.height = NumCast(activeItem.presHeight);
-            await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right
-        } else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) {
+            targetDoc.x = NumCast(activeItem.presX, NumCast(targetDoc.x));
+            targetDoc.y = NumCast(activeItem.presY, NumCast(targetDoc.y));
+            targetDoc.width = NumCast(activeItem.presWidth, NumCast(targetDoc.width));
+            targetDoc.height = NumCast(activeItem.presHeight, NumCast(targetDoc.height));
+            setTimeout(() => (targetDoc._dataTransition = undefined), transTime + 10);
+        }
+        // If openDocument is selected then it should open the document for the user
+        if (activeItem.openDocument) {
+            LightboxView.SetLightboxDoc(targetDoc); // openInTab(targetDoc);
+        } else if (targetDoc && curDoc.presMovement !== PresMovement.None && targetDoc) {
             LightboxView.SetLightboxDoc(undefined);
-
-            const transTime = NumCast(activeItem.presDuration, 500);
-            const presTransitionTime = `all ${transTime}ms`;
-            targetDoc._dataTransition = presTransitionTime;
-            targetDoc.x = NumCast(activeItem.presX);
-            targetDoc.y = NumCast(activeItem.presY);
-            targetDoc.width = NumCast(activeItem.presWidth);
-            targetDoc.height = NumCast(activeItem.presHeight);
-            //awaiting jump so that new scale can be found, since jumping is async
-            await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right
+            const zooming = curDoc.presMovement !== PresMovement.Pan;
+            DocumentManager.Instance.jumpToDocument(targetDoc, zooming, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom));
         }
         // After navigating to the document, if it is added as a presPinView then it will
         // adjust the pan and scale to that of the pinView when it was added.
         if (activeItem.presPinView) {
             // if targetDoc is not displayed but one of its aliases is, then we need to modify that alias, not the original target
-            this.navigateToView(targetDoc, activeItem);
+            clearTimeout(this._navTimer);
+            const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document;
+            if (bestTarget) this._navTimer = PresBox.restoreTargetDocView(bestTarget, activeItem, false);
         }
     };
 
@@ -1488,12 +1449,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     style={{ display: targetDoc.type === DocumentType.AUDIO ? 'none' : 'block' }}
                                     className={'toolbar-slider'}
                                     id="duration-slider"
-                                    onPointerDown={() => {
-                                        this._batch = UndoManager.StartBatch('presDuration');
-                                    }}
-                                    onPointerUp={() => {
-                                        if (this._batch) this._batch.end();
-                                    }}
+                                    onPointerDown={() => (this._batch = UndoManager.StartBatch('presDuration'))}
+                                    onPointerUp={() => this._batch?.end()}
                                     onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                                         e.stopPropagation();
                                         this.setDurationTime(e.target.value);
@@ -1650,28 +1607,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         });
     };
 
-    @computed get mediaStopSlides() {
-        const activeItem: Doc = this.activeItem;
-        const list = this.childDocs.map((doc, i) => {
-            if (i > this.itemIndex) {
-                return (
-                    <option>
-                        {i + 1}. {StrCast(doc.title)}
-                    </option>
-                );
-            }
-        });
-        return list;
-    }
-
     @computed get mediaOptionsDropdown() {
         const activeItem: Doc = this.activeItem;
         const targetDoc: Doc = this.targetDoc;
         const clipStart: number = NumCast(activeItem.clipStart);
         const clipEnd: number = NumCast(activeItem.clipEnd);
-        const duration = Math.round(NumCast(activeItem[`${Doc.LayoutFieldKey(activeItem)}-duration`]) * 10);
         const mediaStopDocInd: number = NumCast(activeItem.mediaStopDoc);
-        const mediaStopDocStr: string = mediaStopDocInd ? mediaStopDocInd + '. ' + this.childDocs[mediaStopDocInd - 1].title : '';
         if (activeItem && targetDoc) {
             return (
                 <div>
@@ -1842,55 +1783,53 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @computed get newDocumentToolbarDropdown() {
         return (
-            <div>
-                <div
-                    className={'presBox-toolbar-dropdown'}
-                    style={{ display: this.newDocumentTools && this.layoutDoc.presStatus === 'edit' ? 'inline-flex' : 'none' }}
-                    onClick={e => e.stopPropagation()}
-                    onPointerUp={e => e.stopPropagation()}
-                    onPointerDown={e => e.stopPropagation()}>
-                    <div className="layout-container" style={{ height: 'max-content' }}>
-                        <div
-                            className="layout"
-                            style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
-                            onClick={action(() => {
-                                this.layout = 'blank';
-                                this.createNewSlide(this.layout);
-                            })}
-                        />
-                        <div
-                            className="layout"
-                            style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
-                            onClick={action(() => {
-                                this.layout = 'title';
-                                this.createNewSlide(this.layout);
-                            })}>
-                            <div className="title">Title</div>
-                            <div className="subtitle">Subtitle</div>
-                        </div>
-                        <div
-                            className="layout"
-                            style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
-                            onClick={action(() => {
-                                this.layout = 'header';
-                                this.createNewSlide(this.layout);
-                            })}>
-                            <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>
-                                Section header
-                            </div>
+            <div
+                className={'presBox-toolbar-dropdown'}
+                style={{ display: this.newDocumentTools && this.layoutDoc.presStatus === 'edit' ? 'inline-flex' : 'none' }}
+                onClick={e => e.stopPropagation()}
+                onPointerUp={e => e.stopPropagation()}
+                onPointerDown={e => e.stopPropagation()}>
+                <div className="layout-container" style={{ height: 'max-content' }}>
+                    <div
+                        className="layout"
+                        style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+                        onClick={action(() => {
+                            this.layout = 'blank';
+                            this.createNewSlide(this.layout);
+                        })}
+                    />
+                    <div
+                        className="layout"
+                        style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+                        onClick={action(() => {
+                            this.layout = 'title';
+                            this.createNewSlide(this.layout);
+                        })}>
+                        <div className="title">Title</div>
+                        <div className="subtitle">Subtitle</div>
+                    </div>
+                    <div
+                        className="layout"
+                        style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+                        onClick={action(() => {
+                            this.layout = 'header';
+                            this.createNewSlide(this.layout);
+                        })}>
+                        <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>
+                            Section header
                         </div>
-                        <div
-                            className="layout"
-                            style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
-                            onClick={action(() => {
-                                this.layout = 'content';
-                                this.createNewSlide(this.layout);
-                            })}>
-                            <div className="title" style={{ alignSelf: 'center' }}>
-                                Title
-                            </div>
-                            <div className="content">Text goes here</div>
+                    </div>
+                    <div
+                        className="layout"
+                        style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+                        onClick={action(() => {
+                            this.layout = 'content';
+                            this.createNewSlide(this.layout);
+                        })}>
+                        <div className="title" style={{ alignSelf: 'center' }}>
+                            Title
                         </div>
+                        <div className="content">Text goes here</div>
                     </div>
                 </div>
             </div>
@@ -1904,73 +1843,71 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @computed get newDocumentDropdown() {
         return (
-            <div>
-                <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
-                    <div className="ribbon-box">
-                        Slide Title: <br></br>
-                        <input
-                            className="ribbon-textInput"
-                            placeholder="..."
-                            type="text"
-                            name="fname"
-                            onChange={e => {
-                                e.stopPropagation();
-                                e.preventDefault();
-                                runInAction(() => (this.title = e.target.value));
-                            }}></input>
+            <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+                <div className="ribbon-box">
+                    Slide Title: <br></br>
+                    <input
+                        className="ribbon-textInput"
+                        placeholder="..."
+                        type="text"
+                        name="fname"
+                        onChange={e => {
+                            e.stopPropagation();
+                            e.preventDefault();
+                            runInAction(() => (this.title = e.target.value));
+                        }}></input>
+                </div>
+                <div className="ribbon-box">
+                    Choose type:
+                    <div className="ribbon-doubleButton">
+                        <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? '' : Colors.LIGHT_BLUE }} onClick={action(() => (this.addFreeform = !this.addFreeform))}>
+                            Text
+                        </div>
+                        <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? Colors.LIGHT_BLUE : '' }} onClick={action(() => (this.addFreeform = !this.addFreeform))}>
+                            Freeform
+                        </div>
                     </div>
-                    <div className="ribbon-box">
-                        Choose type:
-                        <div className="ribbon-doubleButton">
-                            <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? '' : Colors.LIGHT_BLUE }} onClick={action(() => (this.addFreeform = !this.addFreeform))}>
-                                Text
-                            </div>
-                            <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? Colors.LIGHT_BLUE : '' }} onClick={action(() => (this.addFreeform = !this.addFreeform))}>
-                                Freeform
+                </div>
+                <div className="ribbon-box" style={{ display: this.addFreeform ? 'grid' : 'none' }}>
+                    Preset layouts:
+                    <div className="layout-container" style={{ height: this.openLayouts ? 'max-content' : '75px' }}>
+                        <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'blank'))} />
+                        <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'title'))}>
+                            <div className="title">Title</div>
+                            <div className="subtitle">Subtitle</div>
+                        </div>
+                        <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'header'))}>
+                            <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>
+                                Section header
                             </div>
                         </div>
-                    </div>
-                    <div className="ribbon-box" style={{ display: this.addFreeform ? 'grid' : 'none' }}>
-                        Preset layouts:
-                        <div className="layout-container" style={{ height: this.openLayouts ? 'max-content' : '75px' }}>
-                            <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'blank'))} />
-                            <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'title'))}>
-                                <div className="title">Title</div>
-                                <div className="subtitle">Subtitle</div>
+                        <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'content'))}>
+                            <div className="title" style={{ alignSelf: 'center' }}>
+                                Title
                             </div>
-                            <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'header'))}>
-                                <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>
-                                    Section header
-                                </div>
+                            <div className="content">Text goes here</div>
+                        </div>
+                        <div className="layout" style={{ border: this.layout === 'twoColumns' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'twoColumns'))}>
+                            <div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}>
+                                Title
                             </div>
-                            <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'content'))}>
-                                <div className="title" style={{ alignSelf: 'center' }}>
-                                    Title
-                                </div>
-                                <div className="content">Text goes here</div>
+                            <div className="content" style={{ gridColumn: 1, gridRow: 2 }}>
+                                Column one text
                             </div>
-                            <div className="layout" style={{ border: this.layout === 'twoColumns' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'twoColumns'))}>
-                                <div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}>
-                                    Title
-                                </div>
-                                <div className="content" style={{ gridColumn: 1, gridRow: 2 }}>
-                                    Column one text
-                                </div>
-                                <div className="content" style={{ gridColumn: 2, gridRow: 2 }}>
-                                    Column two text
-                                </div>
+                            <div className="content" style={{ gridColumn: 2, gridRow: 2 }}>
+                                Column two text
                             </div>
                         </div>
-                        <div className="open-layout" onClick={action(() => (this.openLayouts = !this.openLayouts))}>
-                            <FontAwesomeIcon style={{ transition: 'all 0.3s', transform: this.openLayouts ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'caret-down'} size={'lg'} />
-                        </div>
                     </div>
-                    <div className="ribbon-final-box">
-                        <div
-                            className={this.title !== '' && ((this.addFreeform && this.layout !== '') || !this.addFreeform) ? 'ribbon-final-button-hidden' : 'ribbon-final-button'}
-                            onClick={() => this.createNewSlide(this.layout, this.title, this.addFreeform)}>
-                            Create New Slide
-                        </div>
+                    <div className="open-layout" onClick={action(() => (this.openLayouts = !this.openLayouts))}>
+                        <FontAwesomeIcon style={{ transition: 'all 0.3s', transform: this.openLayouts ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'caret-down'} size={'lg'} />
+                    </div>
+                </div>
+                <div className="ribbon-final-box">
+                    <div
+                        className={this.title !== '' && ((this.addFreeform && this.layout !== '') || !this.addFreeform) ? 'ribbon-final-button-hidden' : 'ribbon-final-button'}
+                        onClick={() => this.createNewSlide(this.layout, this.title, this.addFreeform)}>
+                        Create New Slide
                     </div>
                 </div>
             </div>
@@ -2105,115 +2042,109 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const activeFontColor = targetDoc['pres-text-color'] ? StrCast(targetDoc['pres-text-color']) : 'Black';
             const viewedFontColor = targetDoc['pres-text-viewed-color'] ? StrCast(targetDoc['pres-text-viewed-color']) : 'Black';
             return (
-                <div>
-                    <div
-                        className={`presBox-ribbon ${this.progressivizeTools && this.layoutDoc.presStatus === 'edit' ? 'active' : ''}`}
-                        onClick={e => e.stopPropagation()}
-                        onPointerUp={e => e.stopPropagation()}
-                        onPointerDown={e => e.stopPropagation()}>
-                        <div className="ribbon-box">
-                            {this.stringType} selected
-                            <div
-                                className="ribbon-doubleButton"
-                                style={{
-                                    borderTop: 'solid 1px darkgrey',
-                                    display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG || targetDoc.type === DocumentType.RTF ? 'inline-flex' : 'none',
-                                }}>
-                                <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.progressivizeChild}>
-                                    Contents
-                                </div>
-                                <div className="ribbon-toggle" style={{ opacity: activeItem.presProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.editProgressivize}>
-                                    Edit
-                                </div>
-                            </div>
-                            <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? 'inline-flex' : 'none' }}>
-                                <div className="presBox-subheading">Active text color</div>
-                                <div
-                                    className="ribbon-colorBox"
-                                    style={{ backgroundColor: activeFontColor, height: 15, width: 15 }}
-                                    onClick={action(() => {
-                                        this.openActiveColorPicker = !this.openActiveColorPicker;
-                                    })}></div>
+                <div className={`presBox-ribbon ${this.progressivizeTools && this.layoutDoc.presStatus === 'edit' ? 'active' : ''}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+                    <div className="ribbon-box">
+                        {this.stringType} selected
+                        <div
+                            className="ribbon-doubleButton"
+                            style={{
+                                borderTop: 'solid 1px darkgrey',
+                                display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG || targetDoc.type === DocumentType.RTF ? 'inline-flex' : 'none',
+                            }}>
+                            <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.progressivizeChild}>
+                                Contents
                             </div>
-                            {this.activeColorPicker}
-                            <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? 'inline-flex' : 'none' }}>
-                                <div className="presBox-subheading">Viewed font color</div>
-                                <div className="ribbon-colorBox" style={{ backgroundColor: viewedFontColor, height: 15, width: 15 }} onClick={action(() => (this.openViewedColorPicker = !this.openViewedColorPicker))}></div>
+                            <div className="ribbon-toggle" style={{ opacity: activeItem.presProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.editProgressivize}>
+                                Edit
                             </div>
-                            {this.viewedColorPicker}
+                        </div>
+                        <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? 'inline-flex' : 'none' }}>
+                            <div className="presBox-subheading">Active text color</div>
                             <div
-                                className="ribbon-doubleButton"
-                                style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG ? 'inline-flex' : 'none' }}>
-                                <div className="ribbon-toggle" style={{ backgroundColor: activeItem.zoomProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.progressivizeZoom}>
-                                    Zoom
-                                </div>
-                                <div className="ribbon-toggle" style={{ opacity: activeItem.zoomProgressivize ? 1 : 0.4, backgroundColor: activeItem.editZoomProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.editZoomProgressivize}>
-                                    Edit
-                                </div>
+                                className="ribbon-colorBox"
+                                style={{ backgroundColor: activeFontColor, height: 15, width: 15 }}
+                                onClick={action(() => {
+                                    this.openActiveColorPicker = !this.openActiveColorPicker;
+                                })}></div>
+                        </div>
+                        {this.activeColorPicker}
+                        <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? 'inline-flex' : 'none' }}>
+                            <div className="presBox-subheading">Viewed font color</div>
+                            <div className="ribbon-colorBox" style={{ backgroundColor: viewedFontColor, height: 15, width: 15 }} onClick={action(() => (this.openViewedColorPicker = !this.openViewedColorPicker))}></div>
+                        </div>
+                        {this.viewedColorPicker}
+                        <div
+                            className="ribbon-doubleButton"
+                            style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG ? 'inline-flex' : 'none' }}>
+                            <div className="ribbon-toggle" style={{ backgroundColor: activeItem.zoomProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.progressivizeZoom}>
+                                Zoom
                             </div>
-                            <div
-                                className="ribbon-doubleButton"
-                                style={{
-                                    borderTop: 'solid 1px darkgrey',
-                                    display: targetDoc._viewType === 'stacking' || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF ? 'inline-flex' : 'none',
-                                }}>
-                                <div className="ribbon-toggle" style={{ backgroundColor: activeItem.scrollProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.progressivizeScroll}>
-                                    Scroll
-                                </div>
-                                <div className="ribbon-toggle" style={{ opacity: activeItem.scrollProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editScrollProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.editScrollProgressivize}>
-                                    Edit
-                                </div>
+                            <div className="ribbon-toggle" style={{ opacity: activeItem.zoomProgressivize ? 1 : 0.4, backgroundColor: activeItem.editZoomProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.editZoomProgressivize}>
+                                Edit
                             </div>
                         </div>
-                        <div className="ribbon-final-box">
-                            Frames
-                            <div className="ribbon-doubleButton">
-                                <div className="ribbon-frameSelector">
-                                    <div
-                                        key="back"
-                                        title="back frame"
-                                        className="backKeyframe"
-                                        onClick={e => {
-                                            e.stopPropagation();
-                                            this.prevKeyframe(targetDoc, activeItem);
-                                        }}>
-                                        <FontAwesomeIcon icon={'caret-left'} size={'lg'} />
-                                    </div>
-                                    <div
-                                        key="num"
-                                        title="toggle view all"
-                                        className="numKeyframe"
-                                        style={{ color: targetDoc.keyFrameEditing ? 'white' : 'black', backgroundColor: targetDoc.keyFrameEditing ? Colors.MEDIUM_BLUE : Colors.LIGHT_BLUE }}
-                                        onClick={action(() => (targetDoc.keyFrameEditing = !targetDoc.keyFrameEditing))}>
-                                        {NumCast(targetDoc._currentFrame)}
-                                    </div>
-                                    <div
-                                        key="fwd"
-                                        title="forward frame"
-                                        className="fwdKeyframe"
-                                        onClick={e => {
-                                            e.stopPropagation();
-                                            this.nextKeyframe(targetDoc, activeItem);
-                                        }}>
-                                        <FontAwesomeIcon icon={'caret-right'} size={'lg'} />
-                                    </div>
-                                </div>
-                                <Tooltip
-                                    title={
-                                        <>
-                                            <div className="dash-tooltip">{'Last frame'}</div>
-                                        </>
-                                    }>
-                                    <div className="ribbon-property">{NumCast(targetDoc.lastFrame)}</div>
-                                </Tooltip>
+                        <div
+                            className="ribbon-doubleButton"
+                            style={{
+                                borderTop: 'solid 1px darkgrey',
+                                display: targetDoc._viewType === 'stacking' || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF ? 'inline-flex' : 'none',
+                            }}>
+                            <div className="ribbon-toggle" style={{ backgroundColor: activeItem.scrollProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.progressivizeScroll}>
+                                Scroll
                             </div>
-                            <div className="ribbon-frameList">
-                                {this.frameListHeader}
-                                {this.frameList}
+                            <div className="ribbon-toggle" style={{ opacity: activeItem.scrollProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editScrollProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.editScrollProgressivize}>
+                                Edit
                             </div>
-                            <div className="ribbon-toggle" style={{ height: 20, backgroundColor: Colors.LIGHT_BLUE }} onClick={() => console.log(' TODO: play frames')}>
-                                Play
+                        </div>
+                    </div>
+                    <div className="ribbon-final-box">
+                        Frames
+                        <div className="ribbon-doubleButton">
+                            <div className="ribbon-frameSelector">
+                                <div
+                                    key="back"
+                                    title="back frame"
+                                    className="backKeyframe"
+                                    onClick={e => {
+                                        e.stopPropagation();
+                                        this.prevKeyframe(targetDoc, activeItem);
+                                    }}>
+                                    <FontAwesomeIcon icon={'caret-left'} size={'lg'} />
+                                </div>
+                                <div
+                                    key="num"
+                                    title="toggle view all"
+                                    className="numKeyframe"
+                                    style={{ color: targetDoc.keyFrameEditing ? 'white' : 'black', backgroundColor: targetDoc.keyFrameEditing ? Colors.MEDIUM_BLUE : Colors.LIGHT_BLUE }}
+                                    onClick={action(() => (targetDoc.keyFrameEditing = !targetDoc.keyFrameEditing))}>
+                                    {NumCast(targetDoc._currentFrame)}
+                                </div>
+                                <div
+                                    key="fwd"
+                                    title="forward frame"
+                                    className="fwdKeyframe"
+                                    onClick={e => {
+                                        e.stopPropagation();
+                                        this.nextKeyframe(targetDoc, activeItem);
+                                    }}>
+                                    <FontAwesomeIcon icon={'caret-right'} size={'lg'} />
+                                </div>
                             </div>
+                            <Tooltip
+                                title={
+                                    <>
+                                        <div className="dash-tooltip">{'Last frame'}</div>
+                                    </>
+                                }>
+                                <div className="ribbon-property">{NumCast(targetDoc.lastFrame)}</div>
+                            </Tooltip>
+                        </div>
+                        <div className="ribbon-frameList">
+                            {this.frameListHeader}
+                            {this.frameList}
+                        </div>
+                        <div className="ribbon-toggle" style={{ height: 20, backgroundColor: Colors.LIGHT_BLUE }} onClick={() => console.log(' TODO: play frames')}>
+                            Play
                         </div>
                     </div>
                 </div>
@@ -2233,7 +2164,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @undoBatch
     @action
     switchPresented = (color: ColorState) => {
-        const activeItem: Doc = this.activeItem;
         const targetDoc: Doc = this.targetDoc;
         const val = String(color.hex);
         targetDoc['pres-text-viewed-color'] = val;
@@ -2241,25 +2171,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     @computed get activeColorPicker() {
-        const activeItem: Doc = this.activeItem;
-        const targetDoc: Doc = this.targetDoc;
         return !this.openActiveColorPicker ? null : (
             <SketchPicker
                 onChange={this.switchActive}
                 presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
-                color={StrCast(targetDoc['pres-text-color'])}
+                color={StrCast(this.targetDoc['pres-text-color'])}
             />
         );
     }
 
     @computed get viewedColorPicker() {
-        const activeItem: Doc = this.activeItem;
-        const targetDoc: Doc = this.targetDoc;
         return !this.openViewedColorPicker ? null : (
             <SketchPicker
                 onChange={this.switchPresented}
                 presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
-                color={StrCast(targetDoc['pres-text-viewed-color'])}
+                color={StrCast(this.targetDoc['pres-text-viewed-color'])}
             />
         );
     }
@@ -2422,10 +2348,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     @observable
-    toggleDisplayMovement = (doc: Doc) => {
-        if (doc.displayMovement) doc.displayMovement = false;
-        else doc.displayMovement = true;
-    };
+    toggleDisplayMovement = (doc: Doc) => (doc.displayMovement = !doc.displayMovement);
 
     @action
     checkList = (doc: Doc, list: any): number => {
@@ -2538,24 +2461,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
     };
 
-    @computed get moreInfoDropdown() {
-        return <div></div>;
-    }
-
     @computed
     get toolbarWidth(): number {
-        const width = this.props.PanelWidth();
-        return width;
+        return this.props.PanelWidth();
     }
 
     @action
-    toggleProperties = () => {
-        if (SettingsManager.propertiesWidth > 0) {
-            SettingsManager.propertiesWidth = 0;
-        } else {
-            SettingsManager.propertiesWidth = 250;
-        }
-    };
+    toggleProperties = () => (SettingsManager.propertiesWidth = SettingsManager.propertiesWidth > 0 ? 0 : 250);
 
     @computed get toolbar() {
         const propIcon = SettingsManager.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
@@ -2677,10 +2589,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @action
-    getList = (list: any): List<number> => {
-        const x: List<number> = list;
-        return x;
-    };
+    getList = (list: any): List<number> => list;
 
     @action
     updateList = (list: any): List<number> => {
@@ -2709,12 +2618,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             <div className="frameList-header">
                 &nbsp; Frames {this.panable ? <i>Panable</i> : this.scrollable ? <i>Scrollable</i> : null}
                 <div className={'frameList-headerButtons'}>
-                    <Tooltip
-                        title={
-                            <>
-                                <div className="dash-tooltip">{'Add frame by example'}</div>
-                            </>
-                        }>
+                    <Tooltip title={<div className="dash-tooltip">{'Add frame by example'}</div>}>
                         <div
                             className={'headerButton'}
                             onClick={e => {
@@ -2724,12 +2628,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             <FontAwesomeIcon icon={'plus'} onPointerDown={e => e.stopPropagation()} />
                         </div>
                     </Tooltip>
-                    <Tooltip
-                        title={
-                            <>
-                                <div className="dash-tooltip">{'Edit in collection'}</div>
-                            </>
-                        }>
+                    <Tooltip title={<div className="dash-tooltip">{'Edit in collection'}</div>}>
                         <div
                             className={'headerButton'}
                             onClick={e => {
@@ -2746,7 +2645,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @computed get frameList() {
         const activeItem: Doc = this.activeItem;
-        const targetDoc: Doc = this.targetDoc;
         const frameList: List<number> = this.getList(activeItem.frameList);
         if (frameList) {
             const frameItems = frameList.map(value => <div className="framList-item"></div>);
@@ -2755,18 +2653,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @computed get playButtonFrames() {
-        const targetDoc: Doc = this.targetDoc;
-        return (
-            <>
-                {this.targetDoc ? (
-                    <div className="presPanel-button-frame" style={{ display: targetDoc.lastFrame !== undefined && targetDoc.lastFrame >= 0 ? 'inline-flex' : 'none' }}>
-                        <div>{NumCast(targetDoc._currentFrame)}</div>
-                        <div className="presPanel-divider" style={{ border: 'solid 0.5px white', height: '60%' }}></div>
-                        <div>{NumCast(targetDoc.lastFrame)}</div>
-                    </div>
-                ) : null}
-            </>
-        );
+        const targetDoc = this.targetDoc;
+        return this.targetDoc ? (
+            <div className="presPanel-button-frame" style={{ display: targetDoc.lastFrame !== undefined && targetDoc.lastFrame >= 0 ? 'inline-flex' : 'none' }}>
+                <div>{NumCast(targetDoc._currentFrame)}</div>
+                <div className="presPanel-divider" style={{ border: 'solid 0.5px white', height: '60%' }}></div>
+                <div>{NumCast(targetDoc.lastFrame)}</div>
+            </div>
+        ) : null;
     }
 
     @computed get playButtons() {
@@ -2775,12 +2669,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         // Case 1: There are still other frames and should go through all frames before going to next slide
         return (
             <div className="presPanelOverlay" style={{ display: this.layoutDoc.presStatus !== 'edit' ? 'inline-flex' : 'none' }}>
-                <Tooltip
-                    title={
-                        <>
-                            <div className="dash-tooltip">{'Loop'}</div>
-                        </>
-                    }>
+                <Tooltip title={<div className="dash-tooltip">{'Loop'}</div>}>
                     <div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : 'white' }} onClick={() => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop)}>
                         <FontAwesomeIcon icon={'redo-alt'} />
                     </div>
@@ -2798,12 +2687,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     }}>
                     <FontAwesomeIcon icon={'arrow-left'} />
                 </div>
-                <Tooltip
-                    title={
-                        <>
-                            <div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>
-                        </>
-                    }>
+                <Tooltip title={<div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>}>
                     <div className="presPanel-button" onClick={this.startOrPause}>
                         <FontAwesomeIcon icon={this.layoutDoc.presStatus === PresStatus.Autoplay ? 'pause' : 'play'} />
                     </div>
@@ -2821,12 +2705,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     <FontAwesomeIcon icon={'arrow-right'} />
                 </div>
                 <div className="presPanel-divider"></div>
-                <Tooltip
-                    title={
-                        <>
-                            <div className="dash-tooltip">{'Click to return to 1st slide'}</div>
-                        </>
-                    }>
+                <Tooltip title={<div className="dash-tooltip">{'Click to return to 1st slide'}</div>}>
                     <div className="presPanel-button" style={{ border: 'solid 1px white' }} onClick={() => this.gotoDocument(0, this.activeItem)}>
                         <b>1</b>
                     </div>
@@ -2931,12 +2810,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return DocListCast(Doc.MyOverlayDocs?.data).includes(this.rootDoc) ? (
             <div className="miniPres" onClick={e => e.stopPropagation()}>
                 <div className="presPanelOverlay" style={{ display: 'inline-flex', height: 30, background: '#323232', top: 0, zIndex: 3000000, boxShadow: presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}>
-                    <Tooltip
-                        title={
-                            <>
-                                <div className="dash-tooltip">{'Loop'}</div>
-                            </>
-                        }>
+                    <Tooltip title={<div className="dash-tooltip">{'Loop'}</div>}>
                         <div
                             className="presPanel-button"
                             style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : undefined }}
@@ -2948,12 +2822,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     <div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.prevClicked, false, false)}>
                         <FontAwesomeIcon icon={'arrow-left'} />
                     </div>
-                    <Tooltip
-                        title={
-                            <>
-                                <div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>
-                            </>
-                        }>
+                    <Tooltip title={<div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>}>
                         <div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.startOrPause, false, false)}>
                             <FontAwesomeIcon icon={this.layoutDoc.presStatus === 'auto' ? 'pause' : 'play'} />
                         </div>
@@ -2962,12 +2831,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <FontAwesomeIcon icon={'arrow-right'} />
                     </div>
                     <div className="presPanel-divider"></div>
-                    <Tooltip
-                        title={
-                            <>
-                                <div className="dash-tooltip">{'Click to return to 1st slide'}</div>
-                            </>
-                        }>
+                    <Tooltip title={<div className="dash-tooltip">{'Click to return to 1st slide'}</div>}>
                         <div className="presPanel-button" style={{ border: 'solid 1px white' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.gotoDocument(0, this.activeItem), false, false)}>
                             <b>1</b>
                         </div>
@@ -3033,5 +2897,17 @@ ScriptingGlobals.add(function navigateToDoc(bestTarget: Doc, activeItem: Doc) {
         CollectionDockingView.AddSplit(doc, 'right');
         finished?.();
     };
-    DocumentManager.Instance.jumpToDocument(bestTarget, true, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, () => PresBox.navigateToDoc(bestTarget, activeItem, true), undefined, true, NumCast(activeItem.presZoom));
+    DocumentManager.Instance.jumpToDocument(
+        bestTarget,
+        true,
+        openInTab,
+        srcContext ? [srcContext] : [],
+        undefined,
+        undefined,
+        undefined,
+        () => PresBox.restoreTargetDocView(bestTarget, activeItem, true),
+        undefined,
+        true,
+        NumCast(activeItem.presZoom)
+    );
 });
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 38a87c34b..c78828a78 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -162,15 +162,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         ));
         return groupSlides;
     }
-    @computed get duration() {
-        let durationInS: number;
-        if (this.rootDoc.type === DocumentType.AUDIO || this.rootDoc.type === DocumentType.VID) {
-            durationInS = NumCast(this.rootDoc.presEndTime) - NumCast(this.rootDoc.presStartTime);
-            durationInS = Math.round(durationInS * 10) / 10;
-        } else if (this.rootDoc.presDuration) durationInS = NumCast(this.rootDoc.presDuration) / 1000;
-        else durationInS = 2;
-        return 'D: ' + durationInS + 's';
-    }
 
     @computed get transition() {
         let transitionInS: number;
-- 
cgit v1.2.3-70-g09d2


From a4ce2913b8a15cdd4670002a4a74f1d86601348e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 26 Aug 2022 18:34:41 -0400
Subject: a bunch of mostly decorative cleanup to presBox

---
 src/client/views/PropertiesView.tsx              |   6 +-
 src/client/views/collections/TabDocView.tsx      |   2 +-
 src/client/views/nodes/trails/PresBox.tsx        | 558 +++++++----------------
 src/client/views/nodes/trails/PresElementBox.tsx |   2 +-
 src/client/views/nodes/trails/PresEnums.ts       |  42 +-
 5 files changed, 186 insertions(+), 424 deletions(-)

(limited to 'src/client/views/nodes/trails/PresBox.tsx')

diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 33f17047b..2708c561d 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -58,7 +58,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     }
     @computed get selectedDocumentView() {
         if (SelectionManager.Views().length) return SelectionManager.Views()[0];
-        if (PresBox.Instance?._selectedArray.size) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc);
+        if (PresBox.Instance?.selectedArray.size) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc);
         return undefined;
     }
     @computed get isPres(): boolean {
@@ -1612,7 +1612,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                 );
             }
             if (this.isPres) {
-                const selectedItem: boolean = PresBox.Instance?._selectedArray.size > 0;
+                const selectedItem: boolean = PresBox.Instance?.selectedArray.size > 0;
                 const type = PresBox.Instance.activeItem?.type;
                 return (
                     <div className="propertiesView" style={{ width: this.props.width }}>
@@ -1622,7 +1622,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                         <div className="propertiesView-name" style={{ borderBottom: 0 }}>
                             {this.editableTitle}
                             <div className="propertiesView-presSelected">
-                                <div className="propertiesView-selectedCount">{PresBox.Instance?._selectedArray.size} selected</div>
+                                <div className="propertiesView-selectedCount">{PresBox.Instance?.selectedArray.size} selected</div>
                                 <div className="propertiesView-selectedList">{PresBox.Instance?.listOfSelected}</div>
                             </div>
                         </div>
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 49228a808..bead5825c 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -239,7 +239,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 pinDoc.treeViewGrowsHorizontally = true; // the document expands horizontally when displayed as a tree view header
                 pinDoc.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
                 const presArray: Doc[] = PresBox.Instance?.sortArray();
-                const size: number = PresBox.Instance?._selectedArray.size;
+                const size: number = PresBox.Instance?.selectedArray.size;
                 const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
                 const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null);
 
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index ade098917..ac68ea281 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -12,7 +12,7 @@ import { List } from '../../../../fields/List';
 import { ObjectField } from '../../../../fields/ObjectField';
 import { listSpec } from '../../../../fields/Schema';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents, StopEvent } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
@@ -86,6 +86,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     private _disposers: { [name: string]: IReactionDisposer } = {};
+    public selectedArray = new ObservableSet<Doc>();
 
     constructor(props: any) {
         super(props);
@@ -93,6 +94,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @observable public static Instance: PresBox;
+    @observable static startMarquee: boolean = false; // onclick "+ new slide" in presentation mode, set as true, then when marquee selection finish, onPointerUp automatically triggers PinWithView
 
     @observable _isChildActive = false;
     @observable _moveOnFromAudio: boolean = true;
@@ -103,14 +105,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @observable _dragArray: HTMLElement[] = [];
     @observable _pathBoolean: boolean = false;
     @observable _expandBoolean: boolean = false;
-
-    @observable static startMarquee: boolean = false; // onclick "+ new slide" in presentation mode, set as true, then when marquee selection finish, onPointerUp automatically triggers PinWithView
-    @observable private transitionTools: boolean = false;
-    @observable private newDocumentTools: boolean = false;
-    @observable private progressivizeTools: boolean = false;
-    @observable private openMovementDropdown: boolean = false;
-    @observable private openEffectDropdown: boolean = false;
-    @observable private presentTools: boolean = false;
+    @observable _transitionTools: boolean = false;
+    @observable _newDocumentTools: boolean = false;
+    @observable _progressivizeTools: boolean = false;
+    @observable _openMovementDropdown: boolean = false;
+    @observable _openEffectDropdown: boolean = false;
+    @observable _presentTools: boolean = false;
+    @observable _treeViewMap: Map<Doc, number> = new Map();
     @computed get isTreeOrStack() {
         return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._viewType) as any);
     }
@@ -123,8 +124,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed get childDocs() {
         return DocListCast(this.rootDoc[this.presFieldKey]);
     }
-    @observable _treeViewMap: Map<Doc, number> = new Map();
-
     @computed get tagDocs() {
         return this.childDocs.map(doc => Cast(doc.presentationTargetDoc, Doc, null));
     }
@@ -137,22 +136,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed get targetDoc() {
         return Cast(this.activeItem?.presentationTargetDoc, Doc, null);
     }
-    @computed get scrollable(): boolean {
+    @computed get scrollable() {
         if (this.targetDoc.type === DocumentType.PDF || this.targetDoc.type === DocumentType.WEB || this.targetDoc.type === DocumentType.RTF || this.targetDoc._viewType === CollectionViewType.Stacking) return true;
         return false;
     }
-    @computed get panable(): boolean {
+    @computed get panable() {
         if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._viewType === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true;
         return false;
     }
     @computed get selectedDocumentView() {
         if (SelectionManager.Views().length) return SelectionManager.Views()[0];
-        if (this._selectedArray.size) return DocumentManager.Instance.getDocumentView(this.rootDoc);
+        if (this.selectedArray.size) return DocumentManager.Instance.getDocumentView(this.rootDoc);
     }
-    @computed get isPres(): boolean {
+    @computed get isPres() {
         document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
         if (this.selectedDoc?.type === DocumentType.PRES) {
-            document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
             document.addEventListener('keydown', PresBox.keyEventsWrapper, true);
             return true;
         }
@@ -161,10 +159,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed get selectedDoc() {
         return this.selectedDocumentView?.rootDoc;
     }
-    _selectedArray = new ObservableSet<Doc>();
-    clearSelectedArray = () => this._selectedArray.clear();
-    addToSelectedArray = (doc: Doc) => this._selectedArray.add(doc);
-    removeFromSelectedArray = (doc: Doc) => this._selectedArray.delete(doc);
+    clearSelectedArray = () => this.selectedArray.clear();
+    addToSelectedArray = (doc: Doc) => this.selectedArray.add(doc);
+    removeFromSelectedArray = (doc: Doc) => this.selectedArray.delete(doc);
 
     _unmounting = false;
     @action
@@ -448,7 +445,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             this.layoutDoc.presCollection = srcContext;
         }
         const presStatus = this.rootDoc.presStatus;
-        const selViewCache = Array.from(this._selectedArray);
+        const selViewCache = Array.from(this.selectedArray);
         const dragViewCache = Array.from(this._dragArray);
         const eleViewCache = Array.from(this._eleArray);
         const self = this;
@@ -756,27 +753,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
     });
 
-    setMovementName = action((movement: any, activeItem: Doc): string => {
-        let output: string = 'none';
-        switch (movement) {
-            case PresMovement.Zoom:
-                output = 'Pan & Zoom';
-                break; //Pan and zoom
-            case PresMovement.Pan:
-                output = 'Pan';
-                break; //Pan
-            case PresMovement.Jump:
-                output = 'Jump cut';
-                break; //Jump Cut
-            case PresMovement.None:
-                output = 'None';
-                break; //None
-            default:
-                output = 'Zoom';
-                activeItem.presMovement = 'zoom';
-                break; //default set as zoom
+    movementName = action((activeItem: Doc) => {
+        if (![PresMovement.Zoom, PresMovement.Pan, PresMovement.Jump, PresMovement.None].includes(StrCast(activeItem.presMovement) as any)) {
+            activeItem.presMovement = 'zoom';
         }
-        return output;
+        return StrCast(activeItem.presMovement);
     });
 
     whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isChildActive = isActive)));
@@ -822,16 +803,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     /**
      * For sorting the array so that the order is maintained when it is dropped.
      */
-    @action
-    sortArray = (): Doc[] => {
-        return this.childDocs.filter(doc => this._selectedArray.has(doc));
-    };
+    sortArray = () => this.childDocs.filter(doc => this.selectedArray.has(doc));
 
     /**
      * Method to get the list of selected items in the order in which they have been selected
      */
     @computed get listOfSelected() {
-        return Array.from(this._selectedArray).map((doc: Doc, index: any) => {
+        return Array.from(this.selectedArray).map((doc: Doc, index: any) => {
             const curDoc = Cast(doc, Doc, null);
             const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null);
             if (curDoc && curDoc === this.activeItem)
@@ -874,24 +852,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     //Command click
     @action
     multiSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => {
-        if (!this._selectedArray.has(doc)) {
+        if (!this.selectedArray.has(doc)) {
             this.addToSelectedArray(doc);
             this._eleArray.push(ref);
             this._dragArray.push(drag);
         } else {
             this.removeFromSelectedArray(doc);
-            this.removeFromArray(this._eleArray, doc);
-            this.removeFromArray(this._dragArray, doc);
+            this._eleArray.splice(this._eleArray.indexOf(ref));
+            this._dragArray.splice(this._dragArray.indexOf(drag));
         }
         this.selectPres();
     };
 
-    removeFromArray = (arr: any[], val: any) => {
-        const index: number = arr.indexOf(val);
-        const ret: any[] = arr.splice(index, 1);
-        arr = ret;
-    };
-
     //Shift click
     @action
     shiftSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => {
@@ -924,9 +896,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         else this.regularSelect(doc, ref, drag, focus);
     };
 
-    static keyEventsWrapper = (e: KeyboardEvent) => {
-        PresBox.Instance.keyEvents(e);
-    };
+    static keyEventsWrapper = (e: KeyboardEvent) => PresBox.Instance.keyEvents(e);
 
     // Key for when the presentaiton is active
     @action
@@ -940,7 +910,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 if (this.layoutDoc.presStatus === 'edit') {
                     undoBatch(
                         action(() => {
-                            for (const doc of this._selectedArray) {
+                            for (const doc of this.selectedArray) {
                                 this.removeDocument(doc);
                             }
                             this.clearSelectedArray();
@@ -1027,13 +997,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
     };
 
-    getAllIndexes = (arr: Doc[], val: Doc): number[] => {
-        const indexes = [];
-        for (let i = 0; i < arr.length; i++) {
-            arr[i] === val && indexes.push(i);
-        }
-        return indexes;
-    };
+    getAllIndexes = (arr: Doc[], val: Doc) => arr.map((doc, i) => (doc === val ? i : -1)).filter(i => i !== -1);
 
     // Adds the index in the pres path graphically
     @computed get order() {
@@ -1152,7 +1116,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (change) timeInMS += change;
         if (timeInMS < 100) timeInMS = 100;
         if (timeInMS > 10000) timeInMS = 10000;
-        this._selectedArray.forEach(doc => (doc.presTransition = timeInMS));
+        this.selectedArray.forEach(doc => (doc.presTransition = timeInMS));
     };
 
     // Converts seconds to ms and updates presTransition
@@ -1161,7 +1125,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (change) scale += change;
         if (scale < 0.01) scale = 0.01;
         if (scale > 1.5) scale = 1.5;
-        this._selectedArray.forEach(doc => (doc.presZoom = scale));
+        this.selectedArray.forEach(doc => (doc.presZoom = scale));
     };
 
     // Converts seconds to ms and updates presDuration
@@ -1170,100 +1134,43 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (change) timeInMS += change;
         if (timeInMS < 100) timeInMS = 100;
         if (timeInMS > 20000) timeInMS = 20000;
-        this._selectedArray.forEach(doc => (doc.presDuration = timeInMS));
+        this.selectedArray.forEach(doc => (doc.presDuration = timeInMS));
     };
 
     /**
      * When the movement dropdown is changes
      */
     @undoBatch
-    updateMovement = action((movement: any, all?: boolean) => {
-        (all ? this.childDocs : this._selectedArray).forEach(doc => {
-            switch (movement) {
-                case PresMovement.Zoom: //Pan and zoom
-                    doc.presMovement = PresMovement.Zoom;
-                    break;
-                case PresMovement.Pan: //Pan
-                    doc.presMovement = PresMovement.Pan;
-                    break;
-                case PresMovement.Jump: //Jump Cut
-                    doc.presJump = true;
-                    doc.presMovement = PresMovement.Jump;
-                    break;
-                case PresMovement.None:
-                default:
-                    doc.presMovement = PresMovement.None;
-                    break;
-            }
-        });
-    });
+    updateMovement = action((movement: PresMovement, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presMovement = movement)));
 
     @undoBatch
     @action
     updateHideBefore = (activeItem: Doc) => {
         activeItem.presHideBefore = !activeItem.presHideBefore;
-        this._selectedArray.forEach(doc => (doc.presHideBefore = activeItem.presHideBefore));
+        this.selectedArray.forEach(doc => (doc.presHideBefore = activeItem.presHideBefore));
     };
 
     @undoBatch
     @action
     updateHideAfter = (activeItem: Doc) => {
         activeItem.presHideAfter = !activeItem.presHideAfter;
-        this._selectedArray.forEach(doc => (doc.presHideAfter = activeItem.presHideAfter));
+        this.selectedArray.forEach(doc => (doc.presHideAfter = activeItem.presHideAfter));
     };
 
     @undoBatch
     @action
     updateOpenDoc = (activeItem: Doc) => {
         activeItem.openDocument = !activeItem.openDocument;
-        this._selectedArray.forEach(doc => {
-            doc.openDocument = activeItem.openDocument;
-        });
+        this.selectedArray.forEach(doc => (doc.openDocument = activeItem.openDocument));
     };
 
     @undoBatch
     @action
-    updateEffectDirection = (effect: any, all?: boolean) => {
-        (all ? this.childDocs : this._selectedArray).forEach(doc => {
-            const tagDoc = doc; // Cast(doc.presentationTargetDoc, Doc, null);
-            switch (effect) {
-                case PresEffect.Left:
-                    tagDoc.presEffectDirection = PresEffect.Left;
-                    break;
-                case PresEffect.Right:
-                    tagDoc.presEffectDirection = PresEffect.Right;
-                    break;
-                case PresEffect.Top:
-                    tagDoc.presEffectDirection = PresEffect.Top;
-                    break;
-                case PresEffect.Bottom:
-                    tagDoc.presEffectDirection = PresEffect.Bottom;
-                    break;
-                case PresEffect.Center:
-                default:
-                    tagDoc.presEffectDirection = PresEffect.Center;
-                    break;
-            }
-        });
-    };
+    updateEffectDirection = (effect: PresEffect, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presEffectDirection = effect));
 
     @undoBatch
     @action
-    updateEffect = (effect: any, all?: boolean) => {
-        (all ? this.childDocs : this._selectedArray).forEach(doc => {
-            const tagDoc = doc; //Cast(doc.presentationTargetDoc, Doc, null);
-            //prettier-ignore
-            switch (effect) {
-                default:
-                case PresEffect.None:   tagDoc.presEffect = PresEffect.None; break;
-                case PresEffect.Bounce: tagDoc.presEffect = PresEffect.Bounce; break;
-                case PresEffect.Fade:   tagDoc.presEffect = PresEffect.Fade; break;
-                case PresEffect.Flip:   tagDoc.presEffect = PresEffect.Flip; break;
-                case PresEffect.Roll:   tagDoc.presEffect = PresEffect.Roll; break;
-                case PresEffect.Rotate: tagDoc.presEffect = PresEffect.Rotate; break;
-            }
-        });
-    };
+    updateEffect = (effect: PresEffect, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presEffect = effect));
 
     _batch: UndoManager.Batch | undefined = undefined;
 
@@ -1272,6 +1179,46 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const targetDoc: Doc = this.targetDoc;
         const isPresCollection: boolean = targetDoc === this.layoutDoc.presCollection;
         const isPinWithView: boolean = BoolCast(activeItem.presPinView);
+        const presEffect = (effect: PresEffect) => (
+            <div className={`presBox-dropdownOption ${this.activeItem.presEffect === effect || (effect === PresEffect.None && !this.activeItem.presEffect) ? 'active' : ''}`} onPointerDown={StopEvent} onClick={() => this.updateEffect(effect)}>
+                {effect}
+            </div>
+        );
+        const presMovement = (movement: PresMovement) => (
+            <div className={`presBox-dropdownOption ${activeItem.presMovement === movement ? 'active' : ''}`} onPointerDown={StopEvent} onClick={() => this.updateMovement(movement)}>
+                {movement}
+            </div>
+        );
+        const presDirection = (diretion: PresEffect, icon: string, gridColumn: number, gridRow: number, opts: object) => {
+            const color = this.activeItem.presEffectDirection === diretion || (diretion === PresEffect.Center && !this.activeItem.presEffectDirection) ? Colors.LIGHT_BLUE : 'black';
+            return (
+                <Tooltip title={<div className="dash-tooltip">{diretion}</div>}>
+                    <div
+                        style={{ ...opts, border: diretion === PresEffect.Center ? `solid 2px ${color}` : undefined, borderRadius: '100%', cursor: 'pointer', gridColumn, gridRow, justifySelf: 'center', color }}
+                        onClick={() => this.updateEffectDirection(diretion)}>
+                        {icon ? <FontAwesomeIcon icon={icon as any} /> : null}
+                    </div>
+                </Tooltip>
+            );
+        };
+        const inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void) => {
+            return (
+                <input
+                    type="range"
+                    step={step}
+                    min={min}
+                    max={max}
+                    value={value}
+                    className={`toolbar-slider ${active ? '' : 'none'}`}
+                    onPointerDown={() => (this._batch = UndoManager.StartBatch('pres slider'))}
+                    onPointerUp={() => this._batch?.end()}
+                    onChange={e => {
+                        e.stopPropagation();
+                        change(e.target.value);
+                    }}
+                />
+            );
+        };
         if (activeItem && targetDoc) {
             const type = targetDoc.type;
             const transitionSpeed = activeItem.presTransition ? NumCast(activeItem.presTransition) / 1000 : 0.5;
@@ -1282,13 +1229,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : 'Zoom';
             return (
                 <div
-                    className={`presBox-ribbon ${this.transitionTools && this.layoutDoc.presStatus === PresStatus.Edit ? 'active' : ''}`}
-                    onPointerDown={e => e.stopPropagation()}
-                    onPointerUp={e => e.stopPropagation()}
+                    className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presStatus === PresStatus.Edit ? 'active' : ''}`}
+                    onPointerDown={StopEvent}
+                    onPointerUp={StopEvent}
                     onClick={action(e => {
                         e.stopPropagation();
-                        this.openMovementDropdown = false;
-                        this.openEffectDropdown = false;
+                        this._openMovementDropdown = false;
+                        this._openEffectDropdown = false;
                     })}>
                     <div className="ribbon-box">
                         Movement
@@ -1301,24 +1248,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 className="presBox-dropdown"
                                 onClick={action(e => {
                                     e.stopPropagation();
-                                    this.openMovementDropdown = !this.openMovementDropdown;
+                                    this._openMovementDropdown = !this._openMovementDropdown;
                                 })}
-                                style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
-                                {this.setMovementName(activeItem.presMovement, activeItem)}
-                                <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this.openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
-                                <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={e => e.stopPropagation()} style={{ display: this.openMovementDropdown ? 'grid' : 'none' }}>
-                                    <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.None ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.None)}>
-                                        None
-                                    </div>
-                                    <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Zoom ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Zoom)}>
-                                        Pan {'&'} Zoom
-                                    </div>
-                                    <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Pan ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Pan)}>
-                                        Pan
-                                    </div>
-                                    <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Jump ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Jump)}>
-                                        Jump cut
-                                    </div>
+                                style={{ borderBottomLeftRadius: this._openMovementDropdown ? 0 : 5, border: this._openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
+                                {this.movementName(activeItem)}
+                                <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
+                                <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={StopEvent} style={{ display: this._openMovementDropdown ? 'grid' : 'none' }}>
+                                    {presMovement(PresMovement.None)}
+                                    {presMovement(PresMovement.Zoom)}
+                                    {presMovement(PresMovement.Pan)}
+                                    {presMovement(PresMovement.Jump)}
                                 </div>
                             </div>
                         )}
@@ -1336,21 +1275,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 </div>
                             </div>
                         </div>
-                        <input
-                            type="range"
-                            step="1"
-                            min="0"
-                            max="150"
-                            value={zoom}
-                            className={`toolbar-slider ${activeItem.presMovement === PresMovement.Zoom ? '' : 'none'}`}
-                            id="toolbar-slider"
-                            onPointerDown={() => (this._batch = UndoManager.StartBatch('presZoom'))}
-                            onPointerUp={() => this._batch?.end()}
-                            onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
-                                e.stopPropagation();
-                                this.setZoom(e.target.value);
-                            }}
-                        />
+                        {inputter('0', '1', '150', zoom, activeItem.presMovement === PresMovement.Zoom, this.setZoom)}
                         <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? 'inline-flex' : 'none' }}>
                             <div className="presBox-subheading">Movement Speed</div>
                             <div className="ribbon-property">
@@ -1365,21 +1290,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 </div>
                             </div>
                         </div>
-                        <input
-                            type="range"
-                            step="0.1"
-                            min="0.1"
-                            max="10"
-                            value={transitionSpeed}
-                            className={`toolbar-slider ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? '' : 'none'}`}
-                            id="toolbar-slider"
-                            onPointerDown={() => (this._batch = UndoManager.StartBatch('presTransition'))}
-                            onPointerUp={() => this._batch?.end()}
-                            onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
-                                e.stopPropagation();
-                                this.setTransitionTime(e.target.value);
-                            }}
-                        />
+                        {inputter('0.1', '0.1', '10', transitionSpeed, [PresMovement.Pan, PresMovement.Zoom].includes(activeItem.presMovement as any), this.setTransitionTime)}
                         <div className={`slider-headers ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? '' : 'none'}`}>
                             <div className="slider-text">Fast</div>
                             <div className="slider-text">Medium</div>
@@ -1390,35 +1301,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         Visibility {'&'} Duration
                         <div className="ribbon-doubleButton">
                             {isPresCollection ? null : (
-                                <Tooltip
-                                    title={
-                                        <>
-                                            <div className="dash-tooltip">{'Hide before presented'}</div>
-                                        </>
-                                    }>
+                                <Tooltip title={<div className="dash-tooltip">{'Hide before presented'}</div>}>
                                     <div className={`ribbon-toggle ${activeItem.presHideBefore ? 'active' : ''}`} onClick={() => this.updateHideBefore(activeItem)}>
                                         Hide before
                                     </div>
                                 </Tooltip>
                             )}
                             {isPresCollection ? null : (
-                                <Tooltip
-                                    title={
-                                        <>
-                                            <div className="dash-tooltip">{'Hide after presented'}</div>
-                                        </>
-                                    }>
+                                <Tooltip title={<div className="dash-tooltip">{'Hide after presented'}</div>}>
                                     <div className={`ribbon-toggle ${activeItem.presHideAfter ? 'active' : ''}`} onClick={() => this.updateHideAfter(activeItem)}>
                                         Hide after
                                     </div>
                                 </Tooltip>
                             )}
-                            <Tooltip
-                                title={
-                                    <>
-                                        <div className="dash-tooltip">{'Open in lightbox view'}</div>
-                                    </>
-                                }>
+                            <Tooltip title={<div className="dash-tooltip">{'Open in lightbox view'}</div>}>
                                 <div className="ribbon-toggle" style={{ backgroundColor: activeItem.openDocument ? Colors.LIGHT_BLUE : '' }} onClick={() => this.updateOpenDoc(activeItem)}>
                                     Lightbox
                                 </div>
@@ -1440,22 +1336,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         </div>
                                     </div>
                                 </div>
-                                <input
-                                    type="range"
-                                    step="0.1"
-                                    min="0.1"
-                                    max="20"
-                                    value={duration}
-                                    style={{ display: targetDoc.type === DocumentType.AUDIO ? 'none' : 'block' }}
-                                    className={'toolbar-slider'}
-                                    id="duration-slider"
-                                    onPointerDown={() => (this._batch = UndoManager.StartBatch('presDuration'))}
-                                    onPointerUp={() => this._batch?.end()}
-                                    onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
-                                        e.stopPropagation();
-                                        this.setDurationTime(e.target.value);
-                                    }}
-                                />
+                                {inputter('0.1', '0.1', '20', duration, targetDoc.type !== DocumentType.AUDIO, this.setDurationTime)}
                                 <div className={'slider-headers'} style={{ display: targetDoc.type === DocumentType.AUDIO ? 'none' : 'grid' }}>
                                     <div className="slider-text">Short</div>
                                     <div className="slider-text">Medium</div>
@@ -1471,98 +1352,30 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 className="presBox-dropdown"
                                 onClick={action(e => {
                                     e.stopPropagation();
-                                    this.openEffectDropdown = !this.openEffectDropdown;
+                                    this._openEffectDropdown = !this._openEffectDropdown;
                                 })}
-                                style={{ borderBottomLeftRadius: this.openEffectDropdown ? 0 : 5, border: this.openEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
+                                style={{ borderBottomLeftRadius: this._openEffectDropdown ? 0 : 5, border: this._openEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
                                 {effect.toString()}
-                                <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this.openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
-                                <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} style={{ display: this.openEffectDropdown ? 'grid' : 'none' }} onPointerDown={e => e.stopPropagation()}>
-                                    <div
-                                        className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.None || !this.activeItem.presEffect ? 'active' : ''}`}
-                                        onPointerDown={e => e.stopPropagation()}
-                                        onClick={() => this.updateEffect(PresEffect.None)}>
-                                        None
-                                    </div>
-                                    <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Fade ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}>
-                                        Fade In
-                                    </div>
-                                    <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Flip ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Flip)}>
-                                        Flip
-                                    </div>
-                                    <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Rotate ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Rotate)}>
-                                        Rotate
-                                    </div>
-                                    <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Bounce ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Bounce)}>
-                                        Bounce
-                                    </div>
-                                    <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Roll ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Roll)}>
-                                        Roll
-                                    </div>
+                                <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
+                                <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} style={{ display: this._openEffectDropdown ? 'grid' : 'none' }} onPointerDown={e => e.stopPropagation()}>
+                                    {presEffect(PresEffect.None)}
+                                    {presEffect(PresEffect.Fade)}
+                                    {presEffect(PresEffect.Flip)}
+                                    {presEffect(PresEffect.Rotate)}
+                                    {presEffect(PresEffect.Bounce)}
+                                    {presEffect(PresEffect.Roll)}
                                 </div>
                             </div>
                             <div className="ribbon-doubleButton" style={{ display: effect === 'None' ? 'none' : 'inline-flex' }}>
                                 <div className="presBox-subheading">Effect direction</div>
-                                <div className="ribbon-property">{this.effectDirection}</div>
+                                <div className="ribbon-property">{StrCast(this.activeItem.presEffectDirection)}</div>
                             </div>
                             <div className="effectDirection" style={{ display: effect === 'None' ? 'none' : 'grid', width: 40 }}>
-                                <Tooltip title={<div className="dash-tooltip">{'Enter from left'}</div>}>
-                                    <div
-                                        style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Left ? Colors.LIGHT_BLUE : 'black', cursor: 'pointer' }}
-                                        onClick={() => this.updateEffectDirection(PresEffect.Left)}>
-                                        <FontAwesomeIcon icon={'angle-right'} />
-                                    </div>
-                                </Tooltip>
-                                <Tooltip title={<div className="dash-tooltip">{'Enter from right'}</div>}>
-                                    <div
-                                        style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Right ? Colors.LIGHT_BLUE : 'black', cursor: 'pointer' }}
-                                        onClick={() => this.updateEffectDirection(PresEffect.Right)}>
-                                        <FontAwesomeIcon icon={'angle-left'} />
-                                    </div>
-                                </Tooltip>
-                                <Tooltip
-                                    title={
-                                        <>
-                                            <div className="dash-tooltip">{'Enter from top'}</div>
-                                        </>
-                                    }>
-                                    <div
-                                        style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Top ? Colors.LIGHT_BLUE : 'black', cursor: 'pointer' }}
-                                        onClick={() => this.updateEffectDirection(PresEffect.Top)}>
-                                        <FontAwesomeIcon icon={'angle-down'} />
-                                    </div>
-                                </Tooltip>
-                                <Tooltip
-                                    title={
-                                        <>
-                                            <div className="dash-tooltip">{'Enter from bottom'}</div>
-                                        </>
-                                    }>
-                                    <div
-                                        style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Bottom ? Colors.LIGHT_BLUE : 'black', cursor: 'pointer' }}
-                                        onClick={() => this.updateEffectDirection(PresEffect.Bottom)}>
-                                        <FontAwesomeIcon icon={'angle-up'} />
-                                    </div>
-                                </Tooltip>
-                                <Tooltip
-                                    title={
-                                        <>
-                                            <div className="dash-tooltip">{'Enter from center'}</div>
-                                        </>
-                                    }>
-                                    <div
-                                        style={{
-                                            gridColumn: 2,
-                                            gridRow: 2,
-                                            width: 10,
-                                            height: 10,
-                                            alignSelf: 'center',
-                                            justifySelf: 'center',
-                                            border: this.activeItem.presEffectDirection === PresEffect.Center || !this.activeItem.presEffectDirection ? `solid 2px ${Colors.LIGHT_BLUE}` : 'solid 2px black',
-                                            borderRadius: '100%',
-                                            cursor: 'pointer',
-                                        }}
-                                        onClick={() => this.updateEffectDirection(PresEffect.Center)}></div>
-                                </Tooltip>
+                                {presDirection(PresEffect.Left, 'angle-right', 1, 2, {})}
+                                {presDirection(PresEffect.Right, 'angle-left', 3, 2, {})}
+                                {presDirection(PresEffect.Top, 'angle-down', 2, 1, {})}
+                                {presDirection(PresEffect.Bottom, 'angle-up', 2, 3, {})}
+                                {presDirection(PresEffect.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })}
                             </div>
                         </div>
                     )}
@@ -1576,34 +1389,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
     }
 
-    @computed get effectDirection() {
-        // prettier-ignore
-        switch (this.activeItem.presEffectDirection) {
-            case 'left': return 'Enter from left'; 
-            case 'right': return 'Enter from right'; 
-            case 'top':  return'Enter from top';
-            case 'bottom': return 'Enter from bottom';
-        }
-        return 'Enter from center';
-    }
-
     @undoBatch
     @action
     applyTo = (array: Doc[]) => {
-        const activeItem: Doc = this.activeItem;
-        const targetDoc: Doc = this.targetDoc;
-        this.updateMovement(activeItem.presMovement, true);
-        this.updateEffect(activeItem.presEffect, true);
-        this.updateEffectDirection(activeItem.presEffectDirection, true);
-        array.forEach(doc => {
-            const curDoc = Cast(doc, Doc, null);
-            const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null);
-            if (tagDoc && targetDoc) {
-                curDoc.presTransition = activeItem.presTransition;
-                curDoc.presDuration = activeItem.presDuration;
-                curDoc.presHideBefore = activeItem.presHideBefore;
-                curDoc.presHideAfter = activeItem.presHideAfter;
-            }
+        this.updateMovement(this.activeItem.presMovement as PresMovement, true);
+        this.updateEffect(this.activeItem.presEffect as PresEffect, true);
+        this.updateEffectDirection(this.activeItem.presEffectDirection as PresEffect, true);
+        const { presTransition, presDuration, presHideBefore, presHideAfter } = this.activeItem;
+        array.forEach(curDoc => {
+            curDoc.presTransition = presTransition;
+            curDoc.presDuration = presDuration;
+            curDoc.presHideBefore = presHideBefore;
+            curDoc.presHideAfter = presHideAfter;
         });
     };
 
@@ -1785,7 +1582,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return (
             <div
                 className={'presBox-toolbar-dropdown'}
-                style={{ display: this.newDocumentTools && this.layoutDoc.presStatus === 'edit' ? 'inline-flex' : 'none' }}
+                style={{ display: this._newDocumentTools && this.layoutDoc.presStatus === 'edit' ? 'inline-flex' : 'none' }}
                 onClick={e => e.stopPropagation()}
                 onPointerUp={e => e.stopPropagation()}
                 onPointerDown={e => e.stopPropagation()}>
@@ -1956,7 +1753,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     // Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view)
     @computed get presentDropdown() {
         return (
-            <div className={`dropdown-play ${this.presentTools ? 'active' : ''}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+            <div className={`dropdown-play ${this._presentTools ? 'active' : ''}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
                 <div
                     className="dropdown-play-button"
                     onClick={undoBatch(
@@ -2042,7 +1839,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const activeFontColor = targetDoc['pres-text-color'] ? StrCast(targetDoc['pres-text-color']) : 'Black';
             const viewedFontColor = targetDoc['pres-text-viewed-color'] ? StrCast(targetDoc['pres-text-viewed-color']) : 'Black';
             return (
-                <div className={`presBox-ribbon ${this.progressivizeTools && this.layoutDoc.presStatus === 'edit' ? 'active' : ''}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+                <div className={`presBox-ribbon ${this._progressivizeTools && this.layoutDoc.presStatus === 'edit' ? 'active' : ''}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
                     <div className="ribbon-box">
                         {this.stringType} selected
                         <div
@@ -2155,18 +1952,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @undoBatch
     @action
     switchActive = (color: ColorState) => {
-        const activeItem: Doc = this.activeItem;
-        const targetDoc: Doc = this.targetDoc;
-        const val = String(color.hex);
-        targetDoc['pres-text-color'] = val;
+        this.targetDoc['pres-text-color'] = String(color.hex);
         return true;
     };
     @undoBatch
     @action
     switchPresented = (color: ColorState) => {
-        const targetDoc: Doc = this.targetDoc;
-        const val = String(color.hex);
-        targetDoc['pres-text-viewed-color'] = val;
+        this.targetDoc['pres-text-viewed-color'] = String(color.hex);
         return true;
     };
 
@@ -2228,12 +2020,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     //Toggle whether the user edits or not
     @action
     editScrollProgressivize = (e: React.MouseEvent) => {
-        const activeItem: Doc = this.activeItem;
         const targetDoc: Doc = this.targetDoc;
         if (!targetDoc.editScrollProgressivize) {
             if (!targetDoc.scrollProgressivize) {
                 targetDoc.scrollProgressivize = true;
-                activeItem.scrollProgressivize = true;
+                this.activeItem.scrollProgressivize = true;
             }
             targetDoc.editScrollProgressivize = true;
         } else {
@@ -2245,8 +2036,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     progressivizeScroll = (e: React.MouseEvent) => {
         e.stopPropagation();
-        const activeItem: Doc = this.activeItem;
-        activeItem.scrollProgressivize = !activeItem.scrollProgressivize;
+        this.activeItem.scrollProgressivize = !this.activeItem.scrollProgressivize;
         const targetDoc: Doc = this.targetDoc;
         targetDoc.scrollProgressivize = !targetDoc.scrollProgressivize;
         // CollectionFreeFormDocumentView.setupScroll(targetDoc, NumCast(targetDoc._currentFrame));
@@ -2353,13 +2143,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     checkList = (doc: Doc, list: any): number => {
         const x: List<number> = list;
-        if (x && x.length >= NumCast(doc._currentFrame) + 1) {
+        if (x?.length >= NumCast(doc._currentFrame) + 1) {
             return x[NumCast(doc._currentFrame)];
         } else if (x) {
             x.length = NumCast(doc._currentFrame) + 1;
             x[NumCast(doc._currentFrame)] = x[NumCast(doc._currentFrame) - 1];
             return x[NumCast(doc._currentFrame)];
-        } else return 100;
+        }
+        return 100;
     };
 
     @computed get progressivizeChildDocs() {
@@ -2413,26 +2204,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @action
-    nextAppearFrame = (doc: Doc, i: number): void => {
-        // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
-        // const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
-        const appearFrame = Cast(doc.appearFrame, 'number', null);
-        if (appearFrame === undefined) {
-            doc.appearFrame = 0;
-        }
-        doc.appearFrame = appearFrame + 1;
+    nextAppearFrame = (doc: Doc, i: number) => {
+        doc.appearFrame = (Cast(doc.appearFrame, 'number', null) ?? 0) + 1;
         this.updateOpacityList(doc['opacity-indexed'], NumCast(doc.appearFrame));
     };
 
     @action
-    prevAppearFrame = (doc: Doc, i: number): void => {
-        // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
-        // const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
-        const appearFrame = Cast(doc.appearFrame, 'number', null);
-        if (appearFrame === undefined) {
-            doc.appearFrame = 0;
-        }
-        doc.appearFrame = Math.max(0, appearFrame - 1);
+    prevAppearFrame = (doc: Doc, i: number) => {
+        doc.appearFrame = Math.max(0, (Cast(doc.appearFrame, 'number', null) ?? 0) - 1);
         this.updateOpacityList(doc['opacity-indexed'], NumCast(doc.appearFrame));
     };
 
@@ -2483,12 +2262,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     <FontAwesomeIcon icon={"plus"} />
                     <FontAwesomeIcon className={`dropdown ${this.newDocumentTools ? "active" : ""}`} icon={"angle-down"} />
                 </div></Tooltip> */}
-                <Tooltip
-                    title={
-                        <>
-                            <div className="dash-tooltip">{'View paths'}</div>
-                        </>
-                    }>
+                <Tooltip title={<div className="dash-tooltip">{'View paths'}</div>}>
                     <div
                         style={{ opacity: this.childDocs.length > 1 && this.layoutDoc.presCollection ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? '100%' : undefined }}
                         className={'toolbar-button'}
@@ -2507,22 +2281,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             </div>
                         </Tooltip>
                         <div className="toolbar-divider" /> */}
-                        <Tooltip
-                            title={
-                                <>
-                                    <div className="dash-tooltip">{presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}</div>
-                                </>
-                            }>
+                        <Tooltip title={<div className="dash-tooltip">{presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}</div>}>
                             <div className="toolbar-button" style={{ cursor: presKeyEvents ? 'default' : 'pointer', position: 'absolute', right: 30, fontSize: 16 }}>
                                 <FontAwesomeIcon className={'toolbar-thumbtack'} icon={'keyboard'} style={{ color: presKeyEvents ? activeColor : inactiveColor }} />
                             </div>
                         </Tooltip>
-                        <Tooltip
-                            title={
-                                <>
-                                    <div className="dash-tooltip">{propTitle}</div>
-                                </>
-                            }>
+                        <Tooltip title={<div className="dash-tooltip">{propTitle}</div>}>
                             <div className="toolbar-button" style={{ position: 'absolute', right: 4, fontSize: 16 }} onClick={this.toggleProperties}>
                                 <FontAwesomeIcon className={'toolbar-thumbtack'} icon={propIcon} style={{ color: SettingsManager.propertiesWidth > 0 ? activeColor : inactiveColor }} />
                             </div>
@@ -2545,14 +2309,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             <div className="presBox-buttons" style={{ display: !this.rootDoc._chromeHidden ? 'none' : undefined }}>
                 {isMini ? null : (
                     <select className="presBox-viewPicker" style={{ display: this.layoutDoc.presStatus === 'edit' ? 'block' : 'none' }} onPointerDown={e => e.stopPropagation()} onChange={this.viewChanged} value={mode}>
-                        <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Stacking}>
+                        <option onPointerDown={StopEvent} value={CollectionViewType.Stacking}>
                             List
                         </option>
-                        <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Tree}>
+                        <option onPointerDown={StopEvent} value={CollectionViewType.Tree}>
                             Tree
                         </option>
                         {Doc.noviceMode ? null : (
-                            <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel3D}>
+                            <option onPointerDown={StopEvent} value={CollectionViewType.Carousel3D}>
                                 3D Carousel
                             </option>
                         )}
@@ -2573,11 +2337,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         </div>
                         {mode === CollectionViewType.Carousel3D || isMini ? null : (
                             <div
-                                className={`presBox-button-right ${this.presentTools ? 'active' : ''}`}
+                                className={`presBox-button-right ${this._presentTools ? 'active' : ''}`}
                                 onClick={action(() => {
-                                    if (this.childDocs.length) this.presentTools = !this.presentTools;
+                                    if (this.childDocs.length) this._presentTools = !this._presentTools;
                                 })}>
-                                <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this.presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'angle-down'} />
+                                <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this._presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'angle-down'} />
                                 {this.presentDropdown}
                             </div>
                         )}
@@ -2595,7 +2359,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     updateList = (list: any): List<number> => {
         const targetDoc: Doc = this.targetDoc;
         const x: List<number> = list;
-        x.length + 1;
         x[x.length - 1] = NumCast(targetDoc._scrollY);
         return x;
     };
@@ -2603,8 +2366,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     newFrame = () => {
         const activeItem: Doc = this.activeItem;
-        const targetDoc: Doc = this.targetDoc;
-        const type: string = StrCast(targetDoc.type);
+        const type: string = StrCast(this.targetDoc.type);
         if (!activeItem.frameList) activeItem.frameList = new List<number>();
         switch (type) {
             case DocumentType.PDF || DocumentType.RTF || DocumentType.WEB:
@@ -2644,23 +2406,25 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @computed get frameList() {
-        const activeItem: Doc = this.activeItem;
-        const frameList: List<number> = this.getList(activeItem.frameList);
-        if (frameList) {
-            const frameItems = frameList.map(value => <div className="framList-item"></div>);
-            return <div className="frameList-container">{frameItems}</div>;
-        } else return null;
+        const frameList: List<number> = this.getList(this.activeItem.frameList);
+        return !frameList ? null : (
+            <div className="frameList-container">
+                {frameList.map(value => (
+                    <div className="framList-item" />
+                ))}
+            </div>
+        );
     }
 
     @computed get playButtonFrames() {
         const targetDoc = this.targetDoc;
-        return this.targetDoc ? (
+        return !this.targetDoc ? null : (
             <div className="presPanel-button-frame" style={{ display: targetDoc.lastFrame !== undefined && targetDoc.lastFrame >= 0 ? 'inline-flex' : 'none' }}>
                 <div>{NumCast(targetDoc._currentFrame)}</div>
                 <div className="presPanel-divider" style={{ border: 'solid 0.5px white', height: '60%' }}></div>
                 <div>{NumCast(targetDoc.lastFrame)}</div>
             </div>
-        ) : null;
+        );
     }
 
     @computed get playButtons() {
@@ -2674,7 +2438,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <FontAwesomeIcon icon={'redo-alt'} />
                     </div>
                 </Tooltip>
-                <div className="presPanel-divider"></div>
+                <div className="presPanel-divider" />
                 <div
                     className="presPanel-button"
                     style={{ opacity: presStart ? 0.4 : 1 }}
@@ -2767,9 +2531,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     @action
-    startMarqueeCreateSlide = () => {
-        PresBox.startMarquee = true;
-    };
+    startMarqueeCreateSlide = () => (PresBox.startMarquee = true);
 
     AddToMap = (treeViewDoc: Doc, index: number[]): Doc[] => {
         var indexNum = 0;
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index c78828a78..a00441ddc 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -50,7 +50,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return this.presBox.presStatus;
     }
     @computed get selectedArray() {
-        return this.presBoxView?._selectedArray;
+        return this.presBoxView?.selectedArray;
     }
     @computed get presBoxView() {
         const vpath = this.props.docViewPath();
diff --git a/src/client/views/nodes/trails/PresEnums.ts b/src/client/views/nodes/trails/PresEnums.ts
index 93ab323fb..c6a222c3a 100644
--- a/src/client/views/nodes/trails/PresEnums.ts
+++ b/src/client/views/nodes/trails/PresEnums.ts
@@ -1,28 +1,28 @@
 export enum PresMovement {
-    Zoom = "zoom",
-    Pan = "pan",
-    Jump = "jump",
-    None = "none",
+    Zoom = 'zoom',
+    Pan = 'pan',
+    Jump = 'jump',
+    None = 'none',
 }
 
 export enum PresEffect {
-    Zoom = "Zoom",
-    Lightspeed = "Lightspeed",
-    Fade = "Fade in",
-    Flip = "Flip",
-    Rotate = "Rotate",
-    Bounce = "Bounce",
-    Roll = "Roll",
-    None = "None",
-    Left = "left",
-    Right = "right",
-    Center = "center",
-    Top = "top",
-    Bottom = "bottom"
+    Zoom = 'Zoom',
+    Lightspeed = 'Lightspeed',
+    Fade = 'Fade in',
+    Flip = 'Flip',
+    Rotate = 'Rotate',
+    Bounce = 'Bounce',
+    Roll = 'Roll',
+    None = 'None',
+    Left = 'Enter from left',
+    Right = 'Enter from right',
+    Center = 'Enter from center',
+    Top = 'Enter from Top',
+    Bottom = 'Enter from bottom',
 }
 
 export enum PresStatus {
-    Autoplay = "auto",
-    Manual = "manual",
-    Edit = "edit"
-}
\ No newline at end of file
+    Autoplay = 'auto',
+    Manual = 'manual',
+    Edit = 'edit',
+}
-- 
cgit v1.2.3-70-g09d2


From 33a313252f5f9fbe070053de496df1fb77239656 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 26 Aug 2022 19:08:51 -0400
Subject: cleaned up keyboard event activation for presBox

---
 src/client/views/nodes/trails/PresBox.tsx | 48 ++++++++++++++++++-------------
 1 file changed, 28 insertions(+), 20 deletions(-)

(limited to 'src/client/views/nodes/trails/PresBox.tsx')

diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index ac68ea281..50a77ce57 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -99,7 +99,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @observable _isChildActive = false;
     @observable _moveOnFromAudio: boolean = true;
     @observable _presTimer!: NodeJS.Timeout;
-    @observable _presKeyEventsActive: boolean = false;
 
     @observable _eleArray: HTMLElement[] = [];
     @observable _dragArray: HTMLElement[] = [];
@@ -112,6 +111,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @observable _openEffectDropdown: boolean = false;
     @observable _presentTools: boolean = false;
     @observable _treeViewMap: Map<Doc, number> = new Map();
+    @observable _presKeyEvents: boolean = false;
+    @observable _forceKeyEvents: boolean = false;
     @computed get isTreeOrStack() {
         return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._viewType) as any);
     }
@@ -149,12 +150,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (this.selectedArray.size) return DocumentManager.Instance.getDocumentView(this.rootDoc);
     }
     @computed get isPres() {
-        document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
-        if (this.selectedDoc?.type === DocumentType.PRES) {
-            document.addEventListener('keydown', PresBox.keyEventsWrapper, true);
-            return true;
-        }
-        return false;
+        return this.selectedDoc === this.rootDoc;
     }
     @computed get selectedDoc() {
         return this.selectedDocumentView?.rootDoc;
@@ -168,7 +164,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     componentWillUnmount() {
         this._unmounting = true;
         document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
-        this._presKeyEventsActive = false;
         this.resetPresentation();
         // Turn of progressivize editors
         this.turnOffEdit(true);
@@ -177,6 +172,26 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @action
     componentDidMount() {
+        this._disposers.keyboard = reaction(
+            () => this.selectedDoc,
+            selected => {
+                document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
+                (this._presKeyEvents = selected === this.rootDoc) && document.addEventListener('keydown', PresBox.keyEventsWrapper, true);
+            },
+            { fireImmediately: true }
+        );
+        this._disposers.forcekeyboard = reaction(
+            () => this._forceKeyEvents,
+            force => {
+                if (force) {
+                    document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
+                    document.addEventListener('keydown', PresBox.keyEventsWrapper, true);
+                    this._presKeyEvents = true;
+                }
+                this._forceKeyEvents = false;
+            },
+            { fireImmediately: true }
+        );
         this.props.setContentView?.(this);
         this._unmounting = false;
         this.rootDoc._forceRenderEngine = 'timeline';
@@ -194,9 +209,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     updateCurrentPresentation = (pres?: Doc) => {
         Doc.ActivePresentation = pres ?? this.rootDoc;
-        document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
-        document.addEventListener('keydown', PresBox.keyEventsWrapper, true);
-        this._presKeyEventsActive = true;
         PresBox.Instance = this;
     };
 
@@ -2253,7 +2265,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const propTitle = SettingsManager.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel';
         const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
         const isMini: boolean = this.toolbarWidth <= 100;
-        const presKeyEvents: boolean = this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.ActivePresentation;
         const activeColor = Colors.LIGHT_BLUE;
         const inactiveColor = Colors.WHITE;
         return mode === CollectionViewType.Carousel3D ? null : (
@@ -2281,9 +2292,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             </div>
                         </Tooltip>
                         <div className="toolbar-divider" /> */}
-                        <Tooltip title={<div className="dash-tooltip">{presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}</div>}>
-                            <div className="toolbar-button" style={{ cursor: presKeyEvents ? 'default' : 'pointer', position: 'absolute', right: 30, fontSize: 16 }}>
-                                <FontAwesomeIcon className={'toolbar-thumbtack'} icon={'keyboard'} style={{ color: presKeyEvents ? activeColor : inactiveColor }} />
+                        <Tooltip title={<div className="dash-tooltip">{this._presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}</div>}>
+                            <div className="toolbar-button" style={{ cursor: this._presKeyEvents ? 'default' : 'pointer', position: 'absolute', right: 30, fontSize: 16 }}>
+                                <FontAwesomeIcon className={'toolbar-thumbtack'} icon={'keyboard'} style={{ color: this._presKeyEvents ? activeColor : inactiveColor }} />
                             </div>
                         </Tooltip>
                         <Tooltip title={<div className="dash-tooltip">{propTitle}</div>}>
@@ -2561,17 +2572,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     sort = (treeViewMap: Map<Doc, number>) => [...treeViewMap.entries()].sort((a: [Doc, number], b: [Doc, number]) => (a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0)).map(kv => kv[0]);
 
     render() {
-        // calling this method for keyEvents
-        this.isPres;
         // needed to ensure that the childDocs are loaded for looking up fields
         this.childDocs.slice();
         const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
-        const presKeyEvents: boolean = this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.ActivePresentation;
         const presEnd: boolean = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1;
         const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0;
         return DocListCast(Doc.MyOverlayDocs?.data).includes(this.rootDoc) ? (
-            <div className="miniPres" onClick={e => e.stopPropagation()}>
-                <div className="presPanelOverlay" style={{ display: 'inline-flex', height: 30, background: '#323232', top: 0, zIndex: 3000000, boxShadow: presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}>
+            <div className="miniPres" onClick={e => e.stopPropagation()} onPointerEnter={action(e => (this._forceKeyEvents = true))}>
+                <div className="presPanelOverlay" style={{ display: 'inline-flex', height: 30, background: '#323232', top: 0, zIndex: 3000000, boxShadow: this._presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}>
                     <Tooltip title={<div className="dash-tooltip">{'Loop'}</div>}>
                         <div
                             className="presPanel-button"
-- 
cgit v1.2.3-70-g09d2


From 71f9850387c2719ed5b050db4130fa15852cb38d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 8 Sep 2022 13:30:36 -0400
Subject: fixed pinning viewScale

---
 src/client/views/nodes/trails/PresBox.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/client/views/nodes/trails/PresBox.tsx')

diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 50a77ce57..1439ae5d5 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -420,7 +420,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 const panY = NumCast(pinDoc._panY);
                 const pw = NumCast(pinProps.panelWidth);
                 const ph = NumCast(pinProps.panelHeight);
-                const ps = NumCast(pinDoc._viewScale);
+                const ps = NumCast(pinDoc._viewScale, 1);
                 if (pw && ph && ps) {
                     pinDoc.presPinViewBounds = new List<number>([panX - pw / 2 / ps, panY - ph / 2 / ps, panX + pw / 2 / ps, panY + ph / 2 / ps]);
                 }
-- 
cgit v1.2.3-70-g09d2


From 0454279c9845fc64dc2c77aeb6bca9a5fee573f6 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 8 Sep 2022 13:34:55 -0400
Subject: added rotation to preselement saved parameters.

---
 src/client/views/nodes/trails/PresBox.tsx        | 2 ++
 src/client/views/nodes/trails/PresElementBox.tsx | 1 +
 2 files changed, 3 insertions(+)

(limited to 'src/client/views/nodes/trails/PresBox.tsx')

diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 1439ae5d5..beebca973 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -407,6 +407,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             pinDoc.presPinView = (pinProps?.pinWithView ? true : false) || scrollable || temporal || pannable || clippable || dataview || textview || pinProps.activeFrame !== undefined;
             pinDoc.presX = NumCast(targetDoc.x);
             pinDoc.presY = NumCast(targetDoc.y);
+            pinDoc.presRot = NumCast(targetDoc.jitterRotation);
             pinDoc.presWidth = NumCast(targetDoc.width);
             pinDoc.presHeight = NumCast(targetDoc.height);
 
@@ -487,6 +488,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             targetDoc._dataTransition = presTransitionTime;
             targetDoc.x = NumCast(activeItem.presX, NumCast(targetDoc.x));
             targetDoc.y = NumCast(activeItem.presY, NumCast(targetDoc.y));
+            targetDoc.jitterRotation = NumCast(activeItem.presRot, NumCast(targetDoc.jitterRotation));
             targetDoc.width = NumCast(activeItem.presWidth, NumCast(targetDoc.width));
             targetDoc.height = NumCast(activeItem.presHeight, NumCast(targetDoc.height));
             setTimeout(() => (targetDoc._dataTransition = undefined), transTime + 10);
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index e4fab7528..d88ff45fa 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -337,6 +337,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
         activeItem.presX = NumCast(targetDoc.x);
         activeItem.presY = NumCast(targetDoc.y);
+        activeItem.presRot = NumCast(targetDoc.jitterRotation);
         activeItem.presWidth = NumCast(targetDoc.width);
         activeItem.presHeight = NumCast(targetDoc.height);
     };
-- 
cgit v1.2.3-70-g09d2


From 637dd4dd36a74e49c653aa6e265a0db41797de00 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 8 Sep 2022 13:43:02 -0400
Subject: made presbox slide changes undoable

---
 src/client/views/nodes/trails/PresBox.tsx | 1 +
 1 file changed, 1 insertion(+)

(limited to 'src/client/views/nodes/trails/PresBox.tsx')

diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index beebca973..847617378 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -312,6 +312,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     //The function that is called when a document is clicked or reached through next or back.
     //it'll also execute the necessary actions if presentation is playing.
+    @undoBatch
     public gotoDocument = action((index: number, from?: Doc, group?: boolean) => {
         Doc.UnBrushAllDocs();
 
-- 
cgit v1.2.3-70-g09d2

