From a6cc25e5d03ffed16bfaa32e48e9cc2eaff7deaf Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 7 Nov 2023 13:48:26 -0500
Subject: Changed how selection works to avoid invalidations. Fixed Cast
 problem with ProxyFields that caused renameEmbedding to infinite loop.. 
 Changed brushing for the same reason.   Cleaned up a few things with filter
 code.

---
 src/client/views/nodes/MapBox/MapBox.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 50b070e7f..08dda2e1f 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -307,8 +307,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth();
     panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
     scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
-    transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()];
-    opaqueFilter = () => [...this.props.childFilters(), Utils.IsOpaqueFilter()];
+    transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter];
+    opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter];
     infoWidth = () => this.props.PanelWidth() / 5;
     infoHeight = () => this.props.PanelHeight() / 5;
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
-- 
cgit v1.2.3-70-g09d2


From 216385c7e84febce8988ef1390845b0c661fb04f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 8 Nov 2023 12:55:59 -0500
Subject: fixed bug where tableBox's didn't render all of the rows they
 receive. lots of code cleanup -- moving things from Doc.ts to better
 locations.  Changed overlays and published docs to be local to their
 dashboard.  changed treeview icons.

---
 src/client/documents/Documents.ts                  |  46 +-
 src/client/util/CurrentUserUtils.ts                |   8 +-
 src/client/util/DocumentManager.ts                 |   5 +-
 src/client/util/LinkManager.ts                     |  32 +-
 src/client/util/SettingsManager.tsx                |  10 +-
 src/client/views/DashboardView.tsx                 |   5 +-
 src/client/views/DocumentDecorations.tsx           |  10 +-
 src/client/views/GestureOverlay.tsx                |  11 +-
 src/client/views/InkTranscription.tsx              |   9 +-
 src/client/views/InkingStroke.tsx                  |   9 +-
 src/client/views/MainView.tsx                      |   5 +-
 src/client/views/OverlayView.tsx                   | 128 +++---
 src/client/views/PropertiesView.tsx                |  12 +-
 src/client/views/StyleProvider.tsx                 |   4 +-
 .../views/collections/CollectionNoteTakingView.tsx |  10 +-
 .../views/collections/CollectionPileView.tsx       |  17 +-
 .../views/collections/CollectionStackingView.tsx   |  12 +-
 src/client/views/collections/CollectionSubView.tsx |   5 +-
 .../views/collections/CollectionTreeView.tsx       |   6 +-
 src/client/views/collections/TabDocView.tsx        |   4 +-
 src/client/views/collections/TreeView.tsx          |  18 +-
 .../CollectionFreeFormLayoutEngines.tsx            |   9 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  31 +-
 .../collectionLinear/CollectionLinearView.tsx      |   2 +-
 src/client/views/global/globalScripts.ts           |  25 +-
 src/client/views/nodes/ColorBox.tsx                |  19 +-
 .../views/nodes/DataVizBox/components/TableBox.tsx |   2 +-
 src/client/views/nodes/DocumentView.tsx            |  14 +-
 src/client/views/nodes/EquationBox.tsx             |   3 +-
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |  11 +-
 src/client/views/nodes/ImageBox.tsx                |   6 +-
 src/client/views/nodes/LinkBox.tsx                 |  11 +-
 src/client/views/nodes/LinkDocPreview.tsx          |  30 +-
 src/client/views/nodes/LoadingBox.tsx              |  21 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   4 +-
 src/client/views/nodes/MapBox/MapBox2.tsx          |   3 +-
 src/client/views/nodes/PDFBox.tsx                  |  25 +-
 src/client/views/nodes/ScreenshotBox.tsx           |   9 +-
 src/client/views/nodes/VideoBox.tsx                |   5 +-
 src/client/views/nodes/WebBox.tsx                  |  21 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  21 +-
 src/client/views/nodes/formattedText/marks_rts.ts  |   2 +-
 src/client/views/nodes/trails/PresElementBox.tsx   |  17 +-
 src/client/views/pdf/Annotation.tsx                |   6 +-
 src/fields/Doc.ts                                  | 464 +++++----------------
 src/fields/RichTextUtils.ts                        |   2 +-
 src/fields/util.ts                                 |   2 +-
 src/mobile/MobileInterface.tsx                     |  11 +-
 48 files changed, 482 insertions(+), 660 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c5a42aadc..edf72e45b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { action, reaction, runInAction } from 'mobx';
 import { basename } from 'path';
 import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Field, LinkedTo, Opt, updateCachedAcls } from '../../fields/Doc';
+import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc';
 import { Initializing } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { HtmlField } from '../../fields/HtmlField';
@@ -1253,6 +1253,40 @@ export namespace Docs {
 }
 
 export namespace DocUtils {
+    function matchFieldValue(doc: Doc, key: string, value: any): boolean {
+        const hasFunctionFilter = Utils.HasFunctionFilter(value);
+        if (hasFunctionFilter) {
+            return hasFunctionFilter(StrCast(doc[key]));
+        }
+        if (key === LinkedTo) {
+            // links are not a field value, so handled here.  value is an expression of form ([field=]idToDoc("..."))
+            const allLinks = LinkManager.Instance.getAllRelatedLinks(doc);
+            const matchLink = (value: string, anchor: Doc) => {
+                const linkedToExp = value?.split('=');
+                if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value;
+                return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1];
+            };
+            // prettier-ignore
+            return  (value === Doc.FilterNone && !allLinks.length) || 
+                    (value === Doc.FilterAny && !!allLinks.length) ||
+                    (allLinks.some(link => matchLink(value,DocCast(link.link_anchor_1)) || 
+                                           matchLink(value,DocCast(link.link_anchor_2)) ));
+        }
+        if (typeof value === 'string') {
+            value = value.replace(`,${Utils.noRecursionHack}`, '');
+        }
+        const fieldVal = doc[key];
+        // prettier-ignore
+        if ((value === Doc.FilterAny && fieldVal !== undefined) || 
+            (value === Doc.FilterNone && fieldVal === undefined)) {
+            return true;
+        }
+        const vals = StrListCast(fieldVal); // list typing is very imperfect.  casting to a string list doesn't mean that the entries will actually be strings
+        if (vals.length) {
+            return vals.some(v => typeof v === 'string' && v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+        }
+        return Field.toString(fieldVal as Field).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+    }
     /**
      * @param docs
      * @param childFilters
@@ -1303,8 +1337,8 @@ export namespace DocUtils {
                       const xs = Object.keys(facet).filter(value => facet[value] === 'x');
 
                       if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true;
-                      const failsNotEqualFacets = !xs.length ? false : xs.some(value => Doc.matchFieldValue(d, facetKey, value));
-                      const satisfiesCheckFacets = !checks.length ? true : checks.some(value => Doc.matchFieldValue(d, facetKey, value));
+                      const failsNotEqualFacets = !xs.length ? false : xs.some(value => matchFieldValue(d, facetKey, value));
+                      const satisfiesCheckFacets = !checks.length ? true : checks.some(value => matchFieldValue(d, facetKey, value));
                       const satisfiesExistsFacets = !exists.length ? true : exists.some(value => (facetKey !== LinkedTo ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length));
                       const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined);
                       const satisfiesMatchFacets = !matches.length
@@ -1802,7 +1836,7 @@ export namespace DocUtils {
                 proto.data_duration = result.duration;
             }
             if (overwriteDoc) {
-                Doc.removeCurrentlyLoading(overwriteDoc);
+                LoadingBox.removeCurrentlyLoading(overwriteDoc);
             }
             generatedDocuments.push(doc);
         }
@@ -1844,7 +1878,7 @@ export namespace DocUtils {
                 if (overwriteDoc) {
                     overwriteDoc.isLoading = false;
                     overwriteDoc.loadingError = (result as any).message;
-                    Doc.removeCurrentlyLoading(overwriteDoc);
+                    LoadingBox.removeCurrentlyLoading(overwriteDoc);
                 }
             } else name && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc);
         });
@@ -1885,7 +1919,7 @@ export namespace DocUtils {
             if ((result as any).message) {
                 if (overwriteDoc) {
                     overwriteDoc.loadingError = (result as any).message;
-                    Doc.removeCurrentlyLoading(overwriteDoc);
+                    LoadingBox.removeCurrentlyLoading(overwriteDoc);
                 }
             } else name && type && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc);
         });
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index ba3c26b42..cd19daee5 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -110,7 +110,7 @@ export class CurrentUserUtils {
         const tempClicks = DocCast(doc[field]);
         const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, isSystem: true};
         const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
-            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCast(documentView?.props.docViewPath().lastElement()?.rootDoc.target).then((target) => target && (target.proto.data = new List([self])))"},
+            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCastAsync(documentView?.props.docViewPath().lastElement()?.rootDoc.target).then((target) => target && (target.proto.data = new List([self])))"},
             { opts: { title: "Open Detail On Right",  targetScriptKey: "onChildDoubleClick"}, script: `openDoc(self.doubleClickView.${OpenWhere.addRight})`}];
         const reqdClickList = reqdTempOpts.map(opts => {
             const allOpts = {...reqdClickOpts, ...opts.opts};
@@ -793,11 +793,6 @@ export class CurrentUserUtils {
         return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
     }
 
-    /// collection of documents rendered in the overlay layer above all tabs and other UI
-    static setupOverlays(doc: Doc, field = "myOverlayDocs") {
-        return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.FreeformDocument([], opts), { title: "overlay documents", backgroundColor: "#aca3a6", isSystem: true });
-    }
-
     static setupPublished(doc:Doc, field = "myPublishedDocs") {
         return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), { title: "published docs", backgroundColor: "#aca3a6", isSystem: true });
     }
@@ -901,7 +896,6 @@ export class CurrentUserUtils {
         this.setupSharedDocs(doc, sharingDocumentId);  // sets up the right sidebar collection for mobile upload documents and sharing
         this.setupDefaultIconTemplates(doc);  // creates a set of icon templates triggered by the document deoration icon
         this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile
-        this.setupOverlays(doc);  // sets up the overlay panel where documents and other widgets can be added to float over the rest of the dashboard
         this.setupPublished(doc);  // sets up the list doc of all docs that have been published (meaning that they can be auto-linked by typing their title into another text box)
         this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected
         this.setupTopbarButtons(doc);
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 7cc8afaa6..f05c41fa7 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -13,6 +13,7 @@ import { LightboxView } from '../views/LightboxView';
 import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
 import { KeyValueBox } from '../views/nodes/KeyValueBox';
 import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
+import { LoadingBox } from '../views/nodes/LoadingBox';
 import { PresBox } from '../views/nodes/trails';
 import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
@@ -40,8 +41,8 @@ export class DocumentManager {
 
     //private constructor so no other class can create a nodemanager
     private constructor() {
-        if (!Doc.CurrentlyLoading) Doc.CurrentlyLoading = [];
-        observe(Doc.CurrentlyLoading, change => {
+        if (!LoadingBox.CurrentlyLoading) LoadingBox.CurrentlyLoading = [];
+        observe(LoadingBox.CurrentlyLoading, change => {
             // watch CurrentlyLoading-- when something is loaded, it's removed from the list and we have to update its icon if it were iconified since LoadingBox icons are different than the media they become
             switch (change.type as any) {
                 case 'update':
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index ba53a760f..608184596 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -197,14 +197,32 @@ export class LinkManager {
     }
 
     // finds the opposite anchor of a given anchor in a link
-    //TODO This should probably return undefined if there isn't an opposite anchor
-    //TODO This should also await the return value of the anchor so we don't filter out promises
     public static getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc | undefined {
-        const a1 = Cast(linkDoc.link_anchor_1, Doc, null);
-        const a2 = Cast(linkDoc.link_anchor_2, Doc, null);
-        if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a1?.annotationOn, a1))) return a2;
-        if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a2?.annotationOn, a2))) return a1;
-        if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc;
+        const id = LinkManager.anchorIndex(linkDoc, anchor);
+        const a1 = DocCast(linkDoc.link_anchor_1);
+        const a2 = DocCast(linkDoc.link_anchor_2);
+        return id === '1' ? a2 : id === '2' ? a1 : id === '0' ? linkDoc : undefined;
+        // if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a1?.annotationOn, a1))) return a2;
+        // if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a2?.annotationOn, a2))) return a1;
+        // if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc;
+    }
+    public static anchorIndex(linkDoc: Doc, anchor: Doc) {
+        const a1 = DocCast(linkDoc.link_anchor_1);
+        const a2 = DocCast(linkDoc.link_anchor_2);
+        if (linkDoc.link_matchEmbeddings) {
+            return [a2, a2.annotationOn].includes(anchor) ? '2' : '1';
+        }
+        if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a1?.annotationOn, a1))) return '1';
+        if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a2?.annotationOn, a2))) return '2';
+        if (Doc.AreProtosEqual(anchor, linkDoc)) return '0';
+
+        // const a1 = DocCast(linkDoc.link_anchor_1);
+        // const a2 = DocCast(linkDoc.link_anchor_2);
+        // if (linkDoc.link_matchEmbeddings) {
+        //     return [a2, a2.annotationOn].includes(anchor) ? '2' : '1';
+        // }
+        // if (Doc.AreProtosEqual(a2, anchor) || Doc.AreProtosEqual(a2.annotationOn as Doc, anchor)) return '2';
+        // return Doc.AreProtosEqual(a1, anchor) || Doc.AreProtosEqual(a1.annotationOn as Doc, anchor) ? '1' : '2';
     }
 }
 
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index f75322905..e58c2687c 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -12,7 +12,9 @@ import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { DocServer } from '../DocServer';
 import { Networking } from '../Network';
+import { GestureOverlay } from '../views/GestureOverlay';
 import { MainViewModal } from '../views/MainViewModal';
+import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox';
 import { GroupManager } from './GroupManager';
 import './SettingsManager.scss';
 import { undoBatch } from './UndoManager';
@@ -228,8 +230,8 @@ export class SettingsManager extends React.Component<{}> {
                     formLabel={'Show Button Labels'}
                     formLabelPlacement={'right'}
                     toggleType={ToggleType.SWITCH}
-                    onClick={e => Doc.SetShowIconLabels(!Doc.GetShowIconLabels())}
-                    toggleStatus={Doc.GetShowIconLabels()}
+                    onClick={e => (FontIconBox.ShowIconLabels = !FontIconBox.ShowIconLabels)}
+                    toggleStatus={FontIconBox.ShowIconLabels}
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
                 />
@@ -237,8 +239,8 @@ export class SettingsManager extends React.Component<{}> {
                     formLabel={'Recognize Ink Gestures'}
                     formLabelPlacement={'right'}
                     toggleType={ToggleType.SWITCH}
-                    onClick={e => Doc.SetRecognizeGestures(!Doc.GetRecognizeGestures())}
-                    toggleStatus={Doc.GetRecognizeGestures()}
+                    onClick={e => (GestureOverlay.RecognizeGestures = !GestureOverlay.RecognizeGestures)}
+                    toggleStatus={GestureOverlay.RecognizeGestures}
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
                 />
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 014a6358f..9e2ed7822 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -12,6 +12,7 @@ import { PrefetchProxy } from '../../fields/Proxy';
 import { listSpec } from '../../fields/Schema';
 import { ScriptField } from '../../fields/ScriptField';
 import { Cast, ImageCast, StrCast } from '../../fields/Types';
+import { normalizeEmail } from '../../fields/util';
 import { DocServer } from '../DocServer';
 import { Docs, DocumentOptions, DocUtils } from '../documents/Documents';
 import { HistoryUtil } from '../util/History';
@@ -149,7 +150,7 @@ export class DashboardView extends React.Component {
                             : this.getDashboards(this.selectedDashboardGroup).map(dashboard => {
                                   const href = ImageCast(dashboard.thumb)?.url?.href;
                                   const shared = Object.keys(dashboard[DocAcl])
-                                      .filter(key => key !== `acl-${Doc.CurrentUserEmailNormalized}` && !['acl-Me', 'acl-Guest'].includes(key))
+                                      .filter(key => key !== `acl-${normalizeEmail(Doc.CurrentUserEmail)}` && !['acl-Me', 'acl-Guest'].includes(key))
                                       .some(key => dashboard[DocAcl][key] !== AclPrivate);
                                   return (
                                       <div
@@ -372,6 +373,8 @@ export class DashboardView extends React.Component {
         Doc.AddDocToList(Doc.MyHeaderBar, 'data', freeformDoc);
         dashboardDoc['pane-count'] = 1;
         freeformDoc.embedContainer = dashboardDoc;
+        dashboardDoc.myOverlayDocs = new List<Doc>();
+        dashboardDoc.myPublishedDocs = new List<Doc>();
 
         Doc.AddDocToList(Doc.MyDashboards, 'data', dashboardDoc);
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 5a145e94a..6c55895ef 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -7,7 +7,7 @@ import { observer } from 'mobx-react';
 import { FaUndo } from 'react-icons/fa';
 import { DateField } from '../../fields/DateField';
 import { Doc, DocListCast, Field, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, DocData, Height, Width } from '../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit, DocData } from '../../fields/DocSymbols';
 import { InkField } from '../../fields/InkField';
 import { RichTextField } from '../../fields/RichTextField';
 import { ScriptField } from '../../fields/ScriptField';
@@ -126,10 +126,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                         if (titleFieldKey === 'title') {
                             d.dataDoc.title_custom = !this._accumulatedTitle.startsWith('-');
                             if (StrCast(d.rootDoc.title).startsWith('@') && !this._accumulatedTitle.startsWith('@')) {
-                                Doc.RemoveDocFromList(Doc.MyPublishedDocs, undefined, d.rootDoc);
+                                Doc.RemFromMyPublished(d.rootDoc);
                             }
                             if (!StrCast(d.rootDoc.title).startsWith('@') && this._accumulatedTitle.startsWith('@')) {
-                                Doc.AddDocToList(Doc.MyPublishedDocs, undefined, d.rootDoc);
+                                Doc.AddToMyPublished(d.rootDoc);
                             }
                         }
                         //@ts-ignore
@@ -295,8 +295,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 // open centered in a new workspace with Shift Key
                 const embedding = Doc.MakeEmbedding(selectedDocs[0].rootDoc);
                 embedding.embedContainer = undefined;
-                embedding.x = -embedding[Width]() / 2;
-                embedding.y = -embedding[Height]() / 2;
+                embedding.x = -NumCast(embedding._width) / 2;
+                embedding.y = -NumCast(embedding._height) / 2;
                 CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([embedding], { title: 'Tab for ' + embedding.title }), OpenWhereMod.right);
             } else if (e.altKey) {
                 // open same document in new tab
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 35d6d73e4..caabdb09b 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -5,7 +5,7 @@ import { observer } from 'mobx-react';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
 import { Doc, Opt } from '../../fields/Doc';
 import { InkData, InkTool } from '../../fields/InkField';
-import { NumCast } from '../../fields/Types';
+import { BoolCast, NumCast } from '../../fields/Types';
 import MobileInkOverlay from '../../mobile/MobileInkOverlay';
 import { GestureUtils } from '../../pen-gestures/GestureUtils';
 import { MobileInkOverlayContent } from '../../server/Message';
@@ -44,6 +44,13 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
     static Instance: GestureOverlay;
     static Instances: GestureOverlay[] = [];
 
+    public static set RecognizeGestures(active) {
+        Doc.UserDoc().recognizeGestures = active;
+    }
+    public static get RecognizeGestures() {
+        return BoolCast(Doc.UserDoc().recognizeGestures);
+    }
+
     @observable public InkShape: Opt<GestureUtils.Gestures>;
     @observable public SavedColor?: string;
     @observable public SavedWidth?: number;
@@ -590,7 +597,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                 // need to decide when to turn gestures back on
                 const result = points.length > 2 && GestureUtils.GestureRecognizer.Recognize(new Array(points));
                 let actionPerformed = false;
-                if (Doc.UserDoc().recognizeGestures && result && result.Score > 0.7) {
+                if (GestureOverlay.RecognizeGestures && result && result.Score > 0.7) {
                     switch (result.Name) {
                         case GestureUtils.Gestures.Line:
                         case GestureUtils.Gestures.Triangle:
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index 258bfad66..17136a737 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -2,7 +2,6 @@ import * as iink from 'iink-js';
 import { action, observable } from 'mobx';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../fields/Doc';
-import { Height, Width } from '../../fields/DocSymbols';
 import { InkData, InkField, InkTool } from '../../fields/InkField';
 import { Cast, DateCast, NumCast } from '../../fields/Types';
 import { aggregateBounds } from '../../Utils';
@@ -287,8 +286,8 @@ export class InkTranscription extends React.Component {
             action(d => {
                 const x = NumCast(d.x);
                 const y = NumCast(d.y);
-                const width = d[Width]();
-                const height = d[Height]();
+                const width = NumCast(d._width);
+                const height = NumCast(d._height);
                 bounds.push({ x, y, width, height });
             })
         );
@@ -327,8 +326,8 @@ export class InkTranscription extends React.Component {
         // Gets a collection based on the selected nodes using a marquee view ref
         const newCollection = marqViewRef?.getCollection(selected, undefined, true);
         if (newCollection) {
-            newCollection.height = newCollection[Height]();
-            newCollection.width = newCollection[Width]();
+            newCollection.width = NumCast(newCollection._width);
+            newCollection.height = NumCast(newCollection._height);
             // if the grouping we are creating is an individual word
             if (word) {
                 newCollection.title = word;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index d26c7761e..513061e79 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -24,7 +24,6 @@ import React = require('react');
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc } from '../../fields/Doc';
-import { Height, Width } from '../../fields/DocSymbols';
 import { InkData, InkField } from '../../fields/InkField';
 import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
@@ -382,10 +381,10 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const fillColor = this.fillColor;
 
         // 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[Width]() !== Math.round(this.layoutDoc[Width]()) || this.layoutDoc[Height]() !== Math.round(this.layoutDoc[Height]()))) {
+        if (isInkMask && (this.layoutDoc._width !== Math.round(NumCast(this.layoutDoc._width)) || this.layoutDoc._height !== Math.round(NumCast(this.layoutDoc._height)))) {
             setTimeout(() => {
-                this.layoutDoc._width = Math.round(NumCast(this.layoutDoc[Width]()));
-                this.layoutDoc._height = Math.round(NumCast(this.layoutDoc[Height]()));
+                this.layoutDoc._width = Math.round(NumCast(this.layoutDoc._width));
+                this.layoutDoc._height = Math.round(NumCast(this.layoutDoc._height));
             });
         }
         const highlight = !this.controlUndo && this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting);
@@ -481,7 +480,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                         style={{
                             color: StrCast(this.layoutDoc.textColor, 'black'),
                             pointerEvents: this.props.isDocumentActive?.() ? 'all' : undefined,
-                            width: this.layoutDoc[Width](),
+                            width: NumCast(this.layoutDoc._width),
                             transform: `scale(${this.props.NativeDimScaling?.() || 1})`,
                             transformOrigin: 'top left',
                             //top: (this.props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this.props.NativeDimScaling?.() || 1)) / 2,
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index da5e4f966..ccb46d7ec 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -261,6 +261,8 @@ export class MainView extends React.Component {
                 fa.faWindowRestore,
                 fa.faFolder,
                 fa.faFolderOpen,
+                fa.faFolderPlus,
+                fa.faFolderClosed,
                 fa.faMapPin,
                 fa.faMapMarker,
                 fa.faFingerprint,
@@ -450,6 +452,7 @@ export class MainView extends React.Component {
                 fa.faSortUp,
                 fa.faSortDown,
                 fa.faTable,
+                fa.faTableCells,
                 fa.faTableColumns,
                 fa.faTh,
                 fa.faThList,
@@ -475,11 +478,11 @@ export class MainView extends React.Component {
                 fa.faBookmark,
                 fa.faList,
                 fa.faListOl,
-                fa.faFolderPlus,
                 fa.faLightbulb,
                 fa.faBookOpen,
                 fa.faMapMarkerAlt,
                 fa.faSearchPlus,
+                fa.faSolarPanel,
                 fa.faVolumeUp,
                 fa.faVolumeDown,
                 fa.faSquareRootAlt,
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index c174befc0..c7b59a8e2 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -3,7 +3,7 @@ import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
 import ReactLoading from 'react-loading';
-import { Doc, DocListCast } from '../../fields/Doc';
+import { Doc } from '../../fields/Doc';
 import { Height, Width } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { NumCast } from '../../fields/Types';
@@ -119,7 +119,7 @@ export class OverlayView extends React.Component {
             new _global.ResizeObserver(
                 action((entries: any) => {
                     for (const entry of entries) {
-                        DocListCast(Doc.MyOverlayDocs?.data).forEach(doc => {
+                        Doc.MyOverlayDocs.forEach(doc => {
                             if (NumCast(doc.overlayX) > entry.contentRect.width - 10) {
                                 doc.overlayX = entry.contentRect.width - 10;
                             }
@@ -184,70 +184,68 @@ export class OverlayView extends React.Component {
     );
 
     @computed get overlayDocs() {
-        return DocListCast(Doc.MyOverlayDocs?.data)
-            .filter(d => !LightboxView.LightboxDoc || d.type === DocumentType.PRES)
-            .map(d => {
-                let offsetx = 0,
-                    offsety = 0;
-                const dref = React.createRef<HTMLDivElement>();
-                const onPointerMove = action((e: PointerEvent, down: number[]) => {
-                    if (e.cancelBubble) return false; // if the overlay doc processed the move event (e.g., to pan its contents), then the event should be marked as canceled since propagation can't be stopped
-                    if (e.buttons === 1) {
-                        d.overlayX = e.clientX + offsetx;
-                        d.overlayY = e.clientY + offsety;
-                    }
-                    if (e.metaKey) {
-                        const dragData = new DragManager.DocumentDragData([d]);
-                        dragData.offset = [-offsetx, -offsety];
-                        dragData.dropAction = 'move';
-                        dragData.removeDocument = this.removeOverlayDoc;
-                        dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
-                            return dragData.removeDocument!(doc) ? addDocument(doc) : false;
-                        };
-                        DragManager.StartDocumentDrag([dref.current!], dragData, down[0], down[1]);
-                        return true;
-                    }
-                    return false;
-                });
-
-                const onPointerDown = (e: React.PointerEvent) => {
-                    setupMoveUpEvents(this, e, onPointerMove, emptyFunction, emptyFunction, false);
-                    offsetx = NumCast(d.overlayX) - e.clientX;
-                    offsety = NumCast(d.overlayY) - e.clientY;
-                };
-                return (
-                    <div
-                        className="overlayView-doc"
-                        ref={dref}
-                        key={d[Id]}
-                        onPointerDown={onPointerDown}
-                        style={{ top: d.type === DocumentType.PRES ? 0 : undefined, width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.overlayX}px, ${d.overlayY}px)` }}>
-                        <DocumentView
-                            Document={d}
-                            rootSelected={returnTrue}
-                            bringToFront={emptyFunction}
-                            addDocument={undefined}
-                            removeDocument={this.removeOverlayDoc}
-                            PanelWidth={d[Width]}
-                            PanelHeight={d[Height]}
-                            ScreenToLocalTransform={this.docScreenToLocalXf(d)}
-                            renderDepth={1}
-                            hideDecorations={true}
-                            isDocumentActive={returnTrue}
-                            isContentActive={returnTrue}
-                            whenChildContentsActiveChanged={emptyFunction}
-                            focus={emptyFunction}
-                            styleProvider={DefaultStyleProvider}
-                            docViewPath={returnEmptyDoclist}
-                            addDocTab={DocumentViewInternal.addDocTabFunc}
-                            pinToPres={emptyFunction}
-                            childFilters={returnEmptyFilter}
-                            childFiltersByRanges={returnEmptyFilter}
-                            searchFilterDocs={returnEmptyDoclist}
-                        />
-                    </div>
-                );
+        return Doc.MyOverlayDocs.filter(d => !LightboxView.LightboxDoc || d.type === DocumentType.PRES).map(d => {
+            let offsetx = 0,
+                offsety = 0;
+            const dref = React.createRef<HTMLDivElement>();
+            const onPointerMove = action((e: PointerEvent, down: number[]) => {
+                if (e.cancelBubble) return false; // if the overlay doc processed the move event (e.g., to pan its contents), then the event should be marked as canceled since propagation can't be stopped
+                if (e.buttons === 1) {
+                    d.overlayX = e.clientX + offsetx;
+                    d.overlayY = e.clientY + offsety;
+                }
+                if (e.metaKey) {
+                    const dragData = new DragManager.DocumentDragData([d]);
+                    dragData.offset = [-offsetx, -offsety];
+                    dragData.dropAction = 'move';
+                    dragData.removeDocument = this.removeOverlayDoc;
+                    dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
+                        return dragData.removeDocument!(doc) ? addDocument(doc) : false;
+                    };
+                    DragManager.StartDocumentDrag([dref.current!], dragData, down[0], down[1]);
+                    return true;
+                }
+                return false;
             });
+
+            const onPointerDown = (e: React.PointerEvent) => {
+                setupMoveUpEvents(this, e, onPointerMove, emptyFunction, emptyFunction, false);
+                offsetx = NumCast(d.overlayX) - e.clientX;
+                offsety = NumCast(d.overlayY) - e.clientY;
+            };
+            return (
+                <div
+                    className="overlayView-doc"
+                    ref={dref}
+                    key={d[Id]}
+                    onPointerDown={onPointerDown}
+                    style={{ top: d.type === DocumentType.PRES ? 0 : undefined, width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.overlayX}px, ${d.overlayY}px)` }}>
+                    <DocumentView
+                        Document={d}
+                        rootSelected={returnTrue}
+                        bringToFront={emptyFunction}
+                        addDocument={undefined}
+                        removeDocument={this.removeOverlayDoc}
+                        PanelWidth={d[Width]}
+                        PanelHeight={d[Height]}
+                        ScreenToLocalTransform={this.docScreenToLocalXf(d)}
+                        renderDepth={1}
+                        hideDecorations={true}
+                        isDocumentActive={returnTrue}
+                        isContentActive={returnTrue}
+                        whenChildContentsActiveChanged={emptyFunction}
+                        focus={emptyFunction}
+                        styleProvider={DefaultStyleProvider}
+                        docViewPath={returnEmptyDoclist}
+                        addDocTab={DocumentViewInternal.addDocTabFunc}
+                        pinToPres={emptyFunction}
+                        childFilters={returnEmptyFilter}
+                        childFiltersByRanges={returnEmptyFilter}
+                        searchFilterDocs={returnEmptyDoclist}
+                    />
+                </div>
+            );
+        });
     }
 
     public static ShowSpinner() {
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index d37971517..49bf982ec 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -5,12 +5,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Checkbox, Tooltip } from '@material-ui/core';
 import { Colors, EditableText, IconButton, NumberInput, Size, Slider, Type } from 'browndash-components';
 import { concat } from 'lodash';
-import { action, computed, IReactionDisposer, observable, reaction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { ColorState, SketchPicker } from 'react-color';
 import * as Icons from 'react-icons/bs'; //{BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs"
 import { Doc, DocListCast, Field, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc';
-import { AclAdmin, DocAcl, DocData, Height, Width } from '../../fields/DocSymbols';
+import { AclAdmin, DocAcl, DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { InkField } from '../../fields/InkField';
 import { List } from '../../fields/List';
@@ -123,16 +123,16 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         return [CollectionViewType.Stacking, CollectionViewType.NoteTaking].includes(this.selectedDoc?.type_collection as any);
     }
 
-    rtfWidth = () => (!this.selectedDoc ? 0 : Math.min(this.selectedDoc?.[Width](), this.props.width - 20));
-    rtfHeight = () => (!this.selectedDoc ? 0 : this.rtfWidth() <= this.selectedDoc?.[Width]() ? Math.min(this.selectedDoc?.[Height](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT);
+    rtfWidth = () => (!this.selectedDoc ? 0 : Math.min(NumCast(this.selectedDoc?._width), this.props.width - 20));
+    rtfHeight = () => (!this.selectedDoc ? 0 : this.rtfWidth() <= NumCast(this.selectedDoc?._width) ? Math.min(NumCast(this.selectedDoc?._height), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT);
 
     @action
     docWidth = () => {
         if (this.selectedDoc) {
             const layoutDoc = this.selectedDoc;
             const aspect = Doc.NativeAspect(layoutDoc, undefined, !layoutDoc._layout_fitWidth);
-            if (aspect) return Math.min(layoutDoc[Width](), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.width - 20));
-            return Doc.NativeWidth(layoutDoc) ? Math.min(layoutDoc[Width](), this.props.width - 20) : this.props.width - 20;
+            if (aspect) return Math.min(NumCast(layoutDoc._width), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.width - 20));
+            return Doc.NativeWidth(layoutDoc) ? Math.min(NumCast(layoutDoc._width), this.props.width - 20) : this.props.width - 20;
         }
         return 0;
     };
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 72d7cd1c5..2162d8878 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -105,7 +105,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                 const url = doc?.icon ? img.url.href : img.url.href.replace(ext, '_s' + ext);
                 return <img src={url} width={20} height={15} style={{ margin: 'auto', display: 'block', objectFit: 'contain' }} />;
             }
-            return Doc.toIcon(doc, isEmpty ? undefined : isOpen);
+            return Doc.toIcon(doc, isOpen);
         case StyleProp.TreeViewSortings:
             const allSorts: { [key: string]: { color: string; icon: JSX.Element | string } | undefined } = {};
             allSorts[TreeSort.AlphaDown] = { color: Colors.MEDIUM_BLUE, icon: <BsArrowDown/> };
@@ -117,7 +117,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             if (doc && (Doc.IsSystem(doc) || doc.type === DocumentType.FONTICON)) return undefined;            
             if (doc && !doc.layout_disableBrushing && !props?.disableBrushing) {
                 const selected = Array.from(doc?.[DocViews]??[]).filter(dv => dv.SELECTED).length;
-                const highlightIndex = Doc.isBrushedHighlightedDegree(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0);
+                const highlightIndex = Doc.GetBrushHighlightStatus(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0);
                 const highlightColor = ['transparent', 'rgb(68, 118, 247)', selected ? "black" : 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
                 const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex];
                 if (highlightIndex) {
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index afeef5a8f..bb6d94590 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -1,9 +1,9 @@
 import React = require('react');
 import { CursorProperty } from 'csstype';
-import { action, computed, IReactionDisposer, observable, reaction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Field, Opt } from '../../../fields/Doc';
-import { DocData, Height, Width } from '../../../fields/DocSymbols';
+import { DocData } from '../../../fields/DocSymbols';
 import { Copy, Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
@@ -289,7 +289,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         const existingHeader = this.colHeaderData.find(sh => sh.heading === heading);
         const existingWidth = existingHeader?.width ? existingHeader.width : 0;
         const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth : this.maxColWidth;
-        const width = d.layout_fitWidth ? maxWidth : d[Width]();
+        const width = d.layout_fitWidth ? maxWidth : NumCast(d._width);
         return Math.min(maxWidth - CollectionNoteTakingViewColumn.ColumnMargin, width < maxWidth ? width : maxWidth);
     }
 
@@ -299,8 +299,8 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
         const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.DataDoc;
         const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
-        const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[Width]() : 0);
-        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[Height]() : 0);
+        const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._width) : 0);
+        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._height) : 0);
         if (nw && nh) {
             const docWid = this.getDocWidth(d);
             return Math.min(maxHeight, (docWid * nh) / nw);
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 91701b213..abb12a8ab 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,7 +1,6 @@
 import { action, computed, IReactionDisposer } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
 import { ScriptField } from '../../../fields/ScriptField';
 import { NumCast, StrCast } from '../../../fields/Types';
 import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
@@ -79,12 +78,12 @@ export class CollectionPileView extends CollectionSubView() {
     toggleStarburst = action(() => {
         this.layoutDoc._freeform_scale = undefined;
         if (this.layoutEngine() === computeStarburstLayout.name) {
-            if (this.rootDoc[Width]() !== NumCast(this.rootDoc._starburstDiameter, 500)) {
-                this.rootDoc._starburstDiameter = this.rootDoc[Width]();
+            if (NumCast(this.rootDoc._width) !== NumCast(this.rootDoc._starburstDiameter, 500)) {
+                this.rootDoc._starburstDiameter = NumCast(this.rootDoc._width);
             }
             const defaultSize = 110;
-            this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[Width]() / 2 - NumCast(this.layoutDoc._freeform_pileWidth, defaultSize) / 2;
-            this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[Height]() / 2 - NumCast(this.layoutDoc._freeform_pileHeight, defaultSize) / 2;
+            this.rootDoc.x = NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) / 2 - NumCast(this.layoutDoc._freeform_pileWidth, defaultSize) / 2;
+            this.rootDoc.y = NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) / 2 - NumCast(this.layoutDoc._freeform_pileHeight, defaultSize) / 2;
             this.layoutDoc._width = NumCast(this.layoutDoc._freeform_pileWidth, defaultSize);
             this.layoutDoc._height = NumCast(this.layoutDoc._freeform_pileHeight, defaultSize);
             DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false);
@@ -93,10 +92,10 @@ export class CollectionPileView extends CollectionSubView() {
             this.props.Document._freeform_pileEngine = computePassLayout.name;
         } else {
             const defaultSize = NumCast(this.rootDoc._starburstDiameter, 400);
-            this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[Width]() / 2 - defaultSize / 2;
-            this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[Height]() / 2 - defaultSize / 2;
-            this.layoutDoc._freeform_pileWidth = this.layoutDoc[Width]();
-            this.layoutDoc._freeform_pileHeight = this.layoutDoc[Height]();
+            this.rootDoc.x = NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) / 2 - defaultSize / 2;
+            this.rootDoc.y = NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) / 2 - defaultSize / 2;
+            this.layoutDoc._freeform_pileWidth = NumCast(this.layoutDoc._width);
+            this.layoutDoc._freeform_pileHeight = NumCast(this.layoutDoc._height);
             this.layoutDoc._freeform_panX = this.layoutDoc._freeform_panY = 0;
             this.layoutDoc._width = this.layoutDoc._height = defaultSize;
             this.layoutDoc.background;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 0b29e7286..e5695f63b 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -4,17 +4,18 @@ import { CursorProperty } from 'csstype';
 import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Opt } from '../../../fields/Doc';
-import { DocData, Height, Width } from '../../../fields/DocSymbols';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { CollectionViewType } from '../../documents/DocumentTypes';
 import { DragManager, dropActionType } from '../../util/DragManager';
+import { SettingsManager } from '../../util/SettingsManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
@@ -31,7 +32,6 @@ import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow';
 import './CollectionStackingView.scss';
 import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn';
 import { CollectionSubView } from './CollectionSubView';
-import { SettingsManager } from '../../util/SettingsManager';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 export type collectionStackingViewProps = {
@@ -378,7 +378,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
         const maxWidth = this.columnWidth / this.numGroupColumns;
         if (!this.layoutDoc._columnsFill && !(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d))) {
-            return Math.min(d[Width](), maxWidth);
+            return Math.min(NumCast(d._width), maxWidth);
         }
         return maxWidth;
     }
@@ -387,8 +387,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
         const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.DataDoc;
         const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
-        const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[Width]() : 0);
-        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[Height]() : 0);
+        const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._width) : 0);
+        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._height) : 0);
         if (nw && nh) {
             const colWid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
             const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e9192ebbe..7c8b2f195 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -25,6 +25,7 @@ import { DocComponent } from '../DocComponent';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { CollectionView, CollectionViewProps } from './CollectionView';
 import React = require('react');
+import { LoadingBox } from '../nodes/LoadingBox';
 
 export interface SubCollectionViewProps extends CollectionViewProps {
     isAnyChildContentActive: () => boolean;
@@ -450,13 +451,13 @@ export function CollectionSubView<X>(moreProps?: X) {
             if (typeof files === 'string') {
                 const loading = Docs.Create.LoadingDocument(files, options);
                 generatedDocuments.push(loading);
-                Doc.addCurrentlyLoading(loading);
+                LoadingBox.addCurrentlyLoading(loading);
                 DocUtils.uploadYoutubeVideoLoading(files, {}, loading);
             } else {
                 generatedDocuments.push(
                     ...files.map(file => {
                         const loading = Docs.Create.LoadingDocument(file, options);
-                        Doc.addCurrentlyLoading(loading);
+                        LoadingBox.addCurrentlyLoading(loading);
                         DocUtils.uploadFileToDoc(file, {}, loading);
                         return loading;
                     })
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index e408c193a..761192a22 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,7 +1,7 @@
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
-import { DocData, Height, Width } from '../../../fields/DocSymbols';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
@@ -371,8 +371,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     marginX = () => NumCast(this.doc._xMargin);
     marginTop = () => NumCast(this.doc._yMargin);
     marginBot = () => NumCast(this.doc._yMargin);
-    documentTitleWidth = () => Math.min(this.layoutDoc?.[Width](), this.panelWidth());
-    documentTitleHeight = () => (this.layoutDoc?.[Height]() || 0) - NumCast(this.layoutDoc.layout_autoHeightMargins);
+    documentTitleWidth = () => Math.min(NumCast(this.layoutDoc?._width), this.panelWidth());
+    documentTitleHeight = () => NumCast(this.layoutDoc?._height) - NumCast(this.layoutDoc.layout_autoHeightMargins);
     truncateTitleWidth = () => this.treeViewtruncateTitleWidth;
     onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
     panelWidth = () => Math.max(0, this.props.PanelWidth() - 2 * this.marginX() * (this.props.NativeDimScaling?.() || 1));
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 41f3b2603..180578a36 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -6,7 +6,7 @@ import { action, computed, IReactionDisposer, observable, ObservableSet, reactio
 import { observer } from 'mobx-react';
 import * as ReactDOM from 'react-dom/client';
 import { Doc, Opt } from '../../../fields/Doc';
-import { DocData, Height, Width } from '../../../fields/DocSymbols';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { FieldId } from '../../../fields/RefField';
@@ -539,7 +539,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
                             default:                  return 'gray';
                         }
                     })(doc.type as DocumentType);
-                    return !background ? undefined : <div style={{ width: doc[Width](), height: doc[Height](), position: 'absolute', display: 'block', background }} />;
+                    return !background ? undefined : <div style={{ width: NumCast(doc._width), height: NumCast(doc._height), position: 'absolute', display: 'block', background }} />;
             }
         }
     };
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 193c70add..dbce45fda 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -1,9 +1,10 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { IconButton, Size } from 'browndash-components';
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../fields/Doc';
-import { DocData, Height, Width } from '../../../fields/DocSymbols';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { RichTextField } from '../../../fields/RichTextField';
@@ -19,6 +20,7 @@ import { DragManager, dropActionType } from '../../util/DragManager';
 import { LinkManager } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
+import { SettingsManager } from '../../util/SettingsManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
@@ -32,11 +34,9 @@ import { KeyValueBox } from '../nodes/KeyValueBox';
 import { StyleProp } from '../StyleProvider';
 import { CollectionTreeView, TreeViewType } from './CollectionTreeView';
 import { CollectionView } from './CollectionView';
+import { TreeSort } from './TreeSort';
 import './TreeView.scss';
 import React = require('react');
-import { IconButton, Size } from 'browndash-components';
-import { TreeSort } from './TreeSort';
-import { SettingsManager } from '../../util/SettingsManager';
 
 export interface TreeViewProps {
     treeView: CollectionTreeView;
@@ -455,7 +455,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     embeddedPanelHeight = () => {
         const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
         return Math.min(
-            layoutDoc[Height](),
+            NumCast(layoutDoc._height),
             this.MAX_EMBED_HEIGHT,
             (() => {
                 const aspect = Doc.NativeAspect(layoutDoc);
@@ -464,7 +464,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     ? !Doc.NativeHeight(layoutDoc)
                         ? NumCast(layoutDoc._height)
                         : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.treeViewParent._height)))
-                    : (this.embeddedPanelWidth() * layoutDoc[Height]()) / layoutDoc[Width]();
+                    : (this.embeddedPanelWidth() * NumCast(layoutDoc._height)) / NumCast(layoutDoc._width);
             })()
         );
     };
@@ -741,7 +741,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             <div
                 className={`bullet${this.props.treeView.outlineMode ? '-outline' : ''}`}
                 key="bullet"
-                title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : 'view fields'}
+                title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : `view ${this.props.document.type} content`}
                 onClick={this.bulletClick}
                 style={
                     this.props.treeView.outlineMode
@@ -872,7 +872,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             case StyleProp.Highlighting:    if (this.props.treeView.outlineMode) return undefined;
             case StyleProp.BoxShadow:       return undefined;
             case StyleProp.DocContents:
-                const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.isBrushedHighlightedDegree(doc);
+                const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.GetBrushHighlightStatus(doc);
                 const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
                 return treeView.outlineMode ? null : (
                     <div
@@ -1265,7 +1265,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 const childLayout = Doc.Layout(pair.layout);
                 const rowHeight = () => {
                     const aspect = Doc.NativeAspect(childLayout);
-                    return aspect ? Math.min(childLayout[Width](), rowWidth()) / aspect : childLayout[Height]();
+                    return aspect ? Math.min(NumCast(childLayout._width), rowWidth()) / aspect : NumCast(childLayout._height);
                 };
                 return (
                     <TreeView
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index d93e44ab7..f9f9b5637 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -1,5 +1,4 @@
 import { Doc, Field, FieldResult } from '../../../../fields/Doc';
-import { Height, Width } from '../../../../fields/DocSymbols';
 import { Id, ToString } from '../../../../fields/FieldSymbols';
 import { ObjectField } from '../../../../fields/ObjectField';
 import { RefField } from '../../../../fields/RefField';
@@ -91,8 +90,8 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
         docMap.set(layout[Id], {
             x: NumCast(layout.x),
             y: NumCast(layout.y),
-            width: layout[Width](),
-            height: layout[Height](),
+            width: NumCast(layout._width),
+            height: NumCast(layout._height),
             pair: { layout, data },
             transition: 'all .3s',
             replica: '',
@@ -106,8 +105,8 @@ export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc
     const burstDiam = [NumCast(pivotDoc._width), NumCast(pivotDoc._height)];
     const burstScale = NumCast(pivotDoc._starburstDocScale, 1);
     childPairs.forEach(({ layout, data }, i) => {
-        const aspect = layout[Height]() / layout[Width]();
-        const docSize = Math.min(Math.min(400, layout[Width]()), Math.min(400, layout[Width]()) / aspect) * burstScale;
+        const aspect = NumCast(layout._height) / NumCast(layout._width);
+        const docSize = Math.min(Math.min(400, NumCast(layout._width)), Math.min(400, NumCast(layout._width)) / aspect) * burstScale;
         const deg = (i / childPairs.length) * Math.PI * 2;
         docMap.set(layout[Id], {
             x: Math.min(burstDiam[0] / 2 - docSize, Math.max(-burstDiam[0] / 2, (Math.cos(deg) * burstDiam[0]) / 2 - docSize / 2)),
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index da0f7c893..ee3dcca11 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -460,6 +460,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     onExternalDrop = (e: React.DragEvent) => (([x, y]) => super.onExternalDrop(e, { x, y }))(this.getTransform().transformPoint(e.pageX, e.pageY));
 
+    static overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
+        const doc2Layout = Doc.Layout(doc2);
+        const doc1Layout = Doc.Layout(doc1);
+        const x2 = NumCast(doc2.x) - clusterDistance;
+        const y2 = NumCast(doc2.y) - clusterDistance;
+        const w2 = NumCast(doc2Layout._width) + clusterDistance;
+        const h2 = NumCast(doc2Layout._height) + clusterDistance;
+        const x = NumCast(doc1.x) - clusterDistance;
+        const y = NumCast(doc1.y) - clusterDistance;
+        const w = NumCast(doc1Layout._width) + clusterDistance;
+        const h = NumCast(doc1Layout._height) + clusterDistance;
+        return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 });
+    }
     pickCluster(probe: number[]) {
         return this.childLayoutPairs
             .map(pair => pair.layout)
@@ -519,7 +532,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             docs.map(doc =>
                 this._clusterSets.map((set, i) =>
                     set.map(member => {
-                        if (docFirst.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
+                        if (docFirst.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && CollectionFreeFormView.overlapping(doc, member, this._clusterDistance)) {
                             docFirst.layout_cluster = i;
                         }
                     })
@@ -560,7 +573,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             doc.layout_cluster = -1;
             this._clusterSets.forEach((set, i) =>
                 set.forEach(member => {
-                    if (doc.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
+                    if (doc.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && CollectionFreeFormView.overlapping(doc, member, this._clusterDistance)) {
                         doc.layout_cluster = i;
                     }
                 })
@@ -737,9 +750,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     const inks = this.getActiveDocuments().filter(doc => {
                         if (doc.type === 'ink') {
                             const l = NumCast(doc.x);
-                            const r = l + doc[Width]();
+                            const r = l + NumCast(doc._width);
                             const t = NumCast(doc.y);
-                            const b = t + doc[Height]();
+                            const b = t + NumCast(doc._height);
                             const pass = !(this._inkToTextStartX! > r || end[0] < l || this._inkToTextStartY! > b || end[1] < t);
                             return pass;
                         }
@@ -1217,7 +1230,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
         const layoutdoc = Doc.Layout(doc);
         const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y));
-        const pt2 = xf.transformPoint(NumCast(doc.x) + layoutdoc[Width](), NumCast(doc.y) + layoutdoc[Height]());
+        const pt2 = xf.transformPoint(NumCast(doc.x) + NumCast(layoutdoc._width), NumCast(doc.y) + NumCast(layoutdoc._height));
         const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1], width: pt2[0] - pt[0], height: pt2[1] - pt[1] };
 
         if (scale !== undefined) {
@@ -1586,14 +1599,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 this._disposers.groupBounds = reaction(
                     () => {
                         if (this.Document._isGroup && this.childDocs.length === this.childDocList?.length) {
-                            const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[Width](), height: cd[Height]() }));
+                            const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: NumCast(cd._width), height: NumCast(cd._height) }));
                             return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding));
                         }
                         return undefined;
                     },
                     cbounds => {
                         if (cbounds) {
-                            const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[Width]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[Height]() / 2];
+                            const c = [NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) / 2, NumCast(this.layoutDoc.y) + NumCast(this.layoutDoc._height) / 2];
                             const p = [NumCast(this.layoutDoc[this.panXFieldKey]), NumCast(this.layoutDoc[this.panYFieldKey])];
                             const pbounds = {
                                 x: cbounds.x - p[0] + c[0],
@@ -1680,8 +1693,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         CollectionFreeFormView.UpdateIcon(
             this.layoutDoc[Id] + '-icon' + new Date().getTime(),
             this.props.docViewPath().lastElement().ContentDiv!,
-            this.layoutDoc[Width](),
-            this.layoutDoc[Height](),
+            NumCast(this.layoutDoc._width),
+            NumCast(this.layoutDoc._height),
             this.props.PanelWidth(),
             this.props.PanelHeight(),
             0,
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 9a2c79a22..4267a9059 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -48,7 +48,7 @@ export class CollectionLinearView extends CollectionSubView() {
 
     componentDidMount() {
         this._widthDisposer = reaction(
-            () => 5 + NumCast(this.rootDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (doc[Width]() || this.dimension()) + tot + 4, 0) : 0),
+            () => 5 + NumCast(this.rootDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (NumCast(doc._width) || this.dimension()) + tot + 4, 0) : 0),
             width => this.childDocs.length && (this.layoutDoc._width = width),
             { fireImmediately: true }
         );
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 894afebfd..b529991cb 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -1,25 +1,24 @@
 import { Colors } from 'browndash-components';
-import { runInAction, action } from 'mobx';
-import { aggregateBounds } from '../../../Utils';
+import { action, runInAction } from 'mobx';
 import { Doc } from '../../../fields/Doc';
-import { Width, Height } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
-import { Cast, StrCast, NumCast, BoolCast } from '../../../fields/Types';
+import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
+import { aggregateBounds } from '../../../Utils';
+import { DocumentType } from '../../documents/DocumentTypes';
 import { LinkManager } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
 import { undoable, UndoManager } from '../../util/UndoManager';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 import { GestureOverlay } from '../GestureOverlay';
+import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke';
 import { InkTranscription } from '../InkTranscription';
-import { ActiveFillColor, SetActiveFillColor, ActiveIsInkMask, SetActiveIsInkMask, ActiveInkWidth, SetActiveInkWidth, ActiveInkColor, SetActiveInkColor, InkingStroke } from '../InkingStroke';
-import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
-import { WebBox } from '../nodes/WebBox';
-import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
-import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentView } from '../nodes/DocumentView';
+import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
+import { WebBox } from '../nodes/WebBox';
 
 ScriptingGlobals.add(function IsNoneSelected() {
     return SelectionManager.Views().length <= 0;
@@ -239,8 +238,8 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
                 action(d => {
                     const x = NumCast(d.x);
                     const y = NumCast(d.y);
-                    const width = d[Width]();
-                    const height = d[Height]();
+                    const width = NumCast(d._width);
+                    const height = NumCast(d._height);
                     bounds.push({ x, y, width, height });
                 })
             );
@@ -277,8 +276,8 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
             // TODO: nda - this is the code to actually get a new grouped collection
             const newCollection = marqViewRef?.getCollection(selected, undefined, true);
             if (newCollection) {
-                newCollection.height = newCollection[Height]();
-                newCollection.width = newCollection[Width]();
+                newCollection.height = NumCast(newCollection._height);
+                newCollection.width = NumCast(newCollection._width);
             }
 
             // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 1b6fe5748..23ffd1080 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -3,10 +3,12 @@ import { action } from 'mobx';
 import { observer } from 'mobx-react';
 import { ColorState, SketchPicker } from 'react-color';
 import { Doc } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
+import { Height } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
-import { StrCast } from '../../../fields/Types';
+import { NumCast, StrCast } from '../../../fields/Types';
+import { DashColor } from '../../../Utils';
 import { DocumentType } from '../../documents/DocumentTypes';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
@@ -14,8 +16,6 @@ import { ActiveInkColor, ActiveInkWidth, SetActiveInkColor, SetActiveInkWidth }
 import './ColorBox.scss';
 import { FieldView, FieldViewProps } from './FieldView';
 import { RichTextMenu } from './formattedText/RichTextMenu';
-import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { DashColor } from '../../../Utils';
 
 @observer
 export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -51,7 +51,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     render() {
-        const scaling = Math.min(this.layoutDoc.layout_fitWidth ? 10000 : this.props.PanelHeight() / this.rootDoc[Height](), this.props.PanelWidth() / this.rootDoc[Width]());
+        const scaling = Math.min(this.layoutDoc.layout_fitWidth ? 10000 : this.props.PanelHeight() / NumCast(this.rootDoc._height), this.props.PanelWidth() / NumCast(this.rootDoc._width));
         return (
             <div
                 className={`colorBox-container${this.props.isContentActive() ? '-interactive' : ''}`}
@@ -84,9 +84,6 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 }
 
-
-ScriptingGlobals.add(
-    function interpColors(c1:string, c2:string, weight=0.5) {
-        return DashColor(c1).mix(DashColor(c2),weight)
-    }
-)
\ No newline at end of file
+ScriptingGlobals.add(function interpColors(c1: string, c2: string, weight = 0.5) {
+    return DashColor(c1).mix(DashColor(c2), weight);
+});
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index b88389de6..147dfb182 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -219,7 +219,7 @@ export class TableBox extends React.Component<TableBoxProps> {
                             </thead>
                             <tbody>
                                 {this._tableDataIds
-                                    .filter(rowId => this.startID <= rowId && rowId <= this.endID)
+                                    .filter((rowId, i) => this.startID <= i && i <= this.endID)
                                     ?.map(rowId => (
                                         <tr
                                             key={rowId}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d87efb7b1..943408010 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,11 +1,11 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { Dropdown, DropdownType, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
 import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
-import { AclPrivate, Animation, AudioPlay, DocData, DocViews, Width } from '../../../fields/DocSymbols';
+import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -141,7 +141,7 @@ export interface DocComponentView {
     annotationKey?: string;
     getTitle?: () => string;
     getCenter?: (xf: Transform) => { X: number; Y: number };
-    screenBounds?: () => { left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } };
+    screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>;
     ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
     ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
     snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number };
@@ -1016,7 +1016,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     removeDocument={this.hideLink(link)}
                     styleProvider={this.anchorStyleProvider}
                     LayoutTemplate={undefined}
-                    LayoutTemplateString={LinkAnchorBox.LayoutString(`link_anchor_${Doc.LinkEndpoint(link, this.rootDoc)}`)}
+                    LayoutTemplateString={LinkAnchorBox.LayoutString(`link_anchor_${LinkManager.anchorIndex(link, this.rootDoc)}`)}
                 />
             </div>
         ));
@@ -1520,7 +1520,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         if (!this.docView?.ContentDiv || this.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
             return undefined;
         }
-        if (this.docView._componentView?.screenBounds) {
+        if (this.docView._componentView?.screenBounds?.()) {
             return this.docView._componentView.screenBounds();
         }
         const xf = this.docView.props
@@ -1712,7 +1712,7 @@ ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuff
 
 ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) {
     const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data);
-    let wid = linkSource[Width]();
+    let wid = NumCast(linkSource._width);
     let embedding: Doc | undefined;
     const links = LinkManager.Links(linkSource);
     links.forEach(link => {
@@ -1723,7 +1723,7 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour
             embedding.x = wid;
             embedding.y = 0;
             embedding._lockedPosition = false;
-            wid += otherdoc[Width]();
+            wid += NumCast(otherdoc._width);
             Doc.AddDocToList(Doc.GetProto(linkCollection), 'data', embedding);
         }
     });
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index a77e4bdd1..d8b8bcb27 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -2,7 +2,6 @@ import EquationEditor from 'equation-editor-react';
 import { action, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
@@ -68,7 +67,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
         if (e.key === 'Tab') {
             const graph = Docs.Create.FunctionPlotDocument([this.rootDoc], {
-                x: NumCast(this.layoutDoc.x) + this.layoutDoc[Width](),
+                x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width),
                 y: NumCast(this.layoutDoc.y),
                 _width: 400,
                 _height: 300,
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index d2e1293da..de5ad1631 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -46,6 +46,15 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(FontIconBox, fieldKey);
     }
+    //
+    // This controls whether fontIconButtons will display labels under their icons or not
+    //
+    public static get ShowIconLabels() {
+        return BoolCast(Doc.UserDoc()._showLabel);
+    }
+    public static set ShowIconLabels(show: boolean) {
+        Doc.UserDoc()._showLabel = show;
+    }
     @observable noTooltip = false;
     showTemplate = (): void => {
         const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
@@ -161,7 +170,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                     Doc.UnBrushAllDocs();
                 })}>
                 {this.Icon(color)}
-                {!this.label || !Doc.GetShowIconLabels() ? null : (
+                {!this.label || !FontIconBox.ShowIconLabels ? null : (
                     <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}>
                         {' '}
                         {this.label}{' '}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 2f4f788d4..f7dee1cc1 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from 'mobx-react';
 import { extname } from 'path';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
-import { DocData, Width } from '../../../fields/DocSymbols';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -116,7 +116,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         );
         const layoutDoc = this.layoutDoc;
         this._disposers.path = reaction(
-            () => ({ nativeSize: this.nativeSize, width: this.layoutDoc[Width]() }),
+            () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width) }),
             ({ nativeSize, width }) => {
                 if (layoutDoc === this.layoutDoc || !this.layoutDoc._height) {
                     this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth;
@@ -240,7 +240,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         croppingProto.freeform_panY_max = anchh / viewScale;
         if (addCrop) {
             DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
-            cropping.x = NumCast(this.rootDoc.x) + this.rootDoc[Width]();
+            cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
             cropping.y = NumCast(this.rootDoc.y);
             this.props.addDocTab(cropping, OpenWhere.inParent);
         }
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index e66fed84b..a4dd5e7ef 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -2,7 +2,6 @@ import React = require('react');
 import { Bezier } from 'bezier-js';
 import { computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import { Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { aggregateBounds, emptyFunction, returnAlways, returnFalse, Utils } from '../../../Utils';
@@ -36,8 +35,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) {
             const a_invXf = this.anchor1.props.ScreenToLocalTransform().inverse();
             const b_invXf = this.anchor2.props.ScreenToLocalTransform().inverse();
-            const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(this.anchor1.rootDoc[Width](), this.anchor1.rootDoc[Height]()) };
-            const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(this.anchor2.rootDoc[Width](), this.anchor2.rootDoc[Height]()) };
+            const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(this.anchor1.rootDoc._width), NumCast(this.anchor1.rootDoc._height)) };
+            const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(this.anchor2.rootDoc._width), NumCast(this.anchor2.rootDoc._height)) };
 
             const pts = [] as number[][];
             pts.push([(a_scrBds.tl[0] + a_scrBds.br[0]) / 2, (a_scrBds.tl[1] + a_scrBds.br[1]) / 2]);
@@ -51,7 +50,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
             );
             return { left: agg.x, top: agg.y, right: agg.r, bottom: agg.b, center: undefined };
         }
-        return { left: 0, top: 0, right: 0, bottom: 0, center: undefined };
+        return undefined;
     };
     disposer: IReactionDisposer | undefined;
     componentDidMount() {
@@ -66,8 +65,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     const this_xf = parxf?.getTransform() ?? Transform.Identity; //this.props.ScreenToLocalTransform();
                     const a_invXf = a.props.ScreenToLocalTransform().inverse();
                     const b_invXf = b.props.ScreenToLocalTransform().inverse();
-                    const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(a.rootDoc[Width](), a.rootDoc[Height]()) };
-                    const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(b.rootDoc[Width](), b.rootDoc[Height]()) };
+                    const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(a.rootDoc._width), NumCast(a.rootDoc._height)) };
+                    const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(b.rootDoc._width), NumCast(b.rootDoc._height)) };
                     const a_bds = { tl: this_xf.transformPoint(a_scrBds.tl[0], a_scrBds.tl[1]), br: this_xf.transformPoint(a_scrBds.br[0], a_scrBds.br[1]) };
                     const b_bds = { tl: this_xf.transformPoint(b_scrBds.tl[0], b_scrBds.tl[1]), br: this_xf.transformPoint(b_scrBds.br[0], b_scrBds.br[1]) };
 
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 198cbe851..7e4f1da8e 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -3,13 +3,13 @@ import { Tooltip } from '@material-ui/core';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import wiki from 'wikijs';
-import { Doc, DocCastAsync, Opt } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
+import { Doc, Opt } from '../../../fields/Doc';
 import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
 import { DocServer } from '../../DocServer';
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
+import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
 import { LinkFollower } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
@@ -19,7 +19,6 @@ import { Transform } from '../../util/Transform';
 import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView';
 import './LinkDocPreview.scss';
 import React = require('react');
-import { DocumentManager } from '../../util/DocumentManager';
 
 interface LinkDocPreviewProps {
     linkDoc?: Doc;
@@ -56,21 +55,20 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
         LinkDocPreview._instance = this;
     }
 
-    @action init() {
+    @action
+    init() {
         var linkTarget = this.props.linkDoc;
         this._linkSrc = this.props.linkSrc;
         this._linkDoc = this.props.linkDoc;
-        const link_anchor_1 = this._linkDoc?.link_anchor_1 as Doc;
-        const link_anchor_2 = this._linkDoc?.link_anchor_2 as Doc;
+        const link_anchor_1 = DocCast(this._linkDoc?.link_anchor_1);
+        const link_anchor_2 = DocCast(this._linkDoc?.link_anchor_2);
         if (link_anchor_1 && link_anchor_2) {
             linkTarget = Doc.AreProtosEqual(link_anchor_1, this._linkSrc) || Doc.AreProtosEqual(link_anchor_1?.annotationOn as Doc, this._linkSrc) ? link_anchor_2 : link_anchor_1;
         }
         if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) {
-            // want to show annotation embedContainer document if annotation is not text
-            linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => (this._markerTargetDoc = this._targetDoc = anno)));
-        } else {
-            this._markerTargetDoc = this._targetDoc = linkTarget;
+            linkTarget = DocCast(linkTarget.annotationOn); // want to show annotation embedContainer document if annotation is not text
         }
+        this._markerTargetDoc = this._targetDoc = linkTarget;
         this._toolTipText = '';
         this.updateHref();
     }
@@ -190,17 +188,17 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
 
     width = () => {
         if (!this._targetDoc) return 225;
-        if (this._targetDoc[Width]() < this._targetDoc?.[Height]()) {
-            return (Math.min(225, this._targetDoc[Height]()) * this._targetDoc[Width]()) / this._targetDoc[Height]();
+        if (NumCast(this._targetDoc._width) < NumCast(this._targetDoc._height)) {
+            return (Math.min(225, NumCast(this._targetDoc._height)) * NumCast(this._targetDoc._width)) / NumCast(this._targetDoc._height);
         }
-        return Math.min(225, NumCast(this._targetDoc?.[Width](), 225));
+        return Math.min(225, NumCast(this._targetDoc._width, 225));
     };
     height = () => {
         if (!this._targetDoc) return 225;
-        if (this._targetDoc[Width]() > this._targetDoc?.[Height]()) {
-            return (Math.min(225, this._targetDoc[Width]()) * this._targetDoc[Height]()) / this._targetDoc[Width]();
+        if (NumCast(this._targetDoc._width) > NumCast(this._targetDoc._height)) {
+            return (Math.min(225, NumCast(this._targetDoc._width)) * NumCast(this._targetDoc._height)) / NumCast(this._targetDoc._width);
         }
-        return Math.min(225, NumCast(this._targetDoc?.[Height](), 225));
+        return Math.min(225, NumCast(this._targetDoc._height, 225));
     };
     @computed get previewHeader() {
         return !this._linkDoc || !this._markerTargetDoc || !this._targetDoc || !this._linkSrc ? null : (
diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx
index bdc074e0c..4bb0f14d2 100644
--- a/src/client/views/nodes/LoadingBox.tsx
+++ b/src/client/views/nodes/LoadingBox.tsx
@@ -1,4 +1,4 @@
-import { observable, runInAction } from 'mobx';
+import { action, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import ReactLoading from 'react-loading';
@@ -36,11 +36,28 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(LoadingBox, fieldKey);
     }
+    @observable public static CurrentlyLoading: Doc[] = []; // this assignment doesn't work. the actual assignment happens in DocumentManager's constructor
+    // removes from currently loading display
+    @action
+    public static removeCurrentlyLoading(doc: Doc) {
+        if (LoadingBox.CurrentlyLoading) {
+            const index = LoadingBox.CurrentlyLoading.indexOf(doc);
+            index !== -1 && LoadingBox.CurrentlyLoading.splice(index, 1);
+        }
+    }
+
+    // adds doc to currently loading display
+    @action
+    public static addCurrentlyLoading(doc: Doc) {
+        if (LoadingBox.CurrentlyLoading.indexOf(doc) === -1) {
+            LoadingBox.CurrentlyLoading.push(doc);
+        }
+    }
 
     _timer: any;
     @observable progress = '';
     componentDidMount() {
-        if (!Doc.CurrentlyLoading?.includes(this.rootDoc)) {
+        if (!LoadingBox.CurrentlyLoading?.includes(this.rootDoc)) {
             this.rootDoc.loadingError = 'Upload interrupted, please try again';
         } else {
             const updateFunc = async () => {
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 08dda2e1f..37935c513 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -5,7 +5,7 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
-import { DocCss, Highlight, Width } from '../../../../fields/DocSymbols';
+import { DocCss, Highlight } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
@@ -170,7 +170,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         .ScreenToLocalTransform()
                         .scale(this.props.NativeDimScaling?.() || 1)
                         .transformDirection(delta[0], delta[1]);
-                    const fullWidth = this.layoutDoc[Width]();
+                    const fullWidth = NumCast(this.layoutDoc._width);
                     const mapWidth = fullWidth - this.sidebarWidth();
                     if (this.sidebarWidth() + localDelta[0] > 0) {
                         this.layoutDoc._layout_showSidebar = true;
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index d38857d90..c42664abe 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -4,7 +4,6 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, runInAc
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
-import { Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { NumCast, StrCast } from '../../../../fields/Types';
@@ -368,7 +367,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         .ScreenToLocalTransform()
                         .scale(this.props.NativeDimScaling?.() || 1)
                         .transformDirection(delta[0], delta[1]);
-                    const fullWidth = this.layoutDoc[Width]();
+                    const fullWidth = NumCast(this.layoutDoc._width);
                     const mapWidth = fullWidth - this.sidebarWidth();
                     if (this.sidebarWidth() + localDelta[0] > 0) {
                         this._showSidebar = true;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index c068d9dd7..bde1d5fa4 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -4,7 +4,6 @@ import { observer } from 'mobx-react';
 import * as Pdfjs from 'pdfjs-dist';
 import 'pdfjs-dist/web/pdf_viewer.css';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { ComputedField } from '../../../fields/ScriptField';
@@ -63,7 +62,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         super(props);
         const nw = Doc.NativeWidth(this.Document, this.dataDoc) || 927;
         const nh = Doc.NativeHeight(this.Document, this.dataDoc) || 1200;
-        !this.Document._layout_fitWidth && (this.Document._height = this.Document[Width]() * (nh / nw));
+        !this.Document._layout_fitWidth && (this.Document._height = NumCast(this.Document._width) * (nh / nw));
         if (this.pdfUrl) {
             if (PDFBox.pdfcache.get(this.pdfUrl.url.href)) runInAction(() => (this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href)));
             else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action((pdf: any) => (this._pdf = pdf)));
@@ -104,15 +103,15 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
         const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
         const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
-        newDiv.style.width = this.layoutDoc[Width]().toString();
-        newDiv.style.height = this.layoutDoc[Height]().toString();
+        newDiv.style.width = NumCast(this.layoutDoc._width).toString();
+        newDiv.style.height = NumCast(this.layoutDoc._height).toString();
         this.replaceCanvases(docViewContent, newDiv);
         const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv);
 
         const anchx = NumCast(cropping.x);
         const anchy = NumCast(cropping.y);
-        const anchw = cropping[Width]() * (this.props.NativeDimScaling?.() || 1);
-        const anchh = cropping[Height]() * (this.props.NativeDimScaling?.() || 1);
+        const anchw = NumCast(cropping._width) * (this.props.NativeDimScaling?.() || 1);
+        const anchh = NumCast(cropping._height) * (this.props.NativeDimScaling?.() || 1);
         const viewScale = 1;
         cropping.title = 'crop: ' + this.rootDoc.title;
         cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
@@ -169,8 +168,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             CollectionFreeFormView.UpdateIcon(
                 filename,
                 docViewContent,
-                this.layoutDoc[Width](),
-                this.layoutDoc[Height](),
+                NumCast(this.layoutDoc._width),
+                NumCast(this.layoutDoc._height),
                 this.props.PanelWidth(),
                 this.props.PanelHeight(),
                 NumCast(this.layoutDoc._layout_scrollTop),
@@ -255,8 +254,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         this.dataDoc[this.props.fieldKey + '_numPages'] = np;
         Doc.SetNativeWidth(this.dataDoc, Math.max(Doc.NativeWidth(this.dataDoc), (nw * 96) / 72));
         Doc.SetNativeHeight(this.dataDoc, (nh * 96) / 72);
-        this.layoutDoc._height = this.layoutDoc[Width]() / (Doc.NativeAspect(this.dataDoc) || 1);
-        !this.Document._layout_fitWidth && (this.Document._height = this.Document[Width]() * (nh / nw));
+        this.layoutDoc._height = NumCast(this.layoutDoc._width) / (Doc.NativeAspect(this.dataDoc) || 1);
+        !this.Document._layout_fitWidth && (this.Document._height = NumCast(this.Document._width) * (nh / nw));
     };
 
     public search = action((searchString: string, bwd?: boolean, clear: boolean = false) => {
@@ -331,7 +330,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.NativeDimScaling?.() || 1)) / nativeWidth;
                 if (ratio >= 1) {
                     this.layoutDoc.nativeWidth = nativeWidth * ratio;
-                    onButton && (this.layoutDoc._width = this.layoutDoc[Width]() + localDelta[0]);
+                    onButton && (this.layoutDoc._width = NumCast(this.layoutDoc._width) + localDelta[0]);
                     this.layoutDoc._show_sidebar = nativeWidth !== this.layoutDoc._nativeWidth;
                 }
                 return false;
@@ -352,11 +351,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
         if (preview) {
             this._previewNativeWidth = nativeWidth * sideratio;
-            this._previewWidth = (this.layoutDoc[Width]() * nativeWidth * sideratio) / curNativeWidth;
+            this._previewWidth = (NumCast(this.layoutDoc._width) * nativeWidth * sideratio) / curNativeWidth;
             this._showSidebar = true;
         } else {
             this.layoutDoc.nativeWidth = nativeWidth * pdfratio;
-            this.layoutDoc._width = (this.layoutDoc[Width]() * nativeWidth * pdfratio) / curNativeWidth;
+            this.layoutDoc._width = (NumCast(this.layoutDoc._width) * nativeWidth * pdfratio) / curNativeWidth;
             this.layoutDoc._show_sidebar = nativeWidth !== this.layoutDoc._nativeWidth;
         }
     });
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index ebb8a3374..b2512a0e5 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -6,7 +6,6 @@ import { observer } from 'mobx-react';
 // import { BufferAttribute, Camera, Vector2, Vector3 } from 'three';
 import { DateField } from '../../../fields/DateField';
 import { Doc } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, DocCast, NumCast } from '../../../fields/Types';
@@ -18,16 +17,16 @@ import { DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
 import { CaptureManager } from '../../util/CaptureManager';
 import { SettingsManager } from '../../util/SettingsManager';
+import { TrackMovements } from '../../util/TrackMovements';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
 import { ContextMenu } from '../ContextMenu';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { media_state } from './AudioBox';
 import { FieldView, FieldViewProps } from './FieldView';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import './ScreenshotBox.scss';
 import { VideoBox } from './VideoBox';
-import { TrackMovements } from '../../util/TrackMovements';
-import { media_state } from './AudioBox';
 
 declare class MediaRecorder {
     constructor(e: any, options?: any); // whatever MediaRecorder has
@@ -145,7 +144,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         if (!nativeWidth || !nativeHeight) {
             if (!nativeWidth) Doc.SetNativeWidth(this.dataDoc, 1200);
             Doc.SetNativeHeight(this.dataDoc, (nativeWidth || 1200) / aspect);
-            this.layoutDoc._height = (this.layoutDoc[Width]() || 0) / aspect;
+            this.layoutDoc._height = NumCast(this.layoutDoc._width) / aspect;
         }
     };
 
@@ -295,7 +294,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         dictationTextProto.mediaState = ComputedField.MakeFunction(`self.${textField}_recordingSource.mediaState`);
         this.dataDoc[this.fieldKey + '_dictation'] = dictationText;
     };
-    videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], this.layoutDoc[Height]()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], this.layoutDoc[Width]())) * this.props.PanelWidth();
+    videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc._height)) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc._width))) * this.props.PanelWidth();
     formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight());
     render() {
         TraceMobx();
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index d7f7c9b73..81749c98b 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -4,7 +4,6 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from 'mobx-react';
 import { basename } from 'path';
 import { Doc, StrListCast } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
@@ -355,8 +354,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     updateIcon = () => {
         const makeIcon = (returnedfilename: string) => {
             this.dataDoc.icon = new ImageField(returnedfilename);
-            this.dataDoc.icon_nativeWidth = this.layoutDoc[Width]();
-            this.dataDoc.icon_nativeHeight = this.layoutDoc[Height]();
+            this.dataDoc.icon_nativeWidth = NumCast(this.layoutDoc._width);
+            this.dataDoc.icon_nativeHeight = NumCast(this.layoutDoc._height);
         };
         this.Snapshot(undefined, undefined, makeIcon);
     };
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 5c526fe38..a452b1cdd 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -3,7 +3,6 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from 'mobx-react';
 import * as WebRequest from 'web-request';
 import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { HtmlField } from '../../../fields/HtmlField';
 import { InkTool } from '../../../fields/InkField';
@@ -81,7 +80,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return this._url ? WebBox.urlHash(this._url) + '' : '';
     }
     @computed get scrollHeight() {
-        return Math.max(this.layoutDoc[Height](), this._scrollHeight);
+        return Math.max(NumCast(this.layoutDoc._height), this._scrollHeight);
     }
     @computed get allAnnotations() {
         return DocListCast(this.dataDoc[this.annotationKey]);
@@ -209,7 +208,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) {
                     if (!nativeWidth) Doc.SetNativeWidth(this.layoutDoc, 600);
                     Doc.SetNativeHeight(this.layoutDoc, (nativeWidth || 600) / youtubeaspect);
-                    this.layoutDoc._height = this.layoutDoc[Width]() / youtubeaspect;
+                    this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect;
                 }
             } // else it's an HTMLfield
         } else if (this.webField && !this.dataDoc.text) {
@@ -276,7 +275,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     focus = (anchor: Doc, options: DocFocusOptions) => {
         if (anchor !== this.rootDoc && this._outerRef.current) {
             const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
-            const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), anchor[Height](), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + anchor[Height](), this._scrollHeight));
+            const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), NumCast(anchor._height), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + NumCast(anchor._height), this._scrollHeight));
             if (scrollTo !== undefined) {
                 if (this._initialScroll === undefined) {
                     const focusTime = options.zoomTime ?? 500;
@@ -490,7 +489,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 this._scrollHeight = Math.max(this._scrollHeight, iframeContent.body.scrollHeight || 0);
                 if (this._scrollHeight) {
                     this.rootDoc.nativeHeight = Math.min(NumCast(this.rootDoc.nativeHeight), this._scrollHeight);
-                    this.layoutDoc.height = Math.min(this.layoutDoc[Height](), (this.layoutDoc[Width]() * NumCast(this.rootDoc.nativeHeight)) / NumCast(this.rootDoc.nativeWidth));
+                    this.layoutDoc.height = Math.min(NumCast(this.layoutDoc._height), (NumCast(this.layoutDoc._width) * NumCast(this.rootDoc.nativeHeight)) / NumCast(this.rootDoc.nativeWidth));
                 }
             };
             const swidth = Math.max(NumCast(this.layoutDoc.nativeWidth), iframeContent.body.scrollWidth || 0);
@@ -498,7 +497,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 const aspectResize = swidth / NumCast(this.rootDoc.nativeWidth);
                 this.rootDoc.nativeWidth = swidth;
                 this.rootDoc.nativeHeight = NumCast(this.rootDoc.nativeHeight) * aspectResize;
-                this.layoutDoc.height = this.layoutDoc[Height]() * aspectResize;
+                this.layoutDoc.height = NumCast(this.layoutDoc._height) * aspectResize;
             }
             initHeights();
             this._iframetimeout && clearTimeout(this._iframetimeout);
@@ -838,7 +837,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 if (ratio >= 1) {
                     this.layoutDoc.nativeWidth = nativeWidth * ratio;
                     this.layoutDoc.nativeHeight = nativeHeight * (1 + ratio);
-                    onButton && (this.layoutDoc._width = this.layoutDoc[Width]() + localDelta[0]);
+                    onButton && (this.layoutDoc._width = NumCast(this.layoutDoc._width) + localDelta[0]);
                     this.layoutDoc._layout_showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
                 }
                 return false;
@@ -873,9 +872,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     toggleSidebar = action((preview: boolean = false) => {
         var nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
         if (!nativeWidth) {
-            const defaultNativeWidth = this.rootDoc[this.fieldKey] instanceof WebField ? 850 : this.Document[Width]();
+            const defaultNativeWidth = this.rootDoc[this.fieldKey] instanceof WebField ? 850 : NumCast(this.Document._width);
             Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || defaultNativeWidth);
-            Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || (this.Document[Height]() / this.Document[Width]()) * defaultNativeWidth);
+            Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || (NumCast(this.Document._height) / NumCast(this.Document._width)) * defaultNativeWidth);
             nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
         }
         const sideratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth;
@@ -883,11 +882,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
         if (preview) {
             this._previewNativeWidth = nativeWidth * sideratio;
-            this._previewWidth = (this.layoutDoc[Width]() * nativeWidth * sideratio) / curNativeWidth;
+            this._previewWidth = (NumCast(this.layoutDoc._width) * nativeWidth * sideratio) / curNativeWidth;
             this._showSidebar = true;
         } else {
             this.layoutDoc._layout_showSidebar = !this.layoutDoc._layout_showSidebar;
-            this.layoutDoc._width = (this.layoutDoc[Width]() * nativeWidth * pdfratio) / curNativeWidth;
+            this.layoutDoc._width = (NumCast(this.layoutDoc._width) * nativeWidth * pdfratio) / curNativeWidth;
             if (!this.layoutDoc._layout_showSidebar && !(this.dataDoc[this.fieldKey] instanceof WebField)) {
                 this.layoutDoc.nativeWidth = this.dataDoc[this.fieldKey + '_nativeWidth'] = undefined;
             } else {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 41b1c59b0..515952ae4 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.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 { setCORS } from 'google-translate-api-browser';
 import { isEqual } from 'lodash';
 import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -14,7 +15,7 @@ import { EditorView } from 'prosemirror-view';
 import { BsMarkdownFill } from 'react-icons/bs';
 import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, Height, UpdatingFromServer, Width } from '../../../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -52,6 +53,7 @@ import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { StyleProp } from '../../StyleProvider';
+import { media_state } from '../AudioBox';
 import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { LinkDocPreview } from '../LinkDocPreview';
@@ -71,8 +73,6 @@ import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
 import applyDevTools = require('prosemirror-dev-tools');
 import React = require('react');
-import { media_state } from '../AudioBox';
-import { setCORS } from 'google-translate-api-browser';
 // setting up cors-anywhere server address
 const translate = setCORS('http://cors-anywhere.herokuapp.com/');
 export const GoogleRef = 'googleDocId';
@@ -434,10 +434,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             var tr = this._editorView.state.tr as any;
             const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor;
             tr = tr.removeMark(0, tr.doc.content.size, autoAnch);
-            DocListCast(Doc.MyPublishedDocs.data).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
+            Doc.MyPublishedDocs.forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
             tr = tr.setSelection(isNodeSel && false ? new NodeSelection(tr.doc.resolve(f)) : new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
             this._editorView?.dispatch(tr);
-            // this.prepareForTyping();
         }
         oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink);
     };
@@ -460,7 +459,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             if (!(cfield instanceof ComputedField)) {
                 this.dataDoc.title = (prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : '')).trim();
                 if (str.startsWith('@') && str.length > 1) {
-                    Doc.AddDocToList(Doc.MyPublishedDocs, undefined, this.rootDoc);
+                    Doc.AddToMyPublished(this.rootDoc);
                 }
             }
         }
@@ -487,9 +486,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                                 ) ||
                                     DocUtils.MakeLink(this.rootDoc, target, { link_relationship: LinkManager.AutoKeywords })!);
                             newAutoLinks.add(alink);
+                            // DocCast(alink.link_anchor_1).followLinkLocation = 'add:right';
                             const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }];
                             allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
-                            const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term', location: 'add:right' });
+                            const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term' });
                             tr = tr.addMark(pos, pos + node.nodeSize, link);
                         }
                     });
@@ -579,8 +579,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     // embed document when drag marked as embed
                 } else if (de.embedKey) {
                     const node = schema.nodes.dashDoc.create({
-                        width: draggedDoc[Width](),
-                        height: draggedDoc[Height](),
+                        width: NumCast(draggedDoc._width),
+                        height: NumCast(draggedDoc._height),
                         title: 'dashDoc',
                         docId: draggedDoc[Id],
                         float: 'unset',
@@ -733,7 +733,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             .scale(this.props.NativeDimScaling?.() || 1)
             .transformDirection(delta[0], delta[1]);
         const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.layout_sidebarWidthPercent.replace('%', ''))) / 100;
-        const width = this.layoutDoc[Width]() + localDelta[0];
+        const width = NumCast(this.layoutDoc._width) + localDelta[0];
         this.layoutDoc._layout_sidebarWidthPercent = Math.max(0, (sidebarWidth + localDelta[0]) / width) * 100 + '%';
         this.layoutDoc.width = width;
         this.layoutDoc._layout_showSidebar = this.layoutDoc._layout_sidebarWidthPercent !== '0%';
@@ -1407,7 +1407,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, editable: false }, undefined, [
                             view.state.schema.marks.linkAnchor.create({
                                 allAnchors: [{ href: `/doc/${this.rootDoc[Id]}`, title: this.rootDoc.title, anchorId: `${this.rootDoc[Id]}` }],
-                                location: 'add:right',
                                 title: `from: ${DocCast(pdfAnchor.embedContainer).title}`,
                                 noPreview: true,
                                 docref: false,
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 6f07588b3..c565c4294 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -46,7 +46,7 @@ export const marks: { [index: string]: MarkSpec } = {
         toDOM(node: any) {
             const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), '');
             const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), '');
-            return ['a', { class: anchorids, 'data-targethrefs': targethrefs, 'data-noPreview': 'true', 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0];
+            return ['a', { class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0];
         },
     },
     noAutoLinkAnchor: {
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 82ed9e8d5..0402516a2 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -3,7 +3,6 @@ import { Tooltip } from '@material-ui/core';
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
-import { Height, Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
@@ -15,6 +14,7 @@ import { DragManager } from '../../../util/DragManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
 import { undoable, undoBatch } from '../../../util/UndoManager';
+import { TreeView } from '../../collections/TreeView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
@@ -25,9 +25,6 @@ import { PresBox } from './PresBox';
 import './PresElementBox.scss';
 import { PresMovement } from './PresEnums';
 import React = require('react');
-import { TreeView } from '../../collections/TreeView';
-import { BranchingTrailManager } from '../../../util/BranchingTrailManager';
-import { MultiToggle, Type } from 'browndash-components';
 /**
  * This class models the view a document added to presentation will have in the presentation.
  * It involves some functionality for its buttons and options.
@@ -331,7 +328,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     @computed get recordingIsInOverlay() {
-        return DocListCast(Doc.MyOverlayDocs.data).some(doc => doc.slides === this.rootDoc);
+        return Doc.MyOverlayDocs.some(doc => doc.slides === this.rootDoc);
     }
 
     // a previously recorded video will have timecode defined
@@ -341,14 +338,12 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     removeAllRecordingInOverlay = () => {
-        DocListCast(Doc.MyOverlayDocs.data)
-            .filter(doc => doc.slides === this.rootDoc)
-            .forEach(Doc.RemFromMyOverlay);
+        Doc.MyOverlayDocs.filter(doc => doc.slides === this.rootDoc).forEach(Doc.RemFromMyOverlay);
     };
 
     static removeEveryExistingRecordingInOverlay = () => {
         // Remove every recording that already exists in overlay view
-        DocListCast(Doc.MyOverlayDocs.data).forEach(doc => {
+        Doc.MyOverlayDocs.forEach(doc => {
             if (doc.slides !== null) {
                 // if it's a recording video, don't remove from overlay (user can lose data)
                 if (PresElementBox.videoIsRecorded(DocCast(doc.slides))) {
@@ -407,8 +402,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
             activeItem.recording = recording;
 
             // make recording box appear in the bottom right corner of the screen
-            recording.overlayX = window.innerWidth - recording[Width]() - 20;
-            recording.overlayY = window.innerHeight - recording[Height]() - 20;
+            recording.overlayX = window.innerWidth - NumCast(recording._width) - 20;
+            recording.overlayY = window.innerHeight - NumCast(recording._height) - 20;
             Doc.AddToMyOverlay(recording);
         }
     };
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 17a8048e9..38e4f65b6 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -24,7 +24,7 @@ interface IAnnotationProps extends FieldViewProps {
 export class Annotation extends React.Component<IAnnotationProps> {
     render() {
         return (
-            <div style={{ display: this.props.anno.textCopied && !Doc.isBrushedHighlightedDegree(this.props.anno) ? 'none' : undefined }}>
+            <div style={{ display: this.props.anno.textCopied && !Doc.GetBrushHighlightStatus(this.props.anno) ? 'none' : undefined }}>
                 {DocListCast(this.props.anno.text_inlineAnnotations).map(a => (
                     <RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} />
                 ))}
@@ -90,12 +90,12 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
     @computed get linkHighlighted() {
         for (const link of LinkManager.Instance.getAllDirectLinks(this.props.document)) {
             const a1 = LinkManager.getOppositeAnchor(link, this.props.document);
-            if (a1 && Doc.IsBrushedDegree(DocCast(a1.annotationOn, this.props.document))) return true;
+            if (a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, this.props.document))) return true;
         }
     }
 
     render() {
-        const brushed = this.annoTextRegion && Doc.isBrushedHighlightedDegree(this.annoTextRegion);
+        const brushed = this.annoTextRegion && Doc.GetBrushHighlightStatus(this.annoTextRegion);
         return (
             <div
                 className="htmlAnnotation"
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 2f9eea492..6dece87b5 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1,46 +1,23 @@
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { saveAs } from 'file-saver';
 import { action, computed, observable, ObservableMap, ObservableSet, runInAction } from 'mobx';
 import { computedFn } from 'mobx-utils';
 import { alias, map, serializable } from 'serializr';
 import { DocServer } from '../client/DocServer';
-import { DocumentType } from '../client/documents/DocumentTypes';
+import { CollectionViewType, DocumentType } from '../client/documents/DocumentTypes';
 import { LinkManager } from '../client/util/LinkManager';
 import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
 import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper';
 import { undoable } from '../client/util/UndoManager';
+import { DocumentView } from '../client/views/nodes/DocumentView';
 import { decycle } from '../decycler/decycler';
 import * as JSZipUtils from '../JSZipUtils';
-import { incrementTitleCopy, intersectRect, Utils } from '../Utils';
+import { incrementTitleCopy, Utils } from '../Utils';
 import { DateField } from './DateField';
 import {
-    AclAdmin,
-    AclAugment,
-    AclEdit,
-    AclPrivate,
-    AclReadonly,
-    Animation,
-    AudioPlay,
-    Brushed,
-    CachedUpdates,
-    DirectLinks,
-    DocAcl,
-    DocCss,
-    DocData,
-    DocFields,
-    DocLayout,
-    DocViews,
-    FieldKeys,
-    FieldTuples,
-    ForceServerWrite,
-    Height,
-    Highlight,
-    Initializing,
-    Self,
-    SelfProxy,
-    UpdatingFromServer,
-    Width,
-} from './DocSymbols';
+    AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, Animation, AudioPlay, Brushed, CachedUpdates, DirectLinks,
+    DocAcl, DocCss, DocData, DocFields, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight,
+    Initializing, Self, SelfProxy, UpdatingFromServer, Width
+} from './DocSymbols'; // prettier-ignore
 import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToScriptString, ToString } from './FieldSymbols';
 import { InkField, InkTool } from './InkField';
 import { List, ListFieldName } from './List';
@@ -52,9 +29,8 @@ import { listSpec } from './Schema';
 import { ComputedField, ScriptField } from './ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types';
 import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';
-import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, TraceMobx } from './util';
+import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, setter, SharingPermissions } from './util';
 import JSZip = require('jszip');
-import { DocumentView } from '../client/views/nodes/DocumentView';
 export const LinkedTo = '-linkedTo';
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -66,25 +42,23 @@ export namespace Field {
                 : ''
             : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : field instanceof ScriptField ? `$=${field.script.originalScript}` : Field.toScriptString(field));
     }
-    export function toScriptString(field: Field): string {
+    export function toScriptString(field: Field) {
         switch (typeof field) {
-            case 'string':
-                if (field.startsWith('{"')) return `'${field}'`; // bcz: hack ... want to quote the string the right way. if there are nested "'s, then use ' instead of ".  In this case, test for the start of a JSON string of the format {"property": ... } and use outer 's instead of "s
-                return !field.includes('`') ? `\`${field}\`` : `"${field}"`;
+            case 'string': if (field.startsWith('{"')) return `'${field}'`; // bcz: hack ... want to quote the string the right way. if there are nested "'s, then use ' instead of ".  In this case, test for the start of a JSON string of the format {"property": ... } and use outer 's instead of "s
+                           return !field.includes('`') ? `\`${field}\`` : `"${field}"`;
             case 'number':
-            case 'boolean':
-                return String(field);
-        }
-        return field?.[ToScriptString]?.() ?? 'null';
+            case 'boolean':return String(field);
+            default:       return field?.[ToScriptString]?.() ?? 'null';
+        } // prettier-ignore
     }
-    export function toString(field: Field): string {
+    export function toString(field: Field) {
         if (typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') return String(field);
         return field?.[ToString]?.() || '';
     }
     export function IsField(field: any): field is Field;
     export function IsField(field: any, includeUndefined: true): field is Field | undefined;
     export function IsField(field: any, includeUndefined: boolean = false): field is Field | undefined {
-        return typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean' || field instanceof ObjectField || field instanceof RefField || (includeUndefined && field === undefined);
+        return ['string', 'number', 'boolean'].includes(typeof field) || field instanceof ObjectField || field instanceof RefField || (includeUndefined && field === undefined);
     }
     export function Copy(field: any) {
         return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field;
@@ -107,11 +81,6 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
     const list = Cast(field, listSpec(Doc));
     return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
 }
-
-export async function DocCastAsync(field: FieldResult): Promise<Opt<Doc>> {
-    return Cast(field, Doc);
-}
-
 export function NumListCast(field: FieldResult, defaultVal: number[] = []) {
     return Cast(field, listSpec('number'), defaultVal);
 }
@@ -162,140 +131,51 @@ export function updateCachedAcls(doc: Doc) {
 @Deserializable('Doc', updateCachedAcls, ['id'])
 export class Doc extends RefField {
     @observable public static RecordingEvent = 0;
-
-    // this isn't really used at the moment, but is intended to indicate whether ink stroke are passed through a gesture recognizer
-    static GetRecognizeGestures() {
-        return BoolCast(Doc.UserDoc()._recognizeGestures);
-    }
-    static SetRecognizeGestures(show: boolean) {
-        Doc.UserDoc()._recognizeGestures = show;
-    }
-
-    //
-    // This controls whether fontIconButtons will display labels under their icons or not
-    //
-    static GetShowIconLabels() {
-        return BoolCast(Doc.UserDoc()._showLabel);
-    }
-    static SetShowIconLabels(show: boolean) {
-        Doc.UserDoc()._showLabel = show;
-    }
-    @observable public static CurrentlyLoading: Doc[] = []; // this assignment doesn't work. the actual assignment happens in DocumentManager's constructor
-    // removes from currently loading display
-    @action
-    public static removeCurrentlyLoading(doc: Doc) {
-        if (Doc.CurrentlyLoading) {
-            const index = Doc.CurrentlyLoading.indexOf(doc);
-            index !== -1 && Doc.CurrentlyLoading.splice(index, 1);
-        }
-    }
-
-    // adds doc to currently loading display
-    @action
-    public static addCurrentlyLoading(doc: Doc) {
-        if (Doc.CurrentlyLoading.indexOf(doc) === -1) {
-            Doc.CurrentlyLoading.push(doc);
-        }
-    }
-
     @observable public static GuestDashboard: Doc | undefined;
     @observable public static GuestTarget: Doc | undefined;
     @observable public static GuestMobile: Doc | undefined;
-    public static get MySharedDocs() {
-        return DocCast(Doc.UserDoc().mySharedDocs);
-    }
-    public static get MyUserDocView() {
-        return DocCast(Doc.UserDoc().myUserDocView);
-    }
-    public static get MyDockedBtns() {
-        return DocCast(Doc.UserDoc().myDockedBtns);
-    }
-    public static get MySearcher() {
-        return DocCast(Doc.UserDoc().mySearcher);
-    }
-    public static get MyHeaderBar() {
-        return DocCast(Doc.UserDoc().myHeaderBar);
-    }
-    public static get MyLeftSidebarMenu() {
-        return DocCast(Doc.UserDoc().myLeftSidebarMenu);
-    }
-    public static get MyLeftSidebarPanel() {
-        return DocCast(Doc.UserDoc().myLeftSidebarPanel);
-    }
-    public static get MyContextMenuBtns() {
-        return DocCast(Doc.UserDoc().myContextMenuBtns);
-    }
-    public static get MyTopBarBtns() {
-        return DocCast(Doc.UserDoc().myTopBarBtns);
-    }
-    public static get MyRecentlyClosed() {
-        return DocCast(Doc.UserDoc().myRecentlyClosed);
-    }
-    public static get MyTrails() {
-        return DocCast(Doc.ActiveDashboard?.myTrails);
-    }
-    public static IsInMyOverlay(doc: Doc) {
-        return DocListCast(Doc.MyOverlayDocs?.data).includes(doc);
-    }
-    public static AddToMyOverlay(doc: Doc) {
-        Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc);
-    }
-    public static RemFromMyOverlay(doc: Doc) {
-        Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, doc);
-    }
-    public static get MyOverlayDocs() {
-        return DocCast(Doc.UserDoc().myOverlayDocs);
-    }
-    public static get MyPublishedDocs() {
-        return DocCast(Doc.UserDoc().myPublishedDocs);
-    }
-    public static get MyDashboards() {
-        return DocCast(Doc.UserDoc().myDashboards);
-    }
-    public static get MyTemplates() {
-        return DocCast(Doc.UserDoc().myTemplates);
-    }
-    public static get MyImports() {
-        return DocCast(Doc.UserDoc().myImports);
-    }
-    public static get MyFilesystem() {
-        return DocCast(Doc.UserDoc().myFilesystem);
-    }
-    public static get MyTools() {
-        return DocCast(Doc.UserDoc().myTools);
-    }
-    public static get ActivePage() {
-        return StrCast(Doc.UserDoc().activePage);
-    }
-    public static set ActivePage(val) {
-        Doc.UserDoc().activePage = val;
-        DocServer.UPDATE_SERVER_CACHE();
-    }
-    public static IsComicStyle(doc?: Doc) {
-        return doc && Doc.ActiveDashboard && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic';
-    }
-    public static get ActiveDashboard() {
-        return DocCast(Doc.UserDoc().activeDashboard);
-    }
-    public static set ActiveDashboard(val: Doc | undefined) {
-        const overlays = Cast(Doc.MyOverlayDocs.data, listSpec(Doc), null);
-        overlays && (overlays.length = 0);
-        Doc.UserDoc().activeDashboard = val;
-    }
-    public static set ActiveTool(tool: InkTool) {
-        Doc.UserDoc().activeTool = tool;
-    }
-    public static get ActiveTool(): InkTool {
-        return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool;
-    }
-    public static get ActivePresentation(): Opt<Doc> {
-        return DocCast(Doc.ActiveDashboard?.activePresentation);
-    }
-    public static set ActivePresentation(val) {
-        if (Doc.ActiveDashboard) {
-            Doc.ActiveDashboard.activePresentation = val;
-        }
-    }
+    public static CurrentUserEmail: string = '';
+
+    public static get MySharedDocs()          { return DocCast(Doc.UserDoc().mySharedDocs); } // prettier-ignore
+    public static get MyUserDocView()         { return DocCast(Doc.UserDoc().myUserDocView); } // prettier-ignore
+    public static get MyDockedBtns()          { return DocCast(Doc.UserDoc().myDockedBtns); } // prettier-ignore
+    public static get MySearcher()            { return DocCast(Doc.UserDoc().mySearcher); } // prettier-ignore
+    public static get MyHeaderBar()           { return DocCast(Doc.UserDoc().myHeaderBar); } // prettier-ignore
+    public static get MyLeftSidebarMenu()     { return DocCast(Doc.UserDoc().myLeftSidebarMenu); } // prettier-ignore
+    public static get MyLeftSidebarPanel()    { return DocCast(Doc.UserDoc().myLeftSidebarPanel); } // prettier-ignore
+    public static get MyContextMenuBtns()     { return DocCast(Doc.UserDoc().myContextMenuBtns); } // prettier-ignore
+    public static get MyTopBarBtns()          { return DocCast(Doc.UserDoc().myTopBarBtns); } // prettier-ignore
+    public static get MyRecentlyClosed()      { return DocCast(Doc.UserDoc().myRecentlyClosed); } // prettier-ignore
+    public static get MyTrails()              { return DocCast(Doc.ActiveDashboard?.myTrails); } // prettier-ignore
+    public static get MyOverlayDocs()         { return DocListCast(Doc.ActiveDashboard?.myOverlayDocs ?? DocCast(Doc.UserDoc().myOverlayDocs).data); } // prettier-ignore
+    public static get MyPublishedDocs()       { return DocListCast(Doc.ActiveDashboard?.myPublishedDocs ?? DocCast(Doc.UserDoc().myPublishedDocs).data); } // prettier-ignore
+    public static get MyDashboards()          { return DocCast(Doc.UserDoc().myDashboards); } // prettier-ignore
+    public static get MyTemplates()           { return DocCast(Doc.UserDoc().myTemplates); } // prettier-ignore
+    public static get MyImports()             { return DocCast(Doc.UserDoc().myImports); } // prettier-ignore
+    public static get MyFilesystem()          { return DocCast(Doc.UserDoc().myFilesystem); } // prettier-ignore
+    public static get MyTools()               { return DocCast(Doc.UserDoc().myTools); } // prettier-ignore
+    public static get noviceMode()            { return BoolCast(Doc.UserDoc().noviceMode);  } // prettier-ignore
+    public static set noviceMode(val)         { Doc.UserDoc().noviceMode = val; } // prettier-ignore
+    public static get IsSharingEnabled()      { return BoolCast(Doc.UserDoc().isSharingEnabled); } // prettier-ignore
+    public static set IsSharingEnabled(val)   { Doc.UserDoc().isSharingEnabled = val; } // prettier-ignore
+    public static get defaultAclPrivate()     { return Doc.UserDoc().defaultAclPrivate; } // prettier-ignore
+    public static set defaultAclPrivate(val)  { Doc.UserDoc().defaultAclPrivate = val; } // prettier-ignore
+    public static get ActivePage()            { return StrCast(Doc.UserDoc().activePage); } // prettier-ignore
+    public static set ActivePage(val)         { Doc.UserDoc().activePage = val; } // prettier-ignore
+    public static get ActiveTool(): InkTool   { return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool;  } // prettier-ignore
+    public static set ActiveTool(tool:InkTool){ Doc.UserDoc().activeTool = tool; } // prettier-ignore
+    public static get ActivePresentation()    { return DocCast(Doc.ActiveDashboard?.activePresentation) as Opt<Doc>;  } // prettier-ignore
+    public static set ActivePresentation(val) { Doc.ActiveDashboard && (Doc.ActiveDashboard.activePresentation = val) } // prettier-ignore
+    public static get ActiveDashboard()       { return DocCast(Doc.UserDoc().activeDashboard); } // prettier-ignore
+    public static set ActiveDashboard(val: Opt<Doc>) {  Doc.UserDoc().activeDashboard = val; } // prettier-ignore
+
+    public static IsInMyOverlay(doc: Doc)     { return Doc.MyOverlayDocs.includes(doc); } // prettier-ignore
+    public static AddToMyOverlay(doc: Doc)    { Doc.ActiveDashboard?.myOverlayDocs ? Doc.AddDocToList(Doc.ActiveDashboard, 'myOverlayDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
+    public static RemFromMyOverlay(doc: Doc)  { Doc.ActiveDashboard?.myOverlayDocs ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myOverlayDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
+    public static AddToMyPublished(doc: Doc)  { Doc.ActiveDashboard?.myPublishedDocs ? Doc.AddDocToList(Doc.ActiveDashboard, 'myPublishedDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
+    public static RemFromMyPublished(doc: Doc){ Doc.ActiveDashboard?.myPublishedDocs ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myPublishedDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
+    public static IsComicStyle(doc?: Doc)     { return doc && Doc.ActiveDashboard && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic' ; } // prettier-ignore
+
     constructor(id?: FieldId, forceSave?: boolean) {
         super(id);
         const docProxy = new Proxy<this>(this, {
@@ -359,17 +239,14 @@ export class Doc extends RefField {
     @observable public [Highlight]: boolean = false;
     @observable public [Brushed]: boolean = false;
     @observable public [DocViews] = new ObservableSet<DocumentView>();
-    static __Anim(Doc: Doc) {
-        // for debugging to print AnimationSym field easily.
-        return Doc[Animation];
-    }
 
+    private [Self] = this;
+    private [SelfProxy]: any;
     private [UpdatingFromServer]: boolean = false;
     private [ForceServerWrite]: boolean = false;
-    public [Initializing]: boolean = false;
+    private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
 
-    private [Self] = this;
-    private [SelfProxy]: any;
+    public [Initializing]: boolean = false;
     public [FieldChanged] = (diff: undefined | { op: '$addToSet' | '$remFromSet' | '$set'; items: Field[] | undefined; length: number | undefined; hint?: any }, serverOp: any) => {
         if (!this[UpdatingFromServer] || this[ForceServerWrite]) {
             DocServer.UpdateField(this[Id], serverOp);
@@ -380,9 +257,7 @@ export class Doc extends RefField {
     public [Height] = () => NumCast(this[SelfProxy]._height);
     public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`;
     public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`;
-    public get [DocLayout]() {
-        return this[SelfProxy].__LAYOUT__;
-    }
+    public get [DocLayout]() { return this[SelfProxy].__LAYOUT__; } // prettier-ignore
     public get [DocData](): Doc {
         const self = this[SelfProxy];
         return self.resolvedDataDoc && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self);
@@ -403,29 +278,6 @@ export class Doc extends RefField {
         return undefined;
     }
 
-    private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
-    public static get noviceMode() {
-        return Doc.UserDoc().noviceMode as boolean;
-    }
-    public static set noviceMode(val) {
-        Doc.UserDoc().noviceMode = val;
-    }
-    public static get IsSharingEnabled() {
-        return Doc.UserDoc().isSharingEnabled as boolean;
-    }
-    public static set IsSharingEnabled(val) {
-        Doc.UserDoc().isSharingEnabled = val;
-    }
-    public static get defaultAclPrivate() {
-        return Doc.UserDoc().defaultAclPrivate;
-    }
-    public static set defaultAclPrivate(val) {
-        Doc.UserDoc().defaultAclPrivate = val;
-    }
-    public static CurrentUserEmail: string = '';
-    public static get CurrentUserEmailNormalized() {
-        return normalizeEmail(Doc.CurrentUserEmail);
-    }
     public async [HandleUpdate](diff: any) {
         const set = diff.$set;
         const sameAuthor = this.author === Doc.CurrentUserEmail;
@@ -482,17 +334,6 @@ export class Doc extends RefField {
 }
 
 export namespace Doc {
-    // export function GetAsync(doc: Doc, key: string, ignoreProto: boolean = false): Promise<Field | undefined> {
-    //     const self = doc[Self];
-    //     return new Promise(res => getField(self, key, ignoreProto, res));
-    // }
-    // export function GetTAsync<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): Promise<T | undefined> {
-    //     return new Promise(async res => {
-    //         const field = await GetAsync(doc, key, ignoreProto);
-    //         return Cast(field, ctor);
-    //     });
-    // }
-
     export function SetContainer(doc: Doc, container: Doc) {
         doc.embedContainer = container;
     }
@@ -626,10 +467,9 @@ export namespace Doc {
     /**
      * @returns the index of doc toFind in list of docs, -1 otherwise
      */
-    export function IndexOf(toFind: Doc, list: Doc[], allowProtos: boolean = true) {
-        let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind ? i : p), -1);
-        index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind) ? i : p), -1);
-        return index; // list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind));
+    export function IndexOf(toFind: Doc, list: Doc[]) {
+        const index = list.indexOf(toFind);
+        return index !== -1 ? index : list.findIndex(doc => Doc.AreProtosEqual(doc, toFind));
     }
 
     /**
@@ -663,11 +503,10 @@ export namespace Doc {
         }
         const list = Cast(listDoc[key], listSpec(Doc));
         if (list) {
-            if (allowDuplicates !== true) {
-                const pind = list.reduce((l, d, i) => (d instanceof Doc && d[Id] === doc[Id] ? i : l), -1);
+            if (!allowDuplicates) {
+                const pind = list.findIndex(d => d instanceof Doc && d[Id] === doc[Id]);
                 if (pind !== -1) {
                     return true;
-                    //list.splice(pind, 1);  // bcz: this causes schemaView docs in the Catalog to move to the bottom of the schema view when they are dragged even though they haven't left the collection
                 }
             }
             if (first) {
@@ -687,22 +526,6 @@ export namespace Doc {
         return false;
     }
 
-    /**
-     * Computes the bounds of the contents of a set of documents.
-     */
-    export function ComputeContentBounds(docList: Doc[]) {
-        const bounds = docList.reduce(
-            (bounds, doc) => ({
-                x: Math.min(NumCast(doc.x), bounds.x),
-                y: Math.min(NumCast(doc.y), bounds.y),
-                r: Math.max(NumCast(doc.x) + doc[Width](), bounds.r),
-                b: Math.max(NumCast(doc.y) + doc[Height](), bounds.b),
-            }),
-            { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE }
-        );
-        return bounds;
-    }
-
     export function MakeEmbedding(doc: Doc, id?: string) {
         const embedding = !GetT(doc, 'isDataDoc', 'boolean', true) && doc.proto ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
         const layout = Doc.LayoutField(embedding);
@@ -779,7 +602,7 @@ export namespace Doc {
                 } else if (field instanceof RefField) {
                     assignKey(field);
                 } else if (cfield instanceof ComputedField) {
-                    assignKey(cfield[Copy]()); // ComputedField.MakeFunction(cfield.script.originalScript));
+                    assignKey(cfield[Copy]());
                 } else if (field instanceof ObjectField) {
                     await copyObjectField(field);
                 } else if (field instanceof Promise) {
@@ -990,29 +813,6 @@ export namespace Doc {
         return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc), data: resolvedDataDoc };
     }
 
-    export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc {
-        Object.keys(doc).forEach(key => {
-            const field = ProxyField.WithoutProxy(() => doc[key]);
-            if (key === 'proto' && copyProto) {
-                if (doc.proto instanceof Doc && overwrite.proto instanceof Doc) {
-                    overwrite[key] = Doc.Overwrite(doc.proto, overwrite.proto);
-                }
-            } else {
-                if (field instanceof RefField) {
-                    overwrite[key] = field;
-                } else if (field instanceof ObjectField) {
-                    overwrite[key] = ObjectField.MakeCopy(field);
-                } else if (field instanceof Promise) {
-                    debugger; //This shouldn't happend...
-                } else {
-                    overwrite[key] = field;
-                }
-            }
-        });
-
-        return overwrite;
-    }
-
     export function FindReferences(infield: Doc | List<any>, references: Set<Doc>, system: boolean | undefined) {
         if (infield instanceof List<any>) {
             infield.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
@@ -1217,22 +1017,8 @@ export namespace Doc {
         return '/doc/' + (doc ? doc[Id] : '');
     }
 
-    export function overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
-        const doc2Layout = Doc.Layout(doc2);
-        const doc1Layout = Doc.Layout(doc1);
-        const x2 = NumCast(doc2.x) - clusterDistance;
-        const y2 = NumCast(doc2.y) - clusterDistance;
-        const w2 = NumCast(doc2Layout._width) + clusterDistance;
-        const h2 = NumCast(doc2Layout._height) + clusterDistance;
-        const x = NumCast(doc1.x) - clusterDistance;
-        const y = NumCast(doc1.y) - clusterDistance;
-        const w = NumCast(doc1Layout._width) + clusterDistance;
-        const h = NumCast(doc1Layout._height) + clusterDistance;
-        return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 });
-    }
-
-    export function isBrushedHighlightedDegree(doc: Doc) {
-        return Doc.IsHighlighted(doc) ? DocBrushStatus.highlighted : Doc.IsBrushedDegree(doc);
+    export function GetBrushHighlightStatus(doc: Doc) {
+        return Doc.IsHighlighted(doc) ? DocBrushStatus.highlighted : Doc.GetBrushStatus(doc);
     }
     export class DocBrush {
         BrushedDoc = new Set<Doc>();
@@ -1269,12 +1055,12 @@ export namespace Doc {
         return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
     }
     export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) {
-        return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeWidth'], useWidth ? doc[Width]() : 0));
+        return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeWidth'], useWidth ? NumCast(doc._width) : 0));
     }
     export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) {
         if (!doc) return 0;
-        const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) * doc[Height]()) / doc[Width]();
-        const dheight = NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeHeight'], useHeight ? doc[Height]() : 0);
+        const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) * NumCast(doc._height)) / NumCast(doc._width);
+        const dheight = NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeHeight'], useHeight ? NumCast(doc._height) : 0);
         return NumCast(doc._nativeHeight, nheight || dheight);
     }
     export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) {
@@ -1337,36 +1123,22 @@ export namespace Doc {
         highlighted = 3,
     }
     // returns 'how' a Doc has been brushed over - whether the document itself was brushed, it's prototype, or neither
-    export function IsBrushedDegree(doc: Doc) {
+    export function GetBrushStatus(doc: Doc) {
         if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return DocBrushStatus.unbrushed;
         return doc[Brushed] ? DocBrushStatus.selfBrushed : Doc.GetProto(doc)[Brushed] ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
     }
     export function BrushDoc(doc: Doc, unbrush = false) {
-        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
-        brushManager.brushDoc(doc, unbrush);
-        brushManager.brushDoc(Doc.GetProto(doc), unbrush);
+        if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) {
+            brushManager.brushDoc(doc, unbrush);
+            brushManager.brushDoc(Doc.GetProto(doc), unbrush);
+        }
         return doc;
     }
     export function UnBrushDoc(doc: Doc) {
         return BrushDoc(doc, true);
     }
-
-    export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) {
-        const linkAnchor2 = DocCast(linkDoc.link_anchor_2);
-        const linkAnchor1 = DocCast(linkDoc.link_anchor_1);
-        if (linkDoc.link_matchEmbeddings) {
-            return [linkAnchor2, linkAnchor2.annotationOn].includes(anchorDoc) ? '2' : '1';
-        }
-        if (Doc.AreProtosEqual(linkAnchor2, anchorDoc) || Doc.AreProtosEqual(linkAnchor2.annotationOn as Doc, anchorDoc)) return '2';
-        return Doc.AreProtosEqual(linkAnchor1, anchorDoc) || Doc.AreProtosEqual(linkAnchor1.annotationOn as Doc, anchorDoc) ? '1' : '2';
-    }
-
-    export function linkFollowUnhighlight() {
-        clearTimeout(UnhighlightTimer);
-        UnhighlightWatchers.forEach(watcher => watcher());
-        UnhighlightWatchers.length = 0;
-        highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc));
-        document.removeEventListener('pointerdown', linkFollowUnhighlight);
+    export function UnBrushAllDocs() {
+        Array.from(brushManager.BrushedDoc).forEach(action(doc => (doc[Brushed] = false)));
     }
 
     let UnhighlightWatchers: (() => void)[] = [];
@@ -1376,6 +1148,13 @@ export namespace Doc {
             UnhighlightWatchers.push(watcher);
         } else watcher();
     }
+    export function linkFollowUnhighlight() {
+        clearTimeout(UnhighlightTimer);
+        UnhighlightWatchers.forEach(watcher => watcher());
+        UnhighlightWatchers.length = 0;
+        highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc));
+        document.removeEventListener('pointerdown', linkFollowUnhighlight);
+    }
     export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presentation_effect?: Doc) {
         linkFollowUnhighlight();
         (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presentation_effect));
@@ -1415,9 +1194,6 @@ export namespace Doc {
             });
         });
     }
-    export function UnBrushAllDocs() {
-        Array.from(brushManager.BrushedDoc).forEach(action(doc => (doc[Brushed] = false)));
-    }
 
     export function getDocTemplate(doc?: Doc) {
         return !doc
@@ -1433,41 +1209,6 @@ export namespace Doc {
             : undefined;
     }
 
-    export function matchFieldValue(doc: Doc, key: string, value: any): boolean {
-        const hasFunctionFilter = Utils.HasFunctionFilter(value);
-        if (hasFunctionFilter) {
-            return hasFunctionFilter(StrCast(doc[key]));
-        }
-        if (key === LinkedTo) {
-            // links are not a field value, so handled here.  value is an expression of form ([field=]idToDoc("..."))
-            const allLinks = LinkManager.Instance.getAllRelatedLinks(doc);
-            const matchLink = (value: string, anchor: Doc) => {
-                const linkedToExp = value?.split('=');
-                if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value;
-                return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1];
-            };
-            // prettier-ignore
-            return  (value === Doc.FilterNone && !allLinks.length) || 
-                    (value === Doc.FilterAny && !!allLinks.length) ||
-                    (allLinks.some(link => matchLink(value,DocCast(link.link_anchor_1)) || 
-                                           matchLink(value,DocCast(link.link_anchor_2)) ));
-        }
-        if (typeof value === 'string') {
-            value = value.replace(`,${Utils.noRecursionHack}`, '');
-        }
-        const fieldVal = doc[key];
-        // prettier-ignore
-        if ((value === Doc.FilterAny && fieldVal !== undefined) || 
-            (value === Doc.FilterNone && fieldVal === undefined)) {
-            return true;
-        }
-        const vals = StrListCast(fieldVal); // list typing is very imperfect.  casting to a string list doesn't mean that the entries will actually be strings
-        if (vals.length) {
-            return vals.some(v => typeof v === 'string' && v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
-        }
-        return Field.toString(fieldVal as Field).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
-    }
-
     export function deiconifyView(doc: Doc) {
         StrCast(doc.layout_fieldKey).split('_')[1] === 'icon' && setNativeView(doc);
     }
@@ -1603,15 +1344,25 @@ export namespace Doc {
     }
 
     // prettier-ignore
-    export function toIcon(doc?: Doc, isOpen?: boolean) {
-        switch (isOpen !== undefined ? DocumentType.COL: StrCast(doc?.type)) {
+    export function toIcon(doc?: Doc, isOpen?: Opt<boolean>) {
+        if (isOpen) return doc?.isFolder ? 'chevron-down' : 'folder-open';
+        switch (StrCast(doc?.type)) {
             case DocumentType.IMG:  return 'image';
             case DocumentType.COMPARISON: return 'columns';
             case DocumentType.RTF: return 'sticky-note';
             case DocumentType.COL:
-                const folder: IconProp = isOpen === true ? 'folder-open' : isOpen === false ? 'folder' : doc?.title==='Untitled Collection'? 'object-group': 'chalkboard';
-                const chevron: IconProp = isOpen === true ? 'chevron-down' : isOpen === false ? 'chevron-right' : 'question';
-                return !doc?.isFolder ? folder : chevron;
+                if (doc?.isFolder) {
+                    switch (doc.type_collection) {
+                        default: return isOpen === false ? 'chevron-right' : 'question';
+                    } // prettier-ignore
+                }
+                switch (doc?.type_collection) {
+                    case CollectionViewType.Freeform :   return 'object-group';
+                    case CollectionViewType.NoteTaking : return 'chalkboard';
+                    case CollectionViewType.Schema :     return 'table-cells';
+                    case CollectionViewType.Docking:     return 'solar-panel';
+                    default:                             return 'folder';
+                } // prettier-ignore
             case DocumentType.WEB: return 'globe-asia';
             case DocumentType.SCREENSHOT: return 'photo-video';
             case DocumentType.WEBCAM: return 'video';
@@ -1628,9 +1379,9 @@ export namespace Doc {
             case DocumentType.DATAVIZ: return 'chart-bar';
             case DocumentType.EQUATION: return 'calculator';
             case DocumentType.SIMULATION: return 'rocket';
-            case DocumentType.CONFIG: return 'question-circle';
-            default: return 'question';
+            case DocumentType.CONFIG: return 'folder-closed';
         }
+        return 'question';
     }
 
     ///
@@ -1839,14 +1590,13 @@ ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) {
 ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) {
     return Doc.assignDocToField(doc, field, id);
 });
-ScriptingGlobals.add(function docCast(doc: FieldResult): any {
-    return DocCastAsync(doc);
+ScriptingGlobals.add(function docCastAsync(doc: FieldResult): any {
+    return Cast(doc, Doc);
 });
 ScriptingGlobals.add(function activePresentationItem() {
     const curPres = Doc.ActivePresentation;
     return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)];
 });
-
 ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: any, modifiers: 'match' | 'check' | 'x' | 'remove') {
     Doc.setDocFilter(container, key, value, modifiers);
 });
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index 5ecf25e08..dfd02dbc0 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -275,7 +275,7 @@ export namespace RichTextUtils {
             } else {
                 docId = backingDocId;
             }
-            return schema.node('image', { src, agnostic, width, docId, float: null, location: 'add:right' });
+            return schema.node('image', { src, agnostic, width, docId, float: null });
         };
 
         const textNode = (schema: any, run: docs_v1.Schema$TextRun) => {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 28db77c65..ca02284da 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -234,7 +234,7 @@ function getEffectiveAcl(target: any, user?: string): symbol {
  * @param layoutOnly just sets the layout doc's ACL (unless the data doc has no entry for the ACL, in which case it will be set as well)
  */
 export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited?: Doc[], allowUpgrade?: boolean, layoutOnly = false) {
-    const selfKey = `acl-${Doc.CurrentUserEmailNormalized}`;
+    const selfKey = `acl-${normalizeEmail(Doc.CurrentUserEmail)}`;
     if (!visited) visited = [] as Doc[];
     if (!target || visited.includes(target) || key === selfKey) return;
     visited.push(target);
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 498bec6ed..e24bcd733 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -658,7 +658,7 @@ export class MobileInterface extends React.Component {
     // DocButton for switching into ink mode
     @computed get drawInk() {
         return !this.mainContainer || this._activeDoc._type_collection !== CollectionViewType.Docking ? null : (
-            <div className="docButton" id="inkButton" title={Doc.isDocPinned(this._activeDoc) ? 'Pen on' : 'Pen off'} onClick={this.onSwitchInking}>
+            <div className="docButton" id="inkButton" onClick={this.onSwitchInking}>
                 <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="pen-nib" />
             </div>
         );
@@ -668,7 +668,7 @@ export class MobileInterface extends React.Component {
     @computed get uploadImageButton() {
         if (this._activeDoc.type === DocumentType.COL && this._activeDoc !== this._homeDoc && this._activeDoc._type_collection !== CollectionViewType.Docking && this._activeDoc.title !== 'WORKSPACES') {
             return (
-                <div className="docButton" id="imageButton" title={Doc.isDocPinned(this._activeDoc) ? 'Pen on' : 'Pen off'} onClick={this.toggleUpload}>
+                <div className="docButton" id="imageButton" onClick={this.toggleUpload}>
                     <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="upload" />
                 </div>
             );
@@ -692,13 +692,8 @@ export class MobileInterface extends React.Component {
     @computed get pinToPresentation() {
         // Only making button available if it is an image
         if (!(this._activeDoc.type === 'collection' || this._activeDoc.type === 'presentation')) {
-            const isPinned = this._activeDoc && Doc.isDocPinned(this._activeDoc);
             return (
-                <div
-                    className="docButton"
-                    title={Doc.isDocPinned(this._activeDoc) ? 'Unpin from presentation' : 'Pin to presentation'}
-                    style={{ backgroundColor: isPinned ? 'black' : 'white', color: isPinned ? 'white' : 'black' }}
-                    onClick={e => TabDocView.PinDoc(this._activeDoc, {})}>
+                <div className="docButton" title={'Pin to presentation'} style={{ backgroundColor: 'white', color: 'black' }} onClick={e => TabDocView.PinDoc(this._activeDoc, {})}>
                     <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
                 </div>
             );
-- 
cgit v1.2.3-70-g09d2


From cc75a03d89a4b553a53b55404464cd2ca93d9b48 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 22 Nov 2023 11:52:57 -0500
Subject: fixed more issues with rotation.  restrutured how MarqueeAnnotator
 works to be simpler and more correct.

---
 src/client/views/DocumentDecorations.tsx           |  12 +-
 src/client/views/MarqueeAnnotator.tsx              | 284 ++++++++++-----------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   4 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  14 +-
 src/client/views/nodes/ImageBox.tsx                |  15 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |  46 +---
 src/client/views/nodes/MapBox/MapBox2.tsx          |  46 +---
 src/client/views/nodes/VideoBox.scss               |   1 +
 src/client/views/nodes/VideoBox.tsx                |  23 +-
 src/client/views/nodes/WebBox.tsx                  | 167 ++++++------
 src/client/views/nodes/trails/PresBox.tsx          |   4 +-
 src/client/views/pdf/Annotation.tsx                |  30 ++-
 src/client/views/pdf/PDFViewer.tsx                 |  50 ++--
 13 files changed, 294 insertions(+), 402 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3782a8878..d4b474de9 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -69,7 +69,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 const center = {x: (this.Bounds.x+this.Bounds.r)/2, y: (this.Bounds.y+this.Bounds.b)/2};
                 const {x,y} = Utils.rotPt(e.clientX - center.x, 
                                           e.clientY - center.y, 
-                                          -NumCast(SelectionManager.Views().lastElement()?.screenToLocalTransform().RotateDeg));
+                                          NumCast(SelectionManager.Views().lastElement()?.screenToLocalTransform().Rotate));
                 (this._showNothing = !(this.Bounds.x !== Number.MAX_VALUE && //
                     (this.Bounds.x > center.x+x + this._resizeBorderWidth / 2 || 
                      this.Bounds.r < center.x+x - this._resizeBorderWidth / 2 || 
@@ -630,12 +630,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     @computed get rotCenter() {
         const lastView = SelectionManager.Views().lastElement();
         if (lastView) {
+            const invXf = lastView.props.ScreenToLocalTransform().inverse();
             const seldoc = lastView.layoutDoc;
-            const loccenter = Utils.rotPt(NumCast(seldoc.rotation_centerX) * NumCast(seldoc._width), NumCast(seldoc.rotation_centerY) * NumCast(seldoc._height), lastView.props.ScreenToLocalTransform().Rotate);
-            return lastView.props
-                .ScreenToLocalTransform()
-                .inverse()
-                .transformPoint(loccenter.x + NumCast(seldoc._width) / 2, loccenter.y + NumCast(seldoc._height) / 2);
+            const loccenter = Utils.rotPt(NumCast(seldoc.rotation_centerX) * NumCast(seldoc._width), NumCast(seldoc.rotation_centerY) * NumCast(seldoc._height), invXf.Rotate);
+            return invXf.transformPoint(loccenter.x + NumCast(seldoc._width) / 2, loccenter.y + NumCast(seldoc._height) / 2);
         }
         return this._rotCenter;
     }
@@ -691,7 +689,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         const bounds = this.ClippedBounds;
         const useLock = bounds.r - bounds.x > 135 && seldocview.CollectionFreeFormDocumentView;
         const useRotation = !hideResizers && seldocview.rootDoc.type !== DocumentType.EQUATION && seldocview.CollectionFreeFormDocumentView; // when do we want an object to not rotate?
-        const rotation = SelectionManager.Views().length == 1 ? seldocview.screenToLocalTransform().RotateDeg : 0;
+        const rotation = SelectionManager.Views().length == 1 ? seldocview.screenToLocalTransform().inverse().RotateDeg : 0;
 
         // Radius constants
         const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView;
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 10d2d8568..20c7a08fa 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -1,4 +1,4 @@
-import { action, observable, ObservableMap, runInAction } from 'mobx';
+import { action, computed, observable, ObservableMap, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Opt } from '../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocData } from '../../fields/DocSymbols';
@@ -21,13 +21,13 @@ const _global = (window /* browser */ || global) /* node */ as any;
 export interface MarqueeAnnotatorProps {
     rootDoc: Doc;
     down?: number[];
-    iframe?: () => undefined | HTMLIFrameElement;
     scrollTop: number;
     scaling?: () => number;
-    iframeScaling?: () => number;
+    annotationLayerScaling?: () => number;
+    annotationLayerScrollTop: number;
     containerOffset?: () => number[];
     mainCont: HTMLDivElement;
-    docView: DocumentView;
+    docView: () => DocumentView;
     savedAnnotations: () => ObservableMap<number, HTMLDivElement[]>;
     selectionText: () => string;
     annotationLayer: HTMLDivElement;
@@ -40,27 +40,11 @@ export interface MarqueeAnnotatorProps {
 }
 @observer
 export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
-    private _startX: number = 0;
-    private _startY: number = 0;
-    @observable private _left: number = 0;
-    @observable private _top: number = 0;
+    private _start: { x: number; y: number } = { x: 0, y: 0 };
     @observable private _width: number = 0;
     @observable private _height: number = 0;
-
-    constructor(props: any) {
-        super(props);
-
-        AnchorMenu.Instance.OnCrop = (e: PointerEvent) => {
-            if (this.props.anchorMenuCrop) {
-                UndoManager.RunInBatch(() => this.props.anchorMenuCrop?.(this.highlight('', true, undefined, false), true), 'cropping');
-            }
-        };
-        AnchorMenu.Instance.OnClick = undoable((e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true)), 'make sidebar annotation');
-        AnchorMenu.Instance.OnAudio = unimplementedFunction;
-        AnchorMenu.Instance.Highlight = this.highlight;
-        AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true);
-        AnchorMenu.Instance.onMakeAnchor = () => AnchorMenu.Instance.GetAnchor(undefined, true);
-    }
+    @computed get top() { return Math.min(this._start.y, this._start.y + this._height); } // prettier-ignore
+    @computed get left() { return Math.min(this._start.x, this._start.x + this._width);} // prettier-ignore
 
     @action
     static clearAnnotations(savedAnnotations: ObservableMap<number, HTMLDivElement[]>) {
@@ -71,81 +55,15 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
         savedAnnotations.clear();
     }
 
-    @action gotDownPoint() {
-        if (!this._width && !this._height) {
-            const downPt = this.props.down!;
-            // set marquee x and y positions to the spatially transformed position
-            const boundingRect = this.props.mainCont.getBoundingClientRect();
-            this._startX = this._left = (downPt[0] - boundingRect.left) * (this.props.mainCont.offsetWidth / boundingRect.width);
-            this._startY = this._top = (downPt[1] - boundingRect.top) * (this.props.mainCont.offsetHeight / boundingRect.height) + this.props.mainCont.scrollTop;
-        }
-
-        const doc = this.props.iframe?.()?.contentDocument ?? document;
-        doc.removeEventListener('pointermove', this.onSelectMove);
-        doc.removeEventListener('pointerup', this.onSelectEnd);
-        doc.addEventListener('pointermove', this.onSelectMove);
-        doc.addEventListener('pointerup', this.onSelectEnd);
-
-        /**
-         * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.
-         * It also initiates a Drag/Drop interaction to place the text annotation.
-         */
-        AnchorMenu.Instance.StartDrag = action((e: PointerEvent, ele: HTMLElement) => {
-            e.preventDefault();
-            e.stopPropagation();
-            const sourceAnchorCreator = () => this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true); // hyperlink color
-
-            const targetCreator = (annotationOn: Doc | undefined) => {
-                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
-                FormattedTextBox.SelectOnLoad = target[Id];
-                return target;
-            };
-            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
-                dragComplete: e => {
-                    if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
-                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
-                        e.annoDragData.linkSourceDoc.followLinkZoom = false;
-                    }
-                },
-            });
-        });
-        /**
-         * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.
-         * It also initiates a Drag/Drop interaction to place the text annotation.
-         */
-        AnchorMenu.Instance.StartCropDrag = !this.props.anchorMenuCrop
-            ? unimplementedFunction
-            : action((e: PointerEvent, ele: HTMLElement) => {
-                  e.preventDefault();
-                  e.stopPropagation();
-                  var cropRegion: Doc | undefined;
-                  const sourceAnchorCreator = () => (cropRegion = this.highlight('', true, undefined, true)); // hyperlink color
-                  const targetCreator = (annotationOn: Doc | undefined) => this.props.anchorMenuCrop!(cropRegion, false)!;
-                  DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
-                      dragComplete: e => {
-                          if (!e.aborted && e.linkDocument) {
-                              Doc.GetProto(e.linkDocument).link_relationship = 'cropped image';
-                              Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.docView.rootDoc.title;
-                              Doc.GetProto(e.linkDocument).link_displayLine = false;
-                          }
-                      },
-                  });
-              });
-    }
-    releaseDownPt() {
-        const doc = this.props.iframe?.()?.contentDocument ?? document;
-        doc.removeEventListener('pointermove', this.onSelectMove);
-        doc.removeEventListener('pointerup', this.onSelectEnd);
-    }
-
     @undoBatch
     @action
     makeAnnotationDocument = (color: string, isLinkButton?: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>): Opt<Doc> => {
         const savedAnnoMap = savedAnnotations?.values() && Array.from(savedAnnotations?.values()).length ? savedAnnotations : this.props.savedAnnotations();
         if (savedAnnoMap.size === 0) return undefined;
         const savedAnnos = Array.from(savedAnnoMap.values())[0];
+        const doc = this.props.docView().rootDoc;
+        const scale = (this.props.annotationLayerScaling?.() || 1) * NumCast(doc._freeform_scale, 1);
         if (savedAnnos.length && (savedAnnos[0] as any).marqueeing) {
-            const scale = this.props.scaling?.() || 1;
             const anno = savedAnnos[0];
             const containerOffset = this.props.containerOffset?.() || [0, 0];
             const marqueeAnno = Docs.Create.FreeformDocument([], {
@@ -154,10 +72,10 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
                 annotationOn: this.props.rootDoc,
                 title: 'Annotation on ' + this.props.rootDoc.title,
             });
-            marqueeAnno.x = NumCast(this.props.docView.props.Document.freeform_panX_min) + (parseInt(anno.style.left || '0') - containerOffset[0]) / scale / NumCast(this.props.docView.props.Document._freeform_scale, 1);
-            marqueeAnno.y = NumCast(this.props.docView.props.Document.freeform_panY_min) + (parseInt(anno.style.top || '0') - containerOffset[1]) / scale / NumCast(this.props.docView.props.Document._freeform_scale, 1) + NumCast(this.props.scrollTop);
-            marqueeAnno._height = parseInt(anno.style.height || '0') / scale / NumCast(this.props.docView.props.Document._freeform_scale, 1);
-            marqueeAnno._width = parseInt(anno.style.width || '0') / scale / NumCast(this.props.docView.props.Document._freeform_scale, 1);
+            marqueeAnno.x = NumCast(doc.freeform_panX_min) + (parseInt(anno.style.left || '0') - containerOffset[0]) / scale;
+            marqueeAnno.y = NumCast(doc.freeform_panY_min) + (parseInt(anno.style.top || '0') - containerOffset[1]) / scale;
+            marqueeAnno._height = parseInt(anno.style.height || '0') / scale;
+            marqueeAnno._width = parseInt(anno.style.width || '0') / scale;
             anno.remove();
             savedAnnoMap.clear();
             return marqueeAnno;
@@ -215,83 +133,143 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
     };
 
     public static previewNewAnnotation = action((savedAnnotations: ObservableMap<number, HTMLDivElement[]>, annotationLayer: HTMLDivElement, div: HTMLDivElement, page: number) => {
-        if (div.style.top) {
-            div.style.top = parseInt(div.style.top) /*+ this.getScrollFromPage(page)*/
-                .toString();
-        }
-        annotationLayer.append(div);
         div.style.backgroundColor = '#ACCEF7';
         div.style.opacity = '0.5';
+        annotationLayer.append(div);
         const savedPage = savedAnnotations.get(page);
-        if (savedPage) {
-            savedPage.push(div);
-            savedAnnotations.set(page, savedPage);
-        } else {
-            savedAnnotations.set(page, [div]);
-        }
+        if (savedPage) savedPage.push(div);
+        savedAnnotations.set(page, savedPage ?? [div]);
     });
 
+    getTransformedScreenPt = (down: number[]) => {
+        const boundingRect = this.props.mainCont.getBoundingClientRect();
+        const center = { x: boundingRect.x + boundingRect.width / 2, y: boundingRect.y + boundingRect.height / 2 };
+        const downPt = Utils.rotPt(down[0] - center.x, down[1] - center.y, NumCast(this.props.docView().screenToLocalTransform().Rotate));
+        const scale = this.props.docView().props.ScreenToLocalTransform().Scale;
+        const scalex = this.props.mainCont.offsetWidth / NumCast(this.props.rootDoc.width);
+        const scaley = this.props.mainCont.offsetHeight / NumCast(this.props.rootDoc.height);
+        // set marquee x and y positions to the spatially transformed position
+        return { x: scalex * (downPt.x + NumCast(this.props.rootDoc.width) / scale / 2) * scale, 
+                 y: scaley * (downPt.y + NumCast(this.props.rootDoc.height) / scale / 2) * scale + this.props.annotationLayerScrollTop }; // prettier-ignore
+    };
+
+    @action
+    public onInitiateSelection(down: number[]) {
+        this._width = this._height = 0;
+        this._start = this.getTransformedScreenPt(down);
+
+        document.removeEventListener('pointermove', this.onSelectMove);
+        document.removeEventListener('pointerup', this.onSelectEnd);
+        document.addEventListener('pointermove', this.onSelectMove);
+        document.addEventListener('pointerup', this.onSelectEnd);
+
+        AnchorMenu.Instance.OnCrop = (e: PointerEvent) => {
+            if (this.props.anchorMenuCrop) {
+                UndoManager.RunInBatch(() => this.props.anchorMenuCrop?.(this.highlight('', true, undefined, false), true), 'cropping');
+            }
+        };
+        AnchorMenu.Instance.OnClick = undoable((e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true)), 'make sidebar annotation');
+        AnchorMenu.Instance.OnAudio = unimplementedFunction;
+        AnchorMenu.Instance.Highlight = this.highlight;
+        AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true);
+        AnchorMenu.Instance.onMakeAnchor = () => AnchorMenu.Instance.GetAnchor(undefined, true);
+
+        /**
+         * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.
+         * It also initiates a Drag/Drop interaction to place the text annotation.
+         */
+        AnchorMenu.Instance.StartDrag = action((e: PointerEvent, ele: HTMLElement) => {
+            e.preventDefault();
+            e.stopPropagation();
+            const sourceAnchorCreator = () => this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true); // hyperlink color
+
+            const targetCreator = (annotationOn: Doc | undefined) => {
+                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
+                FormattedTextBox.SelectOnLoad = target[Id];
+                return target;
+            };
+            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
+                dragComplete: e => {
+                    if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
+                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
+                        e.annoDragData.linkSourceDoc.followLinkZoom = false;
+                    }
+                },
+            });
+        });
+        /**
+         * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.
+         * It also initiates a Drag/Drop interaction to place the text annotation.
+         */
+        AnchorMenu.Instance.StartCropDrag = !this.props.anchorMenuCrop
+            ? unimplementedFunction
+            : action((e: PointerEvent, ele: HTMLElement) => {
+                  e.preventDefault();
+                  e.stopPropagation();
+                  var cropRegion: Doc | undefined;
+                  const sourceAnchorCreator = () => (cropRegion = this.highlight('', true, undefined, true)); // hyperlink color
+                  const targetCreator = (annotationOn: Doc | undefined) => this.props.anchorMenuCrop!(cropRegion, false)!;
+                  DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
+                      dragComplete: e => {
+                          if (!e.aborted && e.linkDocument) {
+                              Doc.GetProto(e.linkDocument).link_relationship = 'cropped image';
+                              Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.docView().rootDoc.title;
+                              Doc.GetProto(e.linkDocument).link_displayLine = false;
+                          }
+                      },
+                  });
+              });
+    }
+    public onTerminateSelection() {
+        document.removeEventListener('pointermove', this.onSelectMove);
+        document.removeEventListener('pointerup', this.onSelectEnd);
+    }
+
     @action
     onSelectMove = (e: PointerEvent) => {
-        // transform positions and find the width and height to set the marquee to
-        const boundingRect = (this.props.iframe?.()?.contentDocument?.body || this.props.mainCont).getBoundingClientRect();
-        const mainRect = this.props.mainCont.getBoundingClientRect();
-        const cliX = e.clientX * (this.props.iframeScaling?.() || 1) - boundingRect.left;
-        const cliY = e.clientY * (this.props.iframeScaling?.() || 1) - boundingRect.top;
-        this._width = cliX * (this.props.mainCont.offsetWidth / mainRect.width) - this._startX;
-        this._height = cliY * (this.props.mainCont.offsetHeight / mainRect.height) - this._startY + this.props.mainCont.scrollTop;
-        this._left = Math.min(this._startX, this._startX + this._width);
-        this._top = Math.min(this._startY, this._startY + this._height);
-        this._width = Math.abs(this._width);
-        this._height = Math.abs(this._height);
+        const movLoc = this.getTransformedScreenPt([e.clientX, e.clientY]);
+        this._width = movLoc.x - this._start.x;
+        this._height = movLoc.y - this._start.y;
         //e.stopPropagation(); // overlay documents are all 'active', yet they can be dragged. if we stop propagation, then they can be marqueed but not dragged.  if we don't stop, then they will be marqueed and dragged, but the marquee will be zero width since the doc will move along with the cursor.
     };
 
+    @action
     onSelectEnd = (e: PointerEvent) => {
-        const mainRect = this.props.mainCont.getBoundingClientRect();
-        const cliX = e.clientX * (this.props.iframeScaling?.() || 1) + (this.props.iframe ? mainRect.left : 0);
-        const cliY = e.clientY * (this.props.iframeScaling?.() || 1) + (this.props.iframe ? mainRect.top : 0);
-        if (this._width > 10 || this._height > 10) {
+        e.stopPropagation();
+        const marquees = this.props.mainCont.getElementsByClassName('marqueeAnnotator-dragBox');
+        const marqueeStyle = (Array.from(marquees).lastElement() as HTMLDivElement)?.style;
+        if (!this.isEmpty && marqueeStyle) {
             // configure and show the annotation/link menu if a the drag region is big enough
-            const marquees = this.props.mainCont.getElementsByClassName('marqueeAnnotator-dragBox');
-            if (marquees?.length) {
-                // copy the temporary marquee to allow for multiple selections (not currently available though).
-                const copy = document.createElement('div');
-                ['border', 'opacity'].forEach(prop => (copy.style[prop as any] = (marquees[0] as HTMLDivElement).style[prop as any]));
-                const bounds = (marquees[0] as HTMLDivElement).getBoundingClientRect();
-                const uitls = Utils.GetScreenTransform(marquees[0] as HTMLDivElement);
-                const rbounds = { top: uitls.translateY, left: uitls.translateX, width: bounds.right - bounds.left, height: bounds.bottom - bounds.top };
-                const otls = Utils.GetScreenTransform(this.props.annotationLayer);
-                const fbounds = { top: (rbounds.top - otls.translateY) / otls.scale, left: (rbounds.left - otls.translateX) / otls.scale, width: rbounds.width / otls.scale, height: rbounds.height / otls.scale };
-                copy.style.top = fbounds.top.toString() + 'px';
-                copy.style.left = fbounds.left.toString() + 'px';
-                copy.style.width = fbounds.width.toString() + 'px';
-                copy.style.height = fbounds.height.toString() + 'px';
-                copy.className = 'marqueeAnnotator-annotationBox';
-                (copy as any).marqueeing = true;
-                MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations(), this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this._top) || 0);
-            }
-
-            AnchorMenu.Instance.jumpTo(cliX, cliY);
-
-            this.props.finishMarquee(undefined, undefined, e);
-            runInAction(() => (this._width = this._height = 0));
-        } else {
-            runInAction(() => (this._width = this._height = 0));
-            this.props.finishMarquee(cliX, cliY, e);
+            // copy the temporary marquee to allow for multiple selections (not currently available though).
+            const copy = document.createElement('div');
+            const scale = (this.props.scaling?.() || 1) * NumCast(this.props.docView().rootDoc._freeform_scale, 1);
+            ['border', 'opacity', 'top', 'left', 'width', 'height'].forEach(prop => (copy.style[prop as any] = marqueeStyle[prop as any]));
+            copy.className = 'marqueeAnnotator-annotationBox';
+            copy.style.top = parseInt(marqueeStyle.top.toString().replace('px', '')) / scale + this.props.scrollTop + 'px';
+            copy.style.left = parseInt(marqueeStyle.left.toString().replace('px', '')) / scale + 'px';
+            copy.style.width = parseInt(marqueeStyle.width.toString().replace('px', '')) / scale + 'px';
+            copy.style.height = parseInt(marqueeStyle.height.toString().replace('px', '')) / scale + 'px';
+            (copy as any).marqueeing = true;
+            MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations(), this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this.top) || 0);
+            AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
         }
+        this.props.finishMarquee(this.isEmpty ? e.clientX : undefined, this.isEmpty ? e.clientY : undefined, e);
+        this._width = this._height = 0;
     };
 
+    get isEmpty() {
+        return Math.abs(this._width) <= 10 && Math.abs(this._height) <= 10;
+    }
+
     render() {
-        return !this.props.down ? null : (
+        return (
             <div
-                ref={r => (r ? this.gotDownPoint() : this.releaseDownPt())}
                 className="marqueeAnnotator-dragBox"
                 style={{
-                    left: `${this._left}px`,
-                    top: `${this._top}px`,
-                    width: `${this._width}px`,
-                    height: `${this._height}px`,
+                    left: `${this.left}px`,
+                    top: `${this.top}px`,
+                    width: `${Math.abs(this._width)}px`,
+                    height: `${Math.abs(this._height)}px`,
                     border: `${this._width === 0 ? '' : '2px dashed black'}`,
                 }}
             />
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1b3bbdd18..e350c35cc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -341,7 +341,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
             const d = docDragData.droppedDocuments[i];
             const layoutDoc = Doc.Layout(d);
-            const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], -this.props.ScreenToLocalTransform().Rotate);
+            const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], this.props.ScreenToLocalTransform().Rotate);
             if (this.Document._currentFrame !== undefined) {
                 CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false);
                 const pvals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); // get filled in values (uses defaults when not value is specified) for position
@@ -732,7 +732,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         PresBox.Instance?.pauseAutoPres();
         this.props.DocumentView?.().clearViewTransition();
         const [dxi, dyi] = this.screenToLocalXf.transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
-        const { x: dx, y: dy } = Utils.rotPt(dxi, dyi, -this.props.ScreenToLocalTransform().Rotate);
+        const { x: dx, y: dy } = Utils.rotPt(dxi, dyi, this.props.ScreenToLocalTransform().Rotate);
         this.setPan(NumCast(this.Document[this.panXFieldKey]) - (ctrlKey ? 0 : dx), NumCast(this.Document[this.panYFieldKey]) - (shiftKey ? 0 : dy), 0, true);
         this._lastX = e.clientX;
         this._lastY = e.clientY;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 465bbcf2f..421d431b3 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -131,9 +131,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     public static animStringFields = ['backgroundColor', 'color', 'fillColor']; // fields that are configured to be animatable using animation frames
     public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames
 
-    @observable _animPos: number[] | undefined = undefined;
-    @observable _contentView: DocumentView | undefined | null;
-
     get CollectionFreeFormView() {
         return this.props.CollectionFreeFormView;
     }
@@ -231,7 +228,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         this.props
             .ScreenToLocalTransform()
             .translate(-this.props.w_X(), -this.props.w_Y())
-            .rotateDeg(this.props.w_Rotation?.() || 0);
+            .rotateDeg(-(this.props.w_Rotation?.() || 0));
     returnThis = () => this;
 
     /// this indicates whether the doc view is activated because of its relationshop to a group
@@ -262,14 +259,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 {this.props.RenderCutoffProvider(this.props.Document) ? (
                     <div style={{ position: 'absolute', width: this.props.PanelWidth(), height: this.props.PanelHeight(), background: 'lightGreen' }} />
                 ) : (
-                    <DocumentView
-                        ref={action((r: DocumentView | null) => (this._contentView = r))}
-                        {...passOnProps}
-                        CollectionFreeFormDocumentView={this.returnThis}
-                        styleProvider={this.styleProvider}
-                        ScreenToLocalTransform={this.screenToLocalTransform}
-                        isGroupActive={this.isGroupActive}
-                    />
+                    <DocumentView {...passOnProps} CollectionFreeFormDocumentView={this.returnThis} styleProvider={this.styleProvider} ScreenToLocalTransform={this.screenToLocalTransform} isGroupActive={this.isGroupActive} />
                 )}
             </div>
         );
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index bca062190..103843046 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -70,6 +70,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _getAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
     private _overlayIconRef = React.createRef<HTMLDivElement>();
+    private _marqueeref = React.createRef<MarqueeAnnotator>();
     @observable _curSuffix = '';
     @observable _uploadIcon = uploadIcons.idle;
 
@@ -473,7 +474,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
-    @observable _marqueeing: number[] | undefined;
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @computed get annotationLayer() {
         TraceMobx();
@@ -487,7 +487,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 e,
                 action(e => {
                     MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-                    this._marqueeing = [e.clientX, e.clientY];
+                    this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
                     return true;
                 }),
                 returnFalse,
@@ -499,7 +499,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     @action
     finishMarquee = () => {
         this._getAnchor = AnchorMenu.Instance?.GetAnchor;
-        this._marqueeing = undefined;
+        this._marqueeref.current?.onTerminateSelection();
         this.props.select(false);
     };
     focus = (anchor: Doc, options: DocFocusOptions) => (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options));
@@ -557,13 +557,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     {this.content}
                 </CollectionFreeFormView>
                 {this.annotationLayer}
-                {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
+                {!this._mainCont.current || !this._annotationLayer.current ? null : (
                     <MarqueeAnnotator
                         rootDoc={this.rootDoc}
                         scrollTop={0}
-                        down={this._marqueeing}
-                        scaling={this.props.NativeDimScaling}
-                        docView={this.props.docViewPath().slice(-1)[0]}
+                        annotationLayerScrollTop={0}
+                        scaling={returnOne}
+                        annotationLayerScaling={this.props.NativeDimScaling}
+                        docView={this.props.DocumentView!}
                         addDocument={this.addDocument}
                         finishMarquee={this.finishMarquee}
                         savedAnnotations={this.savedAnnotations}
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 37935c513..9b75ca7e3 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -7,9 +7,8 @@ import * as React from 'react';
 import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
 import { DocCss, Highlight } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
-import { InkTool } from '../../../../fields/InkField';
 import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
@@ -21,7 +20,6 @@ import { undoable, UndoManager } from '../../../util/UndoManager';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
-import { MarqueeAnnotator } from '../../MarqueeAnnotator';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
@@ -67,14 +65,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return FieldView.LayoutString(MapBox, fieldKey);
     }
     private _dragRef = React.createRef<HTMLDivElement>();
-    private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
-    private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _sidebarRef = React.createRef<SidebarAnnos>();
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
     private _disposers: { [key: string]: IReactionDisposer } = {};
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
 
-    @observable private _marqueeing: number[] | undefined;
     @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @computed get allSidebarDocs() {
         return DocListCast(this.dataDoc[this.SidebarKey]);
@@ -278,28 +273,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
 
-    @action
-    onMarqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
-            setupMoveUpEvents(
-                this,
-                e,
-                action(e => {
-                    MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-                    this._marqueeing = [e.clientX, e.clientY];
-                    return true;
-                }),
-                returnFalse,
-                () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations),
-                false
-            );
-        }
-    };
-    @action finishMarquee = (x?: number, y?: number) => {
-        this._marqueeing = undefined;
-        x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false, this.rootDoc);
-    };
-
     addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey);
 
     pointerEvents = () => (this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
@@ -832,23 +805,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                     isAnyChildContentActive={this.isAnyChildContentActive}
                                     whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                                 /> */}
-
-                    {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
-                        <MarqueeAnnotator
-                            rootDoc={this.rootDoc}
-                            anchorMenuClick={this.anchorMenuClick}
-                            scrollTop={0}
-                            down={this._marqueeing}
-                            scaling={returnOne}
-                            addDocument={this.addDocumentWrapper}
-                            docView={this.props.docViewPath().lastElement()}
-                            finishMarquee={this.finishMarquee}
-                            savedAnnotations={this.savedAnnotations}
-                            annotationLayer={this._annotationLayer.current}
-                            selectionText={returnEmptyString}
-                            mainCont={this._mainCont.current}
-                        />
-                    )}
                 </div>
                 {/* </LoadScript > */}
                 <div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index c42664abe..6bad7d724 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -5,9 +5,8 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
-import { InkTool } from '../../../../fields/InkField';
 import { NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { DragManager } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
@@ -15,7 +14,6 @@ import { UndoManager } from '../../../util/UndoManager';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
-import { MarqueeAnnotator } from '../../MarqueeAnnotator';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { Annotation } from '../../pdf/Annotation';
 import { SidebarAnnos } from '../../SidebarAnnos';
@@ -106,8 +104,6 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @observable private selectedPlace: Doc | undefined;
     @observable private markerMap: { [id: string]: google.maps.Marker } = {};
     @observable private center = navigator.geolocation ? navigator.geolocation.getCurrentPosition : defaultCenter;
-    @observable private _marqueeing: number[] | undefined;
-    @observable private _isAnnotating = false;
     @observable private inputRef = React.createRef<HTMLInputElement>();
     @observable private searchMarkers: google.maps.Marker[] = [];
     @observable private searchBox = new window.google.maps.places.Autocomplete(this.inputRef.current!, options);
@@ -119,7 +115,6 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return DocListCast(this.dataDoc[this.annotationKey]);
     }
     @observable private toggleAddMarker = false;
-    private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
 
     @observable _showSidebar = false;
     @computed get SidebarShown() {
@@ -481,29 +476,6 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func);
 
-    @action
-    onMarqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
-            setupMoveUpEvents(
-                this,
-                e,
-                action(e => {
-                    MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-                    this._marqueeing = [e.clientX, e.clientY];
-                    return true;
-                }),
-                returnFalse,
-                () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations),
-                false
-            );
-        }
-    };
-    @action finishMarquee = (x?: number, y?: number) => {
-        this._marqueeing = undefined;
-        this._isAnnotating = false;
-        x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false, this.props.Document);
-    };
-
     addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => {
         return this.addDocument(doc, annotationKey);
     };
@@ -598,22 +570,6 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             {/* {this.handleMapCenter(this._map)} */}
                         </GoogleMap>
                     </div>
-                    {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
-                        <MarqueeAnnotator
-                            rootDoc={this.rootDoc}
-                            anchorMenuClick={this.anchorMenuClick}
-                            scrollTop={0}
-                            down={this._marqueeing}
-                            scaling={returnOne}
-                            addDocument={this.addDocumentWrapper}
-                            docView={this.props.docViewPath().lastElement()}
-                            finishMarquee={this.finishMarquee}
-                            savedAnnotations={this.savedAnnotations}
-                            annotationLayer={this._annotationLayer.current}
-                            selectionText={returnEmptyString}
-                            mainCont={this._mainCont.current}
-                        />
-                    )}
                 </div>
                 {/* </LoadScript > */}
                 <div className="MapBox2-sidebar" style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index f803715ad..f90c19050 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -100,6 +100,7 @@
     padding: 0 10px 0 7px;
     transition: opacity 0.3s;
     z-index: 10001;
+    transform-origin: top left;
 
     .timecode-controls {
         display: flex;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 6ebf84738..8bf2f4ce5 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -64,6 +64,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     private _youtubeIframeId: number = -1;
     private _youtubeContentCreated = false;
     private _audioPlayer: HTMLAudioElement | null = null;
+    private _marqueeref = React.createRef<MarqueeAnnotator>();
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); // outermost div
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _playRegionTimer: any = null; // timeout for playback
@@ -71,7 +72,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     @observable _stackedTimeline: any; // CollectionStackedTimeline ref
     @observable static _nativeControls: boolean; // default html controls
-    @observable _marqueeing: number[] | undefined; // coords for marquee selection
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @observable _screenCapture = false;
     @observable _clicking = false; // used for transition between showing/hiding timeline
@@ -867,7 +867,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 e,
                 action(e => {
                     MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-                    this._marqueeing = [e.clientX, e.clientY];
+                    this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
                     return true;
                 }),
                 returnFalse,
@@ -881,7 +881,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     // ends marquee selection
     @action
     finishMarquee = () => {
-        this._marqueeing = undefined;
+        this._marqueeref.current?.onTerminateSelection();
         this.props.select(true);
     };
 
@@ -912,7 +912,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             .scale(100 / this.heightPercent);
     };
 
-    marqueeFitScaling = () => ((this.props.NativeDimScaling?.() || 1) * this.heightPercent) / 100;
     marqueeOffset = () => [((this.panelWidth() / 2) * (1 - this.heightPercent / 100)) / (this.heightPercent / 100), 0];
 
     timelineDocFilter = () => [`_isTimelineLabel:true,${Utils.noRecursionHack}:x`];
@@ -937,8 +936,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 <div
                     className="videoBox-ui"
                     style={{
-                        transformOrigin: 'top left',
-                        transform: `rotate(${this.props.ScreenToLocalTransform().RotateDeg}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
+                        transform: `rotate(${this.props.ScreenToLocalTransform().inverse().RotateDeg}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
                         left: xPos,
                         top: yMid,
                         height: uiHeight,
@@ -952,6 +950,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         );
     };
 
+    thumbnails = () => StrListCast(this.dataDoc[this.fieldKey + '_thumbnails']);
     // renders CollectionStackedTimeline
     @computed get renderTimeline() {
         return (
@@ -963,7 +962,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     fieldKey={this.annotationKey}
                     dictationKey={this.fieldKey + '_dictation'}
                     mediaPath={this.audiopath}
-                    thumbnails={() => StrListCast(this.dataDoc[this.fieldKey + '_thumbnails'])}
+                    thumbnails={this.thumbnails}
                     renderDepth={this.props.renderDepth + 1}
                     startTag={'_timecodeToShow' /* videoStart */}
                     endTag={'_timecodeToHide' /* videoEnd */}
@@ -1095,13 +1094,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         </CollectionFreeFormView>
                     </div>
                     {this.annotationLayer}
-                    {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
+                    {!this._mainCont.current || !this._annotationLayer.current ? null : (
                         <MarqueeAnnotator
+                            ref={this._marqueeref}
                             rootDoc={this.rootDoc}
                             scrollTop={0}
-                            down={this._marqueeing}
-                            scaling={this.marqueeFitScaling}
-                            docView={this.props.docViewPath().slice(-1)[0]}
+                            annotationLayerScrollTop={0}
+                            scaling={returnOne}
+                            annotationLayerScaling={this.props.NativeDimScaling}
+                            docView={this.props.DocumentView!}
                             containerOffset={this.marqueeOffset}
                             addDocument={this.addDocWithTimecode}
                             finishMarquee={this.finishMarquee}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 524a3cc4a..e42fb4a03 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import * as WebRequest from 'web-request';
 import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
@@ -51,6 +51,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _outerRef: React.RefObject<HTMLDivElement> = React.createRef();
+    private _marqueeref = React.createRef<MarqueeAnnotator>();
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _keyInput = React.createRef<HTMLInputElement>();
@@ -66,10 +67,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @observable private _searching: boolean = false;
     @observable private _showSidebar = false;
     @observable private _webPageHasBeenRendered = false;
-    @observable private _overlayAnnoInfo: Opt<Doc>;
     @observable private _marqueeing: number[] | undefined;
-    @observable private _isAnnotating = false;
-    @observable private _iframeClick: HTMLIFrameElement | undefined = undefined;
+    get marqueeing() {
+        return this._marqueeing;
+    }
+    set marqueeing(val) {
+        val && this._marqueeref.current?.onInitiateSelection(val);
+        !val && this._marqueeref.current?.onTerminateSelection();
+        this._marqueeing = val;
+    }
     @observable private _iframe: HTMLIFrameElement | null = null;
     @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @observable private _scrollHeight = NumCast(this.layoutDoc.scrollHeight);
@@ -245,6 +251,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @action
     createTextAnnotation = (sel: Selection, selRange: Range | undefined) => {
         if (this._mainCont.current && selRange) {
+            if (this.rootDoc[this.props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this.props.DocumentView!().screenToLocalTransform().RotateDeg)}deg)`;
             const clientRects = selRange.getClientRects();
             for (let i = 0; i < clientRects.length; i++) {
                 const rect = clientRects.item(i);
@@ -261,12 +268,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, 1);
                 }
             }
+            this._mainCont.current.style.transform = '';
         }
-        //this._selectionText = selRange.cloneContents().textContent || "";
         this._selectionContent = selRange?.cloneContents();
         this._selectionText = this._selectionContent?.textContent || '';
 
         // clear selection
+        this._textAnnotationCreator = undefined;
         if (sel.empty) sel.empty(); // Chrome
         else if (sel.removeAllRanges) sel.removeAllRanges(); // Firefox
         return this._savedAnnotations;
@@ -354,26 +362,27 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
     @action
     webClipDown = (e: React.PointerEvent) => {
-        const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
+        e.stopPropagation();
+        const sel = window.getSelection();
+        this._textAnnotationCreator = undefined;
+        if (sel?.empty) sel.empty(); // Chrome
+        else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
+        // bcz: NEED TO unrotate e.clientX and e.clientY
         const word = getWordAtPoint(e.target, e.clientX, e.clientY);
         this._setPreviewCursor?.(e.clientX, e.clientY, false, true, this.rootDoc);
         MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-        e.button !== 2 && (this._marqueeing = [e.clientX, e.clientY]);
-        if (word || (e.target as any)?.className?.includes('rangeslider') || (e.target as any)?.onclick || (e.target as any)?.parentNode?.onclick) {
-            e.stopPropagation();
-            setTimeout(
-                action(() => (this._marqueeing = undefined)),
-                100
-            ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
-        } else {
-            this._isAnnotating = true;
-            this.props.select(false);
-            e.stopPropagation();
+
+        if (!word && !(e.target as any)?.className?.includes('rangeslider') && !(e.target as any)?.onclick && !(e.target as any)?.parentNode?.onclick) {
+            if (e.button !== 2) this.marqueeing = [e.clientX, e.clientY];
             e.preventDefault();
         }
         document.addEventListener('pointerup', this.webClipUp);
     };
+    @action
     webClipUp = (e: PointerEvent) => {
+        if (window.getSelection()?.isCollapsed && this._marqueeref.current?.isEmpty) {
+            this.marqueeing = undefined;
+        }
         document.removeEventListener('pointerup', this.webClipUp);
         this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value
         const sel = window.getSelection();
@@ -382,7 +391,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             this._selectionText = sel.toString();
             AnchorMenu.Instance.setSelectedText(sel.toString());
             this._textAnnotationCreator = () => this.createTextAnnotation(sel, selRange);
-            AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
+            (!sel.isCollapsed || this.marqueeing) && AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
             // Changing which document to add the annotation to (the currently selected WebBox)
             GPTPopup.Instance.setSidebarId(`${this.props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
             GPTPopup.Instance.addDoc = this.sidebarAddDocument;
@@ -390,36 +399,26 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
     @action
     iframeDown = (e: PointerEvent) => {
-        const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
-        const scale = (this.props.NativeDimScaling?.() || 1) * mainContBounds.scale;
-        const word = getWordAtPoint(e.target, e.clientX, e.clientY);
-        this._setPreviewCursor?.(e.clientX, e.clientY, false, true, this.rootDoc);
+        this.props.select(false);
+        const locpt = { 
+            x: (e.clientX / NumCast(this.rootDoc.nativeWidth)) * this.props.PanelWidth(), 
+            y: ((e.clientY - NumCast(this.rootDoc.layout_scrollTop))/ NumCast(this.rootDoc.nativeHeight)) * this.props.PanelHeight() }; // prettier-ignore
+        const scrclick = this.props.DocumentView?.().props.ScreenToLocalTransform().inverse().transformPoint(locpt.x, locpt.y)!;
+        const scrcent = this.props
+            .DocumentView?.()
+            .props.ScreenToLocalTransform()
+            .inverse()
+            .transformPoint(NumCast(this.rootDoc.width) / 2, NumCast(this.rootDoc.height) / 2)!;
+        const theclickoff = Utils.rotPt(scrclick[0] - scrcent[0], scrclick[1] - scrcent[1], -this.props.ScreenToLocalTransform().Rotate);
+        const theclick = [theclickoff.x + scrcent[0], theclickoff.y + scrcent[1]];
         MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-        e.button !== 2 && (this._marqueeing = [e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._layout_scrollTop) * scale]);
-        if (word || (e.target as any)?.className?.includes('rangeslider') || (e.target as any)?.onclick || (e.target as any)?.parentNode?.onclick) {
-            setTimeout(
-                action(() => (this._marqueeing = undefined)),
-                100
-            ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
-        } else {
-            this._iframeClick = this._iframe ?? undefined;
-            this._isAnnotating = true;
-            this.props.select(false);
-            e.stopPropagation();
-            e.preventDefault();
-        }
-
-        // bcz: hack - iframe grabs all events which messes up how we handle contextMenus.  So this super naively simulates the event stack to get the specific menu items and the doc view menu items.
-        if (e.button === 2 || (e.button === 0 && e.altKey)) {
+        const word = getWordAtPoint(e.target, e.clientX, e.clientY);
+        if (!word && !(e.target as any)?.className?.includes('rangeslider') && !(e.target as any)?.onclick && !(e.target as any)?.parentNode?.onclick) {
+            this.marqueeing = theclick;
             e.preventDefault();
-            //e.stopPropagation();
-            ContextMenu.Instance.closeMenu();
-            ContextMenu.Instance.setIgnoreEvents(true);
         }
     };
     isFirefox = () => 'InstallTrigger' in window; // navigator.userAgent.indexOf("Chrome") !== -1;
-    iframeClick = () => this._iframeClick;
-    iframeScaling = () => 1 / this.props.ScreenToLocalTransform().Scale;
 
     addWebStyleSheet(document: any, styleType: string = 'text/css') {
         if (document) {
@@ -723,38 +722,56 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     };
 
+    /**
+     * This gets called when some other child of the webbox is selected and a pointer down occurs on the webbox.
+     * it's also called for html clippings when you click outside the bounds of the clipping
+     * @param e
+     */
     @action
     onMarqueeDown = (e: React.PointerEvent) => {
+        const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
+        this._textAnnotationCreator = undefined;
+        if (sel?.empty) sel.empty(); // Chrome
+        else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
+        this.marqueeing = [e.clientX, e.clientY];
         if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             setupMoveUpEvents(
                 this,
                 e,
                 action(e => {
                     MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-                    this._marqueeing = [e.clientX, e.clientY];
                     return true;
                 }),
                 returnFalse,
-                () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations),
+                action(() => {
+                    this.marqueeing = undefined;
+                    MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
+                }),
                 false
             );
+        } else {
+            this.marqueeing = undefined;
         }
     };
     @action finishMarquee = (x?: number, y?: number, e?: PointerEvent) => {
         this._getAnchor = AnchorMenu.Instance?.GetAnchor;
-        this._marqueeing = undefined;
-        this._isAnnotating = false;
-        this._iframeClick = undefined;
+        this.marqueeing = undefined;
+        this._textAnnotationCreator = undefined;
         const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
         if (sel?.empty) sel.empty(); // Chrome
         else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
+        this._setPreviewCursor?.(x ?? 0, y ?? 0, false, !this._marqueeref.current?.isEmpty, this.rootDoc);
         if (x !== undefined && y !== undefined) {
-            this._setPreviewCursor?.(x, y, false, false, this.rootDoc);
             ContextMenu.Instance.closeMenu();
             ContextMenu.Instance.setIgnoreEvents(false);
             if (e?.button === 2 || e?.altKey) {
-                this.specificContextMenu(undefined as any);
-                this.props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y);
+                e?.preventDefault();
+                e?.stopPropagation();
+                setTimeout(() => {
+                    // if menu comes up right away, the down event can still be active causing a menu item to be selected
+                    this.specificContextMenu(undefined as any);
+                    this.props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y);
+                });
             }
         }
     };
@@ -797,7 +814,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     key={this._warning}
                     className="webBox-iframe"
                     ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))}
-                    style={{ pointerEvents: this._isAnyChildContentActive || DocumentView.Interacting ? 'none' : undefined }}
+                    style={{ pointerEvents: DocumentView.Interacting ? 'none' : undefined }}
                     src={url}
                     onLoad={this.iframeLoaded}
                     scrolling="no" // ugh.. on windows, I get an inner scroll bar for the iframe's body even though the scrollHeight should be set to the full height of the document.
@@ -942,7 +959,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 {this.inlineTextAnnotations
                     .sort((a, b) => NumCast(a.y) - NumCast(b.y))
                     .map(anno => (
-                        <Annotation {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
+                        <Annotation {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
                     ))}
             </div>
         );
@@ -1004,7 +1021,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 onWheel={this.onZoomWheel}
                 onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
                 onPointerDown={this.onMarqueeDown}>
-                <div className="webBox-innerContent" style={{ height: (this._webPageHasBeenRendered && this._scrollHeight) || '100%', pointerEvents }}>
+                <div className="webBox-innerContent" style={{ height: (this._webPageHasBeenRendered && this._scrollHeight > this.props.PanelHeight() && this._scrollHeight) || '100%', pointerEvents }}>
                     {this.content}
                     <div style={{ display: SnappingManager.GetCanEmbed() ? 'none' : undefined, mixBlendMode: 'multiply' }}>{this.renderTransparentAnnotations}</div>
                     {this.renderOpaqueAnnotations}
@@ -1049,7 +1066,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         );
     }
     searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value);
-    showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
     panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth;
     panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
@@ -1067,7 +1083,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         !this._draggingSidebar && this.props.isContentActive() && !MarqueeOptionsMenu.Instance?.isShown()
             ? 'all' //
             : 'none';
-    annotationPointerEvents = () => (this.props.isContentActive() && (this._isAnnotating || SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none');
+    annotationPointerEvents = () => (this.props.isContentActive() && (SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none');
     render() {
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
         const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any);
@@ -1090,27 +1106,26 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     }}
                     onContextMenu={this.specificContextMenu}>
                     {this.webpage}
-                    {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
-                        <div style={{ transformOrigin: 'top left', transform: `scale(${1 / scale})` }}>
-                            <MarqueeAnnotator
-                                rootDoc={this.rootDoc}
-                                iframe={this.isFirefox() ? this.iframeClick : undefined}
-                                iframeScaling={this.isFirefox() ? this.iframeScaling : undefined}
-                                anchorMenuClick={this.anchorMenuClick}
-                                scrollTop={0}
-                                down={this._marqueeing}
-                                scaling={returnOne}
-                                addDocument={this.addDocumentWrapper}
-                                docView={this.props.docViewPath().lastElement()}
-                                finishMarquee={this.finishMarquee}
-                                savedAnnotations={this.savedAnnotationsCreator}
-                                selectionText={this.selectionText}
-                                annotationLayer={this._annotationLayer.current}
-                                mainCont={this._mainCont.current}
-                            />
-                        </div>
-                    )}
                 </div>
+                {!this._mainCont.current || !this._annotationLayer.current ? null : (
+                    <div style={{ position: 'absolute', height: '100%', width: '100%', pointerEvents: this.marqueeing ? 'all' : 'none' }}>
+                        <MarqueeAnnotator
+                            ref={this._marqueeref}
+                            rootDoc={this.rootDoc}
+                            anchorMenuClick={this.anchorMenuClick}
+                            scrollTop={NumCast(this.layoutDoc.layout_scrollTop)}
+                            annotationLayerScrollTop={0}
+                            scaling={this.props.NativeDimScaling}
+                            addDocument={this.addDocumentWrapper}
+                            docView={this.props.DocumentView!}
+                            finishMarquee={this.finishMarquee}
+                            savedAnnotations={this.savedAnnotationsCreator}
+                            selectionText={this.selectionText}
+                            annotationLayer={this._annotationLayer.current}
+                            mainCont={this._mainCont.current}
+                        />
+                    </div>
+                )}
                 <div
                     className="webBox-sideResizer"
                     style={{
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index f3f07cc21..789509784 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1640,8 +1640,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 let dataField = Doc.LayoutFieldKey(tagDoc);
                                 if (Cast(tagDoc[dataField], listSpec(Doc), null)?.filter(d => d instanceof Doc) === undefined) dataField = dataField + '_annotations';
 
-                                if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`self.presentation_targetDoc.annotationOn["${dataField}"]`);
-                                else activeItem.data = ComputedField.MakeFunction(`self.presentation_targetDoc["${dataField}"]`);
+                                if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`self.presentation_targetDoc.annotationOn?.["${dataField}"]`);
+                                else activeItem.data = ComputedField.MakeFunction(`self.presentation_targetDoc?.["${dataField}"]`);
                             }}
                             checked={Cast(activeItem.presentation_indexed, 'number', null) !== undefined ? true : false}
                         />
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 38e4f65b6..e1e87763c 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -12,12 +12,13 @@ import { FieldViewProps } from '../nodes/FieldView';
 import { AnchorMenu } from './AnchorMenu';
 import './Annotation.scss';
 import { LinkManager } from '../../util/LinkManager';
+import { Rect } from 'react-measure';
 
 interface IAnnotationProps extends FieldViewProps {
     anno: Doc;
     dataDoc: Doc;
     fieldKey: string;
-    showInfo: (anno: Opt<Doc>) => void;
+    showInfo?: (anno: Opt<Doc>) => void;
     pointerEvents?: () => Opt<string>;
 }
 @observer
@@ -69,18 +70,24 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
         }
     };
 
+    @action
+    onContextMenu = (e: React.MouseEvent) => {
+        AnchorMenu.Instance.Status = 'annotation';
+        AnchorMenu.Instance.Delete = this.deleteAnnotation.bind(this);
+        AnchorMenu.Instance.Pinned = false;
+        AnchorMenu.Instance.PinToPres = this.pinToPres;
+        AnchorMenu.Instance.MakeTargetToggle = this.makeTargretToggle;
+        AnchorMenu.Instance.IsTargetToggler = this.isTargetToggler;
+        AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this.annoTextRegion);
+        AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
+        e.stopPropagation();
+        e.preventDefault();
+    };
     @action
     onPointerDown = (e: React.PointerEvent) => {
         if (e.button === 2 || e.ctrlKey) {
-            AnchorMenu.Instance.Status = 'annotation';
-            AnchorMenu.Instance.Delete = this.deleteAnnotation.bind(this);
-            AnchorMenu.Instance.Pinned = false;
-            AnchorMenu.Instance.PinToPres = this.pinToPres;
-            AnchorMenu.Instance.MakeTargetToggle = this.makeTargretToggle;
-            AnchorMenu.Instance.IsTargetToggler = this.isTargetToggler;
-            AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this.annoTextRegion);
-            AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
             e.stopPropagation();
+            e.preventDefault();
         } else if (e.button === 0) {
             e.stopPropagation();
             LinkFollower.FollowLink(undefined, this.annoTextRegion, false);
@@ -102,13 +109,14 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
                 ref={this._mainCont}
                 onPointerEnter={action(() => {
                     Doc.BrushDoc(this.props.anno);
-                    this.props.showInfo(this.props.anno);
+                    this.props.showInfo?.(this.props.anno);
                 })}
                 onPointerLeave={action(() => {
                     Doc.UnBrushDoc(this.props.anno);
-                    this.props.showInfo(undefined);
+                    this.props.showInfo?.(undefined);
                 })}
                 onPointerDown={this.onPointerDown}
+                onContextMenu={this.onContextMenu}
                 style={{
                     left: NumCast(this.props.document.x),
                     top: NumCast(this.props.document.y),
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index a66e519c9..ca9bf7bd2 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -2,7 +2,7 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from 'mobx-react';
 import * as Pdfjs from 'pdfjs-dist';
 import 'pdfjs-dist/web/pdf_viewer.css';
-import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { Height } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
@@ -11,7 +11,6 @@ import { TraceMobx } from '../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnAll, returnFalse, returnNone, returnZero, smoothScroll, Utils } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { SelectionManager } from '../../util/SelectionManager';
-import { SharingManager } from '../../util/SharingManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
@@ -56,16 +55,15 @@ export class PDFViewer extends React.Component<IViewerProps> {
     static _annotationStyle: any = addStyleSheet();
     @observable private _pageSizes: { width: number; height: number }[] = [];
     @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
-    @observable private _marqueeing: number[] | undefined;
     @observable private _textSelecting = true;
     @observable private _showWaiting = true;
-    @observable private _overlayAnnoInfo: Opt<Doc>;
     @observable private Index: number = -1;
 
     private _pdfViewer: any;
     private _styleRule: any; // stylesheet rule for making hyperlinks clickable
     private _retries = 0; // number of times tried to create the PDF viewer
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
+    private _marqueeref = React.createRef<MarqueeAnnotator>();
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
@@ -368,17 +366,14 @@ export class PDFViewer extends React.Component<IViewerProps> {
         if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             this.props.select(false);
             MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-            this._marqueeing = [e.clientX, e.clientY];
+            this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
             this.isAnnotating = true;
             const target = e.target as any;
             if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.parentElement?.className !== 'textLayer'))) {
                 this._textSelecting = false;
             } else {
                 // if textLayer is hit, then we select text instead of using a marquee so clear out the marquee.
-                setTimeout(
-                    action(() => (this._marqueeing = undefined)),
-                    100
-                ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
+                setTimeout(() => this._marqueeref.current?.onTerminateSelection(), 100); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
 
                 this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, 'htmlAnnotation', { 'pointer-events': 'none' });
                 document.addEventListener('pointerup', this.onSelectEnd);
@@ -390,7 +385,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     finishMarquee = (x?: number, y?: number) => {
         this._getAnchor = AnchorMenu.Instance?.GetAnchor;
         this.isAnnotating = false;
-        this._marqueeing = undefined;
+        this._marqueeref.current?.onTerminateSelection();
         this._textSelecting = true;
     };
 
@@ -420,23 +415,26 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @action
     createTextAnnotation = (sel: Selection, selRange: Range) => {
         if (this._mainCont.current) {
+            this._mainCont.current.style.transform = `rotate(${NumCast(this.props.DocumentView!().screenToLocalTransform().RotateDeg)}deg)`;
             const boundingRect = this._mainCont.current.getBoundingClientRect();
             const clientRects = selRange.getClientRects();
             for (let i = 0; i < clientRects.length; i++) {
                 const rect = clientRects.item(i);
-                if (rect?.width && rect.width < this._mainCont.current.clientWidth / this.props.ScreenToLocalTransform().Scale) {
+                if (rect && rect?.width && rect.width < this._mainCont.current.clientWidth / this.props.ScreenToLocalTransform().Scale) {
                     const scaleX = this._mainCont.current.offsetWidth / boundingRect.width;
+                    const scaleY = this._mainCont.current.offsetHeight / boundingRect.height;
                     const pdfScale = NumCast(this.props.layoutDoc._freeform_scale, 1);
                     const annoBox = document.createElement('div');
                     annoBox.className = 'marqueeAnnotator-annotationBox';
                     // transforms the positions from screen onto the pdf div
-                    annoBox.style.top = (((rect.top - boundingRect.top) * scaleX) / pdfScale + this._mainCont.current.scrollTop).toString();
                     annoBox.style.left = (((rect.left - boundingRect.left) * scaleX) / pdfScale).toString();
-                    annoBox.style.width = ((rect.width * this._mainCont.current.offsetWidth) / boundingRect.width / pdfScale).toString();
-                    annoBox.style.height = ((rect.height * this._mainCont.current.offsetHeight) / boundingRect.height / pdfScale).toString();
+                    annoBox.style.top = (((rect.top - boundingRect.top) * scaleY) / pdfScale + this._mainCont.current.scrollTop).toString();
+                    annoBox.style.width = ((rect.width * scaleX) / pdfScale).toString();
+                    annoBox.style.height = ((rect.height * scaleY) / pdfScale).toString();
                     this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, this.getPageFromScroll(rect.top));
                 }
             }
+            this._mainCont.current!.style.transform = '';
         }
         this._selectionContent = selRange.cloneContents();
         this._selectionText = this._selectionContent?.textContent || '';
@@ -482,24 +480,13 @@ export class PDFViewer extends React.Component<IViewerProps> {
         return (
             <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${NumCast(this.props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
                 {inlineAnnos.map(anno => (
-                    <Annotation {...this.props} fieldKey={this.props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
+                    <Annotation {...this.props} fieldKey={this.props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
                 ))}
             </div>
         );
     }
 
-    @computed get overlayInfo() {
-        return !this._overlayAnnoInfo ? null : (
-            <div className="pdfViewerDash-overlayAnno" style={{ top: NumCast(this._overlayAnnoInfo.y), left: NumCast(this._overlayAnnoInfo.x) }}>
-                <div className="pdfViewerDash-overlayAnno" style={{ right: -50, background: SharingManager.Instance.users.find(users => users.user.email === this._overlayAnnoInfo!.author)?.userColor }}>
-                    {this._overlayAnnoInfo.author + ' ' + Field.toString(this._overlayAnnoInfo.author_date as Field)}
-                </div>
-            </div>
-        );
-    }
-
     getScrollHeight = () => this._scrollHeight;
-    showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
     scrollXf = () => (this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.layoutDoc._layout_scrollTop)) : this.props.ScreenToLocalTransform());
     overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._freeform_scale, 1));
     panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1);
@@ -567,6 +554,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         return <div className={'pdfViewerDash-text' + (this.props.pointerEvents?.() !== 'none' && this._textSelecting && this.props.isContentActive() ? '-selected' : '')} ref={this._viewer} />;
     }
     savedAnnotations = () => this._savedAnnotations;
+    addDocumentWrapper = (doc: Doc | Doc[]) => this.props.addDocument!(doc);
     render() {
         TraceMobx();
         return (
@@ -585,17 +573,17 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     {this.pdfViewerDiv}
                     {this.annotationLayer}
                     {this.overlayLayer}
-                    {this.overlayInfo}
                     {this._showWaiting ? <img className="pdfViewerDash-waiting" src={'/assets/loading.gif'} /> : null}
-                    {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
+                    {!this._mainCont.current || !this._annotationLayer.current ? null : (
                         <MarqueeAnnotator
+                            ref={this._marqueeref}
                             rootDoc={this.props.rootDoc}
                             getPageFromScroll={this.getPageFromScroll}
                             anchorMenuClick={this.props.anchorMenuClick}
                             scrollTop={0}
-                            down={this._marqueeing}
-                            addDocument={(doc: Doc | Doc[]) => this.props.addDocument!(doc)}
-                            docView={this.props.docViewPath().lastElement()}
+                            annotationLayerScrollTop={NumCast(this.props.Document._layout_scrollTop)}
+                            addDocument={this.addDocumentWrapper}
+                            docView={this.props.DocumentView!}
                             finishMarquee={this.finishMarquee}
                             savedAnnotations={this.savedAnnotations}
                             selectionText={this.selectionText}
-- 
cgit v1.2.3-70-g09d2


From e4eac6e4256dc320f6c767ecbad54b83459c4331 Mon Sep 17 00:00:00 2001
From: zaultavangar <zaul_tavangar@brown.edu>
Date: Mon, 11 Dec 2023 14:46:58 -0500
Subject: updates to map feature

---
 deploy/index.html                                  |    1 +
 package-lock.json                                  | 6707 +++++++++++++-------
 package.json                                       |    8 +-
 src/client/documents/DocumentTypes.ts              |    1 +
 src/client/documents/Documents.ts                  |   20 +-
 src/client/views/AntimodeMenu.scss                 |    5 +
 src/client/views/AntimodeMenu.tsx                  |    6 +-
 src/client/views/Main.tsx                          |    1 +
 src/client/views/MainView.tsx                      |    4 +-
 src/client/views/nodes/DocumentContentsView.tsx    |    2 +-
 .../views/nodes/MapBox/DirectionsAnchorMenu.tsx    |  137 +
 src/client/views/nodes/MapBox/GeocoderControl.tsx  |  107 +
 src/client/views/nodes/MapBox/MapAnchorMenu.scss   |   59 +-
 src/client/views/nodes/MapBox/MapAnchorMenu.tsx    |  385 +-
 src/client/views/nodes/MapBox/MapBox.scss          |   38 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |  527 +-
 src/client/views/nodes/MapBox/MapboxApiUtility.ts  |  103 +
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |  844 +++
 src/fields/Doc.ts                                  |    2 +-
 webpack.config.js                                  |    3 +-
 20 files changed, 6722 insertions(+), 2238 deletions(-)
 create mode 100644 src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
 create mode 100644 src/client/views/nodes/MapBox/GeocoderControl.tsx
 create mode 100644 src/client/views/nodes/MapBox/MapboxApiUtility.ts
 create mode 100644 src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx

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

diff --git a/deploy/index.html b/deploy/index.html
index b345e193f..186c217d8 100644
--- a/deploy/index.html
+++ b/deploy/index.html
@@ -1,6 +1,7 @@
 <html style="overflow: hidden">
     <head>
         <title>Dash</title>
+        <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v3.0.1/mapbox-gl.css' rel='stylesheet' />
         <link rel="preconnect" href="https://fonts.googleapis.com" />
         <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
         <link
diff --git a/package-lock.json b/package-lock.json
index 321c4a174..58b3d9eae 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2211,6 +2211,12 @@
                 "to-fast-properties": "^2.0.0"
             }
         },
+        "@colors/colors": {
+            "version": "1.5.0",
+            "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
+            "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==",
+            "optional": true
+        },
         "@cspotcode/source-map-support": {
             "version": "0.8.1",
             "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
@@ -2918,6 +2924,11 @@
                 "prop-types": "^15.8.1"
             }
         },
+        "@gar/promisify": {
+            "version": "1.1.3",
+            "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
+            "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="
+        },
         "@googlemaps/js-api-loader": {
             "version": "1.15.1",
             "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.15.1.tgz",
@@ -3049,6 +3060,69 @@
             "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz",
             "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw=="
         },
+        "@isaacs/cliui": {
+            "version": "8.0.2",
+            "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+            "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+            "requires": {
+                "string-width": "^5.1.2",
+                "string-width-cjs": "npm:string-width@^4.2.0",
+                "strip-ansi": "^7.0.1",
+                "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+                "wrap-ansi": "^8.1.0",
+                "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+            },
+            "dependencies": {
+                "ansi-regex": {
+                    "version": "6.0.1",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+                    "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="
+                },
+                "ansi-styles": {
+                    "version": "6.2.1",
+                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+                    "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="
+                },
+                "emoji-regex": {
+                    "version": "9.2.2",
+                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+                    "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
+                },
+                "string-width": {
+                    "version": "5.1.2",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+                    "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+                    "requires": {
+                        "eastasianwidth": "^0.2.0",
+                        "emoji-regex": "^9.2.2",
+                        "strip-ansi": "^7.0.1"
+                    }
+                },
+                "strip-ansi": {
+                    "version": "7.1.0",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+                    "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+                    "requires": {
+                        "ansi-regex": "^6.0.1"
+                    }
+                },
+                "wrap-ansi": {
+                    "version": "8.1.0",
+                    "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+                    "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+                    "requires": {
+                        "ansi-styles": "^6.1.0",
+                        "string-width": "^5.0.1",
+                        "strip-ansi": "^7.0.1"
+                    }
+                }
+            }
+        },
+        "@isaacs/string-locale-compare": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz",
+            "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="
+        },
         "@jridgewell/gen-mapping": {
             "version": "0.3.2",
             "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
@@ -3097,6 +3171,201 @@
             "resolved": "https://registry.npmjs.org/@log4js-node/log4js-api/-/log4js-api-1.0.2.tgz",
             "integrity": "sha512-6SJfx949YEWooh/CUPpJ+F491y4BYJmknz4hUN1+RHvKoUEynKbRmhnwbk/VLmh4OthLLDNCyWXfbh4DG1cTXA=="
         },
+        "@mapbox/fusspot": {
+            "version": "0.4.0",
+            "resolved": "https://registry.npmjs.org/@mapbox/fusspot/-/fusspot-0.4.0.tgz",
+            "integrity": "sha512-6sys1vUlhNCqMvJOqPEPSi0jc9tg7aJ//oG1A16H3PXoIt9whtNngD7UzBHUVTH15zunR/vRvMtGNVsogm1KzA==",
+            "requires": {
+                "is-plain-obj": "^1.1.0",
+                "xtend": "^4.0.1"
+            },
+            "dependencies": {
+                "is-plain-obj": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+                    "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="
+                }
+            }
+        },
+        "@mapbox/geojson-rewind": {
+            "version": "0.5.2",
+            "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz",
+            "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==",
+            "requires": {
+                "get-stream": "^6.0.1",
+                "minimist": "^1.2.6"
+            }
+        },
+        "@mapbox/jsonlint-lines-primitives": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz",
+            "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ=="
+        },
+        "@mapbox/mapbox-gl-geocoder": {
+            "version": "4.7.4",
+            "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-geocoder/-/mapbox-gl-geocoder-4.7.4.tgz",
+            "integrity": "sha512-9PIilTgqnoSCVtzJ1MnE4f1siWIpC9GO5MPaKTOspb7hXIvcgFOp44PSWP+WlDTHoEze3Dz3WQuppti6TVWjyg==",
+            "requires": {
+                "@mapbox/mapbox-sdk": "^0.13.2",
+                "lodash.debounce": "^4.0.6",
+                "nanoid": "^2.0.1",
+                "subtag": "^0.5.0",
+                "suggestions": "^1.6.0",
+                "xtend": "^4.0.1"
+            },
+            "dependencies": {
+                "lodash.debounce": {
+                    "version": "4.0.8",
+                    "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+                    "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
+                }
+            }
+        },
+        "@mapbox/mapbox-gl-supported": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.1.tgz",
+            "integrity": "sha512-HP6XvfNIzfoMVfyGjBckjiAOQK9WfX0ywdLubuPMPv+Vqf5fj0uCbgBQYpiqcWZT6cbyyRnTSXDheT1ugvF6UQ=="
+        },
+        "@mapbox/mapbox-sdk": {
+            "version": "0.13.7",
+            "resolved": "https://registry.npmjs.org/@mapbox/mapbox-sdk/-/mapbox-sdk-0.13.7.tgz",
+            "integrity": "sha512-JZjBsAVSBv7lX7gQPOQwftBGHIUcvL/tPKFxAL+SCT7u1n+eRH052XebOTkl28pNm7Du6DpKAs1GvgUnDcFFDQ==",
+            "requires": {
+                "@mapbox/fusspot": "^0.4.0",
+                "@mapbox/parse-mapbox-token": "^0.2.0",
+                "@mapbox/polyline": "^1.0.0",
+                "eventemitter3": "^3.1.0",
+                "form-data": "^3.0.0",
+                "got": "^11.8.5",
+                "is-plain-obj": "^1.1.0",
+                "xtend": "^4.0.1"
+            },
+            "dependencies": {
+                "@sindresorhus/is": {
+                    "version": "4.6.0",
+                    "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
+                    "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="
+                },
+                "@szmarczak/http-timer": {
+                    "version": "4.0.6",
+                    "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
+                    "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
+                    "requires": {
+                        "defer-to-connect": "^2.0.0"
+                    }
+                },
+                "cacheable-lookup": {
+                    "version": "5.0.4",
+                    "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
+                    "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="
+                },
+                "cacheable-request": {
+                    "version": "7.0.4",
+                    "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
+                    "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
+                    "requires": {
+                        "clone-response": "^1.0.2",
+                        "get-stream": "^5.1.0",
+                        "http-cache-semantics": "^4.0.0",
+                        "keyv": "^4.0.0",
+                        "lowercase-keys": "^2.0.0",
+                        "normalize-url": "^6.0.1",
+                        "responselike": "^2.0.0"
+                    }
+                },
+                "decompress-response": {
+                    "version": "6.0.0",
+                    "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+                    "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+                    "requires": {
+                        "mimic-response": "^3.1.0"
+                    }
+                },
+                "eventemitter3": {
+                    "version": "3.1.2",
+                    "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
+                    "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
+                },
+                "form-data": {
+                    "version": "3.0.1",
+                    "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
+                    "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
+                    "requires": {
+                        "asynckit": "^0.4.0",
+                        "combined-stream": "^1.0.8",
+                        "mime-types": "^2.1.12"
+                    }
+                },
+                "get-stream": {
+                    "version": "5.2.0",
+                    "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+                    "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+                    "requires": {
+                        "pump": "^3.0.0"
+                    }
+                },
+                "got": {
+                    "version": "11.8.6",
+                    "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
+                    "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
+                    "requires": {
+                        "@sindresorhus/is": "^4.0.0",
+                        "@szmarczak/http-timer": "^4.0.5",
+                        "@types/cacheable-request": "^6.0.1",
+                        "@types/responselike": "^1.0.0",
+                        "cacheable-lookup": "^5.0.3",
+                        "cacheable-request": "^7.0.2",
+                        "decompress-response": "^6.0.0",
+                        "http2-wrapper": "^1.0.0-beta.5.2",
+                        "lowercase-keys": "^2.0.0",
+                        "p-cancelable": "^2.0.0",
+                        "responselike": "^2.0.0"
+                    }
+                },
+                "http2-wrapper": {
+                    "version": "1.0.3",
+                    "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
+                    "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
+                    "requires": {
+                        "quick-lru": "^5.1.1",
+                        "resolve-alpn": "^1.0.0"
+                    }
+                },
+                "is-plain-obj": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+                    "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="
+                },
+                "lowercase-keys": {
+                    "version": "2.0.0",
+                    "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
+                    "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="
+                },
+                "mimic-response": {
+                    "version": "3.1.0",
+                    "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+                    "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="
+                },
+                "normalize-url": {
+                    "version": "6.1.0",
+                    "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
+                    "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="
+                },
+                "p-cancelable": {
+                    "version": "2.1.1",
+                    "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
+                    "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="
+                },
+                "responselike": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
+                    "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
+                    "requires": {
+                        "lowercase-keys": "^2.0.0"
+                    }
+                }
+            }
+        },
         "@mapbox/node-pre-gyp": {
             "version": "1.0.10",
             "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz",
@@ -3123,6 +3392,254 @@
                 }
             }
         },
+        "@mapbox/parse-mapbox-token": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/@mapbox/parse-mapbox-token/-/parse-mapbox-token-0.2.0.tgz",
+            "integrity": "sha512-BjeuG4sodYaoTygwXIuAWlZV6zUv4ZriYAQhXikzx+7DChycMUQ9g85E79Htat+AsBg+nStFALehlOhClYm5cQ==",
+            "requires": {
+                "base-64": "^0.1.0"
+            }
+        },
+        "@mapbox/point-geometry": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz",
+            "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ=="
+        },
+        "@mapbox/polyline": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/@mapbox/polyline/-/polyline-1.2.1.tgz",
+            "integrity": "sha512-sn0V18O3OzW4RCcPoUIVDWvEGQaBNH9a0y5lgqrf5hUycyw1CzrhEoxV5irzrMNXKCkw1xRsZXcaVbsVZggHXA==",
+            "requires": {
+                "meow": "^9.0.0"
+            },
+            "dependencies": {
+                "camelcase-keys": {
+                    "version": "6.2.2",
+                    "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz",
+                    "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==",
+                    "requires": {
+                        "camelcase": "^5.3.1",
+                        "map-obj": "^4.0.0",
+                        "quick-lru": "^4.0.1"
+                    }
+                },
+                "find-up": {
+                    "version": "4.1.0",
+                    "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+                    "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+                    "requires": {
+                        "locate-path": "^5.0.0",
+                        "path-exists": "^4.0.0"
+                    }
+                },
+                "hosted-git-info": {
+                    "version": "4.1.0",
+                    "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
+                    "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==",
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    }
+                },
+                "indent-string": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+                    "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="
+                },
+                "locate-path": {
+                    "version": "5.0.0",
+                    "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+                    "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+                    "requires": {
+                        "p-locate": "^4.1.0"
+                    }
+                },
+                "map-obj": {
+                    "version": "4.3.0",
+                    "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
+                    "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ=="
+                },
+                "meow": {
+                    "version": "9.0.0",
+                    "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz",
+                    "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==",
+                    "requires": {
+                        "@types/minimist": "^1.2.0",
+                        "camelcase-keys": "^6.2.2",
+                        "decamelize": "^1.2.0",
+                        "decamelize-keys": "^1.1.0",
+                        "hard-rejection": "^2.1.0",
+                        "minimist-options": "4.1.0",
+                        "normalize-package-data": "^3.0.0",
+                        "read-pkg-up": "^7.0.1",
+                        "redent": "^3.0.0",
+                        "trim-newlines": "^3.0.0",
+                        "type-fest": "^0.18.0",
+                        "yargs-parser": "^20.2.3"
+                    }
+                },
+                "normalize-package-data": {
+                    "version": "3.0.3",
+                    "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz",
+                    "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==",
+                    "requires": {
+                        "hosted-git-info": "^4.0.1",
+                        "is-core-module": "^2.5.0",
+                        "semver": "^7.3.4",
+                        "validate-npm-package-license": "^3.0.1"
+                    }
+                },
+                "p-locate": {
+                    "version": "4.1.0",
+                    "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+                    "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+                    "requires": {
+                        "p-limit": "^2.2.0"
+                    }
+                },
+                "path-exists": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+                    "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
+                },
+                "quick-lru": {
+                    "version": "4.0.1",
+                    "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
+                    "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g=="
+                },
+                "read-pkg": {
+                    "version": "5.2.0",
+                    "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
+                    "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
+                    "requires": {
+                        "@types/normalize-package-data": "^2.4.0",
+                        "normalize-package-data": "^2.5.0",
+                        "parse-json": "^5.0.0",
+                        "type-fest": "^0.6.0"
+                    },
+                    "dependencies": {
+                        "hosted-git-info": {
+                            "version": "2.8.9",
+                            "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+                            "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="
+                        },
+                        "normalize-package-data": {
+                            "version": "2.5.0",
+                            "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+                            "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+                            "requires": {
+                                "hosted-git-info": "^2.1.4",
+                                "resolve": "^1.10.0",
+                                "semver": "2 || 3 || 4 || 5",
+                                "validate-npm-package-license": "^3.0.1"
+                            }
+                        },
+                        "semver": {
+                            "version": "5.7.2",
+                            "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+                            "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="
+                        },
+                        "type-fest": {
+                            "version": "0.6.0",
+                            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
+                            "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg=="
+                        }
+                    }
+                },
+                "read-pkg-up": {
+                    "version": "7.0.1",
+                    "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
+                    "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
+                    "requires": {
+                        "find-up": "^4.1.0",
+                        "read-pkg": "^5.2.0",
+                        "type-fest": "^0.8.1"
+                    },
+                    "dependencies": {
+                        "type-fest": {
+                            "version": "0.8.1",
+                            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+                            "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="
+                        }
+                    }
+                },
+                "redent": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+                    "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+                    "requires": {
+                        "indent-string": "^4.0.0",
+                        "strip-indent": "^3.0.0"
+                    }
+                },
+                "semver": {
+                    "version": "7.5.4",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                    "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    }
+                },
+                "strip-indent": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+                    "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+                    "requires": {
+                        "min-indent": "^1.0.0"
+                    }
+                },
+                "trim-newlines": {
+                    "version": "3.0.1",
+                    "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz",
+                    "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw=="
+                },
+                "type-fest": {
+                    "version": "0.18.1",
+                    "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz",
+                    "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw=="
+                },
+                "yargs-parser": {
+                    "version": "20.2.9",
+                    "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+                    "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="
+                }
+            }
+        },
+        "@mapbox/tiny-sdf": {
+            "version": "2.0.6",
+            "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz",
+            "integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA=="
+        },
+        "@mapbox/unitbezier": {
+            "version": "0.0.1",
+            "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz",
+            "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw=="
+        },
+        "@mapbox/vector-tile": {
+            "version": "1.3.1",
+            "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz",
+            "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==",
+            "requires": {
+                "@mapbox/point-geometry": "~0.1.0"
+            }
+        },
+        "@mapbox/whoots-js": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz",
+            "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q=="
+        },
+        "@maplibre/maplibre-gl-style-spec": {
+            "version": "19.3.3",
+            "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.3.tgz",
+            "integrity": "sha512-cOZZOVhDSulgK0meTsTkmNXb1ahVvmTmWmfx9gRBwc6hq98wS9JP35ESIoNq3xqEan+UN+gn8187Z6E4NKhLsw==",
+            "requires": {
+                "@mapbox/jsonlint-lines-primitives": "~2.0.2",
+                "@mapbox/unitbezier": "^0.0.1",
+                "json-stringify-pretty-compact": "^3.0.0",
+                "minimist": "^1.2.8",
+                "rw": "^1.3.3",
+                "sort-object": "^3.0.3"
+            }
+        },
         "@material-ui/core": {
             "version": "4.12.4",
             "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.4.tgz",
@@ -3507,232 +4024,1364 @@
                 "fastq": "^1.6.0"
             }
         },
-        "@octokit/auth-token": {
-            "version": "3.0.3",
-            "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.3.tgz",
-            "integrity": "sha512-/aFM2M4HVDBT/jjDBa84sJniv1t9Gm/rLkalaz9htOm+L+8JMj1k9w0CkUdcxNyNxZPlTxKPVko+m1VlM58ZVA==",
-            "requires": {
-                "@octokit/types": "^9.0.0"
+        "@npmcli/arborist": {
+            "version": "6.3.0",
+            "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-6.3.0.tgz",
+            "integrity": "sha512-XrS14qBDhK95RdGhjTSx8AgeZPNah949qp3b0v3GUFOugtPc9Z85rpWid57mONS8gHbuGIHjFzuA+5hSM7BuBA==",
+            "requires": {
+                "@isaacs/string-locale-compare": "^1.1.0",
+                "@npmcli/fs": "^3.1.0",
+                "@npmcli/installed-package-contents": "^2.0.2",
+                "@npmcli/map-workspaces": "^3.0.2",
+                "@npmcli/metavuln-calculator": "^5.0.0",
+                "@npmcli/name-from-folder": "^2.0.0",
+                "@npmcli/node-gyp": "^3.0.0",
+                "@npmcli/package-json": "^4.0.0",
+                "@npmcli/query": "^3.0.0",
+                "@npmcli/run-script": "^6.0.0",
+                "bin-links": "^4.0.1",
+                "cacache": "^17.0.4",
+                "common-ancestor-path": "^1.0.1",
+                "hosted-git-info": "^6.1.1",
+                "json-parse-even-better-errors": "^3.0.0",
+                "json-stringify-nice": "^1.1.4",
+                "minimatch": "^9.0.0",
+                "nopt": "^7.0.0",
+                "npm-install-checks": "^6.0.0",
+                "npm-package-arg": "^10.1.0",
+                "npm-pick-manifest": "^8.0.1",
+                "npm-registry-fetch": "^14.0.3",
+                "npmlog": "^7.0.1",
+                "pacote": "^15.0.8",
+                "parse-conflict-json": "^3.0.0",
+                "proc-log": "^3.0.0",
+                "promise-all-reject-late": "^1.0.0",
+                "promise-call-limit": "^1.0.2",
+                "read-package-json-fast": "^3.0.2",
+                "semver": "^7.3.7",
+                "ssri": "^10.0.1",
+                "treeverse": "^3.0.0",
+                "walk-up-path": "^3.0.1"
+            },
+            "dependencies": {
+                "abbrev": {
+                    "version": "2.0.0",
+                    "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
+                    "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="
+                },
+                "ansi-regex": {
+                    "version": "5.0.1",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                },
+                "are-we-there-yet": {
+                    "version": "4.0.1",
+                    "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-4.0.1.tgz",
+                    "integrity": "sha512-2zuA+jpOYBRgoBCfa+fB87Rk0oGJjDX6pxGzqH6f33NzUhG25Xur6R0u0Z9VVAq8Z5JvQpQI6j6rtonuivC8QA==",
+                    "requires": {
+                        "delegates": "^1.0.0",
+                        "readable-stream": "^4.1.0"
+                    }
+                },
+                "brace-expansion": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+                    "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+                    "requires": {
+                        "balanced-match": "^1.0.0"
+                    }
+                },
+                "buffer": {
+                    "version": "6.0.3",
+                    "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+                    "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+                    "requires": {
+                        "base64-js": "^1.3.1",
+                        "ieee754": "^1.2.1"
+                    }
+                },
+                "cacache": {
+                    "version": "17.1.4",
+                    "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.4.tgz",
+                    "integrity": "sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==",
+                    "requires": {
+                        "@npmcli/fs": "^3.1.0",
+                        "fs-minipass": "^3.0.0",
+                        "glob": "^10.2.2",
+                        "lru-cache": "^7.7.1",
+                        "minipass": "^7.0.3",
+                        "minipass-collect": "^1.0.2",
+                        "minipass-flush": "^1.0.5",
+                        "minipass-pipeline": "^1.2.4",
+                        "p-map": "^4.0.0",
+                        "ssri": "^10.0.0",
+                        "tar": "^6.1.11",
+                        "unique-filename": "^3.0.0"
+                    }
+                },
+                "emoji-regex": {
+                    "version": "8.0.0",
+                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                    "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+                },
+                "fs-minipass": {
+                    "version": "3.0.3",
+                    "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz",
+                    "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==",
+                    "requires": {
+                        "minipass": "^7.0.3"
+                    }
+                },
+                "gauge": {
+                    "version": "5.0.1",
+                    "resolved": "https://registry.npmjs.org/gauge/-/gauge-5.0.1.tgz",
+                    "integrity": "sha512-CmykPMJGuNan/3S4kZOpvvPYSNqSHANiWnh9XcMU2pSjtBfF0XzZ2p1bFAxTbnFxyBuPxQYHhzwaoOmUdqzvxQ==",
+                    "requires": {
+                        "aproba": "^1.0.3 || ^2.0.0",
+                        "color-support": "^1.1.3",
+                        "console-control-strings": "^1.1.0",
+                        "has-unicode": "^2.0.1",
+                        "signal-exit": "^4.0.1",
+                        "string-width": "^4.2.3",
+                        "strip-ansi": "^6.0.1",
+                        "wide-align": "^1.1.5"
+                    }
+                },
+                "glob": {
+                    "version": "10.3.10",
+                    "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
+                    "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+                    "requires": {
+                        "foreground-child": "^3.1.0",
+                        "jackspeak": "^2.3.5",
+                        "minimatch": "^9.0.1",
+                        "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
+                        "path-scurry": "^1.10.1"
+                    }
+                },
+                "hosted-git-info": {
+                    "version": "6.1.1",
+                    "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz",
+                    "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==",
+                    "requires": {
+                        "lru-cache": "^7.5.1"
+                    }
+                },
+                "is-fullwidth-code-point": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+                    "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+                },
+                "json-parse-even-better-errors": {
+                    "version": "3.0.1",
+                    "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz",
+                    "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg=="
+                },
+                "lru-cache": {
+                    "version": "7.18.3",
+                    "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+                    "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="
+                },
+                "minimatch": {
+                    "version": "9.0.3",
+                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+                    "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+                    "requires": {
+                        "brace-expansion": "^2.0.1"
+                    }
+                },
+                "minipass": {
+                    "version": "7.0.4",
+                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                    "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ=="
+                },
+                "nopt": {
+                    "version": "7.2.0",
+                    "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz",
+                    "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==",
+                    "requires": {
+                        "abbrev": "^2.0.0"
+                    }
+                },
+                "npmlog": {
+                    "version": "7.0.1",
+                    "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-7.0.1.tgz",
+                    "integrity": "sha512-uJ0YFk/mCQpLBt+bxN88AKd+gyqZvZDbtiNxk6Waqcj2aPRyfVx8ITawkyQynxUagInjdYT1+qj4NfA5KJJUxg==",
+                    "requires": {
+                        "are-we-there-yet": "^4.0.0",
+                        "console-control-strings": "^1.1.0",
+                        "gauge": "^5.0.0",
+                        "set-blocking": "^2.0.0"
+                    }
+                },
+                "p-map": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+                    "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+                    "requires": {
+                        "aggregate-error": "^3.0.0"
+                    }
+                },
+                "readable-stream": {
+                    "version": "4.4.2",
+                    "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz",
+                    "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==",
+                    "requires": {
+                        "abort-controller": "^3.0.0",
+                        "buffer": "^6.0.3",
+                        "events": "^3.3.0",
+                        "process": "^0.11.10",
+                        "string_decoder": "^1.3.0"
+                    }
+                },
+                "safe-buffer": {
+                    "version": "5.2.1",
+                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+                    "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+                },
+                "semver": {
+                    "version": "7.5.4",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                    "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    },
+                    "dependencies": {
+                        "lru-cache": {
+                            "version": "6.0.0",
+                            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+                            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+                            "requires": {
+                                "yallist": "^4.0.0"
+                            }
+                        }
+                    }
+                },
+                "signal-exit": {
+                    "version": "4.1.0",
+                    "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+                    "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="
+                },
+                "ssri": {
+                    "version": "10.0.5",
+                    "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.5.tgz",
+                    "integrity": "sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==",
+                    "requires": {
+                        "minipass": "^7.0.3"
+                    }
+                },
+                "string-width": {
+                    "version": "4.2.3",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                    "requires": {
+                        "emoji-regex": "^8.0.0",
+                        "is-fullwidth-code-point": "^3.0.0",
+                        "strip-ansi": "^6.0.1"
+                    }
+                },
+                "string_decoder": {
+                    "version": "1.3.0",
+                    "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+                    "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+                    "requires": {
+                        "safe-buffer": "~5.2.0"
+                    }
+                },
+                "strip-ansi": {
+                    "version": "6.0.1",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                    "requires": {
+                        "ansi-regex": "^5.0.1"
+                    }
+                },
+                "unique-filename": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz",
+                    "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==",
+                    "requires": {
+                        "unique-slug": "^4.0.0"
+                    }
+                },
+                "unique-slug": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz",
+                    "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==",
+                    "requires": {
+                        "imurmurhash": "^0.1.4"
+                    }
+                },
+                "wide-align": {
+                    "version": "1.1.5",
+                    "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
+                    "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
+                    "requires": {
+                        "string-width": "^1.0.2 || 2 || 3 || 4"
+                    }
+                }
             }
         },
-        "@octokit/core": {
-            "version": "4.2.0",
-            "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.2.0.tgz",
-            "integrity": "sha512-AgvDRUg3COpR82P7PBdGZF/NNqGmtMq2NiPqeSsDIeCfYFOZ9gddqWNQHnFdEUf+YwOj4aZYmJnlPp7OXmDIDg==",
+        "@npmcli/config": {
+            "version": "6.2.1",
+            "resolved": "https://registry.npmjs.org/@npmcli/config/-/config-6.2.1.tgz",
+            "integrity": "sha512-Cj/OrSbrLvnwWuzquFCDTwFN8QmR+SWH6qLNCBttUreDkKM5D5p36SeSMbcEUiCGdwjUrVy2yd8C0REwwwDPEw==",
             "requires": {
-                "@octokit/auth-token": "^3.0.0",
-                "@octokit/graphql": "^5.0.0",
-                "@octokit/request": "^6.0.0",
-                "@octokit/request-error": "^3.0.0",
-                "@octokit/types": "^9.0.0",
-                "before-after-hook": "^2.2.0",
-                "universal-user-agent": "^6.0.0"
+                "@npmcli/map-workspaces": "^3.0.2",
+                "ci-info": "^3.8.0",
+                "ini": "^4.1.0",
+                "nopt": "^7.0.0",
+                "proc-log": "^3.0.0",
+                "read-package-json-fast": "^3.0.2",
+                "semver": "^7.3.5",
+                "walk-up-path": "^3.0.1"
+            },
+            "dependencies": {
+                "abbrev": {
+                    "version": "2.0.0",
+                    "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
+                    "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="
+                },
+                "ci-info": {
+                    "version": "3.9.0",
+                    "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+                    "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="
+                },
+                "ini": {
+                    "version": "4.1.1",
+                    "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz",
+                    "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g=="
+                },
+                "nopt": {
+                    "version": "7.2.0",
+                    "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz",
+                    "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==",
+                    "requires": {
+                        "abbrev": "^2.0.0"
+                    }
+                },
+                "semver": {
+                    "version": "7.5.4",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                    "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    }
+                }
             }
         },
-        "@octokit/endpoint": {
-            "version": "7.0.5",
-            "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.5.tgz",
-            "integrity": "sha512-LG4o4HMY1Xoaec87IqQ41TQ+glvIeTKqfjkCEmt5AIwDZJwQeVZFIEYXrYY6yLwK+pAScb9Gj4q+Nz2qSw1roA==",
+        "@npmcli/disparity-colors": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/@npmcli/disparity-colors/-/disparity-colors-3.0.0.tgz",
+            "integrity": "sha512-5R/z157/f20Fi0Ou4ZttL51V0xz0EdPEOauFtPCEYOLInDBRCj1/TxOJ5aGTrtShxEshN2d+hXb9ZKSi5RLBcg==",
             "requires": {
-                "@octokit/types": "^9.0.0",
-                "is-plain-object": "^5.0.0",
-                "universal-user-agent": "^6.0.0"
+                "ansi-styles": "^4.3.0"
+            },
+            "dependencies": {
+                "ansi-styles": {
+                    "version": "4.3.0",
+                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+                    "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+                    "requires": {
+                        "color-convert": "^2.0.1"
+                    }
+                },
+                "color-convert": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+                    "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+                    "requires": {
+                        "color-name": "~1.1.4"
+                    }
+                },
+                "color-name": {
+                    "version": "1.1.4",
+                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+                    "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+                }
             }
         },
-        "@octokit/graphql": {
-            "version": "5.0.5",
-            "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.5.tgz",
-            "integrity": "sha512-Qwfvh3xdqKtIznjX9lz2D458r7dJPP8l6r4GQkIdWQouZwHQK0mVT88uwiU2bdTU2OtT1uOlKpRciUWldpG0yQ==",
+        "@npmcli/fs": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz",
+            "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==",
             "requires": {
-                "@octokit/request": "^6.0.0",
-                "@octokit/types": "^9.0.0",
-                "universal-user-agent": "^6.0.0"
+                "semver": "^7.3.5"
+            },
+            "dependencies": {
+                "semver": {
+                    "version": "7.5.4",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                    "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    }
+                }
             }
         },
-        "@octokit/openapi-types": {
-            "version": "16.0.0",
-            "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-16.0.0.tgz",
-            "integrity": "sha512-JbFWOqTJVLHZSUUoF4FzAZKYtqdxWu9Z5m2QQnOyEa04fOFljvyh7D3GYKbfuaSWisqehImiVIMG4eyJeP5VEA=="
-        },
-        "@octokit/request": {
-            "version": "6.2.3",
-            "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.3.tgz",
-            "integrity": "sha512-TNAodj5yNzrrZ/VxP+H5HiYaZep0H3GU0O7PaF+fhDrt8FPrnkei9Aal/txsN/1P7V3CPiThG0tIvpPDYUsyAA==",
+        "@npmcli/git": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz",
+            "integrity": "sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==",
             "requires": {
-                "@octokit/endpoint": "^7.0.0",
-                "@octokit/request-error": "^3.0.0",
-                "@octokit/types": "^9.0.0",
-                "is-plain-object": "^5.0.0",
-                "node-fetch": "^2.6.7",
-                "universal-user-agent": "^6.0.0"
-            }
-        },
-        "@octokit/request-error": {
-            "version": "3.0.3",
-            "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.3.tgz",
-            "integrity": "sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==",
-            "requires": {
-                "@octokit/types": "^9.0.0",
-                "deprecation": "^2.0.0",
-                "once": "^1.4.0"
+                "@npmcli/promise-spawn": "^6.0.0",
+                "lru-cache": "^7.4.4",
+                "npm-pick-manifest": "^8.0.0",
+                "proc-log": "^3.0.0",
+                "promise-inflight": "^1.0.1",
+                "promise-retry": "^2.0.1",
+                "semver": "^7.3.5",
+                "which": "^3.0.0"
+            },
+            "dependencies": {
+                "lru-cache": {
+                    "version": "7.18.3",
+                    "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+                    "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="
+                },
+                "semver": {
+                    "version": "7.5.4",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                    "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    },
+                    "dependencies": {
+                        "lru-cache": {
+                            "version": "6.0.0",
+                            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+                            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+                            "requires": {
+                                "yallist": "^4.0.0"
+                            }
+                        }
+                    }
+                },
+                "which": {
+                    "version": "3.0.1",
+                    "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz",
+                    "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==",
+                    "requires": {
+                        "isexe": "^2.0.0"
+                    }
+                }
             }
         },
-        "@octokit/types": {
-            "version": "9.0.0",
-            "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.0.0.tgz",
-            "integrity": "sha512-LUewfj94xCMH2rbD5YJ+6AQ4AVjFYTgpp6rboWM5T7N3IsIF65SBEOVcYMGAEzO/kKNiNaW4LoWtoThOhH06gw==",
+        "@npmcli/installed-package-contents": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz",
+            "integrity": "sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ==",
             "requires": {
-                "@octokit/openapi-types": "^16.0.0"
+                "npm-bundled": "^3.0.0",
+                "npm-normalize-package-bin": "^3.0.0"
             }
         },
-        "@opentelemetry/api": {
-            "version": "1.4.1",
-            "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.1.tgz",
-            "integrity": "sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA=="
+        "@npmcli/map-workspaces": {
+            "version": "3.0.4",
+            "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-3.0.4.tgz",
+            "integrity": "sha512-Z0TbvXkRbacjFFLpVpV0e2mheCh+WzQpcqL+4xp49uNJOxOnIAPZyXtUxZ5Qn3QBTGKA11Exjd9a5411rBrhDg==",
+            "requires": {
+                "@npmcli/name-from-folder": "^2.0.0",
+                "glob": "^10.2.2",
+                "minimatch": "^9.0.0",
+                "read-package-json-fast": "^3.0.0"
+            },
+            "dependencies": {
+                "brace-expansion": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+                    "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+                    "requires": {
+                        "balanced-match": "^1.0.0"
+                    }
+                },
+                "glob": {
+                    "version": "10.3.10",
+                    "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
+                    "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+                    "requires": {
+                        "foreground-child": "^3.1.0",
+                        "jackspeak": "^2.3.5",
+                        "minimatch": "^9.0.1",
+                        "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
+                        "path-scurry": "^1.10.1"
+                    }
+                },
+                "minimatch": {
+                    "version": "9.0.3",
+                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+                    "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+                    "requires": {
+                        "brace-expansion": "^2.0.1"
+                    }
+                },
+                "minipass": {
+                    "version": "7.0.4",
+                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                    "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ=="
+                }
+            }
         },
-        "@popperjs/core": {
-            "version": "2.11.7",
-            "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz",
-            "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw=="
+        "@npmcli/metavuln-calculator": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-5.0.1.tgz",
+            "integrity": "sha512-qb8Q9wIIlEPj3WeA1Lba91R4ZboPL0uspzV0F9uwP+9AYMVB2zOoa7Pbk12g6D2NHAinSbHh6QYmGuRyHZ874Q==",
+            "requires": {
+                "cacache": "^17.0.0",
+                "json-parse-even-better-errors": "^3.0.0",
+                "pacote": "^15.0.0",
+                "semver": "^7.3.5"
+            },
+            "dependencies": {
+                "brace-expansion": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+                    "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+                    "requires": {
+                        "balanced-match": "^1.0.0"
+                    }
+                },
+                "cacache": {
+                    "version": "17.1.4",
+                    "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.4.tgz",
+                    "integrity": "sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==",
+                    "requires": {
+                        "@npmcli/fs": "^3.1.0",
+                        "fs-minipass": "^3.0.0",
+                        "glob": "^10.2.2",
+                        "lru-cache": "^7.7.1",
+                        "minipass": "^7.0.3",
+                        "minipass-collect": "^1.0.2",
+                        "minipass-flush": "^1.0.5",
+                        "minipass-pipeline": "^1.2.4",
+                        "p-map": "^4.0.0",
+                        "ssri": "^10.0.0",
+                        "tar": "^6.1.11",
+                        "unique-filename": "^3.0.0"
+                    }
+                },
+                "fs-minipass": {
+                    "version": "3.0.3",
+                    "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz",
+                    "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==",
+                    "requires": {
+                        "minipass": "^7.0.3"
+                    }
+                },
+                "glob": {
+                    "version": "10.3.10",
+                    "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
+                    "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+                    "requires": {
+                        "foreground-child": "^3.1.0",
+                        "jackspeak": "^2.3.5",
+                        "minimatch": "^9.0.1",
+                        "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
+                        "path-scurry": "^1.10.1"
+                    }
+                },
+                "json-parse-even-better-errors": {
+                    "version": "3.0.1",
+                    "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz",
+                    "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg=="
+                },
+                "lru-cache": {
+                    "version": "7.18.3",
+                    "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+                    "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="
+                },
+                "minimatch": {
+                    "version": "9.0.3",
+                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+                    "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+                    "requires": {
+                        "brace-expansion": "^2.0.1"
+                    }
+                },
+                "minipass": {
+                    "version": "7.0.4",
+                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                    "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ=="
+                },
+                "p-map": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+                    "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+                    "requires": {
+                        "aggregate-error": "^3.0.0"
+                    }
+                },
+                "semver": {
+                    "version": "7.5.4",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                    "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    },
+                    "dependencies": {
+                        "lru-cache": {
+                            "version": "6.0.0",
+                            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+                            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+                            "requires": {
+                                "yallist": "^4.0.0"
+                            }
+                        }
+                    }
+                },
+                "ssri": {
+                    "version": "10.0.5",
+                    "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.5.tgz",
+                    "integrity": "sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==",
+                    "requires": {
+                        "minipass": "^7.0.3"
+                    }
+                },
+                "unique-filename": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz",
+                    "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==",
+                    "requires": {
+                        "unique-slug": "^4.0.0"
+                    }
+                },
+                "unique-slug": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz",
+                    "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==",
+                    "requires": {
+                        "imurmurhash": "^0.1.4"
+                    }
+                }
+            }
         },
-        "@react-google-maps/api": {
-            "version": "2.18.1",
-            "resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-2.18.1.tgz",
-            "integrity": "sha512-KVlUO/Shh+0g/3egWaKmY0sz6+0QOnYkBGvrBMJbz23519LauA+iJFc4NDCmWNHqD5Vhb/Bkg0kSJgq0Stz3Iw==",
+        "@npmcli/move-file": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz",
+            "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==",
             "requires": {
-                "@googlemaps/js-api-loader": "1.15.1",
-                "@googlemaps/markerclusterer": "2.0.15",
-                "@react-google-maps/infobox": "2.16.0",
-                "@react-google-maps/marker-clusterer": "2.16.1",
-                "@types/google.maps": "3.50.5",
-                "invariant": "2.2.4"
+                "mkdirp": "^1.0.4",
+                "rimraf": "^3.0.2"
+            },
+            "dependencies": {
+                "mkdirp": {
+                    "version": "1.0.4",
+                    "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+                    "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
+                }
             }
         },
-        "@react-google-maps/infobox": {
-            "version": "2.16.0",
-            "resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-2.16.0.tgz",
-            "integrity": "sha512-ZojiMS25388RcUHQPycUAerSqdHDom+3dHczVcXHdT/i8fka3O8InkHxXwMhvBoM143ips7mv2BPaYOAJ5f4Nw=="
+        "@npmcli/name-from-folder": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz",
+            "integrity": "sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg=="
         },
-        "@react-google-maps/marker-clusterer": {
-            "version": "2.16.1",
-            "resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-2.16.1.tgz",
-            "integrity": "sha512-jOuyqzWLeXvQcoAu6TCVWHAuko+sDt0JjawNHBGqUNLywMtTCvYP0L0PiqJZOUCUeRYGdUy0AKxQ+30vAkvwag=="
+        "@npmcli/node-gyp": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz",
+            "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA=="
         },
-        "@react-three/fiber": {
-            "version": "6.2.3",
-            "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-6.2.3.tgz",
-            "integrity": "sha512-XuL3683iVcwjm3enour57k4OjkA1MQztqpRL2oQh30v7ygf1vJg95gKo8cqqgPJctc2pZAMUW1eRpr+Xqjwqrg==",
+        "@npmcli/package-json": {
+            "version": "4.0.1",
+            "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-4.0.1.tgz",
+            "integrity": "sha512-lRCEGdHZomFsURroh522YvA/2cVb9oPIJrjHanCJZkiasz1BzcnLr3tBJhlV7S86MBJBuAQ33is2D60YitZL2Q==",
             "requires": {
-                "@babel/runtime": "^7.13.10",
-                "react-merge-refs": "^1.1.0",
-                "react-reconciler": "^0.26.2",
-                "react-three-fiber": "0.0.0-deprecated",
-                "react-use-measure": "^2.0.4",
-                "resize-observer-polyfill": "^1.5.1",
-                "scheduler": "^0.20.2",
-                "use-asset": "^1.0.4",
-                "utility-types": "^3.10.0",
-                "zustand": "^3.5.1"
+                "@npmcli/git": "^4.1.0",
+                "glob": "^10.2.2",
+                "hosted-git-info": "^6.1.1",
+                "json-parse-even-better-errors": "^3.0.0",
+                "normalize-package-data": "^5.0.0",
+                "proc-log": "^3.0.0",
+                "semver": "^7.5.3"
+            },
+            "dependencies": {
+                "brace-expansion": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+                    "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+                    "requires": {
+                        "balanced-match": "^1.0.0"
+                    }
+                },
+                "glob": {
+                    "version": "10.3.10",
+                    "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
+                    "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+                    "requires": {
+                        "foreground-child": "^3.1.0",
+                        "jackspeak": "^2.3.5",
+                        "minimatch": "^9.0.1",
+                        "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
+                        "path-scurry": "^1.10.1"
+                    }
+                },
+                "hosted-git-info": {
+                    "version": "6.1.1",
+                    "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz",
+                    "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==",
+                    "requires": {
+                        "lru-cache": "^7.5.1"
+                    }
+                },
+                "json-parse-even-better-errors": {
+                    "version": "3.0.1",
+                    "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz",
+                    "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg=="
+                },
+                "lru-cache": {
+                    "version": "7.18.3",
+                    "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+                    "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="
+                },
+                "minimatch": {
+                    "version": "9.0.3",
+                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+                    "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+                    "requires": {
+                        "brace-expansion": "^2.0.1"
+                    }
+                },
+                "minipass": {
+                    "version": "7.0.4",
+                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                    "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ=="
+                },
+                "normalize-package-data": {
+                    "version": "5.0.0",
+                    "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz",
+                    "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==",
+                    "requires": {
+                        "hosted-git-info": "^6.0.0",
+                        "is-core-module": "^2.8.1",
+                        "semver": "^7.3.5",
+                        "validate-npm-package-license": "^3.0.4"
+                    }
+                },
+                "semver": {
+                    "version": "7.5.4",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                    "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    },
+                    "dependencies": {
+                        "lru-cache": {
+                            "version": "6.0.0",
+                            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+                            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+                            "requires": {
+                                "yallist": "^4.0.0"
+                            }
+                        }
+                    }
+                }
             }
         },
-        "@rkusa/linebreak": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/@rkusa/linebreak/-/linebreak-1.0.0.tgz",
-            "integrity": "sha512-yCSm87XA1aYMgfcABSxcIkk3JtCw3AihNceHY+DnZGLvVP/g2z3UWZbi0xIoYpZWAJEVPr5Zt3QE37Q80wF1pA==",
+        "@npmcli/promise-spawn": {
+            "version": "6.0.2",
+            "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz",
+            "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==",
             "requires": {
-                "unicode-trie": "^0.3.0"
+                "which": "^3.0.0"
+            },
+            "dependencies": {
+                "which": {
+                    "version": "3.0.1",
+                    "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz",
+                    "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==",
+                    "requires": {
+                        "isexe": "^2.0.0"
+                    }
+                }
             }
         },
-        "@sindresorhus/is": {
-            "version": "5.3.0",
-            "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.3.0.tgz",
-            "integrity": "sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw=="
-        },
-        "@szmarczak/http-timer": {
-            "version": "5.0.1",
-            "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz",
-            "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==",
+        "@npmcli/query": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-3.0.1.tgz",
+            "integrity": "sha512-0jE8iHBogf/+bFDj+ju6/UMLbJ39c8h6nSe6qile+dB7PJ0iV3gNqcb2vtt6WWCBrxv9uAjzUT/8vroluulidA==",
             "requires": {
-                "defer-to-connect": "^2.0.1"
+                "postcss-selector-parser": "^6.0.10"
             }
         },
-        "@tsconfig/node10": {
-            "version": "1.0.9",
-            "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
-            "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
-            "dev": true
-        },
-        "@tsconfig/node12": {
-            "version": "1.0.11",
-            "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
-            "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
-            "dev": true
-        },
-        "@tsconfig/node14": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
-            "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
-            "dev": true
-        },
-        "@tsconfig/node16": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
-            "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
-            "dev": true
-        },
-        "@types/adm-zip": {
-            "version": "0.4.34",
-            "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.34.tgz",
-            "integrity": "sha512-8ToYLLAYhkRfcmmljrKi22gT2pqu7hGMDtORP1emwIEGmgUTZOsaDjzWFzW5N2frcFRz/50CWt4zA1CxJ73pmQ==",
-            "dev": true,
+        "@npmcli/run-script": {
+            "version": "6.0.2",
+            "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.2.tgz",
+            "integrity": "sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==",
             "requires": {
-                "@types/node": "*"
+                "@npmcli/node-gyp": "^3.0.0",
+                "@npmcli/promise-spawn": "^6.0.0",
+                "node-gyp": "^9.0.0",
+                "read-package-json-fast": "^3.0.0",
+                "which": "^3.0.0"
+            },
+            "dependencies": {
+                "@npmcli/fs": {
+                    "version": "2.1.2",
+                    "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz",
+                    "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==",
+                    "requires": {
+                        "@gar/promisify": "^1.1.3",
+                        "semver": "^7.3.5"
+                    }
+                },
+                "ansi-regex": {
+                    "version": "5.0.1",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                },
+                "are-we-there-yet": {
+                    "version": "3.0.1",
+                    "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz",
+                    "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==",
+                    "requires": {
+                        "delegates": "^1.0.0",
+                        "readable-stream": "^3.6.0"
+                    }
+                },
+                "brace-expansion": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+                    "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+                    "requires": {
+                        "balanced-match": "^1.0.0"
+                    }
+                },
+                "cacache": {
+                    "version": "16.1.3",
+                    "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz",
+                    "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==",
+                    "requires": {
+                        "@npmcli/fs": "^2.1.0",
+                        "@npmcli/move-file": "^2.0.0",
+                        "chownr": "^2.0.0",
+                        "fs-minipass": "^2.1.0",
+                        "glob": "^8.0.1",
+                        "infer-owner": "^1.0.4",
+                        "lru-cache": "^7.7.1",
+                        "minipass": "^3.1.6",
+                        "minipass-collect": "^1.0.2",
+                        "minipass-flush": "^1.0.5",
+                        "minipass-pipeline": "^1.2.4",
+                        "mkdirp": "^1.0.4",
+                        "p-map": "^4.0.0",
+                        "promise-inflight": "^1.0.1",
+                        "rimraf": "^3.0.2",
+                        "ssri": "^9.0.0",
+                        "tar": "^6.1.11",
+                        "unique-filename": "^2.0.0"
+                    },
+                    "dependencies": {
+                        "glob": {
+                            "version": "8.1.0",
+                            "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+                            "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+                            "requires": {
+                                "fs.realpath": "^1.0.0",
+                                "inflight": "^1.0.4",
+                                "inherits": "2",
+                                "minimatch": "^5.0.1",
+                                "once": "^1.3.0"
+                            }
+                        }
+                    }
+                },
+                "emoji-regex": {
+                    "version": "8.0.0",
+                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                    "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+                },
+                "gauge": {
+                    "version": "4.0.4",
+                    "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz",
+                    "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==",
+                    "requires": {
+                        "aproba": "^1.0.3 || ^2.0.0",
+                        "color-support": "^1.1.3",
+                        "console-control-strings": "^1.1.0",
+                        "has-unicode": "^2.0.1",
+                        "signal-exit": "^3.0.7",
+                        "string-width": "^4.2.3",
+                        "strip-ansi": "^6.0.1",
+                        "wide-align": "^1.1.5"
+                    }
+                },
+                "is-fullwidth-code-point": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+                    "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+                },
+                "lru-cache": {
+                    "version": "7.18.3",
+                    "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+                    "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="
+                },
+                "make-fetch-happen": {
+                    "version": "10.2.1",
+                    "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz",
+                    "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==",
+                    "requires": {
+                        "agentkeepalive": "^4.2.1",
+                        "cacache": "^16.1.0",
+                        "http-cache-semantics": "^4.1.0",
+                        "http-proxy-agent": "^5.0.0",
+                        "https-proxy-agent": "^5.0.0",
+                        "is-lambda": "^1.0.1",
+                        "lru-cache": "^7.7.1",
+                        "minipass": "^3.1.6",
+                        "minipass-collect": "^1.0.2",
+                        "minipass-fetch": "^2.0.3",
+                        "minipass-flush": "^1.0.5",
+                        "minipass-pipeline": "^1.2.4",
+                        "negotiator": "^0.6.3",
+                        "promise-retry": "^2.0.1",
+                        "socks-proxy-agent": "^7.0.0",
+                        "ssri": "^9.0.0"
+                    }
+                },
+                "minimatch": {
+                    "version": "5.1.6",
+                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+                    "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+                    "requires": {
+                        "brace-expansion": "^2.0.1"
+                    }
+                },
+                "minipass": {
+                    "version": "3.3.6",
+                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                    "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+                    "requires": {
+                        "yallist": "^4.0.0"
+                    }
+                },
+                "mkdirp": {
+                    "version": "1.0.4",
+                    "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+                    "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
+                },
+                "node-gyp": {
+                    "version": "9.4.1",
+                    "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz",
+                    "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==",
+                    "requires": {
+                        "env-paths": "^2.2.0",
+                        "exponential-backoff": "^3.1.1",
+                        "glob": "^7.1.4",
+                        "graceful-fs": "^4.2.6",
+                        "make-fetch-happen": "^10.0.3",
+                        "nopt": "^6.0.0",
+                        "npmlog": "^6.0.0",
+                        "rimraf": "^3.0.2",
+                        "semver": "^7.3.5",
+                        "tar": "^6.1.2",
+                        "which": "^2.0.2"
+                    },
+                    "dependencies": {
+                        "which": {
+                            "version": "2.0.2",
+                            "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+                            "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+                            "requires": {
+                                "isexe": "^2.0.0"
+                            }
+                        }
+                    }
+                },
+                "nopt": {
+                    "version": "6.0.0",
+                    "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz",
+                    "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==",
+                    "requires": {
+                        "abbrev": "^1.0.0"
+                    }
+                },
+                "npmlog": {
+                    "version": "6.0.2",
+                    "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz",
+                    "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==",
+                    "requires": {
+                        "are-we-there-yet": "^3.0.0",
+                        "console-control-strings": "^1.1.0",
+                        "gauge": "^4.0.3",
+                        "set-blocking": "^2.0.0"
+                    }
+                },
+                "p-map": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+                    "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+                    "requires": {
+                        "aggregate-error": "^3.0.0"
+                    }
+                },
+                "semver": {
+                    "version": "7.5.4",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                    "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    },
+                    "dependencies": {
+                        "lru-cache": {
+                            "version": "6.0.0",
+                            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+                            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+                            "requires": {
+                                "yallist": "^4.0.0"
+                            }
+                        }
+                    }
+                },
+                "ssri": {
+                    "version": "9.0.1",
+                    "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz",
+                    "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==",
+                    "requires": {
+                        "minipass": "^3.1.1"
+                    }
+                },
+                "string-width": {
+                    "version": "4.2.3",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                    "requires": {
+                        "emoji-regex": "^8.0.0",
+                        "is-fullwidth-code-point": "^3.0.0",
+                        "strip-ansi": "^6.0.1"
+                    }
+                },
+                "strip-ansi": {
+                    "version": "6.0.1",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                    "requires": {
+                        "ansi-regex": "^5.0.1"
+                    }
+                },
+                "unique-filename": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz",
+                    "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==",
+                    "requires": {
+                        "unique-slug": "^3.0.0"
+                    }
+                },
+                "unique-slug": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz",
+                    "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==",
+                    "requires": {
+                        "imurmurhash": "^0.1.4"
+                    }
+                },
+                "which": {
+                    "version": "3.0.1",
+                    "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz",
+                    "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==",
+                    "requires": {
+                        "isexe": "^2.0.0"
+                    }
+                },
+                "wide-align": {
+                    "version": "1.1.5",
+                    "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
+                    "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
+                    "requires": {
+                        "string-width": "^1.0.2 || 2 || 3 || 4"
+                    }
+                }
             }
         },
-        "@types/animejs": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/@types/animejs/-/animejs-2.0.2.tgz",
-            "integrity": "sha512-ACymFQ5qgSrZNR1Fqjk7Wv9gH6dFgejn2gpLkkceWxTKzivRJsshX4xhBVALgvF79gUdXiMCRIdusN728XpeGA==",
-            "dev": true
-        },
-        "@types/archiver": {
-            "version": "3.1.1",
-            "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-3.1.1.tgz",
-            "integrity": "sha512-TzVZ9204sH1TuFylfr1cw/AA/3/VldAAXswEwKLXUOzA9mDg+m6gHF9EaqKNlozcjc6knX5m1KAqJzksPLSEfw==",
-            "dev": true,
+        "@octokit/auth-token": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.3.tgz",
+            "integrity": "sha512-/aFM2M4HVDBT/jjDBa84sJniv1t9Gm/rLkalaz9htOm+L+8JMj1k9w0CkUdcxNyNxZPlTxKPVko+m1VlM58ZVA==",
             "requires": {
-                "@types/glob": "*"
+                "@octokit/types": "^9.0.0"
             }
         },
-        "@types/async": {
-            "version": "2.4.2",
-            "resolved": "https://registry.npmjs.org/@types/async/-/async-2.4.2.tgz",
-            "integrity": "sha512-bWBbC7VG2jdjbgZMX0qpds8U/3h3anfIqE81L8jmVrgFZw/urEDnBA78ymGGKTTK6ciBXmmJ/xlok+Re41S8ww==",
-            "dev": true
+        "@octokit/core": {
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.2.0.tgz",
+            "integrity": "sha512-AgvDRUg3COpR82P7PBdGZF/NNqGmtMq2NiPqeSsDIeCfYFOZ9gddqWNQHnFdEUf+YwOj4aZYmJnlPp7OXmDIDg==",
+            "requires": {
+                "@octokit/auth-token": "^3.0.0",
+                "@octokit/graphql": "^5.0.0",
+                "@octokit/request": "^6.0.0",
+                "@octokit/request-error": "^3.0.0",
+                "@octokit/types": "^9.0.0",
+                "before-after-hook": "^2.2.0",
+                "universal-user-agent": "^6.0.0"
+            }
         },
-        "@types/babel-types": {
-            "version": "7.0.11",
-            "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.11.tgz",
-            "integrity": "sha512-pkPtJUUY+Vwv6B1inAz55rQvivClHJxc9aVEPPmaq2cbyeMLCiDpbKpcKyX4LAwpNGi+SHBv0tHv6+0gXv0P2A=="
+        "@octokit/endpoint": {
+            "version": "7.0.5",
+            "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.5.tgz",
+            "integrity": "sha512-LG4o4HMY1Xoaec87IqQ41TQ+glvIeTKqfjkCEmt5AIwDZJwQeVZFIEYXrYY6yLwK+pAScb9Gj4q+Nz2qSw1roA==",
+            "requires": {
+                "@octokit/types": "^9.0.0",
+                "is-plain-object": "^5.0.0",
+                "universal-user-agent": "^6.0.0"
+            }
         },
-        "@types/babylon": {
-            "version": "6.16.6",
-            "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.6.tgz",
-            "integrity": "sha512-G4yqdVlhr6YhzLXFKy5F7HtRBU8Y23+iWy7UKthMq/OSQnL1hbsoeXESQ2LY8zEDlknipDG3nRGhUC9tkwvy/w==",
+        "@octokit/graphql": {
+            "version": "5.0.5",
+            "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.5.tgz",
+            "integrity": "sha512-Qwfvh3xdqKtIznjX9lz2D458r7dJPP8l6r4GQkIdWQouZwHQK0mVT88uwiU2bdTU2OtT1uOlKpRciUWldpG0yQ==",
             "requires": {
-                "@types/babel-types": "*"
+                "@octokit/request": "^6.0.0",
+                "@octokit/types": "^9.0.0",
+                "universal-user-agent": "^6.0.0"
             }
         },
-        "@types/bcrypt-nodejs": {
-            "version": "0.0.30",
-            "resolved": "https://registry.npmjs.org/@types/bcrypt-nodejs/-/bcrypt-nodejs-0.0.30.tgz",
-            "integrity": "sha1-TN2WtJKTs5MhIuS34pVD415rrlg=",
-            "dev": true
+        "@octokit/openapi-types": {
+            "version": "16.0.0",
+            "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-16.0.0.tgz",
+            "integrity": "sha512-JbFWOqTJVLHZSUUoF4FzAZKYtqdxWu9Z5m2QQnOyEa04fOFljvyh7D3GYKbfuaSWisqehImiVIMG4eyJeP5VEA=="
         },
-        "@types/bezier-js": {
-            "version": "4.1.0",
-            "resolved": "https://registry.npmjs.org/@types/bezier-js/-/bezier-js-4.1.0.tgz",
-            "integrity": "sha512-ElU16s8E6Pr6magp8ihwH1O8pbUJASbMND/qgUc9RsLmP3lMLHiDMRXdjtaObwW5GPtOVYOsXDUIhTIluT+yaw=="
+        "@octokit/request": {
+            "version": "6.2.3",
+            "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.3.tgz",
+            "integrity": "sha512-TNAodj5yNzrrZ/VxP+H5HiYaZep0H3GU0O7PaF+fhDrt8FPrnkei9Aal/txsN/1P7V3CPiThG0tIvpPDYUsyAA==",
+            "requires": {
+                "@octokit/endpoint": "^7.0.0",
+                "@octokit/request-error": "^3.0.0",
+                "@octokit/types": "^9.0.0",
+                "is-plain-object": "^5.0.0",
+                "node-fetch": "^2.6.7",
+                "universal-user-agent": "^6.0.0"
+            }
+        },
+        "@octokit/request-error": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.3.tgz",
+            "integrity": "sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==",
+            "requires": {
+                "@octokit/types": "^9.0.0",
+                "deprecation": "^2.0.0",
+                "once": "^1.4.0"
+            }
+        },
+        "@octokit/types": {
+            "version": "9.0.0",
+            "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.0.0.tgz",
+            "integrity": "sha512-LUewfj94xCMH2rbD5YJ+6AQ4AVjFYTgpp6rboWM5T7N3IsIF65SBEOVcYMGAEzO/kKNiNaW4LoWtoThOhH06gw==",
+            "requires": {
+                "@octokit/openapi-types": "^16.0.0"
+            }
+        },
+        "@opentelemetry/api": {
+            "version": "1.4.1",
+            "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.1.tgz",
+            "integrity": "sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA=="
+        },
+        "@pkgjs/parseargs": {
+            "version": "0.11.0",
+            "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+            "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+            "optional": true
+        },
+        "@popperjs/core": {
+            "version": "2.11.7",
+            "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz",
+            "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw=="
+        },
+        "@react-google-maps/api": {
+            "version": "2.18.1",
+            "resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-2.18.1.tgz",
+            "integrity": "sha512-KVlUO/Shh+0g/3egWaKmY0sz6+0QOnYkBGvrBMJbz23519LauA+iJFc4NDCmWNHqD5Vhb/Bkg0kSJgq0Stz3Iw==",
+            "requires": {
+                "@googlemaps/js-api-loader": "1.15.1",
+                "@googlemaps/markerclusterer": "2.0.15",
+                "@react-google-maps/infobox": "2.16.0",
+                "@react-google-maps/marker-clusterer": "2.16.1",
+                "@types/google.maps": "3.50.5",
+                "invariant": "2.2.4"
+            }
+        },
+        "@react-google-maps/infobox": {
+            "version": "2.16.0",
+            "resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-2.16.0.tgz",
+            "integrity": "sha512-ZojiMS25388RcUHQPycUAerSqdHDom+3dHczVcXHdT/i8fka3O8InkHxXwMhvBoM143ips7mv2BPaYOAJ5f4Nw=="
+        },
+        "@react-google-maps/marker-clusterer": {
+            "version": "2.16.1",
+            "resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-2.16.1.tgz",
+            "integrity": "sha512-jOuyqzWLeXvQcoAu6TCVWHAuko+sDt0JjawNHBGqUNLywMtTCvYP0L0PiqJZOUCUeRYGdUy0AKxQ+30vAkvwag=="
+        },
+        "@react-three/fiber": {
+            "version": "6.2.3",
+            "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-6.2.3.tgz",
+            "integrity": "sha512-XuL3683iVcwjm3enour57k4OjkA1MQztqpRL2oQh30v7ygf1vJg95gKo8cqqgPJctc2pZAMUW1eRpr+Xqjwqrg==",
+            "requires": {
+                "@babel/runtime": "^7.13.10",
+                "react-merge-refs": "^1.1.0",
+                "react-reconciler": "^0.26.2",
+                "react-three-fiber": "0.0.0-deprecated",
+                "react-use-measure": "^2.0.4",
+                "resize-observer-polyfill": "^1.5.1",
+                "scheduler": "^0.20.2",
+                "use-asset": "^1.0.4",
+                "utility-types": "^3.10.0",
+                "zustand": "^3.5.1"
+            }
+        },
+        "@rkusa/linebreak": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/@rkusa/linebreak/-/linebreak-1.0.0.tgz",
+            "integrity": "sha512-yCSm87XA1aYMgfcABSxcIkk3JtCw3AihNceHY+DnZGLvVP/g2z3UWZbi0xIoYpZWAJEVPr5Zt3QE37Q80wF1pA==",
+            "requires": {
+                "unicode-trie": "^0.3.0"
+            }
+        },
+        "@sigstore/protobuf-specs": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.1.0.tgz",
+            "integrity": "sha512-a31EnjuIDSX8IXBUib3cYLDRlPMU36AWX4xS8ysLaNu4ZzUesDiPt83pgrW2X1YLMe5L2HbDyaKK5BrL4cNKaQ=="
+        },
+        "@sigstore/tuf": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-1.0.3.tgz",
+            "integrity": "sha512-2bRovzs0nJZFlCN3rXirE4gwxCn97JNjMmwpecqlbgV9WcxX7WRuIrgzx/X7Ib7MYRbyUTpBYE0s2x6AmZXnlg==",
+            "requires": {
+                "@sigstore/protobuf-specs": "^0.2.0",
+                "tuf-js": "^1.1.7"
+            },
+            "dependencies": {
+                "@sigstore/protobuf-specs": {
+                    "version": "0.2.1",
+                    "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.2.1.tgz",
+                    "integrity": "sha512-XTWVxnWJu+c1oCshMLwnKvz8ZQJJDVOlciMfgpJBQbThVjKTCG8dwyhgLngBD2KN0ap9F/gOV8rFDEx8uh7R2A=="
+                }
+            }
+        },
+        "@sindresorhus/is": {
+            "version": "5.3.0",
+            "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.3.0.tgz",
+            "integrity": "sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw=="
+        },
+        "@szmarczak/http-timer": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz",
+            "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==",
+            "requires": {
+                "defer-to-connect": "^2.0.1"
+            }
+        },
+        "@tootallnate/once": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
+            "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A=="
+        },
+        "@tsconfig/node10": {
+            "version": "1.0.9",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
+            "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
+            "dev": true
+        },
+        "@tsconfig/node12": {
+            "version": "1.0.11",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+            "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+            "dev": true
+        },
+        "@tsconfig/node14": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+            "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+            "dev": true
+        },
+        "@tsconfig/node16": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
+            "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
+            "dev": true
+        },
+        "@tufjs/canonical-json": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz",
+            "integrity": "sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ=="
+        },
+        "@tufjs/models": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-1.0.4.tgz",
+            "integrity": "sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A==",
+            "requires": {
+                "@tufjs/canonical-json": "1.0.0",
+                "minimatch": "^9.0.0"
+            },
+            "dependencies": {
+                "brace-expansion": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+                    "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+                    "requires": {
+                        "balanced-match": "^1.0.0"
+                    }
+                },
+                "minimatch": {
+                    "version": "9.0.3",
+                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+                    "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+                    "requires": {
+                        "brace-expansion": "^2.0.1"
+                    }
+                }
+            }
+        },
+        "@types/adm-zip": {
+            "version": "0.4.34",
+            "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.34.tgz",
+            "integrity": "sha512-8ToYLLAYhkRfcmmljrKi22gT2pqu7hGMDtORP1emwIEGmgUTZOsaDjzWFzW5N2frcFRz/50CWt4zA1CxJ73pmQ==",
+            "dev": true,
+            "requires": {
+                "@types/node": "*"
+            }
+        },
+        "@types/animejs": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/@types/animejs/-/animejs-2.0.2.tgz",
+            "integrity": "sha512-ACymFQ5qgSrZNR1Fqjk7Wv9gH6dFgejn2gpLkkceWxTKzivRJsshX4xhBVALgvF79gUdXiMCRIdusN728XpeGA==",
+            "dev": true
+        },
+        "@types/archiver": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-3.1.1.tgz",
+            "integrity": "sha512-TzVZ9204sH1TuFylfr1cw/AA/3/VldAAXswEwKLXUOzA9mDg+m6gHF9EaqKNlozcjc6knX5m1KAqJzksPLSEfw==",
+            "dev": true,
+            "requires": {
+                "@types/glob": "*"
+            }
+        },
+        "@types/async": {
+            "version": "2.4.2",
+            "resolved": "https://registry.npmjs.org/@types/async/-/async-2.4.2.tgz",
+            "integrity": "sha512-bWBbC7VG2jdjbgZMX0qpds8U/3h3anfIqE81L8jmVrgFZw/urEDnBA78ymGGKTTK6ciBXmmJ/xlok+Re41S8ww==",
+            "dev": true
+        },
+        "@types/babel-types": {
+            "version": "7.0.11",
+            "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.11.tgz",
+            "integrity": "sha512-pkPtJUUY+Vwv6B1inAz55rQvivClHJxc9aVEPPmaq2cbyeMLCiDpbKpcKyX4LAwpNGi+SHBv0tHv6+0gXv0P2A=="
+        },
+        "@types/babylon": {
+            "version": "6.16.6",
+            "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.6.tgz",
+            "integrity": "sha512-G4yqdVlhr6YhzLXFKy5F7HtRBU8Y23+iWy7UKthMq/OSQnL1hbsoeXESQ2LY8zEDlknipDG3nRGhUC9tkwvy/w==",
+            "requires": {
+                "@types/babel-types": "*"
+            }
+        },
+        "@types/bcrypt-nodejs": {
+            "version": "0.0.30",
+            "resolved": "https://registry.npmjs.org/@types/bcrypt-nodejs/-/bcrypt-nodejs-0.0.30.tgz",
+            "integrity": "sha1-TN2WtJKTs5MhIuS34pVD415rrlg=",
+            "dev": true
+        },
+        "@types/bezier-js": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/@types/bezier-js/-/bezier-js-4.1.0.tgz",
+            "integrity": "sha512-ElU16s8E6Pr6magp8ihwH1O8pbUJASbMND/qgUc9RsLmP3lMLHiDMRXdjtaObwW5GPtOVYOsXDUIhTIluT+yaw=="
         },
         "@types/bluebird": {
             "version": "3.5.38",
@@ -3774,6 +5423,17 @@
                 }
             }
         },
+        "@types/cacheable-request": {
+            "version": "6.0.3",
+            "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
+            "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
+            "requires": {
+                "@types/http-cache-semantics": "*",
+                "@types/keyv": "^3.1.4",
+                "@types/node": "*",
+                "@types/responselike": "^1.0.0"
+            }
+        },
         "@types/caseless": {
             "version": "0.12.2",
             "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz",
@@ -4355,6 +6015,14 @@
             "integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==",
             "dev": true
         },
+        "@types/keyv": {
+            "version": "3.1.4",
+            "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
+            "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
+            "requires": {
+                "@types/node": "*"
+            }
+        },
         "@types/libxmljs": {
             "version": "0.18.8",
             "resolved": "https://registry.npmjs.org/@types/libxmljs/-/libxmljs-0.18.8.tgz",
@@ -4370,6 +6038,23 @@
             "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==",
             "dev": true
         },
+        "@types/mapbox-gl": {
+            "version": "2.7.19",
+            "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-2.7.19.tgz",
+            "integrity": "sha512-pkRdlhQJNbtwcKJSVC4uuOA6M3OlOObIMhNecqHB991oeaDF5XkjSIRaTE0lh5p4KClptSCxW6MBiREVRHrl2A==",
+            "requires": {
+                "@types/geojson": "*"
+            }
+        },
+        "@types/mapbox__mapbox-gl-geocoder": {
+            "version": "4.7.7",
+            "resolved": "https://registry.npmjs.org/@types/mapbox__mapbox-gl-geocoder/-/mapbox__mapbox-gl-geocoder-4.7.7.tgz",
+            "integrity": "sha512-RFUDSDnFoCBapQWfGdgNGkB76drJ2kVG9tH0ottQtDpTW3KPtdQobw0lvvJK4VIMDEKIt/ssEyyLvY1Tja7MSw==",
+            "requires": {
+                "@types/geojson": "*",
+                "@types/mapbox-gl": "*"
+            }
+        },
         "@types/mdast": {
             "version": "3.0.10",
             "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
@@ -4390,6 +6075,11 @@
             "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
             "dev": true
         },
+        "@types/minimist": {
+            "version": "1.2.5",
+            "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz",
+            "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag=="
+        },
         "@types/mobile-detect": {
             "version": "1.3.4",
             "resolved": "https://registry.npmjs.org/@types/mobile-detect/-/mobile-detect-1.3.4.tgz",
@@ -4470,6 +6160,11 @@
                 "@types/node": "*"
             }
         },
+        "@types/normalize-package-data": {
+            "version": "2.4.4",
+            "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
+            "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA=="
+        },
         "@types/oauth": {
             "version": "0.9.1",
             "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.1.tgz",
@@ -4867,6 +6562,14 @@
                 "@types/request": "*"
             }
         },
+        "@types/responselike": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
+            "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==",
+            "requires": {
+                "@types/node": "*"
+            }
+        },
         "@types/reveal": {
             "version": "3.3.33",
             "resolved": "https://registry.npmjs.org/@types/reveal/-/reveal-3.3.33.tgz",
@@ -5445,6 +7148,30 @@
                 }
             }
         },
+        "agentkeepalive": {
+            "version": "4.5.0",
+            "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
+            "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
+            "requires": {
+                "humanize-ms": "^1.2.1"
+            }
+        },
+        "aggregate-error": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
+            "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+            "requires": {
+                "clean-stack": "^2.0.0",
+                "indent-string": "^4.0.0"
+            },
+            "dependencies": {
+                "indent-string": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+                    "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="
+                }
+            }
+        },
         "ajv": {
             "version": "6.12.6",
             "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -5662,6 +7389,11 @@
                 }
             }
         },
+        "archy": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
+            "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw=="
+        },
         "are-we-there-yet": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
@@ -6418,6 +8150,11 @@
                 }
             }
         },
+        "base-64": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
+            "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
+        },
         "base16": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz",
@@ -6487,10 +8224,37 @@
             "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz",
             "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig=="
         },
-        "binary-extensions": {
-            "version": "1.13.1",
-            "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
-            "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw=="
+        "bin-links": {
+            "version": "4.0.3",
+            "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.3.tgz",
+            "integrity": "sha512-obsRaULtJurnfox/MDwgq6Yo9kzbv1CPTk/1/s7Z/61Lezc8IKkFCOXNeVLXz0456WRzBQmSsDWlai2tIhBsfA==",
+            "requires": {
+                "cmd-shim": "^6.0.0",
+                "npm-normalize-package-bin": "^3.0.0",
+                "read-cmd-shim": "^4.0.0",
+                "write-file-atomic": "^5.0.0"
+            },
+            "dependencies": {
+                "signal-exit": {
+                    "version": "4.1.0",
+                    "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+                    "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="
+                },
+                "write-file-atomic": {
+                    "version": "5.0.1",
+                    "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
+                    "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
+                    "requires": {
+                        "imurmurhash": "^0.1.4",
+                        "signal-exit": "^4.0.1"
+                    }
+                }
+            }
+        },
+        "binary-extensions": {
+            "version": "1.13.1",
+            "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
+            "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw=="
         },
         "bindings": {
             "version": "1.5.0",
@@ -6967,6 +8731,16 @@
                         "@types/react": "*"
                     }
                 },
+                "abbrev": {
+                    "version": "2.0.0",
+                    "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
+                    "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="
+                },
+                "ansi-regex": {
+                    "version": "5.0.1",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                },
                 "anymatch": {
                     "version": "3.1.3",
                     "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
@@ -6977,6 +8751,15 @@
                         "picomatch": "^2.0.4"
                     }
                 },
+                "are-we-there-yet": {
+                    "version": "3.0.1",
+                    "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz",
+                    "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==",
+                    "requires": {
+                        "delegates": "^1.0.0",
+                        "readable-stream": "^3.6.0"
+                    }
+                },
                 "babel-plugin-macros": {
                     "version": "3.1.0",
                     "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
@@ -6993,6 +8776,14 @@
                     "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
                     "optional": true
                 },
+                "brace-expansion": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+                    "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+                    "requires": {
+                        "balanced-match": "^1.0.0"
+                    }
+                },
                 "braces": {
                     "version": "3.0.2",
                     "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
@@ -7002,6 +8793,53 @@
                         "fill-range": "^7.0.1"
                     }
                 },
+                "buffer": {
+                    "version": "6.0.3",
+                    "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+                    "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+                    "requires": {
+                        "base64-js": "^1.3.1",
+                        "ieee754": "^1.2.1"
+                    }
+                },
+                "cacache": {
+                    "version": "17.1.3",
+                    "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.3.tgz",
+                    "integrity": "sha512-jAdjGxmPxZh0IipMdR7fK/4sDSrHMLUV0+GvVUsjwyGNKHsh79kW/otg+GkbXwl6Uzvy9wsvHOX4nUoWldeZMg==",
+                    "requires": {
+                        "@npmcli/fs": "^3.1.0",
+                        "fs-minipass": "^3.0.0",
+                        "glob": "^10.2.2",
+                        "lru-cache": "^7.7.1",
+                        "minipass": "^5.0.0",
+                        "minipass-collect": "^1.0.2",
+                        "minipass-flush": "^1.0.5",
+                        "minipass-pipeline": "^1.2.4",
+                        "p-map": "^4.0.0",
+                        "ssri": "^10.0.0",
+                        "tar": "^6.1.11",
+                        "unique-filename": "^3.0.0"
+                    },
+                    "dependencies": {
+                        "glob": {
+                            "version": "10.3.10",
+                            "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
+                            "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+                            "requires": {
+                                "foreground-child": "^3.1.0",
+                                "jackspeak": "^2.3.5",
+                                "minimatch": "^9.0.1",
+                                "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
+                                "path-scurry": "^1.10.1"
+                            }
+                        }
+                    }
+                },
+                "chalk": {
+                    "version": "5.3.0",
+                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+                    "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="
+                },
                 "chokidar": {
                     "version": "3.5.3",
                     "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -7018,6 +8856,11 @@
                         "readdirp": "~3.6.0"
                     }
                 },
+                "ci-info": {
+                    "version": "3.8.0",
+                    "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
+                    "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw=="
+                },
                 "clsx": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
@@ -7077,11 +8920,21 @@
                     "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
                     "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
                 },
+                "emoji-regex": {
+                    "version": "8.0.0",
+                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                    "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+                },
                 "escape-string-regexp": {
                     "version": "4.0.0",
                     "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
                     "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
                 },
+                "fastest-levenshtein": {
+                    "version": "1.0.16",
+                    "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
+                    "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg=="
+                },
                 "fill-range": {
                     "version": "7.0.1",
                     "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -7091,12 +8944,35 @@
                         "to-regex-range": "^5.0.1"
                     }
                 },
+                "fs-minipass": {
+                    "version": "3.0.2",
+                    "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.2.tgz",
+                    "integrity": "sha512-2GAfyfoaCDRrM6jaOS3UsBts8yJ55VioXdWcOL7dK9zdAuKT71+WBA4ifnNYqVjYv+4SsPxjK0JT4yIIn4cA/g==",
+                    "requires": {
+                        "minipass": "^5.0.0"
+                    }
+                },
                 "fsevents": {
                     "version": "2.3.3",
                     "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
                     "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
                     "optional": true
                 },
+                "gauge": {
+                    "version": "4.0.4",
+                    "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz",
+                    "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==",
+                    "requires": {
+                        "aproba": "^1.0.3 || ^2.0.0",
+                        "color-support": "^1.1.3",
+                        "console-control-strings": "^1.1.0",
+                        "has-unicode": "^2.0.1",
+                        "signal-exit": "^3.0.7",
+                        "string-width": "^4.2.3",
+                        "strip-ansi": "^6.0.1",
+                        "wide-align": "^1.1.5"
+                    }
+                },
                 "glob-parent": {
                     "version": "5.1.2",
                     "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
@@ -7106,6 +8982,24 @@
                         "is-glob": "^4.0.1"
                     }
                 },
+                "graceful-fs": {
+                    "version": "4.2.11",
+                    "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+                    "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
+                },
+                "hosted-git-info": {
+                    "version": "6.1.1",
+                    "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz",
+                    "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==",
+                    "requires": {
+                        "lru-cache": "^7.5.1"
+                    }
+                },
+                "ini": {
+                    "version": "4.1.1",
+                    "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz",
+                    "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g=="
+                },
                 "is-binary-path": {
                     "version": "2.1.0",
                     "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -7115,12 +9009,27 @@
                         "binary-extensions": "^2.0.0"
                     }
                 },
+                "is-fullwidth-code-point": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+                    "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+                },
                 "is-number": {
                     "version": "7.0.0",
                     "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
                     "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
                     "optional": true
                 },
+                "json-parse-even-better-errors": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz",
+                    "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA=="
+                },
+                "lru-cache": {
+                    "version": "7.18.3",
+                    "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+                    "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="
+                },
                 "make-dir": {
                     "version": "2.1.0",
                     "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
@@ -7130,1923 +9039,277 @@
                         "semver": "^5.6.0"
                     }
                 },
+                "minimatch": {
+                    "version": "9.0.3",
+                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+                    "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+                    "requires": {
+                        "brace-expansion": "^2.0.1"
+                    }
+                },
+                "minipass": {
+                    "version": "5.0.0",
+                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+                    "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="
+                },
+                "mkdirp": {
+                    "version": "1.0.4",
+                    "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+                    "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
+                },
+                "ms": {
+                    "version": "2.1.3",
+                    "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+                    "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+                },
                 "nanoid": {
                     "version": "3.3.6",
                     "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
                     "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA=="
                 },
-                "npm": {
-                    "version": "9.8.1",
-                    "resolved": "https://registry.npmjs.org/npm/-/npm-9.8.1.tgz",
-                    "integrity": "sha512-AfDvThQzsIXhYgk9zhbk5R+lh811lKkLAeQMMhSypf1BM7zUafeIIBzMzespeuVEJ0+LvY36oRQYf7IKLzU3rw==",
+                "node-gyp": {
+                    "version": "9.4.0",
+                    "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.0.tgz",
+                    "integrity": "sha512-dMXsYP6gc9rRbejLXmTbVRYjAHw7ppswsKyMxuxJxxOHzluIO1rGp9TOQgjFJ+2MCqcOcQTOPB/8Xwhr+7s4Eg==",
                     "requires": {
-                        "@isaacs/string-locale-compare": "^1.1.0",
-                        "@npmcli/arborist": "^6.3.0",
-                        "@npmcli/config": "^6.2.1",
-                        "@npmcli/fs": "^3.1.0",
-                        "@npmcli/map-workspaces": "^3.0.4",
-                        "@npmcli/package-json": "^4.0.1",
-                        "@npmcli/promise-spawn": "^6.0.2",
-                        "@npmcli/run-script": "^6.0.2",
-                        "abbrev": "^2.0.0",
-                        "archy": "~1.0.0",
-                        "cacache": "^17.1.3",
-                        "chalk": "^5.3.0",
-                        "ci-info": "^3.8.0",
-                        "cli-columns": "^4.0.0",
-                        "cli-table3": "^0.6.3",
-                        "columnify": "^1.6.0",
-                        "fastest-levenshtein": "^1.0.16",
-                        "fs-minipass": "^3.0.2",
-                        "glob": "^10.2.7",
-                        "graceful-fs": "^4.2.11",
-                        "hosted-git-info": "^6.1.1",
-                        "ini": "^4.1.1",
-                        "init-package-json": "^5.0.0",
-                        "is-cidr": "^4.0.2",
-                        "json-parse-even-better-errors": "^3.0.0",
-                        "libnpmaccess": "^7.0.2",
-                        "libnpmdiff": "^5.0.19",
-                        "libnpmexec": "^6.0.3",
-                        "libnpmfund": "^4.0.19",
-                        "libnpmhook": "^9.0.3",
-                        "libnpmorg": "^5.0.4",
-                        "libnpmpack": "^5.0.19",
-                        "libnpmpublish": "^7.5.0",
-                        "libnpmsearch": "^6.0.2",
-                        "libnpmteam": "^5.0.3",
-                        "libnpmversion": "^4.0.2",
-                        "make-fetch-happen": "^11.1.1",
-                        "minimatch": "^9.0.3",
-                        "minipass": "^5.0.0",
-                        "minipass-pipeline": "^1.2.4",
-                        "ms": "^2.1.2",
-                        "node-gyp": "^9.4.0",
-                        "nopt": "^7.2.0",
-                        "npm-audit-report": "^5.0.0",
-                        "npm-install-checks": "^6.1.1",
-                        "npm-package-arg": "^10.1.0",
-                        "npm-pick-manifest": "^8.0.1",
-                        "npm-profile": "^7.0.1",
-                        "npm-registry-fetch": "^14.0.5",
-                        "npm-user-validate": "^2.0.0",
-                        "npmlog": "^7.0.1",
-                        "p-map": "^4.0.0",
-                        "pacote": "^15.2.0",
-                        "parse-conflict-json": "^3.0.1",
-                        "proc-log": "^3.0.0",
-                        "qrcode-terminal": "^0.12.0",
-                        "read": "^2.1.0",
-                        "semver": "^7.5.4",
-                        "sigstore": "^1.7.0",
-                        "ssri": "^10.0.4",
-                        "supports-color": "^9.4.0",
-                        "tar": "^6.1.15",
-                        "text-table": "~0.2.0",
-                        "tiny-relative-date": "^1.3.0",
-                        "treeverse": "^3.0.0",
-                        "validate-npm-package-name": "^5.0.0",
-                        "which": "^3.0.1",
-                        "write-file-atomic": "^5.0.1"
+                        "env-paths": "^2.2.0",
+                        "exponential-backoff": "^3.1.1",
+                        "glob": "^7.1.4",
+                        "graceful-fs": "^4.2.6",
+                        "make-fetch-happen": "^11.0.3",
+                        "nopt": "^6.0.0",
+                        "npmlog": "^6.0.0",
+                        "rimraf": "^3.0.2",
+                        "semver": "^7.3.5",
+                        "tar": "^6.1.2",
+                        "which": "^2.0.2"
                     },
                     "dependencies": {
-                        "@colors/colors": {
-                            "version": "1.5.0",
-                            "bundled": true,
-                            "optional": true
-                        },
-                        "@isaacs/cliui": {
-                            "version": "8.0.2",
-                            "bundled": true,
-                            "requires": {
-                                "string-width": "^5.1.2",
-                                "string-width-cjs": "npm:string-width@^4.2.0",
-                                "strip-ansi": "^7.0.1",
-                                "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
-                                "wrap-ansi": "^8.1.0",
-                                "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
-                            },
-                            "dependencies": {
-                                "ansi-regex": {
-                                    "version": "6.0.1",
-                                    "bundled": true
-                                },
-                                "emoji-regex": {
-                                    "version": "9.2.2",
-                                    "bundled": true
-                                },
-                                "string-width": {
-                                    "version": "5.1.2",
-                                    "bundled": true,
-                                    "requires": {
-                                        "eastasianwidth": "^0.2.0",
-                                        "emoji-regex": "^9.2.2",
-                                        "strip-ansi": "^7.0.1"
-                                    }
-                                },
-                                "string-width-cjs": {
-                                    "version": "npm:string-width@4.2.3",
-                                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-                                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-                                    "requires": {
-                                        "emoji-regex": "^8.0.0",
-                                        "is-fullwidth-code-point": "^3.0.0",
-                                        "strip-ansi": "^6.0.1"
-                                    },
-                                    "dependencies": {
-                                        "ansi-regex": {
-                                            "version": "5.0.1",
-                                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-                                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-                                        },
-                                        "emoji-regex": {
-                                            "version": "8.0.0",
-                                            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-                                            "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
-                                        },
-                                        "strip-ansi": {
-                                            "version": "6.0.1",
-                                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-                                            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-                                            "requires": {
-                                                "ansi-regex": "^5.0.1"
-                                            }
-                                        }
-                                    }
-                                },
-                                "strip-ansi": {
-                                    "version": "7.1.0",
-                                    "bundled": true,
-                                    "requires": {
-                                        "ansi-regex": "^6.0.1"
-                                    }
-                                },
-                                "strip-ansi-cjs": {
-                                    "version": "npm:strip-ansi@6.0.1",
-                                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-                                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-                                    "requires": {
-                                        "ansi-regex": "^5.0.1"
-                                    },
-                                    "dependencies": {
-                                        "ansi-regex": {
-                                            "version": "5.0.1",
-                                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-                                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-                                        }
-                                    }
-                                },
-                                "wrap-ansi-cjs": {
-                                    "version": "npm:wrap-ansi@7.0.0",
-                                    "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
-                                    "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
-                                    "requires": {
-                                        "ansi-styles": "^4.0.0",
-                                        "string-width": "^4.1.0",
-                                        "strip-ansi": "^6.0.0"
-                                    },
-                                    "dependencies": {
-                                        "ansi-regex": {
-                                            "version": "5.0.1",
-                                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-                                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-                                        },
-                                        "emoji-regex": {
-                                            "version": "8.0.0",
-                                            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-                                            "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
-                                        },
-                                        "string-width": {
-                                            "version": "4.2.3",
-                                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-                                            "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-                                            "requires": {
-                                                "emoji-regex": "^8.0.0",
-                                                "is-fullwidth-code-point": "^3.0.0",
-                                                "strip-ansi": "^6.0.1"
-                                            }
-                                        },
-                                        "strip-ansi": {
-                                            "version": "6.0.1",
-                                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-                                            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-                                            "requires": {
-                                                "ansi-regex": "^5.0.1"
-                                            }
-                                        }
-                                    }
-                                }
-                            }
-                        },
-                        "@isaacs/string-locale-compare": {
-                            "version": "1.1.0",
-                            "bundled": true
-                        },
-                        "@npmcli/arborist": {
-                            "version": "6.3.0",
-                            "bundled": true,
-                            "requires": {
-                                "@isaacs/string-locale-compare": "^1.1.0",
-                                "@npmcli/fs": "^3.1.0",
-                                "@npmcli/installed-package-contents": "^2.0.2",
-                                "@npmcli/map-workspaces": "^3.0.2",
-                                "@npmcli/metavuln-calculator": "^5.0.0",
-                                "@npmcli/name-from-folder": "^2.0.0",
-                                "@npmcli/node-gyp": "^3.0.0",
-                                "@npmcli/package-json": "^4.0.0",
-                                "@npmcli/query": "^3.0.0",
-                                "@npmcli/run-script": "^6.0.0",
-                                "bin-links": "^4.0.1",
-                                "cacache": "^17.0.4",
-                                "common-ancestor-path": "^1.0.1",
-                                "hosted-git-info": "^6.1.1",
-                                "json-parse-even-better-errors": "^3.0.0",
-                                "json-stringify-nice": "^1.1.4",
-                                "minimatch": "^9.0.0",
-                                "nopt": "^7.0.0",
-                                "npm-install-checks": "^6.0.0",
-                                "npm-package-arg": "^10.1.0",
-                                "npm-pick-manifest": "^8.0.1",
-                                "npm-registry-fetch": "^14.0.3",
-                                "npmlog": "^7.0.1",
-                                "pacote": "^15.0.8",
-                                "parse-conflict-json": "^3.0.0",
-                                "proc-log": "^3.0.0",
-                                "promise-all-reject-late": "^1.0.0",
-                                "promise-call-limit": "^1.0.2",
-                                "read-package-json-fast": "^3.0.2",
-                                "semver": "^7.3.7",
-                                "ssri": "^10.0.1",
-                                "treeverse": "^3.0.0",
-                                "walk-up-path": "^3.0.1"
-                            }
-                        },
-                        "@npmcli/config": {
-                            "version": "6.2.1",
-                            "bundled": true,
-                            "requires": {
-                                "@npmcli/map-workspaces": "^3.0.2",
-                                "ci-info": "^3.8.0",
-                                "ini": "^4.1.0",
-                                "nopt": "^7.0.0",
-                                "proc-log": "^3.0.0",
-                                "read-package-json-fast": "^3.0.2",
-                                "semver": "^7.3.5",
-                                "walk-up-path": "^3.0.1"
-                            }
-                        },
-                        "@npmcli/disparity-colors": {
-                            "version": "3.0.0",
-                            "bundled": true,
-                            "requires": {
-                                "ansi-styles": "^4.3.0"
-                            }
-                        },
-                        "@npmcli/fs": {
-                            "version": "3.1.0",
-                            "bundled": true,
-                            "requires": {
-                                "semver": "^7.3.5"
-                            }
-                        },
-                        "@npmcli/git": {
-                            "version": "4.1.0",
-                            "bundled": true,
-                            "requires": {
-                                "@npmcli/promise-spawn": "^6.0.0",
-                                "lru-cache": "^7.4.4",
-                                "npm-pick-manifest": "^8.0.0",
-                                "proc-log": "^3.0.0",
-                                "promise-inflight": "^1.0.1",
-                                "promise-retry": "^2.0.1",
-                                "semver": "^7.3.5",
-                                "which": "^3.0.0"
-                            }
-                        },
-                        "@npmcli/installed-package-contents": {
-                            "version": "2.0.2",
-                            "bundled": true,
-                            "requires": {
-                                "npm-bundled": "^3.0.0",
-                                "npm-normalize-package-bin": "^3.0.0"
-                            }
-                        },
-                        "@npmcli/map-workspaces": {
-                            "version": "3.0.4",
-                            "bundled": true,
-                            "requires": {
-                                "@npmcli/name-from-folder": "^2.0.0",
-                                "glob": "^10.2.2",
-                                "minimatch": "^9.0.0",
-                                "read-package-json-fast": "^3.0.0"
-                            }
-                        },
-                        "@npmcli/metavuln-calculator": {
-                            "version": "5.0.1",
-                            "bundled": true,
-                            "requires": {
-                                "cacache": "^17.0.0",
-                                "json-parse-even-better-errors": "^3.0.0",
-                                "pacote": "^15.0.0",
-                                "semver": "^7.3.5"
-                            }
-                        },
-                        "@npmcli/name-from-folder": {
-                            "version": "2.0.0",
-                            "bundled": true
-                        },
-                        "@npmcli/node-gyp": {
-                            "version": "3.0.0",
-                            "bundled": true
-                        },
-                        "@npmcli/package-json": {
-                            "version": "4.0.1",
-                            "bundled": true,
-                            "requires": {
-                                "@npmcli/git": "^4.1.0",
-                                "glob": "^10.2.2",
-                                "hosted-git-info": "^6.1.1",
-                                "json-parse-even-better-errors": "^3.0.0",
-                                "normalize-package-data": "^5.0.0",
-                                "proc-log": "^3.0.0",
-                                "semver": "^7.5.3"
-                            }
-                        },
-                        "@npmcli/promise-spawn": {
-                            "version": "6.0.2",
-                            "bundled": true,
-                            "requires": {
-                                "which": "^3.0.0"
-                            }
-                        },
-                        "@npmcli/query": {
-                            "version": "3.0.0",
-                            "bundled": true,
-                            "requires": {
-                                "postcss-selector-parser": "^6.0.10"
-                            }
-                        },
-                        "@npmcli/run-script": {
-                            "version": "6.0.2",
-                            "bundled": true,
-                            "requires": {
-                                "@npmcli/node-gyp": "^3.0.0",
-                                "@npmcli/promise-spawn": "^6.0.0",
-                                "node-gyp": "^9.0.0",
-                                "read-package-json-fast": "^3.0.0",
-                                "which": "^3.0.0"
-                            }
-                        },
-                        "@pkgjs/parseargs": {
-                            "version": "0.11.0",
-                            "bundled": true,
-                            "optional": true
-                        },
-                        "@sigstore/protobuf-specs": {
-                            "version": "0.1.0",
-                            "bundled": true
-                        },
-                        "@sigstore/tuf": {
-                            "version": "1.0.2",
-                            "bundled": true,
-                            "requires": {
-                                "@sigstore/protobuf-specs": "^0.1.0",
-                                "tuf-js": "^1.1.7"
-                            }
-                        },
-                        "@tootallnate/once": {
-                            "version": "2.0.0",
-                            "bundled": true
-                        },
-                        "@tufjs/canonical-json": {
-                            "version": "1.0.0",
-                            "bundled": true
-                        },
-                        "@tufjs/models": {
-                            "version": "1.0.4",
-                            "bundled": true,
-                            "requires": {
-                                "@tufjs/canonical-json": "1.0.0",
-                                "minimatch": "^9.0.0"
-                            }
-                        },
                         "abbrev": {
-                            "version": "2.0.0",
-                            "bundled": true
-                        },
-                        "abort-controller": {
-                            "version": "3.0.0",
-                            "bundled": true,
-                            "requires": {
-                                "event-target-shim": "^5.0.0"
-                            }
-                        },
-                        "agent-base": {
-                            "version": "6.0.2",
-                            "bundled": true,
-                            "requires": {
-                                "debug": "4"
-                            }
-                        },
-                        "agentkeepalive": {
-                            "version": "4.3.0",
-                            "bundled": true,
-                            "requires": {
-                                "debug": "^4.1.0",
-                                "depd": "^2.0.0",
-                                "humanize-ms": "^1.2.1"
-                            }
-                        },
-                        "aggregate-error": {
-                            "version": "3.1.0",
-                            "bundled": true,
-                            "requires": {
-                                "clean-stack": "^2.0.0",
-                                "indent-string": "^4.0.0"
-                            }
-                        },
-                        "ansi-regex": {
-                            "version": "5.0.1",
-                            "bundled": true
-                        },
-                        "ansi-styles": {
-                            "version": "4.3.0",
-                            "bundled": true,
-                            "requires": {
-                                "color-convert": "^2.0.1"
-                            }
-                        },
-                        "aproba": {
-                            "version": "2.0.0",
-                            "bundled": true
-                        },
-                        "archy": {
-                            "version": "1.0.0",
-                            "bundled": true
-                        },
-                        "are-we-there-yet": {
-                            "version": "4.0.0",
-                            "bundled": true,
-                            "requires": {
-                                "delegates": "^1.0.0",
-                                "readable-stream": "^4.1.0"
-                            }
-                        },
-                        "balanced-match": {
-                            "version": "1.0.2",
-                            "bundled": true
-                        },
-                        "base64-js": {
-                            "version": "1.5.1",
-                            "bundled": true
-                        },
-                        "bin-links": {
-                            "version": "4.0.2",
-                            "bundled": true,
-                            "requires": {
-                                "cmd-shim": "^6.0.0",
-                                "npm-normalize-package-bin": "^3.0.0",
-                                "read-cmd-shim": "^4.0.0",
-                                "write-file-atomic": "^5.0.0"
-                            }
-                        },
-                        "binary-extensions": {
-                            "version": "2.2.0",
-                            "bundled": true
-                        },
-                        "brace-expansion": {
-                            "version": "2.0.1",
-                            "bundled": true,
-                            "requires": {
-                                "balanced-match": "^1.0.0"
-                            }
-                        },
-                        "buffer": {
-                            "version": "6.0.3",
-                            "bundled": true,
-                            "requires": {
-                                "base64-js": "^1.3.1",
-                                "ieee754": "^1.2.1"
-                            }
-                        },
-                        "builtins": {
-                            "version": "5.0.1",
-                            "bundled": true,
-                            "requires": {
-                                "semver": "^7.0.0"
-                            }
-                        },
-                        "cacache": {
-                            "version": "17.1.3",
-                            "bundled": true,
-                            "requires": {
-                                "@npmcli/fs": "^3.1.0",
-                                "fs-minipass": "^3.0.0",
-                                "glob": "^10.2.2",
-                                "lru-cache": "^7.7.1",
-                                "minipass": "^5.0.0",
-                                "minipass-collect": "^1.0.2",
-                                "minipass-flush": "^1.0.5",
-                                "minipass-pipeline": "^1.2.4",
-                                "p-map": "^4.0.0",
-                                "ssri": "^10.0.0",
-                                "tar": "^6.1.11",
-                                "unique-filename": "^3.0.0"
-                            }
-                        },
-                        "chalk": {
-                            "version": "5.3.0",
-                            "bundled": true
-                        },
-                        "chownr": {
-                            "version": "2.0.0",
-                            "bundled": true
-                        },
-                        "ci-info": {
-                            "version": "3.8.0",
-                            "bundled": true
-                        },
-                        "cidr-regex": {
-                            "version": "3.1.1",
-                            "bundled": true,
-                            "requires": {
-                                "ip-regex": "^4.1.0"
-                            }
-                        },
-                        "clean-stack": {
-                            "version": "2.2.0",
-                            "bundled": true
-                        },
-                        "cli-columns": {
-                            "version": "4.0.0",
-                            "bundled": true,
-                            "requires": {
-                                "string-width": "^4.2.3",
-                                "strip-ansi": "^6.0.1"
-                            }
-                        },
-                        "cli-table3": {
-                            "version": "0.6.3",
-                            "bundled": true,
-                            "requires": {
-                                "@colors/colors": "1.5.0",
-                                "string-width": "^4.2.0"
-                            }
-                        },
-                        "clone": {
-                            "version": "1.0.4",
-                            "bundled": true
-                        },
-                        "cmd-shim": {
-                            "version": "6.0.1",
-                            "bundled": true
-                        },
-                        "color-convert": {
-                            "version": "2.0.1",
-                            "bundled": true,
-                            "requires": {
-                                "color-name": "~1.1.4"
-                            }
-                        },
-                        "color-name": {
-                            "version": "1.1.4",
-                            "bundled": true
-                        },
-                        "color-support": {
-                            "version": "1.1.3",
-                            "bundled": true
-                        },
-                        "columnify": {
-                            "version": "1.6.0",
-                            "bundled": true,
-                            "requires": {
-                                "strip-ansi": "^6.0.1",
-                                "wcwidth": "^1.0.0"
-                            }
-                        },
-                        "common-ancestor-path": {
-                            "version": "1.0.1",
-                            "bundled": true
-                        },
-                        "concat-map": {
-                            "version": "0.0.1",
-                            "bundled": true
-                        },
-                        "console-control-strings": {
-                            "version": "1.1.0",
-                            "bundled": true
-                        },
-                        "cross-spawn": {
-                            "version": "7.0.3",
-                            "bundled": true,
-                            "requires": {
-                                "path-key": "^3.1.0",
-                                "shebang-command": "^2.0.0",
-                                "which": "^2.0.1"
-                            },
-                            "dependencies": {
-                                "which": {
-                                    "version": "2.0.2",
-                                    "bundled": true,
-                                    "requires": {
-                                        "isexe": "^2.0.0"
-                                    }
-                                }
-                            }
-                        },
-                        "cssesc": {
-                            "version": "3.0.0",
-                            "bundled": true
-                        },
-                        "debug": {
-                            "version": "4.3.4",
-                            "bundled": true,
-                            "requires": {
-                                "ms": "2.1.2"
-                            },
-                            "dependencies": {
-                                "ms": {
-                                    "version": "2.1.2",
-                                    "bundled": true
-                                }
-                            }
-                        },
-                        "defaults": {
-                            "version": "1.0.4",
-                            "bundled": true,
-                            "requires": {
-                                "clone": "^1.0.2"
-                            }
-                        },
-                        "delegates": {
-                            "version": "1.0.0",
-                            "bundled": true
-                        },
-                        "depd": {
-                            "version": "2.0.0",
-                            "bundled": true
-                        },
-                        "diff": {
-                            "version": "5.1.0",
-                            "bundled": true
-                        },
-                        "eastasianwidth": {
-                            "version": "0.2.0",
-                            "bundled": true
-                        },
-                        "emoji-regex": {
-                            "version": "8.0.0",
-                            "bundled": true
-                        },
-                        "encoding": {
-                            "version": "0.1.13",
-                            "bundled": true,
-                            "optional": true,
-                            "requires": {
-                                "iconv-lite": "^0.6.2"
-                            }
-                        },
-                        "env-paths": {
-                            "version": "2.2.1",
-                            "bundled": true
-                        },
-                        "err-code": {
-                            "version": "2.0.3",
-                            "bundled": true
-                        },
-                        "event-target-shim": {
-                            "version": "5.0.1",
-                            "bundled": true
-                        },
-                        "events": {
-                            "version": "3.3.0",
-                            "bundled": true
-                        },
-                        "exponential-backoff": {
-                            "version": "3.1.1",
-                            "bundled": true
-                        },
-                        "fastest-levenshtein": {
-                            "version": "1.0.16",
-                            "bundled": true
-                        },
-                        "foreground-child": {
-                            "version": "3.1.1",
-                            "bundled": true,
-                            "requires": {
-                                "cross-spawn": "^7.0.0",
-                                "signal-exit": "^4.0.1"
-                            }
-                        },
-                        "fs-minipass": {
-                            "version": "3.0.2",
-                            "bundled": true,
-                            "requires": {
-                                "minipass": "^5.0.0"
-                            }
-                        },
-                        "fs.realpath": {
-                            "version": "1.0.0",
-                            "bundled": true
-                        },
-                        "function-bind": {
-                            "version": "1.1.1",
-                            "bundled": true
-                        },
-                        "gauge": {
-                            "version": "5.0.1",
-                            "bundled": true,
-                            "requires": {
-                                "aproba": "^1.0.3 || ^2.0.0",
-                                "color-support": "^1.1.3",
-                                "console-control-strings": "^1.1.0",
-                                "has-unicode": "^2.0.1",
-                                "signal-exit": "^4.0.1",
-                                "string-width": "^4.2.3",
-                                "strip-ansi": "^6.0.1",
-                                "wide-align": "^1.1.5"
-                            }
-                        },
-                        "glob": {
-                            "version": "10.2.7",
-                            "bundled": true,
-                            "requires": {
-                                "foreground-child": "^3.1.0",
-                                "jackspeak": "^2.0.3",
-                                "minimatch": "^9.0.1",
-                                "minipass": "^5.0.0 || ^6.0.2",
-                                "path-scurry": "^1.7.0"
-                            }
-                        },
-                        "graceful-fs": {
-                            "version": "4.2.11",
-                            "bundled": true
-                        },
-                        "has": {
-                            "version": "1.0.3",
-                            "bundled": true,
-                            "requires": {
-                                "function-bind": "^1.1.1"
-                            }
-                        },
-                        "has-unicode": {
-                            "version": "2.0.1",
-                            "bundled": true
-                        },
-                        "hosted-git-info": {
-                            "version": "6.1.1",
-                            "bundled": true,
-                            "requires": {
-                                "lru-cache": "^7.5.1"
-                            }
-                        },
-                        "http-cache-semantics": {
-                            "version": "4.1.1",
-                            "bundled": true
-                        },
-                        "http-proxy-agent": {
-                            "version": "5.0.0",
-                            "bundled": true,
-                            "requires": {
-                                "@tootallnate/once": "2",
-                                "agent-base": "6",
-                                "debug": "4"
-                            }
-                        },
-                        "https-proxy-agent": {
-                            "version": "5.0.1",
-                            "bundled": true,
-                            "requires": {
-                                "agent-base": "6",
-                                "debug": "4"
-                            }
-                        },
-                        "humanize-ms": {
-                            "version": "1.2.1",
-                            "bundled": true,
-                            "requires": {
-                                "ms": "^2.0.0"
-                            }
-                        },
-                        "iconv-lite": {
-                            "version": "0.6.3",
-                            "bundled": true,
-                            "optional": true,
-                            "requires": {
-                                "safer-buffer": ">= 2.1.2 < 3.0.0"
-                            }
-                        },
-                        "ieee754": {
-                            "version": "1.2.1",
-                            "bundled": true
-                        },
-                        "ignore-walk": {
-                            "version": "6.0.3",
-                            "bundled": true,
-                            "requires": {
-                                "minimatch": "^9.0.0"
-                            }
-                        },
-                        "imurmurhash": {
-                            "version": "0.1.4",
-                            "bundled": true
-                        },
-                        "indent-string": {
-                            "version": "4.0.0",
-                            "bundled": true
-                        },
-                        "inflight": {
-                            "version": "1.0.6",
-                            "bundled": true,
-                            "requires": {
-                                "once": "^1.3.0",
-                                "wrappy": "1"
-                            }
-                        },
-                        "inherits": {
-                            "version": "2.0.4",
-                            "bundled": true
-                        },
-                        "ini": {
-                            "version": "4.1.1",
-                            "bundled": true
-                        },
-                        "init-package-json": {
-                            "version": "5.0.0",
-                            "bundled": true,
-                            "requires": {
-                                "npm-package-arg": "^10.0.0",
-                                "promzard": "^1.0.0",
-                                "read": "^2.0.0",
-                                "read-package-json": "^6.0.0",
-                                "semver": "^7.3.5",
-                                "validate-npm-package-license": "^3.0.4",
-                                "validate-npm-package-name": "^5.0.0"
-                            }
-                        },
-                        "ip": {
-                            "version": "2.0.0",
-                            "bundled": true
-                        },
-                        "ip-regex": {
-                            "version": "4.3.0",
-                            "bundled": true
-                        },
-                        "is-cidr": {
-                            "version": "4.0.2",
-                            "bundled": true,
-                            "requires": {
-                                "cidr-regex": "^3.1.1"
-                            }
-                        },
-                        "is-core-module": {
-                            "version": "2.12.1",
-                            "bundled": true,
-                            "requires": {
-                                "has": "^1.0.3"
-                            }
-                        },
-                        "is-fullwidth-code-point": {
-                            "version": "3.0.0",
-                            "bundled": true
-                        },
-                        "is-lambda": {
-                            "version": "1.0.1",
-                            "bundled": true
-                        },
-                        "isexe": {
-                            "version": "2.0.0",
-                            "bundled": true
-                        },
-                        "jackspeak": {
-                            "version": "2.2.1",
-                            "bundled": true,
-                            "requires": {
-                                "@isaacs/cliui": "^8.0.2",
-                                "@pkgjs/parseargs": "^0.11.0"
-                            }
-                        },
-                        "json-parse-even-better-errors": {
-                            "version": "3.0.0",
-                            "bundled": true
-                        },
-                        "json-stringify-nice": {
-                            "version": "1.1.4",
-                            "bundled": true
-                        },
-                        "jsonparse": {
-                            "version": "1.3.1",
-                            "bundled": true
-                        },
-                        "just-diff": {
-                            "version": "6.0.2",
-                            "bundled": true
-                        },
-                        "just-diff-apply": {
-                            "version": "5.5.0",
-                            "bundled": true
-                        },
-                        "libnpmaccess": {
-                            "version": "7.0.2",
-                            "bundled": true,
-                            "requires": {
-                                "npm-package-arg": "^10.1.0",
-                                "npm-registry-fetch": "^14.0.3"
-                            }
-                        },
-                        "libnpmdiff": {
-                            "version": "5.0.19",
-                            "bundled": true,
-                            "requires": {
-                                "@npmcli/arborist": "^6.3.0",
-                                "@npmcli/disparity-colors": "^3.0.0",
-                                "@npmcli/installed-package-contents": "^2.0.2",
-                                "binary-extensions": "^2.2.0",
-                                "diff": "^5.1.0",
-                                "minimatch": "^9.0.0",
-                                "npm-package-arg": "^10.1.0",
-                                "pacote": "^15.0.8",
-                                "tar": "^6.1.13"
-                            }
-                        },
-                        "libnpmexec": {
-                            "version": "6.0.3",
-                            "bundled": true,
-                            "requires": {
-                                "@npmcli/arborist": "^6.3.0",
-                                "@npmcli/run-script": "^6.0.0",
-                                "ci-info": "^3.7.1",
-                                "npm-package-arg": "^10.1.0",
-                                "npmlog": "^7.0.1",
-                                "pacote": "^15.0.8",
-                                "proc-log": "^3.0.0",
-                                "read": "^2.0.0",
-                                "read-package-json-fast": "^3.0.2",
-                                "semver": "^7.3.7",
-                                "walk-up-path": "^3.0.1"
-                            }
-                        },
-                        "libnpmfund": {
-                            "version": "4.0.19",
-                            "bundled": true,
-                            "requires": {
-                                "@npmcli/arborist": "^6.3.0"
-                            }
-                        },
-                        "libnpmhook": {
-                            "version": "9.0.3",
-                            "bundled": true,
-                            "requires": {
-                                "aproba": "^2.0.0",
-                                "npm-registry-fetch": "^14.0.3"
-                            }
-                        },
-                        "libnpmorg": {
-                            "version": "5.0.4",
-                            "bundled": true,
-                            "requires": {
-                                "aproba": "^2.0.0",
-                                "npm-registry-fetch": "^14.0.3"
-                            }
-                        },
-                        "libnpmpack": {
-                            "version": "5.0.19",
-                            "bundled": true,
-                            "requires": {
-                                "@npmcli/arborist": "^6.3.0",
-                                "@npmcli/run-script": "^6.0.0",
-                                "npm-package-arg": "^10.1.0",
-                                "pacote": "^15.0.8"
-                            }
-                        },
-                        "libnpmpublish": {
-                            "version": "7.5.0",
-                            "bundled": true,
-                            "requires": {
-                                "ci-info": "^3.6.1",
-                                "normalize-package-data": "^5.0.0",
-                                "npm-package-arg": "^10.1.0",
-                                "npm-registry-fetch": "^14.0.3",
-                                "proc-log": "^3.0.0",
-                                "semver": "^7.3.7",
-                                "sigstore": "^1.4.0",
-                                "ssri": "^10.0.1"
-                            }
-                        },
-                        "libnpmsearch": {
-                            "version": "6.0.2",
-                            "bundled": true,
-                            "requires": {
-                                "npm-registry-fetch": "^14.0.3"
-                            }
-                        },
-                        "libnpmteam": {
-                            "version": "5.0.3",
-                            "bundled": true,
-                            "requires": {
-                                "aproba": "^2.0.0",
-                                "npm-registry-fetch": "^14.0.3"
-                            }
-                        },
-                        "libnpmversion": {
-                            "version": "4.0.2",
-                            "bundled": true,
-                            "requires": {
-                                "@npmcli/git": "^4.0.1",
-                                "@npmcli/run-script": "^6.0.0",
-                                "json-parse-even-better-errors": "^3.0.0",
-                                "proc-log": "^3.0.0",
-                                "semver": "^7.3.7"
-                            }
-                        },
-                        "lru-cache": {
-                            "version": "7.18.3",
-                            "bundled": true
-                        },
-                        "make-fetch-happen": {
-                            "version": "11.1.1",
-                            "bundled": true,
-                            "requires": {
-                                "agentkeepalive": "^4.2.1",
-                                "cacache": "^17.0.0",
-                                "http-cache-semantics": "^4.1.1",
-                                "http-proxy-agent": "^5.0.0",
-                                "https-proxy-agent": "^5.0.0",
-                                "is-lambda": "^1.0.1",
-                                "lru-cache": "^7.7.1",
-                                "minipass": "^5.0.0",
-                                "minipass-fetch": "^3.0.0",
-                                "minipass-flush": "^1.0.5",
-                                "minipass-pipeline": "^1.2.4",
-                                "negotiator": "^0.6.3",
-                                "promise-retry": "^2.0.1",
-                                "socks-proxy-agent": "^7.0.0",
-                                "ssri": "^10.0.0"
-                            }
-                        },
-                        "minimatch": {
-                            "version": "9.0.3",
-                            "bundled": true,
-                            "requires": {
-                                "brace-expansion": "^2.0.1"
-                            }
-                        },
-                        "minipass": {
-                            "version": "5.0.0",
-                            "bundled": true
-                        },
-                        "minipass-collect": {
-                            "version": "1.0.2",
-                            "bundled": true,
-                            "requires": {
-                                "minipass": "^3.0.0"
-                            },
-                            "dependencies": {
-                                "minipass": {
-                                    "version": "3.3.6",
-                                    "bundled": true,
-                                    "requires": {
-                                        "yallist": "^4.0.0"
-                                    }
-                                }
-                            }
-                        },
-                        "minipass-fetch": {
-                            "version": "3.0.3",
-                            "bundled": true,
-                            "requires": {
-                                "encoding": "^0.1.13",
-                                "minipass": "^5.0.0",
-                                "minipass-sized": "^1.0.3",
-                                "minizlib": "^2.1.2"
-                            }
-                        },
-                        "minipass-flush": {
-                            "version": "1.0.5",
-                            "bundled": true,
-                            "requires": {
-                                "minipass": "^3.0.0"
-                            },
-                            "dependencies": {
-                                "minipass": {
-                                    "version": "3.3.6",
-                                    "bundled": true,
-                                    "requires": {
-                                        "yallist": "^4.0.0"
-                                    }
-                                }
-                            }
-                        },
-                        "minipass-json-stream": {
-                            "version": "1.0.1",
-                            "bundled": true,
-                            "requires": {
-                                "jsonparse": "^1.3.1",
-                                "minipass": "^3.0.0"
-                            },
-                            "dependencies": {
-                                "minipass": {
-                                    "version": "3.3.6",
-                                    "bundled": true,
-                                    "requires": {
-                                        "yallist": "^4.0.0"
-                                    }
-                                }
-                            }
-                        },
-                        "minipass-pipeline": {
-                            "version": "1.2.4",
-                            "bundled": true,
-                            "requires": {
-                                "minipass": "^3.0.0"
-                            },
-                            "dependencies": {
-                                "minipass": {
-                                    "version": "3.3.6",
-                                    "bundled": true,
-                                    "requires": {
-                                        "yallist": "^4.0.0"
-                                    }
-                                }
-                            }
-                        },
-                        "minipass-sized": {
-                            "version": "1.0.3",
-                            "bundled": true,
-                            "requires": {
-                                "minipass": "^3.0.0"
-                            },
-                            "dependencies": {
-                                "minipass": {
-                                    "version": "3.3.6",
-                                    "bundled": true,
-                                    "requires": {
-                                        "yallist": "^4.0.0"
-                                    }
-                                }
-                            }
-                        },
-                        "minizlib": {
-                            "version": "2.1.2",
-                            "bundled": true,
-                            "requires": {
-                                "minipass": "^3.0.0",
-                                "yallist": "^4.0.0"
-                            },
-                            "dependencies": {
-                                "minipass": {
-                                    "version": "3.3.6",
-                                    "bundled": true,
-                                    "requires": {
-                                        "yallist": "^4.0.0"
-                                    }
-                                }
-                            }
-                        },
-                        "mkdirp": {
-                            "version": "1.0.4",
-                            "bundled": true
-                        },
-                        "ms": {
-                            "version": "2.1.3",
-                            "bundled": true
-                        },
-                        "mute-stream": {
-                            "version": "1.0.0",
-                            "bundled": true
-                        },
-                        "negotiator": {
-                            "version": "0.6.3",
-                            "bundled": true
-                        },
-                        "node-gyp": {
-                            "version": "9.4.0",
-                            "bundled": true,
-                            "requires": {
-                                "env-paths": "^2.2.0",
-                                "exponential-backoff": "^3.1.1",
-                                "glob": "^7.1.4",
-                                "graceful-fs": "^4.2.6",
-                                "make-fetch-happen": "^11.0.3",
-                                "nopt": "^6.0.0",
-                                "npmlog": "^6.0.0",
-                                "rimraf": "^3.0.2",
-                                "semver": "^7.3.5",
-                                "tar": "^6.1.2",
-                                "which": "^2.0.2"
-                            },
-                            "dependencies": {
-                                "abbrev": {
-                                    "version": "1.1.1",
-                                    "bundled": true
-                                },
-                                "are-we-there-yet": {
-                                    "version": "3.0.1",
-                                    "bundled": true,
-                                    "requires": {
-                                        "delegates": "^1.0.0",
-                                        "readable-stream": "^3.6.0"
-                                    }
-                                },
-                                "brace-expansion": {
-                                    "version": "1.1.11",
-                                    "bundled": true,
-                                    "requires": {
-                                        "balanced-match": "^1.0.0",
-                                        "concat-map": "0.0.1"
-                                    }
-                                },
-                                "gauge": {
-                                    "version": "4.0.4",
-                                    "bundled": true,
-                                    "requires": {
-                                        "aproba": "^1.0.3 || ^2.0.0",
-                                        "color-support": "^1.1.3",
-                                        "console-control-strings": "^1.1.0",
-                                        "has-unicode": "^2.0.1",
-                                        "signal-exit": "^3.0.7",
-                                        "string-width": "^4.2.3",
-                                        "strip-ansi": "^6.0.1",
-                                        "wide-align": "^1.1.5"
-                                    }
-                                },
-                                "glob": {
-                                    "version": "7.2.3",
-                                    "bundled": true,
-                                    "requires": {
-                                        "fs.realpath": "^1.0.0",
-                                        "inflight": "^1.0.4",
-                                        "inherits": "2",
-                                        "minimatch": "^3.1.1",
-                                        "once": "^1.3.0",
-                                        "path-is-absolute": "^1.0.0"
-                                    }
-                                },
-                                "minimatch": {
-                                    "version": "3.1.2",
-                                    "bundled": true,
-                                    "requires": {
-                                        "brace-expansion": "^1.1.7"
-                                    }
-                                },
-                                "nopt": {
-                                    "version": "6.0.0",
-                                    "bundled": true,
-                                    "requires": {
-                                        "abbrev": "^1.0.0"
-                                    }
-                                },
-                                "npmlog": {
-                                    "version": "6.0.2",
-                                    "bundled": true,
-                                    "requires": {
-                                        "are-we-there-yet": "^3.0.0",
-                                        "console-control-strings": "^1.1.0",
-                                        "gauge": "^4.0.3",
-                                        "set-blocking": "^2.0.0"
-                                    }
-                                },
-                                "readable-stream": {
-                                    "version": "3.6.2",
-                                    "bundled": true,
-                                    "requires": {
-                                        "inherits": "^2.0.3",
-                                        "string_decoder": "^1.1.1",
-                                        "util-deprecate": "^1.0.1"
-                                    }
-                                },
-                                "signal-exit": {
-                                    "version": "3.0.7",
-                                    "bundled": true
-                                },
-                                "which": {
-                                    "version": "2.0.2",
-                                    "bundled": true,
-                                    "requires": {
-                                        "isexe": "^2.0.0"
-                                    }
-                                }
-                            }
-                        },
-                        "nopt": {
-                            "version": "7.2.0",
-                            "bundled": true,
-                            "requires": {
-                                "abbrev": "^2.0.0"
-                            }
-                        },
-                        "normalize-package-data": {
-                            "version": "5.0.0",
-                            "bundled": true,
-                            "requires": {
-                                "hosted-git-info": "^6.0.0",
-                                "is-core-module": "^2.8.1",
-                                "semver": "^7.3.5",
-                                "validate-npm-package-license": "^3.0.4"
-                            }
-                        },
-                        "npm-audit-report": {
-                            "version": "5.0.0",
-                            "bundled": true
-                        },
-                        "npm-bundled": {
-                            "version": "3.0.0",
-                            "bundled": true,
-                            "requires": {
-                                "npm-normalize-package-bin": "^3.0.0"
-                            }
-                        },
-                        "npm-install-checks": {
-                            "version": "6.1.1",
-                            "bundled": true,
-                            "requires": {
-                                "semver": "^7.1.1"
-                            }
-                        },
-                        "npm-normalize-package-bin": {
-                            "version": "3.0.1",
-                            "bundled": true
-                        },
-                        "npm-package-arg": {
-                            "version": "10.1.0",
-                            "bundled": true,
-                            "requires": {
-                                "hosted-git-info": "^6.0.0",
-                                "proc-log": "^3.0.0",
-                                "semver": "^7.3.5",
-                                "validate-npm-package-name": "^5.0.0"
-                            }
-                        },
-                        "npm-packlist": {
-                            "version": "7.0.4",
-                            "bundled": true,
-                            "requires": {
-                                "ignore-walk": "^6.0.0"
-                            }
-                        },
-                        "npm-pick-manifest": {
-                            "version": "8.0.1",
-                            "bundled": true,
-                            "requires": {
-                                "npm-install-checks": "^6.0.0",
-                                "npm-normalize-package-bin": "^3.0.0",
-                                "npm-package-arg": "^10.0.0",
-                                "semver": "^7.3.5"
-                            }
-                        },
-                        "npm-profile": {
-                            "version": "7.0.1",
-                            "bundled": true,
-                            "requires": {
-                                "npm-registry-fetch": "^14.0.0",
-                                "proc-log": "^3.0.0"
-                            }
-                        },
-                        "npm-registry-fetch": {
-                            "version": "14.0.5",
-                            "bundled": true,
-                            "requires": {
-                                "make-fetch-happen": "^11.0.0",
-                                "minipass": "^5.0.0",
-                                "minipass-fetch": "^3.0.0",
-                                "minipass-json-stream": "^1.0.1",
-                                "minizlib": "^2.1.2",
-                                "npm-package-arg": "^10.0.0",
-                                "proc-log": "^3.0.0"
-                            }
-                        },
-                        "npm-user-validate": {
-                            "version": "2.0.0",
-                            "bundled": true
-                        },
-                        "npmlog": {
-                            "version": "7.0.1",
-                            "bundled": true,
-                            "requires": {
-                                "are-we-there-yet": "^4.0.0",
-                                "console-control-strings": "^1.1.0",
-                                "gauge": "^5.0.0",
-                                "set-blocking": "^2.0.0"
-                            }
-                        },
-                        "once": {
-                            "version": "1.4.0",
-                            "bundled": true,
-                            "requires": {
-                                "wrappy": "1"
-                            }
-                        },
-                        "p-map": {
-                            "version": "4.0.0",
-                            "bundled": true,
-                            "requires": {
-                                "aggregate-error": "^3.0.0"
-                            }
-                        },
-                        "pacote": {
-                            "version": "15.2.0",
-                            "bundled": true,
-                            "requires": {
-                                "@npmcli/git": "^4.0.0",
-                                "@npmcli/installed-package-contents": "^2.0.1",
-                                "@npmcli/promise-spawn": "^6.0.1",
-                                "@npmcli/run-script": "^6.0.0",
-                                "cacache": "^17.0.0",
-                                "fs-minipass": "^3.0.0",
-                                "minipass": "^5.0.0",
-                                "npm-package-arg": "^10.0.0",
-                                "npm-packlist": "^7.0.0",
-                                "npm-pick-manifest": "^8.0.0",
-                                "npm-registry-fetch": "^14.0.0",
-                                "proc-log": "^3.0.0",
-                                "promise-retry": "^2.0.1",
-                                "read-package-json": "^6.0.0",
-                                "read-package-json-fast": "^3.0.0",
-                                "sigstore": "^1.3.0",
-                                "ssri": "^10.0.0",
-                                "tar": "^6.1.11"
-                            }
-                        },
-                        "parse-conflict-json": {
-                            "version": "3.0.1",
-                            "bundled": true,
-                            "requires": {
-                                "json-parse-even-better-errors": "^3.0.0",
-                                "just-diff": "^6.0.0",
-                                "just-diff-apply": "^5.2.0"
-                            }
-                        },
-                        "path-is-absolute": {
-                            "version": "1.0.1",
-                            "bundled": true
-                        },
-                        "path-key": {
-                            "version": "3.1.1",
-                            "bundled": true
-                        },
-                        "path-scurry": {
-                            "version": "1.9.2",
-                            "bundled": true,
-                            "requires": {
-                                "lru-cache": "^9.1.1",
-                                "minipass": "^5.0.0 || ^6.0.2"
-                            },
-                            "dependencies": {
-                                "lru-cache": {
-                                    "version": "9.1.1",
-                                    "bundled": true
-                                }
-                            }
-                        },
-                        "postcss-selector-parser": {
-                            "version": "6.0.13",
-                            "bundled": true,
-                            "requires": {
-                                "cssesc": "^3.0.0",
-                                "util-deprecate": "^1.0.2"
-                            }
-                        },
-                        "proc-log": {
-                            "version": "3.0.0",
-                            "bundled": true
-                        },
-                        "process": {
-                            "version": "0.11.10",
-                            "bundled": true
-                        },
-                        "promise-all-reject-late": {
-                            "version": "1.0.1",
-                            "bundled": true
-                        },
-                        "promise-call-limit": {
-                            "version": "1.0.2",
-                            "bundled": true
-                        },
-                        "promise-inflight": {
-                            "version": "1.0.1",
-                            "bundled": true
-                        },
-                        "promise-retry": {
-                            "version": "2.0.1",
-                            "bundled": true,
-                            "requires": {
-                                "err-code": "^2.0.2",
-                                "retry": "^0.12.0"
-                            }
-                        },
-                        "promzard": {
-                            "version": "1.0.0",
-                            "bundled": true,
-                            "requires": {
-                                "read": "^2.0.0"
-                            }
-                        },
-                        "qrcode-terminal": {
-                            "version": "0.12.0",
-                            "bundled": true
-                        },
-                        "read": {
-                            "version": "2.1.0",
-                            "bundled": true,
-                            "requires": {
-                                "mute-stream": "~1.0.0"
-                            }
-                        },
-                        "read-cmd-shim": {
-                            "version": "4.0.0",
-                            "bundled": true
-                        },
-                        "read-package-json": {
-                            "version": "6.0.4",
-                            "bundled": true,
-                            "requires": {
-                                "glob": "^10.2.2",
-                                "json-parse-even-better-errors": "^3.0.0",
-                                "normalize-package-data": "^5.0.0",
-                                "npm-normalize-package-bin": "^3.0.0"
-                            }
-                        },
-                        "read-package-json-fast": {
-                            "version": "3.0.2",
-                            "bundled": true,
-                            "requires": {
-                                "json-parse-even-better-errors": "^3.0.0",
-                                "npm-normalize-package-bin": "^3.0.0"
-                            }
-                        },
-                        "readable-stream": {
-                            "version": "4.4.0",
-                            "bundled": true,
-                            "requires": {
-                                "abort-controller": "^3.0.0",
-                                "buffer": "^6.0.3",
-                                "events": "^3.3.0",
-                                "process": "^0.11.10"
-                            }
-                        },
-                        "retry": {
-                            "version": "0.12.0",
-                            "bundled": true
-                        },
-                        "rimraf": {
-                            "version": "3.0.2",
-                            "bundled": true,
-                            "requires": {
-                                "glob": "^7.1.3"
-                            },
-                            "dependencies": {
-                                "brace-expansion": {
-                                    "version": "1.1.11",
-                                    "bundled": true,
-                                    "requires": {
-                                        "balanced-match": "^1.0.0",
-                                        "concat-map": "0.0.1"
-                                    }
-                                },
-                                "glob": {
-                                    "version": "7.2.3",
-                                    "bundled": true,
-                                    "requires": {
-                                        "fs.realpath": "^1.0.0",
-                                        "inflight": "^1.0.4",
-                                        "inherits": "2",
-                                        "minimatch": "^3.1.1",
-                                        "once": "^1.3.0",
-                                        "path-is-absolute": "^1.0.0"
-                                    }
-                                },
-                                "minimatch": {
-                                    "version": "3.1.2",
-                                    "bundled": true,
-                                    "requires": {
-                                        "brace-expansion": "^1.1.7"
-                                    }
-                                }
-                            }
-                        },
-                        "safe-buffer": {
-                            "version": "5.2.1",
-                            "bundled": true
-                        },
-                        "safer-buffer": {
-                            "version": "2.1.2",
-                            "bundled": true,
-                            "optional": true
-                        },
-                        "semver": {
-                            "version": "7.5.4",
-                            "bundled": true,
-                            "requires": {
-                                "lru-cache": "^6.0.0"
-                            },
-                            "dependencies": {
-                                "lru-cache": {
-                                    "version": "6.0.0",
-                                    "bundled": true,
-                                    "requires": {
-                                        "yallist": "^4.0.0"
-                                    }
-                                }
-                            }
-                        },
-                        "set-blocking": {
-                            "version": "2.0.0",
-                            "bundled": true
-                        },
-                        "shebang-command": {
-                            "version": "2.0.0",
-                            "bundled": true,
-                            "requires": {
-                                "shebang-regex": "^3.0.0"
-                            }
-                        },
-                        "shebang-regex": {
-                            "version": "3.0.0",
-                            "bundled": true
-                        },
-                        "signal-exit": {
-                            "version": "4.0.2",
-                            "bundled": true
-                        },
-                        "sigstore": {
-                            "version": "1.7.0",
-                            "bundled": true,
-                            "requires": {
-                                "@sigstore/protobuf-specs": "^0.1.0",
-                                "@sigstore/tuf": "^1.0.1",
-                                "make-fetch-happen": "^11.0.1"
-                            }
-                        },
-                        "smart-buffer": {
-                            "version": "4.2.0",
-                            "bundled": true
-                        },
-                        "socks": {
-                            "version": "2.7.1",
-                            "bundled": true,
-                            "requires": {
-                                "ip": "^2.0.0",
-                                "smart-buffer": "^4.2.0"
-                            }
-                        },
-                        "socks-proxy-agent": {
-                            "version": "7.0.0",
-                            "bundled": true,
-                            "requires": {
-                                "agent-base": "^6.0.2",
-                                "debug": "^4.3.3",
-                                "socks": "^2.6.2"
-                            }
-                        },
-                        "spdx-correct": {
-                            "version": "3.2.0",
-                            "bundled": true,
-                            "requires": {
-                                "spdx-expression-parse": "^3.0.0",
-                                "spdx-license-ids": "^3.0.0"
-                            }
-                        },
-                        "spdx-exceptions": {
-                            "version": "2.3.0",
-                            "bundled": true
-                        },
-                        "spdx-expression-parse": {
-                            "version": "3.0.1",
-                            "bundled": true,
-                            "requires": {
-                                "spdx-exceptions": "^2.1.0",
-                                "spdx-license-ids": "^3.0.0"
-                            }
-                        },
-                        "spdx-license-ids": {
-                            "version": "3.0.13",
-                            "bundled": true
-                        },
-                        "ssri": {
-                            "version": "10.0.4",
-                            "bundled": true,
-                            "requires": {
-                                "minipass": "^5.0.0"
-                            }
-                        },
-                        "string-width": {
-                            "version": "4.2.3",
-                            "bundled": true,
-                            "requires": {
-                                "emoji-regex": "^8.0.0",
-                                "is-fullwidth-code-point": "^3.0.0",
-                                "strip-ansi": "^6.0.1"
-                            }
-                        },
-                        "string-width-cjs": {
-                            "version": "npm:string-width-cjs@4.2.3",
-                            "bundled": true,
-                            "requires": {
-                                "emoji-regex": "^8.0.0",
-                                "is-fullwidth-code-point": "^3.0.0",
-                                "strip-ansi": "^6.0.1"
-                            }
-                        },
-                        "string_decoder": {
-                            "version": "1.3.0",
-                            "bundled": true,
-                            "requires": {
-                                "safe-buffer": "~5.2.0"
-                            }
-                        },
-                        "strip-ansi": {
-                            "version": "6.0.1",
-                            "bundled": true,
-                            "requires": {
-                                "ansi-regex": "^5.0.1"
-                            }
-                        },
-                        "strip-ansi-cjs": {
-                            "version": "npm:strip-ansi-cjs@6.0.1",
-                            "bundled": true,
-                            "requires": {
-                                "ansi-regex": "^5.0.1"
-                            }
-                        },
-                        "supports-color": {
-                            "version": "9.4.0",
-                            "bundled": true
-                        },
-                        "tar": {
-                            "version": "6.1.15",
-                            "bundled": true,
-                            "requires": {
-                                "chownr": "^2.0.0",
-                                "fs-minipass": "^2.0.0",
-                                "minipass": "^5.0.0",
-                                "minizlib": "^2.1.1",
-                                "mkdirp": "^1.0.3",
-                                "yallist": "^4.0.0"
-                            },
-                            "dependencies": {
-                                "fs-minipass": {
-                                    "version": "2.1.0",
-                                    "bundled": true,
-                                    "requires": {
-                                        "minipass": "^3.0.0"
-                                    },
-                                    "dependencies": {
-                                        "minipass": {
-                                            "version": "3.3.6",
-                                            "bundled": true,
-                                            "requires": {
-                                                "yallist": "^4.0.0"
-                                            }
-                                        }
-                                    }
-                                }
-                            }
-                        },
-                        "text-table": {
-                            "version": "0.2.0",
-                            "bundled": true
-                        },
-                        "tiny-relative-date": {
-                            "version": "1.3.0",
-                            "bundled": true
-                        },
-                        "treeverse": {
-                            "version": "3.0.0",
-                            "bundled": true
+                            "version": "1.1.1",
+                            "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+                            "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
                         },
-                        "tuf-js": {
-                            "version": "1.1.7",
-                            "bundled": true,
+                        "lru-cache": {
+                            "version": "6.0.0",
+                            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+                            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
                             "requires": {
-                                "@tufjs/models": "1.0.4",
-                                "debug": "^4.3.4",
-                                "make-fetch-happen": "^11.1.1"
+                                "yallist": "^4.0.0"
                             }
                         },
-                        "unique-filename": {
-                            "version": "3.0.0",
-                            "bundled": true,
+                        "nopt": {
+                            "version": "6.0.0",
+                            "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz",
+                            "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==",
                             "requires": {
-                                "unique-slug": "^4.0.0"
+                                "abbrev": "^1.0.0"
                             }
                         },
-                        "unique-slug": {
-                            "version": "4.0.0",
-                            "bundled": true,
+                        "npmlog": {
+                            "version": "6.0.2",
+                            "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz",
+                            "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==",
                             "requires": {
-                                "imurmurhash": "^0.1.4"
+                                "are-we-there-yet": "^3.0.0",
+                                "console-control-strings": "^1.1.0",
+                                "gauge": "^4.0.3",
+                                "set-blocking": "^2.0.0"
                             }
                         },
-                        "util-deprecate": {
-                            "version": "1.0.2",
-                            "bundled": true
-                        },
-                        "validate-npm-package-license": {
-                            "version": "3.0.4",
-                            "bundled": true,
+                        "semver": {
+                            "version": "7.5.4",
+                            "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                            "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
                             "requires": {
-                                "spdx-correct": "^3.0.0",
-                                "spdx-expression-parse": "^3.0.0"
+                                "lru-cache": "^6.0.0"
                             }
                         },
-                        "validate-npm-package-name": {
-                            "version": "5.0.0",
-                            "bundled": true,
+                        "which": {
+                            "version": "2.0.2",
+                            "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+                            "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
                             "requires": {
-                                "builtins": "^5.0.0"
+                                "isexe": "^2.0.0"
                             }
-                        },
-                        "walk-up-path": {
-                            "version": "3.0.1",
-                            "bundled": true
-                        },
-                        "wcwidth": {
-                            "version": "1.0.1",
-                            "bundled": true,
+                        }
+                    }
+                },
+                "nopt": {
+                    "version": "7.2.0",
+                    "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz",
+                    "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==",
+                    "requires": {
+                        "abbrev": "^2.0.0"
+                    }
+                },
+                "npm": {
+                    "version": "9.8.1",
+                    "resolved": "https://registry.npmjs.org/npm/-/npm-9.8.1.tgz",
+                    "integrity": "sha512-AfDvThQzsIXhYgk9zhbk5R+lh811lKkLAeQMMhSypf1BM7zUafeIIBzMzespeuVEJ0+LvY36oRQYf7IKLzU3rw==",
+                    "requires": {
+                        "@isaacs/string-locale-compare": "^1.1.0",
+                        "@npmcli/arborist": "^6.3.0",
+                        "@npmcli/config": "^6.2.1",
+                        "@npmcli/fs": "^3.1.0",
+                        "@npmcli/map-workspaces": "^3.0.4",
+                        "@npmcli/package-json": "^4.0.1",
+                        "@npmcli/promise-spawn": "^6.0.2",
+                        "@npmcli/run-script": "^6.0.2",
+                        "abbrev": "^2.0.0",
+                        "archy": "~1.0.0",
+                        "cacache": "^17.1.3",
+                        "chalk": "^5.3.0",
+                        "ci-info": "^3.8.0",
+                        "cli-columns": "^4.0.0",
+                        "cli-table3": "^0.6.3",
+                        "columnify": "^1.6.0",
+                        "fastest-levenshtein": "^1.0.16",
+                        "fs-minipass": "^3.0.2",
+                        "glob": "^10.2.7",
+                        "graceful-fs": "^4.2.11",
+                        "hosted-git-info": "^6.1.1",
+                        "ini": "^4.1.1",
+                        "init-package-json": "^5.0.0",
+                        "is-cidr": "^4.0.2",
+                        "json-parse-even-better-errors": "^3.0.0",
+                        "libnpmaccess": "^7.0.2",
+                        "libnpmdiff": "^5.0.19",
+                        "libnpmexec": "^6.0.3",
+                        "libnpmfund": "^4.0.19",
+                        "libnpmhook": "^9.0.3",
+                        "libnpmorg": "^5.0.4",
+                        "libnpmpack": "^5.0.19",
+                        "libnpmpublish": "^7.5.0",
+                        "libnpmsearch": "^6.0.2",
+                        "libnpmteam": "^5.0.3",
+                        "libnpmversion": "^4.0.2",
+                        "make-fetch-happen": "^11.1.1",
+                        "minimatch": "^9.0.3",
+                        "minipass": "^5.0.0",
+                        "minipass-pipeline": "^1.2.4",
+                        "ms": "^2.1.2",
+                        "node-gyp": "^9.4.0",
+                        "nopt": "^7.2.0",
+                        "npm-audit-report": "^5.0.0",
+                        "npm-install-checks": "^6.1.1",
+                        "npm-package-arg": "^10.1.0",
+                        "npm-pick-manifest": "^8.0.1",
+                        "npm-profile": "^7.0.1",
+                        "npm-registry-fetch": "^14.0.5",
+                        "npm-user-validate": "^2.0.0",
+                        "npmlog": "^7.0.1",
+                        "p-map": "^4.0.0",
+                        "pacote": "^15.2.0",
+                        "parse-conflict-json": "^3.0.1",
+                        "proc-log": "^3.0.0",
+                        "qrcode-terminal": "^0.12.0",
+                        "read": "^2.1.0",
+                        "semver": "^7.5.4",
+                        "sigstore": "^1.7.0",
+                        "ssri": "^10.0.4",
+                        "supports-color": "^9.4.0",
+                        "tar": "^6.1.15",
+                        "text-table": "~0.2.0",
+                        "tiny-relative-date": "^1.3.0",
+                        "treeverse": "^3.0.0",
+                        "validate-npm-package-name": "^5.0.0",
+                        "which": "^3.0.1",
+                        "write-file-atomic": "^5.0.1"
+                    },
+                    "dependencies": {
+                        "glob": {
+                            "version": "10.2.7",
+                            "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.7.tgz",
+                            "integrity": "sha512-jTKehsravOJo8IJxUGfZILnkvVJM/MOfHRs8QcXolVef2zNI9Tqyy5+SeuOAZd3upViEZQLyFpQhYiHLrMUNmA==",
                             "requires": {
-                                "defaults": "^1.0.3"
+                                "foreground-child": "^3.1.0",
+                                "jackspeak": "^2.0.3",
+                                "minimatch": "^9.0.1",
+                                "minipass": "^5.0.0 || ^6.0.2",
+                                "path-scurry": "^1.7.0"
                             }
                         },
-                        "which": {
-                            "version": "3.0.1",
-                            "bundled": true,
+                        "lru-cache": {
+                            "version": "6.0.0",
+                            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+                            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
                             "requires": {
-                                "isexe": "^2.0.0"
+                                "yallist": "^4.0.0"
                             }
                         },
-                        "wide-align": {
-                            "version": "1.1.5",
-                            "bundled": true,
+                        "semver": {
+                            "version": "7.5.4",
+                            "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                            "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
                             "requires": {
-                                "string-width": "^1.0.2 || 2 || 3 || 4"
+                                "lru-cache": "^6.0.0"
                             }
-                        },
-                        "wrap-ansi": {
-                            "version": "8.1.0",
-                            "bundled": true,
+                        }
+                    }
+                },
+                "npmlog": {
+                    "version": "7.0.1",
+                    "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-7.0.1.tgz",
+                    "integrity": "sha512-uJ0YFk/mCQpLBt+bxN88AKd+gyqZvZDbtiNxk6Waqcj2aPRyfVx8ITawkyQynxUagInjdYT1+qj4NfA5KJJUxg==",
+                    "requires": {
+                        "are-we-there-yet": "^4.0.0",
+                        "console-control-strings": "^1.1.0",
+                        "gauge": "^5.0.0",
+                        "set-blocking": "^2.0.0"
+                    },
+                    "dependencies": {
+                        "are-we-there-yet": {
+                            "version": "4.0.1",
+                            "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-4.0.1.tgz",
+                            "integrity": "sha512-2zuA+jpOYBRgoBCfa+fB87Rk0oGJjDX6pxGzqH6f33NzUhG25Xur6R0u0Z9VVAq8Z5JvQpQI6j6rtonuivC8QA==",
                             "requires": {
-                                "ansi-styles": "^6.1.0",
-                                "string-width": "^5.0.1",
-                                "strip-ansi": "^7.0.1"
-                            },
-                            "dependencies": {
-                                "ansi-regex": {
-                                    "version": "6.0.1",
-                                    "bundled": true
-                                },
-                                "ansi-styles": {
-                                    "version": "6.2.1",
-                                    "bundled": true
-                                },
-                                "emoji-regex": {
-                                    "version": "9.2.2",
-                                    "bundled": true
-                                },
-                                "string-width": {
-                                    "version": "5.1.2",
-                                    "bundled": true,
-                                    "requires": {
-                                        "eastasianwidth": "^0.2.0",
-                                        "emoji-regex": "^9.2.2",
-                                        "strip-ansi": "^7.0.1"
-                                    }
-                                },
-                                "strip-ansi": {
-                                    "version": "7.1.0",
-                                    "bundled": true,
-                                    "requires": {
-                                        "ansi-regex": "^6.0.1"
-                                    }
-                                }
+                                "delegates": "^1.0.0",
+                                "readable-stream": "^4.1.0"
                             }
                         },
-                        "wrap-ansi-cjs": {
-                            "version": "npm:wrap-ansi-cjs@7.0.0",
-                            "bundled": true,
+                        "gauge": {
+                            "version": "5.0.1",
+                            "resolved": "https://registry.npmjs.org/gauge/-/gauge-5.0.1.tgz",
+                            "integrity": "sha512-CmykPMJGuNan/3S4kZOpvvPYSNqSHANiWnh9XcMU2pSjtBfF0XzZ2p1bFAxTbnFxyBuPxQYHhzwaoOmUdqzvxQ==",
                             "requires": {
-                                "ansi-styles": "^4.0.0",
-                                "string-width": "^4.1.0",
-                                "strip-ansi": "^6.0.0"
+                                "aproba": "^1.0.3 || ^2.0.0",
+                                "color-support": "^1.1.3",
+                                "console-control-strings": "^1.1.0",
+                                "has-unicode": "^2.0.1",
+                                "signal-exit": "^4.0.1",
+                                "string-width": "^4.2.3",
+                                "strip-ansi": "^6.0.1",
+                                "wide-align": "^1.1.5"
                             }
                         },
-                        "wrappy": {
-                            "version": "1.0.2",
-                            "bundled": true
-                        },
-                        "write-file-atomic": {
-                            "version": "5.0.1",
-                            "bundled": true,
+                        "readable-stream": {
+                            "version": "4.4.2",
+                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz",
+                            "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==",
                             "requires": {
-                                "imurmurhash": "^0.1.4",
-                                "signal-exit": "^4.0.1"
+                                "abort-controller": "^3.0.0",
+                                "buffer": "^6.0.3",
+                                "events": "^3.3.0",
+                                "process": "^0.11.10",
+                                "string_decoder": "^1.3.0"
                             }
                         },
-                        "yallist": {
-                            "version": "4.0.0",
-                            "bundled": true
+                        "signal-exit": {
+                            "version": "4.1.0",
+                            "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+                            "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="
                         }
                     }
                 },
+                "p-map": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+                    "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+                    "requires": {
+                        "aggregate-error": "^3.0.0"
+                    }
+                },
                 "pify": {
                     "version": "4.0.1",
                     "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
@@ -9086,11 +9349,50 @@
                     "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
                     "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
                 },
+                "safe-buffer": {
+                    "version": "5.2.1",
+                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+                    "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+                },
                 "slash": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
                     "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A=="
                 },
+                "ssri": {
+                    "version": "10.0.4",
+                    "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.4.tgz",
+                    "integrity": "sha512-12+IR2CB2C28MMAw0Ncqwj5QbTcs0nGIhgJzYWzDkb21vWmfNI83KS4f3Ci6GI98WreIfG7o9UXp3C0qbpA8nQ==",
+                    "requires": {
+                        "minipass": "^5.0.0"
+                    }
+                },
+                "string-width": {
+                    "version": "4.2.3",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                    "requires": {
+                        "emoji-regex": "^8.0.0",
+                        "is-fullwidth-code-point": "^3.0.0",
+                        "strip-ansi": "^6.0.1"
+                    }
+                },
+                "string_decoder": {
+                    "version": "1.3.0",
+                    "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+                    "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+                    "requires": {
+                        "safe-buffer": "~5.2.0"
+                    }
+                },
+                "strip-ansi": {
+                    "version": "6.0.1",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                    "requires": {
+                        "ansi-regex": "^5.0.1"
+                    }
+                },
                 "styled-components": {
                     "version": "6.0.7",
                     "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.0.7.tgz",
@@ -9124,18 +9426,109 @@
                         }
                     }
                 },
-                "stylis": {
-                    "version": "4.2.0",
-                    "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
-                    "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
+                "stylis": {
+                    "version": "4.2.0",
+                    "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
+                    "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
+                },
+                "supports-color": {
+                    "version": "9.4.0",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz",
+                    "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw=="
+                },
+                "tar": {
+                    "version": "6.1.15",
+                    "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz",
+                    "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==",
+                    "requires": {
+                        "chownr": "^2.0.0",
+                        "fs-minipass": "^2.0.0",
+                        "minipass": "^5.0.0",
+                        "minizlib": "^2.1.1",
+                        "mkdirp": "^1.0.3",
+                        "yallist": "^4.0.0"
+                    },
+                    "dependencies": {
+                        "fs-minipass": {
+                            "version": "2.1.0",
+                            "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
+                            "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
+                            "requires": {
+                                "minipass": "^3.0.0"
+                            },
+                            "dependencies": {
+                                "minipass": {
+                                    "version": "3.3.6",
+                                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                                    "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+                                    "requires": {
+                                        "yallist": "^4.0.0"
+                                    }
+                                }
+                            }
+                        }
+                    }
+                },
+                "text-table": {
+                    "version": "0.2.0",
+                    "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+                    "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
+                },
+                "to-regex-range": {
+                    "version": "5.0.1",
+                    "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+                    "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+                    "optional": true,
+                    "requires": {
+                        "is-number": "^7.0.0"
+                    }
+                },
+                "unique-filename": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz",
+                    "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==",
+                    "requires": {
+                        "unique-slug": "^4.0.0"
+                    }
+                },
+                "unique-slug": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz",
+                    "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==",
+                    "requires": {
+                        "imurmurhash": "^0.1.4"
+                    }
+                },
+                "which": {
+                    "version": "3.0.1",
+                    "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz",
+                    "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==",
+                    "requires": {
+                        "isexe": "^2.0.0"
+                    }
+                },
+                "wide-align": {
+                    "version": "1.1.5",
+                    "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
+                    "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
+                    "requires": {
+                        "string-width": "^1.0.2 || 2 || 3 || 4"
+                    }
                 },
-                "to-regex-range": {
+                "write-file-atomic": {
                     "version": "5.0.1",
-                    "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
-                    "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
-                    "optional": true,
+                    "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
+                    "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
                     "requires": {
-                        "is-number": "^7.0.0"
+                        "imurmurhash": "^0.1.4",
+                        "signal-exit": "^4.0.1"
+                    },
+                    "dependencies": {
+                        "signal-exit": {
+                            "version": "4.1.0",
+                            "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+                            "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="
+                        }
                     }
                 }
             }
@@ -9223,11 +9616,46 @@
             "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
             "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw=="
         },
+        "builtins": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
+            "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==",
+            "requires": {
+                "semver": "^7.0.0"
+            },
+            "dependencies": {
+                "semver": {
+                    "version": "7.5.4",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                    "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    }
+                }
+            }
+        },
         "bytes": {
             "version": "3.1.2",
             "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
             "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
         },
+        "bytewise": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz",
+            "integrity": "sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==",
+            "requires": {
+                "bytewise-core": "^1.2.2",
+                "typewise": "^1.0.3"
+            }
+        },
+        "bytewise-core": {
+            "version": "1.2.3",
+            "resolved": "https://registry.npmjs.org/bytewise-core/-/bytewise-core-1.2.3.tgz",
+            "integrity": "sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==",
+            "requires": {
+                "typewise-core": "^1.2"
+            }
+        },
         "cacache": {
             "version": "10.0.4",
             "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz",
@@ -9493,6 +9921,11 @@
             "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz",
             "integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w=="
         },
+        "cheap-ruler": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/cheap-ruler/-/cheap-ruler-3.0.2.tgz",
+            "integrity": "sha512-02T332h1/HTN6cDSufLP8x4JzDs2+VC+8qZ/N0kWIVPyc2xUkWwWh3B2fJxR7raXkL4Mq7k554mfuM9ofv/vGg=="
+        },
         "check-error": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
@@ -9547,6 +9980,21 @@
             "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
             "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A=="
         },
+        "cidr-regex": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/cidr-regex/-/cidr-regex-3.1.1.tgz",
+            "integrity": "sha512-RBqYd32aDwbCMFJRL6wHOlDNYJsPNTt8vC82ErHF5vKt8QQzxm1FrkW8s/R5pVrXMf17sba09Uoy91PKiddAsw==",
+            "requires": {
+                "ip-regex": "^4.1.0"
+            },
+            "dependencies": {
+                "ip-regex": {
+                    "version": "4.3.0",
+                    "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz",
+                    "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q=="
+                }
+            }
+        },
         "clamp": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz",
@@ -9598,11 +10046,60 @@
                 }
             }
         },
+        "clean-stack": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
+            "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="
+        },
         "cli-boxes": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
             "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM="
         },
+        "cli-columns": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/cli-columns/-/cli-columns-4.0.0.tgz",
+            "integrity": "sha512-XW2Vg+w+L9on9wtwKpyzluIPCWXjaBahI7mTcYjx+BVIYD9c3yqcv/yKC7CmdCZat4rq2yiE1UMSJC5ivKfMtQ==",
+            "requires": {
+                "string-width": "^4.2.3",
+                "strip-ansi": "^6.0.1"
+            },
+            "dependencies": {
+                "ansi-regex": {
+                    "version": "5.0.1",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                },
+                "emoji-regex": {
+                    "version": "8.0.0",
+                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                    "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+                },
+                "is-fullwidth-code-point": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+                    "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+                },
+                "string-width": {
+                    "version": "4.2.3",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                    "requires": {
+                        "emoji-regex": "^8.0.0",
+                        "is-fullwidth-code-point": "^3.0.0",
+                        "strip-ansi": "^6.0.1"
+                    }
+                },
+                "strip-ansi": {
+                    "version": "6.0.1",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                    "requires": {
+                        "ansi-regex": "^5.0.1"
+                    }
+                }
+            }
+        },
         "cli-cursor": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
@@ -9612,6 +10109,50 @@
                 "restore-cursor": "^3.1.0"
             }
         },
+        "cli-table3": {
+            "version": "0.6.3",
+            "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz",
+            "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==",
+            "requires": {
+                "@colors/colors": "1.5.0",
+                "string-width": "^4.2.0"
+            },
+            "dependencies": {
+                "ansi-regex": {
+                    "version": "5.0.1",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                },
+                "emoji-regex": {
+                    "version": "8.0.0",
+                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                    "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+                },
+                "is-fullwidth-code-point": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+                    "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+                },
+                "string-width": {
+                    "version": "4.2.3",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                    "requires": {
+                        "emoji-regex": "^8.0.0",
+                        "is-fullwidth-code-point": "^3.0.0",
+                        "strip-ansi": "^6.0.1"
+                    }
+                },
+                "strip-ansi": {
+                    "version": "6.0.1",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                    "requires": {
+                        "ansi-regex": "^5.0.1"
+                    }
+                }
+            }
+        },
         "cli-width": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
@@ -9698,6 +10239,11 @@
             "resolved": "https://registry.npmjs.org/clj-fuzzy/-/clj-fuzzy-0.3.3.tgz",
             "integrity": "sha1-seU0MJHFIC28UlMoY+HEp4RX8D0="
         },
+        "clone": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+            "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="
+        },
         "clone-deep": {
             "version": "4.0.1",
             "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
@@ -9718,11 +10264,31 @@
                 }
             }
         },
+        "clone-response": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
+            "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
+            "requires": {
+                "mimic-response": "^1.0.0"
+            },
+            "dependencies": {
+                "mimic-response": {
+                    "version": "1.0.1",
+                    "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
+                    "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="
+                }
+            }
+        },
         "clsx": {
             "version": "1.2.1",
             "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
             "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
         },
+        "cmd-shim": {
+            "version": "6.0.2",
+            "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.2.tgz",
+            "integrity": "sha512-+FFYbB0YLaAkhkcrjkyNLYDiOsFSfRjwjY19LXk/psmMx1z00xlCv7hhQoTGXXIKi+YXHL/iiFo8NqMVQX9nOw=="
+        },
         "code-point-at": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
@@ -9783,6 +10349,30 @@
             "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
             "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
         },
+        "columnify": {
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz",
+            "integrity": "sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q==",
+            "requires": {
+                "strip-ansi": "^6.0.1",
+                "wcwidth": "^1.0.0"
+            },
+            "dependencies": {
+                "ansi-regex": {
+                    "version": "5.0.1",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                },
+                "strip-ansi": {
+                    "version": "6.0.1",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                    "requires": {
+                        "ansi-regex": "^5.0.1"
+                    }
+                }
+            }
+        },
         "combined-stream": {
             "version": "1.0.8",
             "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -9817,6 +10407,11 @@
             "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
             "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
         },
+        "common-ancestor-path": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz",
+            "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="
+        },
         "commondir": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
@@ -10529,11 +11124,15 @@
             "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
             "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw=="
         },
+        "csscolorparser": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz",
+            "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w=="
+        },
         "cssesc": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
-            "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
-            "dev": true
+            "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="
         },
         "cssom": {
             "version": "0.4.4",
@@ -10595,6 +11194,16 @@
             "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
             "dev": true
         },
+        "d": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
+            "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
+            "dev": true,
+            "requires": {
+                "es5-ext": "^0.10.50",
+                "type": "^1.0.1"
+            }
+        },
         "d3": {
             "version": "7.8.4",
             "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.4.tgz",
@@ -10929,9 +11538,9 @@
             "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0="
         },
         "debounce": {
-            "version": "1.2.1",
-            "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
-            "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.0.0.tgz",
+            "integrity": "sha512-xRetU6gL1VJbs85Mc4FoEGSjQxzpdxRyFhe3lmWFyy2EzydIcD4xzUvRJMD+NPDfMwKNhxa3PvsIOU32luIWeA=="
         },
         "debug": {
             "version": "3.2.6",
@@ -10946,6 +11555,15 @@
             "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
             "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
         },
+        "decamelize-keys": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz",
+            "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==",
+            "requires": {
+                "decamelize": "^1.1.0",
+                "map-obj": "^1.0.0"
+            }
+        },
         "decimal.js-light": {
             "version": "2.5.1",
             "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
@@ -11058,6 +11676,14 @@
                 }
             }
         },
+        "defaults": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
+            "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
+            "requires": {
+                "clone": "^1.0.2"
+            }
+        },
         "defer-to-connect": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
@@ -11667,6 +12293,16 @@
                 "xtend": "^4.0.0"
             }
         },
+        "earcut": {
+            "version": "2.2.4",
+            "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz",
+            "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ=="
+        },
+        "eastasianwidth": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+            "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
+        },
         "ecc-jsbn": {
             "version": "0.1.2",
             "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -11827,6 +12463,11 @@
             "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
             "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
         },
+        "env-paths": {
+            "version": "2.2.1",
+            "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+            "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="
+        },
         "envinfo": {
             "version": "7.8.1",
             "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz",
@@ -11840,6 +12481,11 @@
                 "mathquill": "^0.10.1-a"
             }
         },
+        "err-code": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
+            "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="
+        },
         "errno": {
             "version": "0.1.8",
             "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
@@ -11975,6 +12621,28 @@
                 "is-symbol": "^1.0.2"
             }
         },
+        "es5-ext": {
+            "version": "0.10.62",
+            "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
+            "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
+            "dev": true,
+            "requires": {
+                "es6-iterator": "^2.0.3",
+                "es6-symbol": "^3.1.3",
+                "next-tick": "^1.1.0"
+            }
+        },
+        "es6-iterator": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
+            "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
+            "dev": true,
+            "requires": {
+                "d": "1",
+                "es5-ext": "^0.10.35",
+                "es6-symbol": "^3.1.1"
+            }
+        },
         "es6-promise": {
             "version": "3.2.1",
             "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz",
@@ -11986,6 +12654,7 @@
             "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
             "dev": true,
             "requires": {
+                "d": "^1.0.1",
                 "ext": "^1.1.2"
             }
         },
@@ -13470,6 +14139,11 @@
             "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
             "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="
         },
+        "exponential-backoff": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz",
+            "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw=="
+        },
         "express": {
             "version": "4.18.2",
             "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
@@ -14203,6 +14877,58 @@
             "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
             "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA="
         },
+        "foreground-child": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+            "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
+            "requires": {
+                "cross-spawn": "^7.0.0",
+                "signal-exit": "^4.0.1"
+            },
+            "dependencies": {
+                "cross-spawn": {
+                    "version": "7.0.3",
+                    "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+                    "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+                    "requires": {
+                        "path-key": "^3.1.0",
+                        "shebang-command": "^2.0.0",
+                        "which": "^2.0.1"
+                    }
+                },
+                "path-key": {
+                    "version": "3.1.1",
+                    "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+                    "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
+                },
+                "shebang-command": {
+                    "version": "2.0.0",
+                    "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+                    "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+                    "requires": {
+                        "shebang-regex": "^3.0.0"
+                    }
+                },
+                "shebang-regex": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+                    "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
+                },
+                "signal-exit": {
+                    "version": "4.1.0",
+                    "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+                    "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="
+                },
+                "which": {
+                    "version": "2.0.2",
+                    "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+                    "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+                    "requires": {
+                        "isexe": "^2.0.0"
+                    }
+                }
+            }
+        },
         "forever-agent": {
             "version": "0.6.1",
             "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
@@ -14462,6 +15188,11 @@
             "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
             "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="
         },
+        "fuzzy": {
+            "version": "0.1.3",
+            "resolved": "https://registry.npmjs.org/fuzzy/-/fuzzy-0.1.3.tgz",
+            "integrity": "sha512-/gZffu4ykarLrCiP3Ygsa86UAo1E5vEVlvTrpkKywXSbP9Xhln3oSp9QSV57gEq3JFFpGJ4GZ+5zdEp3FcUh4w=="
+        },
         "gauge": {
             "version": "3.0.2",
             "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
@@ -14554,6 +15285,16 @@
             "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
             "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
         },
+        "geojson": {
+            "version": "0.5.0",
+            "resolved": "https://registry.npmjs.org/geojson/-/geojson-0.5.0.tgz",
+            "integrity": "sha512-/Bx5lEn+qRF4TfQ5aLu6NH+UKtvIv7Lhc487y/c8BdludrCTpiWf9wyI0RTyqg49MFefIAvFDuEi5Dfd/zgNxQ=="
+        },
+        "geojson-vt": {
+            "version": "3.2.1",
+            "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz",
+            "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg=="
+        },
         "get-caller-file": {
             "version": "2.0.5",
             "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -14621,6 +15362,11 @@
             "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
             "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4="
         },
+        "gl-matrix": {
+            "version": "3.4.3",
+            "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
+            "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA=="
+        },
         "glob": {
             "version": "7.2.3",
             "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -14893,6 +15639,11 @@
             "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
             "dev": true
         },
+        "grid-index": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz",
+            "integrity": "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA=="
+        },
         "growl": {
             "version": "1.10.5",
             "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
@@ -14941,6 +15692,11 @@
                 "har-schema": "^2.0.0"
             }
         },
+        "hard-rejection": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
+            "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA=="
+        },
         "has": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -15313,6 +16069,31 @@
                 "requires-port": "^1.0.0"
             }
         },
+        "http-proxy-agent": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
+            "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+            "requires": {
+                "@tootallnate/once": "2",
+                "agent-base": "6",
+                "debug": "4"
+            },
+            "dependencies": {
+                "debug": {
+                    "version": "4.3.4",
+                    "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+                    "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+                    "requires": {
+                        "ms": "2.1.2"
+                    }
+                },
+                "ms": {
+                    "version": "2.1.2",
+                    "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+                    "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+                }
+            }
+        },
         "http-proxy-middleware": {
             "version": "0.19.1",
             "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz",
@@ -15378,6 +16159,14 @@
                 }
             }
         },
+        "humanize-ms": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+            "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
+            "requires": {
+                "ms": "^2.0.0"
+            }
+        },
         "hyntax": {
             "version": "1.1.9",
             "resolved": "https://registry.npmjs.org/hyntax/-/hyntax-1.1.9.tgz",
@@ -15438,6 +16227,32 @@
             "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
             "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk="
         },
+        "ignore-walk": {
+            "version": "6.0.4",
+            "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.4.tgz",
+            "integrity": "sha512-t7sv42WkwFkyKbivUCglsQW5YWMskWtbEf4MNKX5u/CCWHKSPzN4FtBQGsQZgCLbxOzpVlcbWVK5KB3auIOjSw==",
+            "requires": {
+                "minimatch": "^9.0.0"
+            },
+            "dependencies": {
+                "brace-expansion": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+                    "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+                    "requires": {
+                        "balanced-match": "^1.0.0"
+                    }
+                },
+                "minimatch": {
+                    "version": "9.0.3",
+                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+                    "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+                    "requires": {
+                        "brace-expansion": "^2.0.1"
+                    }
+                }
+            }
+        },
         "iink-js": {
             "version": "1.5.4",
             "resolved": "https://registry.npmjs.org/iink-js/-/iink-js-1.5.4.tgz",
@@ -15565,6 +16380,11 @@
             "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
             "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg=="
         },
+        "infer-owner": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
+            "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A=="
+        },
         "inflight": {
             "version": "1.0.6",
             "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -15599,6 +16419,30 @@
             "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
             "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
         },
+        "init-package-json": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-5.0.0.tgz",
+            "integrity": "sha512-kBhlSheBfYmq3e0L1ii+VKe3zBTLL5lDCDWR+f9dLmEGSB3MqLlMlsolubSsyI88Bg6EA+BIMlomAnQ1SwgQBw==",
+            "requires": {
+                "npm-package-arg": "^10.0.0",
+                "promzard": "^1.0.0",
+                "read": "^2.0.0",
+                "read-package-json": "^6.0.0",
+                "semver": "^7.3.5",
+                "validate-npm-package-license": "^3.0.4",
+                "validate-npm-package-name": "^5.0.0"
+            },
+            "dependencies": {
+                "semver": {
+                    "version": "7.5.4",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                    "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    }
+                }
+            }
+        },
         "inline-style-parser": {
             "version": "0.1.1",
             "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz",
@@ -16026,6 +16870,14 @@
                 "ci-info": "^1.5.0"
             }
         },
+        "is-cidr": {
+            "version": "4.0.2",
+            "resolved": "https://registry.npmjs.org/is-cidr/-/is-cidr-4.0.2.tgz",
+            "integrity": "sha512-z4a1ENUajDbEl/Q6/pVBpTR1nBjjEE1X7qb7bmWYanNnPoKAvUCPFKeXV6Fe4mgTkWKBqiHIcwsI3SndiO5FeA==",
+            "requires": {
+                "cidr-regex": "^3.1.1"
+            }
+        },
         "is-core-module": {
             "version": "2.11.0",
             "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
@@ -16153,6 +17005,11 @@
                 "is-path-inside": "^1.0.0"
             }
         },
+        "is-lambda": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz",
+            "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="
+        },
         "is-map": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz",
@@ -16424,6 +17281,15 @@
                 "lodash.get": "^4.4.2"
             }
         },
+        "jackspeak": {
+            "version": "2.3.6",
+            "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
+            "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
+            "requires": {
+                "@isaacs/cliui": "^8.0.2",
+                "@pkgjs/parseargs": "^0.11.0"
+            }
+        },
         "jest-worker": {
             "version": "27.5.1",
             "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
@@ -16649,6 +17515,16 @@
             "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
             "dev": true
         },
+        "json-stringify-nice": {
+            "version": "1.1.4",
+            "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz",
+            "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw=="
+        },
+        "json-stringify-pretty-compact": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz",
+            "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA=="
+        },
         "json-stringify-safe": {
             "version": "5.0.1",
             "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
@@ -16679,6 +17555,11 @@
                 "graceful-fs": "^4.1.6"
             }
         },
+        "jsonparse": {
+            "version": "1.3.1",
+            "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
+            "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="
+        },
         "jsonschema": {
             "version": "1.4.1",
             "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz",
@@ -16840,6 +17721,16 @@
                 }
             }
         },
+        "just-diff": {
+            "version": "6.0.2",
+            "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz",
+            "integrity": "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA=="
+        },
+        "just-diff-apply": {
+            "version": "5.5.0",
+            "resolved": "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.5.0.tgz",
+            "integrity": "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw=="
+        },
         "jwa": {
             "version": "1.4.1",
             "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
@@ -16966,14 +17857,373 @@
                 }
             }
         },
-        "levn": {
-            "version": "0.4.1",
-            "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
-            "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
-            "dev": true,
+        "levn": {
+            "version": "0.4.1",
+            "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+            "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+            "dev": true,
+            "requires": {
+                "prelude-ls": "^1.2.1",
+                "type-check": "~0.4.0"
+            }
+        },
+        "libnpmaccess": {
+            "version": "7.0.2",
+            "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-7.0.2.tgz",
+            "integrity": "sha512-vHBVMw1JFMTgEk15zRsJuSAg7QtGGHpUSEfnbcRL1/gTBag9iEfJbyjpDmdJmwMhvpoLoNBtdAUCdGnaP32hhw==",
+            "requires": {
+                "npm-package-arg": "^10.1.0",
+                "npm-registry-fetch": "^14.0.3"
+            }
+        },
+        "libnpmdiff": {
+            "version": "5.0.19",
+            "resolved": "https://registry.npmjs.org/libnpmdiff/-/libnpmdiff-5.0.19.tgz",
+            "integrity": "sha512-caqIA7SzPeyqOn55GodejyEJRIXaFnzuqxrO9uyXtH4soom4wjDAkU97L1WrBSuVtDk3IZQD72daVeT2GqHSjA==",
+            "requires": {
+                "@npmcli/arborist": "^6.3.0",
+                "@npmcli/disparity-colors": "^3.0.0",
+                "@npmcli/installed-package-contents": "^2.0.2",
+                "binary-extensions": "^2.2.0",
+                "diff": "^5.1.0",
+                "minimatch": "^9.0.0",
+                "npm-package-arg": "^10.1.0",
+                "pacote": "^15.0.8",
+                "tar": "^6.1.13"
+            },
+            "dependencies": {
+                "binary-extensions": {
+                    "version": "2.2.0",
+                    "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+                    "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
+                },
+                "brace-expansion": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+                    "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+                    "requires": {
+                        "balanced-match": "^1.0.0"
+                    }
+                },
+                "diff": {
+                    "version": "5.1.0",
+                    "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
+                    "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw=="
+                },
+                "minimatch": {
+                    "version": "9.0.3",
+                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+                    "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+                    "requires": {
+                        "brace-expansion": "^2.0.1"
+                    }
+                }
+            }
+        },
+        "libnpmexec": {
+            "version": "6.0.3",
+            "resolved": "https://registry.npmjs.org/libnpmexec/-/libnpmexec-6.0.3.tgz",
+            "integrity": "sha512-E87xEzxChUe0qZgoqht5D5t13B876rPoTD877v9ZUSMztBFpuChQn5UNO3z5NaeBpEwWq/BAnQfMYRWR6sVAZA==",
+            "requires": {
+                "@npmcli/arborist": "^6.3.0",
+                "@npmcli/run-script": "^6.0.0",
+                "ci-info": "^3.7.1",
+                "npm-package-arg": "^10.1.0",
+                "npmlog": "^7.0.1",
+                "pacote": "^15.0.8",
+                "proc-log": "^3.0.0",
+                "read": "^2.0.0",
+                "read-package-json-fast": "^3.0.2",
+                "semver": "^7.3.7",
+                "walk-up-path": "^3.0.1"
+            },
+            "dependencies": {
+                "ansi-regex": {
+                    "version": "5.0.1",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                },
+                "are-we-there-yet": {
+                    "version": "4.0.1",
+                    "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-4.0.1.tgz",
+                    "integrity": "sha512-2zuA+jpOYBRgoBCfa+fB87Rk0oGJjDX6pxGzqH6f33NzUhG25Xur6R0u0Z9VVAq8Z5JvQpQI6j6rtonuivC8QA==",
+                    "requires": {
+                        "delegates": "^1.0.0",
+                        "readable-stream": "^4.1.0"
+                    }
+                },
+                "buffer": {
+                    "version": "6.0.3",
+                    "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+                    "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+                    "requires": {
+                        "base64-js": "^1.3.1",
+                        "ieee754": "^1.2.1"
+                    }
+                },
+                "ci-info": {
+                    "version": "3.9.0",
+                    "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+                    "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="
+                },
+                "emoji-regex": {
+                    "version": "8.0.0",
+                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                    "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+                },
+                "gauge": {
+                    "version": "5.0.1",
+                    "resolved": "https://registry.npmjs.org/gauge/-/gauge-5.0.1.tgz",
+                    "integrity": "sha512-CmykPMJGuNan/3S4kZOpvvPYSNqSHANiWnh9XcMU2pSjtBfF0XzZ2p1bFAxTbnFxyBuPxQYHhzwaoOmUdqzvxQ==",
+                    "requires": {
+                        "aproba": "^1.0.3 || ^2.0.0",
+                        "color-support": "^1.1.3",
+                        "console-control-strings": "^1.1.0",
+                        "has-unicode": "^2.0.1",
+                        "signal-exit": "^4.0.1",
+                        "string-width": "^4.2.3",
+                        "strip-ansi": "^6.0.1",
+                        "wide-align": "^1.1.5"
+                    }
+                },
+                "is-fullwidth-code-point": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+                    "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+                },
+                "npmlog": {
+                    "version": "7.0.1",
+                    "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-7.0.1.tgz",
+                    "integrity": "sha512-uJ0YFk/mCQpLBt+bxN88AKd+gyqZvZDbtiNxk6Waqcj2aPRyfVx8ITawkyQynxUagInjdYT1+qj4NfA5KJJUxg==",
+                    "requires": {
+                        "are-we-there-yet": "^4.0.0",
+                        "console-control-strings": "^1.1.0",
+                        "gauge": "^5.0.0",
+                        "set-blocking": "^2.0.0"
+                    }
+                },
+                "readable-stream": {
+                    "version": "4.4.2",
+                    "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz",
+                    "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==",
+                    "requires": {
+                        "abort-controller": "^3.0.0",
+                        "buffer": "^6.0.3",
+                        "events": "^3.3.0",
+                        "process": "^0.11.10",
+                        "string_decoder": "^1.3.0"
+                    }
+                },
+                "safe-buffer": {
+                    "version": "5.2.1",
+                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+                    "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+                },
+                "semver": {
+                    "version": "7.5.4",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                    "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    }
+                },
+                "signal-exit": {
+                    "version": "4.1.0",
+                    "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+                    "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="
+                },
+                "string-width": {
+                    "version": "4.2.3",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                    "requires": {
+                        "emoji-regex": "^8.0.0",
+                        "is-fullwidth-code-point": "^3.0.0",
+                        "strip-ansi": "^6.0.1"
+                    }
+                },
+                "string_decoder": {
+                    "version": "1.3.0",
+                    "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+                    "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+                    "requires": {
+                        "safe-buffer": "~5.2.0"
+                    }
+                },
+                "strip-ansi": {
+                    "version": "6.0.1",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                    "requires": {
+                        "ansi-regex": "^5.0.1"
+                    }
+                },
+                "wide-align": {
+                    "version": "1.1.5",
+                    "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
+                    "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
+                    "requires": {
+                        "string-width": "^1.0.2 || 2 || 3 || 4"
+                    }
+                }
+            }
+        },
+        "libnpmfund": {
+            "version": "4.0.19",
+            "resolved": "https://registry.npmjs.org/libnpmfund/-/libnpmfund-4.0.19.tgz",
+            "integrity": "sha512-g2XV/oqBLo0Mau/nmqvIoNHRmAQqzSvSjSR9npO0+buEqGmyRHDeQJKDI3RxpLcQgd0IuNNAoTjXXpoKcX90EQ==",
+            "requires": {
+                "@npmcli/arborist": "^6.3.0"
+            }
+        },
+        "libnpmhook": {
+            "version": "9.0.3",
+            "resolved": "https://registry.npmjs.org/libnpmhook/-/libnpmhook-9.0.3.tgz",
+            "integrity": "sha512-wMZe58sI7KLhg0+nUWZW5KdMfjNNcOIIbkoP19BDHYoUF9El7eeUWkGNxUGzpHkPKiGoQ1z/v6CYin4deebeuw==",
+            "requires": {
+                "aproba": "^2.0.0",
+                "npm-registry-fetch": "^14.0.3"
+            }
+        },
+        "libnpmorg": {
+            "version": "5.0.4",
+            "resolved": "https://registry.npmjs.org/libnpmorg/-/libnpmorg-5.0.4.tgz",
+            "integrity": "sha512-YqYXLMAN0Y1eJH4w3hUFN9648xfSdvJANMsdeZTOWJOW4Pqp8qapJFzQdqCfUkg+tEuQmnaFQQKXvkMZC51+Mw==",
+            "requires": {
+                "aproba": "^2.0.0",
+                "npm-registry-fetch": "^14.0.3"
+            }
+        },
+        "libnpmpack": {
+            "version": "5.0.19",
+            "resolved": "https://registry.npmjs.org/libnpmpack/-/libnpmpack-5.0.19.tgz",
+            "integrity": "sha512-xxkROnxTZF3imCJ9ve+6ELtRYvOBMwvrKlMGJx6JhmvD5lqIPGOJpY8oY+w8XLmLX1N06scYuLonkFpF2ayrjQ==",
+            "requires": {
+                "@npmcli/arborist": "^6.3.0",
+                "@npmcli/run-script": "^6.0.0",
+                "npm-package-arg": "^10.1.0",
+                "pacote": "^15.0.8"
+            }
+        },
+        "libnpmpublish": {
+            "version": "7.5.0",
+            "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-7.5.0.tgz",
+            "integrity": "sha512-zctH6QcTJ093lpxmkufr2zr3AJ9V90hcRilDFNin6n91ODj+S28RdyMFFJpa9NwyztmyV2hlWLyZv0GaOQBDyA==",
+            "requires": {
+                "ci-info": "^3.6.1",
+                "normalize-package-data": "^5.0.0",
+                "npm-package-arg": "^10.1.0",
+                "npm-registry-fetch": "^14.0.3",
+                "proc-log": "^3.0.0",
+                "semver": "^7.3.7",
+                "sigstore": "^1.4.0",
+                "ssri": "^10.0.1"
+            },
+            "dependencies": {
+                "ci-info": {
+                    "version": "3.9.0",
+                    "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+                    "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="
+                },
+                "hosted-git-info": {
+                    "version": "6.1.1",
+                    "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz",
+                    "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==",
+                    "requires": {
+                        "lru-cache": "^7.5.1"
+                    }
+                },
+                "lru-cache": {
+                    "version": "7.18.3",
+                    "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+                    "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="
+                },
+                "minipass": {
+                    "version": "7.0.4",
+                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                    "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ=="
+                },
+                "normalize-package-data": {
+                    "version": "5.0.0",
+                    "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz",
+                    "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==",
+                    "requires": {
+                        "hosted-git-info": "^6.0.0",
+                        "is-core-module": "^2.8.1",
+                        "semver": "^7.3.5",
+                        "validate-npm-package-license": "^3.0.4"
+                    }
+                },
+                "semver": {
+                    "version": "7.5.4",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                    "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    },
+                    "dependencies": {
+                        "lru-cache": {
+                            "version": "6.0.0",
+                            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+                            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+                            "requires": {
+                                "yallist": "^4.0.0"
+                            }
+                        }
+                    }
+                },
+                "ssri": {
+                    "version": "10.0.5",
+                    "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.5.tgz",
+                    "integrity": "sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==",
+                    "requires": {
+                        "minipass": "^7.0.3"
+                    }
+                }
+            }
+        },
+        "libnpmsearch": {
+            "version": "6.0.2",
+            "resolved": "https://registry.npmjs.org/libnpmsearch/-/libnpmsearch-6.0.2.tgz",
+            "integrity": "sha512-p+5BF19AvnVg8mcIQhy6yWhI6jHQRVMYaIaKeITEfYAffWsqbottA/WZdMtHL76hViC6SFM1WdclM1w5eAIa1g==",
+            "requires": {
+                "npm-registry-fetch": "^14.0.3"
+            }
+        },
+        "libnpmteam": {
+            "version": "5.0.3",
+            "resolved": "https://registry.npmjs.org/libnpmteam/-/libnpmteam-5.0.3.tgz",
+            "integrity": "sha512-7XOGhi45s+ml6TyrhJUTyrErcoDMKGKfEtiTEco4ofU7BGGAUOalVztKMVLLJgJOOXdIAIlzCHqkTXEuSiyCiA==",
             "requires": {
-                "prelude-ls": "^1.2.1",
-                "type-check": "~0.4.0"
+                "aproba": "^2.0.0",
+                "npm-registry-fetch": "^14.0.3"
+            }
+        },
+        "libnpmversion": {
+            "version": "4.0.2",
+            "resolved": "https://registry.npmjs.org/libnpmversion/-/libnpmversion-4.0.2.tgz",
+            "integrity": "sha512-n1X70mFHv8Piy4yos+MFWUARSkTbyV5cdsHScaIkuwYvRAF/s2VtYScDzWB4Oe8uNEuGNdjiRR1E/Dh1tMvv6g==",
+            "requires": {
+                "@npmcli/git": "^4.0.1",
+                "@npmcli/run-script": "^6.0.0",
+                "json-parse-even-better-errors": "^3.0.0",
+                "proc-log": "^3.0.0",
+                "semver": "^7.3.7"
+            },
+            "dependencies": {
+                "json-parse-even-better-errors": {
+                    "version": "3.0.1",
+                    "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz",
+                    "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg=="
+                },
+                "semver": {
+                    "version": "7.5.4",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                    "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    }
+                }
             }
         },
         "lie": {
@@ -17286,6 +18536,166 @@
             "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
             "dev": true
         },
+        "make-fetch-happen": {
+            "version": "11.1.1",
+            "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz",
+            "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==",
+            "requires": {
+                "agentkeepalive": "^4.2.1",
+                "cacache": "^17.0.0",
+                "http-cache-semantics": "^4.1.1",
+                "http-proxy-agent": "^5.0.0",
+                "https-proxy-agent": "^5.0.0",
+                "is-lambda": "^1.0.1",
+                "lru-cache": "^7.7.1",
+                "minipass": "^5.0.0",
+                "minipass-fetch": "^3.0.0",
+                "minipass-flush": "^1.0.5",
+                "minipass-pipeline": "^1.2.4",
+                "negotiator": "^0.6.3",
+                "promise-retry": "^2.0.1",
+                "socks-proxy-agent": "^7.0.0",
+                "ssri": "^10.0.0"
+            },
+            "dependencies": {
+                "brace-expansion": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+                    "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+                    "requires": {
+                        "balanced-match": "^1.0.0"
+                    }
+                },
+                "cacache": {
+                    "version": "17.1.4",
+                    "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.4.tgz",
+                    "integrity": "sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==",
+                    "requires": {
+                        "@npmcli/fs": "^3.1.0",
+                        "fs-minipass": "^3.0.0",
+                        "glob": "^10.2.2",
+                        "lru-cache": "^7.7.1",
+                        "minipass": "^7.0.3",
+                        "minipass-collect": "^1.0.2",
+                        "minipass-flush": "^1.0.5",
+                        "minipass-pipeline": "^1.2.4",
+                        "p-map": "^4.0.0",
+                        "ssri": "^10.0.0",
+                        "tar": "^6.1.11",
+                        "unique-filename": "^3.0.0"
+                    },
+                    "dependencies": {
+                        "minipass": {
+                            "version": "7.0.4",
+                            "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                            "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ=="
+                        }
+                    }
+                },
+                "fs-minipass": {
+                    "version": "3.0.3",
+                    "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz",
+                    "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==",
+                    "requires": {
+                        "minipass": "^7.0.3"
+                    },
+                    "dependencies": {
+                        "minipass": {
+                            "version": "7.0.4",
+                            "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                            "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ=="
+                        }
+                    }
+                },
+                "glob": {
+                    "version": "10.3.10",
+                    "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
+                    "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+                    "requires": {
+                        "foreground-child": "^3.1.0",
+                        "jackspeak": "^2.3.5",
+                        "minimatch": "^9.0.1",
+                        "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
+                        "path-scurry": "^1.10.1"
+                    }
+                },
+                "lru-cache": {
+                    "version": "7.18.3",
+                    "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+                    "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="
+                },
+                "minimatch": {
+                    "version": "9.0.3",
+                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+                    "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+                    "requires": {
+                        "brace-expansion": "^2.0.1"
+                    }
+                },
+                "minipass": {
+                    "version": "5.0.0",
+                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+                    "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="
+                },
+                "minipass-fetch": {
+                    "version": "3.0.4",
+                    "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz",
+                    "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==",
+                    "requires": {
+                        "encoding": "^0.1.13",
+                        "minipass": "^7.0.3",
+                        "minipass-sized": "^1.0.3",
+                        "minizlib": "^2.1.2"
+                    },
+                    "dependencies": {
+                        "minipass": {
+                            "version": "7.0.4",
+                            "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                            "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ=="
+                        }
+                    }
+                },
+                "p-map": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+                    "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+                    "requires": {
+                        "aggregate-error": "^3.0.0"
+                    }
+                },
+                "ssri": {
+                    "version": "10.0.5",
+                    "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.5.tgz",
+                    "integrity": "sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==",
+                    "requires": {
+                        "minipass": "^7.0.3"
+                    },
+                    "dependencies": {
+                        "minipass": {
+                            "version": "7.0.4",
+                            "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                            "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ=="
+                        }
+                    }
+                },
+                "unique-filename": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz",
+                    "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==",
+                    "requires": {
+                        "unique-slug": "^4.0.0"
+                    }
+                },
+                "unique-slug": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz",
+                    "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==",
+                    "requires": {
+                        "imurmurhash": "^0.1.4"
+                    }
+                }
+            }
+        },
         "map-cache": {
             "version": "0.2.2",
             "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
@@ -17304,6 +18714,51 @@
                 "object-visit": "^1.0.0"
             }
         },
+        "mapbox-gl": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.0.1.tgz",
+            "integrity": "sha512-o7C6sAlj6Hkdd4xQVEgQflgJYNYyZOAtYahhIOb9m8chI8umtWcCp8Ie0iGLYJvce1WHRMa3WGzs69ggwuWlDA==",
+            "requires": {
+                "@mapbox/geojson-rewind": "^0.5.2",
+                "@mapbox/jsonlint-lines-primitives": "^2.0.2",
+                "@mapbox/mapbox-gl-supported": "^2.0.1",
+                "@mapbox/point-geometry": "^0.1.0",
+                "@mapbox/tiny-sdf": "^2.0.6",
+                "@mapbox/unitbezier": "^0.0.1",
+                "@mapbox/vector-tile": "^1.3.1",
+                "@mapbox/whoots-js": "^3.1.0",
+                "cheap-ruler": "^3.0.1",
+                "csscolorparser": "~1.0.3",
+                "earcut": "^2.2.4",
+                "geojson-vt": "^3.2.1",
+                "gl-matrix": "^3.4.3",
+                "grid-index": "^1.1.0",
+                "kdbush": "^4.0.1",
+                "murmurhash-js": "^1.0.0",
+                "pbf": "^3.2.1",
+                "potpack": "^2.0.0",
+                "quickselect": "^2.0.0",
+                "rw": "^1.3.3",
+                "supercluster": "^8.0.0",
+                "tinyqueue": "^2.0.3",
+                "vt-pbf": "^3.1.3"
+            },
+            "dependencies": {
+                "kdbush": {
+                    "version": "4.0.2",
+                    "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
+                    "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA=="
+                },
+                "supercluster": {
+                    "version": "8.0.1",
+                    "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz",
+                    "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==",
+                    "requires": {
+                        "kdbush": "^4.0.2"
+                    }
+                }
+            }
+        },
         "markdown-table": {
             "version": "3.0.3",
             "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz",
@@ -17999,6 +19454,11 @@
             "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
             "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA=="
         },
+        "min-indent": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+            "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="
+        },
         "minimalistic-assert": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -18018,11 +19478,145 @@
             "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
             "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
         },
+        "minimist-options": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz",
+            "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
+            "requires": {
+                "arrify": "^1.0.1",
+                "is-plain-obj": "^1.1.0",
+                "kind-of": "^6.0.3"
+            },
+            "dependencies": {
+                "arrify": {
+                    "version": "1.0.1",
+                    "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+                    "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA=="
+                },
+                "is-plain-obj": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+                    "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="
+                }
+            }
+        },
         "minipass": {
             "version": "4.0.3",
             "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.3.tgz",
             "integrity": "sha512-OW2r4sQ0sI+z5ckEt5c1Tri4xTgZwYDxpE54eqWlQloQRoWtXjqt9udJ5Z4dSv7wK+nfFI7FRXyCpBSft+gpFw=="
         },
+        "minipass-collect": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
+            "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
+            "requires": {
+                "minipass": "^3.0.0"
+            },
+            "dependencies": {
+                "minipass": {
+                    "version": "3.3.6",
+                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                    "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+                    "requires": {
+                        "yallist": "^4.0.0"
+                    }
+                }
+            }
+        },
+        "minipass-fetch": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz",
+            "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==",
+            "requires": {
+                "encoding": "^0.1.13",
+                "minipass": "^3.1.6",
+                "minipass-sized": "^1.0.3",
+                "minizlib": "^2.1.2"
+            },
+            "dependencies": {
+                "minipass": {
+                    "version": "3.3.6",
+                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                    "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+                    "requires": {
+                        "yallist": "^4.0.0"
+                    }
+                }
+            }
+        },
+        "minipass-flush": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
+            "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
+            "requires": {
+                "minipass": "^3.0.0"
+            },
+            "dependencies": {
+                "minipass": {
+                    "version": "3.3.6",
+                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                    "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+                    "requires": {
+                        "yallist": "^4.0.0"
+                    }
+                }
+            }
+        },
+        "minipass-json-stream": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz",
+            "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==",
+            "requires": {
+                "jsonparse": "^1.3.1",
+                "minipass": "^3.0.0"
+            },
+            "dependencies": {
+                "minipass": {
+                    "version": "3.3.6",
+                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                    "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+                    "requires": {
+                        "yallist": "^4.0.0"
+                    }
+                }
+            }
+        },
+        "minipass-pipeline": {
+            "version": "1.2.4",
+            "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
+            "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
+            "requires": {
+                "minipass": "^3.0.0"
+            },
+            "dependencies": {
+                "minipass": {
+                    "version": "3.3.6",
+                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                    "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+                    "requires": {
+                        "yallist": "^4.0.0"
+                    }
+                }
+            }
+        },
+        "minipass-sized": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz",
+            "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==",
+            "requires": {
+                "minipass": "^3.0.0"
+            },
+            "dependencies": {
+                "minipass": {
+                    "version": "3.3.6",
+                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                    "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+                    "requires": {
+                        "yallist": "^4.0.0"
+                    }
+                }
+            }
+        },
         "minizlib": {
             "version": "2.1.2",
             "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
@@ -18442,6 +20036,11 @@
             "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
             "dev": true
         },
+        "murmurhash-js": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz",
+            "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw=="
+        },
         "mute-stream": {
             "version": "0.0.8",
             "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
@@ -22582,24 +24181,181 @@
                             "resolved": false,
                             "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
                             "requires": {
-                                "ansi-regex": "^4.1.0"
+                                "ansi-regex": "^4.1.0"
+                            }
+                        }
+                    }
+                },
+                "yargs-parser": {
+                    "version": "15.0.1",
+                    "resolved": false,
+                    "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==",
+                    "requires": {
+                        "camelcase": "^5.0.0",
+                        "decamelize": "^1.2.0"
+                    },
+                    "dependencies": {
+                        "camelcase": {
+                            "version": "5.3.1",
+                            "resolved": false,
+                            "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
+                        }
+                    }
+                }
+            }
+        },
+        "npm-audit-report": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/npm-audit-report/-/npm-audit-report-5.0.0.tgz",
+            "integrity": "sha512-EkXrzat7zERmUhHaoren1YhTxFwsOu5jypE84k6632SXTHcQE1z8V51GC6GVZt8LxkC+tbBcKMUBZAgk8SUSbw=="
+        },
+        "npm-bundled": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz",
+            "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==",
+            "requires": {
+                "npm-normalize-package-bin": "^3.0.0"
+            }
+        },
+        "npm-install-checks": {
+            "version": "6.1.1",
+            "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.1.1.tgz",
+            "integrity": "sha512-dH3GmQL4vsPtld59cOn8uY0iOqRmqKvV+DLGwNXV/Q7MDgD2QfOADWd/mFXcIE5LVhYYGjA3baz6W9JneqnuCw==",
+            "requires": {
+                "semver": "^7.1.1"
+            },
+            "dependencies": {
+                "semver": {
+                    "version": "7.5.4",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                    "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    }
+                }
+            }
+        },
+        "npm-normalize-package-bin": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz",
+            "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ=="
+        },
+        "npm-package-arg": {
+            "version": "10.1.0",
+            "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz",
+            "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==",
+            "requires": {
+                "hosted-git-info": "^6.0.0",
+                "proc-log": "^3.0.0",
+                "semver": "^7.3.5",
+                "validate-npm-package-name": "^5.0.0"
+            },
+            "dependencies": {
+                "hosted-git-info": {
+                    "version": "6.1.1",
+                    "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz",
+                    "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==",
+                    "requires": {
+                        "lru-cache": "^7.5.1"
+                    }
+                },
+                "lru-cache": {
+                    "version": "7.18.3",
+                    "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+                    "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="
+                },
+                "semver": {
+                    "version": "7.5.4",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                    "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    },
+                    "dependencies": {
+                        "lru-cache": {
+                            "version": "6.0.0",
+                            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+                            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+                            "requires": {
+                                "yallist": "^4.0.0"
                             }
                         }
                     }
+                }
+            }
+        },
+        "npm-packlist": {
+            "version": "7.0.4",
+            "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz",
+            "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==",
+            "requires": {
+                "ignore-walk": "^6.0.0"
+            }
+        },
+        "npm-pick-manifest": {
+            "version": "8.0.1",
+            "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz",
+            "integrity": "sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA==",
+            "requires": {
+                "npm-install-checks": "^6.0.0",
+                "npm-normalize-package-bin": "^3.0.0",
+                "npm-package-arg": "^10.0.0",
+                "semver": "^7.3.5"
+            },
+            "dependencies": {
+                "semver": {
+                    "version": "7.5.4",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                    "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    }
+                }
+            }
+        },
+        "npm-profile": {
+            "version": "7.0.1",
+            "resolved": "https://registry.npmjs.org/npm-profile/-/npm-profile-7.0.1.tgz",
+            "integrity": "sha512-VReArOY/fCx5dWL66cbJ2OMogTQAVVQA//8jjmjkarboki3V7UJ0XbGFW+khRwiAJFQjuH0Bqr/yF7Y5RZdkMQ==",
+            "requires": {
+                "npm-registry-fetch": "^14.0.0",
+                "proc-log": "^3.0.0"
+            }
+        },
+        "npm-registry-fetch": {
+            "version": "14.0.5",
+            "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz",
+            "integrity": "sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==",
+            "requires": {
+                "make-fetch-happen": "^11.0.0",
+                "minipass": "^5.0.0",
+                "minipass-fetch": "^3.0.0",
+                "minipass-json-stream": "^1.0.1",
+                "minizlib": "^2.1.2",
+                "npm-package-arg": "^10.0.0",
+                "proc-log": "^3.0.0"
+            },
+            "dependencies": {
+                "minipass": {
+                    "version": "5.0.0",
+                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+                    "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="
                 },
-                "yargs-parser": {
-                    "version": "15.0.1",
-                    "resolved": false,
-                    "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==",
+                "minipass-fetch": {
+                    "version": "3.0.4",
+                    "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz",
+                    "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==",
                     "requires": {
-                        "camelcase": "^5.0.0",
-                        "decamelize": "^1.2.0"
+                        "encoding": "^0.1.13",
+                        "minipass": "^7.0.3",
+                        "minipass-sized": "^1.0.3",
+                        "minizlib": "^2.1.2"
                     },
                     "dependencies": {
-                        "camelcase": {
-                            "version": "5.3.1",
-                            "resolved": false,
-                            "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
+                        "minipass": {
+                            "version": "7.0.4",
+                            "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                            "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ=="
                         }
                     }
                 }
@@ -22613,6 +24369,11 @@
                 "path-key": "^2.0.0"
             }
         },
+        "npm-user-validate": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/npm-user-validate/-/npm-user-validate-2.0.0.tgz",
+            "integrity": "sha512-sSWeqAYJ2dUPStJB+AEj0DyLRltr/f6YNcvCA7phkB8/RMLMnVsQ41GMwHo/ERZLYNDsyB2wPm7pZo1mqPOl7Q=="
+        },
         "npmlog": {
             "version": "5.0.1",
             "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
@@ -23058,6 +24819,151 @@
                 }
             }
         },
+        "pacote": {
+            "version": "15.2.0",
+            "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.2.0.tgz",
+            "integrity": "sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA==",
+            "requires": {
+                "@npmcli/git": "^4.0.0",
+                "@npmcli/installed-package-contents": "^2.0.1",
+                "@npmcli/promise-spawn": "^6.0.1",
+                "@npmcli/run-script": "^6.0.0",
+                "cacache": "^17.0.0",
+                "fs-minipass": "^3.0.0",
+                "minipass": "^5.0.0",
+                "npm-package-arg": "^10.0.0",
+                "npm-packlist": "^7.0.0",
+                "npm-pick-manifest": "^8.0.0",
+                "npm-registry-fetch": "^14.0.0",
+                "proc-log": "^3.0.0",
+                "promise-retry": "^2.0.1",
+                "read-package-json": "^6.0.0",
+                "read-package-json-fast": "^3.0.0",
+                "sigstore": "^1.3.0",
+                "ssri": "^10.0.0",
+                "tar": "^6.1.11"
+            },
+            "dependencies": {
+                "brace-expansion": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+                    "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+                    "requires": {
+                        "balanced-match": "^1.0.0"
+                    }
+                },
+                "cacache": {
+                    "version": "17.1.4",
+                    "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.4.tgz",
+                    "integrity": "sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==",
+                    "requires": {
+                        "@npmcli/fs": "^3.1.0",
+                        "fs-minipass": "^3.0.0",
+                        "glob": "^10.2.2",
+                        "lru-cache": "^7.7.1",
+                        "minipass": "^7.0.3",
+                        "minipass-collect": "^1.0.2",
+                        "minipass-flush": "^1.0.5",
+                        "minipass-pipeline": "^1.2.4",
+                        "p-map": "^4.0.0",
+                        "ssri": "^10.0.0",
+                        "tar": "^6.1.11",
+                        "unique-filename": "^3.0.0"
+                    },
+                    "dependencies": {
+                        "minipass": {
+                            "version": "7.0.4",
+                            "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                            "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ=="
+                        }
+                    }
+                },
+                "fs-minipass": {
+                    "version": "3.0.3",
+                    "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz",
+                    "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==",
+                    "requires": {
+                        "minipass": "^7.0.3"
+                    },
+                    "dependencies": {
+                        "minipass": {
+                            "version": "7.0.4",
+                            "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                            "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ=="
+                        }
+                    }
+                },
+                "glob": {
+                    "version": "10.3.10",
+                    "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
+                    "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+                    "requires": {
+                        "foreground-child": "^3.1.0",
+                        "jackspeak": "^2.3.5",
+                        "minimatch": "^9.0.1",
+                        "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
+                        "path-scurry": "^1.10.1"
+                    }
+                },
+                "lru-cache": {
+                    "version": "7.18.3",
+                    "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+                    "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="
+                },
+                "minimatch": {
+                    "version": "9.0.3",
+                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+                    "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+                    "requires": {
+                        "brace-expansion": "^2.0.1"
+                    }
+                },
+                "minipass": {
+                    "version": "5.0.0",
+                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+                    "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="
+                },
+                "p-map": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+                    "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+                    "requires": {
+                        "aggregate-error": "^3.0.0"
+                    }
+                },
+                "ssri": {
+                    "version": "10.0.5",
+                    "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.5.tgz",
+                    "integrity": "sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==",
+                    "requires": {
+                        "minipass": "^7.0.3"
+                    },
+                    "dependencies": {
+                        "minipass": {
+                            "version": "7.0.4",
+                            "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                            "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ=="
+                        }
+                    }
+                },
+                "unique-filename": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz",
+                    "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==",
+                    "requires": {
+                        "unique-slug": "^4.0.0"
+                    }
+                },
+                "unique-slug": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz",
+                    "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==",
+                    "requires": {
+                        "imurmurhash": "^0.1.4"
+                    }
+                }
+            }
+        },
         "pako": {
             "version": "1.0.11",
             "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@@ -23108,6 +25014,23 @@
                 "callsites": "^3.0.0"
             }
         },
+        "parse-conflict-json": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-3.0.1.tgz",
+            "integrity": "sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw==",
+            "requires": {
+                "json-parse-even-better-errors": "^3.0.0",
+                "just-diff": "^6.0.0",
+                "just-diff-apply": "^5.2.0"
+            },
+            "dependencies": {
+                "json-parse-even-better-errors": {
+                    "version": "3.0.1",
+                    "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz",
+                    "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg=="
+                }
+            }
+        },
         "parse-json": {
             "version": "5.2.0",
             "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
@@ -23230,6 +25153,27 @@
             "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
             "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
         },
+        "path-scurry": {
+            "version": "1.10.1",
+            "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
+            "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
+            "requires": {
+                "lru-cache": "^9.1.1 || ^10.0.0",
+                "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+            },
+            "dependencies": {
+                "lru-cache": {
+                    "version": "10.1.0",
+                    "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz",
+                    "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag=="
+                },
+                "minipass": {
+                    "version": "7.0.4",
+                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                    "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ=="
+                }
+            }
+        },
         "path-to-regexp": {
             "version": "0.1.7",
             "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
@@ -23250,6 +25194,15 @@
             "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
             "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10="
         },
+        "pbf": {
+            "version": "3.2.1",
+            "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz",
+            "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==",
+            "requires": {
+                "ieee754": "^1.1.12",
+                "resolve-protobuf-schema": "^2.1.0"
+            }
+        },
         "pdf-parse": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.1.tgz",
@@ -23522,7 +25475,6 @@
             "version": "6.0.11",
             "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz",
             "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==",
-            "dev": true,
             "requires": {
                 "cssesc": "^3.0.0",
                 "util-deprecate": "^1.0.2"
@@ -23533,6 +25485,11 @@
             "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
             "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ=="
         },
+        "potpack": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz",
+            "integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw=="
+        },
         "prebuild-install": {
             "version": "5.3.6",
             "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.6.tgz",
@@ -23658,12 +25615,6 @@
             "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
             "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
         },
-        "prettier": {
-            "version": "2.8.4",
-            "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz",
-            "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==",
-            "dev": true
-        },
         "prettier-linter-helpers": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
@@ -23702,6 +25653,11 @@
                 }
             }
         },
+        "proc-log": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz",
+            "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A=="
+        },
         "process": {
             "version": "0.11.10",
             "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@@ -23725,11 +25681,37 @@
                 "asap": "~2.0.3"
             }
         },
+        "promise-all-reject-late": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz",
+            "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw=="
+        },
+        "promise-call-limit": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-1.0.2.tgz",
+            "integrity": "sha512-1vTUnfI2hzui8AEIixbdAJlFY4LFDXqQswy/2eOlThAscXCY4It8FdVuI0fMJGAB2aWGbdQf/gv0skKYXmdrHA=="
+        },
         "promise-inflight": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
-            "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
-            "dev": true
+            "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM="
+        },
+        "promise-retry": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
+            "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
+            "requires": {
+                "err-code": "^2.0.2",
+                "retry": "^0.12.0"
+            }
+        },
+        "promzard": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/promzard/-/promzard-1.0.0.tgz",
+            "integrity": "sha512-KQVDEubSUHGSt5xLakaToDFrSoZhStB8dXLzk2xvwR67gJktrHFvpR63oZgHyK19WKbHFLXJqCPXdVR3aBP8Ig==",
+            "requires": {
+                "read": "^2.0.0"
+            }
         },
         "prop-types": {
             "version": "15.8.1",
@@ -23867,6 +25849,11 @@
                 "prosemirror-transform": "^1.1.0"
             }
         },
+        "protocol-buffers-schema": {
+            "version": "3.6.0",
+            "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
+            "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw=="
+        },
         "proxy-addr": {
             "version": "2.0.7",
             "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -24129,6 +26116,11 @@
             "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
             "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
         },
+        "qrcode-terminal": {
+            "version": "0.12.0",
+            "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz",
+            "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ=="
+        },
         "qs": {
             "version": "6.5.3",
             "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
@@ -24167,6 +26159,11 @@
             "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
             "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="
         },
+        "quickselect": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
+            "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="
+        },
         "raf-schd": {
             "version": "4.0.3",
             "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
@@ -24573,6 +26570,15 @@
             "resolved": "https://registry.npmjs.org/react-loading/-/react-loading-2.0.3.tgz",
             "integrity": "sha512-Vdqy79zq+bpeWJqC+xjltUjuGApyoItPgL0vgVfcJHhqwU7bAMKzysfGW/ADu6i0z0JiOCRJjo+IkFNkRNbA3A=="
         },
+        "react-map-gl": {
+            "version": "7.1.6",
+            "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-7.1.6.tgz",
+            "integrity": "sha512-9XbrvpFF67Fyi+e6vRLJFnGpo3UF6ZHifIa8cS/wUYSsnv9sVyzGsN++FJy57zkz3Jh3kmf0xKZemR8K0FZLVw==",
+            "requires": {
+                "@maplibre/maplibre-gl-style-spec": "^19.2.1",
+                "@types/mapbox-gl": ">=1.0.0"
+            }
+        },
         "react-markdown": {
             "version": "8.0.5",
             "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.5.tgz",
@@ -24807,39 +26813,175 @@
                     "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
                     "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
                 },
-                "dom-helpers": {
-                    "version": "5.2.1",
-                    "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
-                    "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+                "dom-helpers": {
+                    "version": "5.2.1",
+                    "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+                    "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+                    "requires": {
+                        "@babel/runtime": "^7.8.7",
+                        "csstype": "^3.0.2"
+                    }
+                }
+            }
+        },
+        "react-typist": {
+            "version": "2.0.5",
+            "resolved": "https://registry.npmjs.org/react-typist/-/react-typist-2.0.5.tgz",
+            "integrity": "sha512-iZCkeqeegO0TlkTMiH2JD1tvMtY9RrXkRylnAI6m8aCVAUUwNzoWTVF7CKLij6THeOMcUDCznLDDvNp55s+YZA==",
+            "requires": {
+                "prop-types": "^15.5.10"
+            }
+        },
+        "react-use-measure": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz",
+            "integrity": "sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==",
+            "requires": {
+                "debounce": "^1.2.1"
+            },
+            "dependencies": {
+                "debounce": {
+                    "version": "1.2.1",
+                    "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
+                    "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="
+                }
+            }
+        },
+        "reactcss": {
+            "version": "1.2.3",
+            "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
+            "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==",
+            "requires": {
+                "lodash": "^4.0.1"
+            }
+        },
+        "read": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/read/-/read-2.1.0.tgz",
+            "integrity": "sha512-bvxi1QLJHcaywCAEsAk4DG3nVoqiY2Csps3qzWalhj5hFqRn1d/OixkFXtLO1PrgHUcAP0FNaSY/5GYNfENFFQ==",
+            "requires": {
+                "mute-stream": "~1.0.0"
+            },
+            "dependencies": {
+                "mute-stream": {
+                    "version": "1.0.0",
+                    "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz",
+                    "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA=="
+                }
+            }
+        },
+        "read-cmd-shim": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz",
+            "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q=="
+        },
+        "read-package-json": {
+            "version": "6.0.4",
+            "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz",
+            "integrity": "sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==",
+            "requires": {
+                "glob": "^10.2.2",
+                "json-parse-even-better-errors": "^3.0.0",
+                "normalize-package-data": "^5.0.0",
+                "npm-normalize-package-bin": "^3.0.0"
+            },
+            "dependencies": {
+                "brace-expansion": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+                    "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+                    "requires": {
+                        "balanced-match": "^1.0.0"
+                    }
+                },
+                "glob": {
+                    "version": "10.3.10",
+                    "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
+                    "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+                    "requires": {
+                        "foreground-child": "^3.1.0",
+                        "jackspeak": "^2.3.5",
+                        "minimatch": "^9.0.1",
+                        "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
+                        "path-scurry": "^1.10.1"
+                    }
+                },
+                "hosted-git-info": {
+                    "version": "6.1.1",
+                    "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz",
+                    "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==",
+                    "requires": {
+                        "lru-cache": "^7.5.1"
+                    }
+                },
+                "json-parse-even-better-errors": {
+                    "version": "3.0.1",
+                    "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz",
+                    "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg=="
+                },
+                "lru-cache": {
+                    "version": "7.18.3",
+                    "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+                    "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="
+                },
+                "minimatch": {
+                    "version": "9.0.3",
+                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+                    "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
                     "requires": {
-                        "@babel/runtime": "^7.8.7",
-                        "csstype": "^3.0.2"
+                        "brace-expansion": "^2.0.1"
+                    }
+                },
+                "minipass": {
+                    "version": "7.0.4",
+                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                    "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ=="
+                },
+                "normalize-package-data": {
+                    "version": "5.0.0",
+                    "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz",
+                    "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==",
+                    "requires": {
+                        "hosted-git-info": "^6.0.0",
+                        "is-core-module": "^2.8.1",
+                        "semver": "^7.3.5",
+                        "validate-npm-package-license": "^3.0.4"
+                    }
+                },
+                "semver": {
+                    "version": "7.5.4",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+                    "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+                    "requires": {
+                        "lru-cache": "^6.0.0"
+                    },
+                    "dependencies": {
+                        "lru-cache": {
+                            "version": "6.0.0",
+                            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+                            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+                            "requires": {
+                                "yallist": "^4.0.0"
+                            }
+                        }
                     }
                 }
             }
         },
-        "react-typist": {
-            "version": "2.0.5",
-            "resolved": "https://registry.npmjs.org/react-typist/-/react-typist-2.0.5.tgz",
-            "integrity": "sha512-iZCkeqeegO0TlkTMiH2JD1tvMtY9RrXkRylnAI6m8aCVAUUwNzoWTVF7CKLij6THeOMcUDCznLDDvNp55s+YZA==",
-            "requires": {
-                "prop-types": "^15.5.10"
-            }
-        },
-        "react-use-measure": {
-            "version": "2.1.1",
-            "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz",
-            "integrity": "sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==",
-            "requires": {
-                "debounce": "^1.2.1"
-            }
-        },
-        "reactcss": {
-            "version": "1.2.3",
-            "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
-            "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==",
+        "read-package-json-fast": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz",
+            "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==",
             "requires": {
-                "lodash": "^4.0.1"
+                "json-parse-even-better-errors": "^3.0.0",
+                "npm-normalize-package-bin": "^3.0.0"
+            },
+            "dependencies": {
+                "json-parse-even-better-errors": {
+                    "version": "3.0.1",
+                    "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz",
+                    "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg=="
+                }
             }
         },
         "read-pkg": {
@@ -25438,6 +27580,14 @@
             "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
             "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
         },
+        "resolve-protobuf-schema": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
+            "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==",
+            "requires": {
+                "protocol-buffers-schema": "^3.3.1"
+            }
+        },
         "resolve-url": {
             "version": "0.2.1",
             "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@@ -25469,8 +27619,7 @@
         "retry": {
             "version": "0.12.0",
             "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
-            "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=",
-            "dev": true
+            "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs="
         },
         "reusify": {
             "version": "1.0.4",
@@ -26147,6 +28296,16 @@
             "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
             "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
         },
+        "sigstore": {
+            "version": "1.7.0",
+            "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.7.0.tgz",
+            "integrity": "sha512-KP7QULhWdlu3hlp+jw2EvgWKlOGOY9McLj/jrchLjHNlNPK0KWIwF919cbmOp6QiKXLmPijR2qH/5KYWlbtG9Q==",
+            "requires": {
+                "@sigstore/protobuf-specs": "^0.1.0",
+                "@sigstore/tuf": "^1.0.1",
+                "make-fetch-happen": "^11.0.1"
+            }
+        },
         "simple-assign": {
             "version": "0.1.0",
             "resolved": "https://registry.npmjs.org/simple-assign/-/simple-assign-0.1.0.tgz",
@@ -26204,6 +28363,11 @@
             "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
             "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
         },
+        "smart-buffer": {
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
+            "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="
+        },
         "snapdragon": {
             "version": "0.8.2",
             "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
@@ -26468,6 +28632,47 @@
                 }
             }
         },
+        "socks": {
+            "version": "2.7.1",
+            "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz",
+            "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==",
+            "requires": {
+                "ip": "^2.0.0",
+                "smart-buffer": "^4.2.0"
+            },
+            "dependencies": {
+                "ip": {
+                    "version": "2.0.0",
+                    "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
+                    "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
+                }
+            }
+        },
+        "socks-proxy-agent": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz",
+            "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==",
+            "requires": {
+                "agent-base": "^6.0.2",
+                "debug": "^4.3.3",
+                "socks": "^2.6.2"
+            },
+            "dependencies": {
+                "debug": {
+                    "version": "4.3.4",
+                    "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+                    "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+                    "requires": {
+                        "ms": "2.1.2"
+                    }
+                },
+                "ms": {
+                    "version": "2.1.2",
+                    "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+                    "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+                }
+            }
+        },
         "solr-node": {
             "version": "1.2.1",
             "resolved": "https://registry.npmjs.org/solr-node/-/solr-node-1.2.1.tgz",
@@ -26478,6 +28683,29 @@
                 "underscore": "^1.8.3"
             }
         },
+        "sort-asc": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.2.0.tgz",
+            "integrity": "sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA=="
+        },
+        "sort-desc": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.2.0.tgz",
+            "integrity": "sha512-NqZqyvL4VPW+RAxxXnB8gvE1kyikh8+pR+T+CXLksVRN9eiQqkQlPwqWYU0mF9Jm7UnctShlxLyAt1CaBOTL1w=="
+        },
+        "sort-object": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/sort-object/-/sort-object-3.0.3.tgz",
+            "integrity": "sha512-nK7WOY8jik6zaG9CRwZTaD5O7ETWDLZYMM12pqY8htll+7dYeqGfEUPcUBHOpSJg2vJOrvFIY2Dl5cX2ih1hAQ==",
+            "requires": {
+                "bytewise": "^1.1.0",
+                "get-value": "^2.0.2",
+                "is-extendable": "^0.1.1",
+                "sort-asc": "^0.2.0",
+                "sort-desc": "^0.2.0",
+                "union-value": "^1.0.1"
+            }
+        },
         "source-map": {
             "version": "0.5.7",
             "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -26809,6 +29037,41 @@
                 "strip-ansi": "^4.0.0"
             }
         },
+        "string-width-cjs": {
+            "version": "npm:string-width@4.2.3",
+            "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+            "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+            "requires": {
+                "emoji-regex": "^8.0.0",
+                "is-fullwidth-code-point": "^3.0.0",
+                "strip-ansi": "^6.0.1"
+            },
+            "dependencies": {
+                "ansi-regex": {
+                    "version": "5.0.1",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                },
+                "emoji-regex": {
+                    "version": "8.0.0",
+                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                    "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+                },
+                "is-fullwidth-code-point": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+                    "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+                },
+                "strip-ansi": {
+                    "version": "6.0.1",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                    "requires": {
+                        "ansi-regex": "^5.0.1"
+                    }
+                }
+            }
+        },
         "string.prototype.codepointat": {
             "version": "0.2.1",
             "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
@@ -26893,6 +29156,21 @@
                 "ansi-regex": "^3.0.0"
             }
         },
+        "strip-ansi-cjs": {
+            "version": "npm:strip-ansi@6.0.1",
+            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+            "requires": {
+                "ansi-regex": "^5.0.1"
+            },
+            "dependencies": {
+                "ansi-regex": {
+                    "version": "5.0.1",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                }
+            }
+        },
         "strip-bom": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
@@ -26989,6 +29267,20 @@
             "resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz",
             "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw=="
         },
+        "subtag": {
+            "version": "0.5.0",
+            "resolved": "https://registry.npmjs.org/subtag/-/subtag-0.5.0.tgz",
+            "integrity": "sha512-CaIBcTSb/nyk4xiiSOtZYz1B+F12ZxW8NEp54CdT+84vmh/h4sUnHGC6+KQXUfED8u22PQjCYWfZny8d2ELXwg=="
+        },
+        "suggestions": {
+            "version": "1.7.1",
+            "resolved": "https://registry.npmjs.org/suggestions/-/suggestions-1.7.1.tgz",
+            "integrity": "sha512-gl5YPAhPYl07JZ5obiD9nTZsg4SyZswAQU/NNtnYiSnFkI3+ZHuXAiEsYm7AaZ71E0LXSFaGVaulGSWN3Gd71A==",
+            "requires": {
+                "fuzzy": "^0.1.1",
+                "xtend": "^4.0.0"
+            }
+        },
         "supercluster": {
             "version": "7.1.5",
             "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz",
@@ -27268,6 +29560,11 @@
             "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
             "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
         },
+        "tiny-relative-date": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz",
+            "integrity": "sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A=="
+        },
         "tiny-warning": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
@@ -27278,6 +29575,11 @@
             "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
             "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="
         },
+        "tinyqueue": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz",
+            "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA=="
+        },
         "tmp": {
             "version": "0.0.33",
             "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
@@ -27395,6 +29697,11 @@
             "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
             "dev": true
         },
+        "treeverse": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-3.0.0.tgz",
+            "integrity": "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ=="
+        },
         "trim-lines": {
             "version": "3.0.1",
             "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
@@ -27762,6 +30069,31 @@
                 }
             }
         },
+        "tuf-js": {
+            "version": "1.1.7",
+            "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.7.tgz",
+            "integrity": "sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg==",
+            "requires": {
+                "@tufjs/models": "1.0.4",
+                "debug": "^4.3.4",
+                "make-fetch-happen": "^11.1.1"
+            },
+            "dependencies": {
+                "debug": {
+                    "version": "4.3.4",
+                    "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+                    "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+                    "requires": {
+                        "ms": "2.1.2"
+                    }
+                },
+                "ms": {
+                    "version": "2.1.2",
+                    "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+                    "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+                }
+            }
+        },
         "tunnel": {
             "version": "0.0.6",
             "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
@@ -27780,6 +30112,12 @@
             "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
             "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
         },
+        "type": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
+            "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
+            "dev": true
+        },
         "type-check": {
             "version": "0.4.0",
             "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -27884,6 +30222,19 @@
                 }
             }
         },
+        "typewise": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/typewise/-/typewise-1.0.3.tgz",
+            "integrity": "sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==",
+            "requires": {
+                "typewise-core": "^1.2.0"
+            }
+        },
+        "typewise-core": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz",
+            "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg=="
+        },
         "typical": {
             "version": "2.6.1",
             "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
@@ -28403,6 +30754,14 @@
                 "spdx-expression-parse": "^3.0.0"
             }
         },
+        "validate-npm-package-name": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz",
+            "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==",
+            "requires": {
+                "builtins": "^5.0.0"
+            }
+        },
         "validator": {
             "version": "10.11.0",
             "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz",
@@ -28538,6 +30897,16 @@
             "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.8.tgz",
             "integrity": "sha512-obtSWTlbJ+a+TFRYGaUumtVwb+InIUVI0Lu0VBUAPmj2cU5JutEXg3xUE0c2J5Tcy7h2DEKVJBFi+Y9ZSFzzPQ=="
         },
+        "vt-pbf": {
+            "version": "3.1.3",
+            "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz",
+            "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==",
+            "requires": {
+                "@mapbox/point-geometry": "0.1.0",
+                "@mapbox/vector-tile": "^1.3.1",
+                "pbf": "^3.2.1"
+            }
+        },
         "vue-template-compiler": {
             "version": "2.7.14",
             "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz",
@@ -28580,6 +30949,11 @@
                 }
             }
         },
+        "walk-up-path": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz",
+            "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA=="
+        },
         "walkdir": {
             "version": "0.4.1",
             "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz",
@@ -28612,6 +30986,14 @@
                 "minimalistic-assert": "^1.0.0"
             }
         },
+        "wcwidth": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+            "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
+            "requires": {
+                "defaults": "^1.0.3"
+            }
+        },
         "web-namespaces": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
@@ -29302,6 +31684,72 @@
                 }
             }
         },
+        "wrap-ansi-cjs": {
+            "version": "npm:wrap-ansi@7.0.0",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+            "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+            "requires": {
+                "ansi-styles": "^4.0.0",
+                "string-width": "^4.1.0",
+                "strip-ansi": "^6.0.0"
+            },
+            "dependencies": {
+                "ansi-regex": {
+                    "version": "5.0.1",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                },
+                "ansi-styles": {
+                    "version": "4.3.0",
+                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+                    "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+                    "requires": {
+                        "color-convert": "^2.0.1"
+                    }
+                },
+                "color-convert": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+                    "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+                    "requires": {
+                        "color-name": "~1.1.4"
+                    }
+                },
+                "color-name": {
+                    "version": "1.1.4",
+                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+                    "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+                },
+                "emoji-regex": {
+                    "version": "8.0.0",
+                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                    "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+                },
+                "is-fullwidth-code-point": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+                    "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+                },
+                "string-width": {
+                    "version": "4.2.3",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                    "requires": {
+                        "emoji-regex": "^8.0.0",
+                        "is-fullwidth-code-point": "^3.0.0",
+                        "strip-ansi": "^6.0.1"
+                    }
+                },
+                "strip-ansi": {
+                    "version": "6.0.1",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                    "requires": {
+                        "ansi-regex": "^5.0.1"
+                    }
+                }
+            }
+        },
         "wrappy": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -29405,8 +31853,7 @@
         "xtend": {
             "version": "4.0.2",
             "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
-            "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
-            "dev": true
+            "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
         },
         "y18n": {
             "version": "4.0.3",
diff --git a/package.json b/package.json
index d013ad74d..42d43e758 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,7 @@
         "@types/jquery": "^3.5.14",
         "@types/libxmljs": "^0.18.7",
         "@types/lodash": "^4.14.179",
+        "@types/mapbox-gl": "^2.7.19",
         "@types/mobile-detect": "^1.3.4",
         "@types/mocha": "^5.2.6",
         "@types/mongodb": "^3.6.20",
@@ -116,7 +117,6 @@
         "fork-ts-checker-webpack-plugin": "^1.6.0",
         "jsdom": "^15.2.1",
         "mocha": "^5.2.0",
-        "prettier": "^2.7.1",
         "sass-loader": "^7.3.1",
         "scss-loader": "0.0.1",
         "style-loader": "^0.23.1",
@@ -144,6 +144,7 @@
         "@hig/flyout": "^1.3.1",
         "@hig/theme-context": "^2.1.3",
         "@hig/theme-data": "^2.23.1",
+        "@mapbox/mapbox-gl-geocoder": "^4.7.4",
         "@material-ui/core": "^4.12.3",
         "@mui/icons-material": "^5.11.16",
         "@mui/material": "^5.13.1",
@@ -159,6 +160,7 @@
         "@types/dom-speech-recognition": "0.0.1",
         "@types/formidable": "1.0.31",
         "@types/google-maps": "^3.2.3",
+        "@types/mapbox__mapbox-gl-geocoder": "^4.7.2",
         "@types/reveal": "^3.3.33",
         "@types/supercluster": "^7.1.0",
         "@types/three": "^0.126.2",
@@ -198,6 +200,7 @@
         "csv-parser": "^3.0.0",
         "csv-stringify": "^6.3.0",
         "d3": "^7.6.1",
+        "debounce": "^2.0.0",
         "depcheck": "^0.9.2",
         "equation-editor-react": "github:bobzel/equation-editor-react#useLocally",
         "exif": "^0.6.0",
@@ -216,6 +219,7 @@
         "forever-agent": "^0.6.1",
         "formidable": "1.2.1",
         "function-plot": "^1.22.8",
+        "geojson": "^0.5.0",
         "golden-layout": "^1.5.9",
         "google-auth-library": "^4.2.4",
         "google-maps-react": "^2.0.6",
@@ -239,6 +243,7 @@
         "jsonschema": "^1.4.0",
         "jszip": "^3.7.1",
         "lodash": "^4.17.21",
+        "mapbox-gl": "^3.0.1",
         "material-ui": "^0.20.2",
         "md5-file": "^5.0.0",
         "memorystream": "^0.3.1",
@@ -297,6 +302,7 @@
         "react-jsx-parser": "^1.29.0",
         "react-loader-spinner": "^5.3.4",
         "react-loading": "^2.0.3",
+        "react-map-gl": "^7.1.0",
         "react-markdown": "^8.0.3",
         "react-measure": "^2.5.2",
         "react-refresh-typescript": "^2.0.7",
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 87010f770..306e9e14b 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -37,6 +37,7 @@ export enum DocumentType {
     COMPARISON = 'comparison',
     GROUP = 'group',
     PUSHPIN = 'pushpin',
+    MAPROUTE = 'maproute',
 
     SCRIPTDB = 'scriptdb', // database of scripts
     GROUPDB = 'groupdb', // database of groups
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index dfb9f4327..02794c432 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -176,9 +176,12 @@ export class DocumentOptions {
     _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units");
     latitude?: NUMt = new NumInfo('latitude coordinate for map views', false);
     longitude?: NUMt = new NumInfo('longitude coordinate for map views', false);
+    routeCoordinates?: LISTt = new ListInfo("stores a route's/direction's coordinates"); // for a route document, this stores the route's coordiantes
     map?: STRt = new StrInfo('text location of map');
     map_type?: STRt = new StrInfo('type of map view', false);
     map_zoom?: NUMt = new NumInfo('zoom of a map view', false);
+    wikiData?: STRt = new StrInfo('WikiData ID related to map location');
+    description?: STRt = new StrInfo('A description of the document')
     _timecodeToShow?: NUMt = new NumInfo('the time that a document should be displayed (e.g., when an annotation shows up as a video plays)', false);
     _timecodeToHide?: NUMt = new NumInfo('the time that a document should be hidden', false);
     _width?: NUMt = new NumInfo('displayed width of a document');
@@ -1129,10 +1132,25 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.MAP), new List(documents), options);
         }
 
-        export function PushpinDocument(latitude: number, longitude: number, infoWindowOpen: boolean, documents: Array<Doc>, options: DocumentOptions, id?: string) {
+        export function PushpinDocument(
+            latitude: number, 
+            longitude: number, 
+            infoWindowOpen: boolean, 
+            documents: Array<Doc>, 
+            options: DocumentOptions, 
+            id?: string) {
             return InstanceFromProto(Prototypes.get(DocumentType.PUSHPIN), new List(documents), { latitude, longitude, infoWindowOpen, ...options }, id);
         }
 
+        export function MapRouteDocument(
+            infoWindowOpen: boolean,
+            documents: Array<Doc>,
+            options: DocumentOptions,
+            id?: string
+        ) {
+            return InstanceFromProto(Prototypes.get(DocumentType.MAPROUTE), new List(documents), {infoWindowOpen, ...options}, id)
+        }
+
         // shouldn't ever need to create a KVP document-- instead set the LayoutTemplateString to be a KeyValueBox for the DocumentView (see addDocTab in TabDocView)
         // export function KVPDocument(document: Doc, options: DocumentOptions = {}) {
         //     return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + '.kvp', ...options });
diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss
index b205a0f1e..62608ec4d 100644
--- a/src/client/views/AntimodeMenu.scss
+++ b/src/client/views/AntimodeMenu.scss
@@ -15,6 +15,11 @@
     align-items: center;
     gap: 3px;
 
+    &.expanded {
+        // Conditionally unset the height when the class is applied
+        height: auto;
+    }
+
     &.with-rows {
         flex-direction: column
     }
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index 16e76694d..a6938acbd 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -139,10 +139,12 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends React.Co
         return <div className="antimodeMenu-dragger" key="dragger" onPointerDown={this.dragStart} style={{ width: '20px' }} />;
     };
 
-    protected getElement(buttons: JSX.Element) {
+    protected getElement(buttons: JSX.Element, expanded: boolean = false) {
+        const containerClass = expanded ? 'antimodeMenu-cont expanded' : 'antimodeMenu-cont';
+
         return (
             <div
-                className="antimodeMenu-cont"
+                className={containerClass}
                 onPointerLeave={this.pointerLeave}
                 onPointerEnter={this.pointerEntered}
                 ref={this._mainCont}
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 96bd52d39..5ad33f5fd 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -15,6 +15,7 @@ import { CollectionView } from './collections/CollectionView';
 import './global/globalScripts';
 import { MainView } from './MainView';
 import { BranchingTrailManager } from '../util/BranchingTrailManager';
+
 dotenv.config();
 
 AssignAllExtensions();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index ce47a46c3..13f7dc896 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -71,6 +71,7 @@ import { PreviewCursor } from './PreviewCursor';
 import { PropertiesView } from './PropertiesView';
 import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
 import { TopBar } from './topbar/TopBar';
+import { DirectionsAnchorMenu } from './nodes/MapBox/DirectionsAnchorMenu';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
@@ -982,7 +983,7 @@ export class MainView extends React.Component {
     @observable mapBoxHackBool = false;
     @computed get mapBoxHack() {
         return this.mapBoxHackBool ? null : (
-            <MapBox
+            <MapBox 
                 ref={action((r: any) => r && (this.mapBoxHackBool = true))}
                 fieldKey="data"
                 select={returnFalse}
@@ -1071,6 +1072,7 @@ export class MainView extends React.Component {
                 <RadialMenu />
                 <AnchorMenu />
                 <MapAnchorMenu />
+                <DirectionsAnchorMenu/>
                 <DashFieldViewMenu />
                 <MarqueeOptionsMenu />
                 <TimelineMenu />
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index eed787b6d..669d45ca5 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -269,7 +269,7 @@ export class DocumentContentsView extends React.Component<
                     PhysicsSimulationBox,
                     SchemaRowBox,
                     ImportElementBox,
-                    MapPushpinBox,
+                    // MapPushpinBox,
                 }}
                 bindings={bindings}
                 jsx={layoutFrame}
diff --git a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
new file mode 100644
index 000000000..bf4028f01
--- /dev/null
+++ b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
@@ -0,0 +1,137 @@
+import React = require('react');
+import { observer } from "mobx-react";
+import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu";
+import { IReactionDisposer, ObservableMap, reaction } from "mobx";
+import { Doc, Opt } from "../../../../fields/Doc";
+import { returnFalse, unimplementedFunction } from "../../../../Utils";
+import { NumCast, StrCast } from "../../../../fields/Types";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { IconButton } from "browndash-components";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { SettingsManager } from "../../../util/SettingsManager";
+import { IconLookup, faAdd, faCalendarDays, faRoute } from "@fortawesome/free-solid-svg-icons";
+
+@observer
+export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
+    static Instance: DirectionsAnchorMenu;
+
+    private _disposer: IReactionDisposer | undefined;
+
+    public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
+
+    public Center: () => void = unimplementedFunction;
+    public OnClick: (e: PointerEvent) => void = unimplementedFunction;
+    // public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
+    public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
+    public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined;
+    public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
+    public Delete: () => void = unimplementedFunction;
+    // public MakeTargetToggle: () => void = unimplementedFunction;
+    // public ShowTargetTrail: () => void = unimplementedFunction;
+    public IsTargetToggler: () => boolean = returnFalse;
+
+    private title: string | undefined = undefined;
+
+    public setPinDoc(pinDoc: Doc){
+        this.title = StrCast(pinDoc.title ? pinDoc.title : `${NumCast(pinDoc.longitude)}, ${NumCast(pinDoc.latitude)}`) ;
+        console.log("Title: ", this.title)
+    }
+
+    public get Active() {
+        return this._left > 0;
+    }
+
+    constructor(props: Readonly<{}>) {
+        super(props);
+
+        DirectionsAnchorMenu.Instance = this;
+        DirectionsAnchorMenu.Instance._canFade = false;
+    }
+
+    componentWillUnmount() {
+        this._disposer?.();
+    }
+
+    componentDidMount() {
+        this._disposer = reaction(
+            () => SelectionManager.Views().slice(),
+            sel => DirectionsAnchorMenu.Instance.fadeOut(true)
+        );
+    }
+    // audioDown = (e: React.PointerEvent) => {
+    //     setupMoveUpEvents(this, e, returnFalse, returnFalse, e => this.OnAudio?.(e));
+    // };
+
+    // cropDown = (e: React.PointerEvent) => {
+    //     setupMoveUpEvents(
+    //         this,
+    //         e,
+    //         (e: PointerEvent) => {
+    //             this.StartCropDrag(e, this._commentCont.current!);
+    //             return true;
+    //         },
+    //         returnFalse,
+    //         e => this.OnCrop?.(e)
+    //     );
+    // };
+    // notePointerDown = (e: React.PointerEvent) => {
+    //     setupMoveUpEvent(
+    //         this,
+    //         e,
+    //         (e: PointerEvent) => {
+    //             this.StartDrag(e, this._commentRef.current!);
+    //             return true;
+    //         },
+    //         returnFalse,
+    //         e => this.OnClick(e)
+    //     );
+    // };
+
+    static top = React.createRef<HTMLDivElement>();
+
+    // public get Top(){
+    //     return this.top
+    // }
+
+    render() {
+        const buttons = (
+            <div className='directions-menu-buttons' style={{display: 'flex'}}>
+                <IconButton
+                    tooltip="Add route" //
+                    onPointerDown={this.Delete}
+                    icon={<FontAwesomeIcon icon={faAdd as IconLookup} />}
+                    color={SettingsManager.userColor}
+                />
+            
+            
+                <IconButton
+                    tooltip='Animate route'
+                    onPointerDown={this.Delete} /**TODO: fix */
+                    icon={<FontAwesomeIcon icon={faRoute as IconLookup}/>}
+                    color={SettingsManager.userColor}
+                />
+                <IconButton
+                    tooltip='Add to calendar'
+                    onPointerDown={this.Delete} /**TODO: fix */
+                    icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup}/>}
+                    color={SettingsManager.userColor}
+                />
+            </div>
+        )
+
+        return this.getElement(
+            <div ref={DirectionsAnchorMenu.top} style={{ height: 'max-content' , width: '100%', display: 'flex', flexDirection: 'column' }}>
+                <div>{this.title}</div>
+                <div className='direction-inputs' style={{display: 'flex', flexDirection: 'column'}}>
+                    <input
+                        placeholder="Origin"
+                    />
+                    <input
+                        placeholder="Destination"
+                    />
+                </div>
+                {buttons}
+            </div>
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/MapBox/GeocoderControl.tsx b/src/client/views/nodes/MapBox/GeocoderControl.tsx
new file mode 100644
index 000000000..e4ba51316
--- /dev/null
+++ b/src/client/views/nodes/MapBox/GeocoderControl.tsx
@@ -0,0 +1,107 @@
+// import React from 'react';
+// import MapboxGeocoder , { GeocoderOptions} from '@mapbox/mapbox-gl-geocoder'
+// import { ControlPosition, MarkerProps, useControl } from "react-map-gl";
+
+// import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css'
+
+
+// export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & {
+//     mapboxAccessToken: string;
+//     marker?: Omit<MarkerProps, 'longitude' | 'latitude'>;
+//     position: ControlPosition;
+
+//     onLoading: (...args: any[]) => void;
+//     onResults: (...args: any[]) => void;
+//     onResult: (...args: any[]) => void;
+//     onError: (...args: any[]) => void;
+// }
+
+// export const GeocoderControl = (props: GeocoderControlProps) => {
+
+//     console.log(props);
+
+//     const geocoder = useControl<MapboxGeocoder>(
+//         () => {
+//           const ctrl = new MapboxGeocoder({
+//             ...props,
+//             marker: false,
+//             accessToken: props.mapboxAccessToken
+//           });
+//           ctrl.on('loading', props.onLoading);
+//           ctrl.on('results', props.onResults);
+//           ctrl.on('result', evt => {
+//             props.onResult(evt);
+    
+//             // const {result} = evt;
+//             // const location =
+//             //   result &&
+//             //   (result.center || (result.geometry?.type === 'Point' && result.geometry.coordinates));
+//             // if (location && props.marker) {
+//             //   setMarker(<Marker {...props.marker} longitude={location[0]} latitude={location[1]} />);
+//             // } else {
+//             //   setMarker(null);
+//             // }
+//           });
+//           ctrl.on('error', props.onError);
+//           return ctrl;
+//         },
+//         {
+//           position: props.position
+//         }
+//       );
+
+
+//     // @ts-ignore (TS2339) private member
+//     if (geocoder._map) {
+//         if (geocoder.getProximity() !== props.proximity && props.proximity !== undefined) {
+//             geocoder.setProximity(props.proximity);
+//         }
+//         if (geocoder.getRenderFunction() !== props.render && props.render !== undefined) {
+//             geocoder.setRenderFunction(props.render);
+//         }
+//         if (geocoder.getLanguage() !== props.language && props.language !== undefined) {
+//             geocoder.setLanguage(props.language);
+//         }
+//         if (geocoder.getZoom() !== props.zoom && props.zoom !== undefined) {
+//             geocoder.setZoom(props.zoom);
+//         }
+//         if (geocoder.getFlyTo() !== props.flyTo && props.flyTo !== undefined) {
+//             geocoder.setFlyTo(props.flyTo);
+//         }
+//         if (geocoder.getPlaceholder() !== props.placeholder && props.placeholder !== undefined) {
+//             geocoder.setPlaceholder(props.placeholder);
+//         }
+//         if (geocoder.getCountries() !== props.countries && props.countries !== undefined) {
+//             geocoder.setCountries(props.countries);
+//         }
+//         if (geocoder.getTypes() !== props.types && props.types !== undefined) {
+//             geocoder.setTypes(props.types);
+//         }
+//         if (geocoder.getMinLength() !== props.minLength && props.minLength !== undefined) {
+//             geocoder.setMinLength(props.minLength);
+//         }
+//         if (geocoder.getLimit() !== props.limit && props.limit !== undefined) {
+//             geocoder.setLimit(props.limit);
+//         }
+//         if (geocoder.getFilter() !== props.filter && props.filter !== undefined) {
+//             geocoder.setFilter(props.filter);
+//         }
+//         if (geocoder.getOrigin() !== props.origin && props.origin !== undefined) {
+//             geocoder.setOrigin(props.origin);
+//         }
+//     }
+//     return (
+//         <div>
+//             Geocoder
+//         </div>
+//     )
+// }
+
+// const noop = () => {};
+
+// GeocoderControl.defaultProps = {
+//   marker: true,
+//   onLoading: noop,
+//   onResults: noop,
+//   onError: noop
+// };
\ No newline at end of file
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.scss b/src/client/views/nodes/MapBox/MapAnchorMenu.scss
index 6990bdcf1..e2fcd78fc 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.scss
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.scss
@@ -51,4 +51,61 @@
             border: 2px solid white;
         }
     }
-}
\ No newline at end of file
+}
+
+.map-anchor-menu-container {
+    display: flex;
+    flex-direction: column;
+    gap: 5px;
+    padding: 5px;
+    height: max-content;
+    min-width: 300px;
+
+    .direction-inputs {
+        display: flex;
+        flex-direction: column;
+        gap: 5px;
+
+        #get-routes-button {
+            padding: 8px 10px;
+            border-radius: 5px;
+        }
+    }
+
+    .MuiInputBase-input{
+        color: white !important;
+    }
+    
+    
+    .css-1t8l2tu-MuiInputBase-input-MuiOutlinedInput-input.Mui-disabled{
+        -webkit-text-fill-color: #b3b2b2 !important;
+    }
+
+    .current-route-info-container {
+        width: 100%;
+
+        .transportation-icons-container {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            gap: 5px;
+        }
+
+        .selected-route-details-container{
+            display: flex;
+            flex-direction: column;
+            gap: 3px;
+            justify-content: center;
+            align-items: flex-start;
+            padding: 5px;
+        }
+
+
+    }
+
+
+
+
+}
+
+
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index f6680aac0..fca3998c8 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -1,15 +1,39 @@
 import React = require('react');
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { IReactionDisposer, ObservableMap, reaction } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, Opt } from '../../../../fields/Doc';
+import { Doc, NumListCast, Opt } from '../../../../fields/Doc';
 import { returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../../Utils';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 // import { GPTPopup, GPTPopupMode } from './../../GPTPopup/GPTPopup';
-import { IconButton } from 'browndash-components';
+import { Button, IconButton } from 'browndash-components';
 import { SettingsManager } from '../../../util/SettingsManager';
 import './MapAnchorMenu.scss';
+import { NumCast, StrCast } from '../../../../fields/Types';
+import { 
+    IconLookup, 
+    faDiamondTurnRight, 
+    faCalendarDays, 
+    faEdit, 
+    faAdd, 
+    faRoute, 
+    faArrowLeft,
+    faLocationDot,
+    faArrowDown,
+    faCar,
+    faBicycle,
+    faPersonWalking,
+    faUpload,
+    faArrowsRotate,
+ } from '@fortawesome/free-solid-svg-icons';
+import { DirectionsAnchorMenu } from './DirectionsAnchorMenu';
+import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material';
+import { MapboxApiUtility, TransportationType } from './MapboxApiUtility';
+import { MapBox } from './MapBox';
+import { List } from '../../../../fields/List';
+
+type MapAnchorMenuType = 'standard' | 'route' | 'calendar' | 'customize';
 
 @observer
 export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -17,6 +41,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     private _disposer: IReactionDisposer | undefined;
     private _commentRef = React.createRef<HTMLDivElement>();
+    private _fileInputRef = React.createRef<HTMLInputElement>();
 
     public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
 
@@ -30,6 +55,32 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     // public MakeTargetToggle: () => void = unimplementedFunction;
     // public ShowTargetTrail: () => void = unimplementedFunction;
     public IsTargetToggler: () => boolean = returnFalse;
+
+    public DisplayRoute: (routeInfoMap: Record<TransportationType, any> | undefined, type: TransportationType) => void = unimplementedFunction;
+    public HideRoute: () => void = unimplementedFunction;
+    public AddNewRouteToMap: (coordinates: List<any>, origin: string, destination: string) => void = unimplementedFunction;
+    public CreatePin: (feature: any) => void = unimplementedFunction;
+
+
+    private allMapPinDocs: Doc[] = [];
+
+    private pinDoc: Doc | undefined = undefined 
+
+    private title: string | undefined = undefined;
+
+
+    public setPinDoc(pinDoc: Doc){
+        this.pinDoc = pinDoc;
+        this.title = StrCast(pinDoc.title ? pinDoc.title : `${NumCast(pinDoc.longitude)}, ${NumCast(pinDoc.latitude)}`) ;
+    }
+
+    public setAllMapboxPins(pinDocs: Doc[]) {
+        this.allMapPinDocs = pinDocs;
+        pinDocs.forEach((p, idx) => {
+            console.log(`Pin ${idx}: ${p.title}`);
+        })
+    }   
+
     public get Active() {
         return this._left > 0;
     }
@@ -42,6 +93,10 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     }
 
     componentWillUnmount() {
+        this.destinationFeatures = [];
+        this.destinationSelected = false;
+        this.selectedDestinationFeature = undefined;
+        this.currentRouteInfoMap = undefined;
         this._disposer?.();
     }
 
@@ -81,39 +136,218 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     };
 
     static top = React.createRef<HTMLDivElement>();
+
     // public get Top(){
     //     return this.top
     // }
 
+    @observable
+    menuType: MapAnchorMenuType = 'standard';
+
+    @action
+    DirectionsClick = () => {
+        this.menuType = 'route';
+    }
+
+    @action
+    CustomizeClick = () => {
+        this.menuType = 'customize';
+    }
+
+    @action
+    BackClick = () => {
+        this.menuType = 'standard';
+    }
+
+    @action
+    TriggerFileInputClick = () => {
+        if (this._fileInputRef) {
+            this._fileInputRef.current?.click(); // Trigger the file input click event
+          }
+    }
+
+
+    @observable
+    destinationFeatures: any[] = []
+
+    @observable
+    destinationSelected: boolean = false;
+
+    @observable
+    selectedDestinationFeature: any = undefined;
+
+    @observable 
+    createPinForDestination: boolean = true;
+
+    @observable
+    currentRouteInfoMap: Record<TransportationType, any> | undefined = undefined;
+
+    @observable
+    selectedTransportationType: TransportationType = 'driving';
+
+    @action
+    handleTransportationTypeChange = (newType: TransportationType) => {
+        if (newType !== this.selectedTransportationType){
+            this.selectedTransportationType = newType;
+            this.DisplayRoute(this.currentRouteInfoMap, newType);
+        }
+
+    }
+
+    @action
+    handleSelectedDestinationFeature = (destinationFeature: any) => {
+        this.selectedDestinationFeature = destinationFeature;
+    }
+
+    @action
+    toggleCreatePinForDestinationCheckbox = () => {
+        this.createPinForDestination = !this.createPinForDestination;
+    }
+
+    @action
+    handleDestinationSearchChange = async (searchText: string) => {
+        if (this.selectedDestinationFeature !== undefined) this.selectedDestinationFeature = undefined;
+        const features = await MapboxApiUtility.forwardGeocodeForFeatures(searchText);
+        if (features){
+            runInAction(() => {
+                this.destinationFeatures = features;
+                
+            })
+        }
+    }
+
+    getRoutes = async (destinationFeature: any) => {
+        const currentPinLong: number = NumCast(this.pinDoc?.longitude);
+        const currentPinLat: number = NumCast(this.pinDoc?.latitude);
+
+        if (currentPinLong && currentPinLat && destinationFeature.center){
+            const routeInfoMap = await MapboxApiUtility.getDirections([currentPinLong, currentPinLat], destinationFeature.center);
+            if (routeInfoMap) {
+                runInAction(() => {
+                    this.currentRouteInfoMap = routeInfoMap;
+                })
+                this.DisplayRoute(routeInfoMap, 'driving');
+            }
+        }
+
+        // get route menu, set it equal to here 
+        // create a temporary route 
+        // create pin if createPinForDestination was clicked
+    }
+
+    HandleAddRouteClick = () => {
+        if (this.currentRouteInfoMap && this.selectedTransportationType && this.selectedDestinationFeature){
+            const coordinates = this.currentRouteInfoMap[this.selectedTransportationType].coordinates;
+            this.AddNewRouteToMap(this.currentRouteInfoMap![this.selectedTransportationType].coordinates, this.title ?? "", this.selectedDestinationFeature.place_name);
+            this.HideRoute();
+        }
+    }
+
+
     render() {
         const buttons = (
-            <>
-                {
-                    <IconButton
+            <div className='menu-buttons' style={{display: 'flex'}}>
+                {this.menuType === 'standard' && 
+                    <>
+                        <IconButton
                         tooltip="Delete Pin" //
                         onPointerDown={this.Delete}
                         icon={<FontAwesomeIcon icon="trash-alt" />}
                         color={SettingsManager.userColor}
-                    />
+                        />
+                        <IconButton
+                            tooltip='Get directions'
+                            onPointerDown={this.DirectionsClick} /**TODO: fix */
+                            icon={<FontAwesomeIcon icon={faDiamondTurnRight as IconLookup}/>}
+                            color={SettingsManager.userColor}
+                        />
+                        <IconButton
+                            tooltip='Add to calendar'
+                            onPointerDown={this.Delete} /**TODO: fix */
+                            icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup}/>}
+                            color={SettingsManager.userColor}
+                        />
+                        <div ref={this._commentRef}>
+                            <IconButton
+                                tooltip="Link Note to Pin" //
+                                onPointerDown={this.notePointerDown}
+                                icon={<FontAwesomeIcon icon="sticky-note" />}
+                                color={SettingsManager.userColor}
+                            />
+                        </div>
+                        <IconButton
+                            tooltip="Customize pin"
+                            onPointerDown={this.CustomizeClick}
+                            icon={<FontAwesomeIcon icon={faEdit as IconLookup}/>}
+                            color={SettingsManager.userColor}
+                        />
+                        <IconButton
+                            tooltip="Center on pin" //
+                            onPointerDown={this.Center}
+                            icon={<FontAwesomeIcon icon="compress-arrows-alt" />}
+                            color={SettingsManager.userColor}
+                        />
+                    </>
                 }
-                {
-                    <div ref={this._commentRef}>
+                {this.menuType === 'route' && 
+                    <>
                         <IconButton
-                            tooltip="Link Note to Pin" //
-                            onPointerDown={this.notePointerDown}
-                            icon={<FontAwesomeIcon icon="sticky-note" />}
+                            tooltip="Go back" //
+                            onPointerDown={this.BackClick}
+                            icon={<FontAwesomeIcon icon={faArrowLeft as IconLookup} />}
                             color={SettingsManager.userColor}
                         />
-                    </div>
+                        <IconButton
+                            tooltip="Add route" //
+                            onPointerDown={this.HandleAddRouteClick}
+                            icon={<FontAwesomeIcon icon={faAdd as IconLookup} />}
+                            color={SettingsManager.userColor}
+                        />
+                        <IconButton
+                            tooltip='Animate route'
+                            onPointerDown={this.Delete} /**TODO: fix */
+                            icon={<FontAwesomeIcon icon={faRoute as IconLookup}/>}
+                            color={SettingsManager.userColor}
+                        />
+                        <IconButton
+                            tooltip='Add to calendar'
+                            onPointerDown={this.Delete} /**TODO: fix */
+                            icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup}/>}
+                            color={SettingsManager.userColor}
+                        />
+                    </>
                 }
-                {
-                    <IconButton
-                        tooltip="Center on pin" //
-                        onPointerDown={this.Center}
-                        icon={<FontAwesomeIcon icon="compress-arrows-alt" />}
-                        color={SettingsManager.userColor}
-                    />
+                {this.menuType === 'customize' &&
+                    <>
+                        <IconButton
+                            tooltip="Go back" //
+                            onPointerDown={this.BackClick}
+                            icon={<FontAwesomeIcon icon={faArrowLeft as IconLookup} />}
+                            color={SettingsManager.userColor}
+                        />
+                        <IconButton
+                            tooltip="Upload image" //
+                            onPointerDown={this.TriggerFileInputClick}
+                            icon={<FontAwesomeIcon icon={faUpload as IconLookup} />}
+                            color={SettingsManager.userColor}
+                        />
+                        <input
+                            type="file"
+                            accept="image/*" // Optionally, specify accepted file types
+                            ref={this._fileInputRef}
+                            style={{ display: "none" }}
+                            onChange={() => {}}
+                        />
+                        <IconButton
+                            tooltip="Revert to original" //
+                            onPointerDown={this.BackClick}
+                            icon={<FontAwesomeIcon icon={faArrowsRotate as IconLookup} />}
+                            color={SettingsManager.userColor}
+                        />
+                    </>
                 }
+               
+                
                 {/* {this.IsTargetToggler !== returnFalse && (
                         <Toggle
                             tooltip={'Make target visibility toggle on click'}
@@ -125,13 +359,118 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             color={SettingsManager.userColor}
                         />
                     )} */}
-            </>
+            </div>
         );
+        // return (
+        //     <div ref={MapAnchorMenu.top} style={{zIndex: 30000, width: '100%', height: '100px'}}>
+        //         HELLO THIS IS ANCHOR MENU
+        //         {this.getElement(buttons)}
+        //     </div>
+        // )
 
         return this.getElement(
-            <div ref={MapAnchorMenu.top} style={{ width: '100%', display: 'flex' }}>
+            <div 
+                ref={MapAnchorMenu.top} 
+                className='map-anchor-menu-container'>
+                {this.menuType === 'standard' &&
+                    <div>{this.title}</div>
+                }
+                {this.menuType === 'route' && 
+                    <div className='direction-inputs' style={{display: 'flex', flexDirection: 'column'}}>
+                        <TextField
+                            fullWidth
+                            disabled
+                            value={this.title}
+                        />
+                        <FontAwesomeIcon icon={faArrowDown as IconLookup} size='xs'/>
+                        <Autocomplete
+                            fullWidth
+                            id="route-destination-searcher"
+                            
+                            onInputChange={(e, searchText) => this.handleDestinationSearchChange(searchText)}
+                            onChange={(e, feature, reason) => {
+                                if (reason === 'clear'){
+                                    this.handleSelectedDestinationFeature(undefined);
+                                } else if (reason === 'selectOption'){
+                                    this.handleSelectedDestinationFeature(feature);
+                                }
+                            }}
+                            options={this.destinationFeatures
+                                .filter(feature => feature.place_name)
+                                .map(feature => feature)}
+                            getOptionLabel={(feature) => feature.place_name}
+                            renderInput={(params) => (
+                                <TextField
+                                    {...params}
+                                    placeholder='Enter a destination'
+                                />
+                            )}
+                        />
+                        {this.selectedDestinationFeature && 
+                            <>
+                             {!this.allMapPinDocs.some(pinDoc => pinDoc.title === this.selectedDestinationFeature.place_name) && 
+                                <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '5px'}}>
+                                    <FormControlLabel
+                                        label='Create pin for destination?'
+                                        control={
+                                            <Checkbox 
+                                                color='success'
+                                                checked={this.createPinForDestination}
+                                                onChange={this.toggleCreatePinForDestinationCheckbox}
+                                            />
+                                        }
+                                    />
+                                </div>
+                             }
+                            </>
+                           
+
+                        }
+                        <button 
+                            id='get-routes-button'
+                            disabled={this.selectedDestinationFeature ? false : true}
+                            onClick={() => this.getRoutes(this.selectedDestinationFeature)}
+                            >
+                            Get routes
+                        </button>
+
+                        {/* <input
+                            placeholder="Origin"
+                        /> */}
+                    </div>
+                }
+                {this.currentRouteInfoMap &&
+                    <div className='current-route-info-container'> 
+                        <div className='transportation-icons-container'>
+                            <IconButton
+                                tooltip="Driving route" 
+                                onPointerDown={() => this.handleTransportationTypeChange('driving')}
+                                icon={<FontAwesomeIcon icon={faCar as IconLookup}/>}
+                                color={this.selectedTransportationType === 'driving' ? 'lightblue': 'grey'}
+                            />
+                            <IconButton
+                                tooltip="Cycling route" 
+                                onPointerDown={() => this.handleTransportationTypeChange('cycling')}
+                                icon={<FontAwesomeIcon icon={faBicycle as IconLookup}/>}
+                                color={this.selectedTransportationType === 'cycling' ? 'lightblue': 'grey'}
+                            />
+                            <IconButton
+                                tooltip="Walking route" 
+                                onPointerDown={() => this.handleTransportationTypeChange('walking')}
+                                icon={<FontAwesomeIcon icon={faPersonWalking as IconLookup}/>}
+                                color={this.selectedTransportationType === 'walking' ? 'lightblue': 'grey'}
+                            />
+                        </div>
+                        <div className='selected-route-details-container'>
+                            <div>Duration: {this.currentRouteInfoMap[this.selectedTransportationType].duration}</div>
+                            <div>Distance: {this.currentRouteInfoMap[this.selectedTransportationType].distance}</div>
+                        </div>
+                    </div>
+                    
+
+                }
                 {buttons}
             </div>
-        );
+        , true);
     }
 }
diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss
index 242677231..946c6f495 100644
--- a/src/client/views/nodes/MapBox/MapBox.scss
+++ b/src/client/views/nodes/MapBox/MapBox.scss
@@ -12,17 +12,40 @@
         font-size: 17;
     }
     .mapBox-searchbar {
-        display: flex;
-        flex-direction: row;
+        // display: flex;
+        // flex-direction: row;
         width: calc(100% - 40px);
-        .editableText-container {
-            width: 100%;
-            font-size: 16px !important;
-        }
-        input {
+
+        // .editableText-container {
+        //     width: 100%;
+        //     font-size: 16px !important;
+        // }
+        // input {
+        //     width: 100%;
+        // }
+    }
+
+    .mapbox-geocoding-search-results {
+        z-index: 900;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: flex-start;
+        position: absolute;
+        background-color: rgb(187, 187, 187);
+        font-size: 1.4em;
+        padding: 10px;
+
+        .search-result-container {
             width: 100%;
+            padding: 10px;
+            &:hover{
+                background-color: lighten(rgb(187, 187, 187), 10%);
+            }
         }
+
     }
+
     .mapBox-topbar {
         display: flex;
         flex-direction: row;
@@ -106,3 +129,4 @@
         display: block;
     }
 }
+
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 9b75ca7e3..2b563faf2 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1,7 +1,9 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import BingMapsReact from 'bingmaps-react';
+// import 'mapbox-gl/dist/mapbox-gl.css';
+
 import { Button, EditableText, IconButton, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, flow, toJS} from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
@@ -26,7 +28,36 @@ import { FieldView, FieldViewProps } from '../FieldView';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../trails';
 import { MapAnchorMenu } from './MapAnchorMenu';
+import { 
+    Map as MapboxMap,
+    MapRef,
+    Marker, 
+    ControlPosition, 
+    FullscreenControl,  
+    MapProvider, 
+    MarkerProps, 
+    NavigationControl, 
+    ScaleControl, 
+    ViewState, 
+    ViewStateChangeEvent,
+    useControl,
+    GeolocateControl,
+    Popup,
+    MapEvent,
+    Source,
+    Layer} from 'react-map-gl';
+import MapboxGeocoder, {GeocoderOptions} from '@mapbox/mapbox-gl-geocoder';
+import debounce from 'debounce';
 import './MapBox.scss';
+import { NumberLiteralType } from 'typescript';
+// import { GeocoderControl } from './GeocoderControl';
+import mapboxgl, { LngLat, MapLayerMouseEvent } from 'mapbox-gl';
+import { Feature, FeatureCollection } from 'geojson';
+import { MarkerEvent } from 'react-map-gl/dist/esm/types';
+import { MapboxApiUtility, TransportationType} from './MapboxApiUtility';
+import { Autocomplete, TextField } from '@mui/material';
+import { List } from '../../../../fields/List';
+
 // amongus
 /**
  * MapBox architecture:
@@ -42,6 +73,30 @@ import './MapBox.scss';
  */
 
 const bingApiKey = process.env.BING_MAPS; // if you're running local, get a Bing Maps api key here: https://www.bingmapsportal.com/  and then add it to the .env file in the Dash-Web root directory as: _CLIENT_BING_MAPS=<your apikey>
+const MAPBOX_ACCESS_TOKEN = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNscHgwNDd1MDA3MXIydm92ODdianp6cGYifQ.WFAqbhwxtMHOWSPtu0l2uQ';
+const MAPBOX_FORWARD_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
+
+const MAPBOX_REVERSE_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
+
+type PopupInfo = {
+    longitude: number,
+    latitude: number,
+    title: string,
+    description: string
+}
+
+export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & {
+    mapboxAccessToken: string,
+    marker?: Omit<MarkerProps, 'longitude' | 'latitude'>;
+    position: ControlPosition;
+
+    onResult: (...args: any[]) => void;
+}
+
+type MapMarker = {
+    longitude: number,
+    latitude: number
+}
 
 /**
  * Consider integrating later: allows for drawing, circling, making shapes on map
@@ -67,6 +122,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     private _dragRef = React.createRef<HTMLDivElement>();
     private _sidebarRef = React.createRef<SidebarAnnos>();
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
+    private _mapRef: React.RefObject<MapRef> = React.createRef();
     private _disposers: { [key: string]: IReactionDisposer } = {};
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
 
@@ -81,6 +137,26 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @computed get allPushpins() {
         return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN);
     }
+    @computed get allRoutes() {
+        return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE);
+    }
+    @computed get allRoutesGeoJson() {
+        const features = this.allRoutes.map(route => {
+            return {
+              type: 'Feature',
+              properties: {},
+              geometry: {
+                type: 'LineString',
+                coordinates: route.coordinates
+              }
+            };
+          });
+        
+          return {
+            type: 'FeatureCollection',
+            features: features
+          };
+    }
     @computed get SidebarShown() {
         return this.layoutDoc._layout_showSidebar ? true : false;
     }
@@ -97,8 +173,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     componentDidMount() {
         this._unmounting = false;
         this.props.setContentView?.(this);
+
     }
 
+
     _unmounting = false;
     componentWillUnmount(): void {
         this._unmounting = true;
@@ -327,49 +405,29 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         entityType: 'PopulatedPlace',
     };
 
-    // incrementer: number = 0;
-    /*
-     * Creates Pushpin doc and adds it to the list of annotations
-     */
-    @action
-    createPushpin = undoable((latitude: number, longitude: number, map?: string) => {
-        // Stores the pushpin as a MapMarkerDocument
-        const pushpin = Docs.Create.PushpinDocument(
-            NumCast(latitude),
-            NumCast(longitude),
-            false,
-            [],
-            { title: map ?? `lat=${latitude},lng=${longitude}`, map: map }
-            // ,'pushpinIDamongus'+ this.incrementer++
-        );
-        this.addDocument(pushpin, this.annotationKey);
-        return pushpin;
-        // mapMarker.infoWindowOpen = true;
-    }, 'createpin');
-
     // The pin that is selected
     @observable selectedPin: Doc | undefined;
 
     @action
     deselectPin = () => {
         if (this.selectedPin) {
-            // Removes filter
-            Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove');
-            Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove');
-            Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
+            // // Removes filter
+            // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove');
+            // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove');
+            // Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
 
-            const temp = this.selectedPin;
-            if (!this._unmounting) {
-                this._bingMap.current.entities.remove(this.map_docToPinMap.get(temp));
-            }
-            const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(temp.latitude, temp.longitude));
-            this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(temp as Doc));
-            if (!this._unmounting) {
-                this._bingMap.current.entities.push(newpin);
-            }
-            this.map_docToPinMap.set(temp, newpin);
-            this.selectedPin = undefined;
-            this.bingSearchBarContents = this.rootDoc.map;
+            // const temp = this.selectedPin;
+            // if (!this._unmounting) {
+            //     this._bingMap.current.entities.remove(this.map_docToPinMap.get(temp));
+            // }
+            // const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(temp.latitude, temp.longitude));
+            // this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(temp as Doc));
+            // if (!this._unmounting) {
+            //     this._bingMap.current.entities.push(newpin);
+            // }
+            // this.map_docToPinMap.set(temp, newpin);
+            // this.selectedPin = undefined;
+            // this.bingSearchBarContents = this.rootDoc.map;
         }
     };
 
@@ -534,6 +592,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     tryHideMapAnchorMenu = (e: PointerEvent) => {
         let target = document.elementFromPoint(e.x, e.y);
         while (target) {
+            if (target.id === 'route-destination-searcher-listbox') return;
             if (target === MapAnchorMenu.top.current) return;
             target = target.parentElement;
         }
@@ -546,11 +605,16 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @action
     centerOnSelectedPin = () => {
         if (this.selectedPin) {
-            this.dataDoc.latitude = this.selectedPin.latitude;
-            this.dataDoc.longitude = this.selectedPin.longitude;
-            this.dataDoc.map = this.selectedPin.map ?? '';
-            this.bingSearchBarContents = this.selectedPin.map;
+            this._mapRef.current?.flyTo({
+                center: [NumCast(this.selectedPin.longitude), NumCast(this.selectedPin.latitude)]
+            })
         }
+        // if (this.selectedPin) {
+        //     this.dataDoc.latitude = this.selectedPin.latitude;
+        //     this.dataDoc.longitude = this.selectedPin.longitude;
+        //     this.dataDoc.map = this.selectedPin.map ?? '';
+        //     this.bingSearchBarContents = this.selectedPin.map;
+        // }
         MapAnchorMenu.Instance.fadeOut(true);
         document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu);
     };
@@ -564,6 +628,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         mapTypeId: 'grayscale',
     };
 
+
     /**
      * Map options
      */
@@ -582,16 +647,15 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         },
     };
 
-    @action
-    searchbarOnEdit = (newText: string) => (this.bingSearchBarContents = newText);
+
 
     recolorPin = (pin: Doc, color?: string) => {
-        this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin));
-        this.map_docToPinMap.delete(pin);
-        const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(pin.latitude, pin.longitude), color ? { color } : {});
-        this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(pin));
-        this._bingMap.current.entities.push(newpin);
-        this.map_docToPinMap.set(pin, newpin);
+        // this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin));
+        // this.map_docToPinMap.delete(pin);
+        // const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(pin.latitude, pin.longitude), color ? { color } : {});
+        // this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(pin));
+        // this._bingMap.current.entities.push(newpin);
+        // this.map_docToPinMap.set(pin, newpin);
     };
 
     /*
@@ -660,25 +724,25 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         setupMoveUpEvents(
             e,
             e,
-            e => {
+            e => { // move event 
                 if (!dragClone) {
-                    dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement;
+                    dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement; // copy draggable pin 
                     dragClone.style.position = 'absolute';
                     dragClone.style.zIndex = '10000';
-                    DragManager.Root().appendChild(dragClone);
+                    DragManager.Root().appendChild(dragClone); // add clone to root 
                 }
                 dragClone.style.transform = `translate(${e.clientX - 15}px, ${e.clientY - 15}px)`;
                 return false;
             },
-            e => {
+            e => { // up event
                 if (!dragClone) return;
                 DragManager.Root().removeChild(dragClone);
-                let target = document.elementFromPoint(e.x, e.y);
+                let target = document.elementFromPoint(e.x, e.y); // element for specified x and y coordinates
                 while (target) {
-                    if (target === this._ref.current) {
+                    if (target === this._ref.current) { 
                         const cpt = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
                         const x = cpt[0] - (this.props.PanelWidth() - this.sidebarWidth()) / 2;
-                        const y = cpt[1] - 32 /* height of search bar */ - this.props.PanelHeight() / 2;
+                        const y = cpt[1] - 20 /* height of search bar */ - this.props.PanelHeight() / 2;
                         const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
                         this.createPushpin(location.latitude, location.longitude);
                         break;
@@ -695,8 +759,236 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         );
     };
 
+        // incrementer: number = 0;
+    /*
+     * Creates Pushpin doc and adds it to the list of annotations
+     */
+    @action
+    createPushpin = undoable((latitude: number, longitude: number, location?: string, wikiData?: string) => {
+        // Stores the pushpin as a MapMarkerDocument
+        const pushpin = Docs.Create.PushpinDocument(
+            NumCast(latitude),
+            NumCast(longitude),
+            false,
+            [],
+            {title: location ?? `lat=${latitude},lng=${longitude}`, map: location, description: "", wikiData: wikiData},
+            // { title: map ?? `lat=${latitude},lng=${longitude}`, map: map },
+            // ,'pushpinIDamongus'+ this.incrementer++
+        );
+        this.addDocument(pushpin, this.annotationKey);
+        console.log(pushpin);
+        return pushpin;
+
+        // mapMarker.infoWindowOpen = true;
+    }, 'createpin');
+
+    @action
+    createMapRoute = undoable((coordinates: List<any>, origin: string, destination: string) => {
+        const mapRoute = Docs.Create.MapRouteDocument(
+            false,
+            [],
+            {title: `${origin} -> ${destination}`, routeCoordinates: coordinates},
+        );
+        this.addDocument(mapRoute, this.annotationKey);
+        return mapRoute;
+
+        // mapMarker.infoWindowOpen = true;
+    }, 'createmaproute');
+
     searchbarKeyDown = (e: any) => e.key === 'Enter' && this.bingSearch();
 
+
+
+
+    @observable
+    mapboxMapViewState: ViewState = {
+        zoom: 9,
+        longitude: -71.41,
+        latitude: 41.82,
+        pitch: 0,
+        bearing: 0,
+        padding: {
+            top: 0,
+            bottom: 0,
+            left: 0,
+            right: 0
+        }
+    }
+
+    @observable
+    featuresFromGeocodeResults: any[] = [];
+
+    @action
+    onMapMove = (e: ViewStateChangeEvent) => {
+        this.mapboxMapViewState = e.viewState;
+    }
+
+
+    @action
+    addMarkerForFeature = (feature: any) => {
+        const location = feature.place_name;
+        if (feature.center){
+            const longitude = feature.center[0];
+            const latitude = feature.center[1];
+            const wikiData = feature.properties?.wikiData;
+    
+            this.createPushpin(
+                latitude, 
+                longitude, 
+                location, 
+                wikiData
+            )
+            this.featuresFromGeocodeResults = [];
+
+        } else {
+            // TODO: handle error
+        }
+    }
+
+
+
+    /**
+     * Makes a forward geocoding API call to Mapbox to retrieve locations based on the search input
+     * @param searchText the search input (presumably a location)
+     */
+    handleSearchChange = async (searchText: string) => {
+        const features = await MapboxApiUtility.forwardGeocodeForFeatures(searchText);
+        if (features){
+            runInAction(() => {
+                this.featuresFromGeocodeResults = features;
+            })
+        }
+        // try {
+        //     const url = MAPBOX_FORWARD_GEOCODE_BASE_URL + encodeURI(searchText) +'.json' +`?access_token=${MAPBOX_ACCESS_TOKEN}`;
+        //     const response = await fetch(url);
+        //     const data = await response.json();
+        //     runInAction(() => {
+        //         this.featuresFromGeocodeResults = data.features;
+        //     })
+        // } catch (error: any){
+        //     // TODO: handle error in better way 
+        //     console.log(error);
+        // }
+    }
+    // @action
+    // debouncedCall = React.useCallback(debounce(this.debouncedOnSearchBarChange, 300), []);
+
+    /**
+     * Makes a reverse geocoding API call to retrieve features corresponding to a map click (based on longitude
+     * and latitude). Sets the search results accordingly. 
+     * @param e 
+     */
+    handleMapClick = async (e: MapLayerMouseEvent) => {
+        e.preventDefault();
+        const lngLat: LngLat = e.lngLat;
+        const longitude: number = lngLat.lng;
+        const latitude: number = lngLat.lat;
+
+        const features = await MapboxApiUtility.reverseGeocodeForFeatures(longitude, latitude);
+        if (features){
+            runInAction(() => {
+                this.featuresFromGeocodeResults = features;
+            })
+        }
+
+        // // REVERSE GEOCODE TO GET LOCATION DETAILS 
+        // try {
+        //     const url = MAPBOX_REVERSE_GEOCODE_BASE_URL + encodeURI(longitude.toString() + "," + latitude.toString()) + '.json' +
+        //         `?access_token=${MAPBOX_ACCESS_TOKEN}`;
+        //     const response = await fetch(url);
+        //     const data = await response.json();
+        //     console.log("REV GEOCODE DATA: ", data);
+        //     runInAction(() => {
+        //         this.featuresFromGeocodeResults = data.features;
+        //     })
+        // } catch (error: any){
+        //     // TODO: handle error in better way
+        //     console.log(error);
+        // }
+    }
+
+    @observable 
+    currentPopup: PopupInfo | undefined = undefined;
+
+    @action
+    handleMarkerClick = (e: MarkerEvent<mapboxgl.Marker, MouseEvent>, pinDoc: Doc) => {
+        this.featuresFromGeocodeResults = [];
+        this.deselectPin(); // TODO: check this method
+        this.selectedPin = pinDoc;
+        // this.bingSearchBarContents = pinDoc.map;
+
+        // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'match');
+        // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'match');
+        Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPin)}`, 'check');
+
+        this.recolorPin(this.selectedPin, 'green'); // TODO: check this method
+       
+        
+        MapAnchorMenu.Instance.Delete = this.deleteSelectedPin;
+        MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
+        MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
+        MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
+
+        // pass in the pinDoc 
+        MapAnchorMenu.Instance.setPinDoc(pinDoc);
+        MapAnchorMenu.Instance.setAllMapboxPins(
+            this.allAnnotations.filter(anno => !anno.layout_unrendered)
+        )
+
+        MapAnchorMenu.Instance.DisplayRoute = this.displayRoute;
+        MapAnchorMenu.Instance.HideRoute = this.hideRoute;
+        MapAnchorMenu.Instance.AddNewRouteToMap = this.createMapRoute;
+        MapAnchorMenu.Instance.CreatePin = this.addMarkerForFeature;
+
+        // const longitude = NumCast(pinDoc.longitude);
+        // const latitude = NumCast(pinDoc.longitude);
+        // const x = longitude + (this.props.PanelWidth() - this.sidebarWidth()) / 2;
+        // const y = latitude + this.props.PanelHeight() / 2 + 20;
+        // const cpt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+        MapAnchorMenu.Instance.jumpTo(e.originalEvent.clientX, e.originalEvent.clientY, true);
+
+        document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
+    };
+
+    @observable
+    temporaryRouteSource: FeatureCollection = {
+        type: 'FeatureCollection',
+        features: []
+    }
+
+    @action
+    displayRoute = (routeInfoMap: Record<TransportationType, any> | undefined, type: TransportationType) => {
+        if (routeInfoMap){
+            const newTempRouteSource: FeatureCollection = {
+                type: 'FeatureCollection',
+                features: [
+                    {
+                        type: 'Feature',
+                        properties: {},
+                        geometry: {
+                            type: 'LineString',
+                            coordinates: routeInfoMap[type].coordinates
+                        }
+                    }
+                ]
+            }
+            // TODO: Create pin for destination 
+            // TODO: Fly to point where full route will be shown 
+            this.temporaryRouteSource = newTempRouteSource;
+        }
+    }
+
+    @action
+    hideRoute = () => {
+        this.temporaryRouteSource = {
+            type: 'FeatureCollection',
+            features: []
+        }
+    }
+
+
+
+
     static _firstRender = true;
     static _rerenderDelay = 500;
     _rerenderTimeout: any;
@@ -732,14 +1024,42 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     {SnappingManager.GetIsDragging() ? null : renderAnnotations()}
 
                     <div className="mapBox-searchbar">
-                        <EditableText
-                            // editing
-                            setVal={(newText: string | number) => typeof newText === 'string' && this.searchbarOnEdit(newText)}
-                            onEnter={e => this.bingSearch()}
-                            placeholder={this.bingSearchBarContents || 'enter city/zip/...'}
-                            textAlign="center"
+                        <TextField 
+                            fullWidth
+                            placeholder='Enter a location'
+                            onChange={(e) => this.handleSearchChange(e.target.value)}
                         />
-                        <IconButton
+                        {/* <Autocomplete
+                            fullWidth
+                            id="map-location-searcher"
+                            freeSolo
+                            onInputChange={(e, searchText) => this.handleSearchChange(searchText)}
+                            onChange={(e, selectedOption) => {
+                                this.handleSearchChange(""); // clear input 
+                                this.addMarkerForFeature(selectedOption);
+                            }}
+                            options={this.featuresFromGeocodeResults
+                                .filter(feature => feature.place_name)
+                                .map(feature => feature)}
+                            getOptionLabel={(feature) => feature.place_name}
+                            renderInput={(params) => (
+                                <TextField
+                                    {...params}
+                                    placeholder='Enter a location'
+                                />
+                            )}
+                            /> */}
+                        {/* <EditableText
+                            // editing
+                            setVal={(newText: string | number) => typeof newText === 'string' && this.handleSearchChange(newText)}
+                            // onEnter={e => this.bingSearch()}
+                            onEnter={e => {}}
+                            height={32}
+                            // placeholder={this.bingSearchBarContents || 'Enter a location'}
+                            placeholder='Enter a location'
+                            textAlign="center" 
+                        /> */}
+                        {/* <IconButton
                             icon={
                                 <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="magnifying-glass" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" color="#DFDFDF">
                                     <path
@@ -752,18 +1072,87 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         />
                         <div style={{ width: 30, height: 30 }} ref={this._dragRef} onPointerDown={this.dragToggle}>
                             <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size={'lg'} icon={'bullseye'} />} />
-                        </div>
-                    </div>
+                        </div> */}
+                    </div> 
+                    <div className='mapbox-geocoding-search-results'>
+                        {this.featuresFromGeocodeResults.length > 0 && (
+                            <React.Fragment>
+                            <h4>Choose a location for your pin: </h4>
+                            {this.featuresFromGeocodeResults
+                                .filter(feature => feature.place_name)
+                                .map((feature, idx) => (
+                                <div 
+                                    key={idx} 
+                                    className='search-result-container'
+                                    onClick={() => {
+                                        this.handleSearchChange("");
+                                        this.addMarkerForFeature(feature);
+                                    }}
+                                >
+                                    <div className='search-result-place-name'> 
+                                    {feature.place_name}
+                                    </div>
+                                </div>
+                                ))}
+                            </React.Fragment>
+                        )}
+                    </div>              
+                    <MapProvider>
+                        <MapboxMap
+                            ref={this._mapRef}
+                            initialViewState={{
+                                longitude: -100,
+                                latitude: 40,
+                                zoom: 3.5
+                              }}
+                            mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
+                            id="mapbox-map"
+                            mapStyle="mapbox://styles/mapbox/streets-v9"
+                            style={{height: '100%', width: '100%'}}
+                            {...this.mapboxMapViewState}
+                            onMove={this.onMapMove}
+                            onDblClick={this.handleMapClick}
+                            
+                            
+                        >
+                            <Source id='temporary-route' type='geojson' data={this.temporaryRouteSource}/>
+                            <Layer
+                                id='temporary-route-layer'
+                                type='line'
+                                source='temporary-route'
+                                layout={{"line-join": "round", "line-cap": "round"}}
+                                paint={{"line-color": "#36454F", "line-width": 4, "line-dasharray": [1,1]}}
+                                />
+                            <>
+                                {this.allPushpins
+                                    // .filter(anno => !anno.layout_unrendered)
+                                    .map((pushpin, idx) => (
+                                        <Marker 
+                                            key={idx} 
+                                            longitude={NumCast(pushpin.longitude)} 
+                                            latitude={NumCast(pushpin.latitude)}
+                                            anchor='bottom'
+                                            onClick={(e: MarkerEvent<mapboxgl.Marker, MouseEvent>) => this.handleMarkerClick(e, pushpin)}
+                                        />
+                                ))}
+                            </>
+                            
+                            {/* {this.mapMarkers.length > 0 && this.mapMarkers.map((marker, idx) => (
+                                <Marker key={idx} longitude={marker.longitude} latitude={marker.latitude}/>
+                            ))} */}
 
-                    <BingMapsReact
+                        </MapboxMap>
+                    </MapProvider>
+
+                    {/* <BingMapsReact
                         onMapReady={this.bingMapReady} //
                         bingMapsKey={bingApiKey}
                         height="100%"
                         mapOptions={this.bingMapOptions}
                         width="100%"
                         viewOptions={this.bingViewOptions}
-                    />
-                    <div>
+                    /> */}
+                    {/* <div>
                         {!this._mapReady
                             ? null
                             : this.allAnnotations
@@ -793,7 +1182,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                           focus={returnOne}
                                       />
                                   ))}
-                    </div>
+                    </div> */}
                     {/* <MapBoxInfoWindow
                                     key={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})[Id]}
                                     {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
diff --git a/src/client/views/nodes/MapBox/MapboxApiUtility.ts b/src/client/views/nodes/MapBox/MapboxApiUtility.ts
new file mode 100644
index 000000000..80962f435
--- /dev/null
+++ b/src/client/views/nodes/MapBox/MapboxApiUtility.ts
@@ -0,0 +1,103 @@
+
+const MAPBOX_FORWARD_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
+const MAPBOX_REVERSE_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
+const MAPBOX_DIRECTIONS_BASE_URL = 'https://api.mapbox.com/directions/v5/mapbox';
+const MAPBOX_ACCESS_TOKEN = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNscHgwNDd1MDA3MXIydm92ODdianp6cGYifQ.WFAqbhwxtMHOWSPtu0l2uQ';
+
+export type TransportationType = 'driving' | 'cycling' | 'walking';
+
+export class MapboxApiUtility {
+
+    static forwardGeocodeForFeatures = async (searchText: string) => {
+        try {
+            const url = MAPBOX_FORWARD_GEOCODE_BASE_URL + encodeURI(searchText) +'.json' +`?access_token=${MAPBOX_ACCESS_TOKEN}`;
+            const response = await fetch(url);
+            const data = await response.json();
+            return data.features;
+        } catch (error: any){
+            // TODO: handle error in better way 
+            return null;
+        }
+    }
+
+    static reverseGeocodeForFeatures = async (longitude: number, latitude: number) => {
+        try {
+            const url = MAPBOX_REVERSE_GEOCODE_BASE_URL + encodeURI(longitude.toString() + "," + latitude.toString()) + '.json' +
+                `?access_token=${MAPBOX_ACCESS_TOKEN}`;
+            const response = await fetch(url);
+            const data = await response.json();
+            return data.features;
+        } catch (error: any){
+            return null;
+        }
+    }
+
+    static getDirections = async (origin: number[], destination: number[]): Promise<Record<TransportationType, any> | undefined> => {
+        try {
+            const drivingQuery = await fetch(
+                `${MAPBOX_DIRECTIONS_BASE_URL}/driving/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`);
+
+            const cyclingQuery = await fetch(
+                `${MAPBOX_DIRECTIONS_BASE_URL}/cycling/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`);
+
+            const walkingQuery = await fetch(
+                `${MAPBOX_DIRECTIONS_BASE_URL}/walking/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`);
+
+            const drivingJson = await drivingQuery.json();
+            const cyclingJson = await cyclingQuery.json();
+            const walkingJson = await walkingQuery.json();
+
+            console.log("Driving: ", drivingJson);
+            console.log("Cycling: ", cyclingJson);
+            console.log("Waling: ", walkingJson);
+
+            const routeMap = {
+                'driving': drivingJson.routes[0],
+                'cycling': cyclingJson.routes[0],
+                'walking': walkingJson.routes[0]
+            }
+
+            const routeInfoMap: Record<TransportationType, any> = {
+                'driving': {},
+                'cycling': {},
+                'walking': {},
+              };
+
+            Object.entries(routeMap).forEach(([key, routeData]) => {
+                const transportationTypeKey = key as TransportationType;
+                const geometry = routeData.geometry;
+                const coordinates = geometry.coordinates;
+
+                routeInfoMap[transportationTypeKey] = {
+                    duration: this.secondsToMinutesHours(routeData.duration),
+                    distance: this.metersToMiles(routeData.distance),
+                    coordinates: coordinates
+                }
+            })
+
+            return routeInfoMap;
+
+            // return current route info, and the temporary route 
+
+        } catch (error: any){
+            return undefined;
+            console.log("Error: ", error);
+        }
+    }
+
+    private static secondsToMinutesHours = (seconds: number) => {
+        const hours = Math.floor(seconds / 3600);
+        const minutes = Math.floor((seconds % 3600) / 60).toFixed(2);
+
+        if (hours === 0){
+            return `${minutes} min`
+        } else {
+            return `${hours} hr ${minutes} min`
+        }
+    }
+
+    private static metersToMiles = (meters: number) => {
+        return `${parseFloat((meters/1609.34).toFixed(2))} mi`;
+    }   
+
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
new file mode 100644
index 000000000..a6182991d
--- /dev/null
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -0,0 +1,844 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import BingMapsReact from 'bingmaps-react';
+import { Button, EditableText, IconButton, Type } from 'browndash-components';
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
+import { DocCss, Highlight } from '../../../../fields/DocSymbols';
+import { Id } from '../../../../fields/FieldSymbols';
+import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { Docs, DocUtils } from '../../../documents/Documents';
+import { DocumentType } from '../../../documents/DocumentTypes';
+import { DocumentManager } from '../../../util/DocumentManager';
+import { DragManager } from '../../../util/DragManager';
+import { LinkManager } from '../../../util/LinkManager';
+import { SnappingManager } from '../../../util/SnappingManager';
+import { Transform } from '../../../util/Transform';
+import { undoable, UndoManager } from '../../../util/UndoManager';
+import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
+import { Colors } from '../../global/globalEnums';
+import { SidebarAnnos } from '../../SidebarAnnos';
+import { DocumentView } from '../DocumentView';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { FormattedTextBox } from '../formattedText/FormattedTextBox';
+import { PinProps, PresBox } from '../trails';
+import './MapBox.scss';
+import { MapAnchorMenu } from '../MapBox/MapAnchorMenu';
+import { MapProvider, Map as MapboxMap } from 'react-map-gl';
+
+// amongus
+/**
+ * MapBox architecture:
+ * Main component: MapBox.tsx
+ * Supporting Components: SidebarAnnos, CollectionStackingView
+ *
+ * MapBox is a node that extends the ViewBoxAnnotatableComponent. Similar to PDFBox and WebBox, it supports interaction between sidebar content and document content.
+ * The main body of MapBox uses Google Maps API to allow location retrieval, adding map markers, pan and zoom, and open street view.
+ * Dash Document architecture is integrated with Maps API: When drag and dropping documents with ExifData (gps Latitude and Longitude information) available,
+ * sidebarAddDocument function checks if the document contains lat & lng information, if it does, then the document is added to both the sidebar and the infowindow (a pop up corresponding to a map marker--pin on map).
+ * The lat and lng field of the document is filled when importing (spec see ConvertDMSToDD method and processFileUpload method in Documents.ts).
+ * A map marker is considered a document that contains a collection with stacking view of documents, it has a lat, lng location, which is passed to Maps API's custom marker (red pin) to be rendered on the google maps
+ */
+
+const mapboxApiKey = "pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNsbnc2eHJpbTA1ZTUyam85aGx4Z2FhbGwifQ.2Kqw9mk-9wAAg9kmHmKzcg";
+const bingApiKey = process.env.BING_MAPS; // if you're running local, get a Bing Maps api key here: https://www.bingmapsportal.com/  and then add it to the .env file in the Dash-Web root directory as: _CLIENT_BING_MAPS=<your apikey>
+
+/**
+ * Consider integrating later: allows for drawing, circling, making shapes on map
+ */
+// const drawingManager = new window.google.maps.drawing.DrawingManager({
+//     drawingControl: true,
+//     drawingControlOptions: {
+//         position: google.maps.ControlPosition.TOP_RIGHT,
+//         drawingModes: [
+//             google.maps.drawing.OverlayType.MARKER,
+//             // currently we are not supporting the following drawing mode on map, a thought for future development
+//             google.maps.drawing.OverlayType.CIRCLE,
+//             google.maps.drawing.OverlayType.POLYLINE,
+//         ],
+//     },
+// });
+
+@observer
+export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+    public static LayoutString(fieldKey: string) {
+        return FieldView.LayoutString(MapBoxContainer, fieldKey);
+    }
+    private _dragRef = React.createRef<HTMLDivElement>();
+    private _sidebarRef = React.createRef<SidebarAnnos>();
+    private _ref: React.RefObject<HTMLDivElement> = React.createRef();
+    private _disposers: { [key: string]: IReactionDisposer } = {};
+    private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
+
+    @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
+    @computed get allSidebarDocs() {
+        return DocListCast(this.dataDoc[this.SidebarKey]);
+    }
+    // this list contains pushpins and configs
+    @computed get allAnnotations() {
+        return DocListCast(this.dataDoc[this.annotationKey]);
+    }
+    @computed get allPushpins() {
+        return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN);
+    }
+    @computed get SidebarShown() {
+        return this.layoutDoc._layout_showSidebar ? true : false;
+    }
+    @computed get sidebarWidthPercent() {
+        return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
+    }
+    @computed get sidebarColor() {
+        return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4'));
+    }
+    @computed get SidebarKey() {
+        return this.fieldKey + '_sidebar';
+    }
+
+    componentDidMount() {
+        this._unmounting = false;
+        this.props.setContentView?.(this);
+    }
+
+    _unmounting = false;
+    componentWillUnmount(): void {
+        this._unmounting = true;
+        this.deselectPin();
+        this._rerenderTimeout && clearTimeout(this._rerenderTimeout);
+        Object.keys(this._disposers).forEach(key => this._disposers[key]?.());
+    }
+
+    /**
+     * Called when dragging documents into map sidebar or directly into infowindow; to create a map marker, ref to MapMarkerDocument in Documents.ts
+     * @param doc
+     * @param sidebarKey
+     * @returns
+     */
+    sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
+        if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
+        const docs = doc instanceof Doc ? [doc] : doc;
+        docs.forEach(doc => {
+            let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this.selectedPin;
+            if (doc.latitude !== undefined && doc.longitude !== undefined && !existingPin) {
+                existingPin = this.createPushpin(NumCast(doc.latitude), NumCast(doc.longitude), StrCast(doc.map));
+            }
+            if (existingPin) {
+                setTimeout(() => {
+                    // we use a timeout in case this is called from the sidebar which may have just added a link that hasn't made its way into th elink manager yet
+                    if (!LinkManager.Instance.getAllRelatedLinks(doc).some(link => DocCast(link.link_anchor_1)?.mapPin === existingPin || DocCast(link.link_anchor_2)?.mapPin === existingPin)) {
+                        const anchor = this.getAnchor(true, undefined, existingPin);
+                        anchor && DocUtils.MakeLink(anchor, doc, { link_relationship: 'link to map location' });
+                        doc.latitude = existingPin?.latitude;
+                        doc.longitude = existingPin?.longitude;
+                    }
+                });
+            }
+        }); //add to annotation list
+
+        return this.addDocument(doc, sidebarKey); // add to sidebar list
+    };
+
+    removeMapDocument = (doc: Doc | Doc[], annotationKey?: string) => {
+        const docs = doc instanceof Doc ? [doc] : doc;
+        this.allAnnotations.filter(anno => docs.includes(DocCast(anno.mapPin))).forEach(anno => (anno.mapPin = undefined));
+        return this.removeDocument(doc, annotationKey, undefined);
+    };
+
+    /**
+     * Removing documents from the sidebar
+     * @param doc
+     * @param sidebarKey
+     * @returns
+     */
+    sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => this.removeMapDocument(doc, sidebarKey);
+
+    /**
+     * Toggle sidebar onclick the tiny comment button on the top right corner
+     * @param e
+     */
+    sidebarBtnDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(
+            this,
+            e,
+            (e, down, delta) =>
+                runInAction(() => {
+                    const localDelta = this.props
+                        .ScreenToLocalTransform()
+                        .scale(this.props.NativeDimScaling?.() || 1)
+                        .transformDirection(delta[0], delta[1]);
+                    const fullWidth = NumCast(this.layoutDoc._width);
+                    const mapWidth = fullWidth - this.sidebarWidth();
+                    if (this.sidebarWidth() + localDelta[0] > 0) {
+                        this.layoutDoc._layout_showSidebar = true;
+                        this.layoutDoc._width = fullWidth + localDelta[0];
+                        this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%';
+                    } else {
+                        this.layoutDoc._layout_showSidebar = false;
+                        this.layoutDoc._width = mapWidth;
+                        this.layoutDoc._layout_sidebarWidthPercent = '0%';
+                    }
+                    return false;
+                }),
+            emptyFunction,
+            () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map')
+        );
+    };
+    sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
+
+    /**
+     * Handles toggle of sidebar on click the little comment button
+     */
+    @computed get sidebarHandle() {
+        return (
+            <div
+                className="mapBox-overlayButton-sidebar"
+                key="sidebar"
+                title="Toggle Sidebar"
+                style={{
+                    display: !this.props.isContentActive() ? 'none' : undefined,
+                    top: StrCast(this.rootDoc._layout_showTitle) === 'title' ? 20 : 5,
+                    backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
+                }}
+                onPointerDown={this.sidebarBtnDown}>
+                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+            </div>
+        );
+    }
+
+    // TODO: Adding highlight box layer to Maps
+    @action
+    toggleSidebar = () => {
+        const prevWidth = this.sidebarWidth();
+        this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? `${(100 * 0.2) / 1.2}%` : '0%') !== '0%';
+        this.layoutDoc._width = this.layoutDoc._layout_showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
+    };
+
+    startAnchorDrag = (e: PointerEvent, ele: HTMLElement) => {
+        e.preventDefault();
+        e.stopPropagation();
+
+        const sourceAnchorCreator = action(() => {
+            const note = this.getAnchor(true);
+            if (note && this.selectedPin) {
+                note.latitude = this.selectedPin.latitude;
+                note.longitude = this.selectedPin.longitude;
+                note.map = this.selectedPin.map;
+            }
+            return note as Doc;
+        });
+
+        const targetCreator = (annotationOn: Doc | undefined) => {
+            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
+            FormattedTextBox.SelectOnLoad = target[Id];
+            return target;
+        };
+        const docView = this.props.DocumentView?.();
+        docView &&
+            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
+                dragComplete: e => {
+                    if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
+                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.Document;
+                        e.annoDragData.linkSourceDoc.followLinkZoom = false;
+                    }
+                },
+            });
+    };
+
+    createNoteAnnotation = () => {
+        const createFunc = undoable(
+            action(() => {
+                const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', LinkedTo]);
+                if (note && this.selectedPin) {
+                    note.latitude = this.selectedPin.latitude;
+                    note.longitude = this.selectedPin.longitude;
+                    note.map = this.selectedPin.map;
+                }
+            }),
+            'create note annotation'
+        );
+        if (!this.layoutDoc.layout_showSidebar) {
+            this.toggleSidebar();
+            setTimeout(createFunc);
+        } else createFunc();
+    };
+    sidebarDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
+    };
+    sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
+        const bounds = this._ref.current!.getBoundingClientRect();
+        this.layoutDoc._layout_sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%';
+        this.layoutDoc._layout_showSidebar = this.layoutDoc._layout_sidebarWidthPercent !== '0%';
+        e.preventDefault();
+        return false;
+    };
+
+    setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
+
+    addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey);
+
+    pointerEvents = () => (this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
+
+    panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth();
+    panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
+    scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+    transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter];
+    opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter];
+    infoWidth = () => this.props.PanelWidth() / 5;
+    infoHeight = () => this.props.PanelHeight() / 5;
+    anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
+    savedAnnotations = () => this._savedAnnotations;
+
+    _bingSearchManager: any;
+    _bingMap: any;
+    get MicrosoftMaps() {
+        return (window as any).Microsoft.Maps;
+    }
+    // uses Bing Search to retrieve lat/lng for a location.  eg.,
+    //   const results = this.geocodeQuery(map.map, 'Philadelphia, PA');
+    // to move the map to that location:
+    //     const location = await this.geocodeQuery(this._bingMap, 'Philadelphia, PA');
+    //     this._bingMap.current.setView({
+    //        mapTypeId: this.MicrosoftMaps.MapTypeId.aerial,
+    //        center: new this.MicrosoftMaps.Location(loc.latitude, loc.longitude),
+    //     });
+    //
+    bingGeocode = (map: any, query: string) => {
+        return new Promise<{ latitude: number; longitude: number }>((res, reject) => {
+            //If search manager is not defined, load the search module.
+            if (!this._bingSearchManager) {
+                //Create an instance of the search manager and call the geocodeQuery function again.
+                this.MicrosoftMaps.loadModule('Microsoft.Maps.Search', () => {
+                    this._bingSearchManager = new this.MicrosoftMaps.Search.SearchManager(map.current);
+                    res(this.bingGeocode(map, query));
+                });
+            } else {
+                this._bingSearchManager.geocode({
+                    where: query,
+                    callback: action((r: any) => res(r.results[0].location)),
+                    errorCallback: (e: any) => reject(),
+                });
+            }
+        });
+    };
+
+    @observable
+    bingSearchBarContents: any = this.rootDoc.map; // For Bing Maps: The contents of the Bing search bar (string)
+
+    geoDataRequestOptions = {
+        entityType: 'PopulatedPlace',
+    };
+
+    // incrementer: number = 0;
+    /*
+     * Creates Pushpin doc and adds it to the list of annotations
+     */
+    @action
+    createPushpin = undoable((latitude: number, longitude: number, map?: string) => {
+        // Stores the pushpin as a MapMarkerDocument
+        const pushpin = Docs.Create.PushpinDocument(
+            NumCast(latitude),
+            NumCast(longitude),
+            false,
+            [],
+            { title: map ?? `lat=${latitude},lng=${longitude}`, map: map }
+            // ,'pushpinIDamongus'+ this.incrementer++
+        );
+        this.addDocument(pushpin, this.annotationKey);
+        return pushpin;
+        // mapMarker.infoWindowOpen = true;
+    }, 'createpin');
+
+    // The pin that is selected
+    @observable selectedPin: Doc | undefined;
+
+    @action
+    deselectPin = () => {
+        if (this.selectedPin) {
+            // Removes filter
+            Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove');
+            Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove');
+            Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
+
+            const temp = this.selectedPin;
+            if (!this._unmounting) {
+                this._bingMap.current.entities.remove(this.map_docToPinMap.get(temp));
+            }
+            const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(temp.latitude, temp.longitude));
+            this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(temp as Doc));
+            if (!this._unmounting) {
+                this._bingMap.current.entities.push(newpin);
+            }
+            this.map_docToPinMap.set(temp, newpin);
+            this.selectedPin = undefined;
+            this.bingSearchBarContents = this.rootDoc.map;
+        }
+    };
+
+    getView = async (doc: Doc) => {
+        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar();
+        return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+    };
+    /*
+     * Pushpin onclick
+     */
+    @action
+    pushpinClicked = (pinDoc: Doc) => {
+        this.deselectPin();
+        this.selectedPin = pinDoc;
+        this.bingSearchBarContents = pinDoc.map;
+
+        // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'match');
+        // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'match');
+        Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPin)}`, 'check');
+
+        this.recolorPin(this.selectedPin, 'green');
+
+        MapAnchorMenu.Instance.Delete = this.deleteSelectedPin;
+        MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
+        MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
+        MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
+
+        const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPin.latitude, this.selectedPin.longitude));
+        const x = point.x + (this.props.PanelWidth() - this.sidebarWidth()) / 2;
+        const y = point.y + this.props.PanelHeight() / 2 + 32;
+        const cpt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+        MapAnchorMenu.Instance.jumpTo(cpt[0], cpt[1], true);
+
+        document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
+    };
+
+    /**
+     * Map OnClick
+     */
+    @action
+    mapOnClick = (e: { location: { latitude: any; longitude: any } }) => {
+        this.props.select(false);
+        this.deselectPin();
+    };
+    /*
+     * Updates values of layout doc to match the current map
+     */
+    @action
+    mapRecentered = () => {
+        if (
+            Math.abs(NumCast(this.dataDoc.latitude) - this._bingMap.current.getCenter().latitude) > 1e-7 || //
+            Math.abs(NumCast(this.dataDoc.longitude) - this._bingMap.current.getCenter().longitude) > 1e-7
+        ) {
+            this.dataDoc.latitude = this._bingMap.current.getCenter().latitude;
+            this.dataDoc.longitude = this._bingMap.current.getCenter().longitude;
+            this.dataDoc.map = '';
+            this.bingSearchBarContents = '';
+        }
+        this.dataDoc.map_zoom = this._bingMap.current.getZoom();
+    };
+    /*
+     * Updates maptype
+     */
+    @action
+    updateMapType = () => (this.dataDoc.map_type = this._bingMap.current.getMapTypeId());
+
+    /*
+     * For Bing Maps
+     * Called by search button's onClick
+     * Finds the geocode of the searched contents and sets location to that location
+     **/
+    @action
+    bingSearch = () => {
+        return this.bingGeocode(this._bingMap, this.bingSearchBarContents).then(location => {
+            this.dataDoc.latitude = location.latitude;
+            this.dataDoc.longitude = location.longitude;
+            this.dataDoc.map_zoom = this._bingMap.current.getZoom();
+            this.dataDoc.map = this.bingSearchBarContents;
+        });
+    };
+
+    /*
+     * Returns doc w/ relevant info
+     */
+    getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps, existingPin?: Doc) => {
+        /// this should use SELECTED pushpin for lat/long  if there is a selection, otherwise CENTER
+        const anchor = Docs.Create.ConfigDocument({
+            title: 'MapAnchor:' + this.rootDoc.title,
+            text: StrCast(this.selectedPin?.map) || StrCast(this.rootDoc.map) || 'map location',
+            config_latitude: NumCast((existingPin ?? this.selectedPin)?.latitude ?? this.dataDoc.latitude),
+            config_longitude: NumCast((existingPin ?? this.selectedPin)?.longitude ?? this.dataDoc.longitude),
+            config_map_zoom: NumCast(this.dataDoc.map_zoom),
+            config_map_type: StrCast(this.dataDoc.map_type),
+            config_map: StrCast((existingPin ?? this.selectedPin)?.map) || StrCast(this.dataDoc.map),
+            layout_unrendered: true,
+            mapPin: existingPin ?? this.selectedPin,
+            annotationOn: this.rootDoc,
+        });
+        if (anchor) {
+            if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
+            addAsAnnotation && this.addDocument(anchor);
+            PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.rootDoc);
+            return anchor;
+        }
+        return this.rootDoc;
+    };
+
+    map_docToPinMap = new Map<Doc, any>();
+    map_pinHighlighted = new Map<Doc, boolean>();
+    /*
+     *   Input: pin doc
+     *   Adds MicrosoftMaps Pushpin to the map (render)
+     */
+    @action
+    addPushpin = (pin: Doc) => {
+        const pushPin = pin.infoWindowOpen
+            ? new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(pin.latitude, pin.longitude), {})
+            : new this.MicrosoftMaps.Pushpin(
+                  new this.MicrosoftMaps.Location(pin.latitude, pin.longitude)
+                  // {icon: 'http://icons.iconarchive.com/icons/icons-land/vista-map-markers/24/Map-Marker-Marker-Outside-Chartreuse-icon.png'}
+              );
+
+        this._bingMap.current.entities.push(pushPin);
+
+        this.MicrosoftMaps.Events.addHandler(pushPin, 'click', (e: any) => this.pushpinClicked(pin));
+        // this.MicrosoftMaps.Events.addHandler(pushPin, 'dblclick', (e: any) => this.pushpinDblClicked(pushPin, pin));
+        this.map_docToPinMap.set(pin, pushPin);
+    };
+
+    /*
+     *   Input: pin doc
+     *   Removes pin from annotations
+     */
+    @action
+    removePushpin = (pinDoc: Doc) => this.removeMapDocument(pinDoc, this.annotationKey);
+
+    /*
+     * Removes pushpin from map render
+     */
+    deletePushpin = (pinDoc: Doc) => {
+        if (!this._unmounting) {
+            this._bingMap.current.entities.remove(this.map_docToPinMap.get(pinDoc));
+        }
+        this.map_docToPinMap.delete(pinDoc);
+        this.selectedPin = undefined;
+    };
+
+    @action
+    deleteSelectedPin = undoable(() => {
+        if (this.selectedPin) {
+            // Removes filter
+            Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove');
+            Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove');
+            Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
+
+            this.removePushpin(this.selectedPin);
+        }
+        MapAnchorMenu.Instance.fadeOut(true);
+        document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
+    }, 'delete pin');
+
+    tryHideMapAnchorMenu = (e: PointerEvent) => {
+        let target = document.elementFromPoint(e.x, e.y);
+        while (target) {
+            if (target === MapAnchorMenu.top.current) return;
+            target = target.parentElement;
+        }
+        e.stopPropagation();
+        e.preventDefault();
+        MapAnchorMenu.Instance.fadeOut(true);
+        document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
+    };
+
+    @action
+    centerOnSelectedPin = () => {
+        if (this.selectedPin) {
+            this.dataDoc.latitude = this.selectedPin.latitude;
+            this.dataDoc.longitude = this.selectedPin.longitude;
+            this.dataDoc.map = this.selectedPin.map ?? '';
+            this.bingSearchBarContents = this.selectedPin.map;
+        }
+        MapAnchorMenu.Instance.fadeOut(true);
+        document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu);
+    };
+
+    /**
+     * View options for bing maps
+     */
+    bingViewOptions = {
+        // center: { latitude: this.dataDoc.latitude ?? defaultCenter.lat, longitude: this.dataDoc.longitude ?? defaultCenter.lng },
+        zoom: this.dataDoc.latitude ?? 10,
+        mapTypeId: 'grayscale',
+    };
+
+    /**
+     * Map options
+     */
+    bingMapOptions = {
+        navigationBarMode: 'square',
+        backgroundColor: '#f1f3f4',
+        enableInertia: true,
+        supportedMapTypes: ['grayscale', 'canvasLight'],
+        disableMapTypeSelectorMouseOver: true,
+        // showScalebar:true
+        // disableRoadView:true,
+        // disableBirdseye:true
+        streetsideOptions: {
+            showProblemReporting: false,
+            showCurrentAddress: false,
+        },
+    };
+
+    @action
+    searchbarOnEdit = (newText: string) => (this.bingSearchBarContents = newText);
+
+    recolorPin = (pin: Doc, color?: string) => {
+        this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin));
+        this.map_docToPinMap.delete(pin);
+        const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(pin.latitude, pin.longitude), color ? { color } : {});
+        this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(pin));
+        this._bingMap.current.entities.push(newpin);
+        this.map_docToPinMap.set(pin, newpin);
+    };
+
+    /*
+     * Called when BingMap is first rendered
+     * Initializes starting values
+     */
+    @observable _mapReady = false;
+    @action
+    bingMapReady = (map: any) => {
+        this._mapReady = true;
+        this._bingMap = map.map;
+        if (!this._bingMap.current) {
+            alert('NO Map!?');
+        }
+        this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'click', this.mapOnClick);
+        this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'viewchangeend', undoable(this.mapRecentered, 'Map Layout Change'));
+        this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'maptypechanged', undoable(this.updateMapType, 'Map ViewType Change'));
+
+        this._disposers.mapLocation = reaction(
+            () => this.rootDoc.map,
+            mapLoc => (this.bingSearchBarContents = mapLoc),
+            { fireImmediately: true }
+        );
+        this._disposers.highlight = reaction(
+            () => this.allAnnotations.map(doc => doc[Highlight]),
+            () => {
+                const allConfigPins = this.allAnnotations.map(doc => ({ doc, pushpin: DocCast(doc.mapPin) })).filter(pair => pair.pushpin);
+                allConfigPins.forEach(({ doc, pushpin }) => {
+                    if (!pushpin[Highlight] && this.map_pinHighlighted.get(pushpin)) {
+                        this.recolorPin(pushpin);
+                        this.map_pinHighlighted.delete(pushpin);
+                    }
+                });
+                allConfigPins.forEach(({ doc, pushpin }) => {
+                    if (doc[Highlight] && !this.map_pinHighlighted.get(pushpin)) {
+                        this.recolorPin(pushpin, 'orange');
+                        this.map_pinHighlighted.set(pushpin, true);
+                    }
+                });
+            },
+            { fireImmediately: true }
+        );
+
+        this._disposers.location = reaction(
+            () => ({ lat: this.rootDoc.latitude, lng: this.rootDoc.longitude, zoom: this.rootDoc.map_zoom, mapType: this.rootDoc.map_type }),
+            locationObject => {
+                // if (this._bingMap.current)
+                try {
+                    locationObject?.zoom &&
+                        this._bingMap.current?.setView({
+                            mapTypeId: locationObject.mapType,
+                            zoom: locationObject.zoom,
+                            center: new this.MicrosoftMaps.Location(locationObject.lat, locationObject.lng),
+                        });
+                } catch (e) {
+                    console.log(e);
+                }
+            },
+            { fireImmediately: true }
+        );
+    };
+
+    dragToggle = (e: React.PointerEvent) => {
+        let dragClone: HTMLDivElement | undefined;
+
+        setupMoveUpEvents(
+            e,
+            e,
+            e => {
+                if (!dragClone) {
+                    dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement;
+                    dragClone.style.position = 'absolute';
+                    dragClone.style.zIndex = '10000';
+                    DragManager.Root().appendChild(dragClone);
+                }
+                dragClone.style.transform = `translate(${e.clientX - 15}px, ${e.clientY - 15}px)`;
+                return false;
+            },
+            e => {
+                if (!dragClone) return;
+                DragManager.Root().removeChild(dragClone);
+                let target = document.elementFromPoint(e.x, e.y);
+                while (target) {
+                    if (target === this._ref.current) {
+                        const cpt = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+                        const x = cpt[0] - (this.props.PanelWidth() - this.sidebarWidth()) / 2;
+                        const y = cpt[1] - 32 /* height of search bar */ - this.props.PanelHeight() / 2;
+                        const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
+                        this.createPushpin(location.latitude, location.longitude);
+                        break;
+                    }
+                    target = target.parentElement;
+                }
+            },
+            e => {
+                const createPin = () => this.createPushpin(this.rootDoc.latitude, this.rootDoc.longitude, this.rootDoc.map);
+                if (this.bingSearchBarContents) {
+                    this.bingSearch().then(createPin);
+                } else createPin();
+            }
+        );
+    };
+
+    searchbarKeyDown = (e: any) => e.key === 'Enter' && this.bingSearch();
+
+    static _firstRender = true;
+    static _rerenderDelay = 500;
+    _rerenderTimeout: any;
+    render() {
+        // bcz:  no idea what's going on here, but bings maps have some kind of bug
+        // such that we need to delay rendering a second map on startup until the first map is rendered.
+        this.rootDoc[DocCss];
+        if (MapBoxContainer._rerenderDelay) {
+            // prettier-ignore
+            this._rerenderTimeout = this._rerenderTimeout ??
+                setTimeout(action(() => {
+                    if ((window as any).Microsoft?.Maps?.Internal._WorkDispatcher) {
+                        MapBoxContainer._rerenderDelay = 0;
+                    }
+                    this._rerenderTimeout = undefined;
+                    this.rootDoc[DocCss] = this.rootDoc[DocCss] + 1;
+                }), MapBoxContainer._rerenderDelay);
+            return null;
+        }
+
+        const renderAnnotations = (childFilters?: () => string[]) => null;
+        return (
+            <div className="mapBox" ref={this._ref}>
+                <div
+                    className="mapBox-wrapper"
+                    onWheel={e => e.stopPropagation()}
+                    onPointerDown={async e => {
+                        e.button === 0 && !e.ctrlKey && e.stopPropagation();
+                    }}
+                    style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
+                    <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
+                    {renderAnnotations(this.opaqueFilter)}
+                    {SnappingManager.GetIsDragging() ? null : renderAnnotations()}
+
+                    <div className="mapBox-searchbar">
+                        <EditableText
+                            // editing
+                            setVal={(newText: string | number) => typeof newText === 'string' && this.searchbarOnEdit(newText)}
+                            onEnter={e => this.bingSearch()}
+                            placeholder={this.bingSearchBarContents || 'enter city/zip/...'}
+                            textAlign="center"
+                        />
+                        <IconButton
+                            icon={
+                                <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="magnifying-glass" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" color="#DFDFDF">
+                                    <path
+                                        fill="currentColor"
+                                        d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"></path>
+                                </svg>
+                            }
+                            onClick={this.bingSearch}
+                            type={Type.TERT}
+                        />
+                        <div style={{ width: 30, height: 30 }} ref={this._dragRef} onPointerDown={this.dragToggle}>
+                            <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size={'lg'} icon={'bullseye'} />} />
+                        </div>
+                    </div>
+                    <MapProvider>
+                        <MapboxMap
+                            id="mabox-map"
+                            mapStyle={`mapbox://styles/mapbox/streets-v9`}
+                            mapboxAccessToken={mapboxApiKey}
+                        />
+                    </MapProvider>
+                    
+  
+{/* 
+                    <BingMapsReact
+                        onMapReady={this.bingMapReady} //
+                        bingMapsKey={bingApiKey}
+                        height="100%"
+                        mapOptions={this.bingMapOptions}
+                        width="100%"
+                        viewOptions={this.bingViewOptions}
+                    /> */}
+                    <div>
+                        {!this._mapReady
+                            ? null
+                            : this.allAnnotations
+                                  .filter(anno => !anno.layout_unrendered)
+                                  .map((pushpin, i) => (
+                                      <DocumentView
+                                          key={i}
+                                          {...this.props}
+                                          renderDepth={this.props.renderDepth + 1}
+                                          Document={pushpin}
+                                          DataDoc={undefined}
+                                          PanelWidth={returnOne}
+                                          PanelHeight={returnOne}
+                                          NativeWidth={returnOne}
+                                          NativeHeight={returnOne}
+                                          onKey={undefined}
+                                          onDoubleClick={undefined}
+                                          onBrowseClick={undefined}
+                                          childFilters={returnEmptyFilter}
+                                          childFiltersByRanges={returnEmptyFilter}
+                                          searchFilterDocs={returnEmptyDoclist}
+                                          isDocumentActive={returnFalse}
+                                          isContentActive={returnFalse}
+                                          addDocTab={returnFalse}
+                                          ScreenToLocalTransform={Transform.Identity}
+                                          fitContentsToBox={undefined}
+                                          focus={returnOne}
+                                      />
+                                  ))}
+                    </div>
+                    {/* <MapBoxInfoWindow
+                                    key={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})[Id]}
+                                    {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                                    place={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})}
+                                    markerMap={this.markerMap}
+                                    PanelWidth={this.infoWidth}
+                                    PanelHeight={this.infoHeight}
+                                    moveDocument={this.moveDocument}
+                                    isAnyChildContentActive={this.isAnyChildContentActive}
+                                    whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+                                /> */}
+                </div>
+                {/* </LoadScript > */}
+                <div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+                    <SidebarAnnos
+                        ref={this._sidebarRef}
+                        {...this.props}
+                        fieldKey={this.fieldKey}
+                        rootDoc={this.rootDoc}
+                        layoutDoc={this.layoutDoc}
+                        dataDoc={this.dataDoc}
+                        usePanelWidth={true}
+                        showSidebar={this.SidebarShown}
+                        nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
+                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+                        PanelWidth={this.sidebarWidth}
+                        sidebarAddDocument={this.sidebarAddDocument}
+                        moveDocument={this.moveDocument}
+                        removeDocument={this.sidebarRemoveDocument}
+                    />
+                </div>
+                {this.sidebarHandle}
+            </div>
+        );
+    }
+}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 24c18c232..084cb872a 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -816,7 +816,7 @@ export namespace Doc {
     }
 
     export function FindReferences(infield: Doc | List<any>, references: Set<Doc>, system: boolean | undefined) {
-        if (infield instanceof List<any>) {
+        if (!(infield instanceof Doc)) {
             infield.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
             return;
         }
diff --git a/webpack.config.js b/webpack.config.js
index 01625988c..72dad9493 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -99,7 +99,8 @@ module.exports = {
                 use: 'file-loader?name=fonts/[name].[ext]!static',
             },
             {
-                test: /\.scss|css$/,
+                test: /\.(scss|css)$/,
+                // test: /\.scss|css$/,
                 use: [
                     {
                         loader: 'style-loader',
-- 
cgit v1.2.3-70-g09d2


From bc23855777633dfd1caf7237b75c1e8fee88dff4 Mon Sep 17 00:00:00 2001
From: zaultavangar <zaul_tavangar@brown.edu>
Date: Mon, 11 Dec 2023 20:37:52 -0500
Subject: new updates to map feature

---
 src/client/documents/Documents.ts                  |  25 +-
 src/client/views/nodes/MapBox/MapAnchorMenu.scss   |  20 ++
 src/client/views/nodes/MapBox/MapAnchorMenu.tsx    |  76 ++++--
 src/client/views/nodes/MapBox/MapBox.scss          |  40 ++-
 src/client/views/nodes/MapBox/MapBox.tsx           | 288 +++++++++++++++------
 src/client/views/nodes/MapBox/MapboxApiUtility.ts  |   2 +
 src/client/views/nodes/MapBox/MarkerIcons.tsx      |  87 +++++++
 .../icon_images/mapbox-marker-icon-20px-blue.png   | Bin 0 -> 1623 bytes
 src/fields/Doc.ts                                  |   2 +-
 9 files changed, 438 insertions(+), 102 deletions(-)
 create mode 100644 src/client/views/nodes/MapBox/MarkerIcons.tsx
 create mode 100644 src/client/views/nodes/MapBox/icon_images/mapbox-marker-icon-20px-blue.png

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 02794c432..3db9f4f06 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -176,12 +176,14 @@ export class DocumentOptions {
     _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units");
     latitude?: NUMt = new NumInfo('latitude coordinate for map views', false);
     longitude?: NUMt = new NumInfo('longitude coordinate for map views', false);
-    routeCoordinates?: LISTt = new ListInfo("stores a route's/direction's coordinates"); // for a route document, this stores the route's coordiantes
+    routeCoordinates?: STRt = new StrInfo("stores a route's/direction's coordinates (stringified version)"); // for a route document, this stores the route's coordiantes
+    markerType?: STRt = new StrInfo('Defines the marker type for a pushpin document');
+    markerColor?: STRt= new StrInfo('Defines the marker color for a pushpin document');
     map?: STRt = new StrInfo('text location of map');
     map_type?: STRt = new StrInfo('type of map view', false);
     map_zoom?: NUMt = new NumInfo('zoom of a map view', false);
     wikiData?: STRt = new StrInfo('WikiData ID related to map location');
-    description?: STRt = new StrInfo('A description of the document')
+    description?: STRt = new StrInfo('A description of the document');
     _timecodeToShow?: NUMt = new NumInfo('the time that a document should be displayed (e.g., when an annotation shows up as a video plays)', false);
     _timecodeToHide?: NUMt = new NumInfo('the time that a document should be hidden', false);
     _width?: NUMt = new NumInfo('displayed width of a document');
@@ -783,6 +785,13 @@ export namespace Docs {
                     options: {},
                 },
             ],
+            [
+                DocumentType.MAPROUTE,
+                {
+                    layout: { view: CollectionView, dataField: defaultDataKey },
+                    options: {},
+                },
+            ],
         ]);
 
         const suffix = 'Proto';
@@ -1139,16 +1148,12 @@ export namespace Docs {
             documents: Array<Doc>, 
             options: DocumentOptions, 
             id?: string) {
+
             return InstanceFromProto(Prototypes.get(DocumentType.PUSHPIN), new List(documents), { latitude, longitude, infoWindowOpen, ...options }, id);
         }
 
-        export function MapRouteDocument(
-            infoWindowOpen: boolean,
-            documents: Array<Doc>,
-            options: DocumentOptions,
-            id?: string
-        ) {
-            return InstanceFromProto(Prototypes.get(DocumentType.MAPROUTE), new List(documents), {infoWindowOpen, ...options}, id)
+        export function MapRouteDocument(infoWindowOpen: boolean, documents: Array<Doc>, options: DocumentOptions, id?: string) {
+            return InstanceFromProto(Prototypes.get(DocumentType.MAPROUTE), new List(documents), { infoWindowOpen, ...options }, id);
         }
 
         // shouldn't ever need to create a KVP document-- instead set the LayoutTemplateString to be a KeyValueBox for the DocumentView (see addDocTab in TabDocView)
@@ -2015,4 +2020,4 @@ ScriptingGlobals.add(function generateLinkTitle(link: Doc) {
     const link_anchor_2title = link.link_anchor_2 && link.link_anchor_2 !== link ? Cast(link.link_anchor_2, Doc, null)?.title : '<?>';
     const relation = link.link_relationship || 'to';
     return `${link_anchor_1title} (${relation}) ${link_anchor_2title}`;
-});
+});
\ No newline at end of file
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.scss b/src/client/views/nodes/MapBox/MapAnchorMenu.scss
index e2fcd78fc..c36d98afe 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.scss
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.scss
@@ -103,6 +103,26 @@
 
     }
 
+    .customized-marker-container{
+        display: flex;
+        flex-direction: column;
+        gap: 10px;
+
+        .current-marker-container{
+            display: flex;
+            align-items: center;
+            gap: 5px;
+        }
+
+        .all-markers-container{
+            display: flex;
+            align-items:  center;
+            gap: 10px;
+            flex-wrap: wrap;
+            max-width: 400px;
+        }
+    }
+
 
 
 
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index fca3998c8..2c2879900 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -32,6 +32,9 @@ import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/materi
 import { MapboxApiUtility, TransportationType } from './MapboxApiUtility';
 import { MapBox } from './MapBox';
 import { List } from '../../../../fields/List';
+import { MapboxColor, MarkerIcons } from './MarkerIcons';
+import { CirclePicker } from 'react-color';
+import { Position } from 'geojson';
 
 type MapAnchorMenuType = 'standard' | 'route' | 'calendar' | 'customize';
 
@@ -58,9 +61,13 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     public DisplayRoute: (routeInfoMap: Record<TransportationType, any> | undefined, type: TransportationType) => void = unimplementedFunction;
     public HideRoute: () => void = unimplementedFunction;
-    public AddNewRouteToMap: (coordinates: List<any>, origin: string, destination: string) => void = unimplementedFunction;
+    public AddNewRouteToMap: (coordinates: Position[], origin: string, destination: string) => void = unimplementedFunction;
     public CreatePin: (feature: any) => void = unimplementedFunction;
 
+    public UpdateMarkerColor: (color: string) => void = unimplementedFunction;
+    public UpdateMarkerIcon: (iconKey: string) => void = unimplementedFunction;
+
+    
 
     private allMapPinDocs: Doc[] = [];
 
@@ -151,11 +158,13 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     @action
     CustomizeClick = () => {
+        this.currentRouteInfoMap = undefined;
         this.menuType = 'customize';
     }
 
     @action
     BackClick = () => {
+        this.currentRouteInfoMap = undefined;
         this.menuType = 'standard';
     }
 
@@ -238,11 +247,26 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     HandleAddRouteClick = () => {
         if (this.currentRouteInfoMap && this.selectedTransportationType && this.selectedDestinationFeature){
             const coordinates = this.currentRouteInfoMap[this.selectedTransportationType].coordinates;
-            this.AddNewRouteToMap(this.currentRouteInfoMap![this.selectedTransportationType].coordinates, this.title ?? "", this.selectedDestinationFeature.place_name);
+            console.log(coordinates);
+            this.AddNewRouteToMap(coordinates, this.title ?? "", this.selectedDestinationFeature.place_name);
             this.HideRoute();
         }
     }
 
+    getMarkerIcon = (): JSX.Element | undefined => {
+        if (this.pinDoc){
+            const markerType = StrCast(this.pinDoc.markerType);
+            const markerColor = StrCast(this.pinDoc.markerColor);
+
+            if (markerType.startsWith("MAPBOX")){
+                return MarkerIcons.getMapboxIcon(markerColor as MapboxColor);
+            } else { // font awesome icon
+                return MarkerIcons.getFontAwesomeIcon(markerType, markerColor);
+            }
+        }
+        return undefined;
+    }
+
 
     render() {
         const buttons = (
@@ -325,19 +349,6 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             icon={<FontAwesomeIcon icon={faArrowLeft as IconLookup} />}
                             color={SettingsManager.userColor}
                         />
-                        <IconButton
-                            tooltip="Upload image" //
-                            onPointerDown={this.TriggerFileInputClick}
-                            icon={<FontAwesomeIcon icon={faUpload as IconLookup} />}
-                            color={SettingsManager.userColor}
-                        />
-                        <input
-                            type="file"
-                            accept="image/*" // Optionally, specify accepted file types
-                            ref={this._fileInputRef}
-                            style={{ display: "none" }}
-                            onChange={() => {}}
-                        />
                         <IconButton
                             tooltip="Revert to original" //
                             onPointerDown={this.BackClick}
@@ -468,6 +479,41 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                     </div>
                     
 
+                }
+                {this.menuType === 'customize' && 
+                <div className='customized-marker-container'>
+                    <div className='current-marker-container'>
+                        <div>Current Marker: </div>
+                        <div>
+                            {this.getMarkerIcon()}
+                        </div>
+                    </div>
+                    <div className='color-picker-container' style={{marginBottom: '10px'}}>
+                        <CirclePicker
+                            circleSize={15}
+                            circleSpacing={7}
+                            width='100%'
+                            onChange={(color) => console.log(color.hex)}
+                        />
+                    </div>
+                    <div className='all-markers-container'>
+                         {Object.keys(MarkerIcons.FAMarkerIconsMap).map((iconKey) => {
+                            const icon = MarkerIcons.getFontAwesomeIcon(iconKey);
+                            if (icon){
+                                return (
+                                    <div key={iconKey} className='marker-icon'>
+                                        <IconButton
+                                            onPointerDown={() => {}}
+                                            icon={MarkerIcons.getFontAwesomeIcon(iconKey, 'white')}
+                                        />
+                                    </div>
+                                )
+                            }
+                            return null;
+                        })}
+                    </div>
+                    <div style={{width: '100%', height:'3px', color: 'white'}}></div>
+                </div>
                 }
                 {buttons}
             </div>
diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss
index 946c6f495..bc2f90fbd 100644
--- a/src/client/views/nodes/MapBox/MapBox.scss
+++ b/src/client/views/nodes/MapBox/MapBox.scss
@@ -12,8 +12,10 @@
         font-size: 17;
     }
     .mapBox-searchbar {
-        // display: flex;
-        // flex-direction: row;
+        display: flex;
+        flex-direction: row;
+        gap: 5px;
+        align-items: center;
         width: calc(100% - 40px);
 
         // .editableText-container {
@@ -25,6 +27,38 @@
         // }
     }
 
+    .mapbox-settings-panel{
+        z-index: 900;
+        padding: 10px 20px;
+        display: flex;
+        background-color: rgb(187, 187, 187);
+        font-size: 1.3em;
+        flex-direction: column;
+        align-items: flex-start;
+        justify-content: center;
+        gap: 7px;
+        position: absolute;
+        border-top-left-radius: 5px;
+        border-bottom-left-radius: 5px;
+
+        .mapbox-style-select{
+            display: flex;
+            flex-direction: column;
+            align-items: flex-start;
+            justify-content: center;
+            gap: 4px;
+        }
+
+        .mapbox-terrain-selection{
+            display: flex;
+            flex-direction: row;
+            align-items: center;
+            justify-content: flex-start;
+            gap: 4px;
+        }
+
+    }
+
     .mapbox-geocoding-search-results {
         z-index: 900;
         display: flex;
@@ -35,6 +69,8 @@
         background-color: rgb(187, 187, 187);
         font-size: 1.4em;
         padding: 10px;
+        border-top-right-radius: 5px;
+        border-bottom-right-radius: 5px;
 
         .search-result-container {
             width: 100%;
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 2b563faf2..ac926e1fb 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -9,7 +9,7 @@ import * as React from 'react';
 import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
 import { DocCss, Highlight } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
-import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
+import { Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { DocumentType } from '../../../documents/DocumentTypes';
@@ -52,11 +52,13 @@ import './MapBox.scss';
 import { NumberLiteralType } from 'typescript';
 // import { GeocoderControl } from './GeocoderControl';
 import mapboxgl, { LngLat, MapLayerMouseEvent } from 'mapbox-gl';
-import { Feature, FeatureCollection } from 'geojson';
+import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, MultiLineString, Position } from 'geojson';
 import { MarkerEvent } from 'react-map-gl/dist/esm/types';
 import { MapboxApiUtility, TransportationType} from './MapboxApiUtility';
-import { Autocomplete, TextField } from '@mui/material';
+import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material';
 import { List } from '../../../../fields/List';
+import { listSpec } from '../../../../fields/Schema';
+import { IconLookup, faGear } from '@fortawesome/free-solid-svg-icons';
 
 // amongus
 /**
@@ -140,15 +142,17 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @computed get allRoutes() {
         return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE);
     }
-    @computed get allRoutesGeoJson() {
-        const features = this.allRoutes.map(route => {
+    @computed get allRoutesGeoJson(): FeatureCollection {
+        const features: Feature<Geometry, GeoJsonProperties>[] = this.allRoutes.map(route => {
+            console.log("Route coords: ", route.coordinates);
+            const geometry: LineString = {
+                type: 'LineString',
+                coordinates: JSON.parse(StrCast(route.coordinates))
+            }
             return {
               type: 'Feature',
               properties: {},
-              geometry: {
-                type: 'LineString',
-                coordinates: route.coordinates
-              }
+              geometry: geometry
             };
           });
         
@@ -771,7 +775,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             NumCast(longitude),
             false,
             [],
-            {title: location ?? `lat=${latitude},lng=${longitude}`, map: location, description: "", wikiData: wikiData},
+            {
+                title: location ?? `lat=${latitude},lng=${longitude}`, 
+                map: location, 
+                description: "", 
+                wikiData: wikiData,
+                markerType: 'MAPBOX_MARKER',
+                markerColor: '#3FB1CE'
+            },
             // { title: map ?? `lat=${latitude},lng=${longitude}`, map: map },
             // ,'pushpinIDamongus'+ this.incrementer++
         );
@@ -783,11 +794,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }, 'createpin');
 
     @action
-    createMapRoute = undoable((coordinates: List<any>, origin: string, destination: string) => {
+    createMapRoute = undoable((coordinates: Position[], origin: string, destination: string) => {
+        console.log(coordinates);
         const mapRoute = Docs.Create.MapRouteDocument(
             false,
             [],
-            {title: `${origin} -> ${destination}`, routeCoordinates: coordinates},
+            {title: `${origin} -> ${destination}`, routeCoordinates: JSON.stringify(coordinates)},
         );
         this.addDocument(mapRoute, this.annotationKey);
         return mapRoute;
@@ -800,29 +812,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
 
 
-    @observable
-    mapboxMapViewState: ViewState = {
-        zoom: 9,
-        longitude: -71.41,
-        latitude: 41.82,
-        pitch: 0,
-        bearing: 0,
-        padding: {
-            top: 0,
-            bottom: 0,
-            left: 0,
-            right: 0
-        }
-    }
+
 
     @observable
     featuresFromGeocodeResults: any[] = [];
 
-    @action
-    onMapMove = (e: ViewStateChangeEvent) => {
-        this.mapboxMapViewState = e.viewState;
-    }
-
 
     @action
     addMarkerForFeature = (feature: any) => {
@@ -838,6 +832,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 location, 
                 wikiData
             )
+
+            if (this._mapRef.current){
+                this._mapRef.current.flyTo({
+                    center: feature.center
+                });
+            }
             this.featuresFromGeocodeResults = [];
 
         } else {
@@ -855,6 +855,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const features = await MapboxApiUtility.forwardGeocodeForFeatures(searchText);
         if (features){
             runInAction(() => {
+                this.settingsOpen= false;
                 this.featuresFromGeocodeResults = features;
             })
         }
@@ -988,6 +989,75 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
 
 
+    @observable
+    mapboxMapViewState: ViewState = {
+        zoom: 9,
+        longitude: -71.45, 
+        latitude: 41.82,
+        pitch: 0,
+        bearing: 0,
+        padding: {
+            top: 0,
+            bottom: 0,
+            left: 0,
+            right: 0
+        }
+    }
+
+    @observable 
+    settingsOpen: boolean = false;
+
+    @observable 
+    mapStyle: string = 'mapbox://styles/mapbox/streets-v12'
+
+    @observable
+    showTerrain: boolean = false;
+
+    @action
+    toggleSettings = () => {
+        this.featuresFromGeocodeResults = [];
+        this.settingsOpen = !this.settingsOpen;
+    }
+
+    @action
+    changeMapStyle = (e: React.ChangeEvent<HTMLSelectElement>) => {
+        this.mapStyle = `mapbox://styles/mapbox/${e.target.value}`
+    }
+
+    @action
+    onMapMove = (e: ViewStateChangeEvent) => {
+        this.mapboxMapViewState = e.viewState;
+    }
+
+    @action
+    toggleShowTerrain = () => {
+        this.showTerrain = !this.showTerrain;
+    }
+
+    @action
+    onBearingChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        const newVal = parseInt(e.target.value)
+        if (!isNaN(newVal) && newVal >= 0){
+            this.mapboxMapViewState = {
+                ...this.mapboxMapViewState,
+                bearing: parseInt(e.target.value)
+            }
+        }
+    }
+
+    @action
+    onPitchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        const newVal = parseInt(e.target.value);
+        if (!isNaN(newVal) && newVal >= 0){
+            this.mapboxMapViewState = {
+                ...this.mapboxMapViewState,
+                pitch: parseInt(e.target.value)
+            }
+        }
+    }
+
+
+
 
     static _firstRender = true;
     static _rerenderDelay = 500;
@@ -1029,51 +1099,59 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             placeholder='Enter a location'
                             onChange={(e) => this.handleSearchChange(e.target.value)}
                         />
-                        {/* <Autocomplete
-                            fullWidth
-                            id="map-location-searcher"
-                            freeSolo
-                            onInputChange={(e, searchText) => this.handleSearchChange(searchText)}
-                            onChange={(e, selectedOption) => {
-                                this.handleSearchChange(""); // clear input 
-                                this.addMarkerForFeature(selectedOption);
-                            }}
-                            options={this.featuresFromGeocodeResults
-                                .filter(feature => feature.place_name)
-                                .map(feature => feature)}
-                            getOptionLabel={(feature) => feature.place_name}
-                            renderInput={(params) => (
-                                <TextField
-                                    {...params}
-                                    placeholder='Enter a location'
-                                />
-                            )}
-                            /> */}
-                        {/* <EditableText
-                            // editing
-                            setVal={(newText: string | number) => typeof newText === 'string' && this.handleSearchChange(newText)}
-                            // onEnter={e => this.bingSearch()}
-                            onEnter={e => {}}
-                            height={32}
-                            // placeholder={this.bingSearchBarContents || 'Enter a location'}
-                            placeholder='Enter a location'
-                            textAlign="center" 
-                        /> */}
-                        {/* <IconButton
-                            icon={
-                                <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="magnifying-glass" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" color="#DFDFDF">
-                                    <path
-                                        fill="currentColor"
-                                        d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"></path>
-                                </svg>
-                            }
-                            onClick={this.bingSearch}
+                        <IconButton
+                            icon={<FontAwesomeIcon icon={faGear as IconLookup} size='1x'/>}
                             type={Type.TERT}
+                            onClick={(e) => this.toggleSettings()}
+
                         />
-                        <div style={{ width: 30, height: 30 }} ref={this._dragRef} onPointerDown={this.dragToggle}>
-                            <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size={'lg'} icon={'bullseye'} />} />
-                        </div> */}
                     </div> 
+                    {this.settingsOpen &&
+                        <div className='mapbox-settings-panel' style={{right: `${0+ this.sidebarWidth()}px`}}>
+                            <div className='mapbox-style-select'>
+                                <div>
+                                    Map Style:
+                                </div>
+                                <div>
+                                    <select onChange={this.changeMapStyle}>
+                                        <option value='streets-v12'>Streets</option>
+                                        <option value='outdoors-v12'>Outdoors</option>
+                                        <option value='light-v11'>Light</option>
+                                        <option value='dark-v11'>Dark</option>
+                                        <option value='satellite-v9'>Satellite</option>
+                                        <option value='satellite-streets-v12'>Satellite Streets</option>
+                                        <option value='navigation-day-v1'>Navigation Day</option>
+                                        <option value='navigation-night-v1'>Navigation Night</option>
+                                    </select>
+                                </div>
+                            </div>
+                            <div className='mapbox-bearing-selection'>
+                                <div>Bearing: </div>
+                                <input 
+                                    value={this.mapboxMapViewState.bearing}
+                                    min={0}
+                                    type='number'
+                                    onChange={this.onBearingChange}/>
+                            </div>
+                            <div className='mapbox-pitch-selection'>
+                                <div>Pitch: </div>
+                                    <input 
+                                        value={this.mapboxMapViewState.pitch}
+                                        min={0}
+                                        type='number'
+                                        onChange={this.onPitchChange}/>
+                                </div>
+                            <div className='mapbox-terrain-selection'>
+                                <div>Show terrain: </div>
+                                <input 
+                                    type='checkbox'
+                                    checked={this.showTerrain}
+                                    onChange={this.toggleShowTerrain}
+                                    />
+                            </div>
+                        </div>
+                    }
+                
                     <div className='mapbox-geocoding-search-results'>
                         {this.featuresFromGeocodeResults.length > 0 && (
                             <React.Fragment>
@@ -1107,15 +1185,24 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                               }}
                             mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
                             id="mapbox-map"
-                            mapStyle="mapbox://styles/mapbox/streets-v9"
+                            mapStyle={this.mapStyle}
                             style={{height: '100%', width: '100%'}}
                             {...this.mapboxMapViewState}
                             onMove={this.onMapMove}
                             onDblClick={this.handleMapClick}
+                            terrain={this.showTerrain ? { source: 'mapbox-dem', exaggeration: 2.0 } : undefined}
                             
                             
                         >
+                            <Source
+                                id="mapbox-dem"
+                                type="raster-dem"
+                                url="mapbox://mapbox.mapbox-terrain-dem-v1"
+                                tileSize={512}
+                                maxzoom={14}
+                            />
                             <Source id='temporary-route' type='geojson' data={this.temporaryRouteSource}/>
+                            <Source id='map-routes' type='geojson' data={this.allRoutesGeoJson}/>
                             <Layer
                                 id='temporary-route-layer'
                                 type='line'
@@ -1123,6 +1210,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                 layout={{"line-join": "round", "line-cap": "round"}}
                                 paint={{"line-color": "#36454F", "line-width": 4, "line-dasharray": [1,1]}}
                                 />
+                            <Layer
+                                id='map-routes-layer'
+                                type='line'
+                                source='map-routes'
+                                layout={{"line-join": "round", "line-cap": "round"}}
+                                paint={{"line-color": "#FF0000", "line-width": 4}}
+                                />
+
                             <>
                                 {this.allPushpins
                                     // .filter(anno => !anno.layout_unrendered)
@@ -1219,3 +1314,48 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         );
     }
 }
+
+{/* <Autocomplete
+                            fullWidth
+                            id="map-location-searcher"
+                            freeSolo
+                            onInputChange={(e, searchText) => this.handleSearchChange(searchText)}
+                            onChange={(e, selectedOption) => {
+                                this.handleSearchChange(""); // clear input 
+                                this.addMarkerForFeature(selectedOption);
+                            }}
+                            options={this.featuresFromGeocodeResults
+                                .filter(feature => feature.place_name)
+                                .map(feature => feature)}
+                            getOptionLabel={(feature) => feature.place_name}
+                            renderInput={(params) => (
+                                <TextField
+                                    {...params}
+                                    placeholder='Enter a location'
+                                />
+                            )}
+                            /> */}
+                        {/* <EditableText
+                            // editing
+                            setVal={(newText: string | number) => typeof newText === 'string' && this.handleSearchChange(newText)}
+                            // onEnter={e => this.bingSearch()}
+                            onEnter={e => {}}
+                            height={32}
+                            // placeholder={this.bingSearchBarContents || 'Enter a location'}
+                            placeholder='Enter a location'
+                            textAlign="center" 
+                        /> */}
+                        {/* <IconButton
+                            icon={
+                                <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="magnifying-glass" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" color="#DFDFDF">
+                                    <path
+                                        fill="currentColor"
+                                        d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"></path>
+                                </svg>
+                            }
+                            onClick={this.bingSearch}
+                            type={Type.TERT}
+                        />
+                        <div style={{ width: 30, height: 30 }} ref={this._dragRef} onPointerDown={this.dragToggle}>
+                            <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size={'lg'} icon={'bullseye'} />} />
+                        </div> */}
\ No newline at end of file
diff --git a/src/client/views/nodes/MapBox/MapboxApiUtility.ts b/src/client/views/nodes/MapBox/MapboxApiUtility.ts
index 80962f435..011b6f72a 100644
--- a/src/client/views/nodes/MapBox/MapboxApiUtility.ts
+++ b/src/client/views/nodes/MapBox/MapboxApiUtility.ts
@@ -68,6 +68,8 @@ export class MapboxApiUtility {
                 const geometry = routeData.geometry;
                 const coordinates = geometry.coordinates;
 
+                console.log(coordinates);
+
                 routeInfoMap[transportationTypeKey] = {
                     duration: this.secondsToMinutesHours(routeData.duration),
                     distance: this.metersToMiles(routeData.distance),
diff --git a/src/client/views/nodes/MapBox/MarkerIcons.tsx b/src/client/views/nodes/MapBox/MarkerIcons.tsx
new file mode 100644
index 000000000..cf50109ac
--- /dev/null
+++ b/src/client/views/nodes/MapBox/MarkerIcons.tsx
@@ -0,0 +1,87 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { faShopify } from '@fortawesome/free-brands-svg-icons';
+import { IconLookup, faBasketball, faBicycle, faBowlFood, faBus, faCameraRetro, faCar, faCartShopping, faFilm, faFootball, faFutbol, faHockeyPuck, faHospital, faHotel, faHouse, faLandmark, faMasksTheater, faMugSaucer, faPersonHiking, faPlane, faSchool, faShirt, faShop, faSquareParking, faStar, faTrainSubway, faTree, faUtensils, faVolleyball } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import React = require('react');
+
+export type MapboxColor = 'yellow' | 'red' | 'orange' | 'purple' | 'pink' | 'blue' | 'green';
+type ColorProperties = {
+    fill: string,
+    stroke: string
+}
+type ColorsMap = {
+    [key in MapboxColor]: ColorProperties;
+}
+
+export class MarkerIcons {
+
+    static getMapboxIcon = (color: string) => {
+        return (
+            <svg xmlns="http://www.w3.org/2000/svg" id="marker" data-name="marker" width="20" height="48" viewBox="0 0 20 35">
+            <g id="mapbox-marker-icon">
+                <g id="icon">
+                <ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" />
+                <g id="mask" opacity="0.3">
+                    <g id="group">
+                    <path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fillRule="evenodd"/>
+                    </g>
+                </g>
+                <path id="color" fill={color} strokeWidth="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
+                <path id="circle" fill="#fff" stroke='white' strokeWidth="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
+                </g>
+            </g>
+            <rect width="20" height="48" fill="none"/>
+        </svg>
+        )
+    }
+
+    static getFontAwesomeIcon(key: string, color?: string): JSX.Element | undefined {
+        const icon: IconProp = MarkerIcons.FAMarkerIconsMap[key];
+
+        if (icon) {
+            const iconProps: any = { icon };
+        
+            if (color) {
+              iconProps.color = color;
+            }
+        
+            return (<FontAwesomeIcon {...iconProps} size='1x' />);
+          }
+        
+        return undefined;
+    }
+
+    static FAMarkerIconsMap: {[key: string]: IconProp} = {
+        'RESTAURANT_ICON': faUtensils,
+        'HOTEL_ICON': faHotel,
+        'HOUSE_ICON': faHouse,
+        'AIRPLANE_ICON': faPlane,
+        'CAR_ICON': faCar,
+        'BUS_ICON': faBus,
+        'TRAIN_ICON': faTrainSubway,
+        'BICYCLE_ICON': faBicycle,
+        'PARKING_ICON': faSquareParking,
+        'PHOTO_ICON': faCameraRetro,
+        'CAFE_ICON': faMugSaucer,
+        'STAR_ICON': faStar,
+        'SHOPPING_CART_ICON': faCartShopping,
+        'SHOPIFY_ICON': faShopify,
+        'SHOP_ICON': faShop,
+        'SHIRT_ICON': faShirt,
+        'FOOD_ICON': faBowlFood,
+        'LANDMARK_ICON': faLandmark,
+        'HOSPITAL_ICON': faHospital,
+        'NATURE_ICON': faTree,
+        'HIKING_ICON': faPersonHiking,
+        'SOCCER_ICON': faFutbol,
+        'VOLLEYBALL_ICON': faVolleyball,
+        'BASKETBALL_ICON': faBasketball,
+        'HOCKEY_ICON': faHockeyPuck,
+        'FOOTBALL_ICON': faFootball,
+        'SCHOOL_ICON': faSchool,
+        'THEATER_ICON': faMasksTheater,
+        'FILM_ICON': faFilm
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/MapBox/icon_images/mapbox-marker-icon-20px-blue.png b/src/client/views/nodes/MapBox/icon_images/mapbox-marker-icon-20px-blue.png
new file mode 100644
index 000000000..8b686e2aa
Binary files /dev/null and b/src/client/views/nodes/MapBox/icon_images/mapbox-marker-icon-20px-blue.png differ
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 084cb872a..1678d9012 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1598,4 +1598,4 @@ ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: a
 });
 ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, range: number[]) {
     Doc.setDocRangeFilter(container, key, range);
-});
+});
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From e9b59a2790006af60206af7f676ad4a0ccceee13 Mon Sep 17 00:00:00 2001
From: zaultavangar <zaul_tavangar@brown.edu>
Date: Thu, 14 Dec 2023 12:43:40 -0500
Subject: adding animation functionality

---
 package-lock.json                                  | 1433 +++++++++++++++++++-
 package.json                                       |    9 +-
 .../views/nodes/MapBox/AnimationSpeedIcons.tsx     |   35 +
 src/client/views/nodes/MapBox/AnimationUtility.ts  |  429 ++++++
 src/client/views/nodes/MapBox/MapAnchorMenu.tsx    |  118 +-
 src/client/views/nodes/MapBox/MapBox.scss          |   50 +
 src/client/views/nodes/MapBox/MapBox.tsx           |  776 +++++++++--
 src/client/views/nodes/MapBox/MarkerIcons.tsx      |   69 +-
 8 files changed, 2714 insertions(+), 205 deletions(-)
 create mode 100644 src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx
 create mode 100644 src/client/views/nodes/MapBox/AnimationUtility.ts

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

diff --git a/package-lock.json b/package-lock.json
index 58b3d9eae..311eb9281 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5329,6 +5329,1294 @@
                 }
             }
         },
+        "@turf/along": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/along/-/along-6.5.0.tgz",
+            "integrity": "sha512-LLyWQ0AARqJCmMcIEAXF4GEu8usmd4Kbz3qk1Oy5HoRNpZX47+i5exQtmIWKdqJ1MMhW26fCTXgpsEs5zgJ5gw==",
+            "requires": {
+                "@turf/bearing": "^6.5.0",
+                "@turf/destination": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/angle": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/angle/-/angle-6.5.0.tgz",
+            "integrity": "sha512-4pXMbWhFofJJAOvTMCns6N4C8CMd5Ih4O2jSAG9b3dDHakj3O4yN1+Zbm+NUei+eVEZ9gFeVp9svE3aMDenIkw==",
+            "requires": {
+                "@turf/bearing": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/rhumb-bearing": "^6.5.0"
+            }
+        },
+        "@turf/area": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/area/-/area-6.5.0.tgz",
+            "integrity": "sha512-xCZdiuojokLbQ+29qR6qoMD89hv+JAgWjLrwSEWL+3JV8IXKeNFl6XkEJz9HGkVpnXvQKJoRz4/liT+8ZZ5Jyg==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/bbox": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-6.5.0.tgz",
+            "integrity": "sha512-RBbLaao5hXTYyyg577iuMtDB8ehxMlUqHEJiMs8jT1GHkFhr6sYre3lmLsPeYEi/ZKj5TP5tt7fkzNdJ4GIVyw==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/bbox-clip": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/bbox-clip/-/bbox-clip-6.5.0.tgz",
+            "integrity": "sha512-F6PaIRF8WMp8EmgU/Ke5B1Y6/pia14UAYB5TiBC668w5rVVjy5L8rTm/m2lEkkDMHlzoP9vNY4pxpNthE7rLcQ==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/bbox-polygon": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/bbox-polygon/-/bbox-polygon-6.5.0.tgz",
+            "integrity": "sha512-+/r0NyL1lOG3zKZmmf6L8ommU07HliP4dgYToMoTxqzsWzyLjaj/OzgQ8rBmv703WJX+aS6yCmLuIhYqyufyuw==",
+            "requires": {
+                "@turf/helpers": "^6.5.0"
+            }
+        },
+        "@turf/bearing": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/bearing/-/bearing-6.5.0.tgz",
+            "integrity": "sha512-dxINYhIEMzgDOztyMZc20I7ssYVNEpSv04VbMo5YPQsqa80KO3TFvbuCahMsCAW5z8Tncc8dwBlEFrmRjJG33A==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/bezier-spline": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/bezier-spline/-/bezier-spline-6.5.0.tgz",
+            "integrity": "sha512-vokPaurTd4PF96rRgGVm6zYYC5r1u98ZsG+wZEv9y3kJTuJRX/O3xIY2QnTGTdbVmAJN1ouOsD0RoZYaVoXORQ==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/boolean-clockwise": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-clockwise/-/boolean-clockwise-6.5.0.tgz",
+            "integrity": "sha512-45+C7LC5RMbRWrxh3Z0Eihsc8db1VGBO5d9BLTOAwU4jR6SgsunTfRWR16X7JUwIDYlCVEmnjcXJNi/kIU3VIw==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/boolean-contains": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-contains/-/boolean-contains-6.5.0.tgz",
+            "integrity": "sha512-4m8cJpbw+YQcKVGi8y0cHhBUnYT+QRfx6wzM4GI1IdtYH3p4oh/DOBJKrepQyiDzFDaNIjxuWXBh0ai1zVwOQQ==",
+            "requires": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/boolean-point-on-line": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/boolean-crosses": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-crosses/-/boolean-crosses-6.5.0.tgz",
+            "integrity": "sha512-gvshbTPhAHporTlQwBJqyfW+2yV8q/mOTxG6PzRVl6ARsqNoqYQWkd4MLug7OmAqVyBzLK3201uAeBjxbGw0Ng==",
+            "requires": {
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/line-intersect": "^6.5.0",
+                "@turf/polygon-to-line": "^6.5.0"
+            }
+        },
+        "@turf/boolean-disjoint": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-disjoint/-/boolean-disjoint-6.5.0.tgz",
+            "integrity": "sha512-rZ2ozlrRLIAGo2bjQ/ZUu4oZ/+ZjGvLkN5CKXSKBcu6xFO6k2bgqeM8a1836tAW+Pqp/ZFsTA5fZHsJZvP2D5g==",
+            "requires": {
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/line-intersect": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/polygon-to-line": "^6.5.0"
+            }
+        },
+        "@turf/boolean-equal": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-equal/-/boolean-equal-6.5.0.tgz",
+            "integrity": "sha512-cY0M3yoLC26mhAnjv1gyYNQjn7wxIXmL2hBmI/qs8g5uKuC2hRWi13ydufE3k4x0aNRjFGlg41fjoYLwaVF+9Q==",
+            "requires": {
+                "@turf/clean-coords": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "geojson-equality": "0.1.6"
+            }
+        },
+        "@turf/boolean-intersects": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-intersects/-/boolean-intersects-6.5.0.tgz",
+            "integrity": "sha512-nIxkizjRdjKCYFQMnml6cjPsDOBCThrt+nkqtSEcxkKMhAQj5OO7o2CecioNTaX8EayqwMGVKcsz27oP4mKPTw==",
+            "requires": {
+                "@turf/boolean-disjoint": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/boolean-overlap": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-overlap/-/boolean-overlap-6.5.0.tgz",
+            "integrity": "sha512-8btMIdnbXVWUa1M7D4shyaSGxLRw6NjMcqKBcsTXcZdnaixl22k7ar7BvIzkaRYN3SFECk9VGXfLncNS3ckQUw==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/line-intersect": "^6.5.0",
+                "@turf/line-overlap": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "geojson-equality": "0.1.6"
+            }
+        },
+        "@turf/boolean-parallel": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-parallel/-/boolean-parallel-6.5.0.tgz",
+            "integrity": "sha512-aSHJsr1nq9e5TthZGZ9CZYeXklJyRgR5kCLm5X4urz7+MotMOp/LsGOsvKvK9NeUl9+8OUmfMn8EFTT8LkcvIQ==",
+            "requires": {
+                "@turf/clean-coords": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/line-segment": "^6.5.0",
+                "@turf/rhumb-bearing": "^6.5.0"
+            }
+        },
+        "@turf/boolean-point-in-polygon": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-6.5.0.tgz",
+            "integrity": "sha512-DtSuVFB26SI+hj0SjrvXowGTUCHlgevPAIsukssW6BG5MlNSBQAo70wpICBNJL6RjukXg8d2eXaAWuD/CqL00A==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/boolean-point-on-line": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-point-on-line/-/boolean-point-on-line-6.5.0.tgz",
+            "integrity": "sha512-A1BbuQ0LceLHvq7F/P7w3QvfpmZqbmViIUPHdNLvZimFNLo4e6IQunmzbe+8aSStH9QRZm3VOflyvNeXvvpZEQ==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/boolean-within": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-within/-/boolean-within-6.5.0.tgz",
+            "integrity": "sha512-YQB3oU18Inx35C/LU930D36RAVe7LDXk1kWsQ8mLmuqYn9YdPsDQTMTkLJMhoQ8EbN7QTdy333xRQ4MYgToteQ==",
+            "requires": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/boolean-point-on-line": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/buffer": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/buffer/-/buffer-6.5.0.tgz",
+            "integrity": "sha512-qeX4N6+PPWbKqp1AVkBVWFerGjMYMUyencwfnkCesoznU6qvfugFHNAngNqIBVnJjZ5n8IFyOf+akcxnrt9sNg==",
+            "requires": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/center": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/projection": "^6.5.0",
+                "d3-geo": "1.7.1",
+                "turf-jsts": "*"
+            },
+            "dependencies": {
+                "d3-array": {
+                    "version": "1.2.4",
+                    "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
+                    "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
+                },
+                "d3-geo": {
+                    "version": "1.7.1",
+                    "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.7.1.tgz",
+                    "integrity": "sha512-O4AempWAr+P5qbk2bC2FuN/sDW4z+dN2wDf9QV3bxQt4M5HfOEeXLgJ/UKQW0+o1Dj8BE+L5kiDbdWUMjsmQpw==",
+                    "requires": {
+                        "d3-array": "1"
+                    }
+                }
+            }
+        },
+        "@turf/center": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/center/-/center-6.5.0.tgz",
+            "integrity": "sha512-T8KtMTfSATWcAX088rEDKjyvQCBkUsLnK/Txb6/8WUXIeOZyHu42G7MkdkHRoHtwieLdduDdmPLFyTdG5/e7ZQ==",
+            "requires": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/helpers": "^6.5.0"
+            }
+        },
+        "@turf/center-mean": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/center-mean/-/center-mean-6.5.0.tgz",
+            "integrity": "sha512-AAX6f4bVn12pTVrMUiB9KrnV94BgeBKpyg3YpfnEbBpkN/znfVhL8dG8IxMAxAoSZ61Zt9WLY34HfENveuOZ7Q==",
+            "requires": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/center-median": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/center-median/-/center-median-6.5.0.tgz",
+            "integrity": "sha512-dT8Ndu5CiZkPrj15PBvslpuf01ky41DEYEPxS01LOxp5HOUHXp1oJxsPxvc+i/wK4BwccPNzU1vzJ0S4emd1KQ==",
+            "requires": {
+                "@turf/center-mean": "^6.5.0",
+                "@turf/centroid": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/center-of-mass": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/center-of-mass/-/center-of-mass-6.5.0.tgz",
+            "integrity": "sha512-EWrriU6LraOfPN7m1jZi+1NLTKNkuIsGLZc2+Y8zbGruvUW+QV7K0nhf7iZWutlxHXTBqEXHbKue/o79IumAsQ==",
+            "requires": {
+                "@turf/centroid": "^6.5.0",
+                "@turf/convex": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/centroid": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/centroid/-/centroid-6.5.0.tgz",
+            "integrity": "sha512-MwE1oq5E3isewPprEClbfU5pXljIK/GUOMbn22UM3IFPDJX0KeoyLNwghszkdmFp/qMGL/M13MMWvU+GNLXP/A==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/circle": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/circle/-/circle-6.5.0.tgz",
+            "integrity": "sha512-oU1+Kq9DgRnoSbWFHKnnUdTmtcRUMmHoV9DjTXu9vOLNV5OWtAAh1VZ+mzsioGGzoDNT/V5igbFOkMfBQc0B6A==",
+            "requires": {
+                "@turf/destination": "^6.5.0",
+                "@turf/helpers": "^6.5.0"
+            }
+        },
+        "@turf/clean-coords": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/clean-coords/-/clean-coords-6.5.0.tgz",
+            "integrity": "sha512-EMX7gyZz0WTH/ET7xV8MyrExywfm9qUi0/MY89yNffzGIEHuFfqwhcCqZ8O00rZIPZHUTxpmsxQSTfzJJA1CPw==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/clone": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-6.5.0.tgz",
+            "integrity": "sha512-mzVtTFj/QycXOn6ig+annKrM6ZlimreKYz6f/GSERytOpgzodbQyOgkfwru100O1KQhhjSudKK4DsQ0oyi9cTw==",
+            "requires": {
+                "@turf/helpers": "^6.5.0"
+            }
+        },
+        "@turf/clusters": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/clusters/-/clusters-6.5.0.tgz",
+            "integrity": "sha512-Y6gfnTJzQ1hdLfCsyd5zApNbfLIxYEpmDibHUqR5z03Lpe02pa78JtgrgUNt1seeO/aJ4TG1NLN8V5gOrHk04g==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/clusters-dbscan": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/clusters-dbscan/-/clusters-dbscan-6.5.0.tgz",
+            "integrity": "sha512-SxZEE4kADU9DqLRiT53QZBBhu8EP9skviSyl+FGj08Y01xfICM/RR9ACUdM0aEQimhpu+ZpRVcUK+2jtiCGrYQ==",
+            "requires": {
+                "@turf/clone": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "density-clustering": "1.3.0"
+            }
+        },
+        "@turf/clusters-kmeans": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/clusters-kmeans/-/clusters-kmeans-6.5.0.tgz",
+            "integrity": "sha512-DwacD5+YO8kwDPKaXwT9DV46tMBVNsbi1IzdajZu1JDSWoN7yc7N9Qt88oi+p30583O0UPVkAK+A10WAQv4mUw==",
+            "requires": {
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "skmeans": "0.9.7"
+            }
+        },
+        "@turf/collect": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/collect/-/collect-6.5.0.tgz",
+            "integrity": "sha512-4dN/T6LNnRg099m97BJeOcTA5fSI8cu87Ydgfibewd2KQwBexO69AnjEFqfPX3Wj+Zvisj1uAVIZbPmSSrZkjg==",
+            "requires": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "rbush": "2.x"
+            },
+            "dependencies": {
+                "quickselect": {
+                    "version": "1.1.1",
+                    "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-1.1.1.tgz",
+                    "integrity": "sha512-qN0Gqdw4c4KGPsBOQafj6yj/PA6c/L63f6CaZ/DCF/xF4Esu3jVmKLUDYxghFx8Kb/O7y9tI7x2RjTSXwdK1iQ=="
+                },
+                "rbush": {
+                    "version": "2.0.2",
+                    "resolved": "https://registry.npmjs.org/rbush/-/rbush-2.0.2.tgz",
+                    "integrity": "sha512-XBOuALcTm+O/H8G90b6pzu6nX6v2zCKiFG4BJho8a+bY6AER6t8uQUZdi5bomQc0AprCWhEGa7ncAbbRap0bRA==",
+                    "requires": {
+                        "quickselect": "^1.0.1"
+                    }
+                }
+            }
+        },
+        "@turf/combine": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/combine/-/combine-6.5.0.tgz",
+            "integrity": "sha512-Q8EIC4OtAcHiJB3C4R+FpB4LANiT90t17uOd851qkM2/o6m39bfN5Mv0PWqMZIHWrrosZqRqoY9dJnzz/rJxYQ==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/concave": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/concave/-/concave-6.5.0.tgz",
+            "integrity": "sha512-I/sUmUC8TC5h/E2vPwxVht+nRt+TnXIPRoztDFvS8/Y0+cBDple9inLSo9nnPXMXidrBlGXZ9vQx/BjZUJgsRQ==",
+            "requires": {
+                "@turf/clone": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/tin": "^6.5.0",
+                "topojson-client": "3.x",
+                "topojson-server": "3.x"
+            }
+        },
+        "@turf/convex": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/convex/-/convex-6.5.0.tgz",
+            "integrity": "sha512-x7ZwC5z7PJB0SBwNh7JCeCNx7Iu+QSrH7fYgK0RhhNop13TqUlvHMirMLRgf2db1DqUetrAO2qHJeIuasquUWg==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "concaveman": "*"
+            }
+        },
+        "@turf/destination": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/destination/-/destination-6.5.0.tgz",
+            "integrity": "sha512-4cnWQlNC8d1tItOz9B4pmJdWpXqS0vEvv65bI/Pj/genJnsL7evI0/Xw42RvEGROS481MPiU80xzvwxEvhQiMQ==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/difference": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/difference/-/difference-6.5.0.tgz",
+            "integrity": "sha512-l8iR5uJqvI+5Fs6leNbhPY5t/a3vipUF/3AeVLpwPQcgmedNXyheYuy07PcMGH5Jdpi5gItOiTqwiU/bUH4b3A==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "polygon-clipping": "^0.15.3"
+            }
+        },
+        "@turf/dissolve": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/dissolve/-/dissolve-6.5.0.tgz",
+            "integrity": "sha512-WBVbpm9zLTp0Bl9CE35NomTaOL1c4TQCtEoO43YaAhNEWJOOIhZMFJyr8mbvYruKl817KinT3x7aYjjCMjTAsQ==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "polygon-clipping": "^0.15.3"
+            }
+        },
+        "@turf/distance": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/distance/-/distance-6.5.0.tgz",
+            "integrity": "sha512-xzykSLfoURec5qvQJcfifw/1mJa+5UwByZZ5TZ8iaqjGYN0vomhV9aiSLeYdUGtYRESZ+DYC/OzY+4RclZYgMg==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/distance-weight": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/distance-weight/-/distance-weight-6.5.0.tgz",
+            "integrity": "sha512-a8qBKkgVNvPKBfZfEJZnC3DV7dfIsC3UIdpRci/iap/wZLH41EmS90nM+BokAJflUHYy8PqE44wySGWHN1FXrQ==",
+            "requires": {
+                "@turf/centroid": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/ellipse": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/ellipse/-/ellipse-6.5.0.tgz",
+            "integrity": "sha512-kuXtwFviw/JqnyJXF1mrR/cb496zDTSbGKtSiolWMNImYzGGkbsAsFTjwJYgD7+4FixHjp0uQPzo70KDf3AIBw==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/rhumb-destination": "^6.5.0",
+                "@turf/transform-rotate": "^6.5.0"
+            }
+        },
+        "@turf/envelope": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/envelope/-/envelope-6.5.0.tgz",
+            "integrity": "sha512-9Z+FnBWvOGOU4X+fMZxYFs1HjFlkKqsddLuMknRaqcJd6t+NIv5DWvPtDL8ATD2GEExYDiFLwMdckfr1yqJgHA==",
+            "requires": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/bbox-polygon": "^6.5.0",
+                "@turf/helpers": "^6.5.0"
+            }
+        },
+        "@turf/explode": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/explode/-/explode-6.5.0.tgz",
+            "integrity": "sha512-6cSvMrnHm2qAsace6pw9cDmK2buAlw8+tjeJVXMfMyY+w7ZUi1rprWMsY92J7s2Dar63Bv09n56/1V7+tcj52Q==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/flatten": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/flatten/-/flatten-6.5.0.tgz",
+            "integrity": "sha512-IBZVwoNLVNT6U/bcUUllubgElzpMsNoCw8tLqBw6dfYg9ObGmpEjf9BIYLr7a2Yn5ZR4l7YIj2T7kD5uJjZADQ==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/flip": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/flip/-/flip-6.5.0.tgz",
+            "integrity": "sha512-oyikJFNjt2LmIXQqgOGLvt70RgE2lyzPMloYWM7OR5oIFGRiBvqVD2hA6MNw6JewIm30fWZ8DQJw1NHXJTJPbg==",
+            "requires": {
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/great-circle": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/great-circle/-/great-circle-6.5.0.tgz",
+            "integrity": "sha512-7ovyi3HaKOXdFyN7yy1yOMa8IyOvV46RC1QOQTT+RYUN8ke10eyqExwBpL9RFUPvlpoTzoYbM/+lWPogQlFncg==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/helpers": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz",
+            "integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw=="
+        },
+        "@turf/hex-grid": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/hex-grid/-/hex-grid-6.5.0.tgz",
+            "integrity": "sha512-Ln3tc2tgZT8etDOldgc6e741Smg1CsMKAz1/Mlel+MEL5Ynv2mhx3m0q4J9IB1F3a4MNjDeVvm8drAaf9SF33g==",
+            "requires": {
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/intersect": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/interpolate": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/interpolate/-/interpolate-6.5.0.tgz",
+            "integrity": "sha512-LSH5fMeiGyuDZ4WrDJNgh81d2DnNDUVJtuFryJFup8PV8jbs46lQGfI3r1DJ2p1IlEJIz3pmAZYeTfMMoeeohw==",
+            "requires": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/centroid": "^6.5.0",
+                "@turf/clone": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/hex-grid": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/point-grid": "^6.5.0",
+                "@turf/square-grid": "^6.5.0",
+                "@turf/triangle-grid": "^6.5.0"
+            }
+        },
+        "@turf/intersect": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/intersect/-/intersect-6.5.0.tgz",
+            "integrity": "sha512-2legGJeKrfFkzntcd4GouPugoqPUjexPZnOvfez+3SfIMrHvulw8qV8u7pfVyn2Yqs53yoVCEjS5sEpvQ5YRQg==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "polygon-clipping": "^0.15.3"
+            }
+        },
+        "@turf/invariant": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-6.5.0.tgz",
+            "integrity": "sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==",
+            "requires": {
+                "@turf/helpers": "^6.5.0"
+            }
+        },
+        "@turf/isobands": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/isobands/-/isobands-6.5.0.tgz",
+            "integrity": "sha512-4h6sjBPhRwMVuFaVBv70YB7eGz+iw0bhPRnp+8JBdX1UPJSXhoi/ZF2rACemRUr0HkdVB/a1r9gC32vn5IAEkw==",
+            "requires": {
+                "@turf/area": "^6.5.0",
+                "@turf/bbox": "^6.5.0",
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/explode": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "object-assign": "*"
+            }
+        },
+        "@turf/isolines": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/isolines/-/isolines-6.5.0.tgz",
+            "integrity": "sha512-6ElhiLCopxWlv4tPoxiCzASWt/jMRvmp6mRYrpzOm3EUl75OhHKa/Pu6Y9nWtCMmVC/RcWtiiweUocbPLZLm0A==",
+            "requires": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "object-assign": "*"
+            }
+        },
+        "@turf/kinks": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/kinks/-/kinks-6.5.0.tgz",
+            "integrity": "sha512-ViCngdPt1eEL7hYUHR2eHR662GvCgTc35ZJFaNR6kRtr6D8plLaDju0FILeFFWSc+o8e3fwxZEJKmFj9IzPiIQ==",
+            "requires": {
+                "@turf/helpers": "^6.5.0"
+            }
+        },
+        "@turf/length": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/length/-/length-6.5.0.tgz",
+            "integrity": "sha512-5pL5/pnw52fck3oRsHDcSGrj9HibvtlrZ0QNy2OcW8qBFDNgZ4jtl6U7eATVoyWPKBHszW3dWETW+iLV7UARig==",
+            "requires": {
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/line-arc": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-arc/-/line-arc-6.5.0.tgz",
+            "integrity": "sha512-I6c+V6mIyEwbtg9P9zSFF89T7QPe1DPTG3MJJ6Cm1MrAY0MdejwQKOpsvNl8LDU2ekHOlz2kHpPVR7VJsoMllA==",
+            "requires": {
+                "@turf/circle": "^6.5.0",
+                "@turf/destination": "^6.5.0",
+                "@turf/helpers": "^6.5.0"
+            }
+        },
+        "@turf/line-chunk": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-chunk/-/line-chunk-6.5.0.tgz",
+            "integrity": "sha512-i1FGE6YJaaYa+IJesTfyRRQZP31QouS+wh/pa6O3CC0q4T7LtHigyBSYjrbjSLfn2EVPYGlPCMFEqNWCOkC6zg==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/length": "^6.5.0",
+                "@turf/line-slice-along": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/line-intersect": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-intersect/-/line-intersect-6.5.0.tgz",
+            "integrity": "sha512-CS6R1tZvVQD390G9Ea4pmpM6mJGPWoL82jD46y0q1KSor9s6HupMIo1kY4Ny+AEYQl9jd21V3Scz20eldpbTVA==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/line-segment": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "geojson-rbush": "3.x"
+            }
+        },
+        "@turf/line-offset": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-offset/-/line-offset-6.5.0.tgz",
+            "integrity": "sha512-CEXZbKgyz8r72qRvPchK0dxqsq8IQBdH275FE6o4MrBkzMcoZsfSjghtXzKaz9vvro+HfIXal0sTk2mqV1lQTw==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/line-overlap": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-overlap/-/line-overlap-6.5.0.tgz",
+            "integrity": "sha512-xHOaWLd0hkaC/1OLcStCpfq55lPHpPNadZySDXYiYjEz5HXr1oKmtMYpn0wGizsLwrOixRdEp+j7bL8dPt4ojQ==",
+            "requires": {
+                "@turf/boolean-point-on-line": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/line-segment": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/nearest-point-on-line": "^6.5.0",
+                "deep-equal": "1.x",
+                "geojson-rbush": "3.x"
+            }
+        },
+        "@turf/line-segment": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-segment/-/line-segment-6.5.0.tgz",
+            "integrity": "sha512-jI625Ho4jSuJESNq66Mmi290ZJ5pPZiQZruPVpmHkUw257Pew0alMmb6YrqYNnLUuiVVONxAAKXUVeeUGtycfw==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/line-slice": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-slice/-/line-slice-6.5.0.tgz",
+            "integrity": "sha512-vDqJxve9tBHhOaVVFXqVjF5qDzGtKWviyjbyi2QnSnxyFAmLlLnBfMX8TLQCAf2GxHibB95RO5FBE6I2KVPRuw==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/nearest-point-on-line": "^6.5.0"
+            }
+        },
+        "@turf/line-slice-along": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-slice-along/-/line-slice-along-6.5.0.tgz",
+            "integrity": "sha512-KHJRU6KpHrAj+BTgTNqby6VCTnDzG6a1sJx/I3hNvqMBLvWVA2IrkR9L9DtsQsVY63IBwVdQDqiwCuZLDQh4Ng==",
+            "requires": {
+                "@turf/bearing": "^6.5.0",
+                "@turf/destination": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0"
+            }
+        },
+        "@turf/line-split": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-split/-/line-split-6.5.0.tgz",
+            "integrity": "sha512-/rwUMVr9OI2ccJjw7/6eTN53URtGThNSD5I0GgxyFXMtxWiloRJ9MTff8jBbtPWrRka/Sh2GkwucVRAEakx9Sw==",
+            "requires": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/line-intersect": "^6.5.0",
+                "@turf/line-segment": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/nearest-point-on-line": "^6.5.0",
+                "@turf/square": "^6.5.0",
+                "@turf/truncate": "^6.5.0",
+                "geojson-rbush": "3.x"
+            }
+        },
+        "@turf/line-to-polygon": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-to-polygon/-/line-to-polygon-6.5.0.tgz",
+            "integrity": "sha512-qYBuRCJJL8Gx27OwCD1TMijM/9XjRgXH/m/TyuND4OXedBpIWlK5VbTIO2gJ8OCfznBBddpjiObLBrkuxTpN4Q==",
+            "requires": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/mask": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/mask/-/mask-6.5.0.tgz",
+            "integrity": "sha512-RQha4aU8LpBrmrkH8CPaaoAfk0Egj5OuXtv6HuCQnHeGNOQt3TQVibTA3Sh4iduq4EPxnZfDjgsOeKtrCA19lg==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "polygon-clipping": "^0.15.3"
+            }
+        },
+        "@turf/meta": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-6.5.0.tgz",
+            "integrity": "sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==",
+            "requires": {
+                "@turf/helpers": "^6.5.0"
+            }
+        },
+        "@turf/midpoint": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/midpoint/-/midpoint-6.5.0.tgz",
+            "integrity": "sha512-MyTzV44IwmVI6ec9fB2OgZ53JGNlgOpaYl9ArKoF49rXpL84F9rNATndbe0+MQIhdkw8IlzA6xVP4lZzfMNVCw==",
+            "requires": {
+                "@turf/bearing": "^6.5.0",
+                "@turf/destination": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0"
+            }
+        },
+        "@turf/moran-index": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/moran-index/-/moran-index-6.5.0.tgz",
+            "integrity": "sha512-ItsnhrU2XYtTtTudrM8so4afBCYWNaB0Mfy28NZwLjB5jWuAsvyV+YW+J88+neK/ougKMTawkmjQqodNJaBeLQ==",
+            "requires": {
+                "@turf/distance-weight": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/nearest-point": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/nearest-point/-/nearest-point-6.5.0.tgz",
+            "integrity": "sha512-fguV09QxilZv/p94s8SMsXILIAMiaXI5PATq9d7YWijLxWUj6Q/r43kxyoi78Zmwwh1Zfqz9w+bCYUAxZ5+euA==",
+            "requires": {
+                "@turf/clone": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/nearest-point-on-line": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/nearest-point-on-line/-/nearest-point-on-line-6.5.0.tgz",
+            "integrity": "sha512-WthrvddddvmymnC+Vf7BrkHGbDOUu6Z3/6bFYUGv1kxw8tiZ6n83/VG6kHz4poHOfS0RaNflzXSkmCi64fLBlg==",
+            "requires": {
+                "@turf/bearing": "^6.5.0",
+                "@turf/destination": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/line-intersect": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/nearest-point-to-line": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/nearest-point-to-line/-/nearest-point-to-line-6.5.0.tgz",
+            "integrity": "sha512-PXV7cN0BVzUZdjj6oeb/ESnzXSfWmEMrsfZSDRgqyZ9ytdiIj/eRsnOXLR13LkTdXVOJYDBuf7xt1mLhM4p6+Q==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/point-to-line-distance": "^6.5.0",
+                "object-assign": "*"
+            }
+        },
+        "@turf/planepoint": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/planepoint/-/planepoint-6.5.0.tgz",
+            "integrity": "sha512-R3AahA6DUvtFbka1kcJHqZ7DMHmPXDEQpbU5WaglNn7NaCQg9HB0XM0ZfqWcd5u92YXV+Gg8QhC8x5XojfcM4Q==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/point-grid": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/point-grid/-/point-grid-6.5.0.tgz",
+            "integrity": "sha512-Iq38lFokNNtQJnOj/RBKmyt6dlof0yhaHEDELaWHuECm1lIZLY3ZbVMwbs+nXkwTAHjKfS/OtMheUBkw+ee49w==",
+            "requires": {
+                "@turf/boolean-within": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/point-on-feature": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/point-on-feature/-/point-on-feature-6.5.0.tgz",
+            "integrity": "sha512-bDpuIlvugJhfcF/0awAQ+QI6Om1Y1FFYE8Y/YdxGRongivix850dTeXCo0mDylFdWFPGDo7Mmh9Vo4VxNwW/TA==",
+            "requires": {
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/center": "^6.5.0",
+                "@turf/explode": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/nearest-point": "^6.5.0"
+            }
+        },
+        "@turf/point-to-line-distance": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/point-to-line-distance/-/point-to-line-distance-6.5.0.tgz",
+            "integrity": "sha512-opHVQ4vjUhNBly1bob6RWy+F+hsZDH9SA0UW36pIRzfpu27qipU18xup0XXEePfY6+wvhF6yL/WgCO2IbrLqEA==",
+            "requires": {
+                "@turf/bearing": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/projection": "^6.5.0",
+                "@turf/rhumb-bearing": "^6.5.0",
+                "@turf/rhumb-distance": "^6.5.0"
+            }
+        },
+        "@turf/points-within-polygon": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/points-within-polygon/-/points-within-polygon-6.5.0.tgz",
+            "integrity": "sha512-YyuheKqjliDsBDt3Ho73QVZk1VXX1+zIA2gwWvuz8bR1HXOkcuwk/1J76HuFMOQI3WK78wyAi+xbkx268PkQzQ==",
+            "requires": {
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/polygon-smooth": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/polygon-smooth/-/polygon-smooth-6.5.0.tgz",
+            "integrity": "sha512-LO/X/5hfh/Rk4EfkDBpLlVwt3i6IXdtQccDT9rMjXEP32tRgy0VMFmdkNaXoGlSSKf/1mGqLl4y4wHd86DqKbg==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/polygon-tangents": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/polygon-tangents/-/polygon-tangents-6.5.0.tgz",
+            "integrity": "sha512-sB4/IUqJMYRQH9jVBwqS/XDitkEfbyqRy+EH/cMRJURTg78eHunvJ708x5r6umXsbiUyQU4eqgPzEylWEQiunw==",
+            "requires": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/boolean-within": "^6.5.0",
+                "@turf/explode": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/nearest-point": "^6.5.0"
+            }
+        },
+        "@turf/polygon-to-line": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/polygon-to-line/-/polygon-to-line-6.5.0.tgz",
+            "integrity": "sha512-5p4n/ij97EIttAq+ewSnKt0ruvuM+LIDzuczSzuHTpq4oS7Oq8yqg5TQ4nzMVuK41r/tALCk7nAoBuw3Su4Gcw==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/polygonize": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/polygonize/-/polygonize-6.5.0.tgz",
+            "integrity": "sha512-a/3GzHRaCyzg7tVYHo43QUChCspa99oK4yPqooVIwTC61npFzdrmnywMv0S+WZjHZwK37BrFJGFrZGf6ocmY5w==",
+            "requires": {
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/envelope": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/projection": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/projection/-/projection-6.5.0.tgz",
+            "integrity": "sha512-/Pgh9mDvQWWu8HRxqpM+tKz8OzgauV+DiOcr3FCjD6ubDnrrmMJlsf6fFJmggw93mtVPrZRL6yyi9aYCQBOIvg==",
+            "requires": {
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/random": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/random/-/random-6.5.0.tgz",
+            "integrity": "sha512-8Q25gQ/XbA7HJAe+eXp4UhcXM9aOOJFaxZ02+XSNwMvY8gtWSCBLVqRcW4OhqilgZ8PeuQDWgBxeo+BIqqFWFQ==",
+            "requires": {
+                "@turf/helpers": "^6.5.0"
+            }
+        },
+        "@turf/rectangle-grid": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/rectangle-grid/-/rectangle-grid-6.5.0.tgz",
+            "integrity": "sha512-yQZ/1vbW68O2KsSB3OZYK+72aWz/Adnf7m2CMKcC+aq6TwjxZjAvlbCOsNUnMAuldRUVN1ph6RXMG4e9KEvKvg==",
+            "requires": {
+                "@turf/boolean-intersects": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0"
+            }
+        },
+        "@turf/rewind": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/rewind/-/rewind-6.5.0.tgz",
+            "integrity": "sha512-IoUAMcHWotBWYwSYuYypw/LlqZmO+wcBpn8ysrBNbazkFNkLf3btSDZMkKJO/bvOzl55imr/Xj4fi3DdsLsbzQ==",
+            "requires": {
+                "@turf/boolean-clockwise": "^6.5.0",
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/rhumb-bearing": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/rhumb-bearing/-/rhumb-bearing-6.5.0.tgz",
+            "integrity": "sha512-jMyqiMRK4hzREjQmnLXmkJ+VTNTx1ii8vuqRwJPcTlKbNWfjDz/5JqJlb5NaFDcdMpftWovkW5GevfnuzHnOYA==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/rhumb-destination": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/rhumb-destination/-/rhumb-destination-6.5.0.tgz",
+            "integrity": "sha512-RHNP1Oy+7xTTdRrTt375jOZeHceFbjwohPHlr9Hf68VdHHPMAWgAKqiX2YgSWDcvECVmiGaBKWus1Df+N7eE4Q==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/rhumb-distance": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/rhumb-distance/-/rhumb-distance-6.5.0.tgz",
+            "integrity": "sha512-oKp8KFE8E4huC2Z1a1KNcFwjVOqa99isxNOwfo4g3SUABQ6NezjKDDrnvC4yI5YZ3/huDjULLBvhed45xdCrzg==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            }
+        },
+        "@turf/sample": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/sample/-/sample-6.5.0.tgz",
+            "integrity": "sha512-kSdCwY7el15xQjnXYW520heKUrHwRvnzx8ka4eYxX9NFeOxaFITLW2G7UtXb6LJK8mmPXI8Aexv23F2ERqzGFg==",
+            "requires": {
+                "@turf/helpers": "^6.5.0"
+            }
+        },
+        "@turf/sector": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/sector/-/sector-6.5.0.tgz",
+            "integrity": "sha512-cYUOkgCTWqa23SOJBqxoFAc/yGCUsPRdn/ovbRTn1zNTm/Spmk6hVB84LCKOgHqvSF25i0d2kWqpZDzLDdAPbw==",
+            "requires": {
+                "@turf/circle": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/line-arc": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/shortest-path": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/shortest-path/-/shortest-path-6.5.0.tgz",
+            "integrity": "sha512-4de5+G7+P4hgSoPwn+SO9QSi9HY5NEV/xRJ+cmoFVRwv2CDsuOPDheHKeuIAhKyeKDvPvPt04XYWbac4insJMg==",
+            "requires": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/bbox-polygon": "^6.5.0",
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/clean-coords": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/transform-scale": "^6.5.0"
+            }
+        },
+        "@turf/simplify": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/simplify/-/simplify-6.5.0.tgz",
+            "integrity": "sha512-USas3QqffPHUY184dwQdP8qsvcVH/PWBYdXY5am7YTBACaQOMAlf6AKJs9FT8jiO6fQpxfgxuEtwmox+pBtlOg==",
+            "requires": {
+                "@turf/clean-coords": "^6.5.0",
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/square": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/square/-/square-6.5.0.tgz",
+            "integrity": "sha512-BM2UyWDmiuHCadVhHXKIx5CQQbNCpOxB6S/aCNOCLbhCeypKX5Q0Aosc5YcmCJgkwO5BERCC6Ee7NMbNB2vHmQ==",
+            "requires": {
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0"
+            }
+        },
+        "@turf/square-grid": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/square-grid/-/square-grid-6.5.0.tgz",
+            "integrity": "sha512-mlR0ayUdA+L4c9h7p4k3pX6gPWHNGuZkt2c5II1TJRmhLkW2557d6b/Vjfd1z9OVaajb1HinIs1FMSAPXuuUrA==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/rectangle-grid": "^6.5.0"
+            }
+        },
+        "@turf/standard-deviational-ellipse": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/standard-deviational-ellipse/-/standard-deviational-ellipse-6.5.0.tgz",
+            "integrity": "sha512-02CAlz8POvGPFK2BKK8uHGUk/LXb0MK459JVjKxLC2yJYieOBTqEbjP0qaWhiBhGzIxSMaqe8WxZ0KvqdnstHA==",
+            "requires": {
+                "@turf/center-mean": "^6.5.0",
+                "@turf/ellipse": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/points-within-polygon": "^6.5.0"
+            }
+        },
+        "@turf/tag": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/tag/-/tag-6.5.0.tgz",
+            "integrity": "sha512-XwlBvrOV38CQsrNfrxvBaAPBQgXMljeU0DV8ExOyGM7/hvuGHJw3y8kKnQ4lmEQcmcrycjDQhP7JqoRv8vFssg==",
+            "requires": {
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/tesselate": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/tesselate/-/tesselate-6.5.0.tgz",
+            "integrity": "sha512-M1HXuyZFCfEIIKkglh/r5L9H3c5QTEsnMBoZOFQiRnGPGmJWcaBissGb7mTFX2+DKE7FNWXh4TDnZlaLABB0dQ==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "earcut": "^2.0.0"
+            }
+        },
+        "@turf/tin": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/tin/-/tin-6.5.0.tgz",
+            "integrity": "sha512-YLYikRzKisfwj7+F+Tmyy/LE3d2H7D4kajajIfc9mlik2+esG7IolsX/+oUz1biguDYsG0DUA8kVYXDkobukfg==",
+            "requires": {
+                "@turf/helpers": "^6.5.0"
+            }
+        },
+        "@turf/transform-rotate": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/transform-rotate/-/transform-rotate-6.5.0.tgz",
+            "integrity": "sha512-A2Ip1v4246ZmpssxpcL0hhiVBEf4L8lGnSPWTgSv5bWBEoya2fa/0SnFX9xJgP40rMP+ZzRaCN37vLHbv1Guag==",
+            "requires": {
+                "@turf/centroid": "^6.5.0",
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/rhumb-bearing": "^6.5.0",
+                "@turf/rhumb-destination": "^6.5.0",
+                "@turf/rhumb-distance": "^6.5.0"
+            }
+        },
+        "@turf/transform-scale": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/transform-scale/-/transform-scale-6.5.0.tgz",
+            "integrity": "sha512-VsATGXC9rYM8qTjbQJ/P7BswKWXHdnSJ35JlV4OsZyHBMxJQHftvmZJsFbOqVtQnIQIzf2OAly6rfzVV9QLr7g==",
+            "requires": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/center": "^6.5.0",
+                "@turf/centroid": "^6.5.0",
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/rhumb-bearing": "^6.5.0",
+                "@turf/rhumb-destination": "^6.5.0",
+                "@turf/rhumb-distance": "^6.5.0"
+            }
+        },
+        "@turf/transform-translate": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/transform-translate/-/transform-translate-6.5.0.tgz",
+            "integrity": "sha512-NABLw5VdtJt/9vSstChp93pc6oel4qXEos56RBMsPlYB8hzNTEKYtC146XJvyF4twJeeYS8RVe1u7KhoFwEM5w==",
+            "requires": {
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/rhumb-destination": "^6.5.0"
+            }
+        },
+        "@turf/triangle-grid": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/triangle-grid/-/triangle-grid-6.5.0.tgz",
+            "integrity": "sha512-2jToUSAS1R1htq4TyLQYPTIsoy6wg3e3BQXjm2rANzw4wPQCXGOxrur1Fy9RtzwqwljlC7DF4tg0OnWr8RjmfA==",
+            "requires": {
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/intersect": "^6.5.0"
+            }
+        },
+        "@turf/truncate": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/truncate/-/truncate-6.5.0.tgz",
+            "integrity": "sha512-pFxg71pLk+eJj134Z9yUoRhIi8vqnnKvCYwdT4x/DQl/19RVdq1tV3yqOT3gcTQNfniteylL5qV1uTBDV5sgrg==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            }
+        },
+        "@turf/turf": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/turf/-/turf-6.5.0.tgz",
+            "integrity": "sha512-ipMCPnhu59bh92MNt8+pr1VZQhHVuTMHklciQURo54heoxRzt1neNYZOBR6jdL+hNsbDGAECMuIpAutX+a3Y+w==",
+            "requires": {
+                "@turf/along": "^6.5.0",
+                "@turf/angle": "^6.5.0",
+                "@turf/area": "^6.5.0",
+                "@turf/bbox": "^6.5.0",
+                "@turf/bbox-clip": "^6.5.0",
+                "@turf/bbox-polygon": "^6.5.0",
+                "@turf/bearing": "^6.5.0",
+                "@turf/bezier-spline": "^6.5.0",
+                "@turf/boolean-clockwise": "^6.5.0",
+                "@turf/boolean-contains": "^6.5.0",
+                "@turf/boolean-crosses": "^6.5.0",
+                "@turf/boolean-disjoint": "^6.5.0",
+                "@turf/boolean-equal": "^6.5.0",
+                "@turf/boolean-intersects": "^6.5.0",
+                "@turf/boolean-overlap": "^6.5.0",
+                "@turf/boolean-parallel": "^6.5.0",
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/boolean-point-on-line": "^6.5.0",
+                "@turf/boolean-within": "^6.5.0",
+                "@turf/buffer": "^6.5.0",
+                "@turf/center": "^6.5.0",
+                "@turf/center-mean": "^6.5.0",
+                "@turf/center-median": "^6.5.0",
+                "@turf/center-of-mass": "^6.5.0",
+                "@turf/centroid": "^6.5.0",
+                "@turf/circle": "^6.5.0",
+                "@turf/clean-coords": "^6.5.0",
+                "@turf/clone": "^6.5.0",
+                "@turf/clusters": "^6.5.0",
+                "@turf/clusters-dbscan": "^6.5.0",
+                "@turf/clusters-kmeans": "^6.5.0",
+                "@turf/collect": "^6.5.0",
+                "@turf/combine": "^6.5.0",
+                "@turf/concave": "^6.5.0",
+                "@turf/convex": "^6.5.0",
+                "@turf/destination": "^6.5.0",
+                "@turf/difference": "^6.5.0",
+                "@turf/dissolve": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/distance-weight": "^6.5.0",
+                "@turf/ellipse": "^6.5.0",
+                "@turf/envelope": "^6.5.0",
+                "@turf/explode": "^6.5.0",
+                "@turf/flatten": "^6.5.0",
+                "@turf/flip": "^6.5.0",
+                "@turf/great-circle": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/hex-grid": "^6.5.0",
+                "@turf/interpolate": "^6.5.0",
+                "@turf/intersect": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/isobands": "^6.5.0",
+                "@turf/isolines": "^6.5.0",
+                "@turf/kinks": "^6.5.0",
+                "@turf/length": "^6.5.0",
+                "@turf/line-arc": "^6.5.0",
+                "@turf/line-chunk": "^6.5.0",
+                "@turf/line-intersect": "^6.5.0",
+                "@turf/line-offset": "^6.5.0",
+                "@turf/line-overlap": "^6.5.0",
+                "@turf/line-segment": "^6.5.0",
+                "@turf/line-slice": "^6.5.0",
+                "@turf/line-slice-along": "^6.5.0",
+                "@turf/line-split": "^6.5.0",
+                "@turf/line-to-polygon": "^6.5.0",
+                "@turf/mask": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/midpoint": "^6.5.0",
+                "@turf/moran-index": "^6.5.0",
+                "@turf/nearest-point": "^6.5.0",
+                "@turf/nearest-point-on-line": "^6.5.0",
+                "@turf/nearest-point-to-line": "^6.5.0",
+                "@turf/planepoint": "^6.5.0",
+                "@turf/point-grid": "^6.5.0",
+                "@turf/point-on-feature": "^6.5.0",
+                "@turf/point-to-line-distance": "^6.5.0",
+                "@turf/points-within-polygon": "^6.5.0",
+                "@turf/polygon-smooth": "^6.5.0",
+                "@turf/polygon-tangents": "^6.5.0",
+                "@turf/polygon-to-line": "^6.5.0",
+                "@turf/polygonize": "^6.5.0",
+                "@turf/projection": "^6.5.0",
+                "@turf/random": "^6.5.0",
+                "@turf/rewind": "^6.5.0",
+                "@turf/rhumb-bearing": "^6.5.0",
+                "@turf/rhumb-destination": "^6.5.0",
+                "@turf/rhumb-distance": "^6.5.0",
+                "@turf/sample": "^6.5.0",
+                "@turf/sector": "^6.5.0",
+                "@turf/shortest-path": "^6.5.0",
+                "@turf/simplify": "^6.5.0",
+                "@turf/square": "^6.5.0",
+                "@turf/square-grid": "^6.5.0",
+                "@turf/standard-deviational-ellipse": "^6.5.0",
+                "@turf/tag": "^6.5.0",
+                "@turf/tesselate": "^6.5.0",
+                "@turf/tin": "^6.5.0",
+                "@turf/transform-rotate": "^6.5.0",
+                "@turf/transform-scale": "^6.5.0",
+                "@turf/transform-translate": "^6.5.0",
+                "@turf/triangle-grid": "^6.5.0",
+                "@turf/truncate": "^6.5.0",
+                "@turf/union": "^6.5.0",
+                "@turf/unkink-polygon": "^6.5.0",
+                "@turf/voronoi": "^6.5.0"
+            }
+        },
+        "@turf/union": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/union/-/union-6.5.0.tgz",
+            "integrity": "sha512-igYWCwP/f0RFHIlC2c0SKDuM/ObBaqSljI3IdV/x71805QbIvY/BYGcJdyNcgEA6cylIGl/0VSlIbpJHZ9ldhw==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "polygon-clipping": "^0.15.3"
+            }
+        },
+        "@turf/unkink-polygon": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/unkink-polygon/-/unkink-polygon-6.5.0.tgz",
+            "integrity": "sha512-8QswkzC0UqKmN1DT6HpA9upfa1HdAA5n6bbuzHy8NJOX8oVizVAqfEPY0wqqTgboDjmBR4yyImsdPGUl3gZ8JQ==",
+            "requires": {
+                "@turf/area": "^6.5.0",
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "rbush": "^2.0.1"
+            },
+            "dependencies": {
+                "quickselect": {
+                    "version": "1.1.1",
+                    "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-1.1.1.tgz",
+                    "integrity": "sha512-qN0Gqdw4c4KGPsBOQafj6yj/PA6c/L63f6CaZ/DCF/xF4Esu3jVmKLUDYxghFx8Kb/O7y9tI7x2RjTSXwdK1iQ=="
+                },
+                "rbush": {
+                    "version": "2.0.2",
+                    "resolved": "https://registry.npmjs.org/rbush/-/rbush-2.0.2.tgz",
+                    "integrity": "sha512-XBOuALcTm+O/H8G90b6pzu6nX6v2zCKiFG4BJho8a+bY6AER6t8uQUZdi5bomQc0AprCWhEGa7ncAbbRap0bRA==",
+                    "requires": {
+                        "quickselect": "^1.0.1"
+                    }
+                }
+            }
+        },
+        "@turf/voronoi": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/voronoi/-/voronoi-6.5.0.tgz",
+            "integrity": "sha512-C/xUsywYX+7h1UyNqnydHXiun4UPjK88VDghtoRypR9cLlb7qozkiLRphQxxsCM0KxyxpVPHBVQXdAL3+Yurow==",
+            "requires": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "d3-voronoi": "1.1.2"
+            }
+        },
         "@types/adm-zip": {
             "version": "0.4.34",
             "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.34.tgz",
@@ -9921,11 +11209,6 @@
             "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz",
             "integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w=="
         },
-        "cheap-ruler": {
-            "version": "3.0.2",
-            "resolved": "https://registry.npmjs.org/cheap-ruler/-/cheap-ruler-3.0.2.tgz",
-            "integrity": "sha512-02T332h1/HTN6cDSufLP8x4JzDs2+VC+8qZ/N0kWIVPyc2xUkWwWh3B2fJxR7raXkL4Mq7k554mfuM9ofv/vGg=="
-        },
         "check-error": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
@@ -10539,6 +11822,24 @@
                 }
             }
         },
+        "concaveman": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/concaveman/-/concaveman-1.2.1.tgz",
+            "integrity": "sha512-PwZYKaM/ckQSa8peP5JpVr7IMJ4Nn/MHIaWUjP4be+KoZ7Botgs8seAZGpmaOM+UZXawcdYRao/px9ycrCihHw==",
+            "requires": {
+                "point-in-polygon": "^1.1.0",
+                "rbush": "^3.0.1",
+                "robust-predicates": "^2.0.4",
+                "tinyqueue": "^2.0.3"
+            },
+            "dependencies": {
+                "robust-predicates": {
+                    "version": "2.0.4",
+                    "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-2.0.4.tgz",
+                    "integrity": "sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg=="
+                }
+            }
+        },
         "configstore": {
             "version": "3.1.5",
             "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.5.tgz",
@@ -11205,9 +12506,9 @@
             }
         },
         "d3": {
-            "version": "7.8.4",
-            "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.4.tgz",
-            "integrity": "sha512-q2WHStdhiBtD8DMmhDPyJmXUxr6VWRngKyiJ5EfXMxPw+tqT6BhNjhJZ4w3BHsNm3QoVfZLY8Orq/qPFczwKRA==",
+            "version": "7.8.5",
+            "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz",
+            "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==",
             "requires": {
                 "d3-array": "3",
                 "d3-axis": "3",
@@ -11462,6 +12763,11 @@
                 "d3-timer": "1 - 3"
             }
         },
+        "d3-voronoi": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.2.tgz",
+            "integrity": "sha512-RhGS1u2vavcO7ay7ZNAPo4xeDh/VYeGof3x5ZLJBQgYhLegxr3s5IykvWmJ94FTU6mcbtp4sloqZ54mP6R4Utw=="
+        },
         "d3-zoom": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
@@ -11826,6 +13132,11 @@
             "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz",
             "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw=="
         },
+        "density-clustering": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/density-clustering/-/density-clustering-1.3.0.tgz",
+            "integrity": "sha512-icpmBubVTwLnsaor9qH/4tG5+7+f61VcqMN3V3pm9sxxSCt2Jcs0zWOgwZW9ARJYaKD3FumIgHiMOcIMRRAzFQ=="
+        },
         "depcheck": {
             "version": "0.9.2",
             "resolved": "https://registry.npmjs.org/depcheck/-/depcheck-0.9.2.tgz",
@@ -15290,6 +16601,33 @@
             "resolved": "https://registry.npmjs.org/geojson/-/geojson-0.5.0.tgz",
             "integrity": "sha512-/Bx5lEn+qRF4TfQ5aLu6NH+UKtvIv7Lhc487y/c8BdludrCTpiWf9wyI0RTyqg49MFefIAvFDuEi5Dfd/zgNxQ=="
         },
+        "geojson-equality": {
+            "version": "0.1.6",
+            "resolved": "https://registry.npmjs.org/geojson-equality/-/geojson-equality-0.1.6.tgz",
+            "integrity": "sha512-TqG8YbqizP3EfwP5Uw4aLu6pKkg6JQK9uq/XZ1lXQntvTHD1BBKJWhNpJ2M0ax6TuWMP3oyx6Oq7FCIfznrgpQ==",
+            "requires": {
+                "deep-equal": "^1.0.0"
+            }
+        },
+        "geojson-rbush": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/geojson-rbush/-/geojson-rbush-3.2.0.tgz",
+            "integrity": "sha512-oVltQTXolxvsz1sZnutlSuLDEcQAKYC/uXt9zDzJJ6bu0W+baTI8LZBaTup5afzibEH4N3jlq2p+a152wlBJ7w==",
+            "requires": {
+                "@turf/bbox": "*",
+                "@turf/helpers": "6.x",
+                "@turf/meta": "6.x",
+                "@types/geojson": "7946.0.8",
+                "rbush": "^3.0.1"
+            },
+            "dependencies": {
+                "@types/geojson": {
+                    "version": "7946.0.8",
+                    "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz",
+                    "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA=="
+                }
+            }
+        },
         "geojson-vt": {
             "version": "3.2.1",
             "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz",
@@ -18715,9 +20053,9 @@
             }
         },
         "mapbox-gl": {
-            "version": "3.0.1",
-            "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.0.1.tgz",
-            "integrity": "sha512-o7C6sAlj6Hkdd4xQVEgQflgJYNYyZOAtYahhIOb9m8chI8umtWcCp8Ie0iGLYJvce1WHRMa3WGzs69ggwuWlDA==",
+            "version": "2.15.0",
+            "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-2.15.0.tgz",
+            "integrity": "sha512-fjv+aYrd5TIHiL7wRa+W7KjtUqKWziJMZUkK5hm8TvJ3OLeNPx4NmW/DgfYhd/jHej8wWL+QJBDbdMMAKvNC0A==",
             "requires": {
                 "@mapbox/geojson-rewind": "^0.5.2",
                 "@mapbox/jsonlint-lines-primitives": "^2.0.2",
@@ -18727,7 +20065,6 @@
                 "@mapbox/unitbezier": "^0.0.1",
                 "@mapbox/vector-tile": "^1.3.1",
                 "@mapbox/whoots-js": "^3.1.0",
-                "cheap-ruler": "^3.0.1",
                 "csscolorparser": "~1.0.3",
                 "earcut": "^2.2.4",
                 "geojson-vt": "^3.2.1",
@@ -25366,6 +26703,19 @@
             "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==",
             "dev": true
         },
+        "point-in-polygon": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz",
+            "integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw=="
+        },
+        "polygon-clipping": {
+            "version": "0.15.3",
+            "resolved": "https://registry.npmjs.org/polygon-clipping/-/polygon-clipping-0.15.3.tgz",
+            "integrity": "sha512-ho0Xx5DLkgxRx/+n4O74XyJ67DcyN3Tu9bGYKsnTukGAW6ssnuak6Mwcyb1wHy9MZc9xsUWqIoiazkZB5weECg==",
+            "requires": {
+                "splaytree": "^3.1.0"
+            }
+        },
         "popper.js": {
             "version": "1.16.1",
             "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
@@ -26218,6 +27568,14 @@
                 "schema-utils": "^1.0.0"
             }
         },
+        "rbush": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz",
+            "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==",
+            "requires": {
+                "quickselect": "^2.0.0"
+            }
+        },
         "rc": {
             "version": "1.2.8",
             "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
@@ -27649,9 +29007,9 @@
             }
         },
         "robust-predicates": {
-            "version": "3.0.1",
-            "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz",
-            "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g=="
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
+            "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="
         },
         "rope-sequence": {
             "version": "1.3.3",
@@ -28341,6 +29699,11 @@
                 }
             }
         },
+        "skmeans": {
+            "version": "0.9.7",
+            "resolved": "https://registry.npmjs.org/skmeans/-/skmeans-0.9.7.tgz",
+            "integrity": "sha512-hNj1/oZ7ygsfmPZ7ZfN5MUBRoGg1gtpnImuJBgLO0ljQ67DtJuiQaiYdS4lUA6s0KCwnPhGivtC/WRwIZLkHyg=="
+        },
         "slash": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
@@ -28852,6 +30215,11 @@
                 }
             }
         },
+        "splaytree": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.2.tgz",
+            "integrity": "sha512-4OM2BJgC5UzrhVnnJA4BkHKGtjXNzzUfpQjCO8I05xYPsfS/VuQDwjCGGMi8rYQilHEV4j8NBqTFbls/PZEE7A=="
+        },
         "split-on-first": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
@@ -29652,6 +31020,36 @@
             "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz",
             "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo="
         },
+        "topojson-client": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz",
+            "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==",
+            "requires": {
+                "commander": "2"
+            },
+            "dependencies": {
+                "commander": {
+                    "version": "2.20.3",
+                    "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+                    "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+                }
+            }
+        },
+        "topojson-server": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/topojson-server/-/topojson-server-3.0.1.tgz",
+            "integrity": "sha512-/VS9j/ffKr2XAOjlZ9CgyyeLmgJ9dMwq6Y0YEON8O7p/tGGk+dCWnrE03zEdu7i4L7YsFZLEPZPzCvcB7lEEXw==",
+            "requires": {
+                "commander": "2"
+            },
+            "dependencies": {
+                "commander": {
+                    "version": "2.20.3",
+                    "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+                    "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+                }
+            }
+        },
         "touch": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
@@ -30107,6 +31505,11 @@
                 "safe-buffer": "^5.0.1"
             }
         },
+        "turf-jsts": {
+            "version": "1.2.3",
+            "resolved": "https://registry.npmjs.org/turf-jsts/-/turf-jsts-1.2.3.tgz",
+            "integrity": "sha512-Ja03QIJlPuHt4IQ2FfGex4F4JAr8m3jpaHbFbQrgwr7s7L6U8ocrHiF3J1+wf9jzhGKxvDeaCAnGDot8OjGFyA=="
+        },
         "tweetnacl": {
             "version": "0.14.5",
             "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
diff --git a/package.json b/package.json
index 42d43e758..9a9e7675d 100644
--- a/package.json
+++ b/package.json
@@ -46,7 +46,7 @@
         "@types/jquery": "^3.5.14",
         "@types/libxmljs": "^0.18.7",
         "@types/lodash": "^4.14.179",
-        "@types/mapbox-gl": "^2.7.19",
+        "@types/mapbox-gl": "^2.7.17",
         "@types/mobile-detect": "^1.3.4",
         "@types/mocha": "^5.2.6",
         "@types/mongodb": "^3.6.20",
@@ -151,6 +151,7 @@
         "@octokit/core": "^4.0.4",
         "@react-google-maps/api": "^2.7.0",
         "@react-three/fiber": "^6.2.3",
+        "@turf/turf": "^6.5.0",
         "@types/bezier-js": "^4.1.0",
         "@types/cors": "^2.8.12",
         "@types/d3-axis": "^2.1.3",
@@ -199,7 +200,7 @@
         "cors": "^2.8.5",
         "csv-parser": "^3.0.0",
         "csv-stringify": "^6.3.0",
-        "d3": "^7.6.1",
+        "d3": "^7.8.5",
         "debounce": "^2.0.0",
         "depcheck": "^0.9.2",
         "equation-editor-react": "github:bobzel/equation-editor-react#useLocally",
@@ -243,7 +244,7 @@
         "jsonschema": "^1.4.0",
         "jszip": "^3.7.1",
         "lodash": "^4.17.21",
-        "mapbox-gl": "^3.0.1",
+        "mapbox-gl": "^2.15.0",
         "material-ui": "^0.20.2",
         "md5-file": "^5.0.0",
         "memorystream": "^0.3.1",
@@ -302,7 +303,7 @@
         "react-jsx-parser": "^1.29.0",
         "react-loader-spinner": "^5.3.4",
         "react-loading": "^2.0.3",
-        "react-map-gl": "^7.1.0",
+        "react-map-gl": "^7.1.6",
         "react-markdown": "^8.0.3",
         "react-measure": "^2.5.2",
         "react-refresh-typescript": "^2.0.7",
diff --git a/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx b/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx
new file mode 100644
index 000000000..d54a175b2
--- /dev/null
+++ b/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx
@@ -0,0 +1,35 @@
+import * as React from "react";
+
+export const slowSpeedIcon: JSX.Element = (
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 435.62">
+        <defs>
+        <style type="text/css">
+            {`
+                .fil0 { fill: black; fill-rule: nonzero; }
+                .fil1 { fill: #FE0000; fill-rule: nonzero; }
+            `}
+        </style>
+        </defs>
+        <path className="fil0" d="M174.84 343.06c-7.31,-13.12 -13.03,-27.28 -16.89,-42.18 -3.76,-14.56 -5.77,-29.71 -5.77,-45.17 0,-11.94 1.19,-23.66 3.43,-35.03 2.29,-11.57 5.74,-22.83 10.2,-33.63 13.7,-33.14 37.01,-61.29 66.42,-80.96 25.38,-16.96 55.28,-27.66 87.45,-29.87l0 -30.17c0,-0.46 0.02,-0.92 0.06,-1.37l-33.7 0c-5.53,0 -10.05,-4.52 -10.05,-10.04l0 -24.59c0,-5.53 4.52,-10.05 10.05,-10.05l101.27 0c5.53,0 10.05,4.52 10.05,10.05l0 24.59c0,5.52 -4.52,10.04 -10.05,10.04l-33.69 0c0.03,0.45 0.05,0.91 0.05,1.37l0 31.03 -0.1 0c41.1,4.89 77.94,23.63 105.73,51.42 32.56,32.55 52.7,77.54 52.7,127.21 0,49.67 -20.14,94.66 -52.7,127.21 -32.55,32.55 -77.54,52.7 -127.21,52.7 -33.16,0 -64.29,-9.04 -91.05,-24.78 -27.66,-16.27 -50.59,-39.73 -66.2,-67.78zm148.42 -36.62l-80.33 0 0 -25.71 28.6 0 0 -42.57 -28.6 1.93 0 -25.71 36.95 -8.35 25.38 0 0 74.7 18 0 0 25.71zm44.34 -100.41l11.08 26.83 1.61 0 11.09 -26.83 34.86 0 -22.33 48.52 22.33 51.89 -35.67 0 -12.05 -28.92 -1.44 0 -11.89 28.92 -34.06 0 21.85 -50.93 -21.85 -49.48 36.47 0zm126.08 -74.6c6.98,-16.66 6.15,-34.13 -3.84,-45.82 -12,-14.03 -33.67,-15.64 -53.8,-5.77 21.32,14.62 40.68,31.63 57.64,51.59zm-323.17 0c-6.98,-16.66 -6.16,-34.13 3.84,-45.82 11.99,-14.03 33.67,-15.64 53.79,-5.77 -21.32,14.62 -40.68,31.63 -57.63,51.59zm15.31 162.23c3.23,12.5 8.04,24.39 14.18,35.42 13.13,23.58 32.39,43.29 55.6,56.94 22.37,13.16 48.52,20.71 76.49,20.71 41.71,0 79.47,-16.9 106.8,-44.23 27.32,-27.32 44.23,-65.08 44.23,-106.79 0,-41.71 -16.91,-79.47 -44.23,-106.8 -27.33,-27.32 -65.09,-44.23 -106.8,-44.23 -31.07,0 -59.91,9.34 -83.84,25.33 -24.74,16.54 -44.33,40.19 -55.82,67.98 -3.68,8.91 -6.56,18.35 -8.5,28.22 -1.87,9.49 -2.86,19.36 -2.86,29.5 0,13.24 1.65,25.96 4.75,37.95z"/>
+        <path className="fil1" d="M55.23 188.52c-7.98,0 -14.45,-6.47 -14.45,-14.44 0,-7.98 6.47,-14.45 14.45,-14.45l63.94 0c7.98,0 14.45,6.47 14.45,14.45 0,7.97 -6.47,14.44 -14.45,14.44l-63.94 0zm0.72 167.68c-7.97,0 -14.44,-6.47 -14.44,-14.45 0,-7.97 6.47,-14.45 14.44,-14.45l64.58 0c7.97,0 14.45,6.48 14.45,14.45 0,7.98 -6.48,14.45 -14.45,14.45l-64.58 0zm-41.5 -84.94c-7.98,0 -14.45,-6.47 -14.45,-14.45 0,-7.97 6.47,-14.44 14.45,-14.44l89.12 0c7.98,0 14.45,6.47 14.45,14.44 0,7.98 -6.47,14.45 -14.45,14.45l-89.12 0z"/>
+    </svg>
+);
+
+export const mediumSpeedIcon: JSX.Element = (
+    <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 122.88 104.55">
+        <defs><style>{`.cls-1{fill:#fe0000;}`}</style></defs>
+        <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.45l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.09l5.36,12.45H93.74L90.85,66.6H90.5l-2.85,6.94H79.47l5.25-12.22L79.47,49.45ZM58.65,56.08l-1-5.75a33.58,33.58,0,0,1,9.68-1.46c1.28,0,2.35,0,3.22.11a11.77,11.77,0,0,1,2.67.58,5.41,5.41,0,0,1,2.2,1.28c1.24,1.23,1.85,3.12,1.85,5.66s-.72,4.42-2.16,5.63S70.64,64.73,66,66.3v1.08H76.89v6.16H57.11V68.72a10.73,10.73,0,0,1,.81-4.12,8.4,8.4,0,0,1,2.43-2.7,12.13,12.13,0,0,1,2.79-1.7l3.32-1.52c1-.47,1.88-.87,2.52-1.17V55.42a28.59,28.59,0,0,0-3.2-.19,30.66,30.66,0,0,0-7.13.85Zm59.83-24.54c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z"/>
+        <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Z"/>
+        <path className="cls-1" d="M3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z"/>
+        <path className="cls-1" d="M13.43,85.49a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94Z"/>
+    </svg>
+);
+
+export const fastSpeedIcon: JSX.Element = (
+    <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 122.88 104.55">
+        <defs><style>{`.cls-1{fill:#fe0000;`}</style></defs>
+        <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.61l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.26l5.36,12.45H93.74l-2.9-6.94H90.5l-2.86,6.94H79.47l5.24-12.22L79.47,49.61Zm-19,8.48v-2.5a24.92,24.92,0,0,0-3.74-.2A33.25,33.25,0,0,0,59,56.2l-1-5.7A30.47,30.47,0,0,1,67.13,49a22.86,22.86,0,0,1,5.48.47,6.91,6.91,0,0,1,2.5,1.11,5.62,5.62,0,0,1,1.78,4.55,5.84,5.84,0,0,1-3.2,5.56v.19a5.73,5.73,0,0,1,3.81,5.74,8.67,8.67,0,0,1-.63,3.49,6,6,0,0,1-1.6,2.24,7.15,7.15,0,0,1-2.55,1.25,25.64,25.64,0,0,1-6.61.66,37.78,37.78,0,0,1-8.54-1l1.08-6.37a27.22,27.22,0,0,0,6.21.89,35.79,35.79,0,0,0,4.35-.23V65.11l-6.63-.65V58.87l6.63-.78Zm49.27-26.55c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z"/>
+        <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Zm.18,40.24a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94ZM3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z"/>
+    </svg>
+);
+
diff --git a/src/client/views/nodes/MapBox/AnimationUtility.ts b/src/client/views/nodes/MapBox/AnimationUtility.ts
new file mode 100644
index 000000000..256acbf13
--- /dev/null
+++ b/src/client/views/nodes/MapBox/AnimationUtility.ts
@@ -0,0 +1,429 @@
+import mapboxgl from "mapbox-gl";
+import { MercatorCoordinate } from "mapbox-gl";
+import { MapRef } from "react-map-gl";
+import * as React from 'react';
+import * as d3 from "d3";
+import * as turf from '@turf/turf';
+import { Position } from "@turf/turf";
+import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from "geojson";
+import { observer } from "mobx-react";
+import { action, computed, observable } from "mobx";
+
+export enum AnimationStatus {
+    START = 'start',
+    RESUME = 'resume',
+    RESTART = 'restart',
+}
+
+export enum AnimationSpeed {
+    SLOW = '1x',
+    MEDIUM = '2x',
+    FAST = '3x',
+}
+
+@observer
+export class AnimationUtility {
+    private SMOOTH_FACTOR = 0.95
+    private ROUTE_COORDINATES: Position[] = []; 
+    private PATH: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties>;
+    private FLY_IN_START_PITCH = 40;
+    private FIRST_LNG_LAT: {lng: number, lat: number};
+    private START_ALTITUDE = 3_000_000;
+
+    @observable private isStreetViewAnimation: boolean;
+    @observable private animationSpeed: AnimationSpeed;
+
+    
+    private previousLngLat: {lng: number, lat: number};
+
+    private currentStreetViewBearing: number = 0;
+
+    @computed get flyInEndBearing() {
+        return this.isStreetViewAnimation ? 
+            this.calculateBearing(
+                {
+                    lng: this.ROUTE_COORDINATES[0][0],
+                    lat: this.ROUTE_COORDINATES[0][1]
+                },
+                {
+                    lng: this.ROUTE_COORDINATES[1][0],
+                    lat: this.ROUTE_COORDINATES[1][1]
+                }
+            )
+            : -20;
+        }
+
+    @computed get flyInStartBearing() {
+        return Math.max(0, Math.min(this.flyInEndBearing + 20, 360)); // between 0 and 360
+    }
+
+    @computed get flyInEndAltitude() {
+        return this.isStreetViewAnimation ? 70 : 10000;
+    }
+
+    @computed get flyInEndPitch() {
+        return this.isStreetViewAnimation ? 80 : 50;
+    }
+
+    @computed get flyToDuration() {
+        switch (this.animationSpeed) {
+            case AnimationSpeed.SLOW:
+                return 4000;
+            case AnimationSpeed.MEDIUM:
+                return 2500;
+            case AnimationSpeed.FAST:
+                return 1250;
+            default:
+                return 2500;
+        }
+    }
+
+    @computed get animationDuration(): number {
+        return 20_000;
+        // compute path distance for a standard speed 
+        // get animation speed 
+        // get isStreetViewAnimation (should be slower if so)
+    }
+
+    @action
+    public updateAnimationSpeed(speed: AnimationSpeed) {
+        // calculate new flyToDuration and animationDuration
+        this.animationSpeed = speed;
+    }
+    
+    @action
+    public updateIsStreetViewAnimation(isStreetViewAnimation: boolean) {
+        this.isStreetViewAnimation = isStreetViewAnimation;
+    }
+
+
+    constructor(
+        firstLngLat: {lng: number, lat: number},
+        routeCoordinates: Position[],
+        isStreetViewAnimation: boolean,
+        animationSpeed: AnimationSpeed
+    ) {
+        this.FIRST_LNG_LAT = firstLngLat;
+        this.previousLngLat = firstLngLat;
+        this.ROUTE_COORDINATES = routeCoordinates;
+        this.PATH = turf.lineString(routeCoordinates);
+
+        const bearing = this.calculateBearing(
+            {
+                lng: routeCoordinates[0][0],
+                lat: routeCoordinates[0][1]
+            },
+            {
+                lng: routeCoordinates[1][0],
+                lat: routeCoordinates[1][1]
+            }
+        );
+        this.currentStreetViewBearing = bearing;
+        // if (isStreetViewAnimation){
+        //     this.flyInEndBearing = bearing;
+        // }
+        this.isStreetViewAnimation = isStreetViewAnimation;
+        this.animationSpeed = animationSpeed;
+        // calculate animation duration based on speed
+        // this.animationDuration = animationDuration;
+    }
+
+    public animatePath = async ({
+        map,
+        // path,
+        // startBearing,
+        // startAltitude,
+        // pitch,
+        currentAnimationPhase,
+        updateAnimationPhase,
+        updateFrameId,
+      }: {
+        map: MapRef,
+        // path: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties>,
+        // startBearing: number,
+        // startAltitude: number,
+        // pitch: number,
+        currentAnimationPhase: number,
+        updateAnimationPhase: ( 
+            newAnimationPhase: number,
+            ) => void,
+        updateFrameId: (newFrameId: number) => void;
+
+      }) => {
+        return new Promise<void>(async (resolve) => {
+          const pathDistance = turf.lineDistance(this.PATH);
+          console.log("PATH DISTANCE: ", pathDistance);
+          let startTime: number | null = null;
+      
+          const frame = async (currentTime: number) => {
+            if (!startTime) startTime = currentTime;
+            const elapsedSinceLastFrame = currentTime - startTime;
+            const phaseIncrement = elapsedSinceLastFrame / this.animationDuration;
+            const animationPhase = currentAnimationPhase + phaseIncrement;
+      
+            // when the duration is complete, resolve the promise and stop iterating
+            if (animationPhase > 1) {
+              resolve();
+              return;
+            }
+
+      
+            // calculate the distance along the path based on the animationPhase
+            const alongPath = turf.along(this.PATH, pathDistance * animationPhase).geometry
+              .coordinates;
+      
+            const lngLat = {
+              lng: alongPath[0],
+              lat: alongPath[1],
+            };
+      
+            let bearing: number;
+            if (this.isStreetViewAnimation){
+                bearing = this.lerp(
+                    this.currentStreetViewBearing,
+                    this.calculateBearing(this.previousLngLat, lngLat),
+                    0.028 // Adjust the transition speed as needed
+                  );
+                  this.currentStreetViewBearing = bearing;
+                // bearing = this.calculateBearing(this.previousLngLat, lngLat); // TODO: Calculate actual bearing
+            } else {
+                // slowly rotate the map at a constant rate
+                bearing = this.flyInEndBearing - animationPhase * 200.0;
+                // bearing = startBearing - animationPhase * 200.0;
+            }
+
+            this.previousLngLat = lngLat;
+            
+            updateAnimationPhase(animationPhase);
+      
+            // compute corrected camera ground position, so that he leading edge of the path is in view
+            var correctedPosition = this.computeCameraPosition(
+              this.isStreetViewAnimation,
+              this.flyInEndPitch,
+              bearing,
+              lngLat,
+              this.flyInEndAltitude,
+              true // smooth
+            );
+      
+            // set the pitch and bearing of the camera
+            const camera = map.getFreeCameraOptions();
+            camera.setPitchBearing(this.flyInEndPitch, bearing);
+      
+            console.log("Corrected pos: ", correctedPosition);
+            console.log("Start altitude: ", this.flyInEndAltitude);
+            // set the position and altitude of the camera
+            camera.position = MercatorCoordinate.fromLngLat(
+              correctedPosition,
+              this.flyInEndAltitude
+            );
+
+      
+            // apply the new camera options
+            map.setFreeCameraOptions(camera);
+      
+            // repeat!
+            const innerFrameId = await window.requestAnimationFrame(frame);
+            updateFrameId(innerFrameId);
+          };
+      
+          const outerFrameId = await window.requestAnimationFrame(frame);
+          updateFrameId(outerFrameId);
+        });
+      };
+
+    public flyInAndRotate = async ({
+        map,
+        updateFrameId
+      }:
+      {
+        map: MapRef,
+        updateFrameId: (newFrameId: number) => void
+      }
+      ) => {
+        return new Promise<{bearing: number, altitude: number}>(async (resolve) => {
+          let start: number | null;
+      
+          var currentAltitude;
+          var currentBearing;
+          var currentPitch;
+      
+          // the animation frame will run as many times as necessary until the duration has been reached
+          const frame = async (time: number) => {
+            if (!start) {
+              start = time;
+            }
+      
+            // otherwise, use the current time to determine how far along in the duration we are
+            let animationPhase = (time - start) / this.flyToDuration;
+      
+            // because the phase calculation is imprecise, the final zoom can vary
+            // if it ended up greater than 1, set it to 1 so that we get the exact endAltitude that was requested
+            if (animationPhase > 1) {
+              animationPhase = 1;
+            }
+      
+            currentAltitude = this.START_ALTITUDE + (this.flyInEndAltitude - this.START_ALTITUDE) * d3.easeCubicOut(animationPhase)
+            // rotate the camera between startBearing and endBearing
+            currentBearing = this.flyInStartBearing + (this.flyInEndBearing - this.flyInStartBearing) * d3.easeCubicOut(animationPhase)
+      
+            currentPitch = this.FLY_IN_START_PITCH + (this.flyInEndPitch - this.FLY_IN_START_PITCH) * d3.easeCubicOut(animationPhase)
+      
+            // compute corrected camera ground position, so the start of the path is always in view
+            var correctedPosition = this.computeCameraPosition(
+              false,
+              currentPitch,
+              currentBearing,
+              this.FIRST_LNG_LAT,
+              currentAltitude
+            );
+      
+            // set the pitch and bearing of the camera
+            const camera = map.getFreeCameraOptions();
+            camera.setPitchBearing(currentPitch, currentBearing);
+      
+            // set the position and altitude of the camera
+            camera.position = MercatorCoordinate.fromLngLat(
+              correctedPosition,
+              currentAltitude
+            );
+      
+            // apply the new camera options
+            map.setFreeCameraOptions(camera);
+      
+            // when the animationPhase is done, resolve the promise so the parent function can move on to the next step in the sequence
+            if (animationPhase === 1) {
+              resolve({
+                bearing: currentBearing,
+                altitude: currentAltitude,
+              });
+      
+              // return so there are no further iterations of this frame
+              return;
+            }
+      
+            const innerFrameId = await window.requestAnimationFrame(frame);
+            updateFrameId(innerFrameId);
+            
+          };
+      
+         const outerFrameId = await window.requestAnimationFrame(frame);
+         updateFrameId(outerFrameId);
+        });
+      };
+
+      previousCameraPosition: {lng: number, lat: number} | null = null;
+
+      lerp = (start: number, end: number, amt: number) => {
+        return (1 - amt) * start + amt * end;
+      }
+
+      computeCameraPosition = (
+        isStreetViewAnimation: boolean,
+        pitch: number,
+        bearing: number,
+        targetPosition: {lng: number, lat: number},
+        altitude: number,
+        smooth = false
+      ) => {
+        const bearingInRadian = (bearing * Math.PI) / 180;
+        const pitchInRadian = ((90 - pitch)* Math.PI) / 180;
+
+        let correctedLng = targetPosition.lng;
+        let correctedLat = targetPosition.lat;
+
+        if (!isStreetViewAnimation) {
+            const lngDiff =
+                ((altitude / Math.tan(pitchInRadian)) *
+                Math.sin(-bearingInRadian)) /
+                70000; // ~70km/degree longitude
+            const latDiff =
+                ((altitude / Math.tan(pitchInRadian)) *
+                Math.cos(-bearingInRadian)) /
+                110000 // 110km/degree latitude
+
+            correctedLng = targetPosition.lng + lngDiff;
+            correctedLat = targetPosition.lat - latDiff;
+
+        }
+        
+        const newCameraPosition = {
+          lng: correctedLng,
+          lat: correctedLat
+        };
+      
+        if (smooth) {
+          if (this.previousCameraPosition) {
+            newCameraPosition.lng = this.lerp(newCameraPosition.lng, this.previousCameraPosition.lng, this.SMOOTH_FACTOR);
+            newCameraPosition.lat = this.lerp(newCameraPosition.lat, this.previousCameraPosition.lat, this.SMOOTH_FACTOR);
+          }
+        }
+      
+        this.previousCameraPosition = newCameraPosition
+      
+        return newCameraPosition;
+      };
+      
+      public static createGeoJSONCircle = (center: number[], radiusInKm: number, points = 64): Feature<Geometry, GeoJsonProperties>=> {
+        const coords = {
+          latitude: center[1],
+          longitude: center[0],
+        };
+        const km = radiusInKm;
+        const ret = [];
+        const distanceX = km / (111.320 * Math.cos((coords.latitude * Math.PI) / 180));
+        const distanceY = km / 110.574;
+        let theta;
+        let x;
+        let y;
+        for (let i = 0; i < points; i += 1) {
+          theta = (i / points) * (2 * Math.PI);
+          x = distanceX * Math.cos(theta);
+          y = distanceY * Math.sin(theta);
+          ret.push([coords.longitude + x, coords.latitude + y]);
+        }
+        ret.push(ret[0]);
+        return {
+          type: 'Feature',
+          geometry: {
+            type: 'Polygon',
+            coordinates: [ret],
+          },
+          properties: {}
+        };
+      }
+
+      private calculateBearing(
+        from: { lng: number; lat: number },
+        to: { lng: number; lat: number }
+      ): number {
+        const lon1 = from.lng;
+        const lat1 = from.lat;
+        const lon2 = to.lng;
+        const lat2 = to.lat;
+      
+        const lon1Rad = (lon1 * Math.PI) / 180;
+        const lon2Rad = (lon2 * Math.PI) / 180;
+        const lat1Rad = (lat1 * Math.PI) / 180;
+        const lat2Rad = (lat2 * Math.PI) / 180;
+      
+        const y = Math.sin(lon2Rad - lon1Rad) * Math.cos(lat2Rad);
+        const x =
+          Math.cos(lat1Rad) * Math.sin(lat2Rad) -
+          Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(lon2Rad - lon1Rad);
+      
+        let bearing = Math.atan2(y,x);
+      
+        // Convert bearing from radians to degrees
+        bearing = (bearing * 180) / Math.PI;
+      
+        // Ensure the bearing is within [0, 360)
+        if (bearing < 0) {
+          bearing += 360;
+        }
+      
+        return bearing;
+      }
+
+
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index 2c2879900..f4e24d9c1 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -32,11 +32,11 @@ import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/materi
 import { MapboxApiUtility, TransportationType } from './MapboxApiUtility';
 import { MapBox } from './MapBox';
 import { List } from '../../../../fields/List';
-import { MapboxColor, MarkerIcons } from './MarkerIcons';
-import { CirclePicker } from 'react-color';
+import { MarkerIcons } from './MarkerIcons';
+import { CirclePicker, ColorState } from 'react-color';
 import { Position } from 'geojson';
 
-type MapAnchorMenuType = 'standard' | 'route' | 'calendar' | 'customize';
+type MapAnchorMenuType = 'standard' | 'routeCreation' | 'calendar' | 'customize' | 'route';
 
 @observer
 export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -61,17 +61,28 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     public DisplayRoute: (routeInfoMap: Record<TransportationType, any> | undefined, type: TransportationType) => void = unimplementedFunction;
     public HideRoute: () => void = unimplementedFunction;
-    public AddNewRouteToMap: (coordinates: Position[], origin: string, destination: string) => void = unimplementedFunction;
+    public AddNewRouteToMap: (coordinates: Position[], origin: string, destination: any, createPinForDestination: boolean) => void = unimplementedFunction;
     public CreatePin: (feature: any) => void = unimplementedFunction;
 
     public UpdateMarkerColor: (color: string) => void = unimplementedFunction;
     public UpdateMarkerIcon: (iconKey: string) => void = unimplementedFunction;
 
+    public OpenAnimationPanel: (routeDoc: Doc | undefined) => void = unimplementedFunction;
+
+    @observable
+    menuType: MapAnchorMenuType = 'standard';
+
+    @action
+    public setMenuType = (menuType: MapAnchorMenuType) => {
+        this.menuType = menuType;
+    }
     
 
     private allMapPinDocs: Doc[] = [];
 
-    private pinDoc: Doc | undefined = undefined 
+    private pinDoc: Doc | undefined = undefined;
+
+    private routeDoc: Doc | undefined = undefined;
 
     private title: string | undefined = undefined;
 
@@ -81,6 +92,11 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         this.title = StrCast(pinDoc.title ? pinDoc.title : `${NumCast(pinDoc.longitude)}, ${NumCast(pinDoc.latitude)}`) ;
     }
 
+    public setRouteDoc(routeDoc: Doc){
+        this.routeDoc = routeDoc;
+        this.title = StrCast(routeDoc.title ?? 'Map route')
+    }
+
     public setAllMapboxPins(pinDocs: Doc[]) {
         this.allMapPinDocs = pinDocs;
         pinDocs.forEach((p, idx) => {
@@ -148,12 +164,11 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     //     return this.top
     // }
 
-    @observable
-    menuType: MapAnchorMenuType = 'standard';
+
 
     @action
     DirectionsClick = () => {
-        this.menuType = 'route';
+        this.menuType = 'routeCreation';
     }
 
     @action
@@ -175,6 +190,26 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
           }
     }
 
+    @action 
+    onMarkerColorChange = (color: ColorState) => {
+        if (this.pinDoc){
+            this.pinDoc.markerColor = color.hex;
+        }
+    }
+
+    revertToOriginalMarker = () => {
+        if (this.pinDoc) {
+            this.pinDoc.markerType = "MAP_PIN";
+            this.pinDoc.markerColor = "#ff5722";
+        }
+    }
+
+    onMarkerIconChange = (iconKey: string) => {
+        if (this.pinDoc) {
+            this.pinDoc.markerType = iconKey;
+        }
+    }
+
 
     @observable
     destinationFeatures: any[] = []
@@ -248,7 +283,8 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         if (this.currentRouteInfoMap && this.selectedTransportationType && this.selectedDestinationFeature){
             const coordinates = this.currentRouteInfoMap[this.selectedTransportationType].coordinates;
             console.log(coordinates);
-            this.AddNewRouteToMap(coordinates, this.title ?? "", this.selectedDestinationFeature.place_name);
+            console.log(this.selectedDestinationFeature);
+            this.AddNewRouteToMap(coordinates, this.title ?? "", this.selectedDestinationFeature, this.createPinForDestination);
             this.HideRoute();
         }
     }
@@ -258,11 +294,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             const markerType = StrCast(this.pinDoc.markerType);
             const markerColor = StrCast(this.pinDoc.markerColor);
 
-            if (markerType.startsWith("MAPBOX")){
-                return MarkerIcons.getMapboxIcon(markerColor as MapboxColor);
-            } else { // font awesome icon
-                return MarkerIcons.getFontAwesomeIcon(markerType, markerColor);
-            }
+            return MarkerIcons.getFontAwesomeIcon(markerType, '2x', markerColor);
         }
         return undefined;
     }
@@ -313,7 +345,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                         />
                     </>
                 }
-                {this.menuType === 'route' && 
+                {this.menuType === 'routeCreation' && 
                     <>
                         <IconButton
                             tooltip="Go back" //
@@ -327,19 +359,39 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             icon={<FontAwesomeIcon icon={faAdd as IconLookup} />}
                             color={SettingsManager.userColor}
                         />
+                    </>
+                }
+                {this.menuType === 'route' && 
+                    <>
+                        <IconButton
+                            tooltip="Delete Route" //
+                            onPointerDown={this.Delete}
+                            icon={<FontAwesomeIcon icon="trash-alt" />}
+                            color={SettingsManager.userColor}
+                        />
                         <IconButton
                             tooltip='Animate route'
-                            onPointerDown={this.Delete} /**TODO: fix */
+                            onPointerDown={() => this.OpenAnimationPanel(this.routeDoc)} /**TODO: fix */
                             icon={<FontAwesomeIcon icon={faRoute as IconLookup}/>}
                             color={SettingsManager.userColor}
                         />
+                        <div ref={this._commentRef}>
+                            <IconButton
+                                tooltip="Link Note to Pin" //
+                                onPointerDown={this.notePointerDown}
+                                icon={<FontAwesomeIcon icon="sticky-note" />}
+                                color={SettingsManager.userColor}
+                            />
+                        </div>
                         <IconButton
                             tooltip='Add to calendar'
                             onPointerDown={this.Delete} /**TODO: fix */
                             icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup}/>}
                             color={SettingsManager.userColor}
                         />
+
                     </>
+
                 }
                 {this.menuType === 'customize' &&
                     <>
@@ -351,7 +403,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                         />
                         <IconButton
                             tooltip="Revert to original" //
-                            onPointerDown={this.BackClick}
+                            onPointerDown={() => this.revertToOriginalMarker()}
                             icon={<FontAwesomeIcon icon={faArrowsRotate as IconLookup} />}
                             color={SettingsManager.userColor}
                         />
@@ -386,7 +438,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                 {this.menuType === 'standard' &&
                     <div>{this.title}</div>
                 }
-                {this.menuType === 'route' && 
+                {this.menuType === 'routeCreation' && 
                     <div className='direction-inputs' style={{display: 'flex', flexDirection: 'column'}}>
                         <TextField
                             fullWidth
@@ -493,27 +545,27 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             circleSize={15}
                             circleSpacing={7}
                             width='100%'
-                            onChange={(color) => console.log(color.hex)}
+                            onChange={(color) => this.onMarkerColorChange(color)}
                         />
                     </div>
                     <div className='all-markers-container'>
-                         {Object.keys(MarkerIcons.FAMarkerIconsMap).map((iconKey) => {
-                            const icon = MarkerIcons.getFontAwesomeIcon(iconKey);
-                            if (icon){
-                                return (
-                                    <div key={iconKey} className='marker-icon'>
-                                        <IconButton
-                                            onPointerDown={() => {}}
-                                            icon={MarkerIcons.getFontAwesomeIcon(iconKey, 'white')}
-                                        />
-                                    </div>
-                                )
-                            }
-                            return null;
-                        })}
+                         {Object.keys(MarkerIcons.FAMarkerIconsMap).map((iconKey) => (
+                            <div key={iconKey} className='marker-icon'>
+                                <IconButton
+                                    onPointerDown={() => this.onMarkerIconChange(iconKey)}
+                                    icon={MarkerIcons.getFontAwesomeIcon(iconKey, '1x', 'white')}
+                                />
+                            </div>
+                        ))}
                     </div>
                     <div style={{width: '100%', height:'3px', color: 'white'}}></div>
                 </div>
+                }
+                {this.menuType === 'route' && this.routeDoc &&
+                    <div>
+                        {StrCast(this.routeDoc.title)}
+                    </div>
+                
                 }
                 {buttons}
             </div>
diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss
index bc2f90fbd..d3c6bb14e 100644
--- a/src/client/views/nodes/MapBox/MapBox.scss
+++ b/src/client/views/nodes/MapBox/MapBox.scss
@@ -82,6 +82,56 @@
 
     }
 
+    .animation-panel {
+        z-index: 900;
+        display: flex;
+        flex-direction: column;
+        justify-content: flex-start;
+        align-items: flex-start;
+        position: absolute;
+        background-color: rgb(187, 187, 187);
+        padding: 10px;
+        border-top-right-radius: 5px;
+        border-bottom-right-radius: 5px;
+        width: 100%;
+
+        #route-to-animate-title {
+            font-size: 1.25em;
+            font-weight: bold;
+        }
+
+        .route-animation-options {
+            display: flex;
+            justify-content: flex-start;
+            align-items: center;
+            gap: 7px;
+
+            .animation-suboptions{
+                display: flex;
+                justify-content: flex-start;
+                align-items: center;
+                gap: 7px;
+
+                label{
+                    margin-bottom: 0;
+                }
+    
+                .speed-label{
+                    margin-right: 5px;
+                }
+
+                #last-divider{
+                    margin-left: 10px;
+                    margin-right: 10px;
+                }
+            }
+
+            
+        }
+
+    }
+
+
     .mapBox-topbar {
         display: flex;
         flex-direction: row;
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index ac926e1fb..cde68a2e6 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -2,7 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import BingMapsReact from 'bingmaps-react';
 // import 'mapbox-gl/dist/mapbox-gl.css';
 
-import { Button, EditableText, IconButton, Type } from 'browndash-components';
+import { Button, EditableText, IconButton, Size, Type } from 'browndash-components';
 import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, flow, toJS} from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -51,14 +51,20 @@ import debounce from 'debounce';
 import './MapBox.scss';
 import { NumberLiteralType } from 'typescript';
 // import { GeocoderControl } from './GeocoderControl';
-import mapboxgl, { LngLat, MapLayerMouseEvent } from 'mapbox-gl';
+import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent, MercatorCoordinate } from 'mapbox-gl';
 import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, MultiLineString, Position } from 'geojson';
 import { MarkerEvent } from 'react-map-gl/dist/esm/types';
 import { MapboxApiUtility, TransportationType} from './MapboxApiUtility';
 import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material';
 import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
-import { IconLookup, faGear } from '@fortawesome/free-solid-svg-icons';
+import { IconLookup, faCircleXmark, faFileExport, faGear, faPause, faPlay, faRotate } from '@fortawesome/free-solid-svg-icons';
+import { MarkerIcons } from './MarkerIcons';
+import { SettingsManager } from '../../../util/SettingsManager';
+import * as turf from '@turf/turf';
+import * as d3 from "d3";
+import { AnimationSpeed, AnimationStatus, AnimationUtility } from './AnimationUtility';
+import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
 
 // amongus
 /**
@@ -142,25 +148,93 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @computed get allRoutes() {
         return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE);
     }
+    @computed get updatedRouteCoordinates(): Feature<Geometry, GeoJsonProperties> {
+        if (this.routeToAnimate?.routeCoordinates) {
+            const originalCoordinates: Position[] = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates));
+            // const index = Math.floor(this.animationPhase * originalCoordinates.length);
+            const index = this.animationPhase * (originalCoordinates.length - 1); // Calculate the fractional index
+            const startIndex = Math.floor(index);
+            const endIndex = Math.ceil(index);
+
+            if (startIndex === endIndex) {
+                // AnimationPhase is at a whole number (no interpolation needed)
+                const coordinates = [originalCoordinates[startIndex]];
+                const geometry: LineString = {
+                    type: 'LineString',
+                    coordinates,
+                };
+                return {
+                    type: 'Feature',
+                    properties: {
+                        'routeTitle': StrCast(this.routeToAnimate.title)
+                    },
+                    geometry: geometry,
+                };
+            } else {
+                // Interpolate between two coordinates
+                const startCoord = originalCoordinates[startIndex];
+                const endCoord = originalCoordinates[endIndex];
+                const fraction = index - startIndex;
+                
+                // Interpolate the coordinates
+                const interpolatedCoord = [
+                    startCoord[0] + fraction * (endCoord[0] - startCoord[0]),
+                    startCoord[1] + fraction * (endCoord[1] - startCoord[1]),
+                ];
+    
+                const coordinates = originalCoordinates.slice(0, startIndex + 1).concat([interpolatedCoord]);
+                
+                const geometry: LineString = {
+                    type: 'LineString',
+                    coordinates,
+                };
+                return {
+                    type: 'Feature',
+                    properties: {
+                        'routeTitle': StrCast(this.routeToAnimate.title)
+                    },
+                    geometry: geometry,
+                };
+            }
+        }
+        return {
+            type: 'Feature',
+            properties: {},
+            geometry: {
+                type: 'LineString',
+                coordinates: []
+            },
+        };
+    }
+    @computed get selectedRouteCoordinates(): Position[] {
+        let coordinates: Position[] = [];
+        if (this.routeToAnimate?.routeCoordinates){
+            coordinates = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates));
+        }
+        return coordinates;
+    }
+
     @computed get allRoutesGeoJson(): FeatureCollection {
-        const features: Feature<Geometry, GeoJsonProperties>[] = this.allRoutes.map(route => {
-            console.log("Route coords: ", route.coordinates);
+        const features: Feature<Geometry, GeoJsonProperties>[] = this.allRoutes.map((routeDoc: Doc) => {
+            console.log('Route coords: ', routeDoc.routeCoordinates);
             const geometry: LineString = {
                 type: 'LineString',
-                coordinates: JSON.parse(StrCast(route.coordinates))
-            }
+                coordinates: JSON.parse(StrCast(routeDoc.routeCoordinates)),
+            };
             return {
-              type: 'Feature',
-              properties: {},
-              geometry: geometry
+                type: 'Feature',
+                properties: {
+                    'routeTitle': routeDoc.title},
+                geometry: geometry,
             };
-          });
-        
-          return {
+        });
+
+        return {
             type: 'FeatureCollection',
-            features: features
-          };
+            features: features,
+        };
     }
+
     @computed get SidebarShown() {
         return this.layoutDoc._layout_showSidebar ? true : false;
     }
@@ -184,7 +258,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     _unmounting = false;
     componentWillUnmount(): void {
         this._unmounting = true;
-        this.deselectPin();
+        this.deselectPinOrRoute();
         this._rerenderTimeout && clearTimeout(this._rerenderTimeout);
         Object.keys(this._disposers).forEach(key => this._disposers[key]?.());
     }
@@ -199,7 +273,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
         const docs = doc instanceof Doc ? [doc] : doc;
         docs.forEach(doc => {
-            let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this.selectedPin;
+            let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this.selectedPinOrRoute;
             if (doc.latitude !== undefined && doc.longitude !== undefined && !existingPin) {
                 existingPin = this.createPushpin(NumCast(doc.latitude), NumCast(doc.longitude), StrCast(doc.map));
             }
@@ -300,10 +374,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
         const sourceAnchorCreator = action(() => {
             const note = this.getAnchor(true);
-            if (note && this.selectedPin) {
-                note.latitude = this.selectedPin.latitude;
-                note.longitude = this.selectedPin.longitude;
-                note.map = this.selectedPin.map;
+            if (note && this.selectedPinOrRoute) {
+                note.latitude = this.selectedPinOrRoute.latitude;
+                note.longitude = this.selectedPinOrRoute.longitude;
+                note.map = this.selectedPinOrRoute.map;
             }
             return note as Doc;
         });
@@ -329,10 +403,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const createFunc = undoable(
             action(() => {
                 const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', LinkedTo]);
-                if (note && this.selectedPin) {
-                    note.latitude = this.selectedPin.latitude;
-                    note.longitude = this.selectedPin.longitude;
-                    note.map = this.selectedPin.map;
+                if (note && this.selectedPinOrRoute) {
+                    note.latitude = this.selectedPinOrRoute.latitude;
+                    note.longitude = this.selectedPinOrRoute.longitude;
+                    note.map = this.selectedPinOrRoute.map;
                 }
             }),
             'create note annotation'
@@ -410,11 +484,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
 
     // The pin that is selected
-    @observable selectedPin: Doc | undefined;
+    @observable selectedPinOrRoute: Doc | undefined;
+
 
     @action
-    deselectPin = () => {
-        if (this.selectedPin) {
+    deselectPinOrRoute = () => {
+        if (this.selectedPinOrRoute) {
             // // Removes filter
             // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove');
             // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove');
@@ -435,6 +510,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     };
 
+
     getView = async (doc: Doc) => {
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar();
         return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
@@ -444,22 +520,22 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
      */
     @action
     pushpinClicked = (pinDoc: Doc) => {
-        this.deselectPin();
-        this.selectedPin = pinDoc;
+        this.deselectPinOrRoute();
+        this.selectedPinOrRoute = pinDoc;
         this.bingSearchBarContents = pinDoc.map;
 
         // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'match');
         // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'match');
-        Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPin)}`, 'check');
+        Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
 
-        this.recolorPin(this.selectedPin, 'green');
+        this.recolorPin(this.selectedPinOrRoute, 'green');
 
-        MapAnchorMenu.Instance.Delete = this.deleteSelectedPin;
+        MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute;
         MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
         MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
         MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
 
-        const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPin.latitude, this.selectedPin.longitude));
+        const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPinOrRoute.latitude, this.selectedPinOrRoute.longitude));
         const x = point.x + (this.props.PanelWidth() - this.sidebarWidth()) / 2;
         const y = point.y + this.props.PanelHeight() / 2 + 32;
         const cpt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
@@ -474,7 +550,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @action
     mapOnClick = (e: { location: { latitude: any; longitude: any } }) => {
         this.props.select(false);
-        this.deselectPin();
+        this.deselectPinOrRoute();
     };
     /*
      * Updates values of layout doc to match the current map
@@ -520,14 +596,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         /// this should use SELECTED pushpin for lat/long  if there is a selection, otherwise CENTER
         const anchor = Docs.Create.ConfigDocument({
             title: 'MapAnchor:' + this.rootDoc.title,
-            text: StrCast(this.selectedPin?.map) || StrCast(this.rootDoc.map) || 'map location',
-            config_latitude: NumCast((existingPin ?? this.selectedPin)?.latitude ?? this.dataDoc.latitude),
-            config_longitude: NumCast((existingPin ?? this.selectedPin)?.longitude ?? this.dataDoc.longitude),
+            text: StrCast(this.selectedPinOrRoute?.map) || StrCast(this.rootDoc.map) || 'map location',
+            config_latitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.latitude ?? this.dataDoc.latitude),
+            config_longitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.longitude ?? this.dataDoc.longitude),
             config_map_zoom: NumCast(this.dataDoc.map_zoom),
             config_map_type: StrCast(this.dataDoc.map_type),
-            config_map: StrCast((existingPin ?? this.selectedPin)?.map) || StrCast(this.dataDoc.map),
+            config_map: StrCast((existingPin ?? this.selectedPinOrRoute)?.map) || StrCast(this.dataDoc.map),
             layout_unrendered: true,
-            mapPin: existingPin ?? this.selectedPin,
+            mapPin: existingPin ?? this.selectedPinOrRoute,
             annotationOn: this.rootDoc,
         });
         if (anchor) {
@@ -566,7 +642,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
      *   Removes pin from annotations
      */
     @action
-    removePushpin = (pinDoc: Doc) => this.removeMapDocument(pinDoc, this.annotationKey);
+    removePushpinOrRoute = (pinOrRouteDoc: Doc) => this.removeMapDocument(pinOrRouteDoc, this.annotationKey);
 
     /*
      * Removes pushpin from map render
@@ -576,23 +652,25 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             this._bingMap.current.entities.remove(this.map_docToPinMap.get(pinDoc));
         }
         this.map_docToPinMap.delete(pinDoc);
-        this.selectedPin = undefined;
+        this.selectedPinOrRoute = undefined;
     };
 
     @action
-    deleteSelectedPin = undoable(() => {
-        if (this.selectedPin) {
+    deleteSelectedPinOrRoute = undoable(() => {
+        if (this.selectedPinOrRoute) {
             // Removes filter
-            Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove');
-            Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove');
-            Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
+            Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPinOrRoute.latitude, 'remove');
+            Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPinOrRoute.longitude, 'remove');
+            Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPinOrRoute))}`, 'remove');
 
-            this.removePushpin(this.selectedPin);
+            this.removePushpinOrRoute(this.selectedPinOrRoute);
         }
         MapAnchorMenu.Instance.fadeOut(true);
         document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
     }, 'delete pin');
 
+
+
     tryHideMapAnchorMenu = (e: PointerEvent) => {
         let target = document.elementFromPoint(e.x, e.y);
         while (target) {
@@ -608,9 +686,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     centerOnSelectedPin = () => {
-        if (this.selectedPin) {
+        if (this.selectedPinOrRoute) {
             this._mapRef.current?.flyTo({
-                center: [NumCast(this.selectedPin.longitude), NumCast(this.selectedPin.latitude)]
+                center: [NumCast(this.selectedPinOrRoute.longitude), NumCast(this.selectedPinOrRoute.latitude)]
             })
         }
         // if (this.selectedPin) {
@@ -776,12 +854,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             false,
             [],
             {
-                title: location ?? `lat=${latitude},lng=${longitude}`, 
+                title: location ?? `lat=${NumCast(latitude)},lng=${NumCast(longitude)}`, 
                 map: location, 
                 description: "", 
                 wikiData: wikiData,
-                markerType: 'MAPBOX_MARKER',
-                markerColor: '#3FB1CE'
+                markerType: 'MAP_PIN',
+                markerColor: '#ff5722'
             },
             // { title: map ?? `lat=${latitude},lng=${longitude}`, map: map },
             // ,'pushpinIDamongus'+ this.incrementer++
@@ -794,14 +872,16 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }, 'createpin');
 
     @action
-    createMapRoute = undoable((coordinates: Position[], origin: string, destination: string) => {
-        console.log(coordinates);
+    createMapRoute = undoable((coordinates: Position[], origin: string, destination: any, createPinForDestination: boolean) => {
         const mapRoute = Docs.Create.MapRouteDocument(
             false,
             [],
-            {title: `${origin} -> ${destination}`, routeCoordinates: JSON.stringify(coordinates)},
+            {title: `${origin} --> ${destination.place_name}`, routeCoordinates: JSON.stringify(coordinates)},
         );
         this.addDocument(mapRoute, this.annotationKey);
+        if (createPinForDestination) {
+            this.createPushpin(destination.center[1], destination.center[0], destination.place_name);
+        }
         return mapRoute;
 
         // mapMarker.infoWindowOpen = true;
@@ -853,10 +933,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
      */
     handleSearchChange = async (searchText: string) => {
         const features = await MapboxApiUtility.forwardGeocodeForFeatures(searchText);
-        if (features){
+        if (features && !this.isAnimating){
             runInAction(() => {
                 this.settingsOpen= false;
                 this.featuresFromGeocodeResults = features;
+                this.routeToAnimate = undefined;
             })
         }
         // try {
@@ -874,12 +955,65 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     // @action
     // debouncedCall = React.useCallback(debounce(this.debouncedOnSearchBarChange, 300), []);
 
+
+    @action
+    handleMapClick = (e: MapLayerMouseEvent) => {
+        if (this._mapRef.current){
+            const features = this._mapRef.current.queryRenderedFeatures(
+                e.point, {
+                    layers: ['map-routes-layer']
+                }
+            );
+
+            console.error(features);
+            if (features && features.length > 0 && features[0].properties && features[0].geometry) {
+                const geometry = features[0].geometry as LineString;
+                const routeTitle: string = features[0].properties['routeTitle'];
+                const routeDoc: Doc | undefined = this.allRoutes.find((routeDoc) => routeDoc.title === routeTitle);
+                this.deselectPinOrRoute(); // TODO: Also deselect route if selected
+                if (routeDoc){
+                    this.selectedPinOrRoute = routeDoc;
+                    Doc.setDocFilter(this.rootDoc, LinkedTo, `mapRoute=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
+
+                    // TODO: Recolor route 
+
+                    MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute;
+                    MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
+                    MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
+                    MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
+
+                    MapAnchorMenu.Instance.setRouteDoc(routeDoc);
+
+                    // TODO: Subject to change
+                    MapAnchorMenu.Instance.setAllMapboxPins(
+                        this.allAnnotations.filter(anno => !anno.layout_unrendered)
+                    )
+
+                    MapAnchorMenu.Instance.DisplayRoute = this.displayRoute;
+                    MapAnchorMenu.Instance.HideRoute = this.hideRoute;
+                    MapAnchorMenu.Instance.AddNewRouteToMap = this.createMapRoute;
+                    MapAnchorMenu.Instance.CreatePin = this.addMarkerForFeature;
+                    MapAnchorMenu.Instance.OpenAnimationPanel = this.openAnimationPanel;
+
+                    // this.selectedRouteCoordinates = geometry.coordinates;
+
+                    MapAnchorMenu.Instance.setMenuType('route');
+
+                    MapAnchorMenu.Instance.jumpTo(e.originalEvent.clientX, e.originalEvent.clientY, true);
+
+                    document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
+                }        
+            }
+        }
+    }
+
+
     /**
      * Makes a reverse geocoding API call to retrieve features corresponding to a map click (based on longitude
      * and latitude). Sets the search results accordingly. 
      * @param e 
      */
-    handleMapClick = async (e: MapLayerMouseEvent) => {
+    handleMapDblClick = async (e: MapLayerMouseEvent) => {
         e.preventDefault();
         const lngLat: LngLat = e.lngLat;
         const longitude: number = lngLat.lng;
@@ -914,18 +1048,18 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @action
     handleMarkerClick = (e: MarkerEvent<mapboxgl.Marker, MouseEvent>, pinDoc: Doc) => {
         this.featuresFromGeocodeResults = [];
-        this.deselectPin(); // TODO: check this method
-        this.selectedPin = pinDoc;
+        this.deselectPinOrRoute(); // TODO: check this method
+        this.selectedPinOrRoute = pinDoc;
         // this.bingSearchBarContents = pinDoc.map;
 
         // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'match');
         // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'match');
-        Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPin)}`, 'check');
+        Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
 
-        this.recolorPin(this.selectedPin, 'green'); // TODO: check this method
+        this.recolorPin(this.selectedPinOrRoute, 'green'); // TODO: check this method
        
         
-        MapAnchorMenu.Instance.Delete = this.deleteSelectedPin;
+        MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute;
         MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
         MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
         MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
@@ -941,11 +1075,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         MapAnchorMenu.Instance.AddNewRouteToMap = this.createMapRoute;
         MapAnchorMenu.Instance.CreatePin = this.addMarkerForFeature;
 
-        // const longitude = NumCast(pinDoc.longitude);
-        // const latitude = NumCast(pinDoc.longitude);
-        // const x = longitude + (this.props.PanelWidth() - this.sidebarWidth()) / 2;
-        // const y = latitude + this.props.PanelHeight() / 2 + 20;
-        // const cpt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+        MapAnchorMenu.Instance.setMenuType('standard');
+
         MapAnchorMenu.Instance.jumpTo(e.originalEvent.clientX, e.originalEvent.clientY, true);
 
         document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
@@ -979,6 +1110,351 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     }
 
+    @observable
+    isAnimating: boolean = false;
+
+    @observable
+    isPaused: boolean = false;
+
+    @observable
+    routeToAnimate: Doc | undefined = undefined;
+
+    @observable 
+    animationPhase: number = 0;
+
+    @observable 
+    finishedFlyTo: boolean = false;
+
+    @action
+    setAnimationPhase = (newValue: number) => {
+        this.animationPhase = newValue;
+    };
+
+    @observable 
+    frameId: number | null = null;
+
+    @action
+    setFrameId = (frameId: number) => {
+        this.frameId = frameId;
+    }
+
+    @action
+    openAnimationPanel = (routeDoc: Doc | undefined) => {
+        if (routeDoc){
+            MapAnchorMenu.Instance.fadeOut(true);
+            document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
+            this.routeToAnimate = routeDoc;
+        }
+    }
+
+    @observable
+    animationDuration = 40000;
+
+    @observable
+    animationAltitude = 12800;
+
+    @observable
+    pathDistance = 0;
+
+    @observable 
+    isStreetViewAnimation: boolean = false;
+
+    @observable 
+    animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM;
+
+    @action
+    updateAnimationSpeed = () => {
+        switch (this.animationSpeed){
+            case AnimationSpeed.SLOW:
+                this.animationSpeed = AnimationSpeed.MEDIUM;
+                break;
+            case AnimationSpeed.MEDIUM:
+                this.animationSpeed = AnimationSpeed.FAST;
+                break;
+            case AnimationSpeed.FAST:
+                this.animationSpeed = AnimationSpeed.SLOW;
+                break;
+            default:
+                this.animationSpeed = AnimationSpeed.MEDIUM;
+                break;
+        }
+    }
+    @computed get animationSpeedTooltipText(): string {
+        switch (this.animationSpeed) {
+          case AnimationSpeed.SLOW:
+            return '1x speed';
+          case AnimationSpeed.MEDIUM:
+            return '2x speed';
+          case AnimationSpeed.FAST:
+            return '3x speed';
+          default:
+            return '2x speed'; 
+        }
+    }
+    @computed get animationSpeedIcon(): JSX.Element{
+        switch (this.animationSpeed) {
+            case AnimationSpeed.SLOW:
+              return slowSpeedIcon;
+            case AnimationSpeed.MEDIUM:
+              return mediumSpeedIcon;
+            case AnimationSpeed.FAST:
+              return fastSpeedIcon;
+            default:
+              return mediumSpeedIcon; 
+          }
+    }
+
+    @action
+    toggleIsStreetViewAnimation = () => {
+        this.isStreetViewAnimation = !this.isStreetViewAnimation;
+    }
+
+    @observable 
+    dynamicRouteFeature: Feature<Geometry, GeoJsonProperties> = {
+        type: 'Feature',
+        properties: {},
+        geometry: {
+            type: 'LineString',
+            coordinates: []
+        }
+    };
+
+
+    @observable 
+    path: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties> = {
+        type: 'Feature',
+        geometry: {
+            type: 'LineString',
+            coordinates: []
+        },
+        properties: {}
+    };
+
+    getFeatureFromRouteDoc = (routeDoc: Doc): Feature<Geometry, GeoJsonProperties> => {
+        const geometry: LineString = {
+            type: 'LineString',
+            coordinates: JSON.parse(StrCast(routeDoc.routeCoordinates)),
+        };
+        return {
+            type: 'Feature',
+            properties: {
+                'routeTitle': routeDoc.title},
+            geometry: geometry,
+        };
+    }
+
+    @action 
+    playAnimation = (status: AnimationStatus) => {
+        if (!this._mapRef.current || !this.routeToAnimate){
+            return;
+        }
+
+        if (this.isAnimating){
+            return;
+        }
+        this.animationPhase = status === AnimationStatus.RESUME ? this.animationPhase : 0;
+        this.frameId = AnimationStatus.RESUME ? this.frameId : null;
+        this.finishedFlyTo = AnimationStatus.RESUME ? this.finishedFlyTo : false;
+
+        const path = turf.lineString(this.selectedRouteCoordinates);
+        
+        this.settingsOpen = false;
+        this.path = path;
+        this.pathDistance = turf.lineDistance(path);
+        this.isAnimating = true;
+        runInAction(() => {
+            return new Promise<void>(async (resolve) => {
+                let animationUtil;
+                try {
+                const targetLngLat = {
+                    lng: this.selectedRouteCoordinates[0][0],
+                    lat: this.selectedRouteCoordinates[0][1],
+                };
+
+                animationUtil = new AnimationUtility(
+                    targetLngLat,
+                    this.selectedRouteCoordinates,
+                    this.isStreetViewAnimation,
+                    this.animationSpeed
+                );
+
+
+                const updateFrameId = (newFrameId: number) => {
+                    this.setFrameId(newFrameId);
+                }
+
+                const updateAnimationPhase = (
+                    newAnimationPhase: number,
+                    ) => {
+                    this.setAnimationPhase(newAnimationPhase);
+                };
+                
+                if (status !== AnimationStatus.RESUME) {
+
+                    const result = await animationUtil.flyInAndRotate({
+                        map: this._mapRef.current!,
+                        // targetLngLat,
+                        // duration 3000
+                        // startAltitude: 3000000,
+                        // endAltitude: this.isStreetViewAnimation ? 80 : 12000,
+                        // startBearing: 0,
+                        // endBearing: -20,
+                        // startPitch: 40,
+                        // endPitch: this.isStreetViewAnimation ? 80 : 50,
+                        updateFrameId,
+                    });
+
+                    console.log("Bearing: ", result.bearing);
+                    console.log("Altitude: ", result.altitude);
+
+                } 
+
+                runInAction(() => {
+                    this.finishedFlyTo = true;
+                })
+
+                // follow the path while slowly rotating the camera, passing in the camera bearing and altitude from the previous animation
+                await animationUtil.animatePath({
+                    map: this._mapRef.current!, 
+                    // path: this.path,
+                    // startBearing: -20,
+                    // startAltitude: this.isStreetViewAnimation ? 80 : 12000,
+                    // pitch: this.isStreetViewAnimation ? 80: 50,
+                    currentAnimationPhase: this.animationPhase,
+                    updateAnimationPhase,
+                    updateFrameId,
+                });
+            
+                // get the bounds of the linestring, use fitBounds() to animate to a final view
+                const bbox3d = turf.bbox(this.path);
+    
+                const bbox2d: LngLatBoundsLike = [bbox3d[0], bbox3d[1], bbox3d[2], bbox3d[3]];
+    
+                this._mapRef.current!.fitBounds(bbox2d, {
+                    duration: 3000,
+                    pitch: 30,
+                    bearing: 0,
+                    padding: 120,
+                });
+            
+                setTimeout(() => {
+                    resolve();
+                }, 10000);
+                } catch (error: any){
+                    console.log(error);
+                    console.log('animation util: ', animationUtil);
+                }});
+            
+            })
+            
+
+    }
+
+
+    @action 
+    pauseAnimation = () => {
+        if (this.frameId && this.animationPhase > 0){
+            window.cancelAnimationFrame(this.frameId);
+            this.frameId = null;
+            this.isAnimating = false;
+        }
+    }
+
+    @action
+    stopAndCloseAnimation = () => {
+        if (this.frameId){
+            window.cancelAnimationFrame(this.frameId);
+            this.frameId = null;
+            this.finishedFlyTo = false;
+            this.isAnimating = false;
+            this.animationPhase = 0;
+            this.routeToAnimate = undefined;
+            // this.selectedRouteCoordinates = [];
+        }
+        // reset bearing and pitch to original, zoom out  
+    }
+
+    @action
+    exportAnimationToVideo = () => {
+
+    }
+
+    getRouteAnimationOptions = (): JSX.Element => {
+        return (
+            <>
+                <IconButton 
+                    tooltip={this.isAnimating && this.finishedFlyTo ? 'Pause Animation' : 'Play Animation'}
+                    onPointerDown={() => {
+                        if (this.isAnimating && this.finishedFlyTo) {
+                            this.pauseAnimation();
+                        } else if (this.animationPhase > 0) {
+                            this.playAnimation(AnimationStatus.RESUME); // Resume from the current phase
+                        } else {
+                            this.playAnimation(AnimationStatus.START); // Play from the beginning
+                        }
+                    }}
+                    icon={this.isAnimating && this.finishedFlyTo ? 
+                        <FontAwesomeIcon icon={faPause as IconLookup}/>  
+                        :
+                        <FontAwesomeIcon icon={faPlay as IconLookup}/>    
+                    }
+                    color='black'
+                    size={Size.MEDIUM}
+                />
+                {this.isAnimating && this.finishedFlyTo && 
+                <IconButton 
+                    tooltip='Restart animation'
+                    onPointerDown={() => this.playAnimation(AnimationStatus.RESTART)}
+                    icon={<FontAwesomeIcon icon={faRotate as IconLookup}/>}
+                    color='black'
+                    size={Size.MEDIUM}
+                />
+                    
+                }
+                <IconButton 
+                    tooltip='Stop and close animation'
+                    onPointerDown={this.stopAndCloseAnimation}
+                    icon={<FontAwesomeIcon icon={faCircleXmark as IconLookup}/>}
+                    color='black'
+                    size={Size.MEDIUM}
+                />
+                <IconButton 
+                    style={{marginRight: '10px'}}
+                    tooltip='Export to video'
+                    onPointerDown={this.exportAnimationToVideo}
+                    icon={<FontAwesomeIcon icon={faFileExport as IconLookup}/>}
+                    color='black'
+                    size={Size.MEDIUM}
+                />
+                {!this.isAnimating &&
+                    <>
+                        <div className='animation-suboptions'>
+                            <div>|</div>
+                            <FormControlLabel
+                                label='Street view animation'
+                                labelPlacement='start'
+                                control={
+                                    <Checkbox 
+                                        color='success'
+                                        checked={this.isStreetViewAnimation}
+                                        onChange={this.toggleIsStreetViewAnimation}
+                                    />
+                                }
+                            />
+                            <div id='last-divider'>|</div>
+                            <IconButton
+                                tooltip={this.animationSpeedTooltipText}
+                                onPointerDown={this.updateAnimationSpeed}
+                                icon={this.animationSpeedIcon}
+                                size={Size.MEDIUM}
+                            />
+                        </div>
+                    </>
+                }
+            </>
+        )
+    }
+
     @action
     hideRoute = () => {
         this.temporaryRouteSource = {
@@ -1015,8 +1491,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     toggleSettings = () => {
-        this.featuresFromGeocodeResults = [];
-        this.settingsOpen = !this.settingsOpen;
+        if (!this.isAnimating && this.animationPhase == 0) {
+            this.featuresFromGeocodeResults = [];
+            this.settingsOpen = !this.settingsOpen;
+        }
     }
 
     @action
@@ -1056,6 +1534,16 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     }
 
+    getMarkerIcon = (pinDoc: Doc): JSX.Element | null => {
+        const markerType = StrCast(pinDoc.markerType);
+        const markerColor = StrCast(pinDoc.markerColor);
+
+        return MarkerIcons.getFontAwesomeIcon(markerType, '2x', markerColor) ?? null;
+        
+    }
+
+
+
 
 
 
@@ -1092,21 +1580,22 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
                     {renderAnnotations(this.opaqueFilter)}
                     {SnappingManager.GetIsDragging() ? null : renderAnnotations()}
+                    {!this.routeToAnimate &&
+                        <div className="mapBox-searchbar">
+                            <TextField 
+                                fullWidth
+                                placeholder='Enter a location'
+                                onChange={(e) => this.handleSearchChange(e.target.value)}
+                            />
+                            <IconButton
+                                icon={<FontAwesomeIcon icon={faGear as IconLookup} size='1x'/>}
+                                type={Type.TERT}
+                                onClick={(e) => this.toggleSettings()}
 
-                    <div className="mapBox-searchbar">
-                        <TextField 
-                            fullWidth
-                            placeholder='Enter a location'
-                            onChange={(e) => this.handleSearchChange(e.target.value)}
-                        />
-                        <IconButton
-                            icon={<FontAwesomeIcon icon={faGear as IconLookup} size='1x'/>}
-                            type={Type.TERT}
-                            onClick={(e) => this.toggleSettings()}
-
-                        />
-                    </div> 
-                    {this.settingsOpen &&
+                            />
+                        </div> 
+                    }
+                    {this.settingsOpen && !this.routeToAnimate && 
                         <div className='mapbox-settings-panel' style={{right: `${0+ this.sidebarWidth()}px`}}>
                             <div className='mapbox-style-select'>
                                 <div>
@@ -1151,45 +1640,52 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             </div>
                         </div>
                     }
-                
-                    <div className='mapbox-geocoding-search-results'>
-                        {this.featuresFromGeocodeResults.length > 0 && (
+                    {this.routeToAnimate &&
+                        <div className='animation-panel'>
+                            <div id='route-to-animate-title'>
+                                {StrCast(this.routeToAnimate.title)}
+                            </div>
+                            <div className='route-animation-options'>
+                                {this.getRouteAnimationOptions()}
+                            </div>
+                        </div>
+                    }
+                    {this.featuresFromGeocodeResults.length > 0 && (
+                        <div className='mapbox-geocoding-search-results'>
                             <React.Fragment>
-                            <h4>Choose a location for your pin: </h4>
-                            {this.featuresFromGeocodeResults
-                                .filter(feature => feature.place_name)
-                                .map((feature, idx) => (
-                                <div 
-                                    key={idx} 
-                                    className='search-result-container'
-                                    onClick={() => {
-                                        this.handleSearchChange("");
-                                        this.addMarkerForFeature(feature);
-                                    }}
-                                >
-                                    <div className='search-result-place-name'> 
-                                    {feature.place_name}
+                                <h4>Choose a location for your pin: </h4>
+                                {this.featuresFromGeocodeResults
+                                    .filter(feature => feature.place_name)
+                                    .map((feature, idx) => (
+                                    <div 
+                                        key={idx} 
+                                        className='search-result-container'
+                                        onClick={() => {
+                                            this.handleSearchChange("");
+                                            this.addMarkerForFeature(feature);
+                                        }}
+                                    >
+                                        <div className='search-result-place-name'> 
+                                        {feature.place_name}
+                                        </div>
                                     </div>
-                                </div>
                                 ))}
                             </React.Fragment>
-                        )}
-                    </div>              
+                            
+                        </div>   
+                    )}           
                     <MapProvider>
                         <MapboxMap
                             ref={this._mapRef}
-                            initialViewState={{
-                                longitude: -100,
-                                latitude: 40,
-                                zoom: 3.5
-                              }}
                             mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
                             id="mapbox-map"
                             mapStyle={this.mapStyle}
                             style={{height: '100%', width: '100%'}}
-                            {...this.mapboxMapViewState}
+                            initialViewState={this.isAnimating ? undefined : this.mapboxMapViewState}
+                            // {...this.mapboxMapViewState}
                             onMove={this.onMapMove}
-                            onDblClick={this.handleMapClick}
+                            onClick={this.handleMapClick}
+                            onDblClick={this.handleMapDblClick}
                             terrain={this.showTerrain ? { source: 'mapbox-dem', exaggeration: 2.0 } : undefined}
                             
                             
@@ -1210,6 +1706,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                 layout={{"line-join": "round", "line-cap": "round"}}
                                 paint={{"line-color": "#36454F", "line-width": 4, "line-dasharray": [1,1]}}
                                 />
+                            {!this.isAnimating && this.animationPhase == 0 &&
                             <Layer
                                 id='map-routes-layer'
                                 type='line'
@@ -1217,9 +1714,60 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                 layout={{"line-join": "round", "line-cap": "round"}}
                                 paint={{"line-color": "#FF0000", "line-width": 4}}
                                 />
+                            }
+                            {this.routeToAnimate && (this.isAnimating || this.animationPhase > 0) &&
+                                <>
+                                    {!this.isStreetViewAnimation &&
+                                        <>
+                                            <Source id='animated-route' type='geojson' data={this.updatedRouteCoordinates}/>
+                                            <Layer 
+                                            id='dynamic-animation-line' 
+                                            type='line' 
+                                            source='animated-route'
+                                            paint={{
+                                                'line-color': 'yellow',
+                                                'line-width': 4,
+                                            }}
+                                            />
+                                        </>
+                                        }
+                                    <Source id='start-pin-base' type='geojson' data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.04)}/>
+                                    <Source id='start-pin-top' type='geojson' data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.25)}/>
+                                    <Source id='end-pin-base' type='geojson' data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.04)}/>
+                                    <Source id='end-pin-top' type='geojson' data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.25)}/>
+                                    <Layer id='start-fill-pin-base' type='fill-extrusion' source='start-pin-base'
+                                        paint={{
+                                            'fill-extrusion-color': '#0bfc03',
+                                            'fill-extrusion-height': 1000
+                                        }}
+                                    />
+                                    <Layer id='start-fill-pin-top' type='fill-extrusion' source='start-pin-top'
+                                        paint={{
+                                            'fill-extrusion-color': '#0bfc03',
+                                            'fill-extrusion-base': 1000,
+                                            'fill-extrusion-height': 1200
+                                        }}
+                                    />
+                                    <Layer id='end-fill-pin-base' type='fill-extrusion' source='end-pin-base'
+                                        paint={{
+                                            'fill-extrusion-color': '#eb1c1c',
+                                            'fill-extrusion-height': 1000
+                                        }}
+                                    />
+                                    <Layer id='end-fill-pin-top' type='fill-extrusion' source='end-pin-top'
+                                        paint={{
+                                            'fill-extrusion-color': '#eb1c1c',
+                                            'fill-extrusion-base': 1000,
+                                            'fill-extrusion-height': 1200
+                                        }}
+                                    />
+                                    
+                                </>
+                            }
+                            
 
                             <>
-                                {this.allPushpins
+                                {!this.isAnimating && this.animationPhase == 0 && this.allPushpins
                                     // .filter(anno => !anno.layout_unrendered)
                                     .map((pushpin, idx) => (
                                         <Marker 
@@ -1228,7 +1776,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                             latitude={NumCast(pushpin.latitude)}
                                             anchor='bottom'
                                             onClick={(e: MarkerEvent<mapboxgl.Marker, MouseEvent>) => this.handleMarkerClick(e, pushpin)}
-                                        />
+                                        >
+                                            {this.getMarkerIcon(pushpin)}
+                                        </Marker>
                                 ))}
                             </>
                             
diff --git a/src/client/views/nodes/MapBox/MarkerIcons.tsx b/src/client/views/nodes/MapBox/MarkerIcons.tsx
index cf50109ac..146f296c1 100644
--- a/src/client/views/nodes/MapBox/MarkerIcons.tsx
+++ b/src/client/views/nodes/MapBox/MarkerIcons.tsx
@@ -1,57 +1,46 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { faShopify } from '@fortawesome/free-brands-svg-icons';
-import { IconLookup, faBasketball, faBicycle, faBowlFood, faBus, faCameraRetro, faCar, faCartShopping, faFilm, faFootball, faFutbol, faHockeyPuck, faHospital, faHotel, faHouse, faLandmark, faMasksTheater, faMugSaucer, faPersonHiking, faPlane, faSchool, faShirt, faShop, faSquareParking, faStar, faTrainSubway, faTree, faUtensils, faVolleyball } from '@fortawesome/free-solid-svg-icons';
+import { faBasketball, faBicycle, faBowlFood, faBus, faCameraRetro, faCar, faCartShopping, faFilm, faFootball, faFutbol, faHockeyPuck, faHospital, faHotel, faHouse, faLandmark, faLocationDot, faLocationPin, faMapPin, faMasksTheater, faMugSaucer, faPersonHiking, faPlane, faSchool, faShirt, faShop, faSquareParking, faStar, faTrainSubway, faTree, faUtensils, faVolleyball } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import React = require('react');
 
-export type MapboxColor = 'yellow' | 'red' | 'orange' | 'purple' | 'pink' | 'blue' | 'green';
-type ColorProperties = {
-    fill: string,
-    stroke: string
-}
-type ColorsMap = {
-    [key in MapboxColor]: ColorProperties;
-}
-
 export class MarkerIcons {
 
-    static getMapboxIcon = (color: string) => {
-        return (
-            <svg xmlns="http://www.w3.org/2000/svg" id="marker" data-name="marker" width="20" height="48" viewBox="0 0 20 35">
-            <g id="mapbox-marker-icon">
-                <g id="icon">
-                <ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" />
-                <g id="mask" opacity="0.3">
-                    <g id="group">
-                    <path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fillRule="evenodd"/>
-                    </g>
-                </g>
-                <path id="color" fill={color} strokeWidth="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
-                <path id="circle" fill="#fff" stroke='white' strokeWidth="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
-                </g>
-            </g>
-            <rect width="20" height="48" fill="none"/>
-        </svg>
-        )
-    }
+    // static getMapboxIcon = (color: string) => {
+    //     return (
+    //         <svg xmlns="http://www.w3.org/2000/svg" id="marker" data-name="marker" width="20" height="48" viewBox="0 0 20 35">
+    //         <g id="mapbox-marker-icon">
+    //             <g id="icon">
+    //             <ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" />
+    //             <g id="mask" opacity="0.3">
+    //                 <g id="group">
+    //                 <path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fillRule="evenodd"/>
+    //                 </g>
+    //             </g>
+    //             <path id="color" fill={color} strokeWidth="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
+    //             <path id="circle" fill="#fff" stroke='white' strokeWidth="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
+    //             </g>
+    //         </g>
+    //         <rect width="20" height="48" fill="none"/>
+    //     </svg>
+    //     )
+    // }
 
-    static getFontAwesomeIcon(key: string, color?: string): JSX.Element | undefined {
+    static getFontAwesomeIcon(key: string, size: string, color?: string): JSX.Element {
         const icon: IconProp = MarkerIcons.FAMarkerIconsMap[key];
+        const iconProps: any = { icon };
 
-        if (icon) {
-            const iconProps: any = { icon };
-        
-            if (color) {
-              iconProps.color = color;
-            }
-        
-            return (<FontAwesomeIcon {...iconProps} size='1x' />);
-          }
+        if (color) {
+            iconProps.color = color;
+        }
+
+        return (<FontAwesomeIcon {...iconProps} size={size} />);
+          
         
-        return undefined;
     }
 
     static FAMarkerIconsMap: {[key: string]: IconProp} = {
+        'MAP_PIN': faLocationDot,
         'RESTAURANT_ICON': faUtensils,
         'HOTEL_ICON': faHotel,
         'HOUSE_ICON': faHouse,
-- 
cgit v1.2.3-70-g09d2


From 1dd4bbb41fedc6e0572cc1ead0dfc16db95d717a Mon Sep 17 00:00:00 2001
From: zaultavangar <zaul_tavangar@brown.edu>
Date: Thu, 14 Dec 2023 22:27:56 -0500
Subject: finished animation, general debugging

---
 package-lock.json                                 |  10 +
 package.json                                      |   2 +
 src/client/documents/Documents.ts                 |   6 +-
 src/client/views/nodes/MapBox/AnimationUtility.ts | 147 +++++++--
 src/client/views/nodes/MapBox/MapBox.scss         |  23 +-
 src/client/views/nodes/MapBox/MapBox.tsx          | 384 ++++++++++++++--------
 6 files changed, 409 insertions(+), 163 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 311eb9281..b0748aae6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17010,6 +17010,11 @@
             "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz",
             "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw=="
         },
+        "h264-mp4-encoder": {
+            "version": "1.0.12",
+            "resolved": "https://registry.npmjs.org/h264-mp4-encoder/-/h264-mp4-encoder-1.0.12.tgz",
+            "integrity": "sha512-xih3J+Go0o1RqGjhOt6TwXLWWGqLONRPyS8yoMu/RoS/S8WyEv4HuHp1KBsDDl8srZQ3gw9f95JYkCSjCuZbHQ=="
+        },
         "handle-thing": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
@@ -32370,6 +32375,11 @@
                 "loose-envify": "^1.0.0"
             }
         },
+        "wasm-feature-detect": {
+            "version": "1.6.1",
+            "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.6.1.tgz",
+            "integrity": "sha512-R1i9ED8UlLu/foILNB1ck9XS63vdtqU/tP1MCugVekETp/ySCrBZRk5I/zI67cI1wlQYeSonNm1PLjDHZDNg6g=="
+        },
         "watchpack": {
             "version": "2.4.0",
             "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
diff --git a/package.json b/package.json
index 9a9e7675d..5d53a6c4e 100644
--- a/package.json
+++ b/package.json
@@ -228,6 +228,7 @@
         "googleapis": "^40.0.0",
         "googlephotos": "^0.2.5",
         "got": "^12.0.1",
+        "h264-mp4-encoder": "^1.0.12",
         "howler": "^2.2.3",
         "html-to-image": "^0.1.3",
         "html-to-text": "^5.1.1",
@@ -341,6 +342,7 @@
         "util": "^0.12.4",
         "uuid": "^3.4.0",
         "valid-url": "^1.0.9",
+        "wasm-feature-detect": "^1.6.1",
         "web-request": "^1.0.7",
         "webpack-cli": "^4.10.0",
         "webpack-dev-middleware": "^5.3.1",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 3db9f4f06..f5f140ae9 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -176,12 +176,16 @@ export class DocumentOptions {
     _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units");
     latitude?: NUMt = new NumInfo('latitude coordinate for map views', false);
     longitude?: NUMt = new NumInfo('longitude coordinate for map views', false);
-    routeCoordinates?: STRt = new StrInfo("stores a route's/direction's coordinates (stringified version)"); // for a route document, this stores the route's coordiantes
+    routeCoordinates?: STRt = new StrInfo("stores a route's/direction's coordinates (stringified version)"); // for a route document, this stores the route's coordinates
     markerType?: STRt = new StrInfo('Defines the marker type for a pushpin document');
     markerColor?: STRt= new StrInfo('Defines the marker color for a pushpin document');
     map?: STRt = new StrInfo('text location of map');
     map_type?: STRt = new StrInfo('type of map view', false);
     map_zoom?: NUMt = new NumInfo('zoom of a map view', false);
+    map_pitch?: NUMt = new NumInfo('pitch of a map view', false);
+    map_bearing?: NUMt = new NumInfo('bearing of a map view', false);
+    map_style?: STRt = new StrInfo('mapbox style for a map view', false);
+
     wikiData?: STRt = new StrInfo('WikiData ID related to map location');
     description?: STRt = new StrInfo('A description of the document');
     _timecodeToShow?: NUMt = new NumInfo('the time that a document should be displayed (e.g., when an annotation shows up as a video plays)', false);
diff --git a/src/client/views/nodes/MapBox/AnimationUtility.ts b/src/client/views/nodes/MapBox/AnimationUtility.ts
index 256acbf13..11b335a96 100644
--- a/src/client/views/nodes/MapBox/AnimationUtility.ts
+++ b/src/client/views/nodes/MapBox/AnimationUtility.ts
@@ -5,9 +5,8 @@ import * as React from 'react';
 import * as d3 from "d3";
 import * as turf from '@turf/turf';
 import { Position } from "@turf/turf";
-import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from "geojson";
-import { observer } from "mobx-react";
-import { action, computed, observable } from "mobx";
+import { Feature, GeoJsonProperties, Geometry } from "geojson";
+import { action, computed, observable, runInAction } from "mobx";
 
 export enum AnimationStatus {
     START = 'start',
@@ -21,23 +20,32 @@ export enum AnimationSpeed {
     FAST = '3x',
 }
 
-@observer
 export class AnimationUtility {
     private SMOOTH_FACTOR = 0.95
     private ROUTE_COORDINATES: Position[] = []; 
+
+    @observable
     private PATH: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties>;
+    
+    private PATH_DISTANCE: number;
     private FLY_IN_START_PITCH = 40;
     private FIRST_LNG_LAT: {lng: number, lat: number};
     private START_ALTITUDE = 3_000_000;
+    private MAP_REF: MapRef | null;
 
     @observable private isStreetViewAnimation: boolean;
     @observable private animationSpeed: AnimationSpeed;
 
-    
+    @observable
     private previousLngLat: {lng: number, lat: number};
 
+    private previousAltitude: number | null = null;
+    private previousPitch: number | null = null;
+    
     private currentStreetViewBearing: number = 0;
 
+    private terrainDisplayed: boolean;
+
     @computed get flyInEndBearing() {
         return this.isStreetViewAnimation ? 
             this.calculateBearing(
@@ -51,38 +59,103 @@ export class AnimationUtility {
                 }
             )
             : -20;
+    }
+
+    @computed get currentAnimationAltitude(): number {
+        if (!this.isStreetViewAnimation) return 20_000;
+        if (!this.terrainDisplayed) return 50;
+        const coords: mapboxgl.LngLatLike = [this.previousLngLat.lng, this.previousLngLat.lat];
+        // console.log('MAP REF: ', this.MAP_REF)
+        // console.log("current elevation: ", this.MAP_REF?.queryTerrainElevation(coords));
+        let altitude = (this.MAP_REF ? (this.MAP_REF.queryTerrainElevation(coords) ?? 0) : 0);
+        if (altitude === 0){
+            altitude+=50;
         }
+        if (this.previousAltitude){
+            return this.lerp(altitude, this.previousAltitude, 0.02);
+        }
+        return altitude;
+        
+    }
 
     @computed get flyInStartBearing() {
         return Math.max(0, Math.min(this.flyInEndBearing + 20, 360)); // between 0 and 360
     }
 
     @computed get flyInEndAltitude() {
-        return this.isStreetViewAnimation ? 70 : 10000;
+        // return this.isStreetViewAnimation ? (this.currentAnimationAltitude + 70 ): 10_000;
+        return this.currentAnimationAltitude;
+    }
+
+    @computed get currentPitch(): number {
+        if (!this.isStreetViewAnimation) return 50;
+        if (!this.terrainDisplayed) return 80;
+        else {
+            // const groundElevation = 0;
+            const heightAboveGround = this.currentAnimationAltitude;
+            const horizontalDistance = 500;
+
+            let pitch;
+            if (heightAboveGround >= 0){
+                pitch = (90- Math.atan(heightAboveGround/horizontalDistance) * (180/Math.PI));
+            }
+            else {
+                pitch = 80;
+            }
+
+            console.log(Math.max(50, Math.min(pitch, 85)));
+
+            if (this.previousPitch){
+                return this.lerp(Math.max(50, Math.min(pitch, 85)), this.previousPitch, 0.02);
+            }
+            return Math.max(50, Math.min(pitch, 85));
+        }
     }
 
     @computed get flyInEndPitch() {
-        return this.isStreetViewAnimation ? 80 : 50;
+        return this.currentPitch;
     }
 
     @computed get flyToDuration() {
         switch (this.animationSpeed) {
             case AnimationSpeed.SLOW:
-                return 4000;
+                return 4_000;
             case AnimationSpeed.MEDIUM:
-                return 2500;
+                return 2_500;
             case AnimationSpeed.FAST:
-                return 1250;
+                return 1_250;
             default:
-                return 2500;
+                return 2_500;
         }
     }
 
     @computed get animationDuration(): number {
-        return 20_000;
-        // compute path distance for a standard speed 
-        // get animation speed 
-        // get isStreetViewAnimation (should be slower if so)
+        let scalingFactor: number;
+        const MIN_DISTANCE = 0;
+        const MAX_DISTANCE = 3_000; // anything greater than 3000 is just set to 1 when normalized
+        const MAX_DURATION = this.isStreetViewAnimation ? 120_000 : 60_000;
+
+        const normalizedDistance = Math.min(1, (this.PATH_DISTANCE - MIN_DISTANCE) / (MAX_DISTANCE - MIN_DISTANCE));
+        const easedDistance = d3.easeExpOut(Math.min(normalizedDistance, 1));
+
+        switch (this.animationSpeed){
+            case AnimationSpeed.SLOW:
+                scalingFactor = 250;
+                break;
+            case AnimationSpeed.MEDIUM:
+                scalingFactor = 150;
+                break;
+            case AnimationSpeed.FAST:
+                scalingFactor = 85;
+                break;
+            default: 
+                scalingFactor = 150;
+                break;
+        }
+
+        const duration = Math.min(MAX_DURATION, (easedDistance * MAX_DISTANCE) * (this.isStreetViewAnimation ? scalingFactor*1.12 : scalingFactor));
+        
+        return duration;
     }
 
     @action
@@ -96,17 +169,29 @@ export class AnimationUtility {
         this.isStreetViewAnimation = isStreetViewAnimation;
     }
 
+    @action 
+    public setPath = (path: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties>) => {
+        this.PATH = path;
+    }
+
 
     constructor(
         firstLngLat: {lng: number, lat: number},
         routeCoordinates: Position[],
         isStreetViewAnimation: boolean,
-        animationSpeed: AnimationSpeed
+        animationSpeed: AnimationSpeed,
+        terrainDisplayed: boolean,
+        mapRef: MapRef | null
     ) {
         this.FIRST_LNG_LAT = firstLngLat;
         this.previousLngLat = firstLngLat;
+        this.isStreetViewAnimation = isStreetViewAnimation;
+        this.MAP_REF = mapRef;
+
         this.ROUTE_COORDINATES = routeCoordinates;
         this.PATH = turf.lineString(routeCoordinates);
+        this.PATH_DISTANCE = turf.lineDistance(this.PATH);
+        this.terrainDisplayed = terrainDisplayed;
 
         const bearing = this.calculateBearing(
             {
@@ -119,13 +204,7 @@ export class AnimationUtility {
             }
         );
         this.currentStreetViewBearing = bearing;
-        // if (isStreetViewAnimation){
-        //     this.flyInEndBearing = bearing;
-        // }
-        this.isStreetViewAnimation = isStreetViewAnimation;
         this.animationSpeed = animationSpeed;
-        // calculate animation duration based on speed
-        // this.animationDuration = animationDuration;
     }
 
     public animatePath = async ({
@@ -151,8 +230,6 @@ export class AnimationUtility {
 
       }) => {
         return new Promise<void>(async (resolve) => {
-          const pathDistance = turf.lineDistance(this.PATH);
-          console.log("PATH DISTANCE: ", pathDistance);
           let startTime: number | null = null;
       
           const frame = async (currentTime: number) => {
@@ -169,7 +246,7 @@ export class AnimationUtility {
 
       
             // calculate the distance along the path based on the animationPhase
-            const alongPath = turf.along(this.PATH, pathDistance * animationPhase).geometry
+            const alongPath = turf.along(this.PATH, this.PATH_DISTANCE * animationPhase).geometry
               .coordinates;
       
             const lngLat = {
@@ -182,7 +259,7 @@ export class AnimationUtility {
                 bearing = this.lerp(
                     this.currentStreetViewBearing,
                     this.calculateBearing(this.previousLngLat, lngLat),
-                    0.028 // Adjust the transition speed as needed
+                    0.032
                   );
                   this.currentStreetViewBearing = bearing;
                 // bearing = this.calculateBearing(this.previousLngLat, lngLat); // TODO: Calculate actual bearing
@@ -192,35 +269,39 @@ export class AnimationUtility {
                 // bearing = startBearing - animationPhase * 200.0;
             }
 
-            this.previousLngLat = lngLat;
+            runInAction(() => {
+                this.previousLngLat = lngLat;
+            })
             
             updateAnimationPhase(animationPhase);
       
             // compute corrected camera ground position, so that he leading edge of the path is in view
             var correctedPosition = this.computeCameraPosition(
               this.isStreetViewAnimation,
-              this.flyInEndPitch,
+              this.currentPitch,
               bearing,
               lngLat,
-              this.flyInEndAltitude,
+              this.currentAnimationAltitude,
               true // smooth
             );
       
             // set the pitch and bearing of the camera
             const camera = map.getFreeCameraOptions();
-            camera.setPitchBearing(this.flyInEndPitch, bearing);
+            camera.setPitchBearing(this.currentPitch, bearing);
       
-            console.log("Corrected pos: ", correctedPosition);
-            console.log("Start altitude: ", this.flyInEndAltitude);
+
             // set the position and altitude of the camera
             camera.position = MercatorCoordinate.fromLngLat(
               correctedPosition,
-              this.flyInEndAltitude
+              this.currentAnimationAltitude
             );
 
       
             // apply the new camera options
             map.setFreeCameraOptions(camera);
+
+            this.previousAltitude = this.currentAnimationAltitude;
+            this.previousPitch = this.previousPitch;
       
             // repeat!
             const innerFrameId = await window.requestAnimationFrame(frame);
diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss
index d3c6bb14e..e25261729 100644
--- a/src/client/views/nodes/MapBox/MapBox.scss
+++ b/src/client/views/nodes/MapBox/MapBox.scss
@@ -109,8 +109,14 @@
             .animation-suboptions{
                 display: flex;
                 justify-content: flex-start;
+                flex-wrap: wrap;
                 align-items: center;
                 gap: 7px;
+                width: 100%;
+
+                .first-person-label{
+                    width: '130px' !important;
+                }
 
                 label{
                     margin-bottom: 0;
@@ -120,7 +126,7 @@
                     margin-right: 5px;
                 }
 
-                #last-divider{
+                #divider{
                     margin-left: 10px;
                     margin-right: 10px;
                 }
@@ -128,6 +134,21 @@
 
             
         }
+    }
+
+    .zoom-box {
+        position: absolute;
+        z-index: 900;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        background-color: white;
+        font-size: 1.4em;
+        border-radius: 5px;
+        bottom: 5px;
+        left: 5px;
+        padding: 3px;
 
     }
 
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index cde68a2e6..f4526c490 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -3,7 +3,7 @@ import BingMapsReact from 'bingmaps-react';
 // import 'mapbox-gl/dist/mapbox-gl.css';
 
 import { Button, EditableText, IconButton, Size, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, flow, toJS} from 'mobx';
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, flow, toJS, autorun} from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
@@ -28,6 +28,9 @@ import { FieldView, FieldViewProps } from '../FieldView';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../trails';
 import { MapAnchorMenu } from './MapAnchorMenu';
+import * as HME from "h264-mp4-encoder";
+import {simd} from 'wasm-feature-detect';
+
 import { 
     Map as MapboxMap,
     MapRef,
@@ -51,20 +54,21 @@ import debounce from 'debounce';
 import './MapBox.scss';
 import { NumberLiteralType } from 'typescript';
 // import { GeocoderControl } from './GeocoderControl';
-import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent, MercatorCoordinate } from 'mapbox-gl';
+import mapboxgl, { LngLat, LngLatBoundsLike, LngLatLike, MapLayerMouseEvent, MercatorCoordinate } from 'mapbox-gl';
 import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, MultiLineString, Position } from 'geojson';
 import { MarkerEvent } from 'react-map-gl/dist/esm/types';
 import { MapboxApiUtility, TransportationType} from './MapboxApiUtility';
 import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material';
 import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
-import { IconLookup, faCircleXmark, faFileExport, faGear, faPause, faPlay, faRotate } from '@fortawesome/free-solid-svg-icons';
+import { IconLookup, faCircleXmark, faFileExport, faGear, faMinus, faPause, faPlay, faPlus, faRotate } from '@fortawesome/free-solid-svg-icons';
 import { MarkerIcons } from './MarkerIcons';
 import { SettingsManager } from '../../../util/SettingsManager';
 import * as turf from '@turf/turf';
 import * as d3 from "d3";
 import { AnimationSpeed, AnimationStatus, AnimationUtility } from './AnimationUtility';
 import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
+import { CirclePicker, ColorState } from 'react-color';
 
 // amongus
 /**
@@ -153,17 +157,20 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             const originalCoordinates: Position[] = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates));
             // const index = Math.floor(this.animationPhase * originalCoordinates.length);
             const index = this.animationPhase * (originalCoordinates.length - 1); // Calculate the fractional index
+            console.log("Animation phase", this.animationPhase);
             const startIndex = Math.floor(index);
             const endIndex = Math.ceil(index);
+            let feature: Feature<Geometry, GeoJsonProperties>;
 
+            let geometry: LineString;
             if (startIndex === endIndex) {
                 // AnimationPhase is at a whole number (no interpolation needed)
                 const coordinates = [originalCoordinates[startIndex]];
-                const geometry: LineString = {
+                geometry = {
                     type: 'LineString',
                     coordinates,
                 };
-                return {
+                feature = {
                     type: 'Feature',
                     properties: {
                         'routeTitle': StrCast(this.routeToAnimate.title)
@@ -175,20 +182,18 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 const startCoord = originalCoordinates[startIndex];
                 const endCoord = originalCoordinates[endIndex];
                 const fraction = index - startIndex;
-                
-                // Interpolate the coordinates
-                const interpolatedCoord = [
-                    startCoord[0] + fraction * (endCoord[0] - startCoord[0]),
-                    startCoord[1] + fraction * (endCoord[1] - startCoord[1]),
-                ];
+
+                const interpolator = d3.interpolateArray(startCoord, endCoord);
+
+                const interpolatedCoord = interpolator(fraction);
     
                 const coordinates = originalCoordinates.slice(0, startIndex + 1).concat([interpolatedCoord]);
-                
-                const geometry: LineString = {
+
+                geometry = {
                     type: 'LineString',
                     coordinates,
                 };
-                return {
+                feature = {
                     type: 'Feature',
                     properties: {
                         'routeTitle': StrCast(this.routeToAnimate.title)
@@ -196,7 +201,25 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     geometry: geometry,
                 };
             }
+            
+            autorun(() => {
+                const animationUtil = this.animationUtility;
+                const concattedCoordinates = geometry.coordinates.concat(originalCoordinates.slice(endIndex));
+                const newFeature: Feature<LineString, turf.Properties> = {
+                    type: 'Feature',
+                    properties: {},
+                    geometry: {
+                        type: 'LineString',
+                        coordinates: concattedCoordinates
+                    }
+                } 
+                if (animationUtil){
+                    animationUtil.setPath(newFeature)
+                }
+            })
+            return feature;
         }
+        console.log("ERROR");
         return {
             type: 'Feature',
             properties: {},
@@ -600,7 +623,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             config_latitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.latitude ?? this.dataDoc.latitude),
             config_longitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.longitude ?? this.dataDoc.longitude),
             config_map_zoom: NumCast(this.dataDoc.map_zoom),
-            config_map_type: StrCast(this.dataDoc.map_type),
+            // config_map_type: StrCast(this.dataDoc.map_type),
             config_map: StrCast((existingPin ?? this.selectedPinOrRoute)?.map) || StrCast(this.dataDoc.map),
             layout_unrendered: true,
             mapPin: existingPin ?? this.selectedPinOrRoute,
@@ -1077,9 +1100,17 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
         MapAnchorMenu.Instance.setMenuType('standard');
 
+        // MapAnchorMenu.Instance.jumpTo(NumCast(pinDoc.longitude), NumCast(pinDoc.latitude)-3, true);
+
         MapAnchorMenu.Instance.jumpTo(e.originalEvent.clientX, e.originalEvent.clientY, true);
 
         document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
+
+        
+            // this._mapRef.current.flyTo({
+            //     center: [NumCast(pinDoc.longitude), NumCast(pinDoc.latitude)-3]
+            // })
+        
     };
 
     @observable
@@ -1113,9 +1144,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @observable
     isAnimating: boolean = false;
 
-    @observable
-    isPaused: boolean = false;
-
     @observable
     routeToAnimate: Doc | undefined = undefined;
 
@@ -1138,6 +1166,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         this.frameId = frameId;
     }
 
+    @observable 
+    animationUtility: AnimationUtility | null = null;
+
+    @action
+    setAnimationUtility = (util: AnimationUtility) => {
+        this.animationUtility = util;
+    }
+
     @action
     openAnimationPanel = (routeDoc: Doc | undefined) => {
         if (routeDoc){
@@ -1148,13 +1184,27 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }
 
     @observable
-    animationDuration = 40000;
+    mapboxMapViewState: ViewState = {
+        zoom: this.dataDoc.map_zoom ? NumCast(this.dataDoc.map_zoom) : 8,
+        longitude: this.dataDoc.longitude ? NumCast(this.dataDoc.longitude) : -71.4128, 
+        latitude: this.dataDoc.latitude ? NumCast(this.dataDoc.latitude) : 41.8240,
+        pitch: this.dataDoc.map_pitch ? NumCast(this.dataDoc.map_pitch) : 0,
+        bearing: this.dataDoc.map_bearing ? NumCast(this.dataDoc.map_bearing) : 0,
+        padding: {
+            top: 0,
+            bottom: 0,
+            left: 0,
+            right: 0
+        },
+    }
 
-    @observable
-    animationAltitude = 12800;
+    @computed 
+    get preAnimationViewState() {
+        if (!this.isAnimating){
+            return this.mapboxMapViewState;
+        }
+    }
 
-    @observable
-    pathDistance = 0;
 
     @observable 
     isStreetViewAnimation: boolean = false;
@@ -1162,22 +1212,36 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @observable 
     animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM;
 
+
+    @observable
+    animationLineColor: string = '#ffff00';
+
+    @action
+    setAnimationLineColor = (color: ColorState) => {
+        this.animationLineColor = color.hex;
+    } 
+
     @action
     updateAnimationSpeed = () => {
+        let newAnimationSpeed: AnimationSpeed;
         switch (this.animationSpeed){
             case AnimationSpeed.SLOW:
-                this.animationSpeed = AnimationSpeed.MEDIUM;
+                newAnimationSpeed = AnimationSpeed.MEDIUM;
                 break;
             case AnimationSpeed.MEDIUM:
-                this.animationSpeed = AnimationSpeed.FAST;
+                newAnimationSpeed = AnimationSpeed.FAST;
                 break;
             case AnimationSpeed.FAST:
-                this.animationSpeed = AnimationSpeed.SLOW;
+                newAnimationSpeed = AnimationSpeed.SLOW;
                 break;
             default:
-                this.animationSpeed = AnimationSpeed.MEDIUM;
+                newAnimationSpeed = AnimationSpeed.MEDIUM;
                 break;
         }
+        this.animationSpeed = newAnimationSpeed;
+        if (this.animationUtility){
+            this.animationUtility.updateAnimationSpeed(newAnimationSpeed);
+        }
     }
     @computed get animationSpeedTooltipText(): string {
         switch (this.animationSpeed) {
@@ -1206,7 +1270,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     toggleIsStreetViewAnimation = () => {
-        this.isStreetViewAnimation = !this.isStreetViewAnimation;
+        const newVal = !this.isStreetViewAnimation;
+        this.isStreetViewAnimation = newVal;
+        if (this.animationUtility){
+            this.animationUtility.updateIsStreetViewAnimation(newVal)
+        }
     }
 
     @observable 
@@ -1249,9 +1317,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             return;
         }
 
-        if (this.isAnimating){
-            return;
-        }
         this.animationPhase = status === AnimationStatus.RESUME ? this.animationPhase : 0;
         this.frameId = AnimationStatus.RESUME ? this.frameId : null;
         this.finishedFlyTo = AnimationStatus.RESUME ? this.finishedFlyTo : false;
@@ -1260,23 +1325,26 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         
         this.settingsOpen = false;
         this.path = path;
-        this.pathDistance = turf.lineDistance(path);
         this.isAnimating = true;
+
         runInAction(() => {
             return new Promise<void>(async (resolve) => {
-                let animationUtil;
-                try {
                 const targetLngLat = {
                     lng: this.selectedRouteCoordinates[0][0],
                     lat: this.selectedRouteCoordinates[0][1],
                 };
 
-                animationUtil = new AnimationUtility(
+                const animationUtil = new AnimationUtility(
                     targetLngLat,
                     this.selectedRouteCoordinates,
                     this.isStreetViewAnimation,
-                    this.animationSpeed
+                    this.animationSpeed,
+                    this.showTerrain,
+                    this._mapRef.current
                 );
+                runInAction(() => {
+                    this.setAnimationUtility(animationUtil);
+                })
 
 
                 const updateFrameId = (newFrameId: number) => {
@@ -1338,14 +1406,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 });
             
                 setTimeout(() => {
+                    this.isStreetViewAnimation = false;
                     resolve();
                 }, 10000);
-                } catch (error: any){
-                    console.log(error);
-                    console.log('animation util: ', animationUtil);
-                }});
+            });
             
-            })
+        })
             
 
     }
@@ -1361,17 +1427,25 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }
 
     @action
-    stopAndCloseAnimation = () => {
+    stopAnimation = (close: boolean) => {
         if (this.frameId){
             window.cancelAnimationFrame(this.frameId);
-            this.frameId = null;
-            this.finishedFlyTo = false;
-            this.isAnimating = false;
-            this.animationPhase = 0;
+        }
+        this.animationPhase = 0;
+        this.frameId = null;
+        this.finishedFlyTo = false;
+        this.isAnimating = false;
+        if (close) {
+            this.animationSpeed = AnimationSpeed.MEDIUM;
+            this.isStreetViewAnimation = false;
             this.routeToAnimate = undefined;
-            // this.selectedRouteCoordinates = [];
+            this.animationUtility = null;
         }
-        // reset bearing and pitch to original, zoom out  
+        if (this.preAnimationViewState){
+            this.mapboxMapViewState = this.preAnimationViewState;
+        }
+        
+ 
     }
 
     @action
@@ -1404,7 +1478,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 {this.isAnimating && this.finishedFlyTo && 
                 <IconButton 
                     tooltip='Restart animation'
-                    onPointerDown={() => this.playAnimation(AnimationStatus.RESTART)}
+                    onPointerDown={() => {
+                        this.stopAnimation(false);
+                        this.playAnimation(AnimationStatus.START)
+                    }}
                     icon={<FontAwesomeIcon icon={faRotate as IconLookup}/>}
                     color='black'
                     size={Size.MEDIUM}
@@ -1412,45 +1489,47 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     
                 }
                 <IconButton 
+                    style={{marginRight: '10px'}}
                     tooltip='Stop and close animation'
-                    onPointerDown={this.stopAndCloseAnimation}
+                    onPointerDown={() => this.stopAnimation(true)}
                     icon={<FontAwesomeIcon icon={faCircleXmark as IconLookup}/>}
                     color='black'
                     size={Size.MEDIUM}
                 />
-                <IconButton 
-                    style={{marginRight: '10px'}}
-                    tooltip='Export to video'
-                    onPointerDown={this.exportAnimationToVideo}
-                    icon={<FontAwesomeIcon icon={faFileExport as IconLookup}/>}
-                    color='black'
-                    size={Size.MEDIUM}
-                />
-                {!this.isAnimating &&
-                    <>
-                        <div className='animation-suboptions'>
-                            <div>|</div>
-                            <FormControlLabel
-                                label='Street view animation'
-                                labelPlacement='start'
-                                control={
-                                    <Checkbox 
-                                        color='success'
-                                        checked={this.isStreetViewAnimation}
-                                        onChange={this.toggleIsStreetViewAnimation}
-                                    />
-                                }
-                            />
-                            <div id='last-divider'>|</div>
-                            <IconButton
-                                tooltip={this.animationSpeedTooltipText}
-                                onPointerDown={this.updateAnimationSpeed}
-                                icon={this.animationSpeedIcon}
-                                size={Size.MEDIUM}
-                            />
-                        </div>
-                    </>
-                }
+                <>
+                    <div className='animation-suboptions'>
+                        <div>|</div>
+                        <FormControlLabel
+                            className='first-person-label'
+                            style={{width: '130px'}}
+                            label='1st person animation:'
+                            labelPlacement='start'
+                            control={
+                                <Checkbox 
+                                    color='success'
+                                    checked={this.isStreetViewAnimation}
+                                    onChange={this.toggleIsStreetViewAnimation}
+                                />
+                            }
+                        />
+                        <div id='divider'>|</div>
+                        <IconButton
+                            tooltip={this.animationSpeedTooltipText}
+                            onPointerDown={this.updateAnimationSpeed}
+                            icon={this.animationSpeedIcon}
+                            size={Size.MEDIUM}
+                        />
+                        <div id='divider'>|</div>
+                        <div style={{width: '230px'}}>Select Line Color: </div>
+                        <CirclePicker
+                            circleSize={12}
+                            circleSpacing={5}
+                            width='100%'
+                            colors={['#ffff00', '#03a9f4', '#ff0000', '#ff5722', '#000000', '#673ab7']}
+                            onChange={(color) => this.setAnimationLineColor(color)}
+                    />
+                    </div>
+                </>            
             </>
         )
     }
@@ -1464,22 +1543,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }
 
 
-
-    @observable
-    mapboxMapViewState: ViewState = {
-        zoom: 9,
-        longitude: -71.45, 
-        latitude: 41.82,
-        pitch: 0,
-        bearing: 0,
-        padding: {
-            top: 0,
-            bottom: 0,
-            left: 0,
-            right: 0
-        }
-    }
-
     @observable 
     settingsOpen: boolean = false;
 
@@ -1487,7 +1550,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     mapStyle: string = 'mapbox://styles/mapbox/streets-v12'
 
     @observable
-    showTerrain: boolean = false;
+    showTerrain: boolean = true;
 
     @action
     toggleSettings = () => {
@@ -1499,39 +1562,85 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     changeMapStyle = (e: React.ChangeEvent<HTMLSelectElement>) => {
-        this.mapStyle = `mapbox://styles/mapbox/${e.target.value}`
+        this.dataDoc.map_style = `mapbox://styles/mapbox/${e.target.value}`;
+        // this.mapStyle = `mapbox://styles/mapbox/${e.target.value}`
     }
 
-    @action
-    onMapMove = (e: ViewStateChangeEvent) => {
-        this.mapboxMapViewState = e.viewState;
+    @action 
+    onBearingChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        const bearing = parseInt(e.target.value);
+        if (!isNaN(bearing) && this._mapRef.current){
+            const fixedBearing = Math.max(0, Math.min(360, bearing));
+            this._mapRef.current.setBearing(fixedBearing);
+            this.dataDoc.map_bearing = fixedBearing;
+            this.mapboxMapViewState = {
+                ...this.mapboxMapViewState,
+                bearing: fixedBearing
+            }
+        }
     }
 
-    @action
-    toggleShowTerrain = () => {
-        this.showTerrain = !this.showTerrain;
+    @action 
+    onPitchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        const pitch = parseInt(e.target.value);
+        if (!isNaN(pitch) && this._mapRef.current){
+            const fixedPitch = Math.max(0, Math.min(85, pitch));
+            this._mapRef.current.setPitch(fixedPitch);
+            this.dataDoc.map_pitch = fixedPitch;
+            this.mapboxMapViewState = {
+                ...this.mapboxMapViewState,
+                pitch: fixedPitch
+            }
+        }
     }
 
-    @action
-    onBearingChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-        const newVal = parseInt(e.target.value)
-        if (!isNaN(newVal) && newVal >= 0){
+    @action 
+    onZoomChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        const zoom = parseInt(e.target.value);
+        if (!isNaN(zoom) && this._mapRef.current){
+            const fixedZoom = Math.max(0, Math.min(16, zoom));
+            this._mapRef.current.setZoom(fixedZoom);
+            this.dataDoc.map_zoom = fixedZoom;
             this.mapboxMapViewState = {
                 ...this.mapboxMapViewState,
-                bearing: parseInt(e.target.value)
+                zoom: fixedZoom
             }
         }
     }
 
     @action
-    onPitchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-        const newVal = parseInt(e.target.value);
-        if (!isNaN(newVal) && newVal >= 0){
+    onStepZoomChange = (increment: boolean) => {
+        if (this._mapRef.current) {
+            let newZoom: number;
+            if (increment) {
+                console.log('inc')
+                newZoom = this.mapboxMapViewState.zoom + 1;
+                
+            } else {
+                console.log('dec')
+                newZoom = this.mapboxMapViewState.zoom - 1;
+            }
+            this._mapRef.current.setZoom(newZoom);
+            this.dataDoc.map_zoom = newZoom;
             this.mapboxMapViewState = {
                 ...this.mapboxMapViewState,
-                pitch: parseInt(e.target.value)
+                zoom: increment ? Math.min(16, newZoom) : Math.max(0, newZoom)
             }
         }
+        
+    }
+
+
+    @action
+    onMapMove = (e: ViewStateChangeEvent) => {
+        this.mapboxMapViewState = e.viewState;
+        this.dataDoc.longitude = e.viewState.longitude;
+        this.dataDoc.latitude = e.viewState.latitude;
+    }
+
+    @action
+    toggleShowTerrain = () => {
+        this.showTerrain = !this.showTerrain;
     }
 
     getMarkerIcon = (pinDoc: Doc): JSX.Element | null => {
@@ -1603,7 +1712,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                 </div>
                                 <div>
                                     <select onChange={this.changeMapStyle}>
-                                        <option value='streets-v12'>Streets</option>
+                                        <option value='streets-v11'>Streets</option>
                                         <option value='outdoors-v12'>Outdoors</option>
                                         <option value='light-v11'>Light</option>
                                         <option value='dark-v11'>Dark</option>
@@ -1617,19 +1726,24 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             <div className='mapbox-bearing-selection'>
                                 <div>Bearing: </div>
                                 <input 
-                                    value={this.mapboxMapViewState.bearing}
-                                    min={0}
+                                    value={NumCast(this.mapboxMapViewState.bearing).toFixed(2)}
                                     type='number'
                                     onChange={this.onBearingChange}/>
                             </div>
                             <div className='mapbox-pitch-selection'>
                                 <div>Pitch: </div>
                                     <input 
-                                        value={this.mapboxMapViewState.pitch}
-                                        min={0}
+                                        value={NumCast(this.mapboxMapViewState.pitch).toFixed(2)}
                                         type='number'
                                         onChange={this.onPitchChange}/>
-                                </div>
+                            </div>
+                            <div className='mapbox-pitch-selection'>
+                                <div>Zoom: </div>
+                                    <input 
+                                        value={NumCast(this.mapboxMapViewState.zoom).toFixed(2)}
+                                        type='number'
+                                        onChange={this.onZoomChange}/>
+                            </div>
                             <div className='mapbox-terrain-selection'>
                                 <div>Show terrain: </div>
                                 <input 
@@ -1673,16 +1787,30 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             </React.Fragment>
                             
                         </div>   
-                    )}           
+                    )}
+                    {/* <div className='zoom-box'>
+                        <IconButton // increment 
+                            style={{borderBottom: '1px', borderBottomColor: 'white'}}
+                            onPointerDown={() => this.onStepZoomChange(true)}
+                            icon={<FontAwesomeIcon icon={faPlus as IconLookup} color='black'/>}
+                            size={Size.SMALL}
+                            color={SettingsManager.userColor}
+                        />
+                        <IconButton  // decrement
+                            onPointerDown={() => this.onStepZoomChange(false)}
+                            icon={<FontAwesomeIcon icon={faMinus as IconLookup} color='black'/>}
+                            size={Size.SMALL}
+                            color={SettingsManager.userColor}
+                        />
+                    </div>            */}
                     <MapProvider>
                         <MapboxMap
                             ref={this._mapRef}
                             mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
                             id="mapbox-map"
-                            mapStyle={this.mapStyle}
-                            style={{height: '100%', width: '100%'}}
+                            mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'}
+                            style={{height: NumCast(this.layoutDoc._height), width: NumCast(this.layoutDoc._width)}}
                             initialViewState={this.isAnimating ? undefined : this.mapboxMapViewState}
-                            // {...this.mapboxMapViewState}
                             onMove={this.onMapMove}
                             onClick={this.handleMapClick}
                             onDblClick={this.handleMapDblClick}
@@ -1725,8 +1853,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                             type='line' 
                                             source='animated-route'
                                             paint={{
-                                                'line-color': 'yellow',
-                                                'line-width': 4,
+                                                'line-color': this.animationLineColor,
+                                                'line-width': 5,
                                             }}
                                             />
                                         </>
-- 
cgit v1.2.3-70-g09d2


From fef6e957126fbc7f838e27d0bef2258008d26457 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 15 Dec 2023 09:17:45 -0500
Subject: added scaling to mapbox nodes.

---
 src/client/views/nodes/MapBox/MapBox.tsx | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index f4526c490..87484e63a 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1676,6 +1676,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             return null;
         }
 
+        const scale = this.props.NativeDimScaling?.() || 1;
+
         const renderAnnotations = (childFilters?: () => string[]) => null;
         return (
             <div className="mapBox" ref={this._ref}>
@@ -1685,7 +1687,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     onPointerDown={async e => {
                         e.button === 0 && !e.ctrlKey && e.stopPropagation();
                     }}
-                    style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
+                    style={{ transformOrigin: "top left",  transform: `scale(${scale})`,  width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
                     <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
                     {renderAnnotations(this.opaqueFilter)}
                     {SnappingManager.GetIsDragging() ? null : renderAnnotations()}
@@ -2036,4 +2038,4 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         />
                         <div style={{ width: 30, height: 30 }} ref={this._dragRef} onPointerDown={this.dragToggle}>
                             <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size={'lg'} icon={'bullseye'} />} />
-                        </div> */}
\ No newline at end of file
+                        </div> */}
-- 
cgit v1.2.3-70-g09d2


From ceb9ff8f2495bfeddb7bc0ab73fd5ddc3a6924fa Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 15 Dec 2023 11:15:31 -0500
Subject: from last

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

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

diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 87484e63a..724c1a770 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1687,7 +1687,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     onPointerDown={async e => {
                         e.button === 0 && !e.ctrlKey && e.stopPropagation();
                     }}
-                    style={{ transformOrigin: "top left",  transform: `scale(${scale})`,  width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
+                    style={{ transformOrigin: "top left",  transform: `scale(${scale})`, width: `${100 / scale}%`, height: `${100 / scale}%`, pointerEvents: this.pointerEvents() }}>
                     <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
                     {renderAnnotations(this.opaqueFilter)}
                     {SnappingManager.GetIsDragging() ? null : renderAnnotations()}
-- 
cgit v1.2.3-70-g09d2


From 3a22a2a2c78bd7d5af2b68541564a429be2864b9 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 15 Dec 2023 13:34:18 -0500
Subject: map box merging

---
 package-lock.json                                 | 757 ++++++++++++++++-
 package.json                                      |   1 +
 src/client/views/nodes/MapBox/AnimationUtility.ts |  16 +-
 src/client/views/nodes/MapBox/MapBox.tsx          | 962 +++++++++-------------
 4 files changed, 1178 insertions(+), 558 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index cf308c977..04b0a9b81 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,6 +21,7 @@
                 "@fortawesome/free-regular-svg-icons": "^6.5.1",
                 "@fortawesome/free-solid-svg-icons": "^6.5.1",
                 "@fortawesome/react-fontawesome": "^0.2.0",
+                "@mapbox/mapbox-gl-geocoder": "^5.0.2",
                 "@mui/icons-material": "^5.14.19",
                 "@mui/material": "^5.14.19",
                 "@octokit/core": "^5.0.2",
@@ -3129,6 +3130,23 @@
             "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==",
             "dev": true
         },
+        "node_modules/@mapbox/fusspot": {
+            "version": "0.4.0",
+            "resolved": "https://registry.npmjs.org/@mapbox/fusspot/-/fusspot-0.4.0.tgz",
+            "integrity": "sha512-6sys1vUlhNCqMvJOqPEPSi0jc9tg7aJ//oG1A16H3PXoIt9whtNngD7UzBHUVTH15zunR/vRvMtGNVsogm1KzA==",
+            "dependencies": {
+                "is-plain-obj": "^1.1.0",
+                "xtend": "^4.0.1"
+            }
+        },
+        "node_modules/@mapbox/fusspot/node_modules/is-plain-obj": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+            "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/@mapbox/geojson-rewind": {
             "version": "0.5.2",
             "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz",
@@ -3160,11 +3178,207 @@
                 "node": ">= 0.6"
             }
         },
+        "node_modules/@mapbox/mapbox-gl-geocoder": {
+            "version": "5.0.2",
+            "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-geocoder/-/mapbox-gl-geocoder-5.0.2.tgz",
+            "integrity": "sha512-o+2atyKKsfbiI2/iutQ/razt5O++kfi9oxwaXSfKc6m/9NudJnQm3rpGB0GagA+becq2NU4U99E9Yzv+UcMCBQ==",
+            "dependencies": {
+                "@mapbox/mapbox-sdk": "^0.13.7",
+                "events": "^3.3.0",
+                "lodash.debounce": "^4.0.6",
+                "nanoid": "^3.1.31",
+                "subtag": "^0.5.0",
+                "suggestions": "^1.6.0",
+                "xtend": "^4.0.1"
+            },
+            "engines": {
+                "node": ">=6"
+            }
+        },
         "node_modules/@mapbox/mapbox-gl-supported": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.1.tgz",
             "integrity": "sha512-HP6XvfNIzfoMVfyGjBckjiAOQK9WfX0ywdLubuPMPv+Vqf5fj0uCbgBQYpiqcWZT6cbyyRnTSXDheT1ugvF6UQ=="
         },
+        "node_modules/@mapbox/mapbox-sdk": {
+            "version": "0.13.7",
+            "resolved": "https://registry.npmjs.org/@mapbox/mapbox-sdk/-/mapbox-sdk-0.13.7.tgz",
+            "integrity": "sha512-JZjBsAVSBv7lX7gQPOQwftBGHIUcvL/tPKFxAL+SCT7u1n+eRH052XebOTkl28pNm7Du6DpKAs1GvgUnDcFFDQ==",
+            "dependencies": {
+                "@mapbox/fusspot": "^0.4.0",
+                "@mapbox/parse-mapbox-token": "^0.2.0",
+                "@mapbox/polyline": "^1.0.0",
+                "eventemitter3": "^3.1.0",
+                "form-data": "^3.0.0",
+                "got": "^11.8.5",
+                "is-plain-obj": "^1.1.0",
+                "xtend": "^4.0.1"
+            },
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/@mapbox/mapbox-sdk/node_modules/@sindresorhus/is": {
+            "version": "4.6.0",
+            "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
+            "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sindresorhus/is?sponsor=1"
+            }
+        },
+        "node_modules/@mapbox/mapbox-sdk/node_modules/@szmarczak/http-timer": {
+            "version": "4.0.6",
+            "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
+            "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
+            "dependencies": {
+                "defer-to-connect": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/@mapbox/mapbox-sdk/node_modules/cacheable-lookup": {
+            "version": "5.0.4",
+            "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
+            "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
+            "engines": {
+                "node": ">=10.6.0"
+            }
+        },
+        "node_modules/@mapbox/mapbox-sdk/node_modules/cacheable-request": {
+            "version": "7.0.4",
+            "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
+            "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
+            "dependencies": {
+                "clone-response": "^1.0.2",
+                "get-stream": "^5.1.0",
+                "http-cache-semantics": "^4.0.0",
+                "keyv": "^4.0.0",
+                "lowercase-keys": "^2.0.0",
+                "normalize-url": "^6.0.1",
+                "responselike": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/@mapbox/mapbox-sdk/node_modules/eventemitter3": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
+            "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
+        },
+        "node_modules/@mapbox/mapbox-sdk/node_modules/form-data": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
+            "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
+            "dependencies": {
+                "asynckit": "^0.4.0",
+                "combined-stream": "^1.0.8",
+                "mime-types": "^2.1.12"
+            },
+            "engines": {
+                "node": ">= 6"
+            }
+        },
+        "node_modules/@mapbox/mapbox-sdk/node_modules/get-stream": {
+            "version": "5.2.0",
+            "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+            "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+            "dependencies": {
+                "pump": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/@mapbox/mapbox-sdk/node_modules/got": {
+            "version": "11.8.6",
+            "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
+            "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
+            "dependencies": {
+                "@sindresorhus/is": "^4.0.0",
+                "@szmarczak/http-timer": "^4.0.5",
+                "@types/cacheable-request": "^6.0.1",
+                "@types/responselike": "^1.0.0",
+                "cacheable-lookup": "^5.0.3",
+                "cacheable-request": "^7.0.2",
+                "decompress-response": "^6.0.0",
+                "http2-wrapper": "^1.0.0-beta.5.2",
+                "lowercase-keys": "^2.0.0",
+                "p-cancelable": "^2.0.0",
+                "responselike": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=10.19.0"
+            },
+            "funding": {
+                "url": "https://github.com/sindresorhus/got?sponsor=1"
+            }
+        },
+        "node_modules/@mapbox/mapbox-sdk/node_modules/http2-wrapper": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
+            "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
+            "dependencies": {
+                "quick-lru": "^5.1.1",
+                "resolve-alpn": "^1.0.0"
+            },
+            "engines": {
+                "node": ">=10.19.0"
+            }
+        },
+        "node_modules/@mapbox/mapbox-sdk/node_modules/is-plain-obj": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+            "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/@mapbox/mapbox-sdk/node_modules/lowercase-keys": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
+            "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/@mapbox/mapbox-sdk/node_modules/normalize-url": {
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
+            "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/@mapbox/mapbox-sdk/node_modules/p-cancelable": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
+            "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/@mapbox/mapbox-sdk/node_modules/responselike": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
+            "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
+            "dependencies": {
+                "lowercase-keys": "^2.0.0"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
         "node_modules/@mapbox/node-pre-gyp": {
             "version": "1.0.11",
             "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
@@ -3267,11 +3481,30 @@
             "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
             "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
         },
+        "node_modules/@mapbox/parse-mapbox-token": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/@mapbox/parse-mapbox-token/-/parse-mapbox-token-0.2.0.tgz",
+            "integrity": "sha512-BjeuG4sodYaoTygwXIuAWlZV6zUv4ZriYAQhXikzx+7DChycMUQ9g85E79Htat+AsBg+nStFALehlOhClYm5cQ==",
+            "dependencies": {
+                "base-64": "^0.1.0"
+            }
+        },
         "node_modules/@mapbox/point-geometry": {
             "version": "0.1.0",
             "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz",
             "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ=="
         },
+        "node_modules/@mapbox/polyline": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/@mapbox/polyline/-/polyline-1.2.1.tgz",
+            "integrity": "sha512-sn0V18O3OzW4RCcPoUIVDWvEGQaBNH9a0y5lgqrf5hUycyw1CzrhEoxV5irzrMNXKCkw1xRsZXcaVbsVZggHXA==",
+            "dependencies": {
+                "meow": "^9.0.0"
+            },
+            "bin": {
+                "polyline": "bin/polyline.bin.js"
+            }
+        },
         "node_modules/@mapbox/tiny-sdf": {
             "version": "2.0.6",
             "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz",
@@ -5521,6 +5754,17 @@
                 "@types/node": "*"
             }
         },
+        "node_modules/@types/cacheable-request": {
+            "version": "6.0.3",
+            "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
+            "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
+            "dependencies": {
+                "@types/http-cache-semantics": "*",
+                "@types/keyv": "^3.1.4",
+                "@types/node": "*",
+                "@types/responselike": "^1.0.0"
+            }
+        },
         "node_modules/@types/caseless": {
             "version": "0.12.5",
             "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz",
@@ -6065,6 +6309,14 @@
             "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==",
             "dev": true
         },
+        "node_modules/@types/keyv": {
+            "version": "3.1.4",
+            "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
+            "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
+            "dependencies": {
+                "@types/node": "*"
+            }
+        },
         "node_modules/@types/libxmljs": {
             "version": "0.18.12",
             "resolved": "https://registry.npmjs.org/@types/libxmljs/-/libxmljs-0.18.12.tgz",
@@ -6108,6 +6360,11 @@
             "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
             "dev": true
         },
+        "node_modules/@types/minimist": {
+            "version": "1.2.5",
+            "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz",
+            "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag=="
+        },
         "node_modules/@types/mocha": {
             "version": "10.0.6",
             "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz",
@@ -6154,6 +6411,11 @@
                 "@types/node": "*"
             }
         },
+        "node_modules/@types/normalize-package-data": {
+            "version": "2.4.4",
+            "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
+            "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA=="
+        },
         "node_modules/@types/oauth": {
             "version": "0.9.4",
             "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.4.tgz",
@@ -6390,6 +6652,14 @@
                 "node": ">= 0.12"
             }
         },
+        "node_modules/@types/responselike": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
+            "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==",
+            "dependencies": {
+                "@types/node": "*"
+            }
+        },
         "node_modules/@types/retry": {
             "version": "0.12.0",
             "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
@@ -11046,6 +11316,38 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/camelcase-keys": {
+            "version": "6.2.2",
+            "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz",
+            "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==",
+            "dependencies": {
+                "camelcase": "^5.3.1",
+                "map-obj": "^4.0.0",
+                "quick-lru": "^4.0.1"
+            },
+            "engines": {
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/camelcase-keys/node_modules/camelcase": {
+            "version": "5.3.1",
+            "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+            "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/camelcase-keys/node_modules/quick-lru": {
+            "version": "4.0.1",
+            "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
+            "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/camelize": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
@@ -11426,6 +11728,25 @@
                 "node": ">=6"
             }
         },
+        "node_modules/clone-response": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
+            "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
+            "dependencies": {
+                "mimic-response": "^1.0.0"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/clone-response/node_modules/mimic-response": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
+            "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
+            "engines": {
+                "node": ">=4"
+            }
+        },
         "node_modules/clsx": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
@@ -12614,6 +12935,37 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/decamelize-keys": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz",
+            "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==",
+            "dependencies": {
+                "decamelize": "^1.1.0",
+                "map-obj": "^1.0.0"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/decamelize-keys/node_modules/decamelize": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+            "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/decamelize-keys/node_modules/map-obj": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+            "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/decimal.js": {
             "version": "10.4.3",
             "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
@@ -13296,6 +13648,14 @@
                 "node": ">= 0.8"
             }
         },
+        "node_modules/end-of-stream": {
+            "version": "1.4.4",
+            "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+            "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+            "dependencies": {
+                "once": "^1.4.0"
+            }
+        },
         "node_modules/engine.io": {
             "version": "6.5.4",
             "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz",
@@ -16394,6 +16754,14 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/fuzzy": {
+            "version": "0.1.3",
+            "resolved": "https://registry.npmjs.org/fuzzy/-/fuzzy-0.1.3.tgz",
+            "integrity": "sha512-/gZffu4ykarLrCiP3Ygsa86UAo1E5vEVlvTrpkKywXSbP9Xhln3oSp9QSV57gEq3JFFpGJ4GZ+5zdEp3FcUh4w==",
+            "engines": {
+                "node": ">= 0.6.0"
+            }
+        },
         "node_modules/gauge": {
             "version": "3.0.2",
             "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
@@ -16922,6 +17290,14 @@
                 "node": ">=6"
             }
         },
+        "node_modules/hard-rejection": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
+            "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==",
+            "engines": {
+                "node": ">=6"
+            }
+        },
         "node_modules/has-bigints": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
@@ -17169,6 +17545,33 @@
                 "node": ">=0.10.0"
             }
         },
+        "node_modules/hosted-git-info": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
+            "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==",
+            "dependencies": {
+                "lru-cache": "^6.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/hosted-git-info/node_modules/lru-cache": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+            "dependencies": {
+                "yallist": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/hosted-git-info/node_modules/yallist": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+            "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+        },
         "node_modules/howler": {
             "version": "2.2.4",
             "resolved": "https://registry.npmjs.org/howler/-/howler-2.2.4.tgz",
@@ -17881,6 +18284,14 @@
                 "node": ">=0.8.19"
             }
         },
+        "node_modules/indent-string": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+            "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/inflight": {
             "version": "1.0.6",
             "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -19777,6 +20188,17 @@
             "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
             "dev": true
         },
+        "node_modules/map-obj": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
+            "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
+            "engines": {
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
         "node_modules/mapbox-gl": {
             "version": "3.0.1",
             "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.0.1.tgz",
@@ -20171,6 +20593,50 @@
                 "node": ">= 0.10.0"
             }
         },
+        "node_modules/meow": {
+            "version": "9.0.0",
+            "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz",
+            "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==",
+            "dependencies": {
+                "@types/minimist": "^1.2.0",
+                "camelcase-keys": "^6.2.2",
+                "decamelize": "^1.2.0",
+                "decamelize-keys": "^1.1.0",
+                "hard-rejection": "^2.1.0",
+                "minimist-options": "4.1.0",
+                "normalize-package-data": "^3.0.0",
+                "read-pkg-up": "^7.0.1",
+                "redent": "^3.0.0",
+                "trim-newlines": "^3.0.0",
+                "type-fest": "^0.18.0",
+                "yargs-parser": "^20.2.3"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/meow/node_modules/decamelize": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+            "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/meow/node_modules/type-fest": {
+            "version": "0.18.1",
+            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz",
+            "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==",
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
         "node_modules/merge-descriptors": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
@@ -20806,6 +21272,14 @@
                 "dom-walk": "^0.1.0"
             }
         },
+        "node_modules/min-indent": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+            "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+            "engines": {
+                "node": ">=4"
+            }
+        },
         "node_modules/minimalistic-assert": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -20833,6 +21307,35 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/minimist-options": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz",
+            "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
+            "dependencies": {
+                "arrify": "^1.0.1",
+                "is-plain-obj": "^1.1.0",
+                "kind-of": "^6.0.3"
+            },
+            "engines": {
+                "node": ">= 6"
+            }
+        },
+        "node_modules/minimist-options/node_modules/arrify": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+            "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/minimist-options/node_modules/is-plain-obj": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+            "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/minipass": {
             "version": "5.0.0",
             "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
@@ -21395,7 +21898,6 @@
             "version": "3.3.3",
             "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
             "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
-            "dev": true,
             "bin": {
                 "nanoid": "bin/nanoid.cjs"
             },
@@ -21660,6 +22162,50 @@
                 "node": ">=6"
             }
         },
+        "node_modules/normalize-package-data": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz",
+            "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==",
+            "dependencies": {
+                "hosted-git-info": "^4.0.1",
+                "is-core-module": "^2.5.0",
+                "semver": "^7.3.4",
+                "validate-npm-package-license": "^3.0.1"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/normalize-package-data/node_modules/lru-cache": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+            "dependencies": {
+                "yallist": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/normalize-package-data/node_modules/semver": {
+            "version": "7.5.4",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+            "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+            "dependencies": {
+                "lru-cache": "^6.0.0"
+            },
+            "bin": {
+                "semver": "bin/semver.js"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/normalize-package-data/node_modules/yallist": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+            "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+        },
         "node_modules/normalize-path": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -25945,6 +26491,15 @@
             "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz",
             "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ=="
         },
+        "node_modules/pump": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+            "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+            "dependencies": {
+                "end-of-stream": "^1.1.0",
+                "once": "^1.3.1"
+            }
+        },
         "node_modules/punycode": {
             "version": "2.3.1",
             "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -26590,6 +27145,124 @@
                 "lodash": "^4.0.1"
             }
         },
+        "node_modules/read-pkg": {
+            "version": "5.2.0",
+            "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
+            "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
+            "dependencies": {
+                "@types/normalize-package-data": "^2.4.0",
+                "normalize-package-data": "^2.5.0",
+                "parse-json": "^5.0.0",
+                "type-fest": "^0.6.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/read-pkg-up": {
+            "version": "7.0.1",
+            "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
+            "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
+            "dependencies": {
+                "find-up": "^4.1.0",
+                "read-pkg": "^5.2.0",
+                "type-fest": "^0.8.1"
+            },
+            "engines": {
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/read-pkg-up/node_modules/find-up": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+            "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+            "dependencies": {
+                "locate-path": "^5.0.0",
+                "path-exists": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/read-pkg-up/node_modules/locate-path": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+            "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+            "dependencies": {
+                "p-locate": "^4.1.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/read-pkg-up/node_modules/p-limit": {
+            "version": "2.3.0",
+            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+            "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+            "dependencies": {
+                "p-try": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=6"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/read-pkg-up/node_modules/p-locate": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+            "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+            "dependencies": {
+                "p-limit": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/read-pkg-up/node_modules/type-fest": {
+            "version": "0.8.1",
+            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+            "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/read-pkg/node_modules/hosted-git-info": {
+            "version": "2.8.9",
+            "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+            "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="
+        },
+        "node_modules/read-pkg/node_modules/normalize-package-data": {
+            "version": "2.5.0",
+            "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+            "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+            "dependencies": {
+                "hosted-git-info": "^2.1.4",
+                "resolve": "^1.10.0",
+                "semver": "2 || 3 || 4 || 5",
+                "validate-npm-package-license": "^3.0.1"
+            }
+        },
+        "node_modules/read-pkg/node_modules/semver": {
+            "version": "5.7.2",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+            "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+            "bin": {
+                "semver": "bin/semver"
+            }
+        },
+        "node_modules/read-pkg/node_modules/type-fest": {
+            "version": "0.6.0",
+            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
+            "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/readable-stream": {
             "version": "3.6.2",
             "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
@@ -26700,6 +27373,18 @@
                 "node": ">= 0.10"
             }
         },
+        "node_modules/redent": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+            "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+            "dependencies": {
+                "indent-string": "^4.0.0",
+                "strip-indent": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/reduce-flatten": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz",
@@ -28372,6 +29057,34 @@
                 "memory-pager": "^1.0.2"
             }
         },
+        "node_modules/spdx-correct": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
+            "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
+            "dependencies": {
+                "spdx-expression-parse": "^3.0.0",
+                "spdx-license-ids": "^3.0.0"
+            }
+        },
+        "node_modules/spdx-exceptions": {
+            "version": "2.3.0",
+            "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
+            "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A=="
+        },
+        "node_modules/spdx-expression-parse": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+            "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+            "dependencies": {
+                "spdx-exceptions": "^2.1.0",
+                "spdx-license-ids": "^3.0.0"
+            }
+        },
+        "node_modules/spdx-license-ids": {
+            "version": "3.0.16",
+            "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz",
+            "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw=="
+        },
         "node_modules/spdy": {
             "version": "4.0.2",
             "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz",
@@ -28773,6 +29486,17 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/strip-indent": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+            "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+            "dependencies": {
+                "min-indent": "^1.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/strip-json-comments": {
             "version": "3.1.1",
             "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -28862,6 +29586,20 @@
             "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
             "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
         },
+        "node_modules/subtag": {
+            "version": "0.5.0",
+            "resolved": "https://registry.npmjs.org/subtag/-/subtag-0.5.0.tgz",
+            "integrity": "sha512-CaIBcTSb/nyk4xiiSOtZYz1B+F12ZxW8NEp54CdT+84vmh/h4sUnHGC6+KQXUfED8u22PQjCYWfZny8d2ELXwg=="
+        },
+        "node_modules/suggestions": {
+            "version": "1.7.1",
+            "resolved": "https://registry.npmjs.org/suggestions/-/suggestions-1.7.1.tgz",
+            "integrity": "sha512-gl5YPAhPYl07JZ5obiD9nTZsg4SyZswAQU/NNtnYiSnFkI3+ZHuXAiEsYm7AaZ71E0LXSFaGVaulGSWN3Gd71A==",
+            "dependencies": {
+                "fuzzy": "^0.1.1",
+                "xtend": "^4.0.0"
+            }
+        },
         "node_modules/supercluster": {
             "version": "8.0.1",
             "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz",
@@ -29349,6 +30087,14 @@
                 "url": "https://github.com/sponsors/wooorm"
             }
         },
+        "node_modules/trim-newlines": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz",
+            "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==",
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/trough": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz",
@@ -30565,6 +31311,15 @@
             "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz",
             "integrity": "sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA=="
         },
+        "node_modules/validate-npm-package-license": {
+            "version": "3.0.4",
+            "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+            "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+            "dependencies": {
+                "spdx-correct": "^3.0.0",
+                "spdx-expression-parse": "^3.0.0"
+            }
+        },
         "node_modules/validator": {
             "version": "13.11.0",
             "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz",
diff --git a/package.json b/package.json
index 98453a651..90eea7fa1 100644
--- a/package.json
+++ b/package.json
@@ -105,6 +105,7 @@
         "@fortawesome/free-regular-svg-icons": "^6.5.1",
         "@fortawesome/free-solid-svg-icons": "^6.5.1",
         "@fortawesome/react-fontawesome": "^0.2.0",
+        "@mapbox/mapbox-gl-geocoder": "^5.0.2",
         "@mui/icons-material": "^5.14.19",
         "@mui/material": "^5.14.19",
         "@octokit/core": "^5.0.2",
diff --git a/src/client/views/nodes/MapBox/AnimationUtility.ts b/src/client/views/nodes/MapBox/AnimationUtility.ts
index a5cff4668..42dfa59b7 100644
--- a/src/client/views/nodes/MapBox/AnimationUtility.ts
+++ b/src/client/views/nodes/MapBox/AnimationUtility.ts
@@ -7,7 +7,7 @@ import * as turf from '@turf/turf';
 import { Position } from '@turf/turf';
 import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from 'geojson';
 import { observer } from 'mobx-react';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, observable, runInAction, makeObservable } from 'mobx';
 
 export enum AnimationStatus {
     START = 'start',
@@ -26,16 +26,16 @@ export class AnimationUtility {
     private ROUTE_COORDINATES: Position[] = [];
 
     @observable
-    private PATH: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties>;
+    private PATH?: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties> = undefined;
 
-    private PATH_DISTANCE: number;
+    private PATH_DISTANCE: number = 0;
     private FLY_IN_START_PITCH = 40;
-    private FIRST_LNG_LAT: { lng: number; lat: number };
+    private FIRST_LNG_LAT: { lng: number; lat: number } = { lng: 0, lat: 0 };
     private START_ALTITUDE = 3_000_000;
-    private MAP_REF: MapRef | null;
+    private MAP_REF: MapRef | null = null;
 
-    @observable private isStreetViewAnimation: boolean;
-    @observable private animationSpeed: AnimationSpeed;
+    @observable private isStreetViewAnimation: boolean = false;
+    @observable private animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM;
 
     @observable
     private previousLngLat: { lng: number; lat: number };
@@ -174,6 +174,7 @@ export class AnimationUtility {
     };
 
     constructor(firstLngLat: { lng: number; lat: number }, routeCoordinates: Position[], isStreetViewAnimation: boolean, animationSpeed: AnimationSpeed, terrainDisplayed: boolean, mapRef: MapRef | null) {
+        makeObservable(this);
         this.FIRST_LNG_LAT = firstLngLat;
         this.previousLngLat = firstLngLat;
         this.isStreetViewAnimation = isStreetViewAnimation;
@@ -232,6 +233,7 @@ export class AnimationUtility {
                     return;
                 }
 
+                if (!this.PATH) return;
                 // calculate the distance along the path based on the animationPhase
                 const alongPath = turf.along(this.PATH, this.PATH_DISTANCE * animationPhase).geometry.coordinates;
 
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 724c1a770..735c04c1d 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -3,7 +3,7 @@ import BingMapsReact from 'bingmaps-react';
 // import 'mapbox-gl/dist/mapbox-gl.css';
 
 import { Button, EditableText, IconButton, Size, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, flow, toJS, autorun} from 'mobx';
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, flow, toJS, autorun, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
@@ -28,36 +28,34 @@ import { FieldView, FieldViewProps } from '../FieldView';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../trails';
 import { MapAnchorMenu } from './MapAnchorMenu';
-import * as HME from "h264-mp4-encoder";
-import {simd} from 'wasm-feature-detect';
 
-import { 
+import {
     Map as MapboxMap,
     MapRef,
-    Marker, 
-    ControlPosition, 
-    FullscreenControl,  
-    MapProvider, 
-    MarkerProps, 
-    NavigationControl, 
-    ScaleControl, 
-    ViewState, 
+    Marker,
+    ControlPosition,
+    FullscreenControl,
+    MapProvider,
+    MarkerProps,
+    NavigationControl,
+    ScaleControl,
+    ViewState,
     ViewStateChangeEvent,
     useControl,
     GeolocateControl,
     Popup,
     MapEvent,
     Source,
-    Layer} from 'react-map-gl';
-import MapboxGeocoder, {GeocoderOptions} from '@mapbox/mapbox-gl-geocoder';
-import debounce from 'debounce';
+    Layer,
+} from 'react-map-gl';
+import MapboxGeocoder, { GeocoderOptions } from '@mapbox/mapbox-gl-geocoder!';
 import './MapBox.scss';
 import { NumberLiteralType } from 'typescript';
 // import { GeocoderControl } from './GeocoderControl';
-import mapboxgl, { LngLat, LngLatBoundsLike, LngLatLike, MapLayerMouseEvent, MercatorCoordinate } from 'mapbox-gl';
+import mapboxgl, { LngLat, LngLatBoundsLike, LngLatLike, MapLayerMouseEvent, MercatorCoordinate } from 'mapbox-gl!';
 import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, MultiLineString, Position } from 'geojson';
 import { MarkerEvent } from 'react-map-gl/dist/esm/types';
-import { MapboxApiUtility, TransportationType} from './MapboxApiUtility';
+import { MapboxApiUtility, TransportationType } from './MapboxApiUtility';
 import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material';
 import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
@@ -65,10 +63,10 @@ import { IconLookup, faCircleXmark, faFileExport, faGear, faMinus, faPause, faPl
 import { MarkerIcons } from './MarkerIcons';
 import { SettingsManager } from '../../../util/SettingsManager';
 import * as turf from '@turf/turf';
-import * as d3 from "d3";
+import * as d3 from 'd3';
 import { AnimationSpeed, AnimationStatus, AnimationUtility } from './AnimationUtility';
 import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
-import { CirclePicker, ColorState } from 'react-color';
+import { CirclePicker, ColorResult } from 'react-color';
 
 // amongus
 /**
@@ -91,24 +89,24 @@ const MAPBOX_FORWARD_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/map
 const MAPBOX_REVERSE_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
 
 type PopupInfo = {
-    longitude: number,
-    latitude: number,
-    title: string,
-    description: string
-}
+    longitude: number;
+    latitude: number;
+    title: string;
+    description: string;
+};
 
 export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & {
-    mapboxAccessToken: string,
+    mapboxAccessToken: string;
     marker?: Omit<MarkerProps, 'longitude' | 'latitude'>;
     position: ControlPosition;
 
     onResult: (...args: any[]) => void;
-}
+};
 
 type MapMarker = {
-    longitude: number,
-    latitude: number
-}
+    longitude: number;
+    latitude: number;
+};
 
 /**
  * Consider integrating later: allows for drawing, circling, making shapes on map
@@ -138,6 +136,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     private _disposers: { [key: string]: IReactionDisposer } = {};
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
 
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
+
     @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @computed get allSidebarDocs() {
         return DocListCast(this.dataDoc[this.SidebarKey]);
@@ -157,7 +160,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             const originalCoordinates: Position[] = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates));
             // const index = Math.floor(this.animationPhase * originalCoordinates.length);
             const index = this.animationPhase * (originalCoordinates.length - 1); // Calculate the fractional index
-            console.log("Animation phase", this.animationPhase);
+            console.log('Animation phase', this.animationPhase);
             const startIndex = Math.floor(index);
             const endIndex = Math.ceil(index);
             let feature: Feature<Geometry, GeoJsonProperties>;
@@ -173,7 +176,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 feature = {
                     type: 'Feature',
                     properties: {
-                        'routeTitle': StrCast(this.routeToAnimate.title)
+                        routeTitle: StrCast(this.routeToAnimate.title),
                     },
                     geometry: geometry,
                 };
@@ -186,7 +189,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 const interpolator = d3.interpolateArray(startCoord, endCoord);
 
                 const interpolatedCoord = interpolator(fraction);
-    
+
                 const coordinates = originalCoordinates.slice(0, startIndex + 1).concat([interpolatedCoord]);
 
                 geometry = {
@@ -196,12 +199,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 feature = {
                     type: 'Feature',
                     properties: {
-                        'routeTitle': StrCast(this.routeToAnimate.title)
+                        routeTitle: StrCast(this.routeToAnimate.title),
                     },
                     geometry: geometry,
                 };
             }
-            
+
             autorun(() => {
                 const animationUtil = this.animationUtility;
                 const concattedCoordinates = geometry.coordinates.concat(originalCoordinates.slice(endIndex));
@@ -210,28 +213,28 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     properties: {},
                     geometry: {
                         type: 'LineString',
-                        coordinates: concattedCoordinates
-                    }
-                } 
-                if (animationUtil){
-                    animationUtil.setPath(newFeature)
+                        coordinates: concattedCoordinates,
+                    },
+                };
+                if (animationUtil) {
+                    animationUtil.setPath(newFeature);
                 }
-            })
+            });
             return feature;
         }
-        console.log("ERROR");
+        console.log('ERROR');
         return {
             type: 'Feature',
             properties: {},
             geometry: {
                 type: 'LineString',
-                coordinates: []
+                coordinates: [],
             },
         };
     }
     @computed get selectedRouteCoordinates(): Position[] {
         let coordinates: Position[] = [];
-        if (this.routeToAnimate?.routeCoordinates){
+        if (this.routeToAnimate?.routeCoordinates) {
             coordinates = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates));
         }
         return coordinates;
@@ -247,7 +250,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             return {
                 type: 'Feature',
                 properties: {
-                    'routeTitle': routeDoc.title},
+                    routeTitle: routeDoc.title,
+                },
                 geometry: geometry,
             };
         });
@@ -265,7 +269,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
     }
     @computed get sidebarColor() {
-        return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4'));
+        return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
     }
     @computed get SidebarKey() {
         return this.fieldKey + '_sidebar';
@@ -273,11 +277,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     componentDidMount() {
         this._unmounting = false;
-        this.props.setContentView?.(this);
-
+        this._props.setContentView?.(this);
     }
 
-
     _unmounting = false;
     componentWillUnmount(): void {
         this._unmounting = true;
@@ -340,9 +342,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             e,
             (e, down, delta) =>
                 runInAction(() => {
-                    const localDelta = this.props
+                    const localDelta = this._props
                         .ScreenToLocalTransform()
-                        .scale(this.props.NativeDimScaling?.() || 1)
+                        .scale(this._props.NativeDimScaling?.() || 1)
                         .transformDirection(delta[0], delta[1]);
                     const fullWidth = NumCast(this.layoutDoc._width);
                     const mapWidth = fullWidth - this.sidebarWidth();
@@ -361,7 +363,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map')
         );
     };
-    sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
+    sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
 
     /**
      * Handles toggle of sidebar on click the little comment button
@@ -373,8 +375,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 key="sidebar"
                 title="Toggle Sidebar"
                 style={{
-                    display: !this.props.isContentActive() ? 'none' : undefined,
-                    top: StrCast(this.rootDoc._layout_showTitle) === 'title' ? 20 : 5,
+                    display: !this._props.isContentActive() ? 'none' : undefined,
+                    top: StrCast(this.Document._layout_showTitle) === 'title' ? 20 : 5,
                     backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
                 }}
                 onPointerDown={this.sidebarBtnDown}>
@@ -406,16 +408,16 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         });
 
         const targetCreator = (annotationOn: Doc | undefined) => {
-            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
-            FormattedTextBox.SelectOnLoad = target[Id];
+            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
+            FormattedTextBox.SetSelectOnLoad(target);
             return target;
         };
-        const docView = this.props.DocumentView?.();
+        const docView = this._props.DocumentView?.();
         docView &&
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                 dragComplete: e => {
                     if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
-                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.Document;
+                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this._props.Document;
                         e.annoDragData.linkSourceDoc.followLinkZoom = false;
                     }
                 },
@@ -454,15 +456,15 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey);
 
-    pointerEvents = () => (this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
+    pointerEvents = () => (this._props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
 
-    panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth();
-    panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
-    scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
-    transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter];
-    opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter];
-    infoWidth = () => this.props.PanelWidth() / 5;
-    infoHeight = () => this.props.PanelHeight() / 5;
+    panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
+    panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
+    scrollXf = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+    transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
+    opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter];
+    infoWidth = () => this._props.PanelWidth() / 5;
+    infoHeight = () => this._props.PanelHeight() / 5;
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
     savedAnnotations = () => this._savedAnnotations;
 
@@ -500,7 +502,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
 
     @observable
-    bingSearchBarContents: any = this.rootDoc.map; // For Bing Maps: The contents of the Bing search bar (string)
+    bingSearchBarContents: any = this.Document.map; // For Bing Maps: The contents of the Bing search bar (string)
 
     geoDataRequestOptions = {
         entityType: 'PopulatedPlace',
@@ -509,15 +511,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     // The pin that is selected
     @observable selectedPinOrRoute: Doc | undefined;
 
-
     @action
     deselectPinOrRoute = () => {
         if (this.selectedPinOrRoute) {
             // // Removes filter
-            // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove');
-            // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove');
-            // Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
-
+            // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'remove');
+            // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'remove');
+            // Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
             // const temp = this.selectedPin;
             // if (!this._unmounting) {
             //     this._bingMap.current.entities.remove(this.map_docToPinMap.get(temp));
@@ -529,11 +529,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             // }
             // this.map_docToPinMap.set(temp, newpin);
             // this.selectedPin = undefined;
-            // this.bingSearchBarContents = this.rootDoc.map;
+            // this.bingSearchBarContents = this.Document.map;
         }
     };
 
-
     getView = async (doc: Doc) => {
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar();
         return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
@@ -547,9 +546,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         this.selectedPinOrRoute = pinDoc;
         this.bingSearchBarContents = pinDoc.map;
 
-        // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'match');
-        // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'match');
-        Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
+        // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'match');
+        // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'match');
+        Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
 
         this.recolorPin(this.selectedPinOrRoute, 'green');
 
@@ -559,9 +558,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
 
         const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPinOrRoute.latitude, this.selectedPinOrRoute.longitude));
-        const x = point.x + (this.props.PanelWidth() - this.sidebarWidth()) / 2;
-        const y = point.y + this.props.PanelHeight() / 2 + 32;
-        const cpt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+        const x = point.x + (this._props.PanelWidth() - this.sidebarWidth()) / 2;
+        const y = point.y + this._props.PanelHeight() / 2 + 32;
+        const cpt = this._props.ScreenToLocalTransform().inverse().transformPoint(x, y);
         MapAnchorMenu.Instance.jumpTo(cpt[0], cpt[1], true);
 
         document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
@@ -572,7 +571,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
      */
     @action
     mapOnClick = (e: { location: { latitude: any; longitude: any } }) => {
-        this.props.select(false);
+        this._props.select(false);
         this.deselectPinOrRoute();
     };
     /*
@@ -618,8 +617,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps, existingPin?: Doc) => {
         /// this should use SELECTED pushpin for lat/long  if there is a selection, otherwise CENTER
         const anchor = Docs.Create.ConfigDocument({
-            title: 'MapAnchor:' + this.rootDoc.title,
-            text: StrCast(this.selectedPinOrRoute?.map) || StrCast(this.rootDoc.map) || 'map location',
+            title: 'MapAnchor:' + this.Document.title,
+            text: StrCast(this.selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location',
             config_latitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.latitude ?? this.dataDoc.latitude),
             config_longitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.longitude ?? this.dataDoc.longitude),
             config_map_zoom: NumCast(this.dataDoc.map_zoom),
@@ -627,15 +626,15 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             config_map: StrCast((existingPin ?? this.selectedPinOrRoute)?.map) || StrCast(this.dataDoc.map),
             layout_unrendered: true,
             mapPin: existingPin ?? this.selectedPinOrRoute,
-            annotationOn: this.rootDoc,
+            annotationOn: this.Document,
         });
         if (anchor) {
             if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
             addAsAnnotation && this.addDocument(anchor);
-            PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.rootDoc);
+            PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.Document);
             return anchor;
         }
-        return this.rootDoc;
+        return this.Document;
     };
 
     map_docToPinMap = new Map<Doc, any>();
@@ -682,9 +681,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     deleteSelectedPinOrRoute = undoable(() => {
         if (this.selectedPinOrRoute) {
             // Removes filter
-            Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPinOrRoute.latitude, 'remove');
-            Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPinOrRoute.longitude, 'remove');
-            Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPinOrRoute))}`, 'remove');
+            Doc.setDocFilter(this.Document, 'latitude', this.selectedPinOrRoute.latitude, 'remove');
+            Doc.setDocFilter(this.Document, 'longitude', this.selectedPinOrRoute.longitude, 'remove');
+            Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPinOrRoute))}`, 'remove');
 
             this.removePushpinOrRoute(this.selectedPinOrRoute);
         }
@@ -692,8 +691,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
     }, 'delete pin');
 
-
-
     tryHideMapAnchorMenu = (e: PointerEvent) => {
         let target = document.elementFromPoint(e.x, e.y);
         while (target) {
@@ -711,8 +708,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     centerOnSelectedPin = () => {
         if (this.selectedPinOrRoute) {
             this._mapRef.current?.flyTo({
-                center: [NumCast(this.selectedPinOrRoute.longitude), NumCast(this.selectedPinOrRoute.latitude)]
-            })
+                center: [NumCast(this.selectedPinOrRoute.longitude), NumCast(this.selectedPinOrRoute.latitude)],
+            });
         }
         // if (this.selectedPin) {
         //     this.dataDoc.latitude = this.selectedPin.latitude;
@@ -733,7 +730,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         mapTypeId: 'grayscale',
     };
 
-
     /**
      * Map options
      */
@@ -752,8 +748,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         },
     };
 
-
-
     recolorPin = (pin: Doc, color?: string) => {
         // this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin));
         // this.map_docToPinMap.delete(pin);
@@ -780,7 +774,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'maptypechanged', undoable(this.updateMapType, 'Map ViewType Change'));
 
         this._disposers.mapLocation = reaction(
-            () => this.rootDoc.map,
+            () => this.Document.map,
             mapLoc => (this.bingSearchBarContents = mapLoc),
             { fireImmediately: true }
         );
@@ -805,7 +799,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         );
 
         this._disposers.location = reaction(
-            () => ({ lat: this.rootDoc.latitude, lng: this.rootDoc.longitude, zoom: this.rootDoc.map_zoom, mapType: this.rootDoc.map_type }),
+            () => ({ lat: this.Document.latitude, lng: this.Document.longitude, zoom: this.Document.map_zoom, mapType: this.Document.map_type }),
             locationObject => {
                 // if (this._bingMap.current)
                 try {
@@ -829,25 +823,27 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         setupMoveUpEvents(
             e,
             e,
-            e => { // move event 
+            e => {
+                // move event
                 if (!dragClone) {
-                    dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement; // copy draggable pin 
+                    dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement; // copy draggable pin
                     dragClone.style.position = 'absolute';
                     dragClone.style.zIndex = '10000';
-                    DragManager.Root().appendChild(dragClone); // add clone to root 
+                    DragManager.Root().appendChild(dragClone); // add clone to root
                 }
                 dragClone.style.transform = `translate(${e.clientX - 15}px, ${e.clientY - 15}px)`;
                 return false;
             },
-            e => { // up event
+            e => {
+                // up event
                 if (!dragClone) return;
                 DragManager.Root().removeChild(dragClone);
                 let target = document.elementFromPoint(e.x, e.y); // element for specified x and y coordinates
                 while (target) {
-                    if (target === this._ref.current) { 
-                        const cpt = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
-                        const x = cpt[0] - (this.props.PanelWidth() - this.sidebarWidth()) / 2;
-                        const y = cpt[1] - 20 /* height of search bar */ - this.props.PanelHeight() / 2;
+                    if (target === this._ref.current) {
+                        const cpt = this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+                        const x = cpt[0] - (this._props.PanelWidth() - this.sidebarWidth()) / 2;
+                        const y = cpt[1] - 20 /* height of search bar */ - this._props.PanelHeight() / 2;
                         const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
                         this.createPushpin(location.latitude, location.longitude);
                         break;
@@ -856,7 +852,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 }
             },
             e => {
-                const createPin = () => this.createPushpin(this.rootDoc.latitude, this.rootDoc.longitude, this.rootDoc.map);
+                const createPin = () => this.createPushpin(this.Document.latitude, this.Document.longitude, this.Document.map);
                 if (this.bingSearchBarContents) {
                     this.bingSearch().then(createPin);
                 } else createPin();
@@ -864,7 +860,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         );
     };
 
-        // incrementer: number = 0;
+    // incrementer: number = 0;
     /*
      * Creates Pushpin doc and adds it to the list of annotations
      */
@@ -877,13 +873,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             false,
             [],
             {
-                title: location ?? `lat=${NumCast(latitude)},lng=${NumCast(longitude)}`, 
-                map: location, 
-                description: "", 
+                title: location ?? `lat=${NumCast(latitude)},lng=${NumCast(longitude)}`,
+                map: location,
+                description: '',
                 wikiData: wikiData,
                 markerType: 'MAP_PIN',
-                markerColor: '#ff5722'
-            },
+                markerColor: '#ff5722',
+            }
             // { title: map ?? `lat=${latitude},lng=${longitude}`, map: map },
             // ,'pushpinIDamongus'+ this.incrementer++
         );
@@ -896,11 +892,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     createMapRoute = undoable((coordinates: Position[], origin: string, destination: any, createPinForDestination: boolean) => {
-        const mapRoute = Docs.Create.MapRouteDocument(
-            false,
-            [],
-            {title: `${origin} --> ${destination.place_name}`, routeCoordinates: JSON.stringify(coordinates)},
-        );
+        const mapRoute = Docs.Create.MapRouteDocument(false, [], { title: `${origin} --> ${destination.place_name}`, routeCoordinates: JSON.stringify(coordinates) });
         this.addDocument(mapRoute, this.annotationKey);
         if (createPinForDestination) {
             this.createPushpin(destination.center[1], destination.center[0], destination.place_name);
@@ -912,43 +904,29 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     searchbarKeyDown = (e: any) => e.key === 'Enter' && this.bingSearch();
 
-
-
-
-
-
     @observable
     featuresFromGeocodeResults: any[] = [];
 
-
     @action
     addMarkerForFeature = (feature: any) => {
         const location = feature.place_name;
-        if (feature.center){
+        if (feature.center) {
             const longitude = feature.center[0];
             const latitude = feature.center[1];
             const wikiData = feature.properties?.wikiData;
-    
-            this.createPushpin(
-                latitude, 
-                longitude, 
-                location, 
-                wikiData
-            )
-
-            if (this._mapRef.current){
+
+            this.createPushpin(latitude, longitude, location, wikiData);
+
+            if (this._mapRef.current) {
                 this._mapRef.current.flyTo({
-                    center: feature.center
+                    center: feature.center,
                 });
             }
             this.featuresFromGeocodeResults = [];
-
         } else {
             // TODO: handle error
         }
-    }
-
-
+    };
 
     /**
      * Makes a forward geocoding API call to Mapbox to retrieve locations based on the search input
@@ -956,12 +934,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
      */
     handleSearchChange = async (searchText: string) => {
         const features = await MapboxApiUtility.forwardGeocodeForFeatures(searchText);
-        if (features && !this.isAnimating){
+        if (features && !this.isAnimating) {
             runInAction(() => {
-                this.settingsOpen= false;
+                this.settingsOpen = false;
                 this.featuresFromGeocodeResults = features;
                 this.routeToAnimate = undefined;
-            })
+            });
         }
         // try {
         //     const url = MAPBOX_FORWARD_GEOCODE_BASE_URL + encodeURI(searchText) +'.json' +`?access_token=${MAPBOX_ACCESS_TOKEN}`;
@@ -971,34 +949,31 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         //         this.featuresFromGeocodeResults = data.features;
         //     })
         // } catch (error: any){
-        //     // TODO: handle error in better way 
+        //     // TODO: handle error in better way
         //     console.log(error);
         // }
-    }
+    };
     // @action
     // debouncedCall = React.useCallback(debounce(this.debouncedOnSearchBarChange, 300), []);
 
-
     @action
     handleMapClick = (e: MapLayerMouseEvent) => {
-        if (this._mapRef.current){
-            const features = this._mapRef.current.queryRenderedFeatures(
-                e.point, {
-                    layers: ['map-routes-layer']
-                }
-            );
+        if (this._mapRef.current) {
+            const features = this._mapRef.current.queryRenderedFeatures(e.point, {
+                layers: ['map-routes-layer'],
+            });
 
             console.error(features);
             if (features && features.length > 0 && features[0].properties && features[0].geometry) {
                 const geometry = features[0].geometry as LineString;
                 const routeTitle: string = features[0].properties['routeTitle'];
-                const routeDoc: Doc | undefined = this.allRoutes.find((routeDoc) => routeDoc.title === routeTitle);
+                const routeDoc: Doc | undefined = this.allRoutes.find(routeDoc => routeDoc.title === routeTitle);
                 this.deselectPinOrRoute(); // TODO: Also deselect route if selected
-                if (routeDoc){
+                if (routeDoc) {
                     this.selectedPinOrRoute = routeDoc;
-                    Doc.setDocFilter(this.rootDoc, LinkedTo, `mapRoute=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
+                    Doc.setDocFilter(this.Document, LinkedTo, `mapRoute=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
 
-                    // TODO: Recolor route 
+                    // TODO: Recolor route
 
                     MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute;
                     MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
@@ -1008,9 +983,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     MapAnchorMenu.Instance.setRouteDoc(routeDoc);
 
                     // TODO: Subject to change
-                    MapAnchorMenu.Instance.setAllMapboxPins(
-                        this.allAnnotations.filter(anno => !anno.layout_unrendered)
-                    )
+                    MapAnchorMenu.Instance.setAllMapboxPins(this.allAnnotations.filter(anno => !anno.layout_unrendered));
 
                     MapAnchorMenu.Instance.DisplayRoute = this.displayRoute;
                     MapAnchorMenu.Instance.HideRoute = this.hideRoute;
@@ -1025,16 +998,15 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     MapAnchorMenu.Instance.jumpTo(e.originalEvent.clientX, e.originalEvent.clientY, true);
 
                     document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
-                }        
+                }
             }
         }
-    }
-
+    };
 
     /**
      * Makes a reverse geocoding API call to retrieve features corresponding to a map click (based on longitude
-     * and latitude). Sets the search results accordingly. 
-     * @param e 
+     * and latitude). Sets the search results accordingly.
+     * @param e
      */
     handleMapDblClick = async (e: MapLayerMouseEvent) => {
         e.preventDefault();
@@ -1043,13 +1015,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const latitude: number = lngLat.lat;
 
         const features = await MapboxApiUtility.reverseGeocodeForFeatures(longitude, latitude);
-        if (features){
+        if (features) {
             runInAction(() => {
                 this.featuresFromGeocodeResults = features;
-            })
+            });
         }
 
-        // // REVERSE GEOCODE TO GET LOCATION DETAILS 
+        // // REVERSE GEOCODE TO GET LOCATION DETAILS
         // try {
         //     const url = MAPBOX_REVERSE_GEOCODE_BASE_URL + encodeURI(longitude.toString() + "," + latitude.toString()) + '.json' +
         //         `?access_token=${MAPBOX_ACCESS_TOKEN}`;
@@ -1063,9 +1035,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         //     // TODO: handle error in better way
         //     console.log(error);
         // }
-    }
+    };
 
-    @observable 
+    @observable
     currentPopup: PopupInfo | undefined = undefined;
 
     @action
@@ -1075,23 +1047,20 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         this.selectedPinOrRoute = pinDoc;
         // this.bingSearchBarContents = pinDoc.map;
 
-        // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'match');
-        // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'match');
-        Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
+        // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'match');
+        // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'match');
+        Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
 
         this.recolorPin(this.selectedPinOrRoute, 'green'); // TODO: check this method
-       
-        
+
         MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute;
         MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
         MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
         MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
 
-        // pass in the pinDoc 
+        // pass in the pinDoc
         MapAnchorMenu.Instance.setPinDoc(pinDoc);
-        MapAnchorMenu.Instance.setAllMapboxPins(
-            this.allAnnotations.filter(anno => !anno.layout_unrendered)
-        )
+        MapAnchorMenu.Instance.setAllMapboxPins(this.allAnnotations.filter(anno => !anno.layout_unrendered));
 
         MapAnchorMenu.Instance.DisplayRoute = this.displayRoute;
         MapAnchorMenu.Instance.HideRoute = this.hideRoute;
@@ -1106,22 +1075,20 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
         document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
 
-        
-            // this._mapRef.current.flyTo({
-            //     center: [NumCast(pinDoc.longitude), NumCast(pinDoc.latitude)-3]
-            // })
-        
+        // this._mapRef.current.flyTo({
+        //     center: [NumCast(pinDoc.longitude), NumCast(pinDoc.latitude)-3]
+        // })
     };
 
     @observable
     temporaryRouteSource: FeatureCollection = {
         type: 'FeatureCollection',
-        features: []
-    }
+        features: [],
+    };
 
     @action
     displayRoute = (routeInfoMap: Record<TransportationType, any> | undefined, type: TransportationType) => {
-        if (routeInfoMap){
+        if (routeInfoMap) {
             const newTempRouteSource: FeatureCollection = {
                 type: 'FeatureCollection',
                 features: [
@@ -1130,16 +1097,16 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         properties: {},
                         geometry: {
                             type: 'LineString',
-                            coordinates: routeInfoMap[type].coordinates
-                        }
-                    }
-                ]
-            }
-            // TODO: Create pin for destination 
-            // TODO: Fly to point where full route will be shown 
+                            coordinates: routeInfoMap[type].coordinates,
+                        },
+                    },
+                ],
+            };
+            // TODO: Create pin for destination
+            // TODO: Fly to point where full route will be shown
             this.temporaryRouteSource = newTempRouteSource;
         }
-    }
+    };
 
     @observable
     isAnimating: boolean = false;
@@ -1147,10 +1114,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @observable
     routeToAnimate: Doc | undefined = undefined;
 
-    @observable 
+    @observable
     animationPhase: number = 0;
 
-    @observable 
+    @observable
     finishedFlyTo: boolean = false;
 
     @action
@@ -1158,73 +1125,71 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         this.animationPhase = newValue;
     };
 
-    @observable 
+    @observable
     frameId: number | null = null;
 
     @action
     setFrameId = (frameId: number) => {
         this.frameId = frameId;
-    }
+    };
 
-    @observable 
+    @observable
     animationUtility: AnimationUtility | null = null;
 
     @action
     setAnimationUtility = (util: AnimationUtility) => {
         this.animationUtility = util;
-    }
+    };
 
     @action
     openAnimationPanel = (routeDoc: Doc | undefined) => {
-        if (routeDoc){
+        if (routeDoc) {
             MapAnchorMenu.Instance.fadeOut(true);
             document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
             this.routeToAnimate = routeDoc;
         }
-    }
+    };
 
     @observable
     mapboxMapViewState: ViewState = {
         zoom: this.dataDoc.map_zoom ? NumCast(this.dataDoc.map_zoom) : 8,
-        longitude: this.dataDoc.longitude ? NumCast(this.dataDoc.longitude) : -71.4128, 
-        latitude: this.dataDoc.latitude ? NumCast(this.dataDoc.latitude) : 41.8240,
+        longitude: this.dataDoc.longitude ? NumCast(this.dataDoc.longitude) : -71.4128,
+        latitude: this.dataDoc.latitude ? NumCast(this.dataDoc.latitude) : 41.824,
         pitch: this.dataDoc.map_pitch ? NumCast(this.dataDoc.map_pitch) : 0,
         bearing: this.dataDoc.map_bearing ? NumCast(this.dataDoc.map_bearing) : 0,
         padding: {
             top: 0,
             bottom: 0,
             left: 0,
-            right: 0
+            right: 0,
         },
-    }
+    };
 
-    @computed 
+    @computed
     get preAnimationViewState() {
-        if (!this.isAnimating){
+        if (!this.isAnimating) {
             return this.mapboxMapViewState;
         }
     }
 
-
-    @observable 
+    @observable
     isStreetViewAnimation: boolean = false;
 
-    @observable 
+    @observable
     animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM;
 
-
     @observable
     animationLineColor: string = '#ffff00';
 
     @action
-    setAnimationLineColor = (color: ColorState) => {
+    setAnimationLineColor = (color: ColorResult) => {
         this.animationLineColor = color.hex;
-    } 
+    };
 
     @action
     updateAnimationSpeed = () => {
         let newAnimationSpeed: AnimationSpeed;
-        switch (this.animationSpeed){
+        switch (this.animationSpeed) {
             case AnimationSpeed.SLOW:
                 newAnimationSpeed = AnimationSpeed.MEDIUM;
                 break;
@@ -1239,63 +1204,62 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 break;
         }
         this.animationSpeed = newAnimationSpeed;
-        if (this.animationUtility){
+        if (this.animationUtility) {
             this.animationUtility.updateAnimationSpeed(newAnimationSpeed);
         }
-    }
+    };
     @computed get animationSpeedTooltipText(): string {
         switch (this.animationSpeed) {
-          case AnimationSpeed.SLOW:
-            return '1x speed';
-          case AnimationSpeed.MEDIUM:
-            return '2x speed';
-          case AnimationSpeed.FAST:
-            return '3x speed';
-          default:
-            return '2x speed'; 
+            case AnimationSpeed.SLOW:
+                return '1x speed';
+            case AnimationSpeed.MEDIUM:
+                return '2x speed';
+            case AnimationSpeed.FAST:
+                return '3x speed';
+            default:
+                return '2x speed';
         }
     }
-    @computed get animationSpeedIcon(): JSX.Element{
+    @computed get animationSpeedIcon(): JSX.Element {
         switch (this.animationSpeed) {
             case AnimationSpeed.SLOW:
-              return slowSpeedIcon;
+                return slowSpeedIcon;
             case AnimationSpeed.MEDIUM:
-              return mediumSpeedIcon;
+                return mediumSpeedIcon;
             case AnimationSpeed.FAST:
-              return fastSpeedIcon;
+                return fastSpeedIcon;
             default:
-              return mediumSpeedIcon; 
-          }
+                return mediumSpeedIcon;
+        }
     }
 
     @action
     toggleIsStreetViewAnimation = () => {
         const newVal = !this.isStreetViewAnimation;
         this.isStreetViewAnimation = newVal;
-        if (this.animationUtility){
-            this.animationUtility.updateIsStreetViewAnimation(newVal)
+        if (this.animationUtility) {
+            this.animationUtility.updateIsStreetViewAnimation(newVal);
         }
-    }
+    };
 
-    @observable 
+    @observable
     dynamicRouteFeature: Feature<Geometry, GeoJsonProperties> = {
         type: 'Feature',
         properties: {},
         geometry: {
             type: 'LineString',
-            coordinates: []
-        }
+            coordinates: [],
+        },
     };
 
-
-    @observable 
+    @observable
     path: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties> = {
         type: 'Feature',
         geometry: {
             type: 'LineString',
-            coordinates: []
+            coordinates: [],
         },
-        properties: {}
+        properties: {},
     };
 
     getFeatureFromRouteDoc = (routeDoc: Doc): Feature<Geometry, GeoJsonProperties> => {
@@ -1306,14 +1270,15 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return {
             type: 'Feature',
             properties: {
-                'routeTitle': routeDoc.title},
+                routeTitle: routeDoc.title,
+            },
             geometry: geometry,
         };
-    }
+    };
 
-    @action 
+    @action
     playAnimation = (status: AnimationStatus) => {
-        if (!this._mapRef.current || !this.routeToAnimate){
+        if (!this._mapRef.current || !this.routeToAnimate) {
             return;
         }
 
@@ -1322,43 +1287,32 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         this.finishedFlyTo = AnimationStatus.RESUME ? this.finishedFlyTo : false;
 
         const path = turf.lineString(this.selectedRouteCoordinates);
-        
+
         this.settingsOpen = false;
         this.path = path;
         this.isAnimating = true;
 
         runInAction(() => {
-            return new Promise<void>(async (resolve) => {
+            return new Promise<void>(async resolve => {
                 const targetLngLat = {
                     lng: this.selectedRouteCoordinates[0][0],
                     lat: this.selectedRouteCoordinates[0][1],
                 };
 
-                const animationUtil = new AnimationUtility(
-                    targetLngLat,
-                    this.selectedRouteCoordinates,
-                    this.isStreetViewAnimation,
-                    this.animationSpeed,
-                    this.showTerrain,
-                    this._mapRef.current
-                );
+                const animationUtil = new AnimationUtility(targetLngLat, this.selectedRouteCoordinates, this.isStreetViewAnimation, this.animationSpeed, this.showTerrain, this._mapRef.current);
                 runInAction(() => {
                     this.setAnimationUtility(animationUtil);
-                })
-
+                });
 
                 const updateFrameId = (newFrameId: number) => {
                     this.setFrameId(newFrameId);
-                }
+                };
 
-                const updateAnimationPhase = (
-                    newAnimationPhase: number,
-                    ) => {
+                const updateAnimationPhase = (newAnimationPhase: number) => {
                     this.setAnimationPhase(newAnimationPhase);
                 };
-                
-                if (status !== AnimationStatus.RESUME) {
 
+                if (status !== AnimationStatus.RESUME) {
                     const result = await animationUtil.flyInAndRotate({
                         map: this._mapRef.current!,
                         // targetLngLat,
@@ -1372,18 +1326,17 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         updateFrameId,
                     });
 
-                    console.log("Bearing: ", result.bearing);
-                    console.log("Altitude: ", result.altitude);
-
-                } 
+                    console.log('Bearing: ', result.bearing);
+                    console.log('Altitude: ', result.altitude);
+                }
 
                 runInAction(() => {
                     this.finishedFlyTo = true;
-                })
+                });
 
                 // follow the path while slowly rotating the camera, passing in the camera bearing and altitude from the previous animation
                 await animationUtil.animatePath({
-                    map: this._mapRef.current!, 
+                    map: this._mapRef.current!,
                     // path: this.path,
                     // startBearing: -20,
                     // startAltitude: this.isStreetViewAnimation ? 80 : 12000,
@@ -1392,43 +1345,39 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     updateAnimationPhase,
                     updateFrameId,
                 });
-            
+
                 // get the bounds of the linestring, use fitBounds() to animate to a final view
                 const bbox3d = turf.bbox(this.path);
-    
+
                 const bbox2d: LngLatBoundsLike = [bbox3d[0], bbox3d[1], bbox3d[2], bbox3d[3]];
-    
+
                 this._mapRef.current!.fitBounds(bbox2d, {
                     duration: 3000,
                     pitch: 30,
                     bearing: 0,
                     padding: 120,
                 });
-            
+
                 setTimeout(() => {
                     this.isStreetViewAnimation = false;
                     resolve();
                 }, 10000);
             });
-            
-        })
-            
-
-    }
-
+        });
+    };
 
-    @action 
+    @action
     pauseAnimation = () => {
-        if (this.frameId && this.animationPhase > 0){
+        if (this.frameId && this.animationPhase > 0) {
             window.cancelAnimationFrame(this.frameId);
             this.frameId = null;
             this.isAnimating = false;
         }
-    }
+    };
 
     @action
     stopAnimation = (close: boolean) => {
-        if (this.frameId){
+        if (this.frameId) {
             window.cancelAnimationFrame(this.frameId);
         }
         this.animationPhase = 0;
@@ -1441,22 +1390,18 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             this.routeToAnimate = undefined;
             this.animationUtility = null;
         }
-        if (this.preAnimationViewState){
+        if (this.preAnimationViewState) {
             this.mapboxMapViewState = this.preAnimationViewState;
         }
-        
- 
-    }
+    };
 
     @action
-    exportAnimationToVideo = () => {
-
-    }
+    exportAnimationToVideo = () => {};
 
     getRouteAnimationOptions = (): JSX.Element => {
         return (
             <>
-                <IconButton 
+                <IconButton
                     tooltip={this.isAnimating && this.finishedFlyTo ? 'Pause Animation' : 'Play Animation'}
                     onPointerDown={() => {
                         if (this.isAnimating && this.finishedFlyTo) {
@@ -1467,87 +1412,57 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             this.playAnimation(AnimationStatus.START); // Play from the beginning
                         }
                     }}
-                    icon={this.isAnimating && this.finishedFlyTo ? 
-                        <FontAwesomeIcon icon={faPause as IconLookup}/>  
-                        :
-                        <FontAwesomeIcon icon={faPlay as IconLookup}/>    
-                    }
-                    color='black'
-                    size={Size.MEDIUM}
-                />
-                {this.isAnimating && this.finishedFlyTo && 
-                <IconButton 
-                    tooltip='Restart animation'
-                    onPointerDown={() => {
-                        this.stopAnimation(false);
-                        this.playAnimation(AnimationStatus.START)
-                    }}
-                    icon={<FontAwesomeIcon icon={faRotate as IconLookup}/>}
-                    color='black'
-                    size={Size.MEDIUM}
-                />
-                    
-                }
-                <IconButton 
-                    style={{marginRight: '10px'}}
-                    tooltip='Stop and close animation'
-                    onPointerDown={() => this.stopAnimation(true)}
-                    icon={<FontAwesomeIcon icon={faCircleXmark as IconLookup}/>}
-                    color='black'
+                    icon={this.isAnimating && this.finishedFlyTo ? <FontAwesomeIcon icon={faPause as IconLookup} /> : <FontAwesomeIcon icon={faPlay as IconLookup} />}
+                    color="black"
                     size={Size.MEDIUM}
                 />
+                {this.isAnimating && this.finishedFlyTo && (
+                    <IconButton
+                        tooltip="Restart animation"
+                        onPointerDown={() => {
+                            this.stopAnimation(false);
+                            this.playAnimation(AnimationStatus.START);
+                        }}
+                        icon={<FontAwesomeIcon icon={faRotate as IconLookup} />}
+                        color="black"
+                        size={Size.MEDIUM}
+                    />
+                )}
+                <IconButton style={{ marginRight: '10px' }} tooltip="Stop and close animation" onPointerDown={() => this.stopAnimation(true)} icon={<FontAwesomeIcon icon={faCircleXmark as IconLookup} />} color="black" size={Size.MEDIUM} />
                 <>
-                    <div className='animation-suboptions'>
+                    <div className="animation-suboptions">
                         <div>|</div>
                         <FormControlLabel
-                            className='first-person-label'
-                            style={{width: '130px'}}
-                            label='1st person animation:'
-                            labelPlacement='start'
-                            control={
-                                <Checkbox 
-                                    color='success'
-                                    checked={this.isStreetViewAnimation}
-                                    onChange={this.toggleIsStreetViewAnimation}
-                                />
-                            }
-                        />
-                        <div id='divider'>|</div>
-                        <IconButton
-                            tooltip={this.animationSpeedTooltipText}
-                            onPointerDown={this.updateAnimationSpeed}
-                            icon={this.animationSpeedIcon}
-                            size={Size.MEDIUM}
+                            className="first-person-label"
+                            style={{ width: '130px' }}
+                            label="1st person animation:"
+                            labelPlacement="start"
+                            control={<Checkbox color="success" checked={this.isStreetViewAnimation} onChange={this.toggleIsStreetViewAnimation} />}
                         />
-                        <div id='divider'>|</div>
-                        <div style={{width: '230px'}}>Select Line Color: </div>
-                        <CirclePicker
-                            circleSize={12}
-                            circleSpacing={5}
-                            width='100%'
-                            colors={['#ffff00', '#03a9f4', '#ff0000', '#ff5722', '#000000', '#673ab7']}
-                            onChange={(color) => this.setAnimationLineColor(color)}
-                    />
+                        <div id="divider">|</div>
+                        <IconButton tooltip={this.animationSpeedTooltipText} onPointerDown={this.updateAnimationSpeed} icon={this.animationSpeedIcon} size={Size.MEDIUM} />
+                        <div id="divider">|</div>
+                        <div style={{ width: '230px' }}>Select Line Color: </div>
+                        <CirclePicker circleSize={12} circleSpacing={5} width="100%" colors={['#ffff00', '#03a9f4', '#ff0000', '#ff5722', '#000000', '#673ab7']} onChange={color => this.setAnimationLineColor(color)} />
                     </div>
-                </>            
+                </>
             </>
-        )
-    }
+        );
+    };
 
     @action
     hideRoute = () => {
         this.temporaryRouteSource = {
             type: 'FeatureCollection',
-            features: []
-        }
-    }
-
+            features: [],
+        };
+    };
 
-    @observable 
+    @observable
     settingsOpen: boolean = false;
 
-    @observable 
-    mapStyle: string = 'mapbox://styles/mapbox/streets-v12'
+    @observable
+    mapStyle: string = 'mapbox://styles/mapbox/streets-v12';
 
     @observable
     showTerrain: boolean = true;
@@ -1558,103 +1473,94 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             this.featuresFromGeocodeResults = [];
             this.settingsOpen = !this.settingsOpen;
         }
-    }
+    };
 
     @action
     changeMapStyle = (e: React.ChangeEvent<HTMLSelectElement>) => {
         this.dataDoc.map_style = `mapbox://styles/mapbox/${e.target.value}`;
         // this.mapStyle = `mapbox://styles/mapbox/${e.target.value}`
-    }
+    };
 
-    @action 
+    @action
     onBearingChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         const bearing = parseInt(e.target.value);
-        if (!isNaN(bearing) && this._mapRef.current){
+        if (!isNaN(bearing) && this._mapRef.current) {
             const fixedBearing = Math.max(0, Math.min(360, bearing));
             this._mapRef.current.setBearing(fixedBearing);
             this.dataDoc.map_bearing = fixedBearing;
             this.mapboxMapViewState = {
                 ...this.mapboxMapViewState,
-                bearing: fixedBearing
-            }
+                bearing: fixedBearing,
+            };
         }
-    }
+    };
 
-    @action 
+    @action
     onPitchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         const pitch = parseInt(e.target.value);
-        if (!isNaN(pitch) && this._mapRef.current){
+        if (!isNaN(pitch) && this._mapRef.current) {
             const fixedPitch = Math.max(0, Math.min(85, pitch));
             this._mapRef.current.setPitch(fixedPitch);
             this.dataDoc.map_pitch = fixedPitch;
             this.mapboxMapViewState = {
                 ...this.mapboxMapViewState,
-                pitch: fixedPitch
-            }
+                pitch: fixedPitch,
+            };
         }
-    }
+    };
 
-    @action 
+    @action
     onZoomChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         const zoom = parseInt(e.target.value);
-        if (!isNaN(zoom) && this._mapRef.current){
+        if (!isNaN(zoom) && this._mapRef.current) {
             const fixedZoom = Math.max(0, Math.min(16, zoom));
             this._mapRef.current.setZoom(fixedZoom);
             this.dataDoc.map_zoom = fixedZoom;
             this.mapboxMapViewState = {
                 ...this.mapboxMapViewState,
-                zoom: fixedZoom
-            }
+                zoom: fixedZoom,
+            };
         }
-    }
+    };
 
     @action
     onStepZoomChange = (increment: boolean) => {
         if (this._mapRef.current) {
             let newZoom: number;
             if (increment) {
-                console.log('inc')
+                console.log('inc');
                 newZoom = this.mapboxMapViewState.zoom + 1;
-                
             } else {
-                console.log('dec')
+                console.log('dec');
                 newZoom = this.mapboxMapViewState.zoom - 1;
             }
             this._mapRef.current.setZoom(newZoom);
             this.dataDoc.map_zoom = newZoom;
             this.mapboxMapViewState = {
                 ...this.mapboxMapViewState,
-                zoom: increment ? Math.min(16, newZoom) : Math.max(0, newZoom)
-            }
+                zoom: increment ? Math.min(16, newZoom) : Math.max(0, newZoom),
+            };
         }
-        
-    }
-
+    };
 
     @action
     onMapMove = (e: ViewStateChangeEvent) => {
         this.mapboxMapViewState = e.viewState;
         this.dataDoc.longitude = e.viewState.longitude;
         this.dataDoc.latitude = e.viewState.latitude;
-    }
+    };
 
     @action
     toggleShowTerrain = () => {
         this.showTerrain = !this.showTerrain;
-    }
+    };
 
     getMarkerIcon = (pinDoc: Doc): JSX.Element | null => {
         const markerType = StrCast(pinDoc.markerType);
         const markerColor = StrCast(pinDoc.markerColor);
 
         return MarkerIcons.getFontAwesomeIcon(markerType, '2x', markerColor) ?? null;
-        
-    }
-
-
-
-
-
+    };
 
     static _firstRender = true;
     static _rerenderDelay = 500;
@@ -1662,7 +1568,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     render() {
         // bcz:  no idea what's going on here, but bings maps have some kind of bug
         // such that we need to delay rendering a second map on startup until the first map is rendered.
-        this.rootDoc[DocCss];
+        this.Document[DocCss];
         if (MapBox._rerenderDelay) {
             // prettier-ignore
             this._rerenderTimeout = this._rerenderTimeout ??
@@ -1671,12 +1577,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         MapBox._rerenderDelay = 0;
                     }
                     this._rerenderTimeout = undefined;
-                    this.rootDoc[DocCss] = this.rootDoc[DocCss] + 1;
+                    this.Document[DocCss] = this.Document[DocCss] + 1;
                 }), MapBox._rerenderDelay);
             return null;
         }
 
-        const scale = this.props.NativeDimScaling?.() || 1;
+        const scale = this._props.NativeDimScaling?.() || 1;
 
         const renderAnnotations = (childFilters?: () => string[]) => null;
         return (
@@ -1687,108 +1593,76 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     onPointerDown={async e => {
                         e.button === 0 && !e.ctrlKey && e.stopPropagation();
                     }}
-                    style={{ transformOrigin: "top left",  transform: `scale(${scale})`, width: `${100 / scale}%`, height: `${100 / scale}%`, pointerEvents: this.pointerEvents() }}>
+                    style={{ pointerEvents: this.pointerEvents() }}>
                     <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
                     {renderAnnotations(this.opaqueFilter)}
-                    {SnappingManager.GetIsDragging() ? null : renderAnnotations()}
-                    {!this.routeToAnimate &&
+                    {SnappingManager.IsDragging ? null : renderAnnotations()}
+                    {!this.routeToAnimate && (
                         <div className="mapBox-searchbar">
-                            <TextField 
-                                fullWidth
-                                placeholder='Enter a location'
-                                onChange={(e) => this.handleSearchChange(e.target.value)}
-                            />
-                            <IconButton
-                                icon={<FontAwesomeIcon icon={faGear as IconLookup} size='1x'/>}
-                                type={Type.TERT}
-                                onClick={(e) => this.toggleSettings()}
-
-                            />
-                        </div> 
-                    }
-                    {this.settingsOpen && !this.routeToAnimate && 
-                        <div className='mapbox-settings-panel' style={{right: `${0+ this.sidebarWidth()}px`}}>
-                            <div className='mapbox-style-select'>
-                                <div>
-                                    Map Style:
-                                </div>
+                            <TextField fullWidth placeholder="Enter a location" onChange={(e: any) => this.handleSearchChange(e.target.value)} />
+                            <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={e => this.toggleSettings()} />
+                        </div>
+                    )}
+                    {this.settingsOpen && !this.routeToAnimate && (
+                        <div className="mapbox-settings-panel" style={{ right: `${0 + this.sidebarWidth()}px` }}>
+                            <div className="mapbox-style-select">
+                                <div>Map Style:</div>
                                 <div>
                                     <select onChange={this.changeMapStyle}>
-                                        <option value='streets-v11'>Streets</option>
-                                        <option value='outdoors-v12'>Outdoors</option>
-                                        <option value='light-v11'>Light</option>
-                                        <option value='dark-v11'>Dark</option>
-                                        <option value='satellite-v9'>Satellite</option>
-                                        <option value='satellite-streets-v12'>Satellite Streets</option>
-                                        <option value='navigation-day-v1'>Navigation Day</option>
-                                        <option value='navigation-night-v1'>Navigation Night</option>
+                                        <option value="streets-v11">Streets</option>
+                                        <option value="outdoors-v12">Outdoors</option>
+                                        <option value="light-v11">Light</option>
+                                        <option value="dark-v11">Dark</option>
+                                        <option value="satellite-v9">Satellite</option>
+                                        <option value="satellite-streets-v12">Satellite Streets</option>
+                                        <option value="navigation-day-v1">Navigation Day</option>
+                                        <option value="navigation-night-v1">Navigation Night</option>
                                     </select>
                                 </div>
                             </div>
-                            <div className='mapbox-bearing-selection'>
+                            <div className="mapbox-bearing-selection">
                                 <div>Bearing: </div>
-                                <input 
-                                    value={NumCast(this.mapboxMapViewState.bearing).toFixed(2)}
-                                    type='number'
-                                    onChange={this.onBearingChange}/>
+                                <input value={NumCast(this.mapboxMapViewState.bearing).toFixed(2)} type="number" onChange={this.onBearingChange} />
                             </div>
-                            <div className='mapbox-pitch-selection'>
+                            <div className="mapbox-pitch-selection">
                                 <div>Pitch: </div>
-                                    <input 
-                                        value={NumCast(this.mapboxMapViewState.pitch).toFixed(2)}
-                                        type='number'
-                                        onChange={this.onPitchChange}/>
+                                <input value={NumCast(this.mapboxMapViewState.pitch).toFixed(2)} type="number" onChange={this.onPitchChange} />
                             </div>
-                            <div className='mapbox-pitch-selection'>
+                            <div className="mapbox-pitch-selection">
                                 <div>Zoom: </div>
-                                    <input 
-                                        value={NumCast(this.mapboxMapViewState.zoom).toFixed(2)}
-                                        type='number'
-                                        onChange={this.onZoomChange}/>
+                                <input value={NumCast(this.mapboxMapViewState.zoom).toFixed(2)} type="number" onChange={this.onZoomChange} />
                             </div>
-                            <div className='mapbox-terrain-selection'>
+                            <div className="mapbox-terrain-selection">
                                 <div>Show terrain: </div>
-                                <input 
-                                    type='checkbox'
-                                    checked={this.showTerrain}
-                                    onChange={this.toggleShowTerrain}
-                                    />
+                                <input type="checkbox" checked={this.showTerrain} onChange={this.toggleShowTerrain} />
                             </div>
                         </div>
-                    }
-                    {this.routeToAnimate &&
-                        <div className='animation-panel'>
-                            <div id='route-to-animate-title'>
-                                {StrCast(this.routeToAnimate.title)}
-                            </div>
-                            <div className='route-animation-options'>
-                                {this.getRouteAnimationOptions()}
-                            </div>
+                    )}
+                    {this.routeToAnimate && (
+                        <div className="animation-panel">
+                            <div id="route-to-animate-title">{StrCast(this.routeToAnimate.title)}</div>
+                            <div className="route-animation-options">{this.getRouteAnimationOptions()}</div>
                         </div>
-                    }
+                    )}
                     {this.featuresFromGeocodeResults.length > 0 && (
-                        <div className='mapbox-geocoding-search-results'>
+                        <div className="mapbox-geocoding-search-results">
                             <React.Fragment>
                                 <h4>Choose a location for your pin: </h4>
                                 {this.featuresFromGeocodeResults
                                     .filter(feature => feature.place_name)
                                     .map((feature, idx) => (
-                                    <div 
-                                        key={idx} 
-                                        className='search-result-container'
-                                        onClick={() => {
-                                            this.handleSearchChange("");
-                                            this.addMarkerForFeature(feature);
-                                        }}
-                                    >
-                                        <div className='search-result-place-name'> 
-                                        {feature.place_name}
+                                        <div
+                                            key={idx}
+                                            className="search-result-container"
+                                            onClick={() => {
+                                                this.handleSearchChange('');
+                                                this.addMarkerForFeature(feature);
+                                            }}>
+                                            <div className="search-result-place-name">{feature.place_name}</div>
                                         </div>
-                                    </div>
-                                ))}
+                                    ))}
                             </React.Fragment>
-                            
-                        </div>   
+                        </div>
                     )}
                     {/* <div className='zoom-box'>
                         <IconButton // increment 
@@ -1810,112 +1684,94 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             ref={this._mapRef}
                             mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
                             id="mapbox-map"
+                            viewState={{ ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width) / scale, height: NumCast(this.layoutDoc._height) / scale }}
                             mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'}
-                            style={{height: NumCast(this.layoutDoc._height), width: NumCast(this.layoutDoc._width)}}
                             initialViewState={this.isAnimating ? undefined : this.mapboxMapViewState}
                             onMove={this.onMapMove}
                             onClick={this.handleMapClick}
                             onDblClick={this.handleMapDblClick}
-                            terrain={this.showTerrain ? { source: 'mapbox-dem', exaggeration: 2.0 } : undefined}
-                            
-                            
-                        >
-                            <Source
-                                id="mapbox-dem"
-                                type="raster-dem"
-                                url="mapbox://mapbox.mapbox-terrain-dem-v1"
-                                tileSize={512}
-                                maxzoom={14}
-                            />
-                            <Source id='temporary-route' type='geojson' data={this.temporaryRouteSource}/>
-                            <Source id='map-routes' type='geojson' data={this.allRoutesGeoJson}/>
-                            <Layer
-                                id='temporary-route-layer'
-                                type='line'
-                                source='temporary-route'
-                                layout={{"line-join": "round", "line-cap": "round"}}
-                                paint={{"line-color": "#36454F", "line-width": 4, "line-dasharray": [1,1]}}
-                                />
-                            {!this.isAnimating && this.animationPhase == 0 &&
-                            <Layer
-                                id='map-routes-layer'
-                                type='line'
-                                source='map-routes'
-                                layout={{"line-join": "round", "line-cap": "round"}}
-                                paint={{"line-color": "#FF0000", "line-width": 4}}
-                                />
-                            }
-                            {this.routeToAnimate && (this.isAnimating || this.animationPhase > 0) &&
+                            terrain={this.showTerrain ? { source: 'mapbox-dem', exaggeration: 2.0 } : undefined}>
+                            <Source id="mapbox-dem" type="raster-dem" url="mapbox://mapbox.mapbox-terrain-dem-v1" tileSize={512} maxzoom={14} />
+                            <Source id="temporary-route" type="geojson" data={this.temporaryRouteSource} />
+                            <Source id="map-routes" type="geojson" data={this.allRoutesGeoJson} />
+                            <Layer id="temporary-route-layer" type="line" source="temporary-route" layout={{ 'line-join': 'round', 'line-cap': 'round' }} paint={{ 'line-color': '#36454F', 'line-width': 4, 'line-dasharray': [1, 1] }} />
+                            {!this.isAnimating && this.animationPhase == 0 && <Layer id="map-routes-layer" type="line" source="map-routes" layout={{ 'line-join': 'round', 'line-cap': 'round' }} paint={{ 'line-color': '#FF0000', 'line-width': 4 }} />}
+                            {this.routeToAnimate && (this.isAnimating || this.animationPhase > 0) && (
                                 <>
-                                    {!this.isStreetViewAnimation &&
+                                    {!this.isStreetViewAnimation && (
                                         <>
-                                            <Source id='animated-route' type='geojson' data={this.updatedRouteCoordinates}/>
-                                            <Layer 
-                                            id='dynamic-animation-line' 
-                                            type='line' 
-                                            source='animated-route'
-                                            paint={{
-                                                'line-color': this.animationLineColor,
-                                                'line-width': 5,
-                                            }}
+                                            <Source id="animated-route" type="geojson" data={this.updatedRouteCoordinates} />
+                                            <Layer
+                                                id="dynamic-animation-line"
+                                                type="line"
+                                                source="animated-route"
+                                                paint={{
+                                                    'line-color': this.animationLineColor,
+                                                    'line-width': 5,
+                                                }}
                                             />
                                         </>
-                                        }
-                                    <Source id='start-pin-base' type='geojson' data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.04)}/>
-                                    <Source id='start-pin-top' type='geojson' data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.25)}/>
-                                    <Source id='end-pin-base' type='geojson' data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.04)}/>
-                                    <Source id='end-pin-top' type='geojson' data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.25)}/>
-                                    <Layer id='start-fill-pin-base' type='fill-extrusion' source='start-pin-base'
+                                    )}
+                                    <Source id="start-pin-base" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.04)} />
+                                    <Source id="start-pin-top" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.25)} />
+                                    <Source id="end-pin-base" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.04)} />
+                                    <Source id="end-pin-top" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.25)} />
+                                    <Layer
+                                        id="start-fill-pin-base"
+                                        type="fill-extrusion"
+                                        source="start-pin-base"
                                         paint={{
                                             'fill-extrusion-color': '#0bfc03',
-                                            'fill-extrusion-height': 1000
+                                            'fill-extrusion-height': 1000,
                                         }}
                                     />
-                                    <Layer id='start-fill-pin-top' type='fill-extrusion' source='start-pin-top'
+                                    <Layer
+                                        id="start-fill-pin-top"
+                                        type="fill-extrusion"
+                                        source="start-pin-top"
                                         paint={{
                                             'fill-extrusion-color': '#0bfc03',
                                             'fill-extrusion-base': 1000,
-                                            'fill-extrusion-height': 1200
+                                            'fill-extrusion-height': 1200,
                                         }}
                                     />
-                                    <Layer id='end-fill-pin-base' type='fill-extrusion' source='end-pin-base'
+                                    <Layer
+                                        id="end-fill-pin-base"
+                                        type="fill-extrusion"
+                                        source="end-pin-base"
                                         paint={{
                                             'fill-extrusion-color': '#eb1c1c',
-                                            'fill-extrusion-height': 1000
+                                            'fill-extrusion-height': 1000,
                                         }}
                                     />
-                                    <Layer id='end-fill-pin-top' type='fill-extrusion' source='end-pin-top'
+                                    <Layer
+                                        id="end-fill-pin-top"
+                                        type="fill-extrusion"
+                                        source="end-pin-top"
                                         paint={{
                                             'fill-extrusion-color': '#eb1c1c',
                                             'fill-extrusion-base': 1000,
-                                            'fill-extrusion-height': 1200
+                                            'fill-extrusion-height': 1200,
                                         }}
                                     />
-                                    
                                 </>
-                            }
-                            
+                            )}
 
                             <>
-                                {!this.isAnimating && this.animationPhase == 0 && this.allPushpins
-                                    // .filter(anno => !anno.layout_unrendered)
-                                    .map((pushpin, idx) => (
-                                        <Marker 
-                                            key={idx} 
-                                            longitude={NumCast(pushpin.longitude)} 
-                                            latitude={NumCast(pushpin.latitude)}
-                                            anchor='bottom'
-                                            onClick={(e: MarkerEvent<mapboxgl.Marker, MouseEvent>) => this.handleMarkerClick(e, pushpin)}
-                                        >
-                                            {this.getMarkerIcon(pushpin)}
-                                        </Marker>
-                                ))}
+                                {!this.isAnimating &&
+                                    this.animationPhase == 0 &&
+                                    this.allPushpins
+                                        // .filter(anno => !anno.layout_unrendered)
+                                        .map((pushpin, idx) => (
+                                            <Marker key={idx} longitude={NumCast(pushpin.longitude)} latitude={NumCast(pushpin.latitude)} anchor="bottom" onClick={(e: MarkerEvent<mapboxgl.Marker, MouseEvent>) => this.handleMarkerClick(e, pushpin)}>
+                                                {this.getMarkerIcon(pushpin)}
+                                            </Marker>
+                                        ))}
                             </>
-                            
+
                             {/* {this.mapMarkers.length > 0 && this.mapMarkers.map((marker, idx) => (
                                 <Marker key={idx} longitude={marker.longitude} latitude={marker.latitude}/>
                             ))} */}
-
                         </MapboxMap>
                     </MapProvider>
 
@@ -1935,8 +1791,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                   .map((pushpin, i) => (
                                       <DocumentView
                                           key={i}
-                                          {...this.props}
-                                          renderDepth={this.props.renderDepth + 1}
+                                          {...this._props}
+                                          renderDepth={this._props.renderDepth + 1}
                                           Document={pushpin}
                                           DataDoc={undefined}
                                           PanelWidth={returnOne}
@@ -1960,7 +1816,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     </div> */}
                     {/* <MapBoxInfoWindow
                                     key={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})[Id]}
-                                    {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                                    {...OmitKeys(this._props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
                                     place={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})}
                                     markerMap={this.markerMap}
                                     PanelWidth={this.infoWidth}
@@ -1974,9 +1830,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 <div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
                     <SidebarAnnos
                         ref={this._sidebarRef}
-                        {...this.props}
+                        {...this._props}
                         fieldKey={this.fieldKey}
-                        rootDoc={this.rootDoc}
+                        Document={this.Document}
                         layoutDoc={this.layoutDoc}
                         dataDoc={this.dataDoc}
                         usePanelWidth={true}
@@ -1995,7 +1851,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }
 }
 
-{/* <Autocomplete
+{
+    /* <Autocomplete
                             fullWidth
                             id="map-location-searcher"
                             freeSolo
@@ -2014,8 +1871,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                     placeholder='Enter a location'
                                 />
                             )}
-                            /> */}
-                        {/* <EditableText
+                            /> */
+}
+{
+    /* <EditableText
                             // editing
                             setVal={(newText: string | number) => typeof newText === 'string' && this.handleSearchChange(newText)}
                             // onEnter={e => this.bingSearch()}
@@ -2024,8 +1883,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             // placeholder={this.bingSearchBarContents || 'Enter a location'}
                             placeholder='Enter a location'
                             textAlign="center" 
-                        /> */}
-                        {/* <IconButton
+                        /> */
+}
+{
+    /* <IconButton
                             icon={
                                 <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="magnifying-glass" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" color="#DFDFDF">
                                     <path
@@ -2038,4 +1899,5 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         />
                         <div style={{ width: 30, height: 30 }} ref={this._dragRef} onPointerDown={this.dragToggle}>
                             <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size={'lg'} icon={'bullseye'} />} />
-                        </div> */}
+                        </div> */
+}
-- 
cgit v1.2.3-70-g09d2


From 2902fc7d0b72a0b1139a50791501367719a5f897 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 15 Dec 2023 13:43:08 -0500
Subject: fixed map resizing.

---
 src/client/views/nodes/MapBox/MapBox.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 724c1a770..eb6a05a49 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1687,7 +1687,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     onPointerDown={async e => {
                         e.button === 0 && !e.ctrlKey && e.stopPropagation();
                     }}
-                    style={{ transformOrigin: "top left",  transform: `scale(${scale})`, width: `${100 / scale}%`, height: `${100 / scale}%`, pointerEvents: this.pointerEvents() }}>
+                    style={{ pointerEvents: this.pointerEvents() }}>
                     <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
                     {renderAnnotations(this.opaqueFilter)}
                     {SnappingManager.GetIsDragging() ? null : renderAnnotations()}
@@ -1810,8 +1810,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             ref={this._mapRef}
                             mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
                             id="mapbox-map"
+                            viewState={{ ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width) / scale, height: NumCast(this.layoutDoc._height) / scale }}
                             mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'}
-                            style={{height: NumCast(this.layoutDoc._height), width: NumCast(this.layoutDoc._width)}}
                             initialViewState={this.isAnimating ? undefined : this.mapboxMapViewState}
                             onMove={this.onMapMove}
                             onClick={this.handleMapClick}
-- 
cgit v1.2.3-70-g09d2


From 4c4b42ee00262bb1fed10c73a6d35e013e2b9ab0 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 15 Dec 2023 15:02:07 -0500
Subject: fix for ctrl-zoom of map

---
 src/client/views/nodes/MapBox/MapBox.tsx | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 735c04c1d..c25d10a0d 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1593,12 +1593,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     onPointerDown={async e => {
                         e.button === 0 && !e.ctrlKey && e.stopPropagation();
                     }}
-                    style={{ pointerEvents: this.pointerEvents() }}>
+                    style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
                     <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
                     {renderAnnotations(this.opaqueFilter)}
                     {SnappingManager.IsDragging ? null : renderAnnotations()}
                     {!this.routeToAnimate && (
-                        <div className="mapBox-searchbar">
+                        <div className="mapBox-searchbar" style={{ zIndex: 1, position: 'relative', background: 'lightGray' }}>
                             <TextField fullWidth placeholder="Enter a location" onChange={(e: any) => this.handleSearchChange(e.target.value)} />
                             <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={e => this.toggleSettings()} />
                         </div>
@@ -1686,6 +1686,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             id="mapbox-map"
                             viewState={{ ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width) / scale, height: NumCast(this.layoutDoc._height) / scale }}
                             mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'}
+                            style={{
+                                transformOrigin: 'center',
+                                transform: `scale(${scale < 1 ? 1 : scale})`,
+                                zIndex: '0',
+                                width: '100%',
+                                height: '100%',
+                            }}
                             initialViewState={this.isAnimating ? undefined : this.mapboxMapViewState}
                             onMove={this.onMapMove}
                             onClick={this.handleMapClick}
-- 
cgit v1.2.3-70-g09d2


From dd86ab9a0d4c8cb5dcc4c789e0c225ab212c050e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 15 Dec 2023 15:02:26 -0500
Subject: fix for ctrl-zoom of map

---
 src/client/views/nodes/MapBox/MapBox.tsx | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index eb6a05a49..348e52d6c 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1687,12 +1687,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     onPointerDown={async e => {
                         e.button === 0 && !e.ctrlKey && e.stopPropagation();
                     }}
-                    style={{ pointerEvents: this.pointerEvents() }}>
+                    style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
                     <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
                     {renderAnnotations(this.opaqueFilter)}
                     {SnappingManager.GetIsDragging() ? null : renderAnnotations()}
                     {!this.routeToAnimate &&
-                        <div className="mapBox-searchbar">
+                        <div className="mapBox-searchbar" style={{ zIndex: 1, position: 'relative', background: 'lightGray' }}>
                             <TextField 
                                 fullWidth
                                 placeholder='Enter a location'
@@ -1812,6 +1812,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             id="mapbox-map"
                             viewState={{ ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width) / scale, height: NumCast(this.layoutDoc._height) / scale }}
                             mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'}
+		            style={{
+                                transformOrigin: 'center',
+                                transform: `scale(${scale < 1 ? 1 : scale})`,
+                                zIndex: '0',
+                                width: '100%',
+                                height: '100%',
+                            }}
                             initialViewState={this.isAnimating ? undefined : this.mapboxMapViewState}
                             onMove={this.onMapMove}
                             onClick={this.handleMapClick}
-- 
cgit v1.2.3-70-g09d2


From ddf35f6b406a2f2e8c27c2c65d15206eaa3ddbe6 Mon Sep 17 00:00:00 2001
From: zaultavangar <zaul_tavangar@brown.edu>
Date: Sat, 16 Dec 2023 14:15:56 -0500
Subject: starting calendar feature

---
 package-lock.json                                 | 262 ++++++++++++++++++++++
 package.json                                      |   5 +
 src/client/util/CalendarManager.scss              |  62 +++++
 src/client/util/CalendarManager.tsx               | 229 +++++++++++++++++++
 src/client/views/DocumentButtonBar.tsx            |  21 +-
 src/client/views/MainView.tsx                     |   2 +
 src/client/views/nodes/MapBox/MapBox.scss         |   3 +-
 src/client/views/nodes/MapBox/MapBox.tsx          |  42 ++--
 src/client/views/nodes/MapBox/MapboxApiUtility.ts |  98 +++++---
 9 files changed, 674 insertions(+), 50 deletions(-)
 create mode 100644 src/client/util/CalendarManager.scss
 create mode 100644 src/client/util/CalendarManager.tsx

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

diff --git a/package-lock.json b/package-lock.json
index b0748aae6..9dec742fb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3992,6 +3992,234 @@
                 }
             }
         },
+        "@mui/x-date-pickers": {
+            "version": "6.18.5",
+            "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.18.5.tgz",
+            "integrity": "sha512-3jImYIWP2Xgi608yzm/Sz1v0MTjQQYdZSQOEIi3dWBfSAU9B06KXDpqlXfRSpTV+rtsnfYIIyiWlz6Ltk7sUWw==",
+            "requires": {
+                "@babel/runtime": "^7.23.2",
+                "@mui/base": "^5.0.0-beta.22",
+                "@mui/utils": "^5.14.16",
+                "@types/react-transition-group": "^4.4.8",
+                "clsx": "^2.0.0",
+                "prop-types": "^15.8.1",
+                "react-transition-group": "^4.4.5"
+            },
+            "dependencies": {
+                "@babel/runtime": {
+                    "version": "7.23.6",
+                    "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz",
+                    "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==",
+                    "requires": {
+                        "regenerator-runtime": "^0.14.0"
+                    }
+                },
+                "@floating-ui/react-dom": {
+                    "version": "2.0.4",
+                    "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz",
+                    "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==",
+                    "requires": {
+                        "@floating-ui/dom": "^1.5.1"
+                    }
+                },
+                "@mui/base": {
+                    "version": "5.0.0-beta.27",
+                    "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.27.tgz",
+                    "integrity": "sha512-duL37qxihT1N0pW/gyXVezP7SttLkF+cLAs/y6g6ubEFmVadjbnZ45SeF12/vAiKzqwf5M0uFH1cczIPXFZygA==",
+                    "requires": {
+                        "@babel/runtime": "^7.23.5",
+                        "@floating-ui/react-dom": "^2.0.4",
+                        "@mui/types": "^7.2.11",
+                        "@mui/utils": "^5.15.0",
+                        "@popperjs/core": "^2.11.8",
+                        "clsx": "^2.0.0",
+                        "prop-types": "^15.8.1"
+                    }
+                },
+                "@mui/types": {
+                    "version": "7.2.11",
+                    "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.11.tgz",
+                    "integrity": "sha512-KWe/QTEsFFlFSH+qRYf3zoFEj3z67s+qAuSnMMg+gFwbxG7P96Hm6g300inQL1Wy///gSRb8juX7Wafvp93m3w=="
+                },
+                "@mui/utils": {
+                    "version": "5.15.0",
+                    "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.0.tgz",
+                    "integrity": "sha512-XSmTKStpKYamewxyJ256+srwEnsT3/6eNo6G7+WC1tj2Iq9GfUJ/6yUoB7YXjOD2jTZ3XobToZm4pVz1LBt6GA==",
+                    "requires": {
+                        "@babel/runtime": "^7.23.5",
+                        "@types/prop-types": "^15.7.11",
+                        "prop-types": "^15.8.1",
+                        "react-is": "^18.2.0"
+                    }
+                },
+                "@popperjs/core": {
+                    "version": "2.11.8",
+                    "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+                    "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="
+                },
+                "@types/prop-types": {
+                    "version": "15.7.11",
+                    "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
+                    "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
+                },
+                "@types/react-transition-group": {
+                    "version": "4.4.10",
+                    "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
+                    "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==",
+                    "requires": {
+                        "@types/react": "*"
+                    }
+                },
+                "clsx": {
+                    "version": "2.0.0",
+                    "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
+                    "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q=="
+                },
+                "react-is": {
+                    "version": "18.2.0",
+                    "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+                    "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+                },
+                "regenerator-runtime": {
+                    "version": "0.14.1",
+                    "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+                    "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+                }
+            }
+        },
+        "@mui/x-date-pickers-pro": {
+            "version": "6.18.5",
+            "resolved": "https://registry.npmjs.org/@mui/x-date-pickers-pro/-/x-date-pickers-pro-6.18.5.tgz",
+            "integrity": "sha512-f1JKEpi0cVmsOTab3rnx1W4aEPuKnF9IU3Ib7an75rW321QdwFYTm9+V4ZwWnN0jwDq8+13jjhdDEzfqcX76RA==",
+            "requires": {
+                "@babel/runtime": "^7.23.2",
+                "@mui/base": "^5.0.0-beta.22",
+                "@mui/utils": "^5.14.16",
+                "@mui/x-date-pickers": "6.18.5",
+                "@mui/x-license-pro": "6.10.2",
+                "clsx": "^2.0.0",
+                "prop-types": "^15.8.1",
+                "react-transition-group": "^4.4.5"
+            },
+            "dependencies": {
+                "@babel/runtime": {
+                    "version": "7.23.6",
+                    "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz",
+                    "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==",
+                    "requires": {
+                        "regenerator-runtime": "^0.14.0"
+                    }
+                },
+                "@floating-ui/react-dom": {
+                    "version": "2.0.4",
+                    "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz",
+                    "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==",
+                    "requires": {
+                        "@floating-ui/dom": "^1.5.1"
+                    }
+                },
+                "@mui/base": {
+                    "version": "5.0.0-beta.27",
+                    "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.27.tgz",
+                    "integrity": "sha512-duL37qxihT1N0pW/gyXVezP7SttLkF+cLAs/y6g6ubEFmVadjbnZ45SeF12/vAiKzqwf5M0uFH1cczIPXFZygA==",
+                    "requires": {
+                        "@babel/runtime": "^7.23.5",
+                        "@floating-ui/react-dom": "^2.0.4",
+                        "@mui/types": "^7.2.11",
+                        "@mui/utils": "^5.15.0",
+                        "@popperjs/core": "^2.11.8",
+                        "clsx": "^2.0.0",
+                        "prop-types": "^15.8.1"
+                    }
+                },
+                "@mui/types": {
+                    "version": "7.2.11",
+                    "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.11.tgz",
+                    "integrity": "sha512-KWe/QTEsFFlFSH+qRYf3zoFEj3z67s+qAuSnMMg+gFwbxG7P96Hm6g300inQL1Wy///gSRb8juX7Wafvp93m3w=="
+                },
+                "@mui/utils": {
+                    "version": "5.15.0",
+                    "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.0.tgz",
+                    "integrity": "sha512-XSmTKStpKYamewxyJ256+srwEnsT3/6eNo6G7+WC1tj2Iq9GfUJ/6yUoB7YXjOD2jTZ3XobToZm4pVz1LBt6GA==",
+                    "requires": {
+                        "@babel/runtime": "^7.23.5",
+                        "@types/prop-types": "^15.7.11",
+                        "prop-types": "^15.8.1",
+                        "react-is": "^18.2.0"
+                    }
+                },
+                "@popperjs/core": {
+                    "version": "2.11.8",
+                    "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+                    "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="
+                },
+                "@types/prop-types": {
+                    "version": "15.7.11",
+                    "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
+                    "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
+                },
+                "clsx": {
+                    "version": "2.0.0",
+                    "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
+                    "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q=="
+                },
+                "react-is": {
+                    "version": "18.2.0",
+                    "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+                    "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+                },
+                "regenerator-runtime": {
+                    "version": "0.14.1",
+                    "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+                    "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+                }
+            }
+        },
+        "@mui/x-license-pro": {
+            "version": "6.10.2",
+            "resolved": "https://registry.npmjs.org/@mui/x-license-pro/-/x-license-pro-6.10.2.tgz",
+            "integrity": "sha512-Baw3shilU+eHgU+QYKNPFUKvfS5rSyNJ98pQx02E0gKA22hWp/XAt88K1qUfUMPlkPpvg/uci6gviQSSLZkuKw==",
+            "requires": {
+                "@babel/runtime": "^7.22.6",
+                "@mui/utils": "^5.13.7"
+            },
+            "dependencies": {
+                "@babel/runtime": {
+                    "version": "7.23.6",
+                    "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz",
+                    "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==",
+                    "requires": {
+                        "regenerator-runtime": "^0.14.0"
+                    }
+                },
+                "@mui/utils": {
+                    "version": "5.15.0",
+                    "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.0.tgz",
+                    "integrity": "sha512-XSmTKStpKYamewxyJ256+srwEnsT3/6eNo6G7+WC1tj2Iq9GfUJ/6yUoB7YXjOD2jTZ3XobToZm4pVz1LBt6GA==",
+                    "requires": {
+                        "@babel/runtime": "^7.23.5",
+                        "@types/prop-types": "^15.7.11",
+                        "prop-types": "^15.8.1",
+                        "react-is": "^18.2.0"
+                    }
+                },
+                "@types/prop-types": {
+                    "version": "15.7.11",
+                    "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
+                    "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
+                },
+                "react-is": {
+                    "version": "18.2.0",
+                    "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+                    "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+                },
+                "regenerator-runtime": {
+                    "version": "0.14.1",
+                    "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+                    "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+                }
+            }
+        },
         "@nicolo-ribaudo/chokidar-2": {
             "version": "2.1.8-no-fsevents.3",
             "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz",
@@ -7695,6 +7923,16 @@
                 "@types/reactcss": "*"
             }
         },
+        "@types/react-date-range": {
+            "version": "1.4.9",
+            "resolved": "https://registry.npmjs.org/@types/react-date-range/-/react-date-range-1.4.9.tgz",
+            "integrity": "sha512-5oVEDW0ElYmY1+YVSzdMUR8stxSI5QrRJCgCFUvuEAV5197t412vimD9aVTW6g4JTaxCnMmB1BdEOT/odpaBxQ==",
+            "dev": true,
+            "requires": {
+                "@types/react": "*",
+                "date-fns": "^2.16.1"
+            }
+        },
         "@types/react-datepicker": {
             "version": "3.1.8",
             "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-3.1.8.tgz",
@@ -12838,6 +13076,11 @@
             "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
             "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA=="
         },
+        "dayjs": {
+            "version": "1.11.10",
+            "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
+            "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
+        },
         "de-indent": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
@@ -27697,6 +27940,17 @@
                 }
             }
         },
+        "react-date-range": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/react-date-range/-/react-date-range-1.4.0.tgz",
+            "integrity": "sha512-+9t0HyClbCqw1IhYbpWecjsiaftCeRN5cdhsi9v06YdimwyMR2yYHWcgVn3URwtN/txhqKpEZB6UX1fHpvK76w==",
+            "requires": {
+                "classnames": "^2.2.6",
+                "prop-types": "^15.7.2",
+                "react-list": "^0.8.13",
+                "shallow-equal": "^1.2.1"
+            }
+        },
         "react-datepicker": {
             "version": "3.8.0",
             "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.8.0.tgz",
@@ -27879,6 +28133,14 @@
             "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
             "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
         },
+        "react-list": {
+            "version": "0.8.17",
+            "resolved": "https://registry.npmjs.org/react-list/-/react-list-0.8.17.tgz",
+            "integrity": "sha512-pgmzGi0G5uGrdHzMhgO7KR1wx5ZXVvI3SsJUmkblSAKtewIhMwbQiMuQiTE83ozo04BQJbe0r3WIWzSO0dR1xg==",
+            "requires": {
+                "prop-types": "15"
+            }
+        },
         "react-loader-spinner": {
             "version": "5.3.4",
             "resolved": "https://registry.npmjs.org/react-loader-spinner/-/react-loader-spinner-5.3.4.tgz",
diff --git a/package.json b/package.json
index 5d53a6c4e..58a25a68c 100644
--- a/package.json
+++ b/package.json
@@ -72,6 +72,7 @@
         "@types/react": "^18.0.15",
         "@types/react-autosuggest": "^9.3.14",
         "@types/react-color": "^2.17.6",
+        "@types/react-date-range": "^1.4.9",
         "@types/react-datepicker": "^3.1.8",
         "@types/react-dom": "^18.0.6",
         "@types/react-grid-layout": "^1.3.2",
@@ -148,6 +149,8 @@
         "@material-ui/core": "^4.12.3",
         "@mui/icons-material": "^5.11.16",
         "@mui/material": "^5.13.1",
+        "@mui/x-date-pickers": "^6.18.5",
+        "@mui/x-date-pickers-pro": "^6.18.5",
         "@octokit/core": "^4.0.4",
         "@react-google-maps/api": "^2.7.0",
         "@react-three/fiber": "^6.2.3",
@@ -201,6 +204,7 @@
         "csv-parser": "^3.0.0",
         "csv-stringify": "^6.3.0",
         "d3": "^7.8.5",
+        "dayjs": "^1.11.10",
         "debounce": "^2.0.0",
         "depcheck": "^0.9.2",
         "equation-editor-react": "github:bobzel/equation-editor-react#useLocally",
@@ -296,6 +300,7 @@
         "react-chartjs-2": "^4.3.0",
         "react-color": "^2.19.3",
         "react-compound-slider": "^2.5.0",
+        "react-date-range": "^1.4.0",
         "react-datepicker": "^3.8.0",
         "react-dom": "^18.2.0",
         "react-dropzone": "^14.2.3",
diff --git a/src/client/util/CalendarManager.scss b/src/client/util/CalendarManager.scss
new file mode 100644
index 000000000..60610f298
--- /dev/null
+++ b/src/client/util/CalendarManager.scss
@@ -0,0 +1,62 @@
+.calendar-interface{
+    width: 600px;
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+    justify-content: center;
+    gap: 7px;
+    padding: 10px;
+
+    .selected-doc-title{
+        font-size: 1.4em;
+    }
+
+    .creation-type-container{
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        align-self: center;
+        gap: 12px;
+
+        .calendar-creation{
+            cursor: pointer;
+        }
+
+        .calendar-creation-selected{
+            border-bottom: 2px solid white;
+        }
+    }
+
+    .choose-calendar-container{
+        margin-top: 10px;
+        align-self: center;
+        width: 60%;
+
+        .MuiFilledInput-input{
+            padding: 10px;
+        }
+    }
+
+    .date-range-picker-container{
+        margin-top: 5px;
+        align-self:center;
+
+        .react-date-range{
+
+        }
+    }
+
+    .create-button-container{
+        
+        margin-top: 5px;
+        align-self: center;
+
+        .button-content{
+            font-size: 1.2em;
+            padding: 10px;
+            border-radius: 5px;
+            background-color: #EFF2F7;
+        }
+
+    }
+}
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
new file mode 100644
index 000000000..39ba41652
--- /dev/null
+++ b/src/client/util/CalendarManager.tsx
@@ -0,0 +1,229 @@
+import * as React from 'react';
+import './CalendarManager.scss';
+import { observer } from "mobx-react";
+import { action, computed, observable, runInAction } from 'mobx';
+import { Doc } from '../../fields/Doc';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { DictationOverlay } from '../views/DictationOverlay';
+import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
+import { MainViewModal } from '../views/MainViewModal';
+import { TextField } from '@material-ui/core';
+import Select from 'react-select';
+import { SettingsManager } from './SettingsManager';
+import { StrCast } from '../../fields/Types';
+import { SelectionManager } from './SelectionManager';
+import { DocumentManager } from './DocumentManager';
+import { DocData } from '../../fields/DocSymbols';
+import { DateRange, Range, RangeKeyDict } from 'react-date-range';
+import { Button } from 'browndash-components';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons';
+import 'react-date-range/dist/styles.css';
+import 'react-date-range/dist/theme/default.css';
+
+type CreationType = 'new-calendar' | 'existing-calendar' | 'manage-calendars';
+
+@observer
+export class CalendarManager extends React.Component<{}> {
+    public static Instance: CalendarManager;
+    @observable private isOpen = false; 
+    @observable private targetDoc: Doc | undefined; // the target document
+    @observable private targetDocView: DocumentView | undefined; // the DocumentView of the target doc
+    @observable private dialogueBoxOpacity = 1; // for the modal
+    @observable private overlayOpacity = 0.4; // for the modal
+    
+    @observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used
+
+    @observable private creationType: CreationType = 'new-calendar';
+
+    @observable 
+    calendarName: string = "";
+
+    @action
+    setInterationType = (type: CreationType) => {
+        this.creationType = type;
+    }
+
+
+    public open = (target?: DocumentView, target_doc?: Doc) => {
+        console.log('hi');
+        runInAction(() => {
+            this.targetDoc = target_doc || target?.props.Document;
+            this.targetDocView = target;
+            DictationOverlay.Instance.hasActiveModal = true;
+            this.isOpen = this.targetDoc !== undefined;
+        })
+    };
+
+    public close = action(() => {
+        this.isOpen = false;
+        TaskCompletionBox.taskCompleted = false;
+        setTimeout(
+            action(() => {
+                DictationOverlay.Instance.hasActiveModal = false;
+                this.targetDoc = undefined;
+            }), 500
+        );
+        this.layoutDocAcls = false;
+    });
+
+    constructor(props: {}) {
+        super(props);
+        CalendarManager.Instance = this;
+    }
+
+    componentDidMount(): void {
+        
+    }
+
+    private focusOn = (contents: string) => {
+        const title = this.targetDoc ? StrCast(this.targetDoc.title) : '';
+        const docs = SelectionManager.Views().length > 1 ? SelectionManager.Views().map(docView => docView.props.Document) : [this.targetDoc];
+        return (
+            <span
+                className="focus-span"
+                title={title}
+                onClick={() => {
+                    if (this.targetDoc && this.targetDocView && docs.length === 1) {
+                        DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true });
+                    }
+                }}
+                onPointerEnter={action(() => {
+                    if (docs.length) {
+                        docs.forEach(doc => doc && Doc.BrushDoc(doc));
+                        this.dialogueBoxOpacity = 0.1;
+                        this.overlayOpacity = 0.1;
+                    }
+                })}
+                onPointerLeave={action(() => {
+                    if (docs.length) {
+                        docs.forEach(doc => doc && Doc.UnBrushDoc(doc));
+                        this.dialogueBoxOpacity = 1;
+                        this.overlayOpacity = 0.4;
+                    }
+                })}>
+                {contents}
+            </span>
+        );
+    };
+
+    @observable
+    selectedDateRange: Range[] = [{
+        startDate: new Date(),
+        endDate: undefined,
+        key: 'selection'
+    }]
+
+    @action
+    setSelectedDateRange = (range: Range[]) => {
+        this.selectedDateRange = range;
+    }
+
+    @computed
+    get createButtonActive() {
+        if (this.calendarName.length === 0) return false // disabled if no calendar name
+        let startDate: Date | undefined;
+        let endDate: Date | undefined;
+        try {
+            startDate = this.selectedDateRange[0].startDate;
+            endDate = this.selectedDateRange[0].endDate;
+        } catch (e: any){
+            return false; // disabled 
+        }
+        if (!startDate || !endDate) return false; // disabled if any is undefined
+        return true;
+    }
+
+    @computed
+    get calendarInterface(){
+        let docs = SelectionManager.Views().length < 2 ? [this.targetDoc] : SelectionManager.Views().map(docView => docView.rootDoc);
+        const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData];
+
+        const currentDate = new Date();
+
+        return (
+            <div 
+                className='calendar-interface'
+                style= {{
+                    background: SettingsManager.userBackgroundColor,
+                    color: StrCast(Doc.UserDoc().userColor)
+                }}
+                >
+                <p className="selected-doc-title" style={{ color: SettingsManager.userColor }}>
+                    <b>{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}</b>
+                </p>
+                <div className='creation-type-container'>
+                    <div 
+                        className={`calendar-creation ${this.creationType === 'new-calendar' ? 'calendar-creation-selected' : ''}`}
+                        onClick={(e) => this.setInterationType('new-calendar')}
+                        >
+                        Add to New Calendar
+                    </div>
+                    <div 
+                        className={`calendar-creation ${this.creationType === 'existing-calendar' ? 'calendar-creation-selected' : ''}`}
+                        onClick={(e) => this.setInterationType('existing-calendar')}
+                        >
+                        Add to Existing calendar
+                    </div>
+                </div>
+                <div className='choose-calendar-container'>
+                    {this.creationType === 'new-calendar' ? 
+                        <TextField
+                            fullWidth
+                            placeholder='Enter calendar name...'
+                            variant='filled'
+                            style={{
+                                backgroundColor: 'white',
+                                color: 'black',
+                                borderRadius: '5px'
+
+                            }}
+                            
+                        />
+                    :
+                        <Select
+                            className='existing-calendar-search'
+                            placeholder='Search for existing calendar...'
+                            isMulti
+                            isSearchable
+                        >
+                            
+                        </Select>
+                    }
+                </div>
+                <div className='date-range-picker-container'>
+                    <DateRange
+                        className='react-date-range'
+                        editableDateInputs={true}
+                        // ranges={[selectionRange]}
+                        onChange={item => this.setSelectedDateRange([item.selection])}
+                        ranges={this.selectedDateRange}
+                    // onChange={this.handleSelect}
+                    />
+                </div>
+                <div className='create-button-container'>
+                    <Button
+                        active={this.createButtonActive}
+                        text="Add to Calendar"
+                        iconPlacement='right'
+                        icon={<FontAwesomeIcon icon={faPlus as IconLookup}/>}
+                    />
+                </div>
+                
+            </div>
+        )
+    }
+
+    render() {
+        return (
+        <MainViewModal 
+            contents={this.calendarInterface} 
+            isDisplayed={this.isOpen} 
+            interactive={true}
+            dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
+            overlayDisplayedOpacity={this.overlayOpacity}
+            closeOnExternalClick={this.close}
+        />
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index bd82f7782..ccd459f0d 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,4 +1,4 @@
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { IconLookup, IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
 import { action, computed, observable, runInAction } from 'mobx';
@@ -14,6 +14,7 @@ import { DragManager } from '../util/DragManager';
 import { IsFollowLinkScript } from '../util/LinkFollower';
 import { SelectionManager } from '../util/SelectionManager';
 import { SharingManager } from '../util/SharingManager';
+import { CalendarManager } from '../util/CalendarManager';
 import { undoBatch, UndoManager } from '../util/UndoManager';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { TabDocView } from './collections/TabDocView';
@@ -28,6 +29,8 @@ import { GoogleRef } from './nodes/formattedText/FormattedTextBox';
 import { PinProps } from './nodes/trails';
 import { TemplateMenu } from './TemplateMenu';
 import React = require('react');
+import { faCalendarDays } from '@fortawesome/free-solid-svg-icons';
+
 const higflyout = require('@hig/flyout');
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -443,6 +446,21 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         );
     }
 
+    @computed
+    get calendarButton(){
+        const targetDoc = this.view0?.props.Document;
+        return !targetDoc ? null : (
+            <Tooltip title={<div className='dash-calendar-button'>Open calendar menu</div>}>
+                <div className="documentButtonBar-icon" style={{ color: 'white' }} onClick={e => {
+                    console.log('hi: ', CalendarManager.Instance)
+                    CalendarManager.Instance.open(this.view0, targetDoc)}
+                }>
+                    <FontAwesomeIcon className="documentdecorations-icon" icon={faCalendarDays as IconLookup}/>
+                </div>
+            </Tooltip>
+        )
+    }
+
     @observable _isRecording = false;
     _stopFunc: () => void = emptyFunction;
     @computed
@@ -606,6 +624,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                 {!SelectionManager.Views()?.some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>}
                 <div className="documentButtonBar-button">{this.pinButton}</div>
                 <div className="documentButtonBar-button">{this.recordButton}</div>
+                <div className="documentButtonBar-button">{this.calendarButton}</div>
                 {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : <div className="documentButtonBar-button">{this.shareButton}</div>}
                 {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : (
                     <div className="documentButtonBar-button" style={{ display: !considerPush() ? 'none' : '' }}>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 13f7dc896..d45f24930 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -72,6 +72,7 @@ import { PropertiesView } from './PropertiesView';
 import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
 import { TopBar } from './topbar/TopBar';
 import { DirectionsAnchorMenu } from './nodes/MapBox/DirectionsAnchorMenu';
+import { CalendarManager } from '../util/CalendarManager';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
@@ -1038,6 +1039,7 @@ export class MainView extends React.Component {
                 {this.inkResources}
                 <DictationOverlay />
                 <SharingManager />
+                <CalendarManager />
                 <ServerStats />
                 <RTFMarkup />
                 <SettingsManager />
diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss
index e25261729..ba1e99f84 100644
--- a/src/client/views/nodes/MapBox/MapBox.scss
+++ b/src/client/views/nodes/MapBox/MapBox.scss
@@ -88,12 +88,11 @@
         flex-direction: column;
         justify-content: flex-start;
         align-items: flex-start;
-        position: absolute;
         background-color: rgb(187, 187, 187);
         padding: 10px;
         border-top-right-radius: 5px;
         border-bottom-right-radius: 5px;
-        width: 100%;
+        position: absolute;
 
         #route-to-animate-title {
             font-size: 1.25em;
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index f4526c490..25299532a 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -895,19 +895,21 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }, 'createpin');
 
     @action
-    createMapRoute = undoable((coordinates: Position[], origin: string, destination: any, createPinForDestination: boolean) => {
-        const mapRoute = Docs.Create.MapRouteDocument(
-            false,
-            [],
-            {title: `${origin} --> ${destination.place_name}`, routeCoordinates: JSON.stringify(coordinates)},
-        );
-        this.addDocument(mapRoute, this.annotationKey);
-        if (createPinForDestination) {
-            this.createPushpin(destination.center[1], destination.center[0], destination.place_name);
+    createMapRoute = undoable((coordinates: Position[], originName: string, destination: any, createPinForDestination: boolean) => {
+        if (originName !== destination.place_name){
+            const mapRoute = Docs.Create.MapRouteDocument(
+                false,
+                [],
+                {title: `${originName} --> ${destination.place_name}`, routeCoordinates: JSON.stringify(coordinates)},
+            );
+            this.addDocument(mapRoute, this.annotationKey);
+            if (createPinForDestination) {
+                this.createPushpin(destination.center[1], destination.center[0], destination.place_name);
+            }
+            return mapRoute;
         }
-        return mapRoute;
-
-        // mapMarker.infoWindowOpen = true;
+        // TODO: Display error that can't create route to same location
+        
     }, 'createmaproute');
 
     searchbarKeyDown = (e: any) => e.key === 'Enter' && this.bingSearch();
@@ -981,6 +983,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     handleMapClick = (e: MapLayerMouseEvent) => {
+        this.featuresFromGeocodeResults = [];
         if (this._mapRef.current){
             const features = this._mapRef.current.queryRenderedFeatures(
                 e.point, {
@@ -1179,6 +1182,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         if (routeDoc){
             MapAnchorMenu.Instance.fadeOut(true);
             document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
+            this.featuresFromGeocodeResults = [];
             this.routeToAnimate = routeDoc;
         }
     }
@@ -1675,6 +1679,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 }), MapBox._rerenderDelay);
             return null;
         }
+        const scale = this.props.NativeDimScaling?.() || 1;
 
         const renderAnnotations = (childFilters?: () => string[]) => null;
         return (
@@ -1686,11 +1691,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         e.button === 0 && !e.ctrlKey && e.stopPropagation();
                     }}
                     style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
+                    {/* style={{ transformOrigin: "top left",  transform: `scale(${scale})`,  width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}> */}
                     <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
                     {renderAnnotations(this.opaqueFilter)}
                     {SnappingManager.GetIsDragging() ? null : renderAnnotations()}
                     {!this.routeToAnimate &&
-                        <div className="mapBox-searchbar">
+                        <div className="mapBox-searchbar" style={{ zIndex: 1, position: 'relative', background: 'lightGray' }}>
                             <TextField 
                                 fullWidth
                                 placeholder='Enter a location'
@@ -1755,7 +1761,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         </div>
                     }
                     {this.routeToAnimate &&
-                        <div className='animation-panel'>
+                        <div className='animation-panel' style={{width: this.sidebarWidth() === 0 ? '100%' : `calc(100% - ${this.sidebarWidth()}px)`}}>
                             <div id='route-to-animate-title'>
                                 {StrCast(this.routeToAnimate.title)}
                             </div>
@@ -1809,7 +1815,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
                             id="mapbox-map"
                             mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'}
-                            style={{height: NumCast(this.layoutDoc._height), width: NumCast(this.layoutDoc._width)}}
+                            style={{
+                                transformOrigin: 'center',
+                                transform: `scale(${scale < 1 ? 1 : scale})`,
+                                zIndex: '0',
+                                width: '100%',
+                                height: '100%',
+                            }}
                             initialViewState={this.isAnimating ? undefined : this.mapboxMapViewState}
                             onMove={this.onMapMove}
                             onClick={this.handleMapClick}
diff --git a/src/client/views/nodes/MapBox/MapboxApiUtility.ts b/src/client/views/nodes/MapBox/MapboxApiUtility.ts
index 011b6f72a..592330ac2 100644
--- a/src/client/views/nodes/MapBox/MapboxApiUtility.ts
+++ b/src/client/views/nodes/MapBox/MapboxApiUtility.ts
@@ -34,48 +34,39 @@ export class MapboxApiUtility {
 
     static getDirections = async (origin: number[], destination: number[]): Promise<Record<TransportationType, any> | undefined> => {
         try {
-            const drivingQuery = await fetch(
-                `${MAPBOX_DIRECTIONS_BASE_URL}/driving/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`);
 
-            const cyclingQuery = await fetch(
-                `${MAPBOX_DIRECTIONS_BASE_URL}/cycling/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`);
+            const directionsPromises: Promise<any>[] = [];
+            const transportationTypes: TransportationType[] = ['driving', 'cycling', 'walking'];
 
-            const walkingQuery = await fetch(
-                `${MAPBOX_DIRECTIONS_BASE_URL}/walking/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`);
+            transportationTypes.forEach((type) => {
+                directionsPromises.push(
+                  fetch(
+                    `${MAPBOX_DIRECTIONS_BASE_URL}/${type}/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`
+                  ).then((response) => response.json())
+                );
+              });
 
-            const drivingJson = await drivingQuery.json();
-            const cyclingJson = await cyclingQuery.json();
-            const walkingJson = await walkingQuery.json();
-
-            console.log("Driving: ", drivingJson);
-            console.log("Cycling: ", cyclingJson);
-            console.log("Waling: ", walkingJson);
-
-            const routeMap = {
-                'driving': drivingJson.routes[0],
-                'cycling': cyclingJson.routes[0],
-                'walking': walkingJson.routes[0]
-            }
+            const results = await Promise.all(directionsPromises);
 
             const routeInfoMap: Record<TransportationType, any> = {
                 'driving': {},
                 'cycling': {},
                 'walking': {},
-              };
-
-            Object.entries(routeMap).forEach(([key, routeData]) => {
-                const transportationTypeKey = key as TransportationType;
-                const geometry = routeData.geometry;
-                const coordinates = geometry.coordinates;
-
-                console.log(coordinates);
-
-                routeInfoMap[transportationTypeKey] = {
+            };
+
+            transportationTypes.forEach((type, index) => {
+                const routeData = results[index].routes[0];
+                if (routeData) {
+                  const geometry = routeData.geometry;
+                  const coordinates = geometry.coordinates;
+          
+                  routeInfoMap[type] = {
                     duration: this.secondsToMinutesHours(routeData.duration),
                     distance: this.metersToMiles(routeData.distance),
-                    coordinates: coordinates
+                    coordinates: coordinates,
+                  };
                 }
-            })
+              });
 
             return routeInfoMap;
 
@@ -102,4 +93,47 @@ export class MapboxApiUtility {
         return `${parseFloat((meters/1609.34).toFixed(2))} mi`;
     }   
 
-}
\ No newline at end of file
+}
+
+// const drivingQuery = await fetch(
+//     `${MAPBOX_DIRECTIONS_BASE_URL}/driving/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`);
+
+// const cyclingQuery = await fetch(
+//     `${MAPBOX_DIRECTIONS_BASE_URL}/cycling/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`);
+
+// const walkingQuery = await fetch(
+//     `${MAPBOX_DIRECTIONS_BASE_URL}/walking/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`);
+
+// const drivingJson = await drivingQuery.json();
+// const cyclingJson = await cyclingQuery.json();
+// const walkingJson = await walkingQuery.json();
+
+// console.log("Driving: ", drivingJson);
+// console.log("Cycling: ", cyclingJson);
+// console.log("Waling: ", walkingJson);
+
+// const routeMap = {
+//     'driving': drivingJson.routes[0],
+//     'cycling': cyclingJson.routes[0],
+//     'walking': walkingJson.routes[0]
+// }
+
+// const routeInfoMap: Record<TransportationType, any> = {
+//     'driving': {},
+//     'cycling': {},
+//     'walking': {},
+//   };
+
+// Object.entries(routeMap).forEach(([key, routeData]) => {
+//     const transportationTypeKey = key as TransportationType;
+//     const geometry = routeData.geometry;
+//     const coordinates = geometry.coordinates;
+
+//     console.log(coordinates);
+
+//     routeInfoMap[transportationTypeKey] = {
+//         duration: this.secondsToMinutesHours(routeData.duration),
+//         distance: this.metersToMiles(routeData.distance),
+//         coordinates: coordinates
+//     }
+// })
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 644fc1b89a385e31cb5e626ef575af6df8500f5d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 16 Dec 2023 16:09:51 -0500
Subject: some cleanup to mapbox, but no fix for ctrl-zoomed maps yet.

---
 package-lock.json                         | 1178 ++++++++++++++---------------
 src/client/views/nodes/MapBox/MapBox.scss |   28 +-
 src/client/views/nodes/MapBox/MapBox.tsx  |   67 +-
 3 files changed, 604 insertions(+), 669 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 9dec742fb..b122ad0ca 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3083,11 +3083,29 @@
                     "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
                     "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="
                 },
+                "color-convert": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+                    "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+                    "requires": {
+                        "color-name": "~1.1.4"
+                    }
+                },
+                "color-name": {
+                    "version": "1.1.4",
+                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+                    "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+                },
                 "emoji-regex": {
                     "version": "9.2.2",
                     "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
                     "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
                 },
+                "is-fullwidth-code-point": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+                    "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+                },
                 "string-width": {
                     "version": "5.1.2",
                     "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@@ -3098,6 +3116,36 @@
                         "strip-ansi": "^7.0.1"
                     }
                 },
+                "string-width-cjs": {
+                    "version": "npm:string-width@4.2.3",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                    "requires": {
+                        "emoji-regex": "^8.0.0",
+                        "is-fullwidth-code-point": "^3.0.0",
+                        "strip-ansi": "^6.0.1"
+                    },
+                    "dependencies": {
+                        "ansi-regex": {
+                            "version": "5.0.1",
+                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                        },
+                        "emoji-regex": {
+                            "version": "8.0.0",
+                            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                            "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+                        },
+                        "strip-ansi": {
+                            "version": "6.0.1",
+                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                            "requires": {
+                                "ansi-regex": "^5.0.1"
+                            }
+                        }
+                    }
+                },
                 "strip-ansi": {
                     "version": "7.1.0",
                     "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
@@ -3106,6 +3154,21 @@
                         "ansi-regex": "^6.0.1"
                     }
                 },
+                "strip-ansi-cjs": {
+                    "version": "npm:strip-ansi@6.0.1",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                    "requires": {
+                        "ansi-regex": "^5.0.1"
+                    },
+                    "dependencies": {
+                        "ansi-regex": {
+                            "version": "5.0.1",
+                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                        }
+                    }
+                },
                 "wrap-ansi": {
                     "version": "8.1.0",
                     "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
@@ -3115,6 +3178,54 @@
                         "string-width": "^5.0.1",
                         "strip-ansi": "^7.0.1"
                     }
+                },
+                "wrap-ansi-cjs": {
+                    "version": "npm:wrap-ansi@7.0.0",
+                    "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+                    "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+                    "requires": {
+                        "ansi-styles": "^4.0.0",
+                        "string-width": "^4.1.0",
+                        "strip-ansi": "^6.0.0"
+                    },
+                    "dependencies": {
+                        "ansi-regex": {
+                            "version": "5.0.1",
+                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                        },
+                        "ansi-styles": {
+                            "version": "4.3.0",
+                            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+                            "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+                            "requires": {
+                                "color-convert": "^2.0.1"
+                            }
+                        },
+                        "emoji-regex": {
+                            "version": "8.0.0",
+                            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                            "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+                        },
+                        "string-width": {
+                            "version": "4.2.3",
+                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                            "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                            "requires": {
+                                "emoji-regex": "^8.0.0",
+                                "is-fullwidth-code-point": "^3.0.0",
+                                "strip-ansi": "^6.0.1"
+                            }
+                        },
+                        "strip-ansi": {
+                            "version": "6.0.1",
+                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                            "requires": {
+                                "ansi-regex": "^5.0.1"
+                            }
+                        }
+                    }
                 }
             }
         },
@@ -12733,16 +12844,6 @@
             "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
             "dev": true
         },
-        "d": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
-            "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
-            "dev": true,
-            "requires": {
-                "es5-ext": "^0.10.50",
-                "type": "^1.0.1"
-            }
-        },
         "d3": {
             "version": "7.8.5",
             "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz",
@@ -14175,28 +14276,6 @@
                 "is-symbol": "^1.0.2"
             }
         },
-        "es5-ext": {
-            "version": "0.10.62",
-            "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
-            "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
-            "dev": true,
-            "requires": {
-                "es6-iterator": "^2.0.3",
-                "es6-symbol": "^3.1.3",
-                "next-tick": "^1.1.0"
-            }
-        },
-        "es6-iterator": {
-            "version": "2.0.3",
-            "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
-            "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
-            "dev": true,
-            "requires": {
-                "d": "1",
-                "es5-ext": "^0.10.35",
-                "es6-symbol": "^3.1.1"
-            }
-        },
         "es6-promise": {
             "version": "3.2.1",
             "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz",
@@ -14208,7 +14287,6 @@
             "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
             "dev": true,
             "requires": {
-                "d": "^1.0.1",
                 "ext": "^1.1.2"
             }
         },
@@ -22236,7 +22314,7 @@
             "dependencies": {
                 "@iarna/cli": {
                     "version": "2.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/@iarna/cli/-/cli-2.1.0.tgz",
                     "integrity": "sha512-rvVVqDa2g860niRbqs3D5RhL4la3dc1vwk+NlpKPZxKaMSHtE2se6C2x8NeveN+rcjp3/686X+u+09CZ+7lmAQ==",
                     "requires": {
                         "glob": "^7.1.2",
@@ -22245,7 +22323,7 @@
                 },
                 "JSONStream": {
                     "version": "1.3.5",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
                     "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
                     "requires": {
                         "jsonparse": "^1.2.0",
@@ -22254,12 +22332,12 @@
                 },
                 "abbrev": {
                     "version": "1.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
                     "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
                 },
                 "agent-base": {
                     "version": "4.3.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
                     "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
                     "requires": {
                         "es6-promisify": "^5.0.0"
@@ -22267,7 +22345,7 @@
                 },
                 "agentkeepalive": {
                     "version": "3.5.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz",
                     "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==",
                     "requires": {
                         "humanize-ms": "^1.2.1"
@@ -22275,7 +22353,7 @@
                 },
                 "ansi-align": {
                     "version": "2.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz",
                     "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=",
                     "requires": {
                         "string-width": "^2.0.0"
@@ -22283,12 +22361,12 @@
                 },
                 "ansi-regex": {
                     "version": "2.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
                     "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
                 },
                 "ansi-styles": {
                     "version": "3.2.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
                     "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
                     "requires": {
                         "color-convert": "^1.9.0"
@@ -22296,27 +22374,27 @@
                 },
                 "ansicolors": {
                     "version": "0.3.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz",
                     "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk="
                 },
                 "ansistyles": {
                     "version": "0.1.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/ansistyles/-/ansistyles-0.1.3.tgz",
                     "integrity": "sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk="
                 },
                 "aproba": {
                     "version": "2.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
                     "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
                 },
                 "archy": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
                     "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA="
                 },
                 "are-we-there-yet": {
                     "version": "1.1.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
                     "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
                     "requires": {
                         "delegates": "^1.0.0",
@@ -22325,7 +22403,7 @@
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
                             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -22339,14 +22417,14 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
                         },
                         "string_decoder": {
                             "version": "1.1.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
                             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                             "requires": {
                                 "safe-buffer": "~5.1.0"
@@ -22354,7 +22432,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -22363,12 +22441,12 @@
                 },
                 "asap": {
                     "version": "2.0.6",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
                     "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
                 },
                 "asn1": {
                     "version": "0.2.6",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
                     "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
                     "requires": {
                         "safer-buffer": "~2.1.0"
@@ -22376,32 +22454,32 @@
                 },
                 "assert-plus": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
                     "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="
                 },
                 "asynckit": {
                     "version": "0.4.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
                     "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
                 },
                 "aws-sign2": {
                     "version": "0.7.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
                     "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA=="
                 },
                 "aws4": {
                     "version": "1.11.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
                     "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
                 },
                 "balanced-match": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
                     "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
                 },
                 "bcrypt-pbkdf": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
                     "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
                     "requires": {
                         "tweetnacl": "^0.14.3"
@@ -22409,7 +22487,7 @@
                 },
                 "bin-links": {
                     "version": "1.1.8",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-1.1.8.tgz",
                     "integrity": "sha512-KgmVfx+QqggqP9dA3iIc5pA4T1qEEEL+hOhOhNPaUm77OTrJoOXE/C05SJLNJe6m/2wUK7F1tDSou7n5TfCDzQ==",
                     "requires": {
                         "bluebird": "^3.5.3",
@@ -22422,12 +22500,12 @@
                 },
                 "bluebird": {
                     "version": "3.7.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
                     "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
                 },
                 "boxen": {
                     "version": "1.3.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz",
                     "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==",
                     "requires": {
                         "ansi-align": "^2.0.0",
@@ -22441,7 +22519,7 @@
                 },
                 "brace-expansion": {
                     "version": "1.1.11",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
                     "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
                     "requires": {
                         "balanced-match": "^1.0.0",
@@ -22450,27 +22528,27 @@
                 },
                 "buffer-from": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz",
                     "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA=="
                 },
                 "builtins": {
                     "version": "1.0.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz",
                     "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og="
                 },
                 "byline": {
                     "version": "5.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz",
                     "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE="
                 },
                 "byte-size": {
                     "version": "5.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-5.0.1.tgz",
                     "integrity": "sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw=="
                 },
                 "cacache": {
                     "version": "12.0.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
                     "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==",
                     "requires": {
                         "bluebird": "^3.5.5",
@@ -22492,27 +22570,27 @@
                 },
                 "call-limit": {
                     "version": "1.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/call-limit/-/call-limit-1.1.1.tgz",
                     "integrity": "sha512-5twvci5b9eRBw2wCfPtN0GmlR2/gadZqyFpPhOK6CvMFoFgA+USnZ6Jpu1lhG9h85pQ3Ouil3PfXWRD4EUaRiQ=="
                 },
                 "camelcase": {
                     "version": "4.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
                     "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0="
                 },
                 "capture-stack-trace": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz",
                     "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0="
                 },
                 "caseless": {
                     "version": "0.12.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
                     "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw=="
                 },
                 "chalk": {
                     "version": "2.4.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
                     "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
                     "requires": {
                         "ansi-styles": "^3.2.1",
@@ -22522,17 +22600,17 @@
                 },
                 "chownr": {
                     "version": "1.1.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
                     "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
                 },
                 "ci-info": {
                     "version": "2.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
                     "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="
                 },
                 "cidr-regex": {
                     "version": "2.0.10",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/cidr-regex/-/cidr-regex-2.0.10.tgz",
                     "integrity": "sha512-sB3ogMQXWvreNPbJUZMRApxuRYd+KoIo4RGQ81VatjmMW6WJPo+IJZ2846FGItr9VzKo5w7DXzijPLGtSd0N3Q==",
                     "requires": {
                         "ip-regex": "^2.1.0"
@@ -22540,12 +22618,12 @@
                 },
                 "cli-boxes": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
                     "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM="
                 },
                 "cli-columns": {
                     "version": "3.1.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/cli-columns/-/cli-columns-3.1.2.tgz",
                     "integrity": "sha1-ZzLZcpee/CrkRKHwjgj6E5yWoY4=",
                     "requires": {
                         "string-width": "^2.0.0",
@@ -22554,7 +22632,7 @@
                 },
                 "cli-table3": {
                     "version": "0.5.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz",
                     "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==",
                     "requires": {
                         "colors": "^1.1.2",
@@ -22564,7 +22642,7 @@
                 },
                 "cliui": {
                     "version": "5.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
                     "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
                     "requires": {
                         "string-width": "^3.1.0",
@@ -22574,17 +22652,17 @@
                     "dependencies": {
                         "ansi-regex": {
                             "version": "4.1.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
                             "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="
                         },
                         "is-fullwidth-code-point": {
                             "version": "2.0.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
                             "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
                         },
                         "string-width": {
                             "version": "3.1.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
                             "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
                             "requires": {
                                 "emoji-regex": "^7.0.1",
@@ -22594,7 +22672,7 @@
                         },
                         "strip-ansi": {
                             "version": "5.2.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
                             "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
                             "requires": {
                                 "ansi-regex": "^4.1.0"
@@ -22604,12 +22682,12 @@
                 },
                 "clone": {
                     "version": "1.0.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
                     "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4="
                 },
                 "cmd-shim": {
                     "version": "3.0.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-3.0.3.tgz",
                     "integrity": "sha512-DtGg+0xiFhQIntSBRzL2fRQBnmtAVwXIDo4Qq46HPpObYquxMaZS4sb82U9nH91qJrlosC1wa9gwr0QyL/HypA==",
                     "requires": {
                         "graceful-fs": "^4.1.2",
@@ -22618,12 +22696,12 @@
                 },
                 "code-point-at": {
                     "version": "1.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
                     "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
                 },
                 "color-convert": {
                     "version": "1.9.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
                     "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
                     "requires": {
                         "color-name": "^1.1.1"
@@ -22631,18 +22709,18 @@
                 },
                 "color-name": {
                     "version": "1.1.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
                     "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
                 },
                 "colors": {
                     "version": "1.3.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz",
                     "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==",
                     "optional": true
                 },
                 "columnify": {
                     "version": "1.5.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz",
                     "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=",
                     "requires": {
                         "strip-ansi": "^3.0.0",
@@ -22651,7 +22729,7 @@
                 },
                 "combined-stream": {
                     "version": "1.0.8",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
                     "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
                     "requires": {
                         "delayed-stream": "~1.0.0"
@@ -22659,12 +22737,12 @@
                 },
                 "concat-map": {
                     "version": "0.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
                     "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
                 },
                 "concat-stream": {
                     "version": "1.6.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
                     "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
                     "requires": {
                         "buffer-from": "^1.0.0",
@@ -22675,7 +22753,7 @@
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
                             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -22689,14 +22767,14 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
                         },
                         "string_decoder": {
                             "version": "1.1.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
                             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                             "requires": {
                                 "safe-buffer": "~5.1.0"
@@ -22704,7 +22782,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -22713,7 +22791,7 @@
                 },
                 "config-chain": {
                     "version": "1.1.13",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
                     "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
                     "requires": {
                         "ini": "^1.3.4",
@@ -22722,7 +22800,7 @@
                 },
                 "configstore": {
                     "version": "3.1.5",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.5.tgz",
                     "integrity": "sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA==",
                     "requires": {
                         "dot-prop": "^4.2.1",
@@ -22735,12 +22813,12 @@
                 },
                 "console-control-strings": {
                     "version": "1.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
                     "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
                 },
                 "copy-concurrently": {
                     "version": "1.0.5",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
                     "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
                     "requires": {
                         "aproba": "^1.1.1",
@@ -22753,24 +22831,24 @@
                     "dependencies": {
                         "aproba": {
                             "version": "1.2.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
                             "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
                         },
                         "iferr": {
                             "version": "0.1.5",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
                             "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE="
                         }
                     }
                 },
                 "core-util-is": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
                     "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
                 },
                 "create-error-class": {
                     "version": "3.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz",
                     "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=",
                     "requires": {
                         "capture-stack-trace": "^1.0.0"
@@ -22778,7 +22856,7 @@
                 },
                 "cross-spawn": {
                     "version": "5.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
                     "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
                     "requires": {
                         "lru-cache": "^4.0.1",
@@ -22788,7 +22866,7 @@
                     "dependencies": {
                         "lru-cache": {
                             "version": "4.1.5",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
                             "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
                             "requires": {
                                 "pseudomap": "^1.0.2",
@@ -22797,24 +22875,24 @@
                         },
                         "yallist": {
                             "version": "2.1.2",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
                             "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
                         }
                     }
                 },
                 "crypto-random-string": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
                     "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4="
                 },
                 "cyclist": {
                     "version": "0.2.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz",
                     "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA="
                 },
                 "dashdash": {
                     "version": "1.14.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
                     "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
                     "requires": {
                         "assert-plus": "^1.0.0"
@@ -22822,7 +22900,7 @@
                 },
                 "debug": {
                     "version": "3.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
                     "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
                     "requires": {
                         "ms": "2.0.0"
@@ -22830,34 +22908,34 @@
                     "dependencies": {
                         "ms": {
                             "version": "2.0.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
                             "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
                         }
                     }
                 },
                 "debuglog": {
                     "version": "1.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz",
                     "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI="
                 },
                 "decamelize": {
                     "version": "1.2.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
                     "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
                 },
                 "decode-uri-component": {
                     "version": "0.2.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
                     "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="
                 },
                 "deep-extend": {
                     "version": "0.6.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
                     "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
                 },
                 "defaults": {
                     "version": "1.0.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
                     "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
                     "requires": {
                         "clone": "^1.0.2"
@@ -22865,7 +22943,7 @@
                 },
                 "define-properties": {
                     "version": "1.1.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
                     "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
                     "requires": {
                         "object-keys": "^1.0.12"
@@ -22873,27 +22951,27 @@
                 },
                 "delayed-stream": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
                     "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
                 },
                 "delegates": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
                     "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
                 },
                 "detect-indent": {
                     "version": "5.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz",
                     "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50="
                 },
                 "detect-newline": {
                     "version": "2.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz",
                     "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I="
                 },
                 "dezalgo": {
                     "version": "1.0.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
                     "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
                     "requires": {
                         "asap": "^2.0.0",
@@ -22902,7 +22980,7 @@
                 },
                 "dot-prop": {
                     "version": "4.2.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz",
                     "integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==",
                     "requires": {
                         "is-obj": "^1.0.0"
@@ -22910,17 +22988,17 @@
                 },
                 "dotenv": {
                     "version": "5.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz",
                     "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow=="
                 },
                 "duplexer3": {
                     "version": "0.1.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
                     "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
                 },
                 "duplexify": {
                     "version": "3.6.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz",
                     "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==",
                     "requires": {
                         "end-of-stream": "^1.0.0",
@@ -22931,7 +23009,7 @@
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
                             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -22945,14 +23023,14 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
                         },
                         "string_decoder": {
                             "version": "1.1.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
                             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                             "requires": {
                                 "safe-buffer": "~5.1.0"
@@ -22960,7 +23038,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -22969,7 +23047,7 @@
                 },
                 "ecc-jsbn": {
                     "version": "0.1.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
                     "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
                     "requires": {
                         "jsbn": "~0.1.0",
@@ -22978,17 +23056,17 @@
                 },
                 "editor": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/editor/-/editor-1.0.0.tgz",
                     "integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I="
                 },
                 "emoji-regex": {
                     "version": "7.0.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
                     "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="
                 },
                 "encoding": {
                     "version": "0.1.12",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
                     "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
                     "requires": {
                         "iconv-lite": "~0.4.13"
@@ -22996,7 +23074,7 @@
                 },
                 "end-of-stream": {
                     "version": "1.4.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
                     "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
                     "requires": {
                         "once": "^1.4.0"
@@ -23004,17 +23082,17 @@
                 },
                 "env-paths": {
                     "version": "2.2.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
                     "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="
                 },
                 "err-code": {
                     "version": "1.1.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz",
                     "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA="
                 },
                 "errno": {
                     "version": "0.1.7",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
                     "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
                     "requires": {
                         "prr": "~1.0.1"
@@ -23022,7 +23100,7 @@
                 },
                 "es-abstract": {
                     "version": "1.12.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz",
                     "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==",
                     "requires": {
                         "es-to-primitive": "^1.1.1",
@@ -23034,7 +23112,7 @@
                 },
                 "es-to-primitive": {
                     "version": "1.2.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
                     "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
                     "requires": {
                         "is-callable": "^1.1.4",
@@ -23044,12 +23122,12 @@
                 },
                 "es6-promise": {
                     "version": "4.2.8",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
                     "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
                 },
                 "es6-promisify": {
                     "version": "5.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
                     "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
                     "requires": {
                         "es6-promise": "^4.0.3"
@@ -23057,12 +23135,12 @@
                 },
                 "escape-string-regexp": {
                     "version": "1.0.5",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
                     "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
                 },
                 "execa": {
                     "version": "0.7.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
                     "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
                     "requires": {
                         "cross-spawn": "^5.0.1",
@@ -23076,44 +23154,44 @@
                     "dependencies": {
                         "get-stream": {
                             "version": "3.0.0",
-                            "resolved": false,
+                            "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
                             "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
                         }
                     }
                 },
                 "extend": {
                     "version": "3.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
                     "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
                 },
                 "extsprintf": {
                     "version": "1.3.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
                     "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g=="
                 },
                 "fast-json-stable-stringify": {
                     "version": "2.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
                     "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
                 },
                 "figgy-pudding": {
                     "version": "3.5.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
                     "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw=="
                 },
                 "filter-obj": {
                     "version": "1.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
                     "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="
                 },
                 "find-npm-prefix": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/find-npm-prefix/-/find-npm-prefix-1.0.2.tgz",
                     "integrity": "sha512-KEftzJ+H90x6pcKtdXZEPsQse8/y/UnvzRKrOSQFprnrGaFuJ62fVkP34Iu2IYuMvyauCyoLTNkJZgrrGA2wkA=="
                 },
                 "flush-write-stream": {
                     "version": "1.0.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz",
                     "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==",
                     "requires": {
                         "inherits": "^2.0.1",
@@ -23122,7 +23200,7 @@
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
                             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -23136,14 +23214,14 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
                         },
                         "string_decoder": {
                             "version": "1.1.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
                             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                             "requires": {
                                 "safe-buffer": "~5.1.0"
@@ -23151,7 +23229,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -23160,12 +23238,12 @@
                 },
                 "forever-agent": {
                     "version": "0.6.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
                     "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw=="
                 },
                 "form-data": {
                     "version": "2.3.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
                     "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
                     "requires": {
                         "asynckit": "^0.4.0",
@@ -23175,7 +23253,7 @@
                 },
                 "from2": {
                     "version": "2.3.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
                     "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
                     "requires": {
                         "inherits": "^2.0.1",
@@ -23184,7 +23262,7 @@
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
                             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -23198,14 +23276,14 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
                         },
                         "string_decoder": {
                             "version": "1.1.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
                             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                             "requires": {
                                 "safe-buffer": "~5.1.0"
@@ -23213,7 +23291,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -23222,7 +23300,7 @@
                 },
                 "fs-minipass": {
                     "version": "1.2.7",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
                     "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
                     "requires": {
                         "minipass": "^2.6.0"
@@ -23230,7 +23308,7 @@
                     "dependencies": {
                         "minipass": {
                             "version": "2.9.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
                             "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
                             "requires": {
                                 "safe-buffer": "^5.1.2",
@@ -23241,7 +23319,7 @@
                 },
                 "fs-vacuum": {
                     "version": "1.2.10",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/fs-vacuum/-/fs-vacuum-1.2.10.tgz",
                     "integrity": "sha1-t2Kb7AekAxolSP35n17PHMizHjY=",
                     "requires": {
                         "graceful-fs": "^4.1.2",
@@ -23251,7 +23329,7 @@
                 },
                 "fs-write-stream-atomic": {
                     "version": "1.0.10",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
                     "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
                     "requires": {
                         "graceful-fs": "^4.1.2",
@@ -23262,12 +23340,12 @@
                     "dependencies": {
                         "iferr": {
                             "version": "0.1.5",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
                             "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE="
                         },
                         "readable-stream": {
                             "version": "2.3.6",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
                             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -23281,14 +23359,14 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
                         },
                         "string_decoder": {
                             "version": "1.1.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
                             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                             "requires": {
                                 "safe-buffer": "~5.1.0"
@@ -23296,7 +23374,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -23305,17 +23383,17 @@
                 },
                 "fs.realpath": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
                     "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
                 },
                 "function-bind": {
                     "version": "1.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
                     "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
                 },
                 "gauge": {
                     "version": "2.7.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
                     "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
                     "requires": {
                         "aproba": "^1.0.3",
@@ -23330,12 +23408,12 @@
                     "dependencies": {
                         "aproba": {
                             "version": "1.2.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
                             "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
                         },
                         "string-width": {
                             "version": "1.0.2",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
                             "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
                             "requires": {
                                 "code-point-at": "^1.0.0",
@@ -23347,12 +23425,12 @@
                 },
                 "genfun": {
                     "version": "5.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz",
                     "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA=="
                 },
                 "gentle-fs": {
                     "version": "2.3.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/gentle-fs/-/gentle-fs-2.3.1.tgz",
                     "integrity": "sha512-OlwBBwqCFPcjm33rF2BjW+Pr6/ll2741l+xooiwTCeaX2CA1ZuclavyMBe0/KlR21/XGsgY6hzEQZ15BdNa13Q==",
                     "requires": {
                         "aproba": "^1.1.2",
@@ -23370,24 +23448,24 @@
                     "dependencies": {
                         "aproba": {
                             "version": "1.2.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
                             "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
                         },
                         "iferr": {
                             "version": "0.1.5",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
                             "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE="
                         }
                     }
                 },
                 "get-caller-file": {
                     "version": "2.0.5",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
                     "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
                 },
                 "get-stream": {
                     "version": "4.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
                     "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
                     "requires": {
                         "pump": "^3.0.0"
@@ -23395,7 +23473,7 @@
                 },
                 "getpass": {
                     "version": "0.1.7",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
                     "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
                     "requires": {
                         "assert-plus": "^1.0.0"
@@ -23403,7 +23481,7 @@
                 },
                 "glob": {
                     "version": "7.2.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
                     "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
                     "requires": {
                         "fs.realpath": "^1.0.0",
@@ -23416,7 +23494,7 @@
                     "dependencies": {
                         "minimatch": {
                             "version": "3.1.2",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
                             "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
                             "requires": {
                                 "brace-expansion": "^1.1.7"
@@ -23426,7 +23504,7 @@
                 },
                 "global-dirs": {
                     "version": "0.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
                     "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=",
                     "requires": {
                         "ini": "^1.3.4"
@@ -23434,7 +23512,7 @@
                 },
                 "got": {
                     "version": "6.7.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
                     "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=",
                     "requires": {
                         "create-error-class": "^3.0.0",
@@ -23452,24 +23530,24 @@
                     "dependencies": {
                         "get-stream": {
                             "version": "3.0.0",
-                            "resolved": false,
+                            "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
                             "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
                         }
                     }
                 },
                 "graceful-fs": {
                     "version": "4.2.10",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
                     "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
                 },
                 "har-schema": {
                     "version": "2.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
                     "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q=="
                 },
                 "har-validator": {
                     "version": "5.1.5",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
                     "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
                     "requires": {
                         "ajv": "^6.12.3",
@@ -23478,7 +23556,7 @@
                     "dependencies": {
                         "ajv": {
                             "version": "6.12.6",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
                             "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
                             "requires": {
                                 "fast-deep-equal": "^3.1.1",
@@ -23489,19 +23567,19 @@
                         },
                         "fast-deep-equal": {
                             "version": "3.1.3",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
                             "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
                         },
                         "json-schema-traverse": {
                             "version": "0.4.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
                             "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
                         }
                     }
                 },
                 "has": {
                     "version": "1.0.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
                     "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
                     "requires": {
                         "function-bind": "^1.1.1"
@@ -23509,32 +23587,32 @@
                 },
                 "has-flag": {
                     "version": "3.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
                     "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
                 },
                 "has-symbols": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
                     "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q="
                 },
                 "has-unicode": {
                     "version": "2.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
                     "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
                 },
                 "hosted-git-info": {
                     "version": "2.8.9",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
                     "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="
                 },
                 "http-cache-semantics": {
                     "version": "3.8.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz",
                     "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w=="
                 },
                 "http-proxy-agent": {
                     "version": "2.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz",
                     "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==",
                     "requires": {
                         "agent-base": "4",
@@ -23543,7 +23621,7 @@
                 },
                 "http-signature": {
                     "version": "1.2.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
                     "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
                     "requires": {
                         "assert-plus": "^1.0.0",
@@ -23553,7 +23631,7 @@
                 },
                 "https-proxy-agent": {
                     "version": "2.2.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz",
                     "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==",
                     "requires": {
                         "agent-base": "^4.3.0",
@@ -23562,7 +23640,7 @@
                 },
                 "humanize-ms": {
                     "version": "1.2.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
                     "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=",
                     "requires": {
                         "ms": "^2.0.0"
@@ -23570,7 +23648,7 @@
                 },
                 "iconv-lite": {
                     "version": "0.4.23",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
                     "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
                     "requires": {
                         "safer-buffer": ">= 2.1.2 < 3"
@@ -23578,12 +23656,12 @@
                 },
                 "iferr": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/iferr/-/iferr-1.0.2.tgz",
                     "integrity": "sha512-9AfeLfji44r5TKInjhz3W9DyZI1zR1JAf2hVBMGhddAKPqBsupb89jGfbCTHIGZd6fGZl9WlHdn4AObygyMKwg=="
                 },
                 "ignore-walk": {
                     "version": "3.0.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
                     "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
                     "requires": {
                         "minimatch": "^3.0.4"
@@ -23591,22 +23669,22 @@
                 },
                 "import-lazy": {
                     "version": "2.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
                     "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM="
                 },
                 "imurmurhash": {
                     "version": "0.1.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
                     "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
                 },
                 "infer-owner": {
                     "version": "1.0.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
                     "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A=="
                 },
                 "inflight": {
                     "version": "1.0.6",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
                     "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
                     "requires": {
                         "once": "^1.3.0",
@@ -23615,17 +23693,17 @@
                 },
                 "inherits": {
                     "version": "2.0.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
                     "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
                 },
                 "ini": {
                     "version": "1.3.8",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
                     "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
                 },
                 "init-package-json": {
                     "version": "1.10.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.3.tgz",
                     "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==",
                     "requires": {
                         "glob": "^7.1.1",
@@ -23640,22 +23718,22 @@
                 },
                 "ip": {
                     "version": "1.1.5",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
                     "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
                 },
                 "ip-regex": {
                     "version": "2.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz",
                     "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk="
                 },
                 "is-callable": {
                     "version": "1.1.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
                     "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA=="
                 },
                 "is-ci": {
                     "version": "1.2.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz",
                     "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==",
                     "requires": {
                         "ci-info": "^1.5.0"
@@ -23663,14 +23741,14 @@
                     "dependencies": {
                         "ci-info": {
                             "version": "1.6.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
                             "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A=="
                         }
                     }
                 },
                 "is-cidr": {
                     "version": "3.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/is-cidr/-/is-cidr-3.1.1.tgz",
                     "integrity": "sha512-Gx+oErgq1j2jAKCR2Kbq0b3wbH0vQKqZ0wOlHxm0o56nq51Cs/DZA8oz9dMDhbHyHEGgJ86eTeVudtgMMOx3Mw==",
                     "requires": {
                         "cidr-regex": "^2.0.10"
@@ -23678,12 +23756,12 @@
                 },
                 "is-date-object": {
                     "version": "1.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
                     "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY="
                 },
                 "is-fullwidth-code-point": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
                     "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
                     "requires": {
                         "number-is-nan": "^1.0.0"
@@ -23691,7 +23769,7 @@
                 },
                 "is-installed-globally": {
                     "version": "0.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz",
                     "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=",
                     "requires": {
                         "global-dirs": "^0.1.0",
@@ -23700,17 +23778,17 @@
                 },
                 "is-npm": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz",
                     "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ="
                 },
                 "is-obj": {
                     "version": "1.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
                     "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8="
                 },
                 "is-path-inside": {
                     "version": "1.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
                     "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
                     "requires": {
                         "path-is-inside": "^1.0.1"
@@ -23718,12 +23796,12 @@
                 },
                 "is-redirect": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
                     "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ="
                 },
                 "is-regex": {
                     "version": "1.0.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
                     "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
                     "requires": {
                         "has": "^1.0.1"
@@ -23731,17 +23809,17 @@
                 },
                 "is-retry-allowed": {
                     "version": "1.2.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz",
                     "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg=="
                 },
                 "is-stream": {
                     "version": "1.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
                     "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
                 },
                 "is-symbol": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
                     "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
                     "requires": {
                         "has-symbols": "^1.0.0"
@@ -23749,57 +23827,57 @@
                 },
                 "is-typedarray": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
                     "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
                 },
                 "isarray": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
                     "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
                 },
                 "isexe": {
                     "version": "2.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
                     "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
                 },
                 "isstream": {
                     "version": "0.1.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
                     "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="
                 },
                 "jsbn": {
                     "version": "0.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
                     "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg=="
                 },
                 "json-parse-better-errors": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
                     "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
                 },
                 "json-parse-even-better-errors": {
                     "version": "2.3.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
                     "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
                 },
                 "json-schema": {
                     "version": "0.4.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
                     "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
                 },
                 "json-stringify-safe": {
                     "version": "5.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
                     "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
                 },
                 "jsonparse": {
                     "version": "1.3.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
                     "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA="
                 },
                 "jsprim": {
                     "version": "1.4.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
                     "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
                     "requires": {
                         "assert-plus": "1.0.0",
@@ -23810,7 +23888,7 @@
                 },
                 "latest-version": {
                     "version": "3.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz",
                     "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=",
                     "requires": {
                         "package-json": "^4.0.0"
@@ -23818,12 +23896,12 @@
                 },
                 "lazy-property": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lazy-property/-/lazy-property-1.0.0.tgz",
                     "integrity": "sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc="
                 },
                 "libcipm": {
                     "version": "4.0.8",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/libcipm/-/libcipm-4.0.8.tgz",
                     "integrity": "sha512-IN3hh2yDJQtZZ5paSV4fbvJg4aHxCCg5tcZID/dSVlTuUiWktsgaldVljJv6Z5OUlYspx6xQkbR0efNodnIrOA==",
                     "requires": {
                         "bin-links": "^1.1.2",
@@ -23845,7 +23923,7 @@
                 },
                 "libnpm": {
                     "version": "3.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/libnpm/-/libnpm-3.0.1.tgz",
                     "integrity": "sha512-d7jU5ZcMiTfBqTUJVZ3xid44fE5ERBm9vBnmhp2ECD2Ls+FNXWxHSkO7gtvrnbLO78gwPdNPz1HpsF3W4rjkBQ==",
                     "requires": {
                         "bin-links": "^1.1.2",
@@ -23872,7 +23950,7 @@
                 },
                 "libnpmaccess": {
                     "version": "3.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-3.0.2.tgz",
                     "integrity": "sha512-01512AK7MqByrI2mfC7h5j8N9V4I7MHJuk9buo8Gv+5QgThpOgpjB7sQBDDkeZqRteFb1QM/6YNdHfG7cDvfAQ==",
                     "requires": {
                         "aproba": "^2.0.0",
@@ -23883,7 +23961,7 @@
                 },
                 "libnpmconfig": {
                     "version": "1.2.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/libnpmconfig/-/libnpmconfig-1.2.1.tgz",
                     "integrity": "sha512-9esX8rTQAHqarx6qeZqmGQKBNZR5OIbl/Ayr0qQDy3oXja2iFVQQI81R6GZ2a02bSNZ9p3YOGX1O6HHCb1X7kA==",
                     "requires": {
                         "figgy-pudding": "^3.5.1",
@@ -23893,7 +23971,7 @@
                     "dependencies": {
                         "find-up": {
                             "version": "3.0.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
                             "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
                             "requires": {
                                 "locate-path": "^3.0.0"
@@ -23901,7 +23979,7 @@
                         },
                         "locate-path": {
                             "version": "3.0.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
                             "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
                             "requires": {
                                 "p-locate": "^3.0.0",
@@ -23910,7 +23988,7 @@
                         },
                         "p-limit": {
                             "version": "2.2.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
                             "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==",
                             "requires": {
                                 "p-try": "^2.0.0"
@@ -23918,7 +23996,7 @@
                         },
                         "p-locate": {
                             "version": "3.0.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
                             "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
                             "requires": {
                                 "p-limit": "^2.0.0"
@@ -23926,14 +24004,14 @@
                         },
                         "p-try": {
                             "version": "2.2.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
                             "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
                         }
                     }
                 },
                 "libnpmhook": {
                     "version": "5.0.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/libnpmhook/-/libnpmhook-5.0.3.tgz",
                     "integrity": "sha512-UdNLMuefVZra/wbnBXECZPefHMGsVDTq5zaM/LgKNE9Keyl5YXQTnGAzEo+nFOpdRqTWI9LYi4ApqF9uVCCtuA==",
                     "requires": {
                         "aproba": "^2.0.0",
@@ -23944,7 +24022,7 @@
                 },
                 "libnpmorg": {
                     "version": "1.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/libnpmorg/-/libnpmorg-1.0.1.tgz",
                     "integrity": "sha512-0sRUXLh+PLBgZmARvthhYXQAWn0fOsa6T5l3JSe2n9vKG/lCVK4nuG7pDsa7uMq+uTt2epdPK+a2g6btcY11Ww==",
                     "requires": {
                         "aproba": "^2.0.0",
@@ -23955,7 +24033,7 @@
                 },
                 "libnpmpublish": {
                     "version": "1.1.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-1.1.2.tgz",
                     "integrity": "sha512-2yIwaXrhTTcF7bkJKIKmaCV9wZOALf/gsTDxVSu/Gu/6wiG3fA8ce8YKstiWKTxSFNC0R7isPUb6tXTVFZHt2g==",
                     "requires": {
                         "aproba": "^2.0.0",
@@ -23971,7 +24049,7 @@
                 },
                 "libnpmsearch": {
                     "version": "2.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/libnpmsearch/-/libnpmsearch-2.0.2.tgz",
                     "integrity": "sha512-VTBbV55Q6fRzTdzziYCr64+f8AopQ1YZ+BdPOv16UegIEaE8C0Kch01wo4s3kRTFV64P121WZJwgmBwrq68zYg==",
                     "requires": {
                         "figgy-pudding": "^3.5.1",
@@ -23981,7 +24059,7 @@
                 },
                 "libnpmteam": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/libnpmteam/-/libnpmteam-1.0.2.tgz",
                     "integrity": "sha512-p420vM28Us04NAcg1rzgGW63LMM6rwe+6rtZpfDxCcXxM0zUTLl7nPFEnRF3JfFBF5skF/yuZDUthTsHgde8QA==",
                     "requires": {
                         "aproba": "^2.0.0",
@@ -23992,7 +24070,7 @@
                 },
                 "libnpx": {
                     "version": "10.2.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/libnpx/-/libnpx-10.2.4.tgz",
                     "integrity": "sha512-BPc0D1cOjBeS8VIBKUu5F80s6njm0wbVt7CsGMrIcJ+SI7pi7V0uVPGpEMH9H5L8csOcclTxAXFE2VAsJXUhfA==",
                     "requires": {
                         "dotenv": "^5.0.1",
@@ -24007,7 +24085,7 @@
                 },
                 "lock-verify": {
                     "version": "2.2.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lock-verify/-/lock-verify-2.2.2.tgz",
                     "integrity": "sha512-2CUNtr1ZSVKJHcYP8uEzafmmuyauCB5zZimj8TvQd/Lflt9kXVZs+8S+EbAzZLaVUDn8CYGmeC3DFGdYfnCzeQ==",
                     "requires": {
                         "@iarna/cli": "^2.1.0",
@@ -24017,7 +24095,7 @@
                 },
                 "lockfile": {
                     "version": "1.0.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz",
                     "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==",
                     "requires": {
                         "signal-exit": "^3.0.2"
@@ -24025,12 +24103,12 @@
                 },
                 "lodash._baseindexof": {
                     "version": "3.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz",
                     "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw="
                 },
                 "lodash._baseuniq": {
                     "version": "4.6.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz",
                     "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=",
                     "requires": {
                         "lodash._createset": "~4.0.0",
@@ -24039,17 +24117,17 @@
                 },
                 "lodash._bindcallback": {
                     "version": "3.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz",
                     "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4="
                 },
                 "lodash._cacheindexof": {
                     "version": "3.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz",
                     "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI="
                 },
                 "lodash._createcache": {
                     "version": "3.1.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz",
                     "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=",
                     "requires": {
                         "lodash._getnative": "^3.0.0"
@@ -24057,52 +24135,52 @@
                 },
                 "lodash._createset": {
                     "version": "4.0.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz",
                     "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY="
                 },
                 "lodash._getnative": {
                     "version": "3.9.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
                     "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U="
                 },
                 "lodash._root": {
                     "version": "3.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz",
                     "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI="
                 },
                 "lodash.clonedeep": {
                     "version": "4.5.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
                     "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
                 },
                 "lodash.restparam": {
                     "version": "3.6.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz",
                     "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU="
                 },
                 "lodash.union": {
                     "version": "4.6.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
                     "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg="
                 },
                 "lodash.uniq": {
                     "version": "4.5.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
                     "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M="
                 },
                 "lodash.without": {
                     "version": "4.4.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lodash.without/-/lodash.without-4.4.0.tgz",
                     "integrity": "sha1-PNRXSgC2e643OpS3SHcmQFB7eqw="
                 },
                 "lowercase-keys": {
                     "version": "1.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
                     "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA=="
                 },
                 "lru-cache": {
                     "version": "5.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
                     "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
                     "requires": {
                         "yallist": "^3.0.2"
@@ -24110,7 +24188,7 @@
                 },
                 "make-dir": {
                     "version": "1.3.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
                     "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
                     "requires": {
                         "pify": "^3.0.0"
@@ -24118,7 +24196,7 @@
                 },
                 "make-fetch-happen": {
                     "version": "5.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz",
                     "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==",
                     "requires": {
                         "agentkeepalive": "^3.4.1",
@@ -24136,17 +24214,17 @@
                 },
                 "meant": {
                     "version": "1.0.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/meant/-/meant-1.0.3.tgz",
                     "integrity": "sha512-88ZRGcNxAq4EH38cQ4D85PM57pikCwS8Z99EWHODxN7KBY+UuPiqzRTtZzS8KTXO/ywSWbdjjJST2Hly/EQxLw=="
                 },
                 "mime-db": {
                     "version": "1.35.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz",
                     "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg=="
                 },
                 "mime-types": {
                     "version": "2.1.19",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz",
                     "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==",
                     "requires": {
                         "mime-db": "~1.35.0"
@@ -24154,7 +24232,7 @@
                 },
                 "minimatch": {
                     "version": "3.1.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
                     "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
                     "requires": {
                         "brace-expansion": "^1.1.7"
@@ -24162,12 +24240,12 @@
                 },
                 "minimist": {
                     "version": "1.2.6",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
                     "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
                 },
                 "minizlib": {
                     "version": "1.3.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
                     "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
                     "requires": {
                         "minipass": "^2.9.0"
@@ -24175,7 +24253,7 @@
                     "dependencies": {
                         "minipass": {
                             "version": "2.9.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
                             "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
                             "requires": {
                                 "safe-buffer": "^5.1.2",
@@ -24186,7 +24264,7 @@
                 },
                 "mississippi": {
                     "version": "3.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
                     "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
                     "requires": {
                         "concat-stream": "^1.5.0",
@@ -24203,7 +24281,7 @@
                 },
                 "mkdirp": {
                     "version": "0.5.6",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
                     "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
                     "requires": {
                         "minimist": "^1.2.6"
@@ -24211,7 +24289,7 @@
                 },
                 "move-concurrently": {
                     "version": "1.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
                     "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
                     "requires": {
                         "aproba": "^1.1.1",
@@ -24224,24 +24302,24 @@
                     "dependencies": {
                         "aproba": {
                             "version": "1.2.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
                             "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
                         }
                     }
                 },
                 "ms": {
                     "version": "2.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
                     "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
                 },
                 "mute-stream": {
                     "version": "0.0.7",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
                     "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s="
                 },
                 "node-fetch-npm": {
                     "version": "2.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz",
                     "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==",
                     "requires": {
                         "encoding": "^0.1.11",
@@ -24251,7 +24329,7 @@
                 },
                 "node-gyp": {
                     "version": "5.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.1.tgz",
                     "integrity": "sha512-WH0WKGi+a4i4DUt2mHnvocex/xPLp9pYt5R6M2JdFB7pJ7Z34hveZ4nDTGTiLXCkitA9T8HFZjhinBCiVHYcWw==",
                     "requires": {
                         "env-paths": "^2.2.0",
@@ -24269,7 +24347,7 @@
                 },
                 "nopt": {
                     "version": "4.0.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
                     "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
                     "requires": {
                         "abbrev": "1",
@@ -24278,7 +24356,7 @@
                 },
                 "normalize-package-data": {
                     "version": "2.5.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
                     "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
                     "requires": {
                         "hosted-git-info": "^2.1.4",
@@ -24289,7 +24367,7 @@
                     "dependencies": {
                         "resolve": {
                             "version": "1.10.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",
                             "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==",
                             "requires": {
                                 "path-parse": "^1.0.6"
@@ -24299,7 +24377,7 @@
                 },
                 "npm-audit-report": {
                     "version": "1.3.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/npm-audit-report/-/npm-audit-report-1.3.3.tgz",
                     "integrity": "sha512-8nH/JjsFfAWMvn474HB9mpmMjrnKb1Hx/oTAdjv4PT9iZBvBxiZ+wtDUapHCJwLqYGQVPaAfs+vL5+5k9QndXw==",
                     "requires": {
                         "cli-table3": "^0.5.0",
@@ -24308,7 +24386,7 @@
                 },
                 "npm-bundled": {
                     "version": "1.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz",
                     "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
                     "requires": {
                         "npm-normalize-package-bin": "^1.0.1"
@@ -24316,12 +24394,12 @@
                 },
                 "npm-cache-filename": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/npm-cache-filename/-/npm-cache-filename-1.0.2.tgz",
                     "integrity": "sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE="
                 },
                 "npm-install-checks": {
                     "version": "3.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-3.0.2.tgz",
                     "integrity": "sha512-E4kzkyZDIWoin6uT5howP8VDvkM+E8IQDcHAycaAxMbwkqhIg5eEYALnXOl3Hq9MrkdQB/2/g1xwBINXdKSRkg==",
                     "requires": {
                         "semver": "^2.3.0 || 3.x || 4 || 5"
@@ -24329,7 +24407,7 @@
                 },
                 "npm-lifecycle": {
                     "version": "3.1.5",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-3.1.5.tgz",
                     "integrity": "sha512-lDLVkjfZmvmfvpvBzA4vzee9cn+Me4orq0QF8glbswJVEbIcSNWib7qGOffolysc3teCqbbPZZkzbr3GQZTL1g==",
                     "requires": {
                         "byline": "^5.0.0",
@@ -24344,17 +24422,17 @@
                 },
                 "npm-logical-tree": {
                     "version": "1.2.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/npm-logical-tree/-/npm-logical-tree-1.2.1.tgz",
                     "integrity": "sha512-AJI/qxDB2PWI4LG1CYN579AY1vCiNyWfkiquCsJWqntRu/WwimVrC8yXeILBFHDwxfOejxewlmnvW9XXjMlYIg=="
                 },
                 "npm-normalize-package-bin": {
                     "version": "1.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
                     "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA=="
                 },
                 "npm-package-arg": {
                     "version": "6.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz",
                     "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==",
                     "requires": {
                         "hosted-git-info": "^2.7.1",
@@ -24365,7 +24443,7 @@
                 },
                 "npm-packlist": {
                     "version": "1.4.8",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
                     "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
                     "requires": {
                         "ignore-walk": "^3.0.1",
@@ -24375,7 +24453,7 @@
                 },
                 "npm-pick-manifest": {
                     "version": "3.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz",
                     "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==",
                     "requires": {
                         "figgy-pudding": "^3.5.1",
@@ -24385,7 +24463,7 @@
                 },
                 "npm-profile": {
                     "version": "4.0.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/npm-profile/-/npm-profile-4.0.4.tgz",
                     "integrity": "sha512-Ta8xq8TLMpqssF0H60BXS1A90iMoM6GeKwsmravJ6wYjWwSzcYBTdyWa3DZCYqPutacBMEm7cxiOkiIeCUAHDQ==",
                     "requires": {
                         "aproba": "^1.1.2 || 2",
@@ -24395,7 +24473,7 @@
                 },
                 "npm-registry-fetch": {
                     "version": "4.0.7",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.7.tgz",
                     "integrity": "sha512-cny9v0+Mq6Tjz+e0erFAB+RYJ/AVGzkjnISiobqP8OWj9c9FLoZZu8/SPSKJWE17F1tk4018wfjV+ZbIbqC7fQ==",
                     "requires": {
                         "JSONStream": "^1.3.4",
@@ -24409,14 +24487,14 @@
                     "dependencies": {
                         "safe-buffer": {
                             "version": "5.2.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
                             "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
                         }
                     }
                 },
                 "npm-run-path": {
                     "version": "2.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
                     "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
                     "requires": {
                         "path-key": "^2.0.0"
@@ -24424,12 +24502,12 @@
                 },
                 "npm-user-validate": {
                     "version": "1.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/npm-user-validate/-/npm-user-validate-1.0.1.tgz",
                     "integrity": "sha512-uQwcd/tY+h1jnEaze6cdX/LrhWhoBxfSknxentoqmIuStxUExxjWd3ULMLFPiFUrZKbOVMowH6Jq2FRWfmhcEw=="
                 },
                 "npmlog": {
                     "version": "4.1.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
                     "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
                     "requires": {
                         "are-we-there-yet": "~1.1.2",
@@ -24440,27 +24518,27 @@
                 },
                 "number-is-nan": {
                     "version": "1.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
                     "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
                 },
                 "oauth-sign": {
                     "version": "0.9.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
                     "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
                 },
                 "object-assign": {
                     "version": "4.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
                     "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
                 },
                 "object-keys": {
                     "version": "1.0.12",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz",
                     "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag=="
                 },
                 "object.getownpropertydescriptors": {
                     "version": "2.0.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
                     "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
                     "requires": {
                         "define-properties": "^1.1.2",
@@ -24469,7 +24547,7 @@
                 },
                 "once": {
                     "version": "1.4.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
                     "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
                     "requires": {
                         "wrappy": "1"
@@ -24477,22 +24555,22 @@
                 },
                 "opener": {
                     "version": "1.5.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
                     "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A=="
                 },
                 "os-homedir": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
                     "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
                 },
                 "os-tmpdir": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
                     "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
                 },
                 "osenv": {
                     "version": "0.1.5",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
                     "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
                     "requires": {
                         "os-homedir": "^1.0.0",
@@ -24501,12 +24579,12 @@
                 },
                 "p-finally": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
                     "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
                 },
                 "package-json": {
                     "version": "4.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz",
                     "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=",
                     "requires": {
                         "got": "^6.7.1",
@@ -24517,7 +24595,7 @@
                 },
                 "pacote": {
                     "version": "9.5.12",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.5.12.tgz",
                     "integrity": "sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ==",
                     "requires": {
                         "bluebird": "^3.5.3",
@@ -24554,7 +24632,7 @@
                     "dependencies": {
                         "minipass": {
                             "version": "2.9.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
                             "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
                             "requires": {
                                 "safe-buffer": "^5.1.2",
@@ -24565,7 +24643,7 @@
                 },
                 "parallel-transform": {
                     "version": "1.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz",
                     "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=",
                     "requires": {
                         "cyclist": "~0.2.2",
@@ -24575,7 +24653,7 @@
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
                             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -24589,14 +24667,14 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
                         },
                         "string_decoder": {
                             "version": "1.1.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
                             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                             "requires": {
                                 "safe-buffer": "~5.1.0"
@@ -24604,7 +24682,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -24613,57 +24691,57 @@
                 },
                 "path-exists": {
                     "version": "3.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
                     "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
                 },
                 "path-is-absolute": {
                     "version": "1.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
                     "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
                 },
                 "path-is-inside": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
                     "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM="
                 },
                 "path-key": {
                     "version": "2.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
                     "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
                 },
                 "path-parse": {
                     "version": "1.0.7",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
                     "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
                 },
                 "performance-now": {
                     "version": "2.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
                     "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="
                 },
                 "pify": {
                     "version": "3.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
                     "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
                 },
                 "prepend-http": {
                     "version": "1.0.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
                     "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
                 },
                 "process-nextick-args": {
                     "version": "2.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
                     "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
                 },
                 "promise-inflight": {
                     "version": "1.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
                     "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM="
                 },
                 "promise-retry": {
                     "version": "1.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz",
                     "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=",
                     "requires": {
                         "err-code": "^1.0.0",
@@ -24672,14 +24750,14 @@
                     "dependencies": {
                         "retry": {
                             "version": "0.10.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz",
                             "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q="
                         }
                     }
                 },
                 "promzard": {
                     "version": "0.3.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz",
                     "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=",
                     "requires": {
                         "read": "1"
@@ -24687,12 +24765,12 @@
                 },
                 "proto-list": {
                     "version": "1.2.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
                     "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="
                 },
                 "protoduck": {
                     "version": "5.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz",
                     "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==",
                     "requires": {
                         "genfun": "^5.0.0"
@@ -24700,22 +24778,22 @@
                 },
                 "prr": {
                     "version": "1.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
                     "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY="
                 },
                 "pseudomap": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
                     "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
                 },
                 "psl": {
                     "version": "1.9.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
                     "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
                 },
                 "pump": {
                     "version": "3.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
                     "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
                     "requires": {
                         "end-of-stream": "^1.1.0",
@@ -24724,7 +24802,7 @@
                 },
                 "pumpify": {
                     "version": "1.5.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
                     "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
                     "requires": {
                         "duplexify": "^3.6.0",
@@ -24734,7 +24812,7 @@
                     "dependencies": {
                         "pump": {
                             "version": "2.0.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
                             "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
                             "requires": {
                                 "end-of-stream": "^1.1.0",
@@ -24745,17 +24823,17 @@
                 },
                 "qrcode-terminal": {
                     "version": "0.12.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz",
                     "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ=="
                 },
                 "qs": {
                     "version": "6.5.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
                     "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA=="
                 },
                 "query-string": {
                     "version": "6.14.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz",
                     "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==",
                     "requires": {
                         "decode-uri-component": "^0.2.0",
@@ -24766,12 +24844,12 @@
                 },
                 "qw": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/qw/-/qw-1.0.2.tgz",
                     "integrity": "sha512-1PhZ/iLKwlVNq45dnerTMKFjMof49uqli7/0QsvPNbX5OJ3IZ8msa9lUpvPheVdP+IYYPrf6cOaVil7S35joVA=="
                 },
                 "rc": {
                     "version": "1.2.8",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
                     "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
                     "requires": {
                         "deep-extend": "^0.6.0",
@@ -24782,7 +24860,7 @@
                 },
                 "read": {
                     "version": "1.0.7",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz",
                     "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=",
                     "requires": {
                         "mute-stream": "~0.0.4"
@@ -24790,7 +24868,7 @@
                 },
                 "read-cmd-shim": {
                     "version": "1.0.5",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz",
                     "integrity": "sha512-v5yCqQ/7okKoZZkBQUAfTsQ3sVJtXdNfbPnI5cceppoxEVLYA3k+VtV2omkeo8MS94JCy4fSiUwlRBAwCVRPUA==",
                     "requires": {
                         "graceful-fs": "^4.1.2"
@@ -24798,7 +24876,7 @@
                 },
                 "read-installed": {
                     "version": "4.0.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz",
                     "integrity": "sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=",
                     "requires": {
                         "debuglog": "^1.0.1",
@@ -24812,7 +24890,7 @@
                 },
                 "read-package-json": {
                     "version": "2.1.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz",
                     "integrity": "sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA==",
                     "requires": {
                         "glob": "^7.1.1",
@@ -24823,7 +24901,7 @@
                 },
                 "read-package-tree": {
                     "version": "5.3.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz",
                     "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==",
                     "requires": {
                         "read-package-json": "^2.0.0",
@@ -24833,7 +24911,7 @@
                 },
                 "readable-stream": {
                     "version": "3.6.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
                     "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
                     "requires": {
                         "inherits": "^2.0.3",
@@ -24843,7 +24921,7 @@
                 },
                 "readdir-scoped-modules": {
                     "version": "1.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz",
                     "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==",
                     "requires": {
                         "debuglog": "^1.0.1",
@@ -24854,7 +24932,7 @@
                 },
                 "registry-auth-token": {
                     "version": "3.4.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz",
                     "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==",
                     "requires": {
                         "rc": "^1.1.6",
@@ -24863,7 +24941,7 @@
                 },
                 "registry-url": {
                     "version": "3.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
                     "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=",
                     "requires": {
                         "rc": "^1.0.1"
@@ -24871,7 +24949,7 @@
                 },
                 "request": {
                     "version": "2.88.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
                     "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
                     "requires": {
                         "aws-sign2": "~0.7.0",
@@ -24898,27 +24976,27 @@
                 },
                 "require-directory": {
                     "version": "2.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
                     "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
                 },
                 "require-main-filename": {
                     "version": "2.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
                     "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
                 },
                 "resolve-from": {
                     "version": "4.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
                     "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
                 },
                 "retry": {
                     "version": "0.12.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
                     "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs="
                 },
                 "rimraf": {
                     "version": "2.7.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
                     "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
                     "requires": {
                         "glob": "^7.1.3"
@@ -24926,7 +25004,7 @@
                 },
                 "run-queue": {
                     "version": "1.0.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
                     "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
                     "requires": {
                         "aproba": "^1.1.1"
@@ -24934,29 +25012,29 @@
                     "dependencies": {
                         "aproba": {
                             "version": "1.2.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
                             "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
                         }
                     }
                 },
                 "safe-buffer": {
                     "version": "5.2.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
                     "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
                 },
                 "safer-buffer": {
                     "version": "2.1.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
                     "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
                 },
                 "semver": {
                     "version": "5.7.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
                     "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
                 },
                 "semver-diff": {
                     "version": "2.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz",
                     "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=",
                     "requires": {
                         "semver": "^5.0.3"
@@ -24964,12 +25042,12 @@
                 },
                 "set-blocking": {
                     "version": "2.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
                     "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
                 },
                 "sha": {
                     "version": "3.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/sha/-/sha-3.0.0.tgz",
                     "integrity": "sha512-DOYnM37cNsLNSGIG/zZWch5CKIRNoLdYUQTQlcgkRkoYIUwDYjqDyye16YcDZg/OPdcbUgTKMjc4SY6TB7ZAPw==",
                     "requires": {
                         "graceful-fs": "^4.1.2"
@@ -24977,7 +25055,7 @@
                 },
                 "shebang-command": {
                     "version": "1.2.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
                     "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
                     "requires": {
                         "shebang-regex": "^1.0.0"
@@ -24985,27 +25063,27 @@
                 },
                 "shebang-regex": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
                     "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
                 },
                 "signal-exit": {
                     "version": "3.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
                     "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
                 },
                 "slide": {
                     "version": "1.1.6",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
                     "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc="
                 },
                 "smart-buffer": {
                     "version": "4.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz",
                     "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw=="
                 },
                 "socks": {
                     "version": "2.3.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz",
                     "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==",
                     "requires": {
                         "ip": "1.1.5",
@@ -25014,7 +25092,7 @@
                 },
                 "socks-proxy-agent": {
                     "version": "4.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz",
                     "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==",
                     "requires": {
                         "agent-base": "~4.2.1",
@@ -25023,7 +25101,7 @@
                     "dependencies": {
                         "agent-base": {
                             "version": "4.2.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
                             "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
                             "requires": {
                                 "es6-promisify": "^5.0.0"
@@ -25033,12 +25111,12 @@
                 },
                 "sorted-object": {
                     "version": "2.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/sorted-object/-/sorted-object-2.0.1.tgz",
                     "integrity": "sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw="
                 },
                 "sorted-union-stream": {
                     "version": "2.1.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/sorted-union-stream/-/sorted-union-stream-2.1.3.tgz",
                     "integrity": "sha1-x3lMfgd4gAUv9xqNSi27Sppjisc=",
                     "requires": {
                         "from2": "^1.3.0",
@@ -25047,7 +25125,7 @@
                     "dependencies": {
                         "from2": {
                             "version": "1.3.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/from2/-/from2-1.3.0.tgz",
                             "integrity": "sha1-iEE7qqX5pZfP3pIh2GmGzTwGHf0=",
                             "requires": {
                                 "inherits": "~2.0.1",
@@ -25056,12 +25134,12 @@
                         },
                         "isarray": {
                             "version": "0.0.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
                             "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
                         },
                         "readable-stream": {
                             "version": "1.1.14",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
                             "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -25072,14 +25150,14 @@
                         },
                         "string_decoder": {
                             "version": "0.10.31",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
                             "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
                         }
                     }
                 },
                 "spdx-correct": {
                     "version": "3.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz",
                     "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==",
                     "requires": {
                         "spdx-expression-parse": "^3.0.0",
@@ -25088,12 +25166,12 @@
                 },
                 "spdx-exceptions": {
                     "version": "2.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz",
                     "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg=="
                 },
                 "spdx-expression-parse": {
                     "version": "3.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
                     "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
                     "requires": {
                         "spdx-exceptions": "^2.1.0",
@@ -25102,12 +25180,12 @@
                 },
                 "spdx-license-ids": {
                     "version": "3.0.5",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
                     "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q=="
                 },
                 "split-on-first": {
                     "version": "1.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
                     "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
                 },
                 "sshpk": {
@@ -25128,7 +25206,7 @@
                 },
                 "ssri": {
                     "version": "6.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
                     "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
                     "requires": {
                         "figgy-pudding": "^3.5.1"
@@ -25136,7 +25214,7 @@
                 },
                 "stream-each": {
                     "version": "1.2.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz",
                     "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==",
                     "requires": {
                         "end-of-stream": "^1.1.0",
@@ -25145,7 +25223,7 @@
                 },
                 "stream-iterate": {
                     "version": "1.2.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/stream-iterate/-/stream-iterate-1.2.0.tgz",
                     "integrity": "sha1-K9fHcpbBcCpGSIuK1B95hl7s1OE=",
                     "requires": {
                         "readable-stream": "^2.1.5",
@@ -25154,7 +25232,7 @@
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
                             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -25168,14 +25246,14 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
                         },
                         "string_decoder": {
                             "version": "1.1.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
                             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                             "requires": {
                                 "safe-buffer": "~5.1.0"
@@ -25183,7 +25261,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -25192,17 +25270,17 @@
                 },
                 "stream-shift": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
                     "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI="
                 },
                 "strict-uri-encode": {
                     "version": "2.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
                     "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="
                 },
                 "string-width": {
                     "version": "2.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
                     "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
                     "requires": {
                         "is-fullwidth-code-point": "^2.0.0",
@@ -25211,17 +25289,17 @@
                     "dependencies": {
                         "ansi-regex": {
                             "version": "3.0.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
                             "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
                         },
                         "is-fullwidth-code-point": {
                             "version": "2.0.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
                             "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
                         },
                         "strip-ansi": {
                             "version": "4.0.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
                             "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
                             "requires": {
                                 "ansi-regex": "^3.0.0"
@@ -25231,7 +25309,7 @@
                 },
                 "string_decoder": {
                     "version": "1.3.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
                     "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
                     "requires": {
                         "safe-buffer": "~5.2.0"
@@ -25239,19 +25317,19 @@
                     "dependencies": {
                         "safe-buffer": {
                             "version": "5.2.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
                             "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
                         }
                     }
                 },
                 "stringify-package": {
                     "version": "1.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz",
                     "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg=="
                 },
                 "strip-ansi": {
                     "version": "3.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
                     "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
                     "requires": {
                         "ansi-regex": "^2.0.0"
@@ -25259,17 +25337,17 @@
                 },
                 "strip-eof": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
                     "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
                 },
                 "strip-json-comments": {
                     "version": "2.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
                     "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
                 },
                 "supports-color": {
                     "version": "5.4.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
                     "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
                     "requires": {
                         "has-flag": "^3.0.0"
@@ -25277,7 +25355,7 @@
                 },
                 "tar": {
                     "version": "4.4.19",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz",
                     "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==",
                     "requires": {
                         "chownr": "^1.1.4",
@@ -25291,7 +25369,7 @@
                     "dependencies": {
                         "minipass": {
                             "version": "2.9.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
                             "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
                             "requires": {
                                 "safe-buffer": "^5.1.2",
@@ -25300,19 +25378,19 @@
                         },
                         "safe-buffer": {
                             "version": "5.2.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
                             "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
                         },
                         "yallist": {
                             "version": "3.1.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
                             "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
                         }
                     }
                 },
                 "term-size": {
                     "version": "1.2.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz",
                     "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=",
                     "requires": {
                         "execa": "^0.7.0"
@@ -25320,17 +25398,17 @@
                 },
                 "text-table": {
                     "version": "0.2.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
                     "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ="
                 },
                 "through": {
                     "version": "2.3.8",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
                     "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
                 },
                 "through2": {
                     "version": "2.0.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
                     "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
                     "requires": {
                         "readable-stream": "^2.1.5",
@@ -25339,7 +25417,7 @@
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
                             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -25353,14 +25431,14 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
                         },
                         "string_decoder": {
                             "version": "1.1.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
                             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                             "requires": {
                                 "safe-buffer": "~5.1.0"
@@ -25368,7 +25446,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -25377,17 +25455,17 @@
                 },
                 "timed-out": {
                     "version": "4.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
                     "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8="
                 },
                 "tiny-relative-date": {
                     "version": "1.3.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz",
                     "integrity": "sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A=="
                 },
                 "tough-cookie": {
                     "version": "2.5.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
                     "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
                     "requires": {
                         "psl": "^1.1.28",
@@ -25396,14 +25474,14 @@
                     "dependencies": {
                         "punycode": {
                             "version": "2.1.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
                             "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
                         }
                     }
                 },
                 "tunnel-agent": {
                     "version": "0.6.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
                     "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
                     "requires": {
                         "safe-buffer": "^5.0.1"
@@ -25411,27 +25489,27 @@
                 },
                 "tweetnacl": {
                     "version": "0.14.5",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
                     "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="
                 },
                 "typedarray": {
                     "version": "0.0.6",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
                     "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
                 },
                 "uid-number": {
                     "version": "0.0.6",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz",
                     "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE="
                 },
                 "umask": {
                     "version": "1.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz",
                     "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0="
                 },
                 "unique-filename": {
                     "version": "1.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
                     "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
                     "requires": {
                         "unique-slug": "^2.0.0"
@@ -25439,7 +25517,7 @@
                 },
                 "unique-slug": {
                     "version": "2.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz",
                     "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=",
                     "requires": {
                         "imurmurhash": "^0.1.4"
@@ -25447,7 +25525,7 @@
                 },
                 "unique-string": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz",
                     "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=",
                     "requires": {
                         "crypto-random-string": "^1.0.0"
@@ -25455,17 +25533,17 @@
                 },
                 "unpipe": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
                     "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
                 },
                 "unzip-response": {
                     "version": "2.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz",
                     "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c="
                 },
                 "update-notifier": {
                     "version": "2.5.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz",
                     "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==",
                     "requires": {
                         "boxen": "^1.2.1",
@@ -25482,7 +25560,7 @@
                 },
                 "uri-js": {
                     "version": "4.4.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
                     "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
                     "requires": {
                         "punycode": "^2.1.0"
@@ -25490,14 +25568,14 @@
                     "dependencies": {
                         "punycode": {
                             "version": "2.1.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
                             "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
                         }
                     }
                 },
                 "url-parse-lax": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz",
                     "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=",
                     "requires": {
                         "prepend-http": "^1.0.1"
@@ -25505,17 +25583,17 @@
                 },
                 "util-deprecate": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
                     "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
                 },
                 "util-extend": {
                     "version": "1.0.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz",
                     "integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8="
                 },
                 "util-promisify": {
                     "version": "2.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz",
                     "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=",
                     "requires": {
                         "object.getownpropertydescriptors": "^2.0.3"
@@ -25523,12 +25601,12 @@
                 },
                 "uuid": {
                     "version": "3.4.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
                     "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
                 },
                 "validate-npm-package-license": {
                     "version": "3.0.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
                     "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
                     "requires": {
                         "spdx-correct": "^3.0.0",
@@ -25537,7 +25615,7 @@
                 },
                 "validate-npm-package-name": {
                     "version": "3.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz",
                     "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=",
                     "requires": {
                         "builtins": "^1.0.3"
@@ -25545,7 +25623,7 @@
                 },
                 "verror": {
                     "version": "1.10.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
                     "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
                     "requires": {
                         "assert-plus": "^1.0.0",
@@ -25555,7 +25633,7 @@
                 },
                 "wcwidth": {
                     "version": "1.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
                     "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
                     "requires": {
                         "defaults": "^1.0.3"
@@ -25563,7 +25641,7 @@
                 },
                 "which": {
                     "version": "1.3.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
                     "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
                     "requires": {
                         "isexe": "^2.0.0"
@@ -25571,12 +25649,12 @@
                 },
                 "which-module": {
                     "version": "2.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
                     "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
                 },
                 "wide-align": {
                     "version": "1.1.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",
                     "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
                     "requires": {
                         "string-width": "^1.0.2"
@@ -25584,7 +25662,7 @@
                     "dependencies": {
                         "string-width": {
                             "version": "1.0.2",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
                             "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
                             "requires": {
                                 "code-point-at": "^1.0.0",
@@ -25596,7 +25674,7 @@
                 },
                 "widest-line": {
                     "version": "2.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz",
                     "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==",
                     "requires": {
                         "string-width": "^2.1.1"
@@ -25604,7 +25682,7 @@
                 },
                 "worker-farm": {
                     "version": "1.7.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",
                     "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==",
                     "requires": {
                         "errno": "~0.1.7"
@@ -25612,7 +25690,7 @@
                 },
                 "wrap-ansi": {
                     "version": "5.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
                     "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
                     "requires": {
                         "ansi-styles": "^3.2.0",
@@ -25622,17 +25700,17 @@
                     "dependencies": {
                         "ansi-regex": {
                             "version": "4.1.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
                             "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="
                         },
                         "is-fullwidth-code-point": {
                             "version": "2.0.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
                             "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
                         },
                         "string-width": {
                             "version": "3.1.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
                             "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
                             "requires": {
                                 "emoji-regex": "^7.0.1",
@@ -25642,7 +25720,7 @@
                         },
                         "strip-ansi": {
                             "version": "5.2.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
                             "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
                             "requires": {
                                 "ansi-regex": "^4.1.0"
@@ -25652,12 +25730,12 @@
                 },
                 "wrappy": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
                     "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
                 },
                 "write-file-atomic": {
                     "version": "2.4.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz",
                     "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==",
                     "requires": {
                         "graceful-fs": "^4.1.11",
@@ -25667,27 +25745,27 @@
                 },
                 "xdg-basedir": {
                     "version": "3.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz",
                     "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ="
                 },
                 "xtend": {
                     "version": "4.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
                     "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
                 },
                 "y18n": {
                     "version": "4.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
                     "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ=="
                 },
                 "yallist": {
                     "version": "3.0.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
                     "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A=="
                 },
                 "yargs": {
                     "version": "14.2.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz",
                     "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==",
                     "requires": {
                         "cliui": "^5.0.0",
@@ -25705,12 +25783,12 @@
                     "dependencies": {
                         "ansi-regex": {
                             "version": "4.1.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
                             "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
                         },
                         "find-up": {
                             "version": "3.0.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
                             "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
                             "requires": {
                                 "locate-path": "^3.0.0"
@@ -25718,12 +25796,12 @@
                         },
                         "is-fullwidth-code-point": {
                             "version": "2.0.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
                             "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
                         },
                         "locate-path": {
                             "version": "3.0.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
                             "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
                             "requires": {
                                 "p-locate": "^3.0.0",
@@ -25732,7 +25810,7 @@
                         },
                         "p-limit": {
                             "version": "2.3.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
                             "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
                             "requires": {
                                 "p-try": "^2.0.0"
@@ -25740,7 +25818,7 @@
                         },
                         "p-locate": {
                             "version": "3.0.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
                             "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
                             "requires": {
                                 "p-limit": "^2.0.0"
@@ -25748,12 +25826,12 @@
                         },
                         "p-try": {
                             "version": "2.2.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
                             "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
                         },
                         "string-width": {
                             "version": "3.1.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
                             "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
                             "requires": {
                                 "emoji-regex": "^7.0.1",
@@ -25763,7 +25841,7 @@
                         },
                         "strip-ansi": {
                             "version": "5.2.0",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
                             "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
                             "requires": {
                                 "ansi-regex": "^4.1.0"
@@ -25773,7 +25851,7 @@
                 },
                 "yargs-parser": {
                     "version": "15.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz",
                     "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==",
                     "requires": {
                         "camelcase": "^5.0.0",
@@ -25782,7 +25860,7 @@
                     "dependencies": {
                         "camelcase": {
                             "version": "5.3.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
                             "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
                         }
                     }
@@ -30672,41 +30750,6 @@
                 "strip-ansi": "^4.0.0"
             }
         },
-        "string-width-cjs": {
-            "version": "npm:string-width@4.2.3",
-            "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-            "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-            "requires": {
-                "emoji-regex": "^8.0.0",
-                "is-fullwidth-code-point": "^3.0.0",
-                "strip-ansi": "^6.0.1"
-            },
-            "dependencies": {
-                "ansi-regex": {
-                    "version": "5.0.1",
-                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-                },
-                "emoji-regex": {
-                    "version": "8.0.0",
-                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-                    "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
-                },
-                "is-fullwidth-code-point": {
-                    "version": "3.0.0",
-                    "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
-                    "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
-                },
-                "strip-ansi": {
-                    "version": "6.0.1",
-                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-                    "requires": {
-                        "ansi-regex": "^5.0.1"
-                    }
-                }
-            }
-        },
         "string.prototype.codepointat": {
             "version": "0.2.1",
             "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
@@ -30791,21 +30834,6 @@
                 "ansi-regex": "^3.0.0"
             }
         },
-        "strip-ansi-cjs": {
-            "version": "npm:strip-ansi@6.0.1",
-            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-            "requires": {
-                "ansi-regex": "^5.0.1"
-            },
-            "dependencies": {
-                "ansi-regex": {
-                    "version": "5.0.1",
-                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-                }
-            }
-        },
         "strip-bom": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
@@ -31782,12 +31810,6 @@
             "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
             "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
         },
-        "type": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
-            "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
-            "dev": true
-        },
         "type-check": {
             "version": "0.4.0",
             "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -33359,72 +33381,6 @@
                 }
             }
         },
-        "wrap-ansi-cjs": {
-            "version": "npm:wrap-ansi@7.0.0",
-            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
-            "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
-            "requires": {
-                "ansi-styles": "^4.0.0",
-                "string-width": "^4.1.0",
-                "strip-ansi": "^6.0.0"
-            },
-            "dependencies": {
-                "ansi-regex": {
-                    "version": "5.0.1",
-                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-                },
-                "ansi-styles": {
-                    "version": "4.3.0",
-                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-                    "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-                    "requires": {
-                        "color-convert": "^2.0.1"
-                    }
-                },
-                "color-convert": {
-                    "version": "2.0.1",
-                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-                    "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-                    "requires": {
-                        "color-name": "~1.1.4"
-                    }
-                },
-                "color-name": {
-                    "version": "1.1.4",
-                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-                    "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
-                },
-                "emoji-regex": {
-                    "version": "8.0.0",
-                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-                    "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
-                },
-                "is-fullwidth-code-point": {
-                    "version": "3.0.0",
-                    "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
-                    "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
-                },
-                "string-width": {
-                    "version": "4.2.3",
-                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-                    "requires": {
-                        "emoji-regex": "^8.0.0",
-                        "is-fullwidth-code-point": "^3.0.0",
-                        "strip-ansi": "^6.0.1"
-                    }
-                },
-                "strip-ansi": {
-                    "version": "6.0.1",
-                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-                    "requires": {
-                        "ansi-regex": "^5.0.1"
-                    }
-                }
-            }
-        },
         "wrappy": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss
index ba1e99f84..81f123758 100644
--- a/src/client/views/nodes/MapBox/MapBox.scss
+++ b/src/client/views/nodes/MapBox/MapBox.scss
@@ -4,6 +4,7 @@
     height: 100%;
     overflow: hidden;
     display: flex;
+    position: absolute;
 
     .mapBox-infoWindow {
         background-color: white;
@@ -27,7 +28,7 @@
         // }
     }
 
-    .mapbox-settings-panel{
+    .mapbox-settings-panel {
         z-index: 900;
         padding: 10px 20px;
         display: flex;
@@ -41,7 +42,7 @@
         border-top-left-radius: 5px;
         border-bottom-left-radius: 5px;
 
-        .mapbox-style-select{
+        .mapbox-style-select {
             display: flex;
             flex-direction: column;
             align-items: flex-start;
@@ -49,14 +50,13 @@
             gap: 4px;
         }
 
-        .mapbox-terrain-selection{
+        .mapbox-terrain-selection {
             display: flex;
             flex-direction: row;
             align-items: center;
             justify-content: flex-start;
             gap: 4px;
         }
-
     }
 
     .mapbox-geocoding-search-results {
@@ -75,11 +75,10 @@
         .search-result-container {
             width: 100%;
             padding: 10px;
-            &:hover{
+            &:hover {
                 background-color: lighten(rgb(187, 187, 187), 10%);
             }
         }
-
     }
 
     .animation-panel {
@@ -105,7 +104,7 @@
             align-items: center;
             gap: 7px;
 
-            .animation-suboptions{
+            .animation-suboptions {
                 display: flex;
                 justify-content: flex-start;
                 flex-wrap: wrap;
@@ -113,25 +112,23 @@
                 gap: 7px;
                 width: 100%;
 
-                .first-person-label{
+                .first-person-label {
                     width: '130px' !important;
                 }
 
-                label{
+                label {
                     margin-bottom: 0;
                 }
-    
-                .speed-label{
+
+                .speed-label {
                     margin-right: 5px;
                 }
 
-                #divider{
+                #divider {
                     margin-left: 10px;
                     margin-right: 10px;
                 }
             }
-
-            
         }
     }
 
@@ -148,10 +145,8 @@
         bottom: 5px;
         left: 5px;
         padding: 3px;
-
     }
 
-
     .mapBox-topbar {
         display: flex;
         flex-direction: row;
@@ -235,4 +230,3 @@
         display: block;
     }
 }
-
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 5c4a6203a..b18bb9ad1 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1149,20 +1149,21 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     };
 
-    @observable
-    mapboxMapViewState: ViewState = {
-        zoom: this.dataDoc.map_zoom ? NumCast(this.dataDoc.map_zoom) : 8,
-        longitude: this.dataDoc.longitude ? NumCast(this.dataDoc.longitude) : -71.4128,
-        latitude: this.dataDoc.latitude ? NumCast(this.dataDoc.latitude) : 41.824,
-        pitch: this.dataDoc.map_pitch ? NumCast(this.dataDoc.map_pitch) : 0,
-        bearing: this.dataDoc.map_bearing ? NumCast(this.dataDoc.map_bearing) : 0,
-        padding: {
-            top: 0,
-            bottom: 0,
-            left: 0,
-            right: 0,
-        },
-    };
+    @computed get mapboxMapViewState(): ViewState {
+        return {
+            zoom: NumCast(this.dataDoc.map_zoom, 8),
+            longitude: NumCast(this.dataDoc.longitude, -71.4128),
+            latitude: NumCast(this.dataDoc.latitude, 41.824),
+            pitch: NumCast(this.dataDoc.map_pitch),
+            bearing: NumCast(this.dataDoc.map_bearing),
+            padding: {
+                top: 0,
+                bottom: 0,
+                left: 0,
+                right: 0,
+            },
+        };
+    }
 
     @computed
     get preAnimationViewState() {
@@ -1389,9 +1390,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             this.routeToAnimate = undefined;
             this.animationUtility = null;
         }
-        if (this.preAnimationViewState) {
-            this.mapboxMapViewState = this.preAnimationViewState;
-        }
     };
 
     @action
@@ -1487,10 +1485,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             const fixedBearing = Math.max(0, Math.min(360, bearing));
             this._mapRef.current.setBearing(fixedBearing);
             this.dataDoc.map_bearing = fixedBearing;
-            this.mapboxMapViewState = {
-                ...this.mapboxMapViewState,
-                bearing: fixedBearing,
-            };
         }
     };
 
@@ -1501,10 +1495,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             const fixedPitch = Math.max(0, Math.min(85, pitch));
             this._mapRef.current.setPitch(fixedPitch);
             this.dataDoc.map_pitch = fixedPitch;
-            this.mapboxMapViewState = {
-                ...this.mapboxMapViewState,
-                pitch: fixedPitch,
-            };
         }
     };
 
@@ -1515,10 +1505,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             const fixedZoom = Math.max(0, Math.min(16, zoom));
             this._mapRef.current.setZoom(fixedZoom);
             this.dataDoc.map_zoom = fixedZoom;
-            this.mapboxMapViewState = {
-                ...this.mapboxMapViewState,
-                zoom: fixedZoom,
-            };
         }
     };
 
@@ -1528,23 +1514,18 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             let newZoom: number;
             if (increment) {
                 console.log('inc');
-                newZoom = this.mapboxMapViewState.zoom + 1;
+                newZoom = Math.min(16, this.mapboxMapViewState.zoom + 1);
             } else {
                 console.log('dec');
-                newZoom = this.mapboxMapViewState.zoom - 1;
+                newZoom = Math.max(0, this.mapboxMapViewState.zoom - 1);
             }
             this._mapRef.current.setZoom(newZoom);
             this.dataDoc.map_zoom = newZoom;
-            this.mapboxMapViewState = {
-                ...this.mapboxMapViewState,
-                zoom: increment ? Math.min(16, newZoom) : Math.max(0, newZoom),
-            };
         }
     };
 
     @action
     onMapMove = (e: ViewStateChangeEvent) => {
-        this.mapboxMapViewState = e.viewState;
         this.dataDoc.longitude = e.viewState.longitude;
         this.dataDoc.latitude = e.viewState.latitude;
     };
@@ -1580,7 +1561,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 }), MapBox._rerenderDelay);
             return null;
         }
+        const parscale = this.props.ScreenToLocalTransform().Scale ?? 1;
         const scale = this.props.NativeDimScaling?.() || 1;
+        const docscale = this.props.DocumentView?.().props.ScreenToLocalTransform().Scale ?? 1;
 
         const renderAnnotations = (childFilters?: () => string[]) => null;
         return (
@@ -1682,15 +1665,17 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         <MapboxMap
                             ref={this._mapRef}
                             mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
-                            id="mapbox-map"
-                            viewState={{ ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width) / scale, height: NumCast(this.layoutDoc._height) / scale }}
+                            viewState={this.isAnimating ? undefined : { ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width) / scale, height: NumCast(this.layoutDoc._height) / scale }}
                             mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'}
                             style={{
-                                transformOrigin: 'center',
+                                transformOrigin: 'top left',
+                                position: 'absolute',
+                                top: 0,
+                                left: 0,
                                 transform: `scale(${scale < 1 ? 1 : scale})`,
                                 zIndex: '0',
-                                width: '100%',
-                                height: '100%',
+                                width: '100%', //NumCast(this.layoutDoc._width) / scale,
+                                height: '100%', // NumCast(this.layoutDoc._height) / scale,
                             }}
                             initialViewState={this.isAnimating ? undefined : this.mapboxMapViewState}
                             onMove={this.onMapMove}
-- 
cgit v1.2.3-70-g09d2


From a142c57a4a75477727122130b091f3aec1bd8d91 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 16 Dec 2023 17:52:57 -0500
Subject: finally got map resizing working ?

---
 src/client/views/nodes/MapBox/MapBox.tsx | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)

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

diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index b18bb9ad1..d3b293bc2 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1561,9 +1561,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 }), MapBox._rerenderDelay);
             return null;
         }
-        const parscale = this.props.ScreenToLocalTransform().Scale ?? 1;
         const scale = this.props.NativeDimScaling?.() || 1;
-        const docscale = this.props.DocumentView?.().props.ScreenToLocalTransform().Scale ?? 1;
+        const parscale = scale === 1 ? 1 : this.props.ScreenToLocalTransform().Scale ?? 1;
 
         const renderAnnotations = (childFilters?: () => string[]) => null;
         return (
@@ -1574,8 +1573,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     onPointerDown={async e => {
                         e.button === 0 && !e.ctrlKey && e.stopPropagation();
                     }}
-                    style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
-                    {/* style={{ transformOrigin: "top left",  transform: `scale(${scale})`,  width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}> */}
+                    style={{ transformOrigin: 'top left', transform: `scale(${scale})`, width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
                     <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
                     {renderAnnotations(this.opaqueFilter)}
                     {SnappingManager.GetIsDragging() ? null : renderAnnotations()}
@@ -1665,17 +1663,15 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         <MapboxMap
                             ref={this._mapRef}
                             mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
-                            viewState={this.isAnimating ? undefined : { ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width) / scale, height: NumCast(this.layoutDoc._height) / scale }}
+                            viewState={this.isAnimating ? undefined : { ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width), height: NumCast(this.layoutDoc._height) }}
                             mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'}
                             style={{
-                                transformOrigin: 'top left',
                                 position: 'absolute',
                                 top: 0,
                                 left: 0,
-                                transform: `scale(${scale < 1 ? 1 : scale})`,
                                 zIndex: '0',
-                                width: '100%', //NumCast(this.layoutDoc._width) / scale,
-                                height: '100%', // NumCast(this.layoutDoc._height) / scale,
+                                width: NumCast(this.layoutDoc._width) * parscale,
+                                height: NumCast(this.layoutDoc._height) * parscale,
                             }}
                             initialViewState={this.isAnimating ? undefined : this.mapboxMapViewState}
                             onMove={this.onMapMove}
-- 
cgit v1.2.3-70-g09d2


From b0872130aca36c790e4279866582af224ed763ba Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 16 Dec 2023 21:27:24 -0500
Subject: added pinch zooming for maps, fixed sizing of search bar when zoomed
 in, turned off some unneeded widgets that don't locate properly when zoomed
 in.

---
 src/client/views/nodes/MapBox/MapBox.scss |  3 +++
 src/client/views/nodes/MapBox/MapBox.tsx  | 10 ++++++----
 2 files changed, 9 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss
index 1d27167f0..b3ce55786 100644
--- a/src/client/views/nodes/MapBox/MapBox.scss
+++ b/src/client/views/nodes/MapBox/MapBox.scss
@@ -9,6 +9,9 @@
     .mapboxgl-map {
         overflow: unset !important;
     }
+    .mapboxgl-ctrl {
+        display: none !important;
+    }
 
     .mapBox-infoWindow {
         background-color: white;
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index efe0a3620..b627ba459 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1501,6 +1501,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     };
 
+    @action
+    onMapZoom = (e: ViewStateChangeEvent) => (this.dataDoc.map_zoom = e.viewState.zoom);
+
     @action
     onMapMove = (e: ViewStateChangeEvent) => {
         this.dataDoc.longitude = e.viewState.longitude;
@@ -1508,9 +1511,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
 
     @action
-    toggleShowTerrain = () => {
-        this.showTerrain = !this.showTerrain;
-    };
+    toggleShowTerrain = () => (this.showTerrain = !this.showTerrain);
 
     getMarkerIcon = (pinDoc: Doc): JSX.Element | null => {
         const markerType = StrCast(pinDoc.markerType);
@@ -1555,7 +1556,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     {renderAnnotations(this.opaqueFilter)}
                     {SnappingManager.IsDragging ? null : renderAnnotations()}
                     {!this.routeToAnimate && (
-                        <div className="mapBox-searchbar" style={{ zIndex: 1, position: 'relative', background: 'lightGray' }}>
+                        <div className="mapBox-searchbar" style={{ width: `${100 / scale}%`, zIndex: 1, position: 'relative', background: 'lightGray' }}>
                             <TextField fullWidth placeholder="Enter a location" onChange={(e: any) => this.handleSearchChange(e.target.value)} />
                             <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={e => this.toggleSettings()} />
                         </div>
@@ -1651,6 +1652,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                 height: NumCast(this.layoutDoc._height) * parscale,
                             }}
                             initialViewState={this.isAnimating ? undefined : this.mapboxMapViewState}
+                            onZoom={this.onMapZoom}
                             onMove={this.onMapMove}
                             onClick={this.handleMapClick}
                             onDblClick={this.handleMapDblClick}
-- 
cgit v1.2.3-70-g09d2


From e39665be22ecee11b093db07ebe72896a6d43a8c Mon Sep 17 00:00:00 2001
From: zaultavangar <zaul_tavangar@brown.edu>
Date: Sun, 17 Dec 2023 15:59:39 -0500
Subject: starting calendar collection view

---
 package-lock.json                                  |  2 +
 package.json                                       |  4 +-
 src/client/documents/DocumentTypes.ts              |  2 +-
 src/client/documents/Documents.ts                  | 12 +++-
 src/client/util/CalendarManager.scss               | 18 ++---
 src/client/util/CalendarManager.tsx                | 82 +++++++++++++++++-----
 src/client/util/CurrentUserUtils.ts                |  2 +-
 .../views/collections/CollectionCalendarView.tsx   | 32 +++++++++
 src/client/views/collections/CollectionView.tsx    |  3 +
 src/client/views/nodes/DocumentContentsView.tsx    |  2 +
 src/client/views/nodes/MapBox/MapBox.tsx           | 18 +++--
 src/client/views/nodes/calendarBox/CalendarBox.tsx | 26 +++++++
 12 files changed, 162 insertions(+), 41 deletions(-)
 create mode 100644 src/client/views/collections/CollectionCalendarView.tsx
 create mode 100644 src/client/views/nodes/calendarBox/CalendarBox.tsx

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

diff --git a/package-lock.json b/package-lock.json
index 180ff097a..2d2740381 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,6 +22,7 @@
                 "@fortawesome/free-regular-svg-icons": "^6.5.1",
                 "@fortawesome/free-solid-svg-icons": "^6.5.1",
                 "@fortawesome/react-fontawesome": "^0.2.0",
+                "@internationalized/date": "^3.5.0",
                 "@mapbox/mapbox-gl-geocoder": "^5.0.2",
                 "@mui/icons-material": "^5.14.19",
                 "@mui/material": "^5.14.19",
@@ -37,6 +38,7 @@
                 "@types/dom-speech-recognition": "0.0.4",
                 "@types/formidable": "3.4.5",
                 "@types/google-maps": "^3.2.6",
+                "@types/mapbox-gl": "^2.7.19",
                 "@types/reveal": "^4.2.0",
                 "@types/supercluster": "^7.1.3",
                 "@types/web": "^0.0.127",
diff --git a/package.json b/package.json
index 661f8c5ec..098e30ffb 100644
--- a/package.json
+++ b/package.json
@@ -92,12 +92,12 @@
         "webpack-hot-middleware": "^2.25.4"
     },
     "dependencies": {
+        "@adobe/react-spectrum": "^3.32.2",
         "@azure/storage-blob": "^12.17.0",
         "@babel/preset-env": "^7.23.5",
         "@babel/preset-react": "^7.23.3",
         "@bundled-es-modules/pdfjs-dist": "^3.6.172-alpha.1",
         "@emotion/react": "^11.11.1",
-        "@adobe/react-spectrum": "^3.32.2",
         "@emotion/styled": "^11.11.0",
         "@ffmpeg/core": "0.12.4",
         "@ffmpeg/ffmpeg": "0.12.7",
@@ -106,6 +106,7 @@
         "@fortawesome/free-regular-svg-icons": "^6.5.1",
         "@fortawesome/free-solid-svg-icons": "^6.5.1",
         "@fortawesome/react-fontawesome": "^0.2.0",
+        "@internationalized/date": "^3.5.0",
         "@mapbox/mapbox-gl-geocoder": "^5.0.2",
         "@mui/icons-material": "^5.14.19",
         "@mui/material": "^5.14.19",
@@ -121,6 +122,7 @@
         "@types/dom-speech-recognition": "0.0.4",
         "@types/formidable": "3.4.5",
         "@types/google-maps": "^3.2.6",
+        "@types/mapbox-gl": "^2.7.19",
         "@types/reveal": "^4.2.0",
         "@types/supercluster": "^7.1.3",
         "@types/web": "^0.0.127",
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 5c2792a53..1123bcac9 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -62,5 +62,5 @@ export enum CollectionViewType {
     Pile = 'pileup',
     StackedTimeline = 'stacked timeline',
     NoteTaking = 'notetaking',
-    Calendar = 'calendar_view'
+    Calendar = 'calendar'
 }
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index d6fd8aea3..ff14eb101 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -58,6 +58,7 @@ import { VideoBox } from '../views/nodes/VideoBox';
 import { WebBox } from '../views/nodes/WebBox';
 import { SearchBox } from '../views/search/SearchBox';
 import { CollectionViewType, DocumentType } from './DocumentTypes';
+import { CalendarBox } from '../views/nodes/calendarBox/CalendarBox';
 const {
     default: { DFLT_IMAGE_NATIVE_DIM },
 } = require('../views/global/globalCssVariables.module.scss');
@@ -784,6 +785,13 @@ export namespace Docs {
                     options: {},
                 },
             ],
+            [
+                DocumentType.CALENDAR,
+                {
+                    layout: { view: CalendarBox, dataField: defaultDataKey },
+                    options: {},
+                }
+            ]
         ]);
 
         const suffix = 'Proto';
@@ -1146,8 +1154,8 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.MAPROUTE), new List(documents), { infoWindowOpen, ...options }, id);
         }
 
-        export function CalendarDocument(options: DocumentOptions={}, documents: Array<Doc>){
-            return InstanceFromProto(Prototypes.get(DocumentType.CALENDAR), new List(documents), options)
+        export function CalendarDocument(options: DocumentOptions, documents: Array<Doc>){
+            return InstanceFromProto(Prototypes.get(DocumentType.CALENDAR), new List(documents), {...options})
         }
 
         // shouldn't ever need to create a KVP document-- instead set the LayoutTemplateString to be a KeyValueBox for the DocumentView (see addDocTab in TabDocView)
diff --git a/src/client/util/CalendarManager.scss b/src/client/util/CalendarManager.scss
index 60610f298..114e19a0e 100644
--- a/src/client/util/CalendarManager.scss
+++ b/src/client/util/CalendarManager.scss
@@ -31,19 +31,20 @@
         margin-top: 10px;
         align-self: center;
         width: 60%;
+    }
 
-        .MuiFilledInput-input{
-            padding: 10px;
-        }
+    .description-container{
+        margin-top: 10px;
+        align-self: center;
+        width: 60%;
     }
 
     .date-range-picker-container{
         margin-top: 5px;
-        align-self:center;
-
-        .react-date-range{
-
-        }
+        align-self: center;
+        display: flex;
+        flex-direction: column;
+        gap: 2px;
     }
 
     .create-button-container{
@@ -57,6 +58,5 @@
             border-radius: 5px;
             background-color: #EFF2F7;
         }
-
     }
 }
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
index 3872294db..d5d4203b1 100644
--- a/src/client/util/CalendarManager.tsx
+++ b/src/client/util/CalendarManager.tsx
@@ -10,16 +10,16 @@ import { MainViewModal } from '../views/MainViewModal';
 import { TextField } from '@mui/material';
 import Select from 'react-select';
 import { SettingsManager } from './SettingsManager';
-import { DocCast, StrCast } from '../../fields/Types';
+import { DateCast, DocCast, StrCast } from '../../fields/Types';
 import { SelectionManager } from './SelectionManager';
 import { DocumentManager } from './DocumentManager';
 import { DocData } from '../../fields/DocSymbols';
 // import { DateRange, Range, RangeKeyDict } from 'react-date-range';
 import { Button } from 'browndash-components';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Provider, useProvider, defaultTheme } from '@adobe/react-spectrum';
-
-import { DateRangePicker } from '@adobe/react-spectrum';
+import { Provider, defaultTheme } from '@adobe/react-spectrum';
+import { DateValue } from '@internationalized/date';
+import { DateRangePicker, SpectrumDateRangePickerProps } from '@adobe/react-spectrum';
 import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons';
 import { Docs } from '../documents/Documents';
 import { ObservableReactComponent } from '../views/ObservableReactComponent';
@@ -33,7 +33,10 @@ interface CalendarSelectOptions {
     value: string;
 }
 
-const formatDateToString = (date: Date) => {
+const formatCalendarDateToString = (calendarDate: any) => {
+    console.log("Formatting the following date: ", calendarDate);
+    const date = new Date(calendarDate.year, calendarDate.month-1, calendarDate.day)
+    console.log(typeof date);
     const year = date.getFullYear();
     const month = String(date.getMonth() + 1).padStart(2, '0');
     const day = String(date.getDate()).padStart(2, '0');
@@ -121,19 +124,26 @@ export class CalendarManager extends ObservableReactComponent<{}> {
     };
 
     @action
-    handleTextFieldChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
+    handleCalendarTitleChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
         this.calendarName = event.target.value;
     };
 
+    @action
+    handleCalendarDescriptionChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
+        this.calendarDescription = event.target.value;
+    }
+
     // TODO: Make undoable
     private addToCalendar = () => {
         let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
         const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData]; // doc to add to calendar
 
+        console.log(targetDoc);
         if (targetDoc) {
             let calendar: Doc;
             if (this.creationType === 'new-calendar') {
                 if (!this.existingCalendars.find(doc => StrCast(doc.title) === this.calendarName)) {
+                    console.log('creating...')
                     calendar = Docs.Create.CalendarDocument(
                         {
                             title: this.calendarName,
@@ -141,6 +151,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
                         },
                         []
                     );
+                    console.log('successful calendar creation')
                 } else {
                     this.errorMessage = 'Calendar with this name already exists';
                     return;
@@ -155,15 +166,20 @@ export class CalendarManager extends ObservableReactComponent<{}> {
                 }
             }
             // Get start and end date strings
-            const startDateStr = formatDateToString(this.selectedDateRange.start);
-            const endDateStr = formatDateToString(this.selectedDateRange.end);
+            const startDateStr = formatCalendarDateToString(this.selectedDateRange.start);
+            const endDateStr = formatCalendarDateToString(this.selectedDateRange.end);
+
+            console.log("start date: ", startDateStr);
+            console.log("end date: ", endDateStr)
 
             const subDocEmbedding = Doc.MakeEmbedding(targetDoc); // embedding
+            console.log("subdoc embedding", subDocEmbedding);
             subDocEmbedding.embedContainer = calendar; // set embed container
             subDocEmbedding.date_range = `${startDateStr}-${endDateStr}`; // set subDoc date range
 
             Doc.AddDocToList(calendar, 'data', subDocEmbedding); // add embedded subDoc to calendar
 
+            console.log("my calendars: ", Doc.MyCalendars);
             if (this.creationType === 'new-calendar') {
                 Doc.AddDocToList(Doc.MyCalendars, 'data', calendar); // add to new calendar to dashboard calendars
             }
@@ -211,6 +227,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
 
     @action
     setSelectedDateRange = (range: any) => {
+        console.log("Range: ", range);
         this.selectedDateRange = range;
     };
 
@@ -220,9 +237,12 @@ export class CalendarManager extends ObservableReactComponent<{}> {
         let startDate: Date | undefined;
         let endDate: Date | undefined;
         try {
-            startDate = this.selectedDateRange[0].startDate;
-            endDate = this.selectedDateRange[0].endDate;
+            startDate = this.selectedDateRange.start;
+            endDate = this.selectedDateRange.end;
+            console.log(startDate);
+            console.log(endDate)
         } catch (e: any) {
+            console.log(e);
             return false; // disabled
         }
         if (!startDate || !endDate) return false; // disabled if any is undefined
@@ -258,8 +278,9 @@ export class CalendarManager extends ObservableReactComponent<{}> {
                     {this.creationType === 'new-calendar' ? (
                         <TextField
                             fullWidth
-                            onChange={this.handleTextFieldChange}
-                            placeholder="Enter calendar name..."
+                            onChange={this.handleCalendarTitleChange}
+                            label='Calendar name'
+                            placeholder='Enter a name...'
                             variant="filled"
                             style={{
                                 backgroundColor: 'white',
@@ -298,14 +319,41 @@ export class CalendarManager extends ObservableReactComponent<{}> {
                             }}></Select>
                     )}
                 </div>
+                <div className='description-container'>
+                    <TextField
+                        fullWidth
+                        multiline
+                        label='Calendar description'
+                        placeholder='Enter a description (optional)...'
+                        onChange={this.handleCalendarDescriptionChange}
+                        variant="filled"
+                        style={{
+                            backgroundColor: 'white',
+                            color: 'black',
+                            borderRadius: '5px',
+                        }}
+                    />
+                </div>
                 <div className="date-range-picker-container">
+                    <div>Select a date range: </div>
                     <Provider theme={defaultTheme}>
-                        <DateRangePicker value={this.selectedDateRange} onChange={v => this.setSelectedDateRange(v)} label="Date range" />
+                        <DateRangePicker 
+                            aria-label='Select a date range'
+                            value={this.selectedDateRange} 
+                            onChange={v => this.setSelectedDateRange(v)}/>
                     </Provider>
                 </div>
-                <div className="create-button-container">
-                    <Button active={this.createButtonActive} onClick={() => {}} text="Add to Calendar" iconPlacement="right" icon={<FontAwesomeIcon icon={faPlus as IconLookup} />} />
-                </div>
+                {this.createButtonActive && 
+                    <div className='create-button-container'>
+                        <Button 
+                            onClick={() => this.addToCalendar()} 
+                            text="Add to Calendar" 
+                            iconPlacement="right" 
+                            icon={<FontAwesomeIcon icon={faPlus as IconLookup} />} />
+                    </div>
+                    
+                }
+                
             </div>
         );
     }
@@ -313,4 +361,4 @@ export class CalendarManager extends ObservableReactComponent<{}> {
     render() {
         return <MainViewModal contents={this.calendarInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
     }
-}
+}
\ No newline at end of file
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 61a9fa7c2..f7070a862 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1022,4 +1022,4 @@ ScriptingGlobals.add(function IsExploreMode() { return DocumentView.ExploreMode;
 ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode");
 ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering");
 ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
-ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; });
+ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; });
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx
new file mode 100644
index 000000000..99dc09732
--- /dev/null
+++ b/src/client/views/collections/CollectionCalendarView.tsx
@@ -0,0 +1,32 @@
+import * as React from 'react';
+import { CollectionSubView } from "./CollectionSubView";
+import { observer } from 'mobx-react';
+import { makeObservable, observable } from 'mobx';
+import { Doc, DocListCast } from '../../../fields/Doc';
+
+@observer
+export class CollectionCalendarView extends CollectionSubView(){
+
+    constructor(props: any){
+        super(props);
+        makeObservable(this);
+    }
+
+    componentDidMount(): void {
+        
+    }
+
+    componentWillUnmount(): void {
+        
+    }
+
+    @observable private existingCalendars: Doc[] = DocListCast(Doc.MyCalendars?.data);
+
+    render(){
+        return (
+            <div>
+                Hello
+            </div>
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 0673b264b..0237ec95e 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -28,6 +28,7 @@ import { CollectionTreeView } from './CollectionTreeView';
 import './CollectionView.scss';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import { CollectionGridView } from './collectionGrid/CollectionGridView';
+import { CollectionCalendarView} from './CollectionCalendarView';
 import { CollectionLinearView } from './collectionLinear';
 import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
 import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
@@ -122,6 +123,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
             default:
             case CollectionViewType.Freeform:     return <CollectionFreeFormView    key="collview" {...props} />;
             case CollectionViewType.Schema:       return <CollectionSchemaView      key="collview" {...props} />;
+            case CollectionViewType.Calendar:     return <CollectionCalendarView    key="collview" {...props} />;
             case CollectionViewType.Docking:      return <CollectionDockingView     key="collview" {...props} />;
             case CollectionViewType.Tree:         return <CollectionTreeView        key="collview" {...props} />;
             case CollectionViewType.Multicolumn:  return <CollectionMulticolumnView key="collview" {...props} />;
@@ -146,6 +148,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
                 { description: 'Schema',      event: () => func(CollectionViewType.Schema), icon: 'th-list' },
                 { description: 'Tree',        event: () => func(CollectionViewType.Tree), icon: 'tree' },
                 { description: 'Stacking',    event: () => (func(CollectionViewType.Stacking)._layout_autoHeight = true), icon: 'ellipsis-v' },
+                { description: 'Calendar',    event: () => func(CollectionViewType.Calendar), icon: 'columns'},
                 { description: 'Notetaking',  event: () => (func(CollectionViewType.NoteTaking)._layout_autoHeight = true), icon: 'ellipsis-v' },
                 { description: 'Multicolumn', event: () => func(CollectionViewType.Multicolumn), icon: 'columns' },
                 { description: 'Multirow',    event: () => func(CollectionViewType.Multirow), icon: 'columns' },
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index e161b4c4c..5b2bf4774 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -14,6 +14,7 @@ import { CollectionDockingView } from '../collections/CollectionDockingView';
 import { CollectionView } from '../collections/CollectionView';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { CollectionSchemaView } from '../collections/collectionSchema/CollectionSchemaView';
+import { CollectionCalendarView } from '../collections/CollectionCalendarView';
 import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox';
 import { PresElementBox } from '../nodes/trails/PresElementBox';
 import { SearchBox } from '../search/SearchBox';
@@ -243,6 +244,7 @@ export class DocumentContentsView extends ObservableReactComponent<
                     CollectionFreeFormView,
                     CollectionDockingView,
                     CollectionSchemaView,
+                    CollectionCalendarView,
                     CollectionView,
                     WebBox,
                     KeyValueBox,
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index b627ba459..44afb8e7b 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -23,9 +23,7 @@ import { FieldView, FieldViewProps } from '../FieldView';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../trails';
 import { MapAnchorMenu } from './MapAnchorMenu';
-
 import { ControlPosition, Layer, MapProvider, MapRef, Map as MapboxMap, Marker, MarkerProps, Source, ViewState, ViewStateChangeEvent } from 'react-map-gl';
-import MapboxGeocoder, { GeocoderOptions } from '@mapbox/mapbox-gl-geocoder!';
 import './MapBox.scss';
 // import { GeocoderControl } from './GeocoderControl';
 import { IconLookup, faCircleXmark, faGear, faPause, faPlay, faRotate } from '@fortawesome/free-solid-svg-icons';
@@ -33,7 +31,7 @@ import { Checkbox, FormControlLabel, TextField } from '@mui/material';
 import * as turf from '@turf/turf';
 import * as d3 from 'd3';
 import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, Position } from 'geojson';
-import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent } from 'mapbox-gl!';
+import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent } from '!mapbox-gl';
 import { CirclePicker, ColorResult } from 'react-color';
 import { MarkerEvent } from 'react-map-gl/dist/esm/types';
 import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
@@ -68,13 +66,13 @@ type PopupInfo = {
     description: string;
 };
 
-export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & {
-    mapboxAccessToken: string;
-    marker?: Omit<MarkerProps, 'longitude' | 'latitude'>;
-    position: ControlPosition;
+// export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & {
+//     mapboxAccessToken: string;
+//     marker?: Omit<MarkerProps, 'longitude' | 'latitude'>;
+//     position: ControlPosition;
 
-    onResult: (...args: any[]) => void;
-};
+//     onResult: (...args: any[]) => void;
+// };
 
 type MapMarker = {
     longitude: number;
@@ -1866,4 +1864,4 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         <div style={{ width: 30, height: 30 }} ref={this._dragRef} onPointerDown={this.dragToggle}>
                             <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size={'lg'} icon={'bullseye'} />} />
                         </div> */
-}
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
new file mode 100644
index 000000000..0aa3b4ccc
--- /dev/null
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -0,0 +1,26 @@
+import * as React from 'react';
+import { observer } from "mobx-react";
+import { Doc } from "../../../../fields/Doc";
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps, ViewBoxBaseComponent } from '../../DocComponent';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { StrCast } from '../../../../fields/Types';
+import { makeObservable } from 'mobx';
+
+@observer
+export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>(){
+    public static LayoutString(fieldKey: string = 'calendar') {
+        return FieldView.LayoutString(CalendarBox, fieldKey);
+    }
+
+    constructor(props: any){
+        super(props);
+        makeObservable(this);
+    }
+
+    render(){
+        return (
+            <div></div>  
+        );
+        
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From b2d824b412e6bbf9b4867ed13f49a433af7c26c7 Mon Sep 17 00:00:00 2001
From: zaultavangar <zaul_tavangar@brown.edu>
Date: Sun, 17 Dec 2023 22:33:08 -0500
Subject: fixing some bugs with MapBox and MapAnchorMenu

---
 src/client/views/nodes/MapBox/MapAnchorMenu.tsx | 236 ++++++++++++++++--------
 src/client/views/nodes/MapBox/MapBox.scss       |   7 +-
 src/client/views/nodes/MapBox/MapBox.tsx        | 183 +++++-------------
 3 files changed, 204 insertions(+), 222 deletions(-)

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

diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index b1fb3368c..1b1b74e7c 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -20,6 +20,7 @@ import { List } from '../../../../fields/List';
 import { MarkerIcons } from './MarkerIcons';
 import { CirclePicker, ColorResult } from 'react-color';
 import { Position } from 'geojson';
+import { CalendarManager } from '../../../util/CalendarManager';
 
 type MapAnchorMenuType = 'standard' | 'routeCreation' | 'calendar' | 'customize' | 'route';
 
@@ -45,13 +46,15 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     public IsTargetToggler: () => boolean = returnFalse;
 
     public DisplayRoute: (routeInfoMap: Record<TransportationType, any> | undefined, type: TransportationType) => void = unimplementedFunction;
-    public HideRoute: () => void = unimplementedFunction;
     public AddNewRouteToMap: (coordinates: Position[], origin: string, destination: any, createPinForDestination: boolean) => void = unimplementedFunction;
     public CreatePin: (feature: any) => void = unimplementedFunction;
 
     public UpdateMarkerColor: (color: string) => void = unimplementedFunction;
     public UpdateMarkerIcon: (iconKey: string) => void = unimplementedFunction;
 
+
+    public Hide: () => void = unimplementedFunction;
+
     public OpenAnimationPanel: (routeDoc: Doc | undefined) => void = unimplementedFunction;
 
     @observable
@@ -70,14 +73,31 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     private title: string | undefined = undefined;
 
-    public setPinDoc(pinDoc: Doc) {
-        this.pinDoc = pinDoc;
-        this.title = StrCast(pinDoc.title ? pinDoc.title : `${NumCast(pinDoc.longitude)}, ${NumCast(pinDoc.latitude)}`);
+    public setPinDoc(pinDoc: Doc | undefined) {
+        if (pinDoc){
+            this.pinDoc = pinDoc;
+            this.title = StrCast(pinDoc.title ? pinDoc.title : `${NumCast(pinDoc.longitude)}, ${NumCast(pinDoc.latitude)}`);
+        }
+        
     }
 
-    public setRouteDoc(routeDoc: Doc) {
-        this.routeDoc = routeDoc;
-        this.title = StrCast(routeDoc.title ?? 'Map route');
+    public setRouteDoc(routeDoc: Doc | undefined) {
+        if (routeDoc){
+            this.routeDoc = routeDoc;
+            this.title = StrCast(routeDoc.title ?? 'Map route');
+        }
+    }
+
+    @action
+    public Reset(){
+        this.destinationSelected = false;
+        this.currentRouteInfoMap = undefined;
+        this.destinationFeatures = [];
+        this.selectedDestinationFeature = undefined;
+        this.allMapPinDocs = [];
+        this.title = undefined;
+        this.routeDoc = undefined;
+        this.pinDoc = undefined;
     }
 
     public setAllMapboxPins(pinDocs: Doc[]) {
@@ -263,7 +283,6 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             console.log(coordinates);
             console.log(this.selectedDestinationFeature);
             this.AddNewRouteToMap(coordinates, this.title ?? '', this.selectedDestinationFeature, this.createPinForDestination);
-            this.HideRoute();
         }
     };
 
@@ -277,86 +296,155 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         return undefined;
     };
 
+    getDirectionsButton: JSX.Element = (
+        <IconButton 
+            tooltip="Get directions" 
+            onPointerDown={this.DirectionsClick}
+            icon={<FontAwesomeIcon icon={faDiamondTurnRight as IconLookup} />} 
+            color={SettingsManager.userColor} />
+    )
+
+    getAddToCalendarButton = (docType: string): JSX.Element => {
+        return (
+            <IconButton 
+            tooltip="Add to calendar" 
+            onPointerDown={() => {
+                CalendarManager.Instance.open(undefined, docType === 'pin' ? this.pinDoc : this.routeDoc)
+            }} 
+            icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} 
+            color={SettingsManager.userColor} 
+            />
+        )
+       
+    }
+    addToCalendarButton: JSX.Element = (
+        <IconButton 
+            tooltip="Add to calendar" 
+            onPointerDown={() => CalendarManager.Instance.open(undefined, this.pinDoc)} 
+            icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} 
+            color={SettingsManager.userColor} />
+    )
+
+    getLinkNoteToDocButton = (docType: string): JSX.Element => {
+        return (
+            <div ref={this._commentRef}>
+                <IconButton
+                    tooltip={`Link Note to ${docType === 'pin' ? 'Pin' : 'Route'}`} //
+                    onPointerDown={this.notePointerDown}
+                    icon={<FontAwesomeIcon icon="sticky-note" />}
+                    color={SettingsManager.userColor}
+                />
+            </div>
+        )
+    }
+
+    linkNoteToPinOrRoutenButton: JSX.Element = (
+        <div ref={this._commentRef}>
+            <IconButton
+                tooltip="Link Note to Pin" //
+                onPointerDown={this.notePointerDown}
+                icon={<FontAwesomeIcon icon="sticky-note" />}
+                color={SettingsManager.userColor}
+            />
+        </div>
+    )
+
+    customizePinButton: JSX.Element = (
+        <IconButton 
+            tooltip="Customize pin" 
+            onPointerDown={this.CustomizeClick} 
+            icon={<FontAwesomeIcon icon={faEdit as IconLookup} />} 
+            color={SettingsManager.userColor} 
+        />
+    )
+
+    centerOnPinButton: JSX.Element = (
+        <IconButton
+            tooltip="Center on pin" //
+            onPointerDown={this.Center}
+            icon={<FontAwesomeIcon icon="compress-arrows-alt" />}
+            color={SettingsManager.userColor}
+        />
+    )
+
+    backButton: JSX.Element = (
+        <IconButton
+            tooltip="Go back" //
+            onPointerDown={this.BackClick}
+            icon={<FontAwesomeIcon icon={faArrowLeft as IconLookup} />}
+            color={SettingsManager.userColor}
+        />
+    )
+
+    addRouteButton: JSX.Element = (
+        <IconButton
+            tooltip="Add route" //
+            onPointerDown={this.HandleAddRouteClick}
+            icon={<FontAwesomeIcon icon={faAdd as IconLookup} />}
+            color={SettingsManager.userColor}
+        />
+    )
+
+    getDeleteButton = (type: string) => {
+        return (
+            <IconButton
+                tooltip={`Delete ${type === 'pin' ? 'Pin' : 'Route'}`} //
+                onPointerDown={this.Delete}
+                icon={<FontAwesomeIcon icon="trash-alt" />}
+                color={SettingsManager.userColor}
+            />
+        )
+    }
+
+    animateRouteButton: JSX.Element = (
+        <IconButton 
+            tooltip="Animate route" 
+            onPointerDown={() => this.OpenAnimationPanel(this.routeDoc)} 
+            icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} 
+            color={SettingsManager.userColor} 
+        />
+    )
+
+    revertToOriginalMarkerButton = (
+        <IconButton
+            tooltip="Revert to original" //
+            onPointerDown={() => this.revertToOriginalMarker()}
+            icon={<FontAwesomeIcon icon={faArrowsRotate as IconLookup} />}
+            color={SettingsManager.userColor}
+        />
+    )
+
     render() {
         const buttons = (
             <div className="menu-buttons" style={{ display: 'flex' }}>
                 {this.menuType === 'standard' && (
                     <>
-                        <IconButton
-                            tooltip="Delete Pin" //
-                            onPointerDown={this.Delete}
-                            icon={<FontAwesomeIcon icon="trash-alt" />}
-                            color={SettingsManager.userColor}
-                        />
-                        <IconButton tooltip="Get directions" onPointerDown={this.DirectionsClick} /**TODO: fix */ icon={<FontAwesomeIcon icon={faDiamondTurnRight as IconLookup} />} color={SettingsManager.userColor} />
-                        <IconButton tooltip="Add to calendar" onPointerDown={this.Delete} /**TODO: fix */ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
-                        <div ref={this._commentRef}>
-                            <IconButton
-                                tooltip="Link Note to Pin" //
-                                onPointerDown={this.notePointerDown}
-                                icon={<FontAwesomeIcon icon="sticky-note" />}
-                                color={SettingsManager.userColor}
-                            />
-                        </div>
-                        <IconButton tooltip="Customize pin" onPointerDown={this.CustomizeClick} icon={<FontAwesomeIcon icon={faEdit as IconLookup} />} color={SettingsManager.userColor} />
-                        <IconButton
-                            tooltip="Center on pin" //
-                            onPointerDown={this.Center}
-                            icon={<FontAwesomeIcon icon="compress-arrows-alt" />}
-                            color={SettingsManager.userColor}
-                        />
+                        {this.getDeleteButton('pin')}
+                        {this.getDirectionsButton}
+                        {this.getAddToCalendarButton('pin')}
+                        {this.getLinkNoteToDocButton('pin')}
+                        {this.customizePinButton}
+                        {this.centerOnPinButton}
                     </>
                 )}
                 {this.menuType === 'routeCreation' && (
                     <>
-                        <IconButton
-                            tooltip="Go back" //
-                            onPointerDown={this.BackClick}
-                            icon={<FontAwesomeIcon icon={faArrowLeft as IconLookup} />}
-                            color={SettingsManager.userColor}
-                        />
-                        <IconButton
-                            tooltip="Add route" //
-                            onPointerDown={this.HandleAddRouteClick}
-                            icon={<FontAwesomeIcon icon={faAdd as IconLookup} />}
-                            color={SettingsManager.userColor}
-                        />
+                        {this.backButton}
+                        {this.addRouteButton}
                     </>
                 )}
                 {this.menuType === 'route' && (
                     <>
-                        <IconButton
-                            tooltip="Delete Route" //
-                            onPointerDown={this.Delete}
-                            icon={<FontAwesomeIcon icon="trash-alt" />}
-                            color={SettingsManager.userColor}
-                        />
-                        <IconButton tooltip="Animate route" onPointerDown={() => this.OpenAnimationPanel(this.routeDoc)} /**TODO: fix */ icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />
-                        <div ref={this._commentRef}>
-                            <IconButton
-                                tooltip="Link Note to Pin" //
-                                onPointerDown={this.notePointerDown}
-                                icon={<FontAwesomeIcon icon="sticky-note" />}
-                                color={SettingsManager.userColor}
-                            />
-                        </div>
-                        <IconButton tooltip="Add to calendar" onPointerDown={this.Delete} /**TODO: fix */ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
+                        {this.getDeleteButton('route')}
+                        {this.animateRouteButton}
+                        {this.getAddToCalendarButton('route')}
+                        {this.getLinkNoteToDocButton('route')}
                     </>
                 )}
                 {this.menuType === 'customize' && (
                     <>
-                        <IconButton
-                            tooltip="Go back" //
-                            onPointerDown={this.BackClick}
-                            icon={<FontAwesomeIcon icon={faArrowLeft as IconLookup} />}
-                            color={SettingsManager.userColor}
-                        />
-                        <IconButton
-                            tooltip="Revert to original" //
-                            onPointerDown={() => this.revertToOriginalMarker()}
-                            icon={<FontAwesomeIcon icon={faArrowsRotate as IconLookup} />}
-                            color={SettingsManager.userColor}
-                        />
+                        {this.backButton}
+                        {this.revertToOriginalMarkerButton}
                     </>
                 )}
 
@@ -373,12 +461,6 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                     )} */}
             </div>
         );
-        // return (
-        //     <div ref={MapAnchorMenu.top} style={{zIndex: 30000, width: '100%', height: '100px'}}>
-        //         HELLO THIS IS ANCHOR MENU
-        //         {this.getElement(buttons)}
-        //     </div>
-        // )
 
         return this.getElement(
             <div ref={MapAnchorMenu.top} className="map-anchor-menu-container">
diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss
index b3ce55786..25b4587a5 100644
--- a/src/client/views/nodes/MapBox/MapBox.scss
+++ b/src/client/views/nodes/MapBox/MapBox.scss
@@ -109,20 +109,15 @@
             display: flex;
             justify-content: flex-start;
             align-items: center;
-            gap: 7px;
+            width: 100%;
 
             .animation-suboptions {
                 display: flex;
                 justify-content: flex-start;
-                flex-wrap: wrap;
                 align-items: center;
                 gap: 7px;
                 width: 100%;
 
-                .first-person-label {
-                    width: '130px' !important;
-                }
-
                 label {
                     margin-bottom: 0;
                 }
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 44afb8e7b..562cb63d1 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -650,6 +650,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     deleteSelectedPinOrRoute = undoable(() => {
+        console.log('deleting')
         if (this.selectedPinOrRoute) {
             // Removes filter
             Doc.setDocFilter(this.Document, 'latitude', this.selectedPinOrRoute.latitude, 'remove');
@@ -672,9 +673,19 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         e.stopPropagation();
         e.preventDefault();
         MapAnchorMenu.Instance.fadeOut(true);
+        runInAction(() => {
+            this.temporaryRouteSource = {
+                type: 'FeatureCollection',
+                features: [],
+            }
+        })
+
+
         document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
     };
 
+   
+
     @action
     centerOnSelectedPin = () => {
         if (this.selectedPinOrRoute) {
@@ -869,6 +880,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             if (createPinForDestination) {
                 this.createPushpin(destination.center[1], destination.center[0], destination.place_name);
             }
+            this.temporaryRouteSource = {
+                type: 'FeatureCollection',
+                features: []
+            }
+            MapAnchorMenu.Instance.fadeOut(true);
             return mapRoute;
         }
         // TODO: Display error that can't create route to same location
@@ -931,6 +947,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @action
     handleMapClick = (e: MapLayerMouseEvent) => {
         this.featuresFromGeocodeResults = [];
+        this.settingsOpen = false;
         if (this._mapRef.current) {
             const features = this._mapRef.current.queryRenderedFeatures(e.point, {
                 layers: ['map-routes-layer'],
@@ -953,13 +970,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
                     MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
 
+                    MapAnchorMenu.Instance.Reset();
+
                     MapAnchorMenu.Instance.setRouteDoc(routeDoc);
 
                     // TODO: Subject to change
                     MapAnchorMenu.Instance.setAllMapboxPins(this.allAnnotations.filter(anno => !anno.layout_unrendered));
 
                     MapAnchorMenu.Instance.DisplayRoute = this.displayRoute;
-                    MapAnchorMenu.Instance.HideRoute = this.hideRoute;
                     MapAnchorMenu.Instance.AddNewRouteToMap = this.createMapRoute;
                     MapAnchorMenu.Instance.CreatePin = this.addMarkerForFeature;
                     MapAnchorMenu.Instance.OpenAnimationPanel = this.openAnimationPanel;
@@ -976,6 +994,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     };
 
+
+
     /**
      * Makes a reverse geocoding API call to retrieve features corresponding to a map click (based on longitude
      * and latitude). Sets the search results accordingly.
@@ -1031,12 +1051,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
         MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
 
+        MapAnchorMenu.Instance.Reset();
+
         // pass in the pinDoc
         MapAnchorMenu.Instance.setPinDoc(pinDoc);
         MapAnchorMenu.Instance.setAllMapboxPins(this.allAnnotations.filter(anno => !anno.layout_unrendered));
 
         MapAnchorMenu.Instance.DisplayRoute = this.displayRoute;
-        MapAnchorMenu.Instance.HideRoute = this.hideRoute;
         MapAnchorMenu.Instance.AddNewRouteToMap = this.createMapRoute;
         MapAnchorMenu.Instance.CreatePin = this.addMarkerForFeature;
 
@@ -1367,8 +1388,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     };
 
-    @action
-    exportAnimationToVideo = () => {};
 
     getRouteAnimationOptions = (): JSX.Element => {
         return (
@@ -1406,7 +1425,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         <div>|</div>
                         <FormControlLabel
                             className="first-person-label"
-                            style={{ width: '130px' }}
                             label="1st person animation:"
                             labelPlacement="start"
                             control={<Checkbox color="success" checked={this.isStreetViewAnimation} onChange={this.toggleIsStreetViewAnimation} />}
@@ -1414,8 +1432,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         <div id="divider">|</div>
                         <IconButton tooltip={this.animationSpeedTooltipText} onPointerDown={this.updateAnimationSpeed} icon={this.animationSpeedIcon} size={Size.MEDIUM} />
                         <div id="divider">|</div>
-                        <div style={{ width: '230px' }}>Select Line Color: </div>
-                        <CirclePicker circleSize={12} circleSpacing={5} width="100%" colors={['#ffff00', '#03a9f4', '#ff0000', '#ff5722', '#000000', '#673ab7']} onChange={color => this.setAnimationLineColor(color)} />
+                        <div style={{display: 'flex', alignItems: 'center'}}>
+                            <div>Select Line Color: </div>
+                            <CirclePicker circleSize={12} circleSpacing={5} width="100%" colors={['#ffff00', '#03a9f4', '#ff0000', '#ff5722', '#000000', '#673ab7']} onChange={color => this.setAnimationLineColor(color)} />
+                        </div>
+                        
                     </div>
                 </>
             </>
@@ -1449,7 +1470,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     changeMapStyle = (e: React.ChangeEvent<HTMLSelectElement>) => {
-        this.dataDoc.map_style = `mapbox://styles/mapbox/${e.target.value}`;
+        this.dataDoc.map_style = e.target.value;
         // this.mapStyle = `mapbox://styles/mapbox/${e.target.value}`
     };
 
@@ -1457,6 +1478,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     onBearingChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         const bearing = parseInt(e.target.value);
         if (!isNaN(bearing) && this._mapRef.current) {
+            console.log('bearing change')
             const fixedBearing = Math.max(0, Math.min(360, bearing));
             this._mapRef.current.setBearing(fixedBearing);
             this.dataDoc.map_bearing = fixedBearing;
@@ -1467,6 +1489,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     onPitchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         const pitch = parseInt(e.target.value);
         if (!isNaN(pitch) && this._mapRef.current) {
+            console.log('pitch change')
             const fixedPitch = Math.max(0, Math.min(85, pitch));
             this._mapRef.current.setPitch(fixedPitch);
             this.dataDoc.map_pitch = fixedPitch;
@@ -1564,29 +1587,29 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             <div className="mapbox-style-select">
                                 <div>Map Style:</div>
                                 <div>
-                                    <select onChange={this.changeMapStyle}>
-                                        <option value="streets-v11">Streets</option>
-                                        <option value="outdoors-v12">Outdoors</option>
-                                        <option value="light-v11">Light</option>
-                                        <option value="dark-v11">Dark</option>
-                                        <option value="satellite-v9">Satellite</option>
-                                        <option value="satellite-streets-v12">Satellite Streets</option>
-                                        <option value="navigation-day-v1">Navigation Day</option>
-                                        <option value="navigation-night-v1">Navigation Night</option>
+                                    <select onChange={this.changeMapStyle} value={StrCast(this.dataDoc.map_style)}>
+                                        <option value="mapbox://styles/mapbox/streets-v11">Streets</option>
+                                        <option value="mapbox://styles/mapbox/outdoors-v12">Outdoors</option>
+                                        <option value="mapbox://styles/mapbox/light-v11">Light</option>
+                                        <option value="mapbox://styles/mapbox/dark-v11">Dark</option>
+                                        <option value="mapbox://styles/mapbox/satellite-v9">Satellite</option>
+                                        <option value="mapbox://styles/mapbox/satellite-streets-v12">Satellite Streets</option>
+                                        <option value="mapbox://styles/mapbox/navigation-day-v1">Navigation Day</option>
+                                        <option value="mapbox://styles/mapbox/navigation-night-v1">Navigation Night</option>
                                     </select>
                                 </div>
                             </div>
                             <div className="mapbox-bearing-selection">
                                 <div>Bearing: </div>
-                                <input value={NumCast(this.mapboxMapViewState.bearing).toFixed(2)} type="number" onChange={this.onBearingChange} />
+                                <input value={NumCast(this.mapboxMapViewState.bearing).toFixed(0)} type="number" onChange={this.onBearingChange} />
                             </div>
                             <div className="mapbox-pitch-selection">
                                 <div>Pitch: </div>
-                                <input value={NumCast(this.mapboxMapViewState.pitch).toFixed(2)} type="number" onChange={this.onPitchChange} />
+                                <input value={NumCast(this.mapboxMapViewState.pitch).toFixed(0)} type="number" onChange={this.onPitchChange} />
                             </div>
                             <div className="mapbox-pitch-selection">
                                 <div>Zoom: </div>
-                                <input value={NumCast(this.mapboxMapViewState.zoom).toFixed(2)} type="number" onChange={this.onZoomChange} />
+                                <input value={NumCast(this.mapboxMapViewState.zoom).toFixed(0)} type="number" onChange={this.onZoomChange} />
                             </div>
                             <div className="mapbox-terrain-selection">
                                 <div>Show terrain: </div>
@@ -1620,26 +1643,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             </React.Fragment>
                         </div>
                     )}
-                    {/* <div className='zoom-box'>
-                        <IconButton // increment 
-                            style={{borderBottom: '1px', borderBottomColor: 'white'}}
-                            onPointerDown={() => this.onStepZoomChange(true)}
-                            icon={<FontAwesomeIcon icon={faPlus as IconLookup} color='black'/>}
-                            size={Size.SMALL}
-                            color={SettingsManager.userColor}
-                        />
-                        <IconButton  // decrement
-                            onPointerDown={() => this.onStepZoomChange(false)}
-                            icon={<FontAwesomeIcon icon={faMinus as IconLookup} color='black'/>}
-                            size={Size.SMALL}
-                            color={SettingsManager.userColor}
-                        />
-                    </div>            */}
                     <MapProvider>
                         <MapboxMap
                             ref={this._mapRef}
                             mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
-                            viewState={this.isAnimating ? undefined : { ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width), height: NumCast(this.layoutDoc._height) }}
+                            viewState={(this.isAnimating || this.routeToAnimate) ? undefined : { ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width), height: NumCast(this.layoutDoc._height) }}
                             mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'}
                             style={{
                                 position: 'absolute',
@@ -1738,59 +1746,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             ))} */}
                         </MapboxMap>
                     </MapProvider>
-
-                    {/* <BingMapsReact
-                        onMapReady={this.bingMapReady} //
-                        bingMapsKey={bingApiKey}
-                        height="100%"
-                        mapOptions={this.bingMapOptions}
-                        width="100%"
-                        viewOptions={this.bingViewOptions}
-                    /> */}
-                    {/* <div>
-                        {!this._mapReady
-                            ? null
-                            : this.allAnnotations
-                                  .filter(anno => !anno.layout_unrendered)
-                                  .map((pushpin, i) => (
-                                      <DocumentView
-                                          key={i}
-                                          {...this._props}
-                                          renderDepth={this._props.renderDepth + 1}
-                                          Document={pushpin}
-                                          DataDoc={undefined}
-                                          PanelWidth={returnOne}
-                                          PanelHeight={returnOne}
-                                          NativeWidth={returnOne}
-                                          NativeHeight={returnOne}
-                                          onKey={undefined}
-                                          onDoubleClick={undefined}
-                                          onBrowseClick={undefined}
-                                          childFilters={returnEmptyFilter}
-                                          childFiltersByRanges={returnEmptyFilter}
-                                          searchFilterDocs={returnEmptyDoclist}
-                                          isDocumentActive={returnFalse}
-                                          isContentActive={returnFalse}
-                                          addDocTab={returnFalse}
-                                          ScreenToLocalTransform={Transform.Identity}
-                                          fitContentsToBox={undefined}
-                                          focus={returnOne}
-                                      />
-                                  ))}
-                    </div> */}
-                    {/* <MapBoxInfoWindow
-                                    key={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})[Id]}
-                                    {...OmitKeys(this._props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
-                                    place={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})}
-                                    markerMap={this.markerMap}
-                                    PanelWidth={this.infoWidth}
-                                    PanelHeight={this.infoHeight}
-                                    moveDocument={this.moveDocument}
-                                    isAnyChildContentActive={this.isAnyChildContentActive}
-                                    whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
-                                /> */}
                 </div>
-                {/* </LoadScript > */}
                 <div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
                     <SidebarAnnos
                         ref={this._sidebarRef}
@@ -1814,54 +1770,3 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         );
     }
 }
-
-{
-    /* <Autocomplete
-                            fullWidth
-                            id="map-location-searcher"
-                            freeSolo
-                            onInputChange={(e, searchText) => this.handleSearchChange(searchText)}
-                            onChange={(e, selectedOption) => {
-                                this.handleSearchChange(""); // clear input 
-                                this.addMarkerForFeature(selectedOption);
-                            }}
-                            options={this.featuresFromGeocodeResults
-                                .filter(feature => feature.place_name)
-                                .map(feature => feature)}
-                            getOptionLabel={(feature) => feature.place_name}
-                            renderInput={(params) => (
-                                <TextField
-                                    {...params}
-                                    placeholder='Enter a location'
-                                />
-                            )}
-                            /> */
-}
-{
-    /* <EditableText
-                            // editing
-                            setVal={(newText: string | number) => typeof newText === 'string' && this.handleSearchChange(newText)}
-                            // onEnter={e => this.bingSearch()}
-                            onEnter={e => {}}
-                            height={32}
-                            // placeholder={this.bingSearchBarContents || 'Enter a location'}
-                            placeholder='Enter a location'
-                            textAlign="center" 
-                        /> */
-}
-{
-    /* <IconButton
-                            icon={
-                                <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="magnifying-glass" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" color="#DFDFDF">
-                                    <path
-                                        fill="currentColor"
-                                        d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"></path>
-                                </svg>
-                            }
-                            onClick={this.bingSearch}
-                            type={Type.TERT}
-                        />
-                        <div style={{ width: 30, height: 30 }} ref={this._dragRef} onPointerDown={this.dragToggle}>
-                            <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size={'lg'} icon={'bullseye'} />} />
-                        </div> */
-}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 2691b951d96f2ce7652acbea9e340b61737b3b57 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 21 Dec 2023 13:06:22 -0500
Subject: added standard 3d gl view for maps.  added close button for infoUI.

---
 deploy/index.html                                  |   1 +
 .../CollectionFreeFormInfoState.tsx                |  10 +-
 .../CollectionFreeFormInfoUI.tsx                   | 103 +--------------------
 .../collectionFreeForm/CollectionFreeFormView.scss |   3 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   7 +-
 5 files changed, 19 insertions(+), 105 deletions(-)

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

diff --git a/deploy/index.html b/deploy/index.html
index 186c217d8..5e564e648 100644
--- a/deploy/index.html
+++ b/deploy/index.html
@@ -1,6 +1,7 @@
 <html style="overflow: hidden">
     <head>
         <title>Dash</title>
+        <script src='https://api.mapbox.com/mapbox-gl-js/v3.0.1/mapbox-gl.js'></script>
         <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v3.0.1/mapbox-gl.css' rel='stylesheet' />
         <link rel="preconnect" href="https://fonts.googleapis.com" />
         <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
index 763d14116..d9f7ca6ea 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
@@ -1,6 +1,8 @@
-import { IReactionDisposer, makeObservable, reaction } from 'mobx';
+import { IconButton, Size, Type } from 'browndash-components';
+import { action, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { SettingsManager } from '../../../util/SettingsManager';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import './CollectionFreeFormView.scss';
 // import assets from './assets/link.png';
@@ -54,6 +56,7 @@ export interface CollectionFreeFormInfoStateProps {
 @observer
 export class CollectionFreeFormInfoState extends ObservableReactComponent<CollectionFreeFormInfoStateProps> {
     _disposers: IReactionDisposer[] = [];
+    @observable _hide = false;
 
     constructor(props: any) {
         super(props);
@@ -96,11 +99,14 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
     }
     render() {
         return (
-            <div className="infoUI">
+            <div className="collectionFreeform-infoUI" style={{display:this._hide ? 'none':undefined}}>
                 <div className="msg">{this.State?.[StateMessage]}</div>
                 <div className="gif-container" style={{ display: this.State?.[StateMessageGIF] ? undefined : 'none' }}>
                     <img className="gif" src={this.State?.[StateMessageGIF]} alt="state message gif"></img>
                 </div>
+               <div style={{position:"absolute", top:-10, left:-10}}>
+                   <IconButton icon="x" color={SettingsManager.userColor} size={Size.XSMALL} type={Type.TERT} background={SettingsManager.userBackgroundColor} onClick={action(e => this._hide = true)} />
+                </div>
             </div>
         );
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
index 6a9c96854..181f325f3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -2,15 +2,14 @@ import { IReactionDisposer, makeObservable, observable, runInAction } from 'mobx
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Field, FieldResult } from '../../../../fields/Doc';
+import { InkTool } from '../../../../fields/InkField';
+import { DocumentManager } from '../../../util/DocumentManager';
 import { LinkManager } from '../../../util/LinkManager';
-import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton';
-import { CollectionFreeFormInfoState, InfoState, StateEntryFunc, infoState } from './CollectionFreeFormInfoState';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { CollectionFreeFormInfoState, InfoState, infoState, StateEntryFunc } from './CollectionFreeFormInfoState';
 import { CollectionFreeFormView } from './CollectionFreeFormView';
 import './CollectionFreeFormView.scss';
-import { InkTool } from '../../../../fields/InkField';
-import { DocumentManager } from '../../../util/DocumentManager';
-import TrailsIcon from '../../nodes/FontIconBox/TrailsIcon';
 
 export interface CollectionFreeFormInfoUIProps {
     Document: Doc;
@@ -19,7 +18,7 @@ export interface CollectionFreeFormInfoUIProps {
 
 @observer
 export class CollectionFreeFormInfoUI extends ObservableReactComponent<CollectionFreeFormInfoUIProps> {
-    private _disposers: { [name: string]: IReactionDisposer } = {};
+    _firstDocPos = { x: 0, y: 0 };
 
     constructor(props: any) {
         super(props);
@@ -225,98 +224,6 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
         return start;
     };
 
-    /*
-    componentDidMount(): void {
-        this._disposers.reaction1 = reaction(
-            () => this._props.Freeform.childDocs.slice(),
-            docs => {
-                if (docs.length === 1) {
-                    this.firstDoc = docs[0];
-                    this.firstDocPos = { x: NumCast(this.firstDoc.x), y: NumCast(this.firstDoc.y) };
-                    this.message = 'Hello world! You can drag and drop to move your document around.';
-                } else if (docs.length === 2) {
-                    this.message = 'Great job. To create a new link between them, click the link icon on both documents.';
-                } else {
-                    // this.message = 'Click anywhere and begin typing to create your first text document!';
-                }
-            },
-            { fireImmediately: true }
-        );
-        this._disposers.reaction2 = reaction(
-            () => ({ x: NumCast(this.firstDoc?.x), y: NumCast(this.firstDoc?.y), links: this.firstDoc && LinkManager.Instance.getAllDirectLinks(this.firstDoc) }),
-            ({ x, y, links }) => {
-                if ((x && x != this.firstDocPos.x) || (y && y != this.firstDocPos.y)) {
-                    this.message = 'Great moves. Try creating a second document.';
-                }
-                if (links && links.length > 0) {
-                    this.message = 'You made your first link! You can view your links by selecting the blue dot.';
-                }
-            },
-            { fireImmediately: true }
-        );
-        this._disposers.reaction3 = reaction(
-            () => ({ activeTool: Doc.ActiveTool, viewingLinks: DocumentLinksButton.LinkEditorDocView }),
-            ({ activeTool, viewingLinks }) => {
-                if (activeTool == InkTool.Pen) {
-                    this.message = "You're in pen mode! Click and drag to draw your first masterpiece.";
-                }
-                if (viewingLinks) {
-                    this.message = 'To edit your links, click the pencil icon.';
-                }
-                if (Doc.ActiveTool === InkTool.Pen) {
-                    this.message = 'Editing links';
-                }
-            },
-            { fireImmediately: true }
-        );
-        this._disposers.reaction4 = reaction(
-            () => ({ startLink: DocumentLinksButton.StartLink, endLink: Doc.UserDoc().links }),
-            ({ startLink, endLink }) => {
-                if (startLink) {
-                    this.message = "You've started a link.";
-                } else if (endLink) {
-                    this.message = "You've completed a link.";
-                }
-            }
-        );
-        this._disposers.reaction5 = reaction(
-            () => ({ pin: Doc.ActivePresentation?.data, trails: DocumentManager.Instance.DocumentViews.find(view => view.Document === Doc.MyTrails) }),
-            ({ pin, trails }) => {
-                // if (pin) {
-                //     this.message = 'You pinned your doc to a trail.';
-                // }
-                if (trails) {
-                    this.message = 'This is your trails tab.';
-                }
-            }
-        );
-        this._disposers.reaction6 = reaction(
-            () => ({ presentationMode: Doc.ActivePresentation?.presentation_status }),
-            ({ presentationMode }) => {
-                if (presentationMode === 'edit') {
-                    this.message = 'You are editing your presentation.';
-                } else if (presentationMode === 'manual') {
-                    this.message = 'Manual presentation mode';
-                } else if (presentationMode === 'auto') {
-                    this.message = 'Auto presentation mode';
-                }
-            }
-        );
-    }
-
-    componentWillUnmount(): void {
-        Object.values(this._disposers).forEach(disposer => disposer?.());
-    }
-
-    // stop reaction from what it's currently doing
-    // this._disposers.reaction1();
-
-    @observable message = 'Click anywhere and begin typing to create your first document!';
-    @observable firstDoc: Doc | undefined = undefined;
-    @observable secondDoc: Doc | undefined = undefined;
-    */
-    firstDocPos = { x: 0, y: 0 };
-
     render() {
         return <CollectionFreeFormInfoState next={this.setCurrState} infoState={this.currState} />;
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 3e0236776..7d3acaea7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -256,11 +256,10 @@
     position: absolute;
 }
 
-.infoUI {
+.collectionFreeform-infoUI {
     position: absolute;
     display: block;
     top: 0;
-    overflow: auto;
 
     color: white;
     background-color: #5075ef;
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 562cb63d1..7db139d74 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -31,7 +31,7 @@ import { Checkbox, FormControlLabel, TextField } from '@mui/material';
 import * as turf from '@turf/turf';
 import * as d3 from 'd3';
 import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, Position } from 'geojson';
-import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent } from '!mapbox-gl';
+import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent } from 'mapbox-gl';
 import { CirclePicker, ColorResult } from 'react-color';
 import { MarkerEvent } from 'react-map-gl/dist/esm/types';
 import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
@@ -1434,7 +1434,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         <div id="divider">|</div>
                         <div style={{display: 'flex', alignItems: 'center'}}>
                             <div>Select Line Color: </div>
-                            <CirclePicker circleSize={12} circleSpacing={5} width="100%" colors={['#ffff00', '#03a9f4', '#ff0000', '#ff5722', '#000000', '#673ab7']} onChange={color => this.setAnimationLineColor(color)} />
+                            <CirclePicker circleSize={12} circleSpacing={5} width="100%" colors={['#ffff00', '#03a9f4', '#ff0000', '#ff5722', '#000000', '#673ab7']} onChange={(color:any) => this.setAnimationLineColor(color)} />
                         </div>
                         
                     </div>
@@ -1455,7 +1455,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     settingsOpen: boolean = false;
 
     @observable
-    mapStyle: string = 'mapbox://styles/mapbox/streets-v12';
+    mapStyle: string = 'mapbox://styles/mapbox/standard';
 
     @observable
     showTerrain: boolean = true;
@@ -1588,6 +1588,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                 <div>Map Style:</div>
                                 <div>
                                     <select onChange={this.changeMapStyle} value={StrCast(this.dataDoc.map_style)}>
+                                        <option value="mapbox://styles/mapbox/standard">Standard</option>
                                         <option value="mapbox://styles/mapbox/streets-v11">Streets</option>
                                         <option value="mapbox://styles/mapbox/outdoors-v12">Outdoors</option>
                                         <option value="mapbox://styles/mapbox/light-v11">Light</option>
-- 
cgit v1.2.3-70-g09d2


From 9b9f54a43793ca6ffb26c56f962d11ba8325abd2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 29 Dec 2023 17:01:40 -0500
Subject: cleaned up imports, mobx observable initialization and some compile
 errors.

---
 src/Utils.ts                                       |  27 ++---
 src/client/util/CalendarManager.tsx                |  34 +++---
 src/client/util/CaptureManager.tsx                 |   2 +-
 src/client/util/CurrentUserUtils.ts                |  14 +--
 src/client/util/GroupManager.tsx                   |   2 +-
 src/client/util/HypothesisUtils.ts                 |  13 ++-
 src/client/views/DocumentButtonBar.tsx             |   5 -
 src/client/views/DocumentDecorations.tsx           |  11 +-
 src/client/views/FilterPanel.tsx                   |  11 +-
 src/client/views/GestureOverlay.tsx                |  25 +++--
 src/client/views/InkTranscription.tsx              |   8 +-
 src/client/views/InkingStroke.tsx                  |  76 +++++---------
 src/client/views/MainView.tsx                      |   2 +-
 src/client/views/MetadataEntryMenu.tsx             |  12 +--
 src/client/views/PreviewCursor.tsx                 |   2 +-
 src/client/views/PropertiesButtons.tsx             |  17 +--
 .../views/PropertiesDocBacklinksSelector.tsx       |   4 +-
 src/client/views/PropertiesSection.tsx             |   6 +-
 src/client/views/TemplateMenu.tsx                  |   2 +-
 src/client/views/UndoStack.tsx                     |  22 ++--
 src/client/views/animationtimeline/Region.tsx      |   2 +-
 src/client/views/animationtimeline/Timeline.tsx    |   4 +-
 .../views/animationtimeline/TimelineOverview.tsx   |   2 +-
 src/client/views/animationtimeline/Track.tsx       |   8 +-
 .../views/collections/CollectionCalendarView.tsx   |  19 ++--
 .../views/collections/CollectionDockingView.tsx    |   4 +-
 src/client/views/collections/CollectionMenu.tsx    |   3 -
 .../CollectionNoteTakingViewDivider.tsx            |   2 +-
 .../views/collections/CollectionPileView.tsx       |   2 +-
 .../collections/CollectionStackedTimeline.tsx      |   4 +-
 .../CollectionStackingViewFieldColumn.tsx          |   2 +-
 src/client/views/collections/CollectionSubView.tsx |   8 +-
 .../views/collections/CollectionTimeView.tsx       |  14 +--
 src/client/views/collections/CollectionView.tsx    |   2 +-
 src/client/views/collections/KeyRestrictionRow.tsx |  37 +++----
 src/client/views/collections/TreeView.tsx          |   2 +-
 .../CollectionFreeFormBackgroundGrid.tsx           |   2 +-
 .../CollectionFreeFormInfoState.tsx                |   2 +-
 .../CollectionFreeFormInfoUI.tsx                   |   8 +-
 .../CollectionFreeFormLinksView.tsx                |   4 +-
 .../CollectionFreeFormPannableContents.tsx         |   5 +-
 .../CollectionFreeFormRemoteCursors.tsx            |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |   5 +-
 .../collectionGrid/CollectionGridView.tsx          |   4 +-
 .../views/collections/collectionGrid/Grid.tsx      |   2 +-
 .../CollectionMulticolumnView.tsx                  |   4 +-
 .../CollectionMultirowView.tsx                     |   3 +-
 .../collectionMulticolumn/MulticolumnResizer.tsx   |   8 +-
 .../MulticolumnWidthLabel.tsx                      |  27 +++--
 .../collectionMulticolumn/MultirowHeightLabel.tsx  |  22 ++--
 .../collectionMulticolumn/MultirowResizer.tsx      |   6 +-
 .../collectionSchema/SchemaColumnHeader.tsx        |   4 +-
 src/client/views/linking/LinkMenuItem.tsx          |   6 +-
 src/client/views/linking/LinkPopup.tsx             |   8 +-
 .../views/linking/LinkRelationshipSearch.tsx       |   2 +-
 .../views/newlightbox/ButtonMenu/ButtonMenu.tsx    |   4 +-
 src/client/views/newlightbox/NewLightboxView.tsx   |  26 ++---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |   4 +-
 .../views/nodes/DataVizBox/SchemaCSVPopUp.tsx      |  30 +++---
 .../nodes/DataVizBox/components/Histogram.tsx      |   6 +-
 .../nodes/DataVizBox/components/LineChart.tsx      |  28 ++---
 .../views/nodes/DataVizBox/components/PieChart.tsx |   6 +-
 .../views/nodes/DataVizBox/components/TableBox.tsx |  12 +--
 src/client/views/nodes/DocumentContentsView.tsx    |   6 +-
 src/client/views/nodes/DocumentView.tsx            |   7 +-
 src/client/views/nodes/FaceRectangle.tsx           |   4 +-
 src/client/views/nodes/FaceRectangles.tsx          |   4 +-
 src/client/views/nodes/FieldView.tsx               |   2 +-
 src/client/views/nodes/KeyValuePair.tsx            |   6 +-
 src/client/views/nodes/LinkDescriptionPopup.tsx    |   2 +-
 src/client/views/nodes/LinkDocPreview.tsx          |   8 +-
 src/client/views/nodes/LoadingBox.tsx              |   2 +-
 src/client/views/nodes/MapBox/AnimationUtility.ts  |  13 +--
 .../views/nodes/MapBox/DirectionsAnchorMenu.tsx    |  14 +--
 src/client/views/nodes/MapBox/MapAnchorMenu.tsx    | 106 +++++++------------
 src/client/views/nodes/MapBox/MapBox.tsx           |  60 +++++------
 src/client/views/nodes/MapBox/MapBox2.tsx          |   2 +-
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |  20 ++--
 src/client/views/nodes/PDFBox.tsx                  |   2 +-
 .../nodes/PhysicsBox/PhysicsSimulationBox.tsx      |   4 +-
 .../nodes/PhysicsBox/PhysicsSimulationWeight.tsx   |   2 +-
 src/client/views/nodes/RadialMenu.tsx              |   2 +-
 src/client/views/nodes/RadialMenuItem.tsx          |   2 +-
 .../views/nodes/RecordingBox/RecordingBox.tsx      |   4 +-
 src/client/views/nodes/ScreenshotBox.tsx           |   2 +-
 src/client/views/nodes/ScriptingBox.tsx            |   8 +-
 src/client/views/nodes/TaskCompletedBox.tsx        |   8 +-
 src/client/views/nodes/VideoBox.tsx                |   6 +-
 src/client/views/nodes/audio/AudioWaveform.tsx     |   2 +-
 src/client/views/nodes/calendarBox/CalendarBox.tsx | 114 +++++++++------------
 .../views/nodes/formattedText/DashDocView.tsx      |   6 +-
 .../views/nodes/formattedText/EquationView.tsx     |   7 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   4 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     |   2 +-
 .../views/nodes/importBox/ImportElementBox.tsx     |   8 +-
 src/client/views/nodes/trails/PresBox.tsx          |   6 +-
 src/client/views/pdf/Annotation.tsx                |   5 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |  16 +--
 src/client/views/pdf/PDFViewer.tsx                 |   2 +-
 src/client/views/search/SearchBox.tsx              |  15 ++-
 src/client/views/selectedDoc/SelectedDocView.tsx   |   6 +-
 src/client/views/topbar/TopBar.tsx                 |   4 +-
 src/client/views/webcam/DashWebRTCVideo.tsx        |   5 +-
 src/debug/Repl.tsx                                 |  10 +-
 src/debug/Viewer.tsx                               |  18 ++--
 src/fields/CursorField.ts                          |   7 +-
 src/fields/Doc.ts                                  |   4 +-
 src/fields/List.ts                                 |   8 +-
 src/fields/Proxy.ts                                |  12 +--
 src/fields/util.ts                                 |   4 +-
 src/mobile/ImageUpload.tsx                         |   6 +-
 src/mobile/MobileInkOverlay.tsx                    |   2 +-
 src/mobile/MobileInterface.tsx                     | 110 ++++++++++----------
 src/typings/index.d.ts                             |   4 +-
 test/test.ts                                       | 106 ++++++++++---------
 116 files changed, 650 insertions(+), 793 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index a060e4a2c..b5c218e01 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -1,13 +1,12 @@
-import * as uuid from 'uuid';
 import { ColorResult } from 'react-color';
+import * as uuid from 'uuid';
 //import { Socket } from '../node_modules/socket.io-client';
-import { Socket } from '../node_modules/socket.io/dist/index';
+import * as Color from 'color';
 import * as rp from 'request-promise';
+import { Socket } from '../node_modules/socket.io/dist/index';
 import { DocumentType } from './client/documents/DocumentTypes';
 import { Colors } from './client/views/global/globalEnums';
 import { Message } from './server/Message';
-import * as Color from 'color';
-import { action } from 'mobx';
 
 export namespace Utils {
     export let CLICK_TIME = 300;
@@ -896,23 +895,19 @@ export function setupMoveUpEvents(
     document.addEventListener('click', _clickEvent, true);
 }
 
-export function dateRangeStrToDates (dateStr: string) {
+export function dateRangeStrToDates(dateStr: string) {
     // dateStr in yyyy-mm-dd format
-    const dateRangeParts = dateStr.split("|"); // splits into from and to date
-    const fromParts = dateRangeParts[0].split("-");
-    const toParts = dateRangeParts[1].split("-");
+    const dateRangeParts = dateStr.split('|'); // splits into from and to date
+    const fromParts = dateRangeParts[0].split('-');
+    const toParts = dateRangeParts[1].split('-');
 
     const fromYear = parseInt(fromParts[0]);
-    const fromMonth = parseInt(fromParts[1])-1;
+    const fromMonth = parseInt(fromParts[1]) - 1;
     const fromDay = parseInt(fromParts[2]);
 
     const toYear = parseInt(toParts[0]);
-    const toMonth = parseInt(toParts[1])-1;
+    const toMonth = parseInt(toParts[1]) - 1;
     const toDay = parseInt(toParts[2]);
 
-
-    return [
-        new Date(fromYear, fromMonth, fromDay),
-        new Date(toYear, toMonth, toDay)
-    ];
-}
\ No newline at end of file
+    return [new Date(fromYear, fromMonth, fromDay), new Date(toYear, toMonth, toDay)];
+}
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
index 6ef2d3429..6e9094b3a 100644
--- a/src/client/util/CalendarManager.tsx
+++ b/src/client/util/CalendarManager.tsx
@@ -1,26 +1,24 @@
-import * as React from 'react';
-import './CalendarManager.scss';
+import { TextField } from '@mui/material';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { action, computed, observable, runInAction, makeObservable } from 'mobx';
+import * as React from 'react';
+import Select from 'react-select';
 import { Doc, DocListCast } from '../../fields/Doc';
-import { DocumentView } from '../views/nodes/DocumentView';
+import { DocData } from '../../fields/DocSymbols';
+import { StrCast } from '../../fields/Types';
 import { DictationOverlay } from '../views/DictationOverlay';
-import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
 import { MainViewModal } from '../views/MainViewModal';
-import { TextField } from '@mui/material';
-import Select from 'react-select';
-import { SettingsManager } from './SettingsManager';
-import { DateCast, DocCast, StrCast } from '../../fields/Types';
-import { SelectionManager } from './SelectionManager';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
+import './CalendarManager.scss';
 import { DocumentManager } from './DocumentManager';
-import { DocData } from '../../fields/DocSymbols';
+import { SelectionManager } from './SelectionManager';
+import { SettingsManager } from './SettingsManager';
 // import { DateRange, Range, RangeKeyDict } from 'react-date-range';
-import { Button } from 'browndash-components';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Provider, defaultTheme } from '@adobe/react-spectrum';
-import { DateValue } from '@internationalized/date';
-import { DateRangePicker, SpectrumDateRangePickerProps } from '@adobe/react-spectrum';
+import { DateRangePicker, Provider, defaultTheme } from '@adobe/react-spectrum';
 import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Button } from 'browndash-components';
 import { Docs } from '../documents/Documents';
 import { ObservableReactComponent } from '../views/ObservableReactComponent';
 // import 'react-date-range/dist/styles.css';
@@ -51,8 +49,8 @@ const formatCalendarDateToString = (calendarDate: any) => {
 export class CalendarManager extends ObservableReactComponent<{}> {
     public static Instance: CalendarManager;
     @observable private isOpen = false;
-    @observable private targetDoc: Doc | undefined; // the target document
-    @observable private targetDocView: DocumentView | undefined; // the DocumentView of the target doc
+    @observable private targetDoc: Doc | undefined = undefined; // the target document
+    @observable private targetDocView: DocumentView | undefined = undefined; // the DocumentView of the target doc
     @observable private dialogueBoxOpacity = 1; // for the modal
     @observable private overlayOpacity = 0.4; // for the modal
 
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index 071fc1ee9..c1e0a5b2e 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -15,7 +15,7 @@ import { SelectionManager } from './SelectionManager';
 export class CaptureManager extends React.Component<{}> {
     public static Instance: CaptureManager;
     static _settingsStyle = addStyleSheet();
-    @observable _document: any;
+    @observable _document: any = undefined;
     @observable isOpen: boolean = false; // whether the CaptureManager is to be displayed or not.
 
     constructor(props: {}) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index c1d74c398..3f28f44fc 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,5 +1,6 @@
 import { observable, reaction, runInAction } from "mobx";
 import * as rp from 'request-promise';
+import { OmitKeys, Utils } from "../../Utils";
 import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
 import { InkTool } from "../../fields/InkField";
 import { List } from "../../fields/List";
@@ -8,29 +9,28 @@ import { RichTextField } from "../../fields/RichTextField";
 import { listSpec } from "../../fields/Schema";
 import { ScriptField } from "../../fields/ScriptField";
 import { Cast, DateCast, DocCast, StrCast } from "../../fields/Types";
-import { nullAudio, WebField } from "../../fields/URLField";
+import { WebField, nullAudio } from "../../fields/URLField";
 import { SetCachedGroups, SharingPermissions } from "../../fields/util";
 import { GestureUtils } from "../../pen-gestures/GestureUtils";
-import { OmitKeys, Utils } from "../../Utils";
 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 { DocUtils, Docs, DocumentOptions, FInfo } from "../documents/Documents";
 import { DashboardView } from "../views/DashboardView";
+import { OverlayView } from "../views/OverlayView";
+import { TreeViewType } from "../views/collections/CollectionTreeView";
 import { Colors } from "../views/global/globalEnums";
 import { media_state } from "../views/nodes/AudioBox";
-import { DocumentView, OpenWhere } from "../views/nodes/DocumentView";
+import { OpenWhere } from "../views/nodes/DocumentView";
 import { ButtonType } from "../views/nodes/FontIconBox/FontIconBox";
 import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox";
-import { OverlayView } from "../views/OverlayView";
 import { DragManager, dropActionType } from "./DragManager";
 import { MakeTemplate } from "./DropConverter";
 import { FollowLinkScript } from "./LinkFollower";
 import { LinkManager } from "./LinkManager";
 import { ScriptingGlobals } from "./ScriptingGlobals";
 import { ColorScheme } from "./SettingsManager";
-import { UndoManager } from "./UndoManager";
 import { SnappingManager } from "./SnappingManager";
+import { UndoManager } from "./UndoManager";
 
 interface Button {
     // DocumentOptions fields a button can set
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 90f65b648..f4f879208 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -33,7 +33,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
     @observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not.
     @observable private users: string[] = []; // list of users populated from the database.
     @observable private selectedUsers: UserOptions[] | null = null; // list of users selected in the "Select users" dropdown.
-    @observable currentGroup: Opt<Doc>; // the currently selected group.
+    @observable currentGroup: Opt<Doc> = undefined; // the currently selected group.
     @observable private createGroupModalOpen: boolean = false;
     private inputRef: React.RefObject<HTMLInputElement> = React.createRef(); // the ref for the input box.
     private createGroupButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // the ref for the group creation button
diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts
index 990798ed3..0ae23d793 100644
--- a/src/client/util/HypothesisUtils.ts
+++ b/src/client/util/HypothesisUtils.ts
@@ -1,16 +1,15 @@
-import { StrCast, Cast } from '../../fields/Types';
-import { SearchUtil } from './SearchUtil';
 import { action, runInAction } from 'mobx';
+import { simulateMouseClick } from '../../Utils';
 import { Doc, Opt } from '../../fields/Doc';
+import { Cast, StrCast } from '../../fields/Types';
+import { WebField } from '../../fields/URLField';
 import { DocumentType } from '../documents/DocumentTypes';
 import { Docs } from '../documents/Documents';
-import { SelectionManager } from './SelectionManager';
-import { WebField } from '../../fields/URLField';
-import { DocumentManager } from './DocumentManager';
 import { DocumentLinksButton } from '../views/nodes/DocumentLinksButton';
-import { simulateMouseClick, Utils } from '../../Utils';
 import { DocumentView } from '../views/nodes/DocumentView';
-import { Id } from '../../fields/FieldSymbols';
+import { DocumentManager } from './DocumentManager';
+import { SearchUtil } from './SearchUtil';
+import { SelectionManager } from './SelectionManager';
 
 export namespace Hypothesis {
     /**
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index f0887676f..3938b81cd 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -40,14 +40,9 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
     private _pullAnimating = false;
     private _pushAnimating = false;
     private _pullColorAnimating = false;
-    @observable private pushIcon: IconProp = 'arrow-alt-circle-up';
-    @observable private pullIcon: IconProp = 'arrow-alt-circle-down';
-    @observable private pullColor: string = 'white';
     @observable public isAnimatingFetch = false;
     @observable public isAnimatingPulse = false;
 
-    @observable private openHover: UtilityButtonState = UtilityButtonState.Default;
-
     @observable public static Instance: DocumentButtonBar;
     public static hasPushedHack = false;
     public static hasPulledHack = false;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index d2f23c2f3..dab6e6193 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -12,7 +12,7 @@ import { RichTextField } from '../../fields/RichTextField';
 import { ScriptField } from '../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
 import { GetEffectiveAcl } from '../../fields/util';
-import { emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils';
+import { emptyFunction, lightOrDark, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils';
 import { Docs } from '../documents/Documents';
 import { DocumentType } from '../documents/DocumentTypes';
 import { DocumentManager } from '../util/DocumentManager';
@@ -85,7 +85,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     }
 
     @computed get ClippedBounds() {
-        const bounds = this.Bounds;
+        const bounds = { ...this.Bounds };
         const leftBounds = this._props.boundsLeft;
         const topBounds = LightboxView.LightboxDoc ? 0 : this._props.boundsTop;
         bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
@@ -106,9 +106,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                             : { x: Math.min(rect.left, bounds.x),
                                 y: Math.min(rect.top, bounds.y),
                                 r: Math.max(rect.right, bounds.r),
-                                b: Math.max(rect.bottom, bounds.b),
-                                c: SelectionManager.Views.length === 1 ? rect.center : undefined },
-                    { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE, c: undefined as { X: number; Y: number } | undefined }); // prettier-ignore
+                                b: Math.max(rect.bottom, bounds.b)},
+                    { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); // prettier-ignore
     }
 
     @action
@@ -811,7 +810,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                 <div
                                     className="documentDecorations-borderRadius"
                                     style={{
-                                        background: `${this._isRounding ? Colors.MEDIUM_BLUE : SettingsManager.userColor}`,
+                                        background: `${this._isRounding ? Colors.MEDIUM_BLUE : lightOrDark(StrCast(seldocview.layoutDoc._backgroundColor, SettingsManager.userColor))}`,
                                         transform: `translate(${radiusHandleLocation ?? 0}px, ${(radiusHandleLocation ?? 0) + (this._showNothing ? 0 : this._titleHeight)}px)`,
                                     }}
                                     onPointerDown={this.onRadiusDown}
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index a3c4350d9..d3075a5cd 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -1,24 +1,23 @@
-import * as React from 'react';
 import { action, computed, observable, ObservableMap } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider';
 import { AiOutlineMinusSquare, AiOutlinePlusSquare } from 'react-icons/ai';
 import { CiCircleRemove } from 'react-icons/ci';
 import Select from 'react-select';
 import { Doc, DocListCast, Field, LinkedTo, StrListCast } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
+import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
-import { DocOptions, DocumentOptions, FInfo } from '../documents/Documents';
+import { DocOptions, FInfo } from '../documents/Documents';
 import { DocumentManager } from '../util/DocumentManager';
 import { UserOptions } from '../util/GroupManager';
 import { SearchUtil } from '../util/SearchUtil';
+import { SettingsManager } from '../util/SettingsManager';
 import { undoable } from '../util/UndoManager';
 import './FilterPanel.scss';
 import { FieldView } from './nodes/FieldView';
 import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components';
-import { SettingsManager } from '../util/SettingsManager';
-import { Id } from '../../fields/FieldSymbols';
-import { List } from '../../fields/List';
-import { emptyFunction } from '../../Utils';
 
 interface filterProps {
     Document: Doc;
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 33cda6a62..65aadc148 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -1,15 +1,14 @@
-import * as React from 'react';
 import * as fitCurve from 'fit-curve';
-import { action, computed, makeObservable, observable, runInAction, toJS } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
+import * as React from 'react';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../Utils';
 import { Doc, Opt } from '../../fields/Doc';
 import { InkData, InkTool } from '../../fields/InkField';
 import { BoolCast, NumCast } from '../../fields/Types';
 import MobileInkOverlay from '../../mobile/MobileInkOverlay';
 import { GestureUtils } from '../../pen-gestures/GestureUtils';
 import { MobileInkOverlayContent } from '../../server/Message';
-import { CognitiveServices } from '../cognitive_services/CognitiveServices';
 import { InteractionUtils } from '../util/InteractionUtils';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
 import { Transform } from '../util/Transform';
@@ -29,9 +28,9 @@ import {
     SetActiveInkColor,
     SetActiveInkWidth,
 } from './InkingStroke';
+import { ObservableReactComponent } from './ObservableReactComponent';
 import { checkInksToGroup } from './global/globalScripts';
 import { DocumentView } from './nodes/DocumentView';
-import { ObservableReactComponent } from './ObservableReactComponent';
 
 interface GestureOverlayProps {
     isActive: boolean;
@@ -48,22 +47,22 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
         return BoolCast(Doc.UserDoc().recognizeGestures);
     }
 
-    @observable public InkShape: Opt<GestureUtils.Gestures>;
-    @observable public SavedColor?: string;
-    @observable public SavedWidth?: number;
+    @observable public InkShape: Opt<GestureUtils.Gestures> = undefined;
+    @observable public SavedColor?: string = undefined;
+    @observable public SavedWidth?: number = undefined;
     @observable public Tool: ToolglassTools = ToolglassTools.None;
     @observable public KeepPrimitiveMode = false; // for whether primitive selection enters a one-shot or persistent mode
 
-    @observable private _thumbX?: number;
-    @observable private _thumbY?: number;
+    @observable private _thumbX?: number = undefined;
+    @observable private _thumbY?: number = undefined;
     @observable private _selectedIndex: number = -1;
     @observable private _menuX: number = -300;
     @observable private _menuY: number = -300;
-    @observable private _pointerY?: number;
+    @observable private _pointerY?: number = undefined;
     @observable private _points: { X: number; Y: number }[] = [];
     @observable private _strokes: InkData[] = [];
-    @observable private _palette?: JSX.Element;
-    @observable private _clipboardDoc?: JSX.Element;
+    @observable private _palette?: JSX.Element = undefined;
+    @observable private _clipboardDoc?: JSX.Element = undefined;
     @observable private _possibilities: JSX.Element[] = [];
 
     public static DownDocView: DocumentView | undefined;
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index b2ac208ca..7e2b334af 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -17,10 +17,10 @@
 // export class InkTranscription extends React.Component {
 //     static Instance: InkTranscription;
 
-//     @observable _mathRegister: any;
-//     @observable _mathRef: any;
-//     @observable _textRegister: any;
-//     @observable _textRef: any;
+//     @observable _mathRegister: any= undefined;
+//     @observable _mathRef: any= undefined;
+//     @observable _textRegister: any= undefined;
+//     @observable _textRef: any= undefined;
 //     private lastJiix: any;
 //     private currGroup?: Doc;
 
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 9c96b4563..95c677845 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -20,9 +20,9 @@
     
     Most of the operations that can be performed on an InkStroke (eg delete a point, rotate, stretch) are implemented in the InkStrokeProperties helper class
 */
-import * as React from 'react';
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc } from '../../fields/Doc';
 import { InkData, InkField } from '../../fields/InkField';
 import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
@@ -32,7 +32,6 @@ import { CognitiveServices } from '../cognitive_services/CognitiveServices';
 import { Docs } from '../documents/Documents';
 import { InteractionUtils } from '../util/InteractionUtils';
 import { SnappingManager } from '../util/SnappingManager';
-import { Transform } from '../util/Transform';
 import { UndoManager } from '../util/UndoManager';
 import { ContextMenu } from './ContextMenu';
 import { ViewBoxBaseComponent } from './DocComponent';
@@ -59,14 +58,14 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     private _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated
     private _disposers: { [key: string]: IReactionDisposer } = {};
 
-    @observable _nearestSeg?: number; // nearest Bezier segment along the ink stroke to the cursor (used for displaying the Add Point highlight)
-    @observable _nearestT?: number; // nearest t value within the nearest Bezier segment "
+    @observable _nearestSeg?: number = undefined; // nearest Bezier segment along the ink stroke to the cursor (used for displaying the Add Point highlight)
+    @observable _nearestT?: number = undefined; // nearest t value within the nearest Bezier segment "
     @observable _nearestScrPt?: { X: number; Y: number }; // nearst screen point on the ink stroke ""
 
     componentDidMount() {
-        this.props.setContentView?.(this);
+        this._props.setContentView?.(this);
         this._disposers.selfDisper = reaction(
-            () => this.props.isSelected(), // react to stroke being deselected by turning off ink handles
+            () => this._props.isSelected(), // react to stroke being deselected by turning off ink handles
             selected => !selected && (InkStrokeProperties.Instance._controlButton = false)
         );
     }
@@ -76,7 +75,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
 
     // transform is the inherited screentolocal xf plus any scaling that was done to make the stroke
     // fit within its panel (e.g., for content fitting views like Lightbox or multicolumn, etc)
-    screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.NativeDimScaling?.() || 1);
+    screenToLocal = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1);
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
         const subAnchor = this._subContentView?.getAnchor?.(addAsAnnotation);
@@ -100,27 +99,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         return this.Document;
     };
 
-    /**
-     * @returns the center of the ink stroke in the ink document's coordinate space (not screen space, and not the ink data coordinate space);
-     * DocumentDecorations calls getBounds() on DocumentViews which call getCenter() if defined - in the case of ink it needs to be defined since
-     * the center of the ink stroke changes as the stroke is rotated.
-     */
-    getCenter = (xf: Transform) => {
-        const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
-        const angle = -NumCast(this.layoutDoc.rotation);
-        const newPoints = inkData.map(pt => {
-            const newX = Math.cos(angle) * pt.X - (Math.sin(angle) * pt.Y * inkScaleY) / inkScaleX;
-            const newY = (Math.sin(angle) * pt.X * inkScaleX) / inkScaleY + Math.cos(angle) * pt.Y;
-            return { X: newX, Y: newY };
-        });
-        const crx = (Math.max(...newPoints.map(np => np.X)) + Math.min(...newPoints.map(np => np.X))) / 2;
-        const cry = (Math.max(...newPoints.map(np => np.Y)) + Math.min(...newPoints.map(np => np.Y))) / 2;
-        const cx = Math.cos(-angle) * crx - (Math.sin(-angle) * cry * inkScaleY) / inkScaleX;
-        const cy = (Math.sin(-angle) * crx * inkScaleX) / inkScaleY + Math.cos(-angle) * cry;
-        const tc = xf.transformPoint((cx - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (cy - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2);
-        return { X: tc[0], Y: tc[1] };
-    };
-
     /**
      * analyzes the ink stroke and saves the analysis of the stroke to the 'inkAnalysis' field,
      * and the recognized words to the 'handwriting'
@@ -147,7 +125,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     onPointerDown = (e: React.PointerEvent) => {
         this._handledClick = false;
-        const inkView = this.props.docViewPath().lastElement();
+        const inkView = this._props.docViewPath().lastElement();
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
         const screenPts = inkData
             .map(point =>
@@ -159,7 +137,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const { nearestSeg } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY });
         const controlIndex = nearestSeg;
         const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex;
-        const isEditing = InkStrokeProperties.Instance._controlButton && this.props.isSelected();
+        const isEditing = InkStrokeProperties.Instance._controlButton && this._props.isSelected();
         this.controlUndo = undefined;
         setupMoveUpEvents(
             this,
@@ -187,7 +165,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                     InkStrokeProperties.Instance._currentPoint = -1;
                     this._handledClick = true; // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
                     if (isEditing) {
-                        this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance.addPoints(this.props.docViewPath().lastElement(), this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
+                        this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance.addPoints(this._props.docViewPath().lastElement(), this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
                     }
                 }
             }),
@@ -258,8 +236,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
             inkLeft,
             inkWidth,
             inkHeight,
-            inkScaleX: (this.props.PanelWidth() - inkStrokeWidth) / (inkWidth - inkStrokeWidth || 1) || 1,
-            inkScaleY: (this.props.PanelHeight() - inkStrokeWidth) / (inkHeight - inkStrokeWidth || 1) || 1,
+            inkScaleX: (this._props.PanelWidth() - inkStrokeWidth) / (inkWidth - inkStrokeWidth || 1) || 1,
+            inkScaleY: (this._props.PanelHeight() - inkStrokeWidth) / (inkHeight - inkStrokeWidth || 1) || 1,
         };
     };
 
@@ -311,7 +289,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
      * @returns the JSX controls for displaying an editing UI for the stroke (control point & tangent handles)
      */
     componentUI = (boundsLeft: number, boundsTop: number) => {
-        const inkDoc = this.props.Document;
+        const inkDoc = this.Document;
         const { inkData, inkStrokeWidth } = this.inkScaledData();
         const screenSpaceCenterlineStrokeWidth = Math.min(3, inkStrokeWidth * this.screenToLocal().inverse().Scale); // the width of the blue line widget that shows the centerline of the ink stroke
 
@@ -321,7 +299,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const endMarker = StrCast(this.layoutDoc.stroke_endMarker);
         const markerScale = NumCast(this.layoutDoc.stroke_markerScale);
         return SnappingManager.IsDragging ? null : !InkStrokeProperties.Instance._controlButton ? (
-            !this.props.isSelected() || InkingStroke.IsClosed(inkData) ? null : (
+            !this._props.isSelected() || InkingStroke.IsClosed(inkData) ? null : (
                 <div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
                     <InkEndPtHandles inkView={this} inkDoc={inkDoc} startPt={this.startPt} endPt={this.endPt} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
                 </div>
@@ -360,12 +338,12 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     setSubContentView = (doc: DocComponentView) => (this._subContentView = doc);
     @computed get fillColor() {
         const isInkMask = BoolCast(this.layoutDoc.stroke_isInkMask);
-        return isInkMask ? DashColor(StrCast(this.layoutDoc.fillColor, 'transparent')).blacken(0).rgb().toString() : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FillColor) ?? 'transparent';
+        return isInkMask ? DashColor(StrCast(this.layoutDoc.fillColor, 'transparent')).blacken(0).rgb().toString() : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FillColor) ?? 'transparent';
     }
     @computed get strokeColor() {
         const { inkData } = this.inkScaledData();
         const fillColor = this.fillColor;
-        return !InkingStroke.IsClosed(inkData) && fillColor && fillColor !== 'transparent' ? fillColor : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color) ?? StrCast(this.layoutDoc.color);
+        return !InkingStroke.IsClosed(inkData) && fillColor && fillColor !== 'transparent' ? fillColor : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) ?? StrCast(this.layoutDoc.color);
     }
     render() {
         TraceMobx();
@@ -385,9 +363,9 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                 this.layoutDoc._height = Math.round(NumCast(this.layoutDoc._height));
             });
         }
-        const highlight = !this.controlUndo && this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Highlighting);
+        const highlight = !this.controlUndo && this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting);
         const highlightIndex = highlight?.highlightIndex;
-        const highlightColor = !this.props.isSelected() && !isInkMask && highlight?.highlightIndex ? highlight?.highlightColor : undefined;
+        const highlightColor = !this._props.isSelected() && !isInkMask && highlight?.highlightIndex ? highlight?.highlightColor : undefined;
         const color = StrCast(this.layoutDoc.stroke_outlineColor, !closed && fillColor && fillColor !== 'transparent' ? StrCast(this.layoutDoc.color, 'transparent') : 'transparent');
 
         // Visually renders the polygonal line made by the user.
@@ -437,7 +415,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                 inkScaleX,
                 inkScaleY,
                 '',
-                this.props.pointerEvents?.() ?? 'visiblepainted',
+                this._props.pointerEvents?.() ?? 'visiblepainted',
                 0.0,
                 false,
                 downHdlr,
@@ -450,7 +428,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const lineHeightGuess = +getComputedStyle(document.body).lineHeight.replace('px', '') / +getComputedStyle(document.body).fontSize.replace('px', '');
         const interactions = {
             onPointerLeave: action(() => (this._nearestScrPt = undefined)),
-            onPointerMove: this.props.isSelected() ? this.onPointerMove : undefined,
+            onPointerMove: this._props.isSelected() ? this.onPointerMove : undefined,
             onClick: (e: React.MouseEvent) => this._handledClick && e.stopPropagation(),
             onContextMenu: () => {
                 const cm = ContextMenu.Instance;
@@ -464,27 +442,27 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                 <svg
                     className="inkStroke"
                     style={{
-                        transform: isInkMask ? `rotate(-${NumCast(this.props.CollectionFreeFormDocumentView?.()._props.w_Rotation?.() ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+                        transform: isInkMask ? `rotate(-${NumCast(this._props.CollectionFreeFormDocumentView?.()._props.w_Rotation?.() ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
                         // mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
-                        cursor: this.props.isSelected() ? 'default' : undefined,
+                        cursor: this._props.isSelected() ? 'default' : undefined,
                     }}
                     {...interactions}>
                     {clickableLine(this.onPointerDown, isInkMask)}
                     {isInkMask ? null : inkLine}
                 </svg>
-                {!closed || (!RTFCast(this.dataDoc.text)?.Text && (!this.props.isSelected() || Doc.UserDoc().activeInkHideTextLabels)) ? null : (
+                {!closed || (!RTFCast(this.dataDoc.text)?.Text && (!this._props.isSelected() || Doc.UserDoc().activeInkHideTextLabels)) ? null : (
                     <div
                         className="inkStroke-text"
                         style={{
                             color: StrCast(this.layoutDoc.textColor, 'black'),
-                            pointerEvents: this.props.isDocumentActive?.() ? 'all' : undefined,
+                            pointerEvents: this._props.isDocumentActive?.() ? 'all' : undefined,
                             width: NumCast(this.layoutDoc._width),
-                            transform: `scale(${this.props.NativeDimScaling?.() || 1})`,
+                            transform: `scale(${this._props.NativeDimScaling?.() || 1})`,
                             transformOrigin: 'top left',
-                            //top: (this.props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this.props.NativeDimScaling?.() || 1)) / 2,
+                            //top: (this._props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this._props.NativeDimScaling?.() || 1)) / 2,
                         }}>
                         <FormattedTextBox
-                            {...this.props}
+                            {...this._props}
                             setHeight={undefined}
                             setContentView={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
                             yPadding={10}
@@ -493,7 +471,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                             dontRegisterView={true}
                             noSidebar={true}
                             dontScale={true}
-                            isContentActive={this.props.isContentActive}
+                            isContentActive={this._props.isContentActive}
                         />
                     </div>
                 )}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 094bd2adb..35ffab337 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -118,7 +118,7 @@ export class MainView extends ObservableReactComponent<{}> {
     @computed private get userDoc() {
         return Doc.UserDoc();
     }
-    @observable mainDoc: Opt<Doc>;
+    @observable mainDoc: Opt<Doc> = undefined;
     @computed private get mainContainer() {
         if (window.location.pathname.startsWith('/doc/') && Doc.CurrentUserEmail === 'guest') {
             DocServer.GetRefField(window.location.pathname.substring('/doc/'.length)).then(main => runInAction(() => (this.mainDoc = main as Doc)));
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index 5c6912121..89c3c41f8 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -1,12 +1,12 @@
-import * as React from 'react';
-import './MetadataEntryMenu.scss';
+import { IReactionDisposer, action, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { observable, action, runInAction, trace, computed, IReactionDisposer, reaction } from 'mobx';
-import { KeyValueBox } from './nodes/KeyValueBox';
-import { Doc, Field, DocListCastAsync, DocListCast } from '../../fields/Doc';
+import * as React from 'react';
 import * as Autosuggest from 'react-autosuggest';
-import { undoBatch, UndoManager } from '../util/UndoManager';
 import { emptyFunction, emptyPath } from '../../Utils';
+import { Doc, DocListCast, Field } from '../../fields/Doc';
+import { undoBatch } from '../util/UndoManager';
+import './MetadataEntryMenu.scss';
+import { KeyValueBox } from './nodes/KeyValueBox';
 
 export type DocLike = Doc | Doc[] | Promise<Doc> | Promise<Doc[]>;
 export interface MetadataEntryProps {
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 166afc0c2..a69a5f5e8 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -24,7 +24,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
     _addLiveTextDoc?: (doc: Doc) => void;
     _nudge?: undefined | ((x: number, y: number) => boolean);
     _slowLoadDocuments?: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>;
-    @observable _clickPoint: number[];
+    @observable _clickPoint: number[] = [];
     @observable public Visible = false;
     public Doc: Opt<Doc>;
     constructor(props: any) {
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 8540e81e1..f1dadcd5e 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -2,6 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Dropdown, DropdownType, IListItemProps, Toggle, ToggleType, Type } from 'browndash-components';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { AiOutlineColumnWidth } from 'react-icons/ai';
 import { BiHide, BiShow } from 'react-icons/bi';
 import { BsGrid3X3GapFill } from 'react-icons/bs';
@@ -12,28 +13,20 @@ import { RxWidth } from 'react-icons/rx';
 import { TbEditCircle, TbEditCircleOff, TbHandOff, TbHandStop, TbHighlight, TbHighlightOff } from 'react-icons/tb';
 import { TfiBarChart } from 'react-icons/tfi';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { InkField } from '../../fields/InkField';
 import { RichTextField } from '../../fields/RichTextField';
 import { BoolCast, ScriptCast } from '../../fields/Types';
 import { ImageField } from '../../fields/URLField';
-import { DocUtils } from '../documents/Documents';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
+import { DocUtils } from '../documents/Documents';
 import { IsFollowLinkScript } from '../util/LinkFollower';
 import { LinkManager } from '../util/LinkManager';
 import { SelectionManager } from '../util/SelectionManager';
 import { SettingsManager } from '../util/SettingsManager';
-import { undoable, undoBatch } from '../util/UndoManager';
-import { Colors } from './global/globalEnums';
+import { undoBatch, undoable } from '../util/UndoManager';
 import { InkingStroke } from './InkingStroke';
-import { DocumentView, OpenWhere } from './nodes/DocumentView';
 import './PropertiesButtons.scss';
-import * as React from 'react';
-
-enum UtilityButtonState {
-    Default,
-    OpenRight,
-    OpenExternally,
-}
+import { Colors } from './global/globalEnums';
+import { DocumentView, OpenWhere } from './nodes/DocumentView';
 
 @observer
 export class PropertiesButtons extends React.Component<{}, {}> {
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index 58d3b72e8..244cd4aa0 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -7,10 +7,10 @@ import { DocumentType } from '../documents/DocumentTypes';
 import { LinkManager } from '../util/LinkManager';
 import { SelectionManager } from '../util/SelectionManager';
 import { SettingsManager } from '../util/SettingsManager';
+import './PropertiesDocBacklinksSelector.scss';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { LinkMenu } from './linking/LinkMenu';
-import { OpenWhere, OpenWhereMod } from './nodes/DocumentView';
-import './PropertiesDocBacklinksSelector.scss';
+import { OpenWhere } from './nodes/DocumentView';
 
 type PropertiesDocBacklinksSelectorProps = {
     Document: Doc;
diff --git a/src/client/views/PropertiesSection.tsx b/src/client/views/PropertiesSection.tsx
index 2b9bfb505..3c9fa1123 100644
--- a/src/client/views/PropertiesSection.tsx
+++ b/src/client/views/PropertiesSection.tsx
@@ -1,11 +1,9 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import './PropertiesSection.scss';
-import { Doc } from '../../fields/Doc';
-import { StrCast } from '../../fields/Types';
+import * as React from 'react';
 import { SettingsManager } from '../util/SettingsManager';
+import './PropertiesSection.scss';
 
 export interface PropertiesSectionProps {
     title: string;
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 48653f30a..791928b4a 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -1,5 +1,6 @@
 import { action, computed, observable, ObservableSet, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc, DocListCast } from '../../fields/Doc';
 import { ScriptField } from '../../fields/ScriptField';
 import { Cast, StrCast } from '../../fields/Types';
@@ -13,7 +14,6 @@ import { CollectionTreeView } from './collections/CollectionTreeView';
 import { DocumentView } from './nodes/DocumentView';
 import { DefaultStyleProvider } from './StyleProvider';
 import './TemplateMenu.scss';
-import * as React from 'react';
 
 @observer
 class TemplateToggle extends React.Component<{ template: string; checked: boolean; toggle: (event: React.ChangeEvent<HTMLInputElement>, template: string) => void }> {
diff --git a/src/client/views/UndoStack.tsx b/src/client/views/UndoStack.tsx
index f07e38af1..ea038250e 100644
--- a/src/client/views/UndoStack.tsx
+++ b/src/client/views/UndoStack.tsx
@@ -15,8 +15,8 @@ interface UndoStackProps {
 }
 @observer
 export class UndoStack extends React.Component<UndoStackProps> {
-    @observable static HideInline: boolean;
-    @observable static Expand: boolean;
+    @observable static HideInline: boolean = false;
+    @observable static Expand: boolean = false;
     render() {
         const background = UndoManager.batchCounter.get() ? 'yellow' : SettingsManager.userVariantColor;
         const color = UndoManager.batchCounter.get() ? 'black' : SettingsManager.userColor;
@@ -39,19 +39,25 @@ export class UndoStack extends React.Component<UndoStackProps> {
                                         color,
                                     }}>
                                     {Array.from(UndoManager.undoStackNames).map((name, i) => (
-                                        <div className="undoStack-resultContainer" key={i} 
-                                             onClick={e => {
+                                        <div
+                                            className="undoStack-resultContainer"
+                                            key={i}
+                                            onClick={e => {
                                                 const size = UndoManager.undoStackNames.length;
-                                                for (let n = 0; n < size-i; n++ ) UndoManager.Undo();  } } 
-                                            >
+                                                for (let n = 0; n < size - i; n++) UndoManager.Undo();
+                                            }}>
                                             <div className="undoStack-commandString">{StrCast(name).replace(/[^\.]*\./, '')}</div>
                                         </div>
                                     ))}
                                     {Array.from(UndoManager.redoStackNames)
                                         .reverse()
                                         .map((name, i) => (
-                                            <div className="undoStack-resultContainer" key={i} onClick={e =>
-                                                { for (let n = 0; n <= i; n++ ) UndoManager.Redo() }}>
+                                            <div
+                                                className="undoStack-resultContainer"
+                                                key={i}
+                                                onClick={e => {
+                                                    for (let n = 0; n <= i; n++) UndoManager.Redo();
+                                                }}>
                                                 <div className="undoStack-commandString" style={{ fontWeight: 'bold', background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
                                                     {StrCast(name).replace(/[^\.]*\./, '')}
                                                 </div>
diff --git a/src/client/views/animationtimeline/Region.tsx b/src/client/views/animationtimeline/Region.tsx
index 15cbbc16f..99163f6c6 100644
--- a/src/client/views/animationtimeline/Region.tsx
+++ b/src/client/views/animationtimeline/Region.tsx
@@ -6,11 +6,11 @@ import { List } from '../../../fields/List';
 import { createSchema, defaultSpec, listSpec, makeInterface } from '../../../fields/Schema';
 import { Cast, NumCast } from '../../../fields/Types';
 import { Transform } from '../../util/Transform';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import '../global/globalCssVariables.module.scss';
 import './Region.scss';
 import './Timeline.scss';
 import { TimelineMenu } from './TimelineMenu';
-import { ObservableReactComponent } from '../ObservableReactComponent';
 
 /**
  * Useful static functions that you can use. Mostly for logic, but you can also add UI logic here also
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index f27a2d2fd..cc4da1694 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -4,17 +4,17 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { Utils, emptyFunction, setupMoveUpEvents } from '../../../Utils';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { BoolCast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils';
 import { DocumentType } from '../../documents/DocumentTypes';
 import clamp from '../../util/clamp';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { FieldViewProps } from '../nodes/FieldView';
 import { RegionHelpers } from './Region';
 import './Timeline.scss';
 import { TimelineOverview } from './TimelineOverview';
 import { Track } from './Track';
-import { ObservableReactComponent } from '../ObservableReactComponent';
 
 /**
  * Timeline class controls most of timeline functions besides individual region and track mechanism. Main functions are 
diff --git a/src/client/views/animationtimeline/TimelineOverview.tsx b/src/client/views/animationtimeline/TimelineOverview.tsx
index 82ac69a3b..928739b53 100644
--- a/src/client/views/animationtimeline/TimelineOverview.tsx
+++ b/src/client/views/animationtimeline/TimelineOverview.tsx
@@ -28,7 +28,7 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
     @observable private overviewBarWidth: number = 0;
     @observable private playbarWidth: number = 0;
     @observable private activeOverviewWidth: number = 0;
-    @observable private _authoringReaction?: IReactionDisposer;
+    @observable private _authoringReaction?: IReactionDisposer = undefined;
     @observable private visibleTime: number = 0;
     @observable private currentX: number = 0;
     @observable private visibleStart: number = 0;
diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index 00aa51cac..490a14be5 100644
--- a/src/client/views/animationtimeline/Track.tsx
+++ b/src/client/views/animationtimeline/Track.tsx
@@ -8,10 +8,10 @@ import { ObjectField } from '../../../fields/ObjectField';
 import { listSpec } from '../../../fields/Schema';
 import { Cast, NumCast } from '../../../fields/Types';
 import { Transform } from '../../util/Transform';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { Region, RegionData, RegionHelpers } from './Region';
 import { Timeline } from './Timeline';
 import './Track.scss';
-import { ObservableReactComponent } from '../ObservableReactComponent';
 
 interface IProps {
     timeline: Timeline;
@@ -29,9 +29,9 @@ interface IProps {
 @observer
 export class Track extends ObservableReactComponent<IProps> {
     @observable private _inner = React.createRef<HTMLDivElement>();
-    @observable private _currentBarXReaction: any;
-    @observable private _timelineVisibleReaction: any;
-    @observable private _autoKfReaction: any;
+    @observable private _currentBarXReaction: any = undefined;
+    @observable private _timelineVisibleReaction: any = undefined;
+    @observable private _autoKfReaction: any = undefined;
     @observable private _newKeyframe: boolean = false;
     private readonly MAX_TITLE_HEIGHT = 75;
     @observable private _trackHeight = 0;
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx
index 2cec29669..4c0a917f5 100644
--- a/src/client/views/collections/CollectionCalendarView.tsx
+++ b/src/client/views/collections/CollectionCalendarView.tsx
@@ -1,18 +1,11 @@
-import * as React from 'react';
-import { CollectionSubView } from './CollectionSubView';
+import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
-import { computed, makeObservable, observable } from 'mobx';
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import * as React from 'react';
+import { dateRangeStrToDates, emptyFunction, returnTrue } from '../../../Utils';
+import { Doc, DocListCast } from '../../../fields/Doc';
+import { StrCast } from '../../../fields/Types';
 import { CollectionStackingView } from './CollectionStackingView';
-import { CollectionViewType } from '../../documents/DocumentTypes';
-import { dateRangeStrToDates, emptyFunction, returnAll, returnEmptyDoclist, returnNone, returnOne, returnTrue } from '../../../Utils';
-import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
-import { TbRuler } from 'react-icons/tb';
-import { Transform } from '../../util/Transform';
-import { DocData } from '../../../fields/DocSymbols';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { StyleProp } from '../StyleProvider';
-import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
+import { CollectionSubView } from './CollectionSubView';
 
 @observer
 export class CollectionCalendarView extends CollectionSubView() {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 75c178136..868df7a3b 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,4 +1,4 @@
-import { action, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
+import { action, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
@@ -30,7 +30,7 @@ import { ScriptingRepl } from '../ScriptingRepl';
 import { UndoStack } from '../UndoStack';
 import './CollectionDockingView.scss';
 import { CollectionFreeFormView } from './collectionFreeForm';
-import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
+import { CollectionSubView } from './CollectionSubView';
 import { TabDocView } from './TabDocView';
 const _global = (window /* browser */ || global) /* node */ as any;
 
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 443aa3f17..2a6cb081f 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -32,16 +32,13 @@ interface CollectionMenuProps {
 @observer
 export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
     @observable static Instance: CollectionMenu;
-
     @observable SelectedCollection: DocumentView | undefined = undefined;
-    @observable FieldKey: string;
 
     private _docBtnRef = React.createRef<HTMLDivElement>();
 
     constructor(props: any) {
         super(props);
         makeObservable(this);
-        this.FieldKey = '';
         CollectionMenu.Instance = this;
         this._canFade = false; // don't let the inking menu fade away
         this.Pinned = Cast(Doc.UserDoc()['menuCollections-pinned'], 'boolean', true);
diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
index af822d917..2aad48d4a 100644
--- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
@@ -1,4 +1,4 @@
-import { action, observable, trace } from 'mobx';
+import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { emptyFunction, setupMoveUpEvents } from '../../../Utils';
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 170b1f1ec..e5068645f 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,5 +1,6 @@
 import { action, computed, IReactionDisposer, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { ScriptField } from '../../../fields/ScriptField';
 import { NumCast, StrCast } from '../../../fields/Types';
@@ -12,7 +13,6 @@ import { computePassLayout, computeStarburstLayout } from './collectionFreeForm'
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import './CollectionPileView.scss';
 import { CollectionSubView } from './CollectionSubView';
-import * as React from 'react';
 
 @observer
 export class CollectionPileView extends CollectionSubView() {
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index fb8bc4da2..fa3844aa4 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -20,7 +20,7 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
-import { CollectionSubView, SubCollectionViewProps } from '../collections/CollectionSubView';
+import { CollectionSubView } from '../collections/CollectionSubView';
 import { LightboxView } from '../LightboxView';
 import { AudioWaveform } from '../nodes/audio/AudioWaveform';
 import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../nodes/DocumentView';
@@ -56,7 +56,7 @@ export enum TrimScope {
 @observer
 export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() {
     @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined;
-    @observable public static CurrentlyPlaying: DocumentView[];
+    @observable public static CurrentlyPlaying: DocumentView[] = [];
     constructor(props: any) {
         super(props);
         makeObservable(this);
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index f9d575da2..69f1c332d 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -20,8 +20,8 @@ import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
-import './CollectionStackingView.scss';
 import { ObservableReactComponent } from '../ObservableReactComponent';
+import './CollectionStackingView.scss';
 
 // So this is how we are storing a column
 interface CSVFieldColumnProps {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 2ff435ce2..570330174 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -4,11 +4,11 @@ import * as rp from 'request-promise';
 import { Utils, returnFalse } from '../../../Utils';
 import CursorField from '../../../fields/CursorField';
 import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
-import { AclPrivate, DocData } from '../../../fields/DocSymbols';
+import { AclPrivate } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
@@ -41,8 +41,8 @@ export function CollectionSubView<X>(moreProps?: X) {
             makeObservable(this);
         }
 
-        @observable _focusFilters: Opt<string[]>; // childFilters that are overridden when previewing a link to an anchor which has childFilters set on it
-        @observable _focusRangeFilters: Opt<string[]>; // childFiltersByRanges that are overridden when previewing a link to an anchor which has childFiltersByRanges set on it
+        @observable _focusFilters: Opt<string[]> = undefined; // childFilters that are overridden when previewing a link to an anchor which has childFilters set on it
+        @observable _focusRangeFilters: Opt<string[]> = undefined; // childFiltersByRanges that are overridden when previewing a link to an anchor which has childFiltersByRanges set on it
         protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
             this.dropDisposer?.();
             this.gestureDisposer?.();
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 6033b897d..7bbdb7073 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,6 +1,8 @@
 import { toUpper } from 'lodash';
 import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
 import { Doc, Opt, StrListCast } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
@@ -8,7 +10,6 @@ import { RichTextField } from '../../../fields/RichTextField';
 import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
@@ -17,20 +18,19 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
 import { DocFocusOptions, DocumentView } from '../nodes/DocumentView';
 import { PresBox } from '../nodes/trails';
-import { computePivotLayout, computeTimelineLayout, ViewDefBounds } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
-import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import { CollectionSubView } from './CollectionSubView';
 import './CollectionTimeView.scss';
-import * as React from 'react';
+import { ViewDefBounds, computePivotLayout, computeTimelineLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 
 @observer
 export class CollectionTimeView extends CollectionSubView() {
     _changing = false;
     @observable _layoutEngine = computePivotLayout.name;
     @observable _collapsed: boolean = false;
-    @observable _childClickedScript: Opt<ScriptField>;
-    @observable _viewDefDivClick: Opt<ScriptField>;
-    @observable _focusPivotField: Opt<string>;
+    @observable _childClickedScript: Opt<ScriptField> = undefined;
+    @observable _viewDefDivClick: Opt<ScriptField> = undefined;
+    @observable _focusPivotField: Opt<string> = undefined;
 
     constructor(props: any) {
         super(props);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 0656843cb..c2f5ab2c0 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -16,6 +16,7 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import { OpenWhere } from '../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import { CollectionCalendarView } from './CollectionCalendarView';
 import { CollectionCarousel3DView } from './CollectionCarousel3DView';
 import { CollectionCarouselView } from './CollectionCarouselView';
 import { CollectionDockingView } from './CollectionDockingView';
@@ -28,7 +29,6 @@ import { CollectionTreeView } from './CollectionTreeView';
 import './CollectionView.scss';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import { CollectionGridView } from './collectionGrid/CollectionGridView';
-import { CollectionCalendarView } from './CollectionCalendarView';
 import { CollectionLinearView } from './collectionLinear';
 import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
 import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
diff --git a/src/client/views/collections/KeyRestrictionRow.tsx b/src/client/views/collections/KeyRestrictionRow.tsx
index f3071b316..4523a4f1e 100644
--- a/src/client/views/collections/KeyRestrictionRow.tsx
+++ b/src/client/views/collections/KeyRestrictionRow.tsx
@@ -1,6 +1,6 @@
-import * as React from "react";
-import { observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
+import { observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
 
 interface IKeyRestrictionProps {
     contains: boolean;
@@ -19,37 +19,28 @@ export default class KeyRestrictionRow extends React.Component<IKeyRestrictionPr
         if (this._key && this._value) {
             let parsedValue: string | number = `"${this._value}"`;
             const parsed = parseInt(this._value);
-            let type = "string";
+            let type = 'string';
             if (!isNaN(parsed)) {
                 parsedValue = parsed;
-                type = "number";
+                type = 'number';
             }
-            const scriptText = `${this._contains ? "" : "!"}(((doc.${this._key} && (doc.${this._key} as ${type})${type === "string" ? ".includes" : "<="}(${parsedValue}))) ||
-                ((doc.data_ext && doc.data_ext.${this._key}) && (doc.data_ext.${this._key} as ${type})${type === "string" ? ".includes" : "<="}(${parsedValue}))))`;
+            const scriptText = `${this._contains ? '' : '!'}(((doc.${this._key} && (doc.${this._key} as ${type})${type === 'string' ? '.includes' : '<='}(${parsedValue}))) ||
+                ((doc.data_ext && doc.data_ext.${this._key}) && (doc.data_ext.${this._key} as ${type})${type === 'string' ? '.includes' : '<='}(${parsedValue}))))`;
             // let doc = new Doc();
             // ((doc.data_ext && doc.data_ext!.text) && (doc.data_ext!.text as string).includes("hello"));
             this.props.script(scriptText);
-        }
-        else {
-            this.props.script("");
+        } else {
+            this.props.script('');
         }
 
         return (
             <div className="collectionViewBaseChrome-viewSpecsMenu-row">
-                <input className="collectionViewBaseChrome-viewSpecsMenu-rowLeft"
-                    value={this._key}
-                    onChange={(e) => runInAction(() => this._key = e.target.value)}
-                    placeholder="KEY" />
-                <button className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
-                    style={{ background: this._contains ? "#77dd77" : "#ff6961" }}
-                    onClick={() => runInAction(() => this._contains = !this._contains)}>
-                    {this._contains ? "CONTAINS" : "DOES NOT CONTAIN"}
+                <input className="collectionViewBaseChrome-viewSpecsMenu-rowLeft" value={this._key} onChange={e => runInAction(() => (this._key = e.target.value))} placeholder="KEY" />
+                <button className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle" style={{ background: this._contains ? '#77dd77' : '#ff6961' }} onClick={() => runInAction(() => (this._contains = !this._contains))}>
+                    {this._contains ? 'CONTAINS' : 'DOES NOT CONTAIN'}
                 </button>
-                <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
-                    value={this._value}
-                    onChange={(e) => runInAction(() => this._value = e.target.value)}
-                    placeholder="VALUE" />
+                <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight" value={this._value} onChange={e => runInAction(() => (this._value = e.target.value))} placeholder="VALUE" />
             </div>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index a7705ea7e..72882ac17 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -93,7 +93,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
     static _openLevelScript: Opt<ScriptField | undefined>;
     private _header: React.RefObject<HTMLDivElement> = React.createRef();
     private _tref = React.createRef<HTMLDivElement>();
-    @observable _docRef: Opt<DocumentView>;
+    @observable _docRef: Opt<DocumentView> = undefined;
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _editTitleScript: (() => ScriptField) | undefined;
     private _openScript: (() => ScriptField) | undefined;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
index 99ee5ef4e..08dfb32ad 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
@@ -1,8 +1,8 @@
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { NumCast } from '../../../../fields/Types';
 import './CollectionFreeFormView.scss';
-import * as React from 'react';
 
 export interface CollectionFreeFormViewBackgroundGridProps {
     panX: () => number;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
index 2606304d0..58f6b1593 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
@@ -1,5 +1,5 @@
 import { IconButton, Size, Type } from 'browndash-components';
-import { action, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { IReactionDisposer, action, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { SettingsManager } from '../../../util/SettingsManager';
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
index 15e4d8360..8628ca3c3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -1,16 +1,16 @@
-import { IReactionDisposer, makeObservable, observable, runInAction } from 'mobx';
+import { makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Field, FieldResult } from '../../../../fields/Doc';
 import { InkTool } from '../../../../fields/InkField';
+import { StrCast } from '../../../../fields/Types';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { LinkManager } from '../../../util/LinkManager';
-import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { CollectionFreeFormInfoState, InfoState, infoState, StateEntryFunc } from './CollectionFreeFormInfoState';
+import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton';
+import { CollectionFreeFormInfoState, InfoState, StateEntryFunc, infoState } from './CollectionFreeFormInfoState';
 import { CollectionFreeFormView } from './CollectionFreeFormView';
 import './CollectionFreeFormView.scss';
-import { StrCast } from '../../../../fields/Types';
 
 export interface CollectionFreeFormInfoUIProps {
     Document: Doc;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 779a0bf96..95d521f65 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,11 +1,11 @@
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Id } from '../../../../fields/FieldSymbols';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { LightboxView } from '../../LightboxView';
-import './CollectionFreeFormLinksView.scss';
 import { CollectionFreeFormLinkView } from './CollectionFreeFormLinkView';
-import * as React from 'react';
+import './CollectionFreeFormLinksView.scss';
 
 @observer
 export class CollectionFreeFormLinksView extends React.Component {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
index f54726a00..ec8416303 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
@@ -1,12 +1,11 @@
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { ScriptField } from '../../../../fields/ScriptField';
 import { PresBox } from '../../nodes/trails/PresBox';
-import './CollectionFreeFormView.scss';
-import * as React from 'react';
 import { CollectionFreeFormView } from './CollectionFreeFormView';
-
+import './CollectionFreeFormView.scss';
 export interface CollectionFreeFormPannableContentsProps {
     Document: Doc;
     viewDefDivClick?: ScriptField;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index 45e24bbb2..fa8218bdd 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -1,6 +1,8 @@
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as mobxUtils from 'mobx-utils';
+import * as React from 'react';
+import * as uuid from 'uuid';
 import CursorField from '../../../../fields/CursorField';
 import { Doc, FieldResult } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
@@ -9,8 +11,6 @@ import { listSpec } from '../../../../fields/Schema';
 import { Cast } from '../../../../fields/Types';
 import { CollectionViewProps } from '../CollectionView';
 import './CollectionFreeFormView.scss';
-import * as React from 'react';
-import * as uuid from 'uuid';
 
 @observer
 export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 645e9cff7..cc0833698 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -37,6 +37,7 @@ import { CtrlKey } from '../../GlobalKeyHandler';
 import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
 import { LightboxView } from '../../LightboxView';
 import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewWrapper } from '../../nodes/CollectionFreeFormDocumentView';
+import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
 import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView';
 import { FieldViewProps } from '../../nodes/FieldView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
@@ -52,7 +53,6 @@ import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannable
 import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
 import './CollectionFreeFormView.scss';
 import { MarqueeView } from './MarqueeView';
-import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
 
 export type collectionFreeformViewProps = {
     NativeWidth?: () => number;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b22fdfa19..39d828302 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -16,19 +16,18 @@ import { DocumentType } from '../../../documents/DocumentTypes';
 import { DocUtils, Docs, DocumentOptions } from '../../../documents/Documents';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { freeformScrollMode } from '../../../util/SettingsManager';
+import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { UndoManager, undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { PreviewCursor } from '../../PreviewCursor';
-import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
+import { OpenWhere } from '../../nodes/DocumentView';
 import { pasteImageBitmap } from '../../nodes/WebBoxRenderer';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { SubCollectionViewProps } from '../CollectionSubView';
 import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
 import './MarqueeView.scss';
-import { SnappingManager } from '../../../util/SnappingManager';
-
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
     getTransform: () => Transform;
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 1e19964d7..3e75257e5 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -7,7 +7,6 @@ import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types
 import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { DragManager } from '../../../util/DragManager';
-import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
@@ -17,13 +16,12 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { CollectionSubView } from '../CollectionSubView';
 import './CollectionGridView.scss';
 import Grid, { Layout } from './Grid';
-
 @observer
 export class CollectionGridView extends CollectionSubView() {
     private _containerRef: React.RefObject<HTMLDivElement> = React.createRef();
     private _changeListenerDisposer: Opt<Lambda>; // listens for changes in this.childLayoutPairs
     private _resetListenerDisposer: Opt<Lambda>; // listens for when the reset button is clicked
-    @observable private _rowHeight: Opt<number>; // temporary store of row height to make change undoable
+    @observable private _rowHeight: Opt<number> = undefined; // temporary store of row height to make change undoable
     @observable private _scroll: number = 0; // required to make sure the decorations box container updates on scroll
     private dropLocation: object = {}; // sets the drop location for external drops
 
diff --git a/src/client/views/collections/collectionGrid/Grid.tsx b/src/client/views/collections/collectionGrid/Grid.tsx
index 3d1d87aa0..9145d7ef1 100644
--- a/src/client/views/collections/collectionGrid/Grid.tsx
+++ b/src/client/views/collections/collectionGrid/Grid.tsx
@@ -1,5 +1,5 @@
-import * as React from 'react';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 
 import '../../../../../node_modules/react-grid-layout/css/styles.css';
 import '../../../../../node_modules/react-resizable/css/styles.css';
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index e12bcd8b0..5bea59e7b 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -4,13 +4,13 @@ import { Button } from 'browndash-components';
 import { action, computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { returnFalse } from '../../../../Utils';
 import { Doc, DocListCast } from '../../../../fields/Doc';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { returnFalse } from '../../../../Utils';
 import { DragManager, dropActionType } from '../../../util/DragManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
-import { undoable, undoBatch } from '../../../util/UndoManager';
+import { undoBatch, undoable } from '../../../util/UndoManager';
 import { DocumentView } from '../../nodes/DocumentView';
 import { CollectionSubView } from '../CollectionSubView';
 import './CollectionMulticolumnView.scss';
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 7dbc18e60..3043eb0f8 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -1,9 +1,9 @@
 import { action, computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { returnFalse } from '../../../../Utils';
 import { Doc, DocListCast } from '../../../../fields/Doc';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { returnFalse } from '../../../../Utils';
 import { DragManager, dropActionType } from '../../../util/DragManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
@@ -12,7 +12,6 @@ import { CollectionSubView } from '../CollectionSubView';
 import './CollectionMultirowView.scss';
 import HeightLabel from './MultirowHeightLabel';
 import ResizeBar from './MultirowResizer';
-
 interface HeightSpecifier {
     magnitude: number;
     unit: string;
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
index 868b1140d..ea99bff2e 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
@@ -1,12 +1,12 @@
-import * as React from 'react';
+import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
+import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { NumCast, StrCast } from '../../../../fields/Types';
-import { DimUnit } from './CollectionMulticolumnView';
 import { UndoManager } from '../../../util/UndoManager';
-import { StyleProviderFunc } from '../../nodes/DocumentView';
 import { StyleProp } from '../../StyleProvider';
+import { StyleProviderFunc } from '../../nodes/DocumentView';
+import { DimUnit } from './CollectionMulticolumnView';
 
 interface ResizerProps {
     width: number;
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
index 9985a9fba..a9579d931 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
@@ -1,27 +1,25 @@
-import * as React from "react";
-import { observer } from "mobx-react";
-import { computed } from "mobx";
-import { Doc } from "../../../../fields/Doc";
-import { NumCast, StrCast, BoolCast } from "../../../../fields/Types";
-import { EditableView } from "../../EditableView";
-import { DimUnit } from "./CollectionMulticolumnView";
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc } from '../../../../fields/Doc';
+import { BoolCast, NumCast, StrCast } from '../../../../fields/Types';
+import { EditableView } from '../../EditableView';
+import { DimUnit } from './CollectionMulticolumnView';
 
 interface WidthLabelProps {
     layout: Doc;
     collectionDoc: Doc;
-    decimals?: number;
 }
 
 @observer
 export default class WidthLabel extends React.Component<WidthLabelProps> {
-
     @computed
     private get contents() {
-        const { layout, decimals } = this.props;
+        const { layout } = this.props;
         const getUnit = () => StrCast(layout.dimUnit);
-        const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(decimals ?? 3));
+        const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(3));
         return (
-            <div className={"label-wrapper"}>
+            <div className={'label-wrapper'}>
                 <EditableView
                     GetValue={getMagnitude}
                     SetValue={value => {
@@ -50,7 +48,6 @@ export default class WidthLabel extends React.Component<WidthLabelProps> {
     }
 
     render() {
-        return BoolCast(this.props.collectionDoc.showWidthLabels) ? this.contents : (null);
+        return BoolCast(this.props.collectionDoc.showWidthLabels) ? this.contents : null;
     }
-
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
index aa5439fa4..878c7ff3c 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
@@ -1,10 +1,10 @@
-import * as React from "react";
-import { observer } from "mobx-react";
-import { computed } from "mobx";
-import { Doc } from "../../../../fields/Doc";
-import { NumCast, StrCast, BoolCast } from "../../../../fields/Types";
-import { EditableView } from "../../EditableView";
-import { DimUnit } from "./CollectionMultirowView";
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc } from '../../../../fields/Doc';
+import { BoolCast, NumCast, StrCast } from '../../../../fields/Types';
+import { EditableView } from '../../EditableView';
+import { DimUnit } from './CollectionMultirowView';
 
 interface HeightLabelProps {
     layout: Doc;
@@ -14,14 +14,13 @@ interface HeightLabelProps {
 
 @observer
 export default class HeightLabel extends React.Component<HeightLabelProps> {
-
     @computed
     private get contents() {
         const { layout, decimals } = this.props;
         const getUnit = () => StrCast(layout.dimUnit);
         const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(decimals ?? 3));
         return (
-            <div className={"label-wrapper"}>
+            <div className={'label-wrapper'}>
                 <EditableView
                     GetValue={getMagnitude}
                     SetValue={value => {
@@ -50,7 +49,6 @@ export default class HeightLabel extends React.Component<HeightLabelProps> {
     }
 
     render() {
-        return BoolCast(this.props.collectionDoc.showHeightLabels) ? this.contents : (null);
+        return BoolCast(this.props.collectionDoc.showHeightLabels) ? this.contents : null;
     }
-
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
index 5a9d6a82c..7dee65e58 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
@@ -1,12 +1,12 @@
-import * as React from 'react';
+import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
+import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { NumCast, StrCast } from '../../../../fields/Types';
-import { DimUnit } from './CollectionMultirowView';
 import { UndoManager } from '../../../util/UndoManager';
 import { StyleProp } from '../../StyleProvider';
 import { StyleProviderFunc } from '../../nodes/DocumentView';
+import { DimUnit } from './CollectionMultirowView';
 
 interface ResizerProps {
     height: number;
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
index 04443b4a7..5f8b412be 100644
--- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -1,7 +1,7 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from 'mobx';
+import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { emptyFunction, setupMoveUpEvents } from '../../../../Utils';
 import { Colors } from '../../global/globalEnums';
 import './CollectionSchemaView.scss';
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 06073b52c..85e97f95f 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -1,3 +1,4 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { action, computed, makeObservable, observable } from 'mobx';
@@ -7,18 +8,17 @@ import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { Cast, DocCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
+import { DocumentType } from '../../documents/DocumentTypes';
 import { DragManager } from '../../util/DragManager';
 import { LinkFollower } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SettingsManager } from '../../util/SettingsManager';
+import { undoBatch } from '../../util/UndoManager';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DocumentView, DocumentViewInternal, OpenWhere } from '../nodes/DocumentView';
 import { LinkInfo } from '../nodes/LinkDocPreview';
 import './LinkMenuItem.scss';
-import { undoBatch } from '../../util/UndoManager';
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { DocumentType } from '../../documents/DocumentTypes';
 
 interface LinkMenuItemProps {
     groupType: string;
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index 5460a6daf..7e344dd7a 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -1,16 +1,16 @@
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { EditorView } from 'prosemirror-view';
-import { Doc } from '../../../fields/Doc';
+import * as React from 'react';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
+import { Doc } from '../../../fields/Doc';
 import { Transform } from '../../util/Transform';
 import { undoBatch } from '../../util/UndoManager';
+import { DefaultStyleProvider } from '../StyleProvider';
 import { OpenWhere } from '../nodes/DocumentView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { SearchBox } from '../search/SearchBox';
-import { DefaultStyleProvider } from '../StyleProvider';
 import './LinkPopup.scss';
-import * as React from 'react';
 
 interface LinkPopupProps {
     linkFrom?: () => Doc | undefined;
@@ -29,7 +29,7 @@ interface LinkPopupProps {
 @observer
 export class LinkPopup extends React.Component<LinkPopupProps> {
     @observable private linkURL: string = '';
-    @observable public view?: EditorView;
+    @observable public view?: EditorView = undefined;
 
     // TODO: should check for valid URL
     @undoBatch
diff --git a/src/client/views/linking/LinkRelationshipSearch.tsx b/src/client/views/linking/LinkRelationshipSearch.tsx
index 3e7f5be74..0902d53b2 100644
--- a/src/client/views/linking/LinkRelationshipSearch.tsx
+++ b/src/client/views/linking/LinkRelationshipSearch.tsx
@@ -1,6 +1,6 @@
 import { observer } from 'mobx-react';
-import './LinkEditor.scss';
 import * as React from 'react';
+import './LinkEditor.scss';
 
 interface link_relationshipSearchProps {
     results: string[] | undefined;
diff --git a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
index 72b63cf8f..bce2b296f 100644
--- a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
+++ b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
@@ -3,12 +3,12 @@ import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { InkTool } from '../../../../fields/InkField';
 import { SelectionManager } from '../../../util/SelectionManager';
+import { SnappingManager } from '../../../util/SnappingManager';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
-import { DocumentView, OpenWhereMod } from '../../nodes/DocumentView';
+import { OpenWhereMod } from '../../nodes/DocumentView';
 import { NewLightboxView } from '../NewLightboxView';
 import './ButtonMenu.scss';
 import { IButtonMenu } from './utils';
-import { SnappingManager } from '../../../util/SnappingManager';
 
 export const ButtonMenu = (props: IButtonMenu) => {
     return (
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
index 6980e31c1..3d159c3e3 100644
--- a/src/client/views/newlightbox/NewLightboxView.tsx
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -2,28 +2,28 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../../Utils';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { InkTool } from '../../../fields/InkField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { LinkManager } from '../../util/LinkManager';
 import { SelectionManager } from '../../util/SelectionManager';
+import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
-import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
-import { TabDocView } from '../collections/TabDocView';
 import { GestureOverlay } from '../GestureOverlay';
 import { LightboxView } from '../LightboxView';
-import { DocumentView, OpenWhere } from '../nodes/DocumentView';
 import { DefaultStyleProvider } from '../StyleProvider';
-import { IRecommendation } from './components';
+import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
+import { TabDocView } from '../collections/TabDocView';
+import { DocumentView, OpenWhere } from '../nodes/DocumentView';
 import { ExploreView } from './ExploreView';
-import { emptyBounds, IBounds } from './ExploreView/utils';
+import { IBounds, emptyBounds } from './ExploreView/utils';
 import { NewLightboxHeader } from './Header';
 import './NewLightboxView.scss';
 import { RecommendationList } from './RecommendationList';
-import { SnappingManager } from '../../util/SnappingManager';
+import { IRecommendation } from './components';
 
 enum LightboxStatus {
     RECOMMENDATIONS = 'recommendations',
@@ -50,15 +50,15 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
         return this._doc;
     }
     private static LightboxDocTemplate = () => NewLightboxView._layoutTemplate;
-    @observable private static _layoutTemplate: Opt<Doc>;
-    @observable private static _layoutTemplateString: Opt<string>;
-    @observable private static _doc: Opt<Doc>;
-    @observable private static _docTarget: Opt<Doc>;
+    @observable private static _layoutTemplate: Opt<Doc> = undefined;
+    @observable private static _layoutTemplateString: Opt<string> = undefined;
+    @observable private static _doc: Opt<Doc> = undefined;
+    @observable private static _docTarget: Opt<Doc> = undefined;
     @observable private static _docFilters: string[] = []; // filters
-    private static _savedState: Opt<LightboxSavedState>;
+    private static _savedState: Opt<LightboxSavedState> = undefined;
     private static _history: Opt<{ doc: Doc; target?: Doc }[]> = [];
     @observable private static _future: Opt<Doc[]> = [];
-    @observable private static _docView: Opt<DocumentView>;
+    @observable private static _docView: Opt<DocumentView> = undefined;
 
     // keywords
     @observable private static _keywords: string[] = [];
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 4c36d2fcb..12e8e1a69 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -44,8 +44,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
     crop: ((region: Doc | undefined, addCrop?: boolean) => Doc | undefined) | undefined;
-    @observable schemaDataVizChildren: any;
-    @observable _marqueeing: number[] | undefined;
+    @observable schemaDataVizChildren: any = undefined;
+    @observable _marqueeing: number[] | undefined = undefined;
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @computed get annotationLayer() {
         TraceMobx();
diff --git a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
index 3cb5125da..24023077f 100644
--- a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
+++ b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
@@ -1,15 +1,14 @@
-import * as React from 'react';
-import './SchemaCSVPopUp.scss';
+import { IconButton } from 'browndash-components';
 import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import { CgClose } from 'react-icons/cg';
+import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../Utils';
 import { Doc } from '../../../../fields/Doc';
-import { Button, IconButton, Type } from 'browndash-components';
 import { StrCast } from '../../../../fields/Types';
-import { MarqueeView } from '../../collections/collectionFreeForm/MarqueeView';
-import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../Utils';
 import { DragManager } from '../../../util/DragManager';
 import { DocumentView } from '../DocumentView';
-import { CgClose } from 'react-icons/cg';
+import './SchemaCSVPopUp.scss';
 
 interface SchemaCSVPopUpProps {}
 
@@ -56,13 +55,11 @@ export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
             <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
                 {this.heading('Schema Table as Data Visualization Doc')}
                 <div className="image-content-wrapper">
-                        <div className="img-wrapper">
-                            <div className="img-container" onPointerDown={e => this.drag(e)}>
-                                <img
-                                    width={150} height={150} src={"/assets/dataVizBox.png"}
-                                />
-                            </div>
+                    <div className="img-wrapper">
+                        <div className="img-container" onPointerDown={e => this.drag(e)}>
+                            <img width={150} height={150} src={'/assets/dataVizBox.png'} />
                         </div>
+                    </div>
                 </div>
             </div>
         );
@@ -88,9 +85,10 @@ export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
                     return embedding;
                 };
                 if (this.view && sourceAnchorCreator && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
-                    DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], 
-                        new DragManager.AnchorAnnoDragData(this.view, sourceAnchorCreator, targetCreator), downX, downY, {
-                        dragComplete: e => {this.setVisible(false);},
+                    DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this.view, sourceAnchorCreator, targetCreator), downX, downY, {
+                        dragComplete: e => {
+                            this.setVisible(false);
+                        },
                     });
                     return true;
                 }
@@ -108,4 +106,4 @@ export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
             </div>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index 227c993c7..9e9a43b34 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -1,7 +1,7 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { ColorPicker, EditableText, IconButton, Size, Type } from 'browndash-components';
 import * as d3 from 'd3';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { FaFillDrip } from 'react-icons/fa';
@@ -11,10 +11,10 @@ import { listSpec } from '../../../../../fields/Schema';
 import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
 import { Docs } from '../../../../documents/Documents';
 import { undoable } from '../../../../util/UndoManager';
+import { ObservableReactComponent } from '../../../ObservableReactComponent';
 import { PinProps, PresBox } from '../../trails';
 import { scaleCreatorNumerical, yAxisCreator } from '../utils/D3Utils';
 import './Chart.scss';
-import { ObservableReactComponent } from '../../../ObservableReactComponent';
 
 export interface HistogramProps {
     Document: Doc;
@@ -461,7 +461,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
 
         if (this._histogramData.length > 0 || !this.parentViz) {
             return this._props.axes.length >= 1 ? (
-                <div className="chart-container" style={{width: this._props.width+this._props.margin.right}}>
+                <div className="chart-container" style={{ width: this._props.width + this._props.margin.right }}>
                     <div className="graph-title">
                         <EditableText
                             val={StrCast(this._props.layoutDoc[titleAccessor])}
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index 4b0df0457..a69f083d1 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -1,6 +1,6 @@
 import { Button, EditableText, Size } from 'browndash-components';
 import * as d3 from 'd3';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, NumListCast, StrListCast } from '../../../../../fields/Doc';
@@ -10,11 +10,11 @@ import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
 import { Docs } from '../../../../documents/Documents';
 import { DocumentManager } from '../../../../util/DocumentManager';
 import { undoable } from '../../../../util/UndoManager';
+import { ObservableReactComponent } from '../../../ObservableReactComponent';
 import { PinProps, PresBox } from '../../trails';
 import { DataVizBox } from '../DataVizBox';
 import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils';
 import './Chart.scss';
-import { ObservableReactComponent } from '../../../ObservableReactComponent';
 
 export interface DataPoint {
     x: number;
@@ -48,12 +48,11 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
     private _lineChartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
     @observable _currSelected: SelectedDataPoint | undefined = undefined;
     // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates
-    constructor(props:any) {
+    constructor(props: any) {
         super(props);
         makeObservable(this);
     }
 
-
     @computed get _tableDataIds() {
         return !this.parentViz ? this._props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
     }
@@ -359,7 +358,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
         const selectedPt = this._currSelected ? `{ ${this._props.axes[0]}: ${this._currSelected.x} ${this._props.axes[1]}: ${this._currSelected.y} }` : 'none';
         if (this._lineChartData.length > 0 || !this.parentViz || this.parentViz.length == 0) {
             return this._props.axes.length >= 2 && /\d/.test(this._props.records[0][this._props.axes[0]]) && /\d/.test(this._props.records[0][this._props.axes[1]]) ? (
-                <div className="chart-container" style={{width: this.props.width+this.props.margin.right}}>
+                <div className="chart-container" style={{ width: this.props.width + this.props.margin.right }}>
                     <div className="graph-title">
                         <EditableText
                             val={StrCast(this._props.layoutDoc[titleAccessor])}
@@ -373,16 +372,17 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
                         />
                     </div>
                     <div ref={this._lineChartRef} />
-                    {selectedPt != 'none' ? 
-                        <div className={'selected-data'}> 
+                    {selectedPt != 'none' ? (
+                        <div className={'selected-data'}>
                             {`Selected: ${selectedPt}`}
-                            <Button onClick={e=>{
-                                console.log("test plzz")
-                                this.props.vizBox.sidebarBtnDown;
-                                this.props.vizBox.sidebarAddDocument;}
-                                }></Button>
-                        </div> 
-                        : null}
+                            <Button
+                                onClick={e => {
+                                    console.log('test plzz');
+                                    this.props.vizBox.sidebarBtnDown;
+                                    this.props.vizBox.sidebarAddDocument;
+                                }}></Button>
+                        </div>
+                    ) : null}
                 </div>
             ) : (
                 <span className="chart-container"> {'first use table view to select two numerical axes to plot'}</span>
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index e644870da..e67556cd0 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -1,7 +1,7 @@
 import { Checkbox } from '@mui/material';
 import { ColorPicker, EditableText, Size, Type } from 'browndash-components';
 import * as d3 from 'd3';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { FaFillDrip } from 'react-icons/fa';
@@ -11,9 +11,9 @@ import { listSpec } from '../../../../../fields/Schema';
 import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
 import { Docs } from '../../../../documents/Documents';
 import { undoable } from '../../../../util/UndoManager';
+import { ObservableReactComponent } from '../../../ObservableReactComponent';
 import { PinProps, PresBox } from '../../trails';
 import './Chart.scss';
-import { ObservableReactComponent } from '../../../ObservableReactComponent';
 
 export interface PieChartProps {
     Document: Doc;
@@ -356,7 +356,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
 
         if (this._pieChartData.length > 0 || !this.parentViz) {
             return this._props.axes.length >= 1 ? (
-                <div className="chart-container" style={{width: this._props.width+this._props.margin.right}}>
+                <div className="chart-container" style={{ width: this._props.width + this._props.margin.right }}>
                     <div className="graph-title">
                         <EditableText
                             val={StrCast(this._props.layoutDoc[titleAccessor])}
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 4f60a156b..f127fecf3 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -1,17 +1,17 @@
 import { Button, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction, trace } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../../Utils';
 import { Doc, Field, NumListCast } from '../../../../../fields/Doc';
 import { List } from '../../../../../fields/List';
 import { listSpec } from '../../../../../fields/Schema';
 import { Cast, DocCast } from '../../../../../fields/Types';
-import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../../Utils';
 import { DragManager } from '../../../../util/DragManager';
+import { ObservableReactComponent } from '../../../ObservableReactComponent';
 import { DocumentView } from '../../DocumentView';
 import { DataVizView } from '../DataVizBox';
 import './Chart.scss';
-import { ObservableReactComponent } from '../../../ObservableReactComponent';
 const {  default: { DATA_VIZ_TABLE_ROW_HEIGHT } } = require('../../../global/globalCssVariables.module.scss'); // prettier-ignore
 interface TableBoxProps {
     Document: Doc;
@@ -87,7 +87,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
         return (this.viewScale * this._tableHeight) / this._tableDataIds.length;
     }
     @computed get startID() {
-        return this.rowHeight ? Math.max(Math.floor(this._scrollTop / this.rowHeight)-1, 0) : 0;
+        return this.rowHeight ? Math.max(Math.floor(this._scrollTop / this.rowHeight) - 1, 0) : 0;
     }
     @computed get endID() {
         console.log('start = ' + this.startID + '  container = ' + this._tableContainerHeight + ' scale = ' + this.viewScale + ' row = ' + this.rowHeight);
@@ -169,7 +169,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
             return (
                 <div
                     className="tableBox"
-                    style={{width: this.props.width+this.props.margin.right}}
+                    style={{ width: this.props.width + this.props.margin.right }}
                     tabIndex={0}
                     onKeyDown={e => {
                         if (this._props.layoutDoc && e.key === 'a' && (e.ctrlKey || e.metaKey)) {
@@ -226,7 +226,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
                             </thead>
                             <tbody>
                                 {this._tableDataIds
-                                    .filter((rowId, i) => this.startID-2 <= i && i <= this.endID+2)
+                                    .filter((rowId, i) => this.startID - 2 <= i && i <= this.endID + 2)
                                     ?.map(rowId => (
                                         <tr
                                             key={rowId}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index fa472312e..4cc7fe4c8 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,6 +1,7 @@
-import { computed, makeObservable, observable } from 'mobx';
+import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import JsxParser from 'react-jsx-parser';
 import * as XRegExp from 'xregexp';
 import { OmitKeys, Without, emptyPath } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
@@ -10,11 +11,11 @@ import { Cast, StrCast } from '../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
 import { InkingStroke } from '../InkingStroke';
 import { ObservableReactComponent } from '../ObservableReactComponent';
+import { CollectionCalendarView } from '../collections/CollectionCalendarView';
 import { CollectionDockingView } from '../collections/CollectionDockingView';
 import { CollectionView } from '../collections/CollectionView';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { CollectionSchemaView } from '../collections/collectionSchema/CollectionSchemaView';
-import { CollectionCalendarView } from '../collections/CollectionCalendarView';
 import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox';
 import { PresElementBox } from '../nodes/trails/PresElementBox';
 import { SearchBox } from '../search/SearchBox';
@@ -47,7 +48,6 @@ import { WebBox } from './WebBox';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { ImportElementBox } from './importBox/ImportElementBox';
 import { PresBox } from './trails/PresBox';
-import JsxParser from 'react-jsx-parser';
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
 export interface JsxBindings {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index cc7fca8d2..f8f4b94a2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,10 +1,11 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { Dropdown, DropdownType, Type } from 'browndash-components';
+import { Howl } from 'howler';
 import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
-import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal';
 import * as React from 'react';
+import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal';
 import { Utils, emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../Utils';
 import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
 import { AclPrivate, Animation, AudioPlay, DocViews } from '../../../fields/DocSymbols';
@@ -53,7 +54,6 @@ import { LinkAnchorBox } from './LinkAnchorBox';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { PresEffect, PresEffectDirection } from './trails';
 import { PinProps, PresBox } from './trails/PresBox';
-import { Howl } from 'howler';
 
 interface Window {
     MediaRecorder: MediaRecorder;
@@ -138,7 +138,6 @@ export interface DocComponentView {
     dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void;
     incrementalRendering?: () => void;
     infoUI?: () => JSX.Element | null;
-    getCenter?: (xf: Transform) => { X: number; Y: number };
     screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>;
     ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
     ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
@@ -1516,7 +1515,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
             const docuBox = this.docView.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
             if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
         }
-        return { left, top, right, bottom, center: this.ComponentView?.getCenter?.(xf) };
+        return { left, top, right, bottom };
     };
 
     public iconify(finished?: () => void, animateTime?: number) {
diff --git a/src/client/views/nodes/FaceRectangle.tsx b/src/client/views/nodes/FaceRectangle.tsx
index 8d03bf57a..46bc6eb03 100644
--- a/src/client/views/nodes/FaceRectangle.tsx
+++ b/src/client/views/nodes/FaceRectangle.tsx
@@ -1,6 +1,6 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
 import { observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
 import { RectangleTemplate } from './FaceRectangles';
 
 @observer
diff --git a/src/client/views/nodes/FaceRectangles.tsx b/src/client/views/nodes/FaceRectangles.tsx
index 26e720c0d..ade4225d9 100644
--- a/src/client/views/nodes/FaceRectangles.tsx
+++ b/src/client/views/nodes/FaceRectangles.tsx
@@ -1,8 +1,8 @@
+import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../../fields/Doc';
-import { Cast, NumCast } from '../../../fields/Types';
-import { observer } from 'mobx-react';
 import { Id } from '../../../fields/FieldSymbols';
+import { Cast, NumCast } from '../../../fields/Types';
 import FaceRectangle from './FaceRectangle';
 
 interface FaceRectanglesProps {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index f4c5167a5..008f10f26 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,6 +1,6 @@
-import * as React from 'react';
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { DateField } from '../../../fields/DateField';
 import { Doc, Field, FieldResult, Opt } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index fd8d8ef56..7bc9d3f85 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,7 +1,6 @@
 import { Tooltip } from '@mui/material';
 import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import { ObservableGroupMap } from 'mobx-utils';
 import * as React from 'react';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../Utils';
 import { Doc, Field } from '../../../fields/Doc';
@@ -11,13 +10,13 @@ import { Transform } from '../../util/Transform';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { EditableView } from '../EditableView';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DefaultStyleProvider } from '../StyleProvider';
 import { OpenWhere } from './DocumentView';
 import { FieldViewProps } from './FieldView';
 import { KeyValueBox } from './KeyValueBox';
 import './KeyValueBox.scss';
 import './KeyValuePair.scss';
-import { ObservableReactComponent } from '../ObservableReactComponent';
 
 // Represents one row in a key value plane
 
@@ -35,12 +34,11 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
     @observable private isPointerOver = false;
     @observable public isChecked = false;
     private checkbox = React.createRef<HTMLInputElement>();
-    constructor(props:any) {
+    constructor(props: any) {
         super(props);
         makeObservable(this);
     }
 
-
     @action
     handleCheck = (e: React.ChangeEvent<HTMLInputElement>) => {
         this.isChecked = e.currentTarget.checked;
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 32300d60a..8ad0b7dde 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -1,6 +1,6 @@
-import * as React from 'react';
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc } from '../../../fields/Doc';
 import { LinkManager } from '../../util/LinkManager';
 import './LinkDescriptionPopup.scss';
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index d0a9f10b4..ea23ecbea 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -2,13 +2,14 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import wiki from 'wikijs';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
 import { DocServer } from '../../DocServer';
-import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
+import { Docs } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
 import { LinkFollower } from '../../util/LinkFollower';
@@ -16,10 +17,9 @@ import { LinkManager } from '../../util/LinkManager';
 import { SearchUtil } from '../../util/SearchUtil';
 import { SettingsManager } from '../../util/SettingsManager';
 import { Transform } from '../../util/Transform';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView';
 import './LinkDocPreview.scss';
-import * as React from 'react';
-import { ObservableReactComponent } from '../ObservableReactComponent';
 
 export class LinkInfo {
     private static _instance: Opt<LinkInfo>;
diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx
index 27d73a585..adccc9db6 100644
--- a/src/client/views/nodes/LoadingBox.tsx
+++ b/src/client/views/nodes/LoadingBox.tsx
@@ -1,4 +1,4 @@
-import { action, observable, runInAction } from 'mobx';
+import { observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import ReactLoading from 'react-loading';
diff --git a/src/client/views/nodes/MapBox/AnimationUtility.ts b/src/client/views/nodes/MapBox/AnimationUtility.ts
index 42dfa59b7..35153f439 100644
--- a/src/client/views/nodes/MapBox/AnimationUtility.ts
+++ b/src/client/views/nodes/MapBox/AnimationUtility.ts
@@ -1,13 +1,10 @@
-import mapboxgl from 'mapbox-gl';
-import { MercatorCoordinate } from 'mapbox-gl';
-import { MapRef } from 'react-map-gl';
-import * as React from 'react';
-import * as d3 from 'd3';
 import * as turf from '@turf/turf';
 import { Position } from '@turf/turf';
-import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from 'geojson';
-import { observer } from 'mobx-react';
-import { action, computed, observable, runInAction, makeObservable } from 'mobx';
+import * as d3 from 'd3';
+import { Feature, GeoJsonProperties, Geometry } from 'geojson';
+import mapboxgl, { MercatorCoordinate } from 'mapbox-gl';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
+import { MapRef } from 'react-map-gl';
 
 export enum AnimationStatus {
     START = 'start',
diff --git a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
index f9607becf..7e99795b5 100644
--- a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
@@ -1,15 +1,15 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { IconLookup, faAdd, faCalendarDays, faRoute } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { IconButton } from 'browndash-components';
 import { IReactionDisposer, ObservableMap, reaction } from 'mobx';
-import { Doc, Opt } from '../../../../fields/Doc';
+import { observer } from 'mobx-react';
+import * as React from 'react';
 import { returnFalse, unimplementedFunction } from '../../../../Utils';
+import { Doc, Opt } from '../../../../fields/Doc';
 import { NumCast, StrCast } from '../../../../fields/Types';
 import { SelectionManager } from '../../../util/SelectionManager';
-import { IconButton } from 'browndash-components';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { SettingsManager } from '../../../util/SettingsManager';
-import { IconLookup, faAdd, faCalendarDays, faRoute } from '@fortawesome/free-solid-svg-icons';
+import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 
 @observer
 export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index 1b1b74e7c..08bea5d9d 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -1,26 +1,23 @@
+import { IconLookup, faAdd, faArrowDown, faArrowLeft, faArrowsRotate, faBicycle, faCalendarDays, faCar, faDiamondTurnRight, faEdit, faPersonWalking, faRoute } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import * as React from 'react';
+import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material';
+import { IconButton } from 'browndash-components';
+import { Position } from 'geojson';
 import { IReactionDisposer, ObservableMap, action, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, NumListCast, Opt } from '../../../../fields/Doc';
+import * as React from 'react';
+import { CirclePicker, ColorResult } from 'react-color';
 import { returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../../Utils';
+import { Doc, Opt } from '../../../../fields/Doc';
+import { NumCast, StrCast } from '../../../../fields/Types';
+import { CalendarManager } from '../../../util/CalendarManager';
 import { SelectionManager } from '../../../util/SelectionManager';
-import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
-// import { GPTPopup, GPTPopupMode } from './../../GPTPopup/GPTPopup';
-import { Button, IconButton } from 'browndash-components';
 import { SettingsManager } from '../../../util/SettingsManager';
+import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 import './MapAnchorMenu.scss';
-import { NumCast, StrCast } from '../../../../fields/Types';
-import { IconLookup, faDiamondTurnRight, faCalendarDays, faEdit, faAdd, faRoute, faArrowLeft, faLocationDot, faArrowDown, faCar, faBicycle, faPersonWalking, faUpload, faArrowsRotate } from '@fortawesome/free-solid-svg-icons';
-import { DirectionsAnchorMenu } from './DirectionsAnchorMenu';
-import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material';
 import { MapboxApiUtility, TransportationType } from './MapboxApiUtility';
-import { MapBox } from './MapBox';
-import { List } from '../../../../fields/List';
 import { MarkerIcons } from './MarkerIcons';
-import { CirclePicker, ColorResult } from 'react-color';
-import { Position } from 'geojson';
-import { CalendarManager } from '../../../util/CalendarManager';
+// import { GPTPopup, GPTPopupMode } from './../../GPTPopup/GPTPopup';
 
 type MapAnchorMenuType = 'standard' | 'routeCreation' | 'calendar' | 'customize' | 'route';
 
@@ -52,7 +49,6 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     public UpdateMarkerColor: (color: string) => void = unimplementedFunction;
     public UpdateMarkerIcon: (iconKey: string) => void = unimplementedFunction;
 
-
     public Hide: () => void = unimplementedFunction;
 
     public OpenAnimationPanel: (routeDoc: Doc | undefined) => void = unimplementedFunction;
@@ -74,22 +70,21 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     private title: string | undefined = undefined;
 
     public setPinDoc(pinDoc: Doc | undefined) {
-        if (pinDoc){
+        if (pinDoc) {
             this.pinDoc = pinDoc;
             this.title = StrCast(pinDoc.title ? pinDoc.title : `${NumCast(pinDoc.longitude)}, ${NumCast(pinDoc.latitude)}`);
         }
-        
     }
 
     public setRouteDoc(routeDoc: Doc | undefined) {
-        if (routeDoc){
+        if (routeDoc) {
             this.routeDoc = routeDoc;
             this.title = StrCast(routeDoc.title ?? 'Map route');
         }
     }
 
     @action
-    public Reset(){
+    public Reset() {
         this.destinationSelected = false;
         this.currentRouteInfoMap = undefined;
         this.destinationFeatures = [];
@@ -296,34 +291,23 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         return undefined;
     };
 
-    getDirectionsButton: JSX.Element = (
-        <IconButton 
-            tooltip="Get directions" 
-            onPointerDown={this.DirectionsClick}
-            icon={<FontAwesomeIcon icon={faDiamondTurnRight as IconLookup} />} 
-            color={SettingsManager.userColor} />
-    )
+    getDirectionsButton: JSX.Element = (<IconButton tooltip="Get directions" onPointerDown={this.DirectionsClick} icon={<FontAwesomeIcon icon={faDiamondTurnRight as IconLookup} />} color={SettingsManager.userColor} />);
 
     getAddToCalendarButton = (docType: string): JSX.Element => {
         return (
-            <IconButton 
-            tooltip="Add to calendar" 
-            onPointerDown={() => {
-                CalendarManager.Instance.open(undefined, docType === 'pin' ? this.pinDoc : this.routeDoc)
-            }} 
-            icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} 
-            color={SettingsManager.userColor} 
+            <IconButton
+                tooltip="Add to calendar"
+                onPointerDown={() => {
+                    CalendarManager.Instance.open(undefined, docType === 'pin' ? this.pinDoc : this.routeDoc);
+                }}
+                icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />}
+                color={SettingsManager.userColor}
             />
-        )
-       
-    }
+        );
+    };
     addToCalendarButton: JSX.Element = (
-        <IconButton 
-            tooltip="Add to calendar" 
-            onPointerDown={() => CalendarManager.Instance.open(undefined, this.pinDoc)} 
-            icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} 
-            color={SettingsManager.userColor} />
-    )
+        <IconButton tooltip="Add to calendar" onPointerDown={() => CalendarManager.Instance.open(undefined, this.pinDoc)} icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
+    );
 
     getLinkNoteToDocButton = (docType: string): JSX.Element => {
         return (
@@ -335,8 +319,8 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                     color={SettingsManager.userColor}
                 />
             </div>
-        )
-    }
+        );
+    };
 
     linkNoteToPinOrRoutenButton: JSX.Element = (
         <div ref={this._commentRef}>
@@ -347,16 +331,9 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                 color={SettingsManager.userColor}
             />
         </div>
-    )
-
-    customizePinButton: JSX.Element = (
-        <IconButton 
-            tooltip="Customize pin" 
-            onPointerDown={this.CustomizeClick} 
-            icon={<FontAwesomeIcon icon={faEdit as IconLookup} />} 
-            color={SettingsManager.userColor} 
-        />
-    )
+    );
+
+    customizePinButton: JSX.Element = (<IconButton tooltip="Customize pin" onPointerDown={this.CustomizeClick} icon={<FontAwesomeIcon icon={faEdit as IconLookup} />} color={SettingsManager.userColor} />);
 
     centerOnPinButton: JSX.Element = (
         <IconButton
@@ -365,7 +342,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             icon={<FontAwesomeIcon icon="compress-arrows-alt" />}
             color={SettingsManager.userColor}
         />
-    )
+    );
 
     backButton: JSX.Element = (
         <IconButton
@@ -374,7 +351,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             icon={<FontAwesomeIcon icon={faArrowLeft as IconLookup} />}
             color={SettingsManager.userColor}
         />
-    )
+    );
 
     addRouteButton: JSX.Element = (
         <IconButton
@@ -383,7 +360,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             icon={<FontAwesomeIcon icon={faAdd as IconLookup} />}
             color={SettingsManager.userColor}
         />
-    )
+    );
 
     getDeleteButton = (type: string) => {
         return (
@@ -393,17 +370,10 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                 icon={<FontAwesomeIcon icon="trash-alt" />}
                 color={SettingsManager.userColor}
             />
-        )
-    }
+        );
+    };
 
-    animateRouteButton: JSX.Element = (
-        <IconButton 
-            tooltip="Animate route" 
-            onPointerDown={() => this.OpenAnimationPanel(this.routeDoc)} 
-            icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} 
-            color={SettingsManager.userColor} 
-        />
-    )
+    animateRouteButton: JSX.Element = (<IconButton tooltip="Animate route" onPointerDown={() => this.OpenAnimationPanel(this.routeDoc)} icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />);
 
     revertToOriginalMarkerButton = (
         <IconButton
@@ -412,7 +382,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             icon={<FontAwesomeIcon icon={faArrowsRotate as IconLookup} />}
             color={SettingsManager.userColor}
         />
-    )
+    );
 
     render() {
         const buttons = (
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 7db139d74..ffd52fb0e 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1,8 +1,17 @@
+import { IconLookup, faCircleXmark, faGear, faPause, faPlay, faRotate } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Checkbox, FormControlLabel, TextField } from '@mui/material';
+import * as turf from '@turf/turf';
 import { IconButton, Size, Type } from 'browndash-components';
+import * as d3 from 'd3';
+import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, Position } from 'geojson';
+import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent } from 'mapbox-gl';
 import { IReactionDisposer, ObservableMap, action, autorun, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { CirclePicker, ColorResult } from 'react-color';
+import { Layer, MapProvider, MapRef, Map as MapboxMap, Marker, Source, ViewState, ViewStateChangeEvent } from 'react-map-gl';
+import { MarkerEvent } from 'react-map-gl/dist/esm/types';
 import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../Utils';
 import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
 import { DocCss, Highlight } from '../../../../fields/DocSymbols';
@@ -22,22 +31,13 @@ import { DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../trails';
-import { MapAnchorMenu } from './MapAnchorMenu';
-import { ControlPosition, Layer, MapProvider, MapRef, Map as MapboxMap, Marker, MarkerProps, Source, ViewState, ViewStateChangeEvent } from 'react-map-gl';
-import './MapBox.scss';
-// import { GeocoderControl } from './GeocoderControl';
-import { IconLookup, faCircleXmark, faGear, faPause, faPlay, faRotate } from '@fortawesome/free-solid-svg-icons';
-import { Checkbox, FormControlLabel, TextField } from '@mui/material';
-import * as turf from '@turf/turf';
-import * as d3 from 'd3';
-import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, Position } from 'geojson';
-import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent } from 'mapbox-gl';
-import { CirclePicker, ColorResult } from 'react-color';
-import { MarkerEvent } from 'react-map-gl/dist/esm/types';
 import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
 import { AnimationSpeed, AnimationStatus, AnimationUtility } from './AnimationUtility';
+import { MapAnchorMenu } from './MapAnchorMenu';
+import './MapBox.scss';
 import { MapboxApiUtility, TransportationType } from './MapboxApiUtility';
 import { MarkerIcons } from './MarkerIcons';
+// import { GeocoderControl } from './GeocoderControl';
 
 // amongus
 /**
@@ -480,7 +480,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
 
     // The pin that is selected
-    @observable selectedPinOrRoute: Doc | undefined;
+    @observable selectedPinOrRoute: Doc | undefined = undefined;
 
     @action
     deselectPinOrRoute = () => {
@@ -650,7 +650,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     deleteSelectedPinOrRoute = undoable(() => {
-        console.log('deleting')
+        console.log('deleting');
         if (this.selectedPinOrRoute) {
             // Removes filter
             Doc.setDocFilter(this.Document, 'latitude', this.selectedPinOrRoute.latitude, 'remove');
@@ -677,15 +677,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             this.temporaryRouteSource = {
                 type: 'FeatureCollection',
                 features: [],
-            }
-        })
-
+            };
+        });
 
         document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
     };
 
-   
-
     @action
     centerOnSelectedPin = () => {
         if (this.selectedPinOrRoute) {
@@ -882,8 +879,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             }
             this.temporaryRouteSource = {
                 type: 'FeatureCollection',
-                features: []
-            }
+                features: [],
+            };
             MapAnchorMenu.Instance.fadeOut(true);
             return mapRoute;
         }
@@ -994,8 +991,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     };
 
-
-
     /**
      * Makes a reverse geocoding API call to retrieve features corresponding to a map click (based on longitude
      * and latitude). Sets the search results accordingly.
@@ -1388,7 +1383,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     };
 
-
     getRouteAnimationOptions = (): JSX.Element => {
         return (
             <>
@@ -1423,20 +1417,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 <>
                     <div className="animation-suboptions">
                         <div>|</div>
-                        <FormControlLabel
-                            className="first-person-label"
-                            label="1st person animation:"
-                            labelPlacement="start"
-                            control={<Checkbox color="success" checked={this.isStreetViewAnimation} onChange={this.toggleIsStreetViewAnimation} />}
-                        />
+                        <FormControlLabel className="first-person-label" label="1st person animation:" labelPlacement="start" control={<Checkbox color="success" checked={this.isStreetViewAnimation} onChange={this.toggleIsStreetViewAnimation} />} />
                         <div id="divider">|</div>
                         <IconButton tooltip={this.animationSpeedTooltipText} onPointerDown={this.updateAnimationSpeed} icon={this.animationSpeedIcon} size={Size.MEDIUM} />
                         <div id="divider">|</div>
-                        <div style={{display: 'flex', alignItems: 'center'}}>
+                        <div style={{ display: 'flex', alignItems: 'center' }}>
                             <div>Select Line Color: </div>
-                            <CirclePicker circleSize={12} circleSpacing={5} width="100%" colors={['#ffff00', '#03a9f4', '#ff0000', '#ff5722', '#000000', '#673ab7']} onChange={(color:any) => this.setAnimationLineColor(color)} />
+                            <CirclePicker circleSize={12} circleSpacing={5} width="100%" colors={['#ffff00', '#03a9f4', '#ff0000', '#ff5722', '#000000', '#673ab7']} onChange={(color: any) => this.setAnimationLineColor(color)} />
                         </div>
-                        
                     </div>
                 </>
             </>
@@ -1478,7 +1466,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     onBearingChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         const bearing = parseInt(e.target.value);
         if (!isNaN(bearing) && this._mapRef.current) {
-            console.log('bearing change')
+            console.log('bearing change');
             const fixedBearing = Math.max(0, Math.min(360, bearing));
             this._mapRef.current.setBearing(fixedBearing);
             this.dataDoc.map_bearing = fixedBearing;
@@ -1489,7 +1477,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     onPitchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         const pitch = parseInt(e.target.value);
         if (!isNaN(pitch) && this._mapRef.current) {
-            console.log('pitch change')
+            console.log('pitch change');
             const fixedPitch = Math.max(0, Math.min(85, pitch));
             this._mapRef.current.setPitch(fixedPitch);
             this.dataDoc.map_pitch = fixedPitch;
@@ -1648,7 +1636,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         <MapboxMap
                             ref={this._mapRef}
                             mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
-                            viewState={(this.isAnimating || this.routeToAnimate) ? undefined : { ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width), height: NumCast(this.layoutDoc._height) }}
+                            viewState={this.isAnimating || this.routeToAnimate ? undefined : { ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width), height: NumCast(this.layoutDoc._height) }}
                             mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'}
                             style={{
                                 position: 'absolute',
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index fdd8285d5..1dbbbb633 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -87,7 +87,7 @@
 //     private _dropDisposer?: DragManager.DragDropDisposer;
 //     private _disposers: { [name: string]: IReactionDisposer } = {};
 //     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
-//     @observable private _overlayAnnoInfo: Opt<Doc>;
+//     @observable private _overlayAnnoInfo: Opt<Doc> = undefined;
 //     showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
 //     public static LayoutString(fieldKey: string) {
 //         return FieldView.LayoutString(MapBox2, fieldKey);
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index 6a14427c0..70037f29c 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -1,33 +1,31 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import BingMapsReact from 'bingmaps-react';
 import { Button, EditableText, IconButton, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, computed, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { MapProvider, Map as MapboxMap } from 'react-map-gl';
+import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils';
 import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
 import { DocCss, Highlight } from '../../../../fields/DocSymbols';
-import { Id } from '../../../../fields/FieldSymbols';
 import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
-import { Docs, DocUtils } from '../../../documents/Documents';
 import { DocumentType } from '../../../documents/DocumentTypes';
+import { DocUtils, Docs } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
 import { LinkManager } from '../../../util/LinkManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
-import { undoable, UndoManager } from '../../../util/UndoManager';
-import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
+import { UndoManager, undoable } from '../../../util/UndoManager';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
-import { Colors } from '../../global/globalEnums';
 import { SidebarAnnos } from '../../SidebarAnnos';
+import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
+import { Colors } from '../../global/globalEnums';
 import { DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
+import { MapAnchorMenu } from '../MapBox/MapAnchorMenu';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../trails';
 import './MapBox.scss';
-import { MapAnchorMenu } from '../MapBox/MapAnchorMenu';
-import { MapProvider, Map as MapboxMap } from 'react-map-gl';
 
 // amongus
 /**
@@ -351,7 +349,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
     }, 'createpin');
 
     // The pin that is selected
-    @observable selectedPin: Doc | undefined;
+    @observable selectedPin: Doc | undefined = undefined;
 
     @action
     deselectPin = () => {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 733febd2d..7f1d6b049 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -48,7 +48,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     private _sidebarRef = React.createRef<SidebarAnnos>();
 
     @observable private _searching: boolean = false;
-    @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>;
+    @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy> = undefined;
     @observable private _pageControls = false;
 
     @computed get pdfUrl() {
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
index e75b1ab6f..135db64e0 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
@@ -6,8 +6,9 @@ import QuestionMarkIcon from '@mui/icons-material/QuestionMark';
 import ReplayIcon from '@mui/icons-material/Replay';
 import { Box, Button, Checkbox, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, FormControl, FormControlLabel, FormGroup, IconButton, LinearProgress, Stack } from '@mui/material';
 import Typography from '@mui/material/Typography';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { IReactionDisposer, action, computed, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { NumListCast } from '../../../../fields/Doc';
 import { List } from '../../../../fields/List';
 import { BoolCast, NumCast, StrCast } from '../../../../fields/Types';
@@ -19,7 +20,6 @@ import questions from './PhysicsSimulationQuestions.json';
 import tutorials from './PhysicsSimulationTutorial.json';
 import Wall from './PhysicsSimulationWall';
 import Weight from './PhysicsSimulationWeight';
-import * as React from 'react';
 
 interface IWallProps {
     length: number;
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
index f5077a07e..3b232ddd0 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
@@ -1,7 +1,7 @@
 import { computed, IReactionDisposer, makeObservable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import './PhysicsSimulationBox.scss';
 import * as React from 'react';
+import './PhysicsSimulationBox.scss';
 
 interface IWallProps {
     length: number;
diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx
index 061a46f03..3b2fc033d 100644
--- a/src/client/views/nodes/RadialMenu.tsx
+++ b/src/client/views/nodes/RadialMenu.tsx
@@ -1,6 +1,6 @@
-import * as React from 'react';
 import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import './RadialMenu.scss';
 import { RadialMenuItem, RadialMenuProps } from './RadialMenuItem';
 
diff --git a/src/client/views/nodes/RadialMenuItem.tsx b/src/client/views/nodes/RadialMenuItem.tsx
index c931202f1..10a90befd 100644
--- a/src/client/views/nodes/RadialMenuItem.tsx
+++ b/src/client/views/nodes/RadialMenuItem.tsx
@@ -1,7 +1,7 @@
-import * as React from 'react';
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { UndoManager } from '../../util/UndoManager';
 
 export interface RadialMenuProps {
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index f01642236..658cfb1ca 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -8,15 +8,15 @@ import { List } from '../../../../fields/List';
 import { BoolCast, DocCast } from '../../../../fields/Types';
 import { VideoField } from '../../../../fields/URLField';
 import { Upload } from '../../../../server/SharedMediaTypes';
-import { Docs } from '../../../documents/Documents';
 import { DocumentType } from '../../../documents/DocumentTypes';
+import { Docs } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
 import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
 import { Presentation } from '../../../util/TrackMovements';
 import { undoBatch } from '../../../util/UndoManager';
-import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
+import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
 import { media_state } from '../AudioBox';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { VideoBox } from '../VideoBox';
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index d5d31b407..79ed69cdd 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -1,5 +1,5 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import * as React from 'react';
 // import { Canvas } from '@react-three/fiber';
 import { computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 7e7eaee45..8e506ec64 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -2,25 +2,23 @@ let ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { returnAlways, returnEmptyString } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { returnAlways, returnEmptyString, returnTrue } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
-import { InteractionUtils } from '../../util/InteractionUtils';
+import { ScriptManager } from '../../util/ScriptManager';
 import { CompileScript, ScriptParam } from '../../util/Scripting';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { ScriptManager } from '../../util/ScriptManager';
 import { ContextMenu } from '../ContextMenu';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import { EditableView } from '../EditableView';
-import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import { OverlayView } from '../OverlayView';
+import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import { DocumentIconContainer } from './DocumentIcon';
-import { DocFocusOptions, DocumentView } from './DocumentView';
 import './ScriptingBox.scss';
 const _global = (window /* browser */ || global) /* node */ as any;
 
diff --git a/src/client/views/nodes/TaskCompletedBox.tsx b/src/client/views/nodes/TaskCompletedBox.tsx
index 9aab8c5a9..c9e15d314 100644
--- a/src/client/views/nodes/TaskCompletedBox.tsx
+++ b/src/client/views/nodes/TaskCompletedBox.tsx
@@ -1,15 +1,15 @@
-import * as React from 'react';
+import { Fade } from '@mui/material';
+import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import './TaskCompletedBox.scss';
-import { observable, action } from 'mobx';
-import { Fade } from '@mui/material';
 
 @observer
 export class TaskCompletionBox extends React.Component<{}> {
     @observable public static taskCompleted: boolean = false;
     @observable public static popupX: number = 500;
     @observable public static popupY: number = 150;
-    @observable public static textDisplayed: string;
+    @observable public static textDisplayed: string = '';
 
     @action
     public static toggleTaskCompleted = () => {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index f205dbd56..8e9cfe3d7 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -74,8 +74,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         this._props.setContentView?.(this);
     }
 
-    @observable _stackedTimeline: any; // CollectionStackedTimeline ref
-    @observable static _nativeControls: boolean; // default html controls
+    @observable _stackedTimeline: any = undefined; // CollectionStackedTimeline ref
+    @observable static _nativeControls: boolean = false; // default html controls
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @observable _screenCapture = false;
     @observable _clicking = false; // used for transition between showing/hiding timeline
@@ -86,7 +86,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     @observable _finished: boolean = false; // has playback reached end of clip
     @observable _volume: number = 1;
     @observable _muted: boolean = false;
-    @observable _controlsTransform?: { X: number; Y: number };
+    @observable _controlsTransform?: { X: number; Y: number } = undefined;
     @observable _controlsVisible: boolean = true;
     @observable _scrubbing: boolean = false;
 
diff --git a/src/client/views/nodes/audio/AudioWaveform.tsx b/src/client/views/nodes/audio/AudioWaveform.tsx
index c034d95ea..3e46ff4e6 100644
--- a/src/client/views/nodes/audio/AudioWaveform.tsx
+++ b/src/client/views/nodes/audio/AudioWaveform.tsx
@@ -7,10 +7,10 @@ import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
 import { Cast } from '../../../../fields/Types';
 import { numberRange } from '../../../../Utils';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { Colors } from './../../global/globalEnums';
 import './AudioWaveform.scss';
 import { WaveCanvas } from './WaveCanvas';
-import { ObservableReactComponent } from '../../ObservableReactComponent';
 
 /**
  * AudioWaveform
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index 989feb774..4fb1a7f17 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -1,40 +1,35 @@
-import * as React from 'react';
-import { observer } from "mobx-react";
-import { Doc } from "../../../../fields/Doc";
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps, ViewBoxBaseComponent } from '../../DocComponent';
-import { FieldView, FieldViewProps } from '../FieldView';
-import { StrCast } from '../../../../fields/Types';
+import { Calendar, EventClickArg, EventSourceInput } from '@fullcalendar/core';
+import dayGridPlugin from '@fullcalendar/daygrid';
+import multiMonthPlugin from '@fullcalendar/multimonth';
 import { makeObservable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
 import { dateRangeStrToDates } from '../../../../Utils';
-import { Calendar, EventClickArg, EventSourceInput } from '@fullcalendar/core'
-import dayGridPlugin from '@fullcalendar/daygrid'
-import multiMonthPlugin from '@fullcalendar/multimonth'
-import { faListNumeric } from '@fortawesome/free-solid-svg-icons';
+import { Doc } from '../../../../fields/Doc';
+import { StrCast } from '../../../../fields/Types';
+import { ViewBoxBaseComponent } from '../../DocComponent';
+import { FieldView, FieldViewProps } from '../FieldView';
 
 type CalendarView = 'month' | 'multi-month' | 'week';
 
 @observer
-export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>(){
+export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string = 'calendar') {
         return FieldView.LayoutString(CalendarBox, fieldKey);
     }
 
-    componentDidMount(): void {
-        
-    }
+    componentDidMount(): void {}
 
-    componentWillUnmount(): void {
-        
-    }
+    componentWillUnmount(): void {}
 
-    _calendarRef = React.createRef<HTMLElement>()
+    _calendarRef = React.createRef<HTMLElement>();
 
-    get dateRangeStr (){
+    get dateRangeStr() {
         return StrCast(this.Document.date_range);
     }
 
-    // Choose a calendar view based on the date range 
-    get calendarViewType (): CalendarView {
+    // Choose a calendar view based on the date range
+    get calendarViewType(): CalendarView {
         const [fromDate, toDate] = dateRangeStrToDates(this.dateRangeStr);
 
         if (fromDate.getFullYear() !== toDate.getFullYear() || fromDate.getMonth() !== toDate.getMonth()) return 'multi-month';
@@ -43,92 +38,83 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>(){
         return 'week';
     }
 
-    get calendarStartDate () {
-        return this.dateRangeStr.split("|")[0];
+    get calendarStartDate() {
+        return this.dateRangeStr.split('|')[0];
     }
 
-    get calendarToDate () {
-        return this.dateRangeStr.split("|")[1];
+    get calendarToDate() {
+        return this.dateRangeStr.split('|')[1];
     }
 
-    get childDocs (): Doc[] {
+    get childDocs(): Doc[] {
         return this.childDocs; // get all sub docs for a calendar
     }
 
-    docBackgroundColor (type: string): string {
+    docBackgroundColor(type: string): string {
         // TODO: Return a different color based on the event type
         return 'blue';
     }
 
-    get calendarEvents (): EventSourceInput | undefined {
+    get calendarEvents(): EventSourceInput | undefined {
         if (this.childDocs.length === 0) return undefined;
         return this.childDocs.map((doc, idx) => {
             const docTitle = StrCast(doc.title);
             const docDateRange = StrCast(doc.date_range);
             const [startDate, endDate] = dateRangeStrToDates(docDateRange);
             const docType = doc.type;
-            const docDescription = doc.description ? StrCast(doc.description): "";
+            const docDescription = doc.description ? StrCast(doc.description) : '';
 
             return {
                 title: docTitle,
                 start: startDate,
                 end: endDate,
                 allDay: false,
-                classNames:[StrCast(docType)], // will determine the style 
+                classNames: [StrCast(docType)], // will determine the style
                 editable: false, // subject to change in the future
                 backgroundColor: this.docBackgroundColor(StrCast(doc.type)),
                 color: 'white',
                 extendedProps: {
-                    description: docDescription
+                    description: docDescription,
                 },
-                
-            }
-
-        })
+            };
+        });
     }
 
     handleEventClick = (arg: EventClickArg) => {
         // TODO: open popover with event description, option to open CalendarManager and change event date, delete event, etc.
-    }
+    };
 
     calendarEl: HTMLElement = document.getElementById('calendar-box-v1')!;
 
     // https://fullcalendar.io
     get calendar() {
-        return new Calendar(this.calendarEl, 
-            {
-                plugins: [this.calendarViewType === 'multi-month' ? multiMonthPlugin : dayGridPlugin],
-                headerToolbar: {
-                    left: 'prev,next today',
-                    center: 'title',
-                    right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
-                },
-                initialDate: this.calendarStartDate,
-                navLinks: true,
-                editable: false,
-                displayEventTime: false,
-                displayEventEnd: false,
-                events: this.calendarEvents,
-                eventClick: this.handleEventClick
-            }
-        )
-        
+        return new Calendar(this.calendarEl, {
+            plugins: [this.calendarViewType === 'multi-month' ? multiMonthPlugin : dayGridPlugin],
+            headerToolbar: {
+                left: 'prev,next today',
+                center: 'title',
+                right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek',
+            },
+            initialDate: this.calendarStartDate,
+            navLinks: true,
+            editable: false,
+            displayEventTime: false,
+            displayEventEnd: false,
+            events: this.calendarEvents,
+            eventClick: this.handleEventClick,
+        });
     }
 
-
-    constructor(props: any){
+    constructor(props: any) {
         super(props);
         makeObservable(this);
     }
 
-    render(){
+    render() {
         return (
-            <div className='calendar-box-conatiner'>
-                <div id='calendar-box-v1'>
-
-                </div>
-            </div>  
+            <div className="calendar-box-conatiner">
+                <div id="calendar-box-v1"></div>
+            </div>
         );
-        
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 6332b200d..1002ee403 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -1,17 +1,17 @@
 import { action, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { NodeSelection } from 'prosemirror-state';
+import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
 import { Doc } from '../../../../fields/Doc';
 import { Height, Width } from '../../../../fields/DocSymbols';
 import { NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, returnTrue, Utils } from '../../../../Utils';
+import { emptyFunction, returnFalse, Utils } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { Transform } from '../../../util/Transform';
 import { DocFocusOptions, DocumentView } from '../DocumentView';
 import { FormattedTextBox } from './FormattedTextBox';
-import * as React from 'react';
 
 export class DashDocView {
     dom: HTMLSpanElement; // container for label and value
@@ -83,7 +83,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
     _disposers: { [name: string]: IReactionDisposer } = {};
     _textBox: FormattedTextBox;
     @observable _dashDoc: Doc | undefined = undefined;
-    @observable _finalLayout: any;
+    @observable _finalLayout: any = undefined;
     @observable _width: number = 0;
     @observable _height: number = 0;
 
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index 331ed1980..7e655531e 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -1,14 +1,13 @@
-import EquationEditor from './EquationEditor';
-import { IReactionDisposer, trace } from 'mobx';
+import { IReactionDisposer } from 'mobx';
 import { observer } from 'mobx-react';
 import { TextSelection } from 'prosemirror-state';
+import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
 import { Doc } from '../../../../fields/Doc';
 import { StrCast } from '../../../../fields/Types';
 import './DashFieldView.scss';
+import EquationEditor from './EquationEditor';
 import { FormattedTextBox } from './FormattedTextBox';
-import * as React from 'react';
-import { AnyArray } from 'mongoose';
 
 export class EquationView {
     dom: HTMLDivElement; // container for label and value
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ad2fab8b0..8bf8abafa 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -11,6 +11,7 @@ import { keymap } from 'prosemirror-keymap';
 import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
 import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
 import { EditorView } from 'prosemirror-view';
+import * as React from 'react';
 import { BsMarkdownFill } from 'react-icons/bs';
 import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
@@ -70,9 +71,8 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
 import { RichTextRules } from './RichTextRules';
 import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
-// import * as applyDevTools from 'prosemirror-dev-tools';
-import * as React from 'react';
 export const GoogleRef = 'googleDocId';
+// import * as applyDevTools from 'prosemirror-dev-tools';
 type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
 
 export interface FormattedTextBoxProps {}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 4881070fd..3b31f2d17 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -32,7 +32,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     private _linkToRef = React.createRef<HTMLInputElement>();
 
     layoutDoc: Doc | undefined;
-    @observable public view?: EditorView;
+    @observable public view?: EditorView = undefined;
     public editorProps: FieldViewProps | undefined;
 
     public _brushMap: Map<string, Set<Mark>> = new Map();
diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx
index 9dc0c5180..b573f7c48 100644
--- a/src/client/views/nodes/importBox/ImportElementBox.tsx
+++ b/src/client/views/nodes/importBox/ImportElementBox.tsx
@@ -1,13 +1,11 @@
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import { returnFalse } from '../../../../Utils';
 import { Doc } from '../../../../fields/Doc';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue } from '../../../../Utils';
-import { Transform } from '../../../util/Transform';
 import { ViewBoxBaseComponent } from '../../DocComponent';
-import { DefaultStyleProvider } from '../../StyleProvider';
-import { DocumentView, DocumentViewInternal } from '../DocumentView';
+import { DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
-import * as React from 'react';
 
 @observer
 export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index e213b2fc5..0305689e7 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1,8 +1,8 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc, DocListCast, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
 import { Animation } from '../../../../fields/DocSymbols';
 import { Copy, Id } from '../../../../fields/FieldSymbols';
@@ -24,7 +24,7 @@ import { SerializationHelper } from '../../../util/SerializationHelper';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
-import { CollectionFreeFormView, computeTimelineLayout, MarqueeViewBounds } from '../../collections/collectionFreeForm';
+import { CollectionFreeFormView, MarqueeViewBounds } from '../../collections/collectionFreeForm';
 import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline';
 import { CollectionView } from '../../collections/CollectionView';
 import { TreeView } from '../../collections/TreeView';
@@ -83,12 +83,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     _batch: UndoManager.Batch | undefined = undefined; // undo batch for dragging sliders which generate multiple scene edit events as the cursor moves
     _keyTimer: NodeJS.Timeout | undefined; // timer for turning off transition flag when key frame change has completed.  Need to clear this if you do a second navigation before first finishes, or else first timer can go off during second naviation.
     _unmounting = false; // flag that view is unmounting used to block RemFromMap from deleting things
+    _presTimer: NodeJS.Timeout | undefined;
 
     @observable public static Instance: PresBox;
 
     @observable _isChildActive = false;
     @observable _moveOnFromAudio: boolean = true;
-    @observable _presTimer!: NodeJS.Timeout;
 
     @observable _eleArray: HTMLElement[] = [];
     @observable _dragArray: HTMLElement[] = [];
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index cf19ff6bc..5ea9e9979 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -1,18 +1,17 @@
-import * as React from 'react';
 import { action, computed } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { LinkFollower } from '../../util/LinkFollower';
+import { LinkManager } from '../../util/LinkManager';
 import { undoBatch } from '../../util/UndoManager';
 import { OpenWhere } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { AnchorMenu } from './AnchorMenu';
 import './Annotation.scss';
-import { LinkManager } from '../../util/LinkManager';
-import { Rect } from 'react-measure';
 
 interface IAnnotationProps extends FieldViewProps {
     anno: Doc;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index db21d9a3d..da8a88803 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -1,20 +1,20 @@
-import * as React from 'react';
-import './GPTPopup.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Button, IconButton, Type } from 'browndash-components';
 import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import { CgClose } from 'react-icons/cg';
 import ReactLoading from 'react-loading';
 import { TypeAnimation } from 'react-type-animation';
+import { Utils } from '../../../../Utils';
 import { Doc } from '../../../../fields/Doc';
-import { DocUtils, Docs } from '../../../documents/Documents';
-import { Button, IconButton, Type } from 'browndash-components';
 import { NumCast, StrCast } from '../../../../fields/Types';
-import { CgClose } from 'react-icons/cg';
-import { AnchorMenu } from '../AnchorMenu';
-import { gptImageCall } from '../../../apis/gpt/GPT';
 import { Networking } from '../../../Network';
-import { Utils } from '../../../../Utils';
+import { gptImageCall } from '../../../apis/gpt/GPT';
+import { DocUtils, Docs } from '../../../documents/Documents';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { AnchorMenu } from '../AnchorMenu';
+import './GPTPopup.scss';
 
 export enum GPTPopupMode {
     SUMMARY,
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index e342c25b3..b6d027d30 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -302,7 +302,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
         }
     };
 
-    @observable private _scrollTimer: any;
+    @observable private _scrollTimer: any = undefined;
 
     onScroll = (e: React.UIEvent<HTMLElement>) => {
         if (this._mainCont.current && !this._forcedScroll) {
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index ccfccb771..14187833f 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -2,24 +2,23 @@ import { Tooltip } from '@mui/material';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../../fields/Doc';
+import { Doc, DocListCastAsync, Field } from '../../../fields/Doc';
 import { DirectLinks } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, StrCast } from '../../../fields/Types';
-import { DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
+import { DocUtils } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { LinkManager } from '../../util/LinkManager';
+import { SearchUtil } from '../../util/SearchUtil';
+import { SettingsManager } from '../../util/SettingsManager';
 import { undoBatch } from '../../util/UndoManager';
-import { CollectionDockingView } from '../collections/CollectionDockingView';
 import { ViewBoxBaseComponent } from '../DocComponent';
+import { CollectionDockingView } from '../collections/CollectionDockingView';
+import { IRecommendation, Recommendation } from '../newlightbox/components';
+import { fetchRecommendations } from '../newlightbox/utils';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import './SearchBox.scss';
-import { fetchRecommendations } from '../newlightbox/utils';
-import { IRecommendation, Recommendation } from '../newlightbox/components';
-import { Colors } from '../global/globalEnums';
-import { SettingsManager } from '../../util/SettingsManager';
-import { SearchUtil } from '../../util/SearchUtil';
 
 const DAMPENING_FACTOR = 0.9;
 const MAX_ITERATIONS = 25;
diff --git a/src/client/views/selectedDoc/SelectedDocView.tsx b/src/client/views/selectedDoc/SelectedDocView.tsx
index 39e778b76..daacb368b 100644
--- a/src/client/views/selectedDoc/SelectedDocView.tsx
+++ b/src/client/views/selectedDoc/SelectedDocView.tsx
@@ -1,14 +1,14 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { ListBox } from 'browndash-components';
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import { emptyFunction } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { StrCast } from '../../../fields/Types';
 import { DocumentManager } from '../../util/DocumentManager';
-import { DocFocusOptions } from '../nodes/DocumentView';
-import { emptyFunction } from '../../../Utils';
 import { SettingsManager } from '../../util/SettingsManager';
+import { DocFocusOptions } from '../nodes/DocumentView';
 
 export interface SelectedDocViewProps {
     selectedDocs: Doc[];
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 0f3487cd1..575d5849e 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -16,15 +16,15 @@ import { ReportManager } from '../../util/reportManager/ReportManager';
 import { ServerStats } from '../../util/ServerStats';
 import { SettingsManager } from '../../util/SettingsManager';
 import { SharingManager } from '../../util/SharingManager';
+import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { CollectionDockingView } from '../collections/CollectionDockingView';
 import { CollectionLinearView } from '../collections/collectionLinear';
 import { DashboardView } from '../DashboardView';
 import { Colors } from '../global/globalEnums';
-import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
+import { DocumentViewInternal } from '../nodes/DocumentView';
 import { DefaultStyleProvider } from '../StyleProvider';
 import './TopBar.scss';
-import { SnappingManager } from '../../util/SnappingManager';
 
 /**
  * ABOUT: This is the topbar in Dash, which included the current Dashboard as well as access to information on the user
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
index f1739a41a..94458563e 100644
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ b/src/client/views/webcam/DashWebRTCVideo.tsx
@@ -3,16 +3,15 @@ import { faPhoneSlash, faSync } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc } from '../../../fields/Doc';
 import { InkTool } from '../../../fields/InkField';
+import { SnappingManager } from '../../util/SnappingManager';
 import '../../views/nodes/WebBox.scss';
 import { CollectionFreeFormDocumentViewProps } from '../nodes/CollectionFreeFormDocumentView';
-import { DocumentView } from '../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import './DashWebRTCVideo.scss';
 import { hangup, initialize, refreshVideos } from './WebCamLogic';
-import * as React from 'react';
-import { SnappingManager } from '../../util/SnappingManager';
 
 /**
  * This models the component that will be rendered, that can be used as a doc that will reflect the video cams.
diff --git a/src/debug/Repl.tsx b/src/debug/Repl.tsx
index b8081648f..a9f7c085f 100644
--- a/src/debug/Repl.tsx
+++ b/src/debug/Repl.tsx
@@ -1,13 +1,13 @@
+import { computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
-import { observer } from 'mobx-react';
-import { observable, computed } from 'mobx';
+import { DocServer } from '../client/DocServer';
+import { resolvedPorts } from '../client/util/CurrentUserUtils';
 import { CompileScript } from '../client/util/Scripting';
-import { makeInterface } from '../fields/Schema';
 import { ObjectField } from '../fields/ObjectField';
 import { RefField } from '../fields/RefField';
-import { DocServer } from '../client/DocServer';
-import { resolvedPorts } from '../client/util/CurrentUserUtils';
+import { makeInterface } from '../fields/Schema';
 
 @observer
 class Repl extends React.Component {
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index 02038c426..f46adef77 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -1,19 +1,19 @@
-import { action, configure, observable, ObservableMap, Lambda } from 'mobx';
+import { action, configure, observable } from 'mobx';
+import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
-import { observer } from 'mobx-react';
-import { Doc, Field, FieldResult, Opt } from '../fields/Doc';
 import { DocServer } from '../client/DocServer';
+import { resolvedPorts } from '../client/util/CurrentUserUtils';
+import { CompileScript } from '../client/util/Scripting';
+import { EditableView } from '../client/views/EditableView';
+import CursorField from '../fields/CursorField';
+import { DateField } from '../fields/DateField';
+import { Doc, Field, FieldResult } from '../fields/Doc';
 import { Id } from '../fields/FieldSymbols';
 import { List } from '../fields/List';
-import { URLField } from '../fields/URLField';
-import { EditableView } from '../client/views/EditableView';
-import { CompileScript } from '../client/util/Scripting';
 import { RichTextField } from '../fields/RichTextField';
-import { DateField } from '../fields/DateField';
 import { ScriptField } from '../fields/ScriptField';
-import CursorField from '../fields/CursorField';
-import { resolvedPorts } from '../client/util/CurrentUserUtils';
+import { URLField } from '../fields/URLField';
 
 DateField;
 URLField;
diff --git a/src/fields/CursorField.ts b/src/fields/CursorField.ts
index 46f5a8e1c..84917ae53 100644
--- a/src/fields/CursorField.ts
+++ b/src/fields/CursorField.ts
@@ -1,8 +1,7 @@
-import { ObjectField } from './ObjectField';
-import { observable } from 'mobx';
+import { createSimpleSchema, object, serializable } from 'serializr';
 import { Deserializable } from '../client/util/SerializationHelper';
-import { serializable, createSimpleSchema, object, date } from 'serializr';
-import { FieldChanged, ToScriptString, ToString, Copy } from './FieldSymbols';
+import { Copy, FieldChanged, ToScriptString, ToString } from './FieldSymbols';
+import { ObjectField } from './ObjectField';
 
 export type CursorPosition = {
     x: number;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 8903a9f97..9dc3c173d 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -265,8 +265,8 @@ export class Doc extends RefField {
     @observable public [DocAcl]: { [key: string]: symbol } = {};
     @observable public [DocCss]: number = 0; // incrementer denoting a change to CSS layout
     @observable public [DirectLinks] = new ObservableSet<Doc>();
-    @observable public [AudioPlay]: any; // meant to store sound object from Howl
-    @observable public [Animation]: Opt<Doc>;
+    @observable public [AudioPlay]: any = undefined; // meant to store sound object from Howl
+    @observable public [Animation]: Opt<Doc> = undefined;
     @observable public [Highlight]: boolean = false;
     @observable public [Brushed]: boolean = false;
     @observable public [DocViews] = new ObservableSet<DocumentView>();
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 8c8ff1ea3..b8ad552d2 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -2,7 +2,7 @@ import { action, computed, makeObservable, observable } from 'mobx';
 import { alias, list, serializable } from 'serializr';
 import { DocServer } from '../client/DocServer';
 import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
-import { afterDocDeserialize, autoObject, Deserializable } from '../client/util/SerializationHelper';
+import { Deserializable, afterDocDeserialize, autoObject } from '../client/util/SerializationHelper';
 import { Field } from './Doc';
 import { FieldTuples, Self, SelfProxy } from './DocSymbols';
 import { Copy, FieldChanged, Parent, ToScriptString, ToString } from './FieldSymbols';
@@ -11,7 +11,7 @@ import { ProxyField } from './Proxy';
 import { RefField } from './RefField';
 import { listSpec } from './Schema';
 import { Cast } from './Types';
-import { deleteProperty, getter, setter, containedFieldChangedHandler } from './util';
+import { containedFieldChangedHandler, deleteProperty, getter, setter } from './util';
 
 function toObjectField(field: Field) {
     return field instanceof RefField ? new ProxyField(field) : field;
@@ -102,8 +102,8 @@ class ListImpl<T extends Field> extends ObjectField {
                 items.length === 0 && deleteCount
                     ? { op: '$remFromSet', items: removed, hint: { start, deleteCount }, length: list.__fieldTuples.length }
                     : items.length && !deleteCount && start === list.__fieldTuples.length
-                    ? { op: '$addToSet', items, length: list.__fieldTuples.length }
-                    : undefined
+                      ? { op: '$addToSet', items, length: list.__fieldTuples.length }
+                      : undefined
             );
             return res.map(toRealField);
         }),
diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts
index c076f5fe1..3a46e3581 100644
--- a/src/fields/Proxy.ts
+++ b/src/fields/Proxy.ts
@@ -1,12 +1,12 @@
-import { Deserializable } from '../client/util/SerializationHelper';
-import { Field, FieldWaiting, Opt } from './Doc';
+import { action, computed, observable, runInAction } from 'mobx';
 import { primitive, serializable } from 'serializr';
-import { observable, action, runInAction, computed } from 'mobx';
 import { DocServer } from '../client/DocServer';
-import { RefField } from './RefField';
-import { ObjectField } from './ObjectField';
-import { Id, Copy, ToScriptString, ToString, ToValue } from './FieldSymbols';
 import { scriptingGlobal } from '../client/util/ScriptingGlobals';
+import { Deserializable } from '../client/util/SerializationHelper';
+import { Field, FieldWaiting, Opt } from './Doc';
+import { Copy, Id, ToScriptString, ToString, ToValue } from './FieldSymbols';
+import { ObjectField } from './ObjectField';
+import { RefField } from './RefField';
 
 function deserializeProxy(field: any) {
     if (!field.cache.field) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 545fe4478..b73520999 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,11 +1,11 @@
 import { $mobx, action, observable, runInAction, trace } from 'mobx';
 import { computedFn } from 'mobx-utils';
+import { returnZero } from '../Utils';
 import { DocServer } from '../client/DocServer';
 import { LinkManager } from '../client/util/LinkManager';
 import { SerializationHelper } from '../client/util/SerializationHelper';
 import { UndoManager } from '../client/util/UndoManager';
-import { returnZero } from '../Utils';
-import { aclLevel, Doc, DocListCast, Field, FieldResult, HierarchyMapping, ReverseHierarchyMap, StrListCast, updateCachedAcls } from './Doc';
+import { Doc, DocListCast, Field, FieldResult, HierarchyMapping, ReverseHierarchyMap, StrListCast, aclLevel, updateCachedAcls } from './Doc';
 import { AclAdmin, AclAugment, AclEdit, AclPrivate, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, UpdatingFromServer, Width } from './DocSymbols';
 import { FieldChanged, Id, Parent, ToValue } from './FieldSymbols';
 import { List } from './List';
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index d2598c2db..e333e6a2e 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -1,19 +1,19 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import * as rp from 'request-promise';
+import { Utils } from '../Utils';
 import { DocServer } from '../client/DocServer';
-import { Docs } from '../client/documents/Documents';
 import { Networking } from '../client/Network';
+import { Docs } from '../client/documents/Documents';
 import { MainViewModal } from '../client/views/MainViewModal';
 import { Doc, Opt } from '../fields/Doc';
 import { List } from '../fields/List';
 import { listSpec } from '../fields/Schema';
 import { Cast } from '../fields/Types';
-import { Utils } from '../Utils';
 import './ImageUpload.scss';
 import { MobileInterface } from './MobileInterface';
-import * as React from 'react';
 const { default: { DFLT_IMAGE_NATIVE_DIM } } = require('../client/views/global/globalCssVariables.module.scss'); // prettier-ignore
 export interface ImageUploadProps {
     Document: Doc; // Target document for upload (upload location)
diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx
index 2e595e4bc..23e19585a 100644
--- a/src/mobile/MobileInkOverlay.tsx
+++ b/src/mobile/MobileInkOverlay.tsx
@@ -1,6 +1,6 @@
-import * as React from 'react';
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { DocServer } from '../client/DocServer';
 import { DragManager } from '../client/util/DragManager';
 import { Doc } from '../fields/Doc';
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index bdd657575..e3d8f9394 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -1,52 +1,33 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import {
-    faTasks,
-    faReply,
-    faQuoteLeft,
-    faHandPointLeft,
-    faFolderOpen,
+    faAddressCard,
+    faAlignLeft,
+    faAlignRight,
     faAngleDoubleLeft,
-    faExternalLinkSquareAlt,
-    faMobile,
-    faThLarge,
-    faWindowClose,
-    faEdit,
-    faTrashAlt,
-    faPalette,
     faAngleRight,
+    faArrowDown,
+    faArrowLeft,
+    faArrowRight,
+    faArrowUp,
+    faArrowsAltH,
+    faAsterisk,
+    faBars,
     faBell,
-    faTrash,
+    faBolt,
+    faBook,
+    faBrain,
+    faBullseye,
+    faCalculator,
     faCamera,
-    faExpand,
     faCaretDown,
     faCaretLeft,
     faCaretRight,
     faCaretSquareDown,
     faCaretSquareRight,
-    faArrowsAltH,
-    faPlus,
-    faMinus,
-    faTerminal,
-    faToggleOn,
-    faFile as fileSolid,
-    faExternalLinkAlt,
-    faLocationArrow,
-    faSearch,
-    faFileDownload,
-    faStop,
-    faCalculator,
-    faWindowMaximize,
-    faAddressCard,
-    faQuestionCircle,
-    faArrowLeft,
-    faArrowRight,
-    faArrowDown,
-    faArrowUp,
-    faBolt,
-    faBullseye,
     faCaretUp,
     faCat,
     faCheck,
+    faChevronLeft,
     faChevronRight,
     faClipboard,
     faClone,
@@ -54,76 +35,95 @@ import {
     faCommentAlt,
     faCompressArrowsAlt,
     faCut,
+    faEdit,
     faEllipsisV,
     faEraser,
     faExclamation,
+    faExpand,
+    faExternalLinkAlt,
+    faExternalLinkSquareAlt,
+    faEye,
     faFileAlt,
     faFileAudio,
+    faFileDownload,
     faFilePdf,
     faFilm,
     faFilter,
+    faFolderOpen,
     faFont,
     faGlobeAsia,
+    faHandPointLeft,
     faHighlighter,
+    faHome,
+    faImage,
+    faLocationArrow,
+    faLongArrowAltLeft,
     faLongArrowAltRight,
     faMicrophone,
+    faMinus,
+    faMobile,
     faMousePointer,
     faMusic,
     faObjectGroup,
+    faPaintBrush,
+    faPalette,
     faPause,
     faPen,
     faPenNib,
     faPhone,
     faPlay,
+    faPlus,
     faPortrait,
+    faQuestionCircle,
+    faQuoteLeft,
     faRedoAlt,
+    faReply,
+    faSearch,
     faStamp,
     faStickyNote,
+    faStop,
+    faTasks,
+    faTerminal,
+    faTh,
+    faThLarge,
     faThumbtack,
+    faTimes,
+    faToggleOn,
+    faTrash,
+    faTrashAlt,
     faTree,
     faTv,
-    faBook,
     faUndoAlt,
     faVideo,
-    faAsterisk,
-    faBrain,
-    faImage,
-    faPaintBrush,
-    faTimes,
-    faEye,
-    faHome,
-    faLongArrowAltLeft,
-    faBars,
-    faTh,
-    faChevronLeft,
-    faAlignRight,
-    faAlignLeft,
+    faWindowClose,
+    faWindowMaximize,
+    faFile as fileSolid,
 } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Docs, DocumentOptions, DocUtils } from '../client/documents/Documents';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../Utils';
 import { CollectionViewType, DocumentType } from '../client/documents/DocumentTypes';
+import { Docs, DocumentOptions } from '../client/documents/Documents';
 import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
 import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
-import { SettingsManager, ColorScheme } from '../client/util/SettingsManager';
+import { SettingsManager } from '../client/util/SettingsManager';
 import { Transform } from '../client/util/Transform';
 import { UndoManager } from '../client/util/UndoManager';
-import { TabDocView } from '../client/views/collections/TabDocView';
 import { GestureOverlay } from '../client/views/GestureOverlay';
+import { TabDocView } from '../client/views/collections/TabDocView';
 import { AudioBox } from '../client/views/nodes/AudioBox';
 import { DocumentView } from '../client/views/nodes/DocumentView';
-import { RichTextMenu } from '../client/views/nodes/formattedText/RichTextMenu';
 import { RadialMenu } from '../client/views/nodes/RadialMenu';
+import { RichTextMenu } from '../client/views/nodes/formattedText/RichTextMenu';
 import { Doc, DocListCast } from '../fields/Doc';
 import { InkTool } from '../fields/InkField';
 import { List } from '../fields/List';
 import { ScriptField } from '../fields/ScriptField';
 import { Cast, FieldValue, StrCast } from '../fields/Types';
-import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero } from '../Utils';
-import { Uploader } from './ImageUpload';
 import './AudioUpload.scss';
+import { Uploader } from './ImageUpload';
 import './ImageUpload.scss';
 import './MobileInterface.scss';
 
diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts
index 8f21966ae..d46977816 100644
--- a/src/typings/index.d.ts
+++ b/src/typings/index.d.ts
@@ -14,9 +14,7 @@ declare module 'pdfjs-dist/web/pdf_viewer';
 declare module 'react-jsx-parser';
 
 declare module 'express-flash';
-declare module 'connect-flash' {
-    interface flash {}
-}
+declare module 'connect-flash';
 declare module 'connect-mongo';
 declare module '@mui/material';
 
diff --git a/test/test.ts b/test/test.ts
index 489aa3025..ee14a9340 100644
--- a/test/test.ts
+++ b/test/test.ts
@@ -1,37 +1,41 @@
 import { expect } from 'chai';
 import 'mocha';
 const { JSDOM } = require('jsdom');
-const dom = new JSDOM("", {
-    url: `http://localhost:${resolvedPorts.server}`
+const dom = new JSDOM('', {
+    url: `http://localhost:${resolvedPorts.server}`,
 });
 (global as any).window = dom.window;
 
-
-import { autorun, reaction } from "mobx";
+import { reaction } from 'mobx';
+import { resolvedPorts } from '../src/client/util/CurrentUserUtils';
 import { Doc } from '../src/fields/Doc';
+import { createSchema, defaultSpec, makeInterface } from '../src/fields/Schema';
 import { Cast } from '../src/fields/Types';
-import { createSchema, makeInterface, defaultSpec } from '../src/fields/Schema';
 import { ImageField } from '../src/fields/URLField';
-import { resolvedPorts } from '../src/client/util/CurrentUserUtils';
-describe("Document", () => {
+describe('Document', () => {
     it('should hold fields', () => {
-        const key = "Test";
-        const key2 = "Test2";
+        const key = 'Test';
+        const key2 = 'Test2';
         const field = 15;
         const doc = new Doc();
         doc[key] = field;
-        const getField = Cast(doc[key], "number");
-        const getField2 = Cast(doc[key2], "number");
+        const getField = Cast(doc[key], 'number');
+        const getField2 = Cast(doc[key2], 'number');
         expect(getField).to.equal(field);
         expect(getField2).to.equal(undefined);
     });
 
     it('should update', () => {
         const doc = new Doc();
-        const key = "Test";
-        const key2 = "Test2";
+        const key = 'Test';
+        const key2 = 'Test2';
         let ran = false;
-        reaction(() => doc[key], (field) => { ran = true; });
+        reaction(
+            () => doc[key],
+            field => {
+                ran = true;
+            }
+        );
         expect(ran).to.equal(false);
 
         doc[key2] = 4;
@@ -44,55 +48,55 @@ describe("Document", () => {
 });
 
 const testSchema1 = createSchema({
-    a: "number",
-    b: "string",
-    c: "boolean",
+    a: 'number',
+    b: 'string',
+    c: 'boolean',
     d: ImageField,
-    e: Doc
+    e: Doc,
 });
 
 type TestDoc = makeInterface<[typeof testSchema1]>;
 const TestDoc = makeInterface(testSchema1);
 
 const testSchema2 = createSchema({
-    a: defaultSpec("boolean", true),
-    b: defaultSpec("number", 5),
-    c: defaultSpec("string", "hello world")
+    a: defaultSpec('boolean', true),
+    b: defaultSpec('number', 5),
+    c: defaultSpec('string', 'hello world'),
 });
 
 type TestDoc2 = makeInterface<[typeof testSchema2]>;
 const TestDoc2 = makeInterface(testSchema2);
 
 const testSchema3 = createSchema({
-    a: TestDoc2
+    a: TestDoc2,
 });
 
 type TestDoc3 = makeInterface<[typeof testSchema3]>;
 const TestDoc3 = makeInterface(testSchema3);
 
-describe("Schema", () => {
-    it("should do the right thing 1", () => {
-        const test1 = new Doc;
-        const test2 = new Doc;
-        const ifield = new ImageField(new URL("http://google.com"));
+describe('Schema', () => {
+    it('should do the right thing 1', () => {
+        const test1 = new Doc();
+        const test2 = new Doc();
+        const ifield = new ImageField(new URL('http://google.com'));
         test1.a = 5;
-        test1.b = "hello";
+        test1.b = 'hello';
         test1.c = true;
         test1.d = ifield;
         test1.e = test2;
         const doc = TestDoc(test1);
         expect(doc.a).to.equal(5);
-        expect(doc.b).to.equal("hello");
+        expect(doc.b).to.equal('hello');
         expect(doc.c).to.equal(true);
         expect(doc.d).to.equal(ifield);
         expect(doc.e).to.equal(test2);
     });
 
-    it("should do the right thing 2", () => {
-        const test1 = new Doc;
-        const test2 = new Doc;
-        const ifield = new ImageField(new URL("http://google.com"));
-        test1.a = "hello";
+    it('should do the right thing 2', () => {
+        const test1 = new Doc();
+        const test2 = new Doc();
+        const ifield = new ImageField(new URL('http://google.com'));
+        test1.a = 'hello';
         test1.b = 5;
         test1.c = test2;
         test1.d = true;
@@ -105,11 +109,11 @@ describe("Schema", () => {
         expect(doc.e).to.equal(undefined);
     });
 
-    it("should do the right thing 3", () => {
-        const test1 = new Doc;
-        const test2 = new Doc;
-        const ifield = new ImageField(new URL("http://google.com"));
-        test1.a = "hello";
+    it('should do the right thing 3', () => {
+        const test1 = new Doc();
+        const test2 = new Doc();
+        const ifield = new ImageField(new URL('http://google.com'));
+        test1.a = 'hello';
         test1.b = 5;
         test1.c = test2;
         test1.d = true;
@@ -122,40 +126,40 @@ describe("Schema", () => {
         expect(doc.e).to.equal(undefined);
     });
 
-    it("should do the right thing 4", () => {
+    it('should do the right thing 4', () => {
         const doc = TestDoc2();
         expect(doc.a).to.equal(true);
         expect(doc.b).to.equal(5);
-        expect(doc.c).to.equal("hello world");
+        expect(doc.c).to.equal('hello world');
 
-        const d2 = new Doc;
+        const d2 = new Doc();
         d2.a = false;
         d2.b = 4;
-        d2.c = "goodbye";
+        d2.c = 'goodbye';
         const doc2 = TestDoc2(d2);
         expect(doc2.a).to.equal(false);
         expect(doc2.b).to.equal(4);
-        expect(doc2.c).to.equal("goodbye");
+        expect(doc2.c).to.equal('goodbye');
 
-        const d3 = new Doc;
-        d3.a = "hello";
+        const d3 = new Doc();
+        d3.a = 'hello';
         d3.b = false;
         d3.c = 5;
         const doc3 = TestDoc2(d3);
         expect(doc3.a).to.equal(true);
         expect(doc3.b).to.equal(5);
-        expect(doc3.c).to.equal("hello world");
+        expect(doc3.c).to.equal('hello world');
     });
 
-    it("should do the right thing 5", async () => {
-        const test1 = new Doc;
-        const test2 = new Doc;
+    it('should do the right thing 5', async () => {
+        const test1 = new Doc();
+        const test2 = new Doc();
         const doc = TestDoc3(test1);
         expect(doc.a).to.equal(undefined);
         test1.a = test2;
         const doc2 = (await doc.a)!;
         expect(doc2.a).to.equal(true);
         expect(doc2.b).to.equal(5);
-        expect(doc2.c).to.equal("hello world");
+        expect(doc2.c).to.equal('hello world');
     });
 });
-- 
cgit v1.2.3-70-g09d2


From 2ea594a4c1ff221200e0795fb07d30b603177a67 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 3 Jan 2024 23:44:28 -0500
Subject: cleaned up screenToLocalTransform in several places by making it
 additional methods.  cleaned up styleProider api a bit to take
 DocumentViewInternalProps, not DocumentViewProps..

---
 src/client/util/HypothesisUtils.ts                 |   2 +-
 src/client/views/DocComponent.tsx                  |  10 ++
 src/client/views/DocumentButtonBar.tsx             |   2 +-
 src/client/views/DocumentDecorations.tsx           |  16 +--
 src/client/views/GestureOverlay.tsx                |   2 +-
 src/client/views/GlobalKeyHandler.ts               |   4 +-
 src/client/views/InkingStroke.tsx                  |  23 ++---
 src/client/views/MarqueeAnnotator.tsx              |   2 +-
 src/client/views/StyleProvider.tsx                 |  13 +--
 .../views/collections/CollectionCarouselView.tsx   |   5 +-
 .../views/collections/CollectionNoteTakingView.tsx |  18 ++--
 .../collections/CollectionStackedTimeline.tsx      |  11 ++-
 .../views/collections/CollectionStackingView.tsx   |  12 +--
 .../views/collections/CollectionTimeView.tsx       |   1 -
 .../views/collections/CollectionTreeView.tsx       |   4 +-
 src/client/views/collections/CollectionView.tsx    |   2 +-
 src/client/views/collections/TabDocView.tsx        |   7 +-
 src/client/views/collections/TreeView.tsx          |  18 ++--
 .../CollectionFreeFormLinkView.tsx                 |  10 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  94 +++++++++---------
 .../collectionGrid/CollectionGridView.tsx          |   6 +-
 .../CollectionMulticolumnView.tsx                  |   2 +-
 .../CollectionMultirowView.tsx                     |   2 +-
 .../collectionSchema/CollectionSchemaView.tsx      |  21 ++--
 src/client/views/nodes/AudioBox.tsx                |   4 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   9 +-
 src/client/views/nodes/ComparisonBox.tsx           |   6 +-
 .../views/nodes/DataVizBox/components/TableBox.tsx |   2 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |   2 +-
 src/client/views/nodes/DocumentView.tsx            |  48 +++++----
 src/client/views/nodes/ImageBox.tsx                |   6 +-
 src/client/views/nodes/LinkBox.tsx                 |  10 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   8 +-
 src/client/views/nodes/MapBox/MapBox2.tsx          |   2 +-
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |   6 +-
 src/client/views/nodes/PDFBox.tsx                  |   2 +-
 src/client/views/nodes/ScreenshotBox.tsx           |   1 -
 src/client/views/nodes/VideoBox.tsx                |   6 +-
 src/client/views/nodes/WebBox.tsx                  |  12 +--
 .../views/nodes/formattedText/FormattedTextBox.tsx | 109 +--------------------
 .../views/nodes/importBox/ImportElementBox.tsx     |   2 +-
 src/client/views/nodes/trails/PresBox.tsx          |   4 +-
 src/client/views/nodes/trails/PresElementBox.tsx   |   4 +-
 src/client/views/pdf/PDFViewer.tsx                 |  15 +--
 44 files changed, 230 insertions(+), 315 deletions(-)

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

diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts
index 0ae23d793..f46c2d431 100644
--- a/src/client/util/HypothesisUtils.ts
+++ b/src/client/util/HypothesisUtils.ts
@@ -180,7 +180,7 @@ export namespace Hypothesis {
                 })
             );
             const targetView: Opt<DocumentView> = DocumentManager.Instance.getFirstDocumentView(target);
-            const position = targetView?.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+            const position = targetView?.screenToViewTransform().inverse().transformPoint(0, 0);
             targetView && position && simulateMouseClick(targetView.ContentDiv!, position[0], position[1], position[0], position[1], false);
         }, 300);
 
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index b5bd17572..dfc298840 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -12,12 +12,14 @@ import { DocumentManager } from '../util/DocumentManager';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import { CollectionFreeFormView } from './collections/collectionFreeForm';
 import { DocumentView } from './nodes/DocumentView';
+import { Transform } from '../util/Transform';
 
 ///  DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
 export interface DocComponentProps {
     Document: Doc;
     LayoutTemplate?: () => Opt<Doc>;
     LayoutTemplateString?: string;
+    ScreenToLocalTransform: () => Transform;
 }
 export function DocComponent<P extends DocComponentProps>() {
     class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
@@ -26,6 +28,8 @@ export function DocComponent<P extends DocComponentProps>() {
             makeObservable(this);
         }
 
+        ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform();
+
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         get Document() {
             return this._props.Document;
@@ -50,10 +54,13 @@ interface ViewBoxBaseProps {
     fieldKey: string;
     isSelected: () => boolean;
     isContentActive: () => boolean | undefined;
+    ScreenToLocalTransform: () => Transform;
     renderDepth: number;
 }
 export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
     class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
+        ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform();
+
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         get Document() {
             return this._props.Document;
@@ -83,6 +90,7 @@ export interface ViewBoxAnnotatableProps {
     isContentActive: () => boolean | undefined;
     select: (isCtrlPressed: boolean) => void;
     whenChildContentsActiveChanged: (isActive: boolean) => void;
+    ScreenToLocalTransform: () => Transform;
     isSelected: () => boolean;
     renderDepth: number;
     isAnnotationOverlay?: boolean;
@@ -94,6 +102,8 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
             makeObservable(this);
         }
 
+        ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform();
+
         @observable _annotationKeySuffix = () => 'annotations';
         @observable _isAnyChildContentActive = false;
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index e6d53e727..bb28d9ec9 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -305,7 +305,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
         if (this._dragRef.current) {
             const dragDocView = this.view0!;
             const dragData = new DragManager.DocumentDragData([dragDocView.Document]);
-            const [left, top] = dragDocView.screenToNativeLocalTransform().inverse().transformPoint(0, 0);
+            const [left, top] = dragDocView.screenToContentsTransform().inverse().transformPoint(0, 0);
             dragData.defaultDropAction = 'embed';
             dragData.canEmbed = true;
             DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, { hideSource: false });
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 583a7c447..ced8799bf 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -77,7 +77,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                 const center = {x: (this.Bounds.x+this.Bounds.r)/2, y: (this.Bounds.y+this.Bounds.b)/2};
                 const {x,y} = Utils.rotPt(e.clientX - center.x, 
                                           e.clientY - center.y, 
-                                          NumCast(SelectionManager.Views.lastElement()?.screenToNativeLocalTransform().Rotate));
+                                          NumCast(SelectionManager.Views.lastElement()?.screenToViewTransform().Rotate));
                 (this._showNothing = !(this.Bounds.x !== Number.MAX_VALUE && //
                     (this.Bounds.x > center.x+x || this.Bounds.r < center.x+x || 
                      this.Bounds.y > center.y+y || this.Bounds.b < center.y+y )));
@@ -208,7 +208,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             SelectionManager.Views.map(dv => dv.Document),
             dragDocView._props.dropAction
         );
-        dragData.offset = dragDocView.screenToNativeLocalTransform().transformDirection(e.x - left, e.y - top);
+        dragData.offset = dragDocView.screenToContentsTransform().transformDirection(e.x - left, e.y - top);
         dragData.moveDocument = dragDocView._props.moveDocument;
         dragData.removeDocument = dragDocView._props.removeDocument;
         dragData.isDocDecorationMove = true;
@@ -353,7 +353,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     };
 
     setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => {
-        const newloccentern = seldocview.screenToNativeLocalTransform().transformPoint(rotCenter[0], rotCenter[1]);
+        const newloccentern = seldocview.screenToContentsTransform().transformPoint(rotCenter[0], rotCenter[1]);
         const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2];
         const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.Document._rotation) / 180) * Math.PI);
         seldocview.Document.rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
@@ -385,7 +385,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const seldocview = SelectionManager.Views[0];
         SelectionManager.Views.forEach(dv => {
             const accumRot = (NumCast(dv.Document._rotation) / 180) * Math.PI;
-            const localRotCtr = dv._props.ScreenToLocalTransform().transformPoint(rcScreen.X, rcScreen.Y);
+            const localRotCtr = dv.screenToViewTransform().transformPoint(rcScreen.X, rcScreen.Y);
             const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.Document.width) / 2, localRotCtr[1] - NumCast(dv.Document.height) / 2];
             const startRotCtr = Utils.rotPt(localRotCtrOffset[0], localRotCtrOffset[1], -accumRot);
             const unrotatedDocPos = { x: NumCast(dv.Document.x) + localRotCtrOffset[0] - startRotCtr.x, y: NumCast(dv.Document.y) + localRotCtrOffset[1] - startRotCtr.y };
@@ -462,7 +462,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             dot = (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
             return [a[0] + atob[0] * t, a[1] + atob[1] * t];
         };
-        const tl = docView.screenToNativeLocalTransform().inverse().transformPoint(0, 0);
+        const tl = docView.screenToContentsTransform().inverse().transformPoint(0, 0);
         return project([e.clientX + this._offset.x, e.clientY + this._offset.y], tl, [tl[0] + fixedAspect, tl[1] + 1]);
     };
     onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
@@ -530,7 +530,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             doc.xPadding = NumCast(doc.xPadding) * scale.x;
             doc.yPadding = NumCast(doc.yPadding) * scale.y;
         } else {
-            const refCent = docView.screenToNativeLocalTransform().transformPoint(refPt[0], refPt[1]); // fixed reference point for resize (ie, a point that doesn't move)
+            const refCent = docView.screenToContentsTransform().transformPoint(refPt[0], refPt[1]); // fixed reference point for resize (ie, a point that doesn't move)
             const [nwidth, nheight] = [docView.nativeWidth, docView.nativeHeight];
             const [initWidth, initHeight] = [NumCast(doc._width, 1), NumCast(doc._height)];
 
@@ -632,7 +632,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     @computed get rotCenter() {
         const lastView = SelectionManager.Views.lastElement();
         if (lastView) {
-            const invXf = lastView.screenToNativeLocalTransform().inverse();
+            const invXf = lastView.screenToContentsTransform().inverse();
             const seldoc = lastView.layoutDoc;
             const loccenter = Utils.rotPt(NumCast(seldoc.rotation_centerX) * NumCast(seldoc._width), NumCast(seldoc.rotation_centerY) * NumCast(seldoc._height), invXf.Rotate);
             return invXf.transformPoint(loccenter.x + NumCast(seldoc._width) / 2, loccenter.y + NumCast(seldoc._height) / 2);
@@ -691,7 +691,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const bounds = this.ClippedBounds;
         const useLock = bounds.r - bounds.x > 135 && seldocview.CollectionFreeFormDocumentView;
         const useRotation = !hideResizers && seldocview.Document.type !== DocumentType.EQUATION && seldocview.CollectionFreeFormDocumentView; // when do we want an object to not rotate?
-        const rotation = SelectionManager.Views.length == 1 ? seldocview.screenToNativeLocalTransform().inverse().RotateDeg : 0;
+        const rotation = SelectionManager.Views.length == 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0;
 
         // Radius constants
         const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView;
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 65aadc148..b1b04237c 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -385,7 +385,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
 
     get elements() {
         const selView = GestureOverlay.DownDocView;
-        const width = Number(ActiveInkWidth()) * NumCast(selView?.Document._freeform_scale, 1); // * (selView?._props.ScreenToLocalTransform().Scale || 1);
+        const width = Number(ActiveInkWidth()) * NumCast(selView?.Document._freeform_scale, 1); // * (selView?.screenToViewTransform().Scale || 1);
         const rect = this._overlayRef.current?.getBoundingClientRect();
         const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(this._points, true);
         B.left = B.left - width / 2;
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 4015fb321..77b831e51 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -301,7 +301,7 @@ export class KeyManager {
             case 'x':
                 if (SelectionManager.Views.length) {
                     const bds = DocumentDecorations.Instance.Bounds;
-                    const pt = SelectionManager.Views[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
+                    const pt = SelectionManager.Views[0].screenToViewTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
                     const text = `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views.map(dv => dv.Document[Id]).join(':');
                     SelectionManager.Views.length && navigator.clipboard.writeText(text);
                     DocumentDecorations.Instance.onCloseClick(true);
@@ -312,7 +312,7 @@ export class KeyManager {
             case 'c':
                 if ((document.activeElement as any)?.type !== 'text' && !AnchorMenu.Instance.Active && DocumentDecorations.Instance.Bounds.r - DocumentDecorations.Instance.Bounds.x > 2) {
                     const bds = DocumentDecorations.Instance.Bounds;
-                    const pt = SelectionManager.Views[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
+                    const pt = SelectionManager.Views[0].screenToViewTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
                     const text = `__DashCloneId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views.map(dv => dv.Document[Id]).join(':');
                     SelectionManager.Views.length && navigator.clipboard.writeText(text);
                     stopPropagation = false;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 95c677845..c652e8966 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -45,6 +45,7 @@ import { FieldView, FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { PinProps, PresBox } from './nodes/trails';
 import { StyleProp } from './StyleProvider';
+import { Transform } from '../util/Transform';
 const { default: { INK_MASK_SIZE } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
 @observer
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -73,10 +74,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         Object.keys(this._disposers).forEach(key => this._disposers[key]());
     }
 
-    // transform is the inherited screentolocal xf plus any scaling that was done to make the stroke
-    // fit within its panel (e.g., for content fitting views like Lightbox or multicolumn, etc)
-    screenToLocal = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1);
-
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
         const subAnchor = this._subContentView?.getAnchor?.(addAsAnnotation);
         if (subAnchor !== this.Document && subAnchor) return subAnchor;
@@ -129,7 +126,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
         const screenPts = inkData
             .map(point =>
-                this.screenToLocal()
+                this.ScreenToLocalBoxXf()
                     .inverse()
                     .transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)
             )
@@ -181,7 +178,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
      */
     ptFromScreen = (scrPt: { X: number; Y: number }) => {
         const { inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
-        const docPt = this.screenToLocal().transformPoint(scrPt.X, scrPt.Y);
+        const docPt = this.ScreenToLocalBoxXf().transformPoint(scrPt.X, scrPt.Y);
         const inkPt = {
             X: (docPt[0] - inkStrokeWidth / 2) / inkScaleX + inkStrokeWidth / 2 + inkLeft,
             Y: (docPt[1] - inkStrokeWidth / 2) / inkScaleY + inkStrokeWidth / 2 + inkTop,
@@ -199,7 +196,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
             X: (inkPt.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
             Y: (inkPt.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2,
         };
-        const scrPt = this.screenToLocal().inverse().transformPoint(docPt.X, docPt.Y);
+        const scrPt = this.ScreenToLocalBoxXf().inverse().transformPoint(docPt.X, docPt.Y);
         return { X: scrPt[0], Y: scrPt[1] };
     };
 
@@ -213,7 +210,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     snapPt = (scrPt: { X: number; Y: number }, excludeSegs?: number[]) => {
         const { inkData } = this.inkScaledData();
         const { nearestPt, distance } = InkStrokeProperties.nearestPtToStroke(inkData, this.ptFromScreen(scrPt), excludeSegs ?? []);
-        return { nearestPt, distance: distance * this.screenToLocal().inverse().Scale };
+        return { nearestPt, distance: distance * this.ScreenToLocalBoxXf().inverse().Scale };
     };
 
     /**
@@ -250,7 +247,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
         const screenPts = inkData
             .map(point =>
-                this.screenToLocal()
+                this.ScreenToLocalBoxXf()
                     .inverse()
                     .transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)
             )
@@ -275,7 +272,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
         return inkData
             .map(point =>
-                this.screenToLocal()
+                this.ScreenToLocalBoxXf()
                     .inverse()
                     .transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)
             )
@@ -291,9 +288,9 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     componentUI = (boundsLeft: number, boundsTop: number) => {
         const inkDoc = this.Document;
         const { inkData, inkStrokeWidth } = this.inkScaledData();
-        const screenSpaceCenterlineStrokeWidth = Math.min(3, inkStrokeWidth * this.screenToLocal().inverse().Scale); // the width of the blue line widget that shows the centerline of the ink stroke
+        const screenSpaceCenterlineStrokeWidth = Math.min(3, inkStrokeWidth * this.ScreenToLocalBoxXf().inverse().Scale); // the width of the blue line widget that shows the centerline of the ink stroke
 
-        const screenInkWidth = this.screenToLocal().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth);
+        const screenInkWidth = this.ScreenToLocalBoxXf().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth);
 
         const startMarker = StrCast(this.layoutDoc.stroke_startMarker);
         const endMarker = StrCast(this.layoutDoc.stroke_endMarker);
@@ -329,7 +326,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                     false
                 )}
                 <InkControlPtHandles inkView={this} inkDoc={inkDoc} inkCtrlPoints={inkData} screenCtrlPoints={this.screenCtrlPts} nearestScreenPt={this.nearestScreenPt} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
-                <InkTangentHandles inkView={this} inkDoc={inkDoc} screenCtrlPoints={this.screenCtrlPts} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} ScreenToLocalTransform={this.screenToLocal} />
+                <InkTangentHandles inkView={this} inkDoc={inkDoc} screenCtrlPoints={this.screenCtrlPts} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} ScreenToLocalTransform={this.ScreenToLocalBoxXf} />
             </div>
         );
     };
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 9f3786d8e..ed09d3bf3 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -158,7 +158,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
     //    4) reattach the vector to the center of the bounding box
     getTransformedScreenPt = (down: number[]) => {
         const marqueeContainer = this.props.marqueeContainer;
-        const containerXf = this.props.isNativeScaled ? this.props.docView().screenToNativeLocalTransform() : this.props.docView().props.ScreenToLocalTransform();
+        const containerXf = this.props.isNativeScaled ? this.props.docView().screenToContentsTransform() : this.props.docView().screenToViewTransform();
         const boundingRect = marqueeContainer.getBoundingClientRect();
         const center = { x: boundingRect.x + boundingRect.width / 2, y: boundingRect.y + boundingRect.height / 2 };
         const downVec = Utils.rotPt(down[0] - center.x,  
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 835e61d55..ce69dcc41 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -20,7 +20,7 @@ import { SnappingManager } from '../util/SnappingManager';
 import { undoBatch, UndoManager } from '../util/UndoManager';
 import { TreeSort } from './collections/TreeSort';
 import { Colors } from './global/globalEnums';
-import { DocumentViewProps } from './nodes/DocumentView';
+import { DocumentViewInternalProps, DocumentViewProps } from './nodes/DocumentView';
 import { FieldViewProps } from './nodes/FieldView';
 import { KeyValueBox } from './nodes/KeyValueBox';
 import { PropertiesView } from './PropertiesView';
@@ -37,7 +37,6 @@ export enum StyleProp {
     BackgroundColor = 'backgroundColor', // background color of a document view
     FillColor = 'fillColor', // fill color of an ink stroke or shape
     WidgetColor = 'widgetColor', // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note
-    HideLinkBtn = 'hideLinkButton', // hides the blue-dot link button.  used when a document acts like a button
     PointerEvents = 'pointerEvents', // pointer events for DocumentView -- inherits pointer events if not specified
     Decorations = 'decorations', // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background
     HeaderMargin = 'headerMargin', // margin at top of documentview, typically for displaying a title -- doc contents will start below that
@@ -81,11 +80,10 @@ export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
 
 // a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab
 //
-export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string): any {
+export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any {
     const remoteDocHeader = 'author;author_date;noMargin';
     const docProps = testDocProps(props) ? props : undefined;
     const fieldProps = testFieldProps(props) ? props : undefined;
-    const selected = property.includes(':selected');
     const isCaption = property.includes(':caption');
     const isAnchor = property.includes(':anchor');
     const isContent = property.includes(':content');
@@ -138,14 +136,13 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.DocContents:return undefined;
         case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
         case StyleProp.Opacity:    return docProps?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 :  Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
-        case StyleProp.HideLinkBtn:return props?.hideLinkButton || (!selected && doc?.layout_hideLinkButton);
         case StyleProp.FontSize:   return StrCast(doc?.[fieldKey + 'fontSize'],   StrCast(doc?._text_fontSize,   StrCast(Doc.UserDoc().fontSize)));
         case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?._text_fontFamily, StrCast(Doc.UserDoc().fontFamily)));
         case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(doc?._text_fontWeight, StrCast(Doc.UserDoc().fontWeight)));
         case StyleProp.FillColor:  return StrCast(doc?._fillColor, StrCast(doc?.fillColor, 'transparent'));
         case StyleProp.ShowCaption:return doc?._type_collection === CollectionViewType.Carousel || props?.hideCaptions ? undefined : StrCast(doc?._layout_showCaption);
         case StyleProp.TitleHeight:
-            return (props?.ScreenToLocalTransform().Scale ?? 1)*(props?.NativeDimScaling?.()??1) * NumCast(Doc.UserDoc().headerHeight,30)
+            return (props?.DocumentView?.().screenToViewTransform().Scale ?? 1) * NumCast(Doc.UserDoc().headerHeight,30)
         case StyleProp.ShowTitle:
             return (
                 (doc &&
@@ -171,7 +168,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             const docColor: Opt<string> = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color));
             if (docColor) return docColor;
             const docView = props?.DocumentView?.();
-            const backColor = backgroundCol() || docView?._props.styleProvider?.(docView._props.treeViewDoc, docView._props, StyleProp.BackgroundColor);
+            const backColor = backgroundCol() || docView?._props.styleProvider?.(docView._props.treeViewDoc, docView.docView?._props, StyleProp.BackgroundColor);
             return backColor ? lightOrDark(backColor) : undefined;
         case StyleProp.BorderRounding:
             return StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.layout_borderRounding, doc?._type_collection === CollectionViewType.Pile ? '50%' : ''));
@@ -373,7 +370,7 @@ export function DashboardToggleButton(doc: Doc, field: string, onIcon: IconProp,
 /**
  * add hide button decorations for the "Dashboards" flyout TreeView
  */
-export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) {
+export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewInternalProps>, property: string) {
     if (doc && property.split(':')[0] === StyleProp.Decorations) {
         return doc._type_collection === CollectionViewType.Docking || Doc.IsSystem(doc)
             ? null
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index b6acf3153..fb57a668e 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -7,10 +7,11 @@ import { Doc, Opt } from '../../../fields/Doc';
 import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { StyleProp } from '../StyleProvider';
-import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../nodes/DocumentView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import './CollectionCarouselView.scss';
 import { CollectionSubView } from './CollectionSubView';
+import { FieldViewProps } from '../nodes/FieldView';
 
 @observer
 export class CollectionCarouselView extends CollectionSubView() {
@@ -40,7 +41,7 @@ export class CollectionCarouselView extends CollectionSubView() {
         e.stopPropagation();
         this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
     };
-    captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<DocumentViewProps>, property: string): any => {
+    captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<DocumentViewInternalProps|FieldViewProps>, property: string): any => {
         // first look for properties on the document in the carousel, then fallback to properties on the container
         const childValue = doc?.['caption-' + property] ? this.props.styleProvider?.(doc, captionProps, property) : undefined;
         return childValue ?? this.props.styleProvider?.(this.layoutDoc, captionProps, property);
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 0744814dd..527b72213 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -18,7 +18,7 @@ import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { LightboxView } from '../LightboxView';
-import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
@@ -195,7 +195,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
         if (found) {
             const top = found.getBoundingClientRect().top;
-            const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
+            const localTop = this.ScreenToLocalBoxXf().transformPoint(0, top);
             if (Math.floor(localTop[1]) !== 0 && Math.ceil(this.props.PanelHeight()) < (this._mainCont?.scrollHeight || 0)) {
                 let focusSpeed = options.zoomTime ?? 500;
                 smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
@@ -204,7 +204,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         }
     };
 
-    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
+    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
         switch (property) {
             case StyleProp.BoxShadow:
                 if (doc && DragManager.docsBeingDragged.includes(doc)) {
@@ -282,7 +282,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         const y = this._scroll; // required for document decorations to update when the text box container is scrolled
         const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined);
         // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform.  so we have to subtract that off
-        return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale);
+        return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.ScreenToLocalBoxXf().Scale);
     }
 
     // how to get the width of a document. Currently returns the width of the column (minus margins)
@@ -341,10 +341,10 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     onPointerMove = (force: boolean, ex: number, ey: number) => {
         if (this.childDocList?.includes(DragManager.DocDragData?.draggedDocuments?.lastElement() as any) || force || SnappingManager.CanEmbed) {
             // get the current docs for the column based on the mouse's x coordinate
-            const xCoord = this.props.ScreenToLocalTransform().transformPoint(ex, ey)[0] - 2 * this.gridGap;
+            const xCoord = this.ScreenToLocalBoxXf().transformPoint(ex, ey)[0] - 2 * this.gridGap;
             const colDocs = this.getDocsFromXCoord(xCoord);
             // get the index for where you need to insert the doc you are currently dragging
-            const clientY = this.props.ScreenToLocalTransform().transformPoint(ex, ey)[1];
+            const clientY = this.ScreenToLocalBoxXf().transformPoint(ex, ey)[1];
             let dropInd = -1;
             let pos0 = (this.refList.lastElement() as HTMLDivElement).children[0].getBoundingClientRect().height + this.yMargin * 2;
             colDocs.forEach((doc, i) => {
@@ -437,7 +437,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                     if (rowCol[0] <= 0) {
                         docs.splice(0, 0, ...newDocs);
                     } else {
-                        const colDocs = this.getDocsFromXCoord(this.props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0]);
+                        const colDocs = this.getDocsFromXCoord(this.ScreenToLocalBoxXf().transformPoint(de.x, de.y)[0]);
                         const previousDoc = colDocs[rowCol[0] - 1];
                         const previousDocIndex = docs.indexOf(previousDoc);
                         docs.splice(previousDocIndex + 1, 0, ...newDocs);
@@ -546,7 +546,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 yMargin={this.yMargin}
                 type={type}
                 createDropTarget={this.createDashEventsTarget}
-                screenToLocalTransform={this.props.ScreenToLocalTransform}
+                screenToLocalTransform={this.ScreenToLocalBoxXf}
                 editableViewProps={this.editableViewProps}
             />
         );
@@ -586,7 +586,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     // setColumnStartXCoords is used to update column widths when using the drag handlers between columns
     @action
     setColumnStartXCoords = (movementXScreen: number, colIndex: number) => {
-        const movementX = this.props.ScreenToLocalTransform().transformDirection(movementXScreen, 0)[0];
+        const movementX = this.ScreenToLocalBoxXf().transformDirection(movementXScreen, 0)[0];
         const leftHeader = this.colHeaderData[colIndex];
         const rightHeader = this.colHeaderData[colIndex + 1];
         leftHeader.setWidth(leftHeader.width + movementX / this.availableWidth);
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index a63688354..d99b4f9de 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -23,11 +23,12 @@ import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { CollectionSubView } from '../collections/CollectionSubView';
 import { LightboxView } from '../LightboxView';
 import { AudioWaveform } from '../nodes/audio/AudioWaveform';
-import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../nodes/DocumentView';
+import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
 import { LabelBox } from '../nodes/LabelBox';
 import { VideoBox } from '../nodes/VideoBox';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import './CollectionStackedTimeline.scss';
+import { FieldViewProps } from '../nodes/FieldView';
 
 export type CollectionStackedTimelineProps = {
     Play: () => void;
@@ -367,7 +368,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number) {
         if (super.onInternalDrop(e, de)) {
             // determine x coordinate of drop and assign it to the documents being dragged  --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view
-            const localPt = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+            const localPt = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y);
             const x = localPt[0] - docDragData.offset[0];
             const timelinePt = this.toTimeline(x + this._scroll, this.timelineContentWidth);
             docDragData.droppedDocuments.forEach(drop => {
@@ -502,7 +503,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         return this._props.PanelWidth() * this.zoomFactor;
     } // subtract size of container border
 
-    dictationScreenToLocalTransform = () => this._props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight);
+    dictationScreenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, -this.timelineContentHeight);
 
     isContentActive = () => this._props.isSelected() || this._props.isContentActive();
 
@@ -672,7 +673,7 @@ interface StackedTimelineAnchorProps {
     width: number;
     height: number;
     toTimeline: (screen_delta: number, width: number) => number;
-    styleProvider?: (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => any;
+    styleProvider?: StyleProviderFunc;
     playLink: (linkDoc: Doc, options: DocFocusOptions) => void;
     setTime: (time: number) => void;
     startTag: string;
@@ -695,7 +696,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
     _lastTimecode: number;
     _disposer: IReactionDisposer | undefined;
 
-    constructor(props: any) {
+    constructor(props: StackedTimelineAnchorProps) {
         super(props);
         makeObservable(this);
         this._lastTimecode = this._props.currentTimecode();
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 16adf4b96..995f071ca 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -24,7 +24,7 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
 import { LightboxView } from '../LightboxView';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
-import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
@@ -255,7 +255,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
         if (found) {
             const top = found.getBoundingClientRect().top;
-            const localTop = this._props.ScreenToLocalTransform().transformPoint(0, top);
+            const localTop = this.ScreenToLocalBoxXf().transformPoint(0, top);
             if (Math.floor(localTop[1]) !== 0) {
                 let focusSpeed = options.zoomTime ?? 500;
                 smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
@@ -265,7 +265,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         return undefined;
     };
 
-    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
+    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
         if (property === StyleProp.Opacity && doc) {
             if (this._props.childOpacity) {
                 return this._props.childOpacity();
@@ -370,7 +370,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         this._scroll; // must be referenced for document decorations to update when the text box container is scrolled
         const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv);
         // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform.  so we have to subtract that off
-        return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this._props.ScreenToLocalTransform().Scale);
+        return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.ScreenToLocalBoxXf().Scale);
     }
     getDocWidth(d?: Doc) {
         if (!d) return 0;
@@ -567,7 +567,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 yMargin={this.yMargin}
                 type={type}
                 createDropTarget={this.createDashEventsTarget}
-                screenToLocalTransform={this._props.ScreenToLocalTransform}
+                screenToLocalTransform={this.ScreenToLocalBoxXf}
             />
         );
     };
@@ -611,7 +611,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 parent={this}
                 type={type}
                 createDropTarget={this.createDashEventsTarget}
-                screenToLocalTransform={this._props.ScreenToLocalTransform}
+                screenToLocalTransform={this.ScreenToLocalBoxXf}
                 setDocHeight={this.setDocHeight}
             />
         );
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 7bbdb7073..7036ec41c 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -211,7 +211,6 @@ export class CollectionTimeView extends CollectionSubView() {
         Array.from(keySet).map(fieldKey => docItems.push({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' }));
         docItems.push({ description: ':default', event: () => (this.layoutDoc._pivotField = undefined), icon: 'compress-arrows-alt' });
         ContextMenu.Instance.addItem({ description: 'Pivot Fields ...', subitems: docItems, icon: 'eye' });
-        const pt = this._props.ScreenToLocalTransform().inverse().transformPoint(x, y);
         ContextMenu.Instance.displayMenu(x, y, ':');
     };
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 7f8d42088..18e0b98ef 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -154,7 +154,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         }
     };
 
-    screenToLocalTransform = () => this._props.ScreenToLocalTransform().translate(0, -this._headerHeight);
+    screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, -this._headerHeight);
 
     @action
     remove = (doc: Doc | Doc[]): boolean => {
@@ -312,7 +312,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         return this.dataDoc === null ? null : (
             <div
                 className="collectionTreeView-titleBar"
-                ref={action((r: any) => (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this._props.ScreenToLocalTransform().Scale))}
+                ref={action((r: any) => (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this.ScreenToLocalBoxXf().Scale))}
                 key={this.Document[Id]}
                 style={!this.outlineMode ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}}>
                 {this.outlineMode ? this.documentTitle : this.editableTitle}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index c2f5ab2c0..4a239e4b1 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -112,7 +112,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
         return viewField as any as CollectionViewType;
     }
 
-    screenToLocalTransform = () => (this._props.renderDepth ? this._props.ScreenToLocalTransform() : this._props.ScreenToLocalTransform().scale(this._props.PanelWidth() / this.bodyPanelWidth()));
+    screenToLocalTransform = () => (this._props.renderDepth ? this.ScreenToLocalBoxXf() : this.ScreenToLocalBoxXf().scale(this._props.PanelWidth() / this.bodyPanelWidth()));
     // prettier-ignore
     private renderSubView = (type: CollectionViewType | undefined, props: SubCollectionViewProps) => {
         TraceMobx();
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index ac0ce7a2a..80808be92 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -29,7 +29,7 @@ import { LightboxView } from '../LightboxView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
 import { Colors } from '../global/globalEnums';
-import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
 import { DashFieldView } from '../nodes/formattedText/DashFieldView';
 import { PinProps, PresBox, PresMovement } from '../nodes/trails';
@@ -37,6 +37,7 @@ import { CollectionDockingView } from './CollectionDockingView';
 import { CollectionView } from './CollectionView';
 import './TabDocView.scss';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { FieldViewProps } from '../nodes/FieldView';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 interface TabDocViewProps {
@@ -426,7 +427,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     ScreenToLocalTransform = () => {
         this._forceInvalidateScreenToLocal;
         const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
-        return CollectionDockingView.Instance?._props.ScreenToLocalTransform().translate(-translateX, -translateY) ?? Transform.Identity();
+        return CollectionDockingView.Instance?.ScreenToLocalBoxXf().translate(-translateX, -translateY) ?? Transform.Identity();
     };
     PanelWidth = () => this._panelWidth;
     PanelHeight = () => this._panelHeight;
@@ -527,7 +528,7 @@ class TabMiniThumb extends React.Component<TabMiniThumbProps> {
 }
 @observer
 export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps> {
-    static miniStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
+    static miniStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
         if (doc) {
             switch (property.split(':')[0]) {
                 default:
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index f2ceaa681..01b80e209 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -27,7 +27,7 @@ import { UndoManager, undoBatch, undoable } from '../../util/UndoManager';
 import { EditableView } from '../EditableView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { StyleProp } from '../StyleProvider';
-import { DocumentView, DocumentViewInternal, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, DocumentViewInternalProps, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
@@ -172,6 +172,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         return this._docRef?.IsSelected;
     }
 
+    ScreenToLocalTransform = () => this._props.ScreenToLocalTransform();
+
     childDocList(field: string) {
         const layout = Cast(Doc.LayoutField(this.Document), Doc, null);
         return DocListCast(this._props.dataDoc?.[field], DocListCast(layout?.[field], DocListCast(this.Document[field])));
@@ -447,11 +449,11 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
     }
 
     refTransform = (ref: HTMLDivElement | undefined | null) => {
-        if (!ref) return this._props.ScreenToLocalTransform();
+        if (!ref) return this.ScreenToLocalTransform();
         const { scale, translateX, translateY } = Utils.GetScreenTransform(ref);
         const outerXf = Utils.GetScreenTransform(this.treeView.MainEle());
-        const offset = this._props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
-        return this._props.ScreenToLocalTransform().translate(offset[0], offset[1]);
+        const offset = this.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
+        return this.ScreenToLocalTransform().translate(offset[0], offset[1]);
     };
     docTransform = () => this.refTransform(this._dref?.ContentRef?.current);
     getTransform = () => this.refTransform(this._tref.current);
@@ -511,7 +513,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                     this._props.dragAction,
                     this._props.addDocTab,
                     this.titleStyleProvider,
-                    this._props.ScreenToLocalTransform,
+                    this.ScreenToLocalTransform,
                     this._props.isContentActive,
                     expandedWidth,
                     this._props.renderDepth,
@@ -671,7 +673,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                                   StrCast(this.Document.childDragAction, this._props.dragAction) as dropActionType,
                                   this._props.addDocTab,
                                   this.titleStyleProvider,
-                                  this._props.ScreenToLocalTransform,
+                                  this.ScreenToLocalTransform,
                                   this._props.isContentActive,
                                   this._props.panelWidth,
                                   this._props.renderDepth,
@@ -865,7 +867,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             e.preventDefault();
         }
     };
-    titleStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
+    titleStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
         if (!doc || doc !== this.Document) return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
 
         const treeView = this.treeView;
@@ -897,7 +899,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         }
         return treeView._props.styleProvider?.(doc, props, property);
     };
-    embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
+    embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
         if (property.startsWith(StyleProp.Decorations)) return null;
         return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
     };
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 6131fe037..5204633ea 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -22,8 +22,6 @@ export interface CollectionFreeFormLinkViewProps {
     LinkDocs: Doc[];
 }
 
-// props.screentolocatransform
-
 @observer
 export class CollectionFreeFormLinkView extends ObservableReactComponent<CollectionFreeFormLinkViewProps> {
     @observable _opacity: number = 0;
@@ -42,10 +40,10 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent<Collect
     componentDidMount() {
         this._anchorDisposer = reaction(
             () => [
-                this._props.A._props.ScreenToLocalTransform(),
+                this._props.A.screenToViewTransform(),
                 Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
                 Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.[DocCss],
-                this._props.B._props.ScreenToLocalTransform(),
+                this._props.B.screenToViewTransform(),
                 Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
                 Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.[DocCss],
             ],
@@ -91,7 +89,7 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent<Collect
             }
         } else {
             const m = targetAhyperlink.getBoundingClientRect();
-            const mp = A._props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+            const mp = A.screenToViewTransform().transformPoint(m.right, m.top + 5);
             const mpx = mp[0] / A._props.PanelWidth();
             const mpy = mp[1] / A._props.PanelHeight();
             if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_1_x = mpx * 100;
@@ -106,7 +104,7 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent<Collect
             }
         } else {
             const m = targetBhyperlink.getBoundingClientRect();
-            const mp = B._props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+            const mp = B.screenToViewTransform().transformPoint(m.right, m.top + 5);
             const mpx = mp[0] / B._props.PanelWidth();
             const mpy = mp[1] / B._props.PanelHeight();
             if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_2_x = mpx * 100;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 44654c8fe..0128647a4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -38,7 +38,7 @@ import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } fr
 import { LightboxView } from '../../LightboxView';
 import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewWrapper } from '../../nodes/CollectionFreeFormDocumentView';
 import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
-import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView';
 import { FieldViewProps } from '../../nodes/FieldView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../../nodes/trails/PresBox';
@@ -172,10 +172,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @computed get panZoomXf() {
         return new Transform(this.panX(), this.panY(), 1 / this.zoomScaling());
     }
-    @computed get screenToLocalXf() {
+    @computed get screenToFreeformContentsXf() {
         return this._props
-            .ScreenToLocalTransform()
-            .scale(this._props.isAnnotationOverlay ? 1 : 1)
+            .ScreenToLocalTransform() //
             .translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY)
             .transform(this.panZoomXf);
     }
@@ -242,7 +241,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1));
     PanZoomCenterXf = () =>
         this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
-    ScreenToLocalXf = () => this.screenToLocalXf.copy();
+    ScreenToContentsXf = () => this.screenToFreeformContentsXf.copy();
     getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
     isAnyChildContentActive = () => this._props.isAnyChildContentActive();
     addLiveTextBox = (newDoc: Doc) => {
@@ -329,13 +328,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             findDoc(dv => res(dv));
         });
 
-    internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) {
+    internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData) {
         if (!super.onInternalDrop(e, de)) return false;
         const refDoc = docDragData.droppedDocuments[0];
-        const [xpo, ypo] = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y);
-        const z = NumCast(refDoc.z);
-        const x = (z ? xpo : xp) - docDragData.offset[0];
-        const y = (z ? ypo : yp) - docDragData.offset[1];
+        const fromScreenXf = NumCast(refDoc.z) ? this.ScreenToLocalBoxXf() : this.screenToFreeformContentsXf;
+        const [xpo, ypo] = fromScreenXf.transformPoint(de.x, de.y);
+        const x = xpo - docDragData.offset[0];
+        const y = ypo - docDragData.offset[1];
         const zsorted = this.childLayoutPairs
             .map(pair => pair.layout)
             .slice()
@@ -347,7 +346,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
             const d = docDragData.droppedDocuments[i];
             const layoutDoc = Doc.Layout(d);
-            const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], this._props.ScreenToLocalTransform().Rotate);
+            const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], fromScreenXf.Rotate);
             if (this.Document._currentFrame !== undefined) {
                 CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false);
                 const pvals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); // get filled in values (uses defaults when not value is specified) for position
@@ -393,8 +392,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     @undoBatch
-    internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData, xp: number, yp: number) {
+    internalAnchorAnnoDrop(e: Event, de: DragManager.DropEvent, annoDragData: DragManager.AnchorAnnoDragData) {
         const dropCreator = annoDragData.dropDocCreator;
+        const [xp, yp] = this.screenToFreeformContentsXf.transformPoint(de.x, de.y);
         annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => {
             const dropDoc = dropCreator(annotationOn);
             if (dropDoc) {
@@ -408,20 +408,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     @undoBatch
-    internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) {
+    internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData) {
         if (linkDragData.linkDragView.props.docViewPath().includes(this._props.docViewPath().lastElement())) {
+            const [x, y] = this.screenToFreeformContentsXf.transformPoint(de.x, de.y);
             let added = false;
             //  do nothing if link is dropped into any freeform view parent of dragged document
             const source =
                 !linkDragData.dragDocument.embedContainer || linkDragData.dragDocument.embedContainer !== this.Document
-                    ? Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' })
+                    ? Docs.Create.TextDocument('', { _width: 200, _height: 75, x, y, title: 'dropped annotation' })
                     : Docs.Create.FontIconDocument({
                           title: 'anchor',
                           icon_label: '',
                           followLinkToggle: true,
                           icon: 'map-pin',
-                          x: xp,
-                          y: yp,
+                          x,
+                          y,
                           backgroundColor: '#ACCEF7',
                           layout_hideAllLinks: true,
                           layout_hideLinkButton: true,
@@ -441,14 +442,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
-        const [xp, yp] = this.screenToLocalXf.transformPoint(de.x, de.y);
-        if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData, xp, yp);
-        else if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp);
-        else if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, xp, yp);
+        if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de, de.complete.annoDragData);
+        else if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData);
+        else if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData);
         return false;
     };
 
-    onExternalDrop = (e: React.DragEvent) => (([x, y]) => super.onExternalDrop(e, { x, y }))(this.screenToLocalXf.transformPoint(e.pageX, e.pageY));
+    onExternalDrop = (e: React.DragEvent) => (([x, y]) => super.onExternalDrop(e, { x, y }))(this.screenToFreeformContentsXf.transformPoint(e.pageX, e.pageY));
 
     static overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
         const doc2Layout = Doc.Layout(doc2);
@@ -489,7 +489,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
                 const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'embed' : undefined);
                 de.moveDocument = this._props.moveDocument;
-                de.offset = this.screenToLocalXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
+                de.offset = this.screenToFreeformContentsXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
                 DragManager.StartDocumentDrag(
                     clusterDocs.map(v => v.ContentDiv!),
                     de,
@@ -586,7 +586,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }
     };
 
-    clusterStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => {
+    clusterStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
         let styleProp = this._props.styleProvider?.(doc, props, property); // bcz: check 'props'  used to be renderDepth + 1
         if (doc && this.childDocList?.includes(doc))
             switch (property) {
@@ -643,7 +643,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         break;
                     case InkTool.None:
                         if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
-                            this._hitCluster = this.pickCluster(this.screenToLocalXf.transformPoint(e.clientX, e.clientY));
+                            this._hitCluster = this.pickCluster(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY));
                             setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false, false);
                         }
                         break;
@@ -664,8 +664,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             case GestureUtils.Gestures.Triangle:
             case GestureUtils.Gestures.Stroke:
                 const points = ge.points;
-                const B = this.screenToLocalXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
-                const inkWidth = ActiveInkWidth() * this._props.ScreenToLocalTransform().Scale;
+                const B = this.screenToFreeformContentsXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
+                const inkWidth = ActiveInkWidth() * this.ScreenToLocalBoxXf().Scale;
                 const inkDoc = Docs.Create.InkDocument(
                     points,
                     { title: ge.gesture.toString(),
@@ -696,7 +696,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 break;
             case GestureUtils.Gestures.Text:
                 if (ge.text) {
-                    const B = this.screenToLocalXf.transformPoint(ge.points[0].X, ge.points[0].Y);
+                    const B = this.screenToFreeformContentsXf.transformPoint(ge.points[0].X, ge.points[0].Y);
                     this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] }));
                     e.stopPropagation();
                 }
@@ -719,7 +719,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 e.preventDefault();
             } else if (this.isContentActive() && e.shiftKey) {
                 // reset zoom of freeform view to 1-to-1 on a shift + double click
-                this.zoomSmoothlyAboutPt(this.screenToLocalXf.transformPoint(e.clientX, e.clientY), 1);
+                this.zoomSmoothlyAboutPt(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY), 1);
                 e.stopPropagation();
                 e.preventDefault();
             }
@@ -738,8 +738,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const shiftKey = e.shiftKey && !e.ctrlKey;
         PresBox.Instance?.pauseAutoPres();
         this._props.DocumentView?.().clearViewTransition();
-        const [dxi, dyi] = this.screenToLocalXf.transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
-        const { x: dx, y: dy } = Utils.rotPt(dxi, dyi, this._props.ScreenToLocalTransform().Rotate);
+        const [dxi, dyi] = this.screenToFreeformContentsXf.transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
+        const { x: dx, y: dy } = Utils.rotPt(dxi, dyi, this.ScreenToLocalBoxXf().Rotate);
         this.setPan(NumCast(this.Document[this.panXFieldKey]) - (ctrlKey ? 0 : dx), NumCast(this.Document[this.panYFieldKey]) - (shiftKey ? 0 : dy), 0, true);
         this._lastX = e.clientX;
         this._lastY = e.clientY;
@@ -940,7 +940,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (this.Document.isGroup || this.Document[(this._props.viewField ?? '_') + 'freeform_noZoom']) return;
         let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05;
         if (deltaScale < 0) deltaScale = -deltaScale;
-        const [x, y] = this.screenToLocalXf.transformPoint(pointX, pointY);
+        const [x, y] = this.screenToFreeformContentsXf.transformPoint(pointX, pointY);
         const invTransform = this.panZoomXf.inverse();
         if (deltaScale * invTransform.Scale > 20) {
             deltaScale = 20 / invTransform.Scale;
@@ -982,7 +982,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 if (((!e.metaKey && !e.altKey) || Doc.UserDoc().freeformScrollMode === freeformScrollMode.Zoom) && this._props.isContentActive(true)) {
                     const deltaX = e.shiftKey ? e.deltaX : e.ctrlKey ? 0 : e.deltaX;
                     const deltaY = e.shiftKey ? 0 : e.ctrlKey ? e.deltaY : e.deltaY;
-                    this.scrollPan({ deltaX: -deltaX * this.screenToLocalXf.Scale, deltaY: e.shiftKey ? 0 : -deltaY * this.screenToLocalXf.Scale });
+                    this.scrollPan({ deltaX: -deltaX * this.screenToFreeformContentsXf.Scale, deltaY: e.shiftKey ? 0 : -deltaY * this.screenToFreeformContentsXf.Scale });
                     break;
                 }
             default:
@@ -1040,9 +1040,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             const minPanY = NumCast(this.dataDoc._freeform_panY_min, 0);
             const maxPanX = NumCast(this.dataDoc._freeform_panX_max, this.nativeWidth);
             const newPanX = Math.min(minPanX + scale * maxPanX, Math.max(minPanX, panX));
-            const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this._props.PanelWidth() - this._props.PanelHeight()) * this._props.ScreenToLocalTransform().Scale) / minScale;
+            const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this._props.PanelWidth() - this._props.PanelHeight()) * this.ScreenToLocalBoxXf().Scale) / minScale;
             const nativeHeight = (this._props.PanelHeight() / this._props.PanelWidth() / (this.nativeHeight / this.nativeWidth)) * this.nativeHeight;
-            const maxScrollTop = this.nativeHeight / this._props.ScreenToLocalTransform().Scale - this._props.PanelHeight();
+            const maxScrollTop = this.nativeHeight / this.ScreenToLocalBoxXf().Scale - this._props.PanelHeight();
             const maxPanY =
                 minPanY + // minPanY + scrolling introduced by view scaling + scrolling introduced by layout_fitWidth
                 scale * NumCast(this.dataDoc._panY_max, nativeHeight) +
@@ -1116,11 +1116,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     zoomSmoothlyAboutPt(docpt: number[], scale: number, transitionTime = 500) {
         if (this.Document.isGroup) return;
         this.setPanZoomTransition(transitionTime);
-        const screenXY = this.screenToLocalXf.inverse().transformPoint(docpt[0], docpt[1]);
+        const screenXY = this.screenToFreeformContentsXf.inverse().transformPoint(docpt[0], docpt[1]);
         this.layoutDoc[this.scaleFieldKey] = scale;
-        const newScreenXY = this.screenToLocalXf.inverse().transformPoint(docpt[0], docpt[1]);
+        const newScreenXY = this.screenToFreeformContentsXf.inverse().transformPoint(docpt[0], docpt[1]);
         const scrDelta = { x: screenXY[0] - newScreenXY[0], y: screenXY[1] - newScreenXY[1] };
-        const newpan = this.screenToLocalXf.transformDirection(scrDelta.x, scrDelta.y);
+        const newpan = this.screenToFreeformContentsXf.transformDirection(scrDelta.x, scrDelta.y);
         this.layoutDoc[this.panXFieldKey] = NumCast(this.layoutDoc[this.panXFieldKey]) - newpan[0];
         this.layoutDoc[this.panYFieldKey] = NumCast(this.layoutDoc[this.panYFieldKey]) - newpan[1];
     }
@@ -1229,7 +1229,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 onKey={this.onKeyDown}
                 onDoubleClick={this.onChildDoubleClickHandler}
                 onBrowseClick={this.onBrowseClickHandler}
-                ScreenToLocalTransform={childLayout.z ? this._props.ScreenToLocalTransform : this.ScreenToLocalXf}
+                ScreenToLocalTransform={childLayout.z ? this.ScreenToLocalBoxXf : this.ScreenToContentsXf}
                 PanelWidth={childLayout[Width]}
                 PanelHeight={childLayout[Height]}
                 childFilters={this.childDocFilters}
@@ -1264,7 +1264,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 return (
                     (this.addDocument?.(
                         (doc instanceof Doc ? [doc] : doc).map(doc => {
-                            const pt = this.screenToLocalXf.transformPoint(NumCast(doc.x), NumCast(doc.y));
+                            const pt = this.screenToFreeformContentsXf.transformPoint(NumCast(doc.x), NumCast(doc.y));
                             doc.x = pt[0];
                             doc.y = pt[1];
                             return doc;
@@ -1437,7 +1437,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @observable _hideInfo = false;
     @action closeInfo = () => (this._hideInfo = true);
-    infoUI = () => (this._hideInfo || this.Document.annotationOn ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} close={this.closeInfo} />);
+    infoUI = () => (this._hideInfo || this.Document.annotationOn || this._props.renderDepth ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} close={this.closeInfo} />);
 
     componentDidMount() {
         this._props.setContentView?.(this);
@@ -1601,7 +1601,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     promoteCollection = () => {
         const childDocs = this.childDocs.slice();
         childDocs.forEach(doc => {
-            const scr = this.screenToLocalXf.inverse().transformPoint(NumCast(doc.x), NumCast(doc.y));
+            const scr = this.screenToFreeformContentsXf.inverse().transformPoint(NumCast(doc.x), NumCast(doc.y));
             doc.x = scr?.[0];
             doc.y = scr?.[1];
         });
@@ -1713,7 +1713,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         visited.add(this.Document);
         showGroupDragTarget && (this.GroupChildDrag = BoolCast(this.Document.isGroup));
         const activeDocs = this.getActiveDocuments();
-        const size = this.screenToLocalXf.transformDirection(this._props.PanelWidth(), this._props.PanelHeight());
+        const size = this.screenToFreeformContentsXf.transformDirection(this._props.PanelWidth(), this._props.PanelHeight());
         const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] };
         const docDims = (doc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) });
         const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect);
@@ -1725,7 +1725,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
         const horizLines: number[] = [];
         const vertLines: number[] = [];
-        const invXf = this.screenToLocalXf.inverse();
+        const invXf = this.screenToFreeformContentsXf.inverse();
         snappableDocs
             .filter(doc => !doc.isGroup && (snapToDraggedDoc || (SnappingManager.IsResizing !== doc && !DragManager.docsBeingDragged.includes(doc))))
             .forEach(doc => {
@@ -1822,8 +1822,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 selectDocuments={this.selectDocuments}
                 addDocument={this.addDocument}
                 addLiveTextDocument={this.addLiveTextBox}
-                getContainerTransform={this._props.ScreenToLocalTransform}
-                getTransform={this.ScreenToLocalXf}
+                getContainerTransform={this.ScreenToLocalBoxXf}
+                getTransform={this.ScreenToContentsXf}
                 panXFieldKey={this.panXFieldKey}
                 panYFieldKey={this.panYFieldKey}
                 isAnnotationOverlay={this.isAnnotationOverlay}>
@@ -1859,7 +1859,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     lightboxPanelWidth = () => Math.max(0, this._props.PanelWidth() - 30);
     lightboxPanelHeight = () => Math.max(0, this._props.PanelHeight() - 30);
-    lightboxScreenToLocal = () => this._props.ScreenToLocalTransform().translate(-15, -15);
+    lightboxScreenToLocal = () => this.ScreenToLocalBoxXf().translate(-15, -15);
     onPassiveWheel = (e: WheelEvent) => {
         const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight);
         const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling;
@@ -1950,7 +1950,7 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
                 }
                 while (parFfview?.Document.isGroup) parFfview = parFfview.props.DocumentView?.().CollectionFreeFormView;
                 const ffview = selfFfview && selfFfview.layoutDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it.  Otherwise, zoom out on the parent ffview
-                ffview?.zoomSmoothlyAboutPt(ffview.screenToLocalXf.transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
+                ffview?.zoomSmoothlyAboutPt(ffview.screenToFreeformContentsXf.transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
                 Doc.linkFollowHighlight(dv?.props.Document, false);
             }
         });
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 0f750c4f8..d078e1d4f 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -130,7 +130,7 @@ export class CollectionGridView extends CollectionSubView() {
      * Maps the x- and y- coordinates of the event to a grid cell.
      */
     screenToCell(sx: number, sy: number) {
-        const pt = this._props.ScreenToLocalTransform().transformPoint(sx, sy);
+        const pt = this.ScreenToLocalBoxXf().transformPoint(sx, sy);
         const x = Math.floor(pt[0] / this.colWidthPlusGap);
         const y = Math.floor((pt[1] + this._scroll) / this.rowHeight);
         return { x, y };
@@ -159,7 +159,7 @@ export class CollectionGridView extends CollectionSubView() {
         const xypos = this.flexGrid ? layout : this.unflexedPosition(this.renderedLayoutList.findIndex(l => l.i === layout.i));
         const pos = { x: xypos.x * this.colWidthPlusGap + this.margin, y: xypos.y * this.rowHeightPlusGap + this.margin - this._scroll };
 
-        return this._props.ScreenToLocalTransform().translate(-pos.x, -pos.y);
+        return this.ScreenToLocalBoxXf().translate(-pos.x, -pos.y);
     };
 
     /**
@@ -390,7 +390,7 @@ export class CollectionGridView extends CollectionSubView() {
                         numCols={this.numCols}
                         rowHeight={this.rowHeight}
                         setLayout={this.setLayout}
-                        transformScale={this._props.ScreenToLocalTransform().Scale}
+                        transformScale={this.ScreenToLocalBoxXf().Scale}
                         compactType={this.compaction} // determines whether nodes should remain in position, be bound to the top, or to the left
                         preventCollision={BoolCast(this.Document.gridPreventCollision)} // determines whether nodes should move out of the way (i.e. collide) when other nodes are dragged over them
                         margin={this.margin}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 5bea59e7b..563084af8 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -189,7 +189,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         let offset = 0;
         for (const { layout: candidate } of this.childLayoutPairs) {
             if (candidate === layout) {
-                return this._props.ScreenToLocalTransform().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0);
+                return this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0);
             }
             offset += this.lookupPixels(candidate) + resizerWidth;
         }
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 3043eb0f8..bf0d39197 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -184,7 +184,7 @@ export class CollectionMultirowView extends CollectionSubView() {
         let offset = 0;
         for (const { layout: candidate } of this.childLayoutPairs) {
             if (candidate === layout) {
-                return this._props.ScreenToLocalTransform().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
+                return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
             }
             offset += this.lookupPixels(candidate) + resizerHeight;
         }
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 2546f5b02..492aed0ea 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -16,7 +16,7 @@ import { undoable, undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
-import { DocFocusOptions, DocumentView, DocumentViewProps } from '../../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../../nodes/DocumentView';
 import { KeyValueBox } from '../../nodes/KeyValueBox';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DefaultStyleProvider, StyleProp } from '../../StyleProvider';
@@ -24,6 +24,7 @@ import { CollectionSubView } from '../CollectionSubView';
 import './CollectionSchemaView.scss';
 import { SchemaColumnHeader } from './SchemaColumnHeader';
 import { SchemaRowBox } from './SchemaRowBox';
+import { FieldViewProps } from '../../nodes/FieldView';
 
 export enum ColumnType {
     Number,
@@ -321,8 +322,8 @@ export class CollectionSchemaView extends CollectionSubView() {
                 change = this._displayColumnWidths[shrinking] - CollectionSchemaView._minColWidth;
             }
 
-            this._displayColumnWidths[shrinking] -= change * this._props.ScreenToLocalTransform().Scale;
-            this._displayColumnWidths[growing] += change * this._props.ScreenToLocalTransform().Scale;
+            this._displayColumnWidths[shrinking] -= change * this.ScreenToLocalBoxXf().Scale;
+            this._displayColumnWidths[growing] += change * this.ScreenToLocalBoxXf().Scale;
 
             return false;
         }
@@ -379,7 +380,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     @action
     highlightDropColumn = (e: PointerEvent) => {
         e.stopPropagation();
-        const mouseX = this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0];
+        const mouseX = this.ScreenToLocalBoxXf().transformPoint(e.clientX, e.clientY)[0];
         const index = this.findDropIndex(mouseX);
         this._colEles.forEach((colRef, i) => {
             let leftStyle = '';
@@ -439,7 +440,7 @@ export class CollectionSchemaView extends CollectionSubView() {
 
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         if (de.complete.columnDragData) {
-            const mouseX = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0];
+            const mouseX = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y)[0];
             const index = this.findDropIndex(mouseX);
             this.moveColumn(de.complete.columnDragData.colIndex, index ?? de.complete.columnDragData.colIndex);
 
@@ -483,7 +484,7 @@ export class CollectionSchemaView extends CollectionSubView() {
         const nativeWidth = this._previewRef!.getBoundingClientRect();
         const minWidth = 40;
         const maxWidth = 1000;
-        const movedWidth = this._props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0];
+        const movedWidth = this.ScreenToLocalBoxXf().transformDirection(nativeWidth.right - e.clientX, 0)[0];
         const width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth;
         this.layoutDoc.schema_previewWidth = width;
         return false;
@@ -507,7 +508,7 @@ export class CollectionSchemaView extends CollectionSubView() {
         const found = this._tableContentRef && Array.from(this._tableContentRef.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
         if (found) {
             const rect = found.getBoundingClientRect();
-            const localRect = this._props.ScreenToLocalTransform().transformBounds(rect.left, rect.top, rect.width, rect.height);
+            const localRect = this.ScreenToLocalBoxXf().transformBounds(rect.left, rect.top, rect.width, rect.height);
             if (localRect.y < this.rowHeightFunc() || localRect.y + localRect.height > this._props.PanelHeight()) {
                 let focusSpeed = options.zoomTime ?? 50;
                 smoothScroll(focusSpeed, this._tableContentRef!, localRect.y + this._tableContentRef!.scrollTop - this.rowHeightFunc(), options.easeFunc);
@@ -829,7 +830,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     rowHeightFunc = () => (BoolCast(this.layoutDoc._schema_singleLine) ? CollectionSchemaView._rowSingleLineHeight : CollectionSchemaView._rowHeight);
     sortedDocsFunc = () => this.sortedDocs;
     isContentActive = () => this._props.isSelected() || this._props.isContentActive();
-    screenToLocal = () => this._props.ScreenToLocalTransform().translate(-this.tableWidth, 0);
+    screenToLocal = () => this.ScreenToLocalBoxXf().translate(-this.tableWidth, 0);
     previewWidthFunc = () => this.previewWidth;
     render() {
         return (
@@ -960,8 +961,8 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
     }
 
     tableWidthFunc = () => this._props.schema.tableWidth;
-    screenToLocalXf = () => this._props.schema._props.ScreenToLocalTransform().translate(0, -this._props.rowHeight() - this._props.index * this._props.rowHeight());
-    noOpacityStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => {
+    screenToLocalXf = () => this._props.schema.ScreenToLocalBoxXf().translate(0, -this._props.rowHeight() - this._props.index * this._props.rowHeight());
+    noOpacityStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
         if (property === StyleProp.Opacity) return 1;
         return DefaultStyleProvider(doc, props, property);
     };
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 567cf193e..908cd5dc0 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -460,7 +460,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @action
     timelineWhenChildContentsActiveChanged = (isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive));
 
-    timelineScreenToLocal = () => this._props.ScreenToLocalTransform().translate(0, -AudioBox.topControlsHeight);
+    timelineScreenToLocal = () => this.ScreenToLocalBoxXf().translate(0, -AudioBox.topControlsHeight);
 
     setPlayheadTime = (time: number) => (this._ele!.currentTime /*= this.layoutDoc._layout_currentTimecode*/ = time);
 
@@ -564,7 +564,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             this._dropDisposer = DragManager.MakeDropTarget(
                 r,
                 (e, de) => {
-                    const [xp, yp] = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+                    const [xp, yp] = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y);
                     de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData, xp);
                 },
                 this.layoutDoc,
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 324a4b8d1..ad5aabc21 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -15,7 +15,8 @@ import { DocComponent } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import './CollectionFreeFormDocumentView.scss';
-import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
+import { DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere } from './DocumentView';
+import { FieldViewProps } from './FieldView';
 
 export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentViewProps {
     x: number;
@@ -148,7 +149,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return this._props.CollectionFreeFormView;
     }
 
-    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
+    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
         if (doc === this.layoutDoc) {
             switch (property) {
                 case StyleProp.Opacity:         return this._props.w_Opacity(); // only change the opacity for this specific document, not its children
@@ -229,7 +230,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @action public float = () => {
         const topDoc = this.Document;
         const containerDocView = this._props.docViewPath().lastElement();
-        const screenXf = containerDocView?.screenToNativeLocalTransform();
+        const screenXf = containerDocView?.screenToContentsTransform();
         if (screenXf) {
             SelectionManager.DeselectAll();
             if (topDoc.z) {
@@ -251,7 +252,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     };
 
     nudge = (x: number, y: number) => {
-        const [locX, locY] = this._props.ScreenToLocalTransform().transformDirection(x, y);
+        const [locX, locY] = this.ScreenToLocalBoxXf().transformDirection(x, y);
         this._props.Document.x = this._props.w_X() + locX;
         this._props.Document.y = this._props.w_Y() + locY;
     };
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index de382fca5..5e7e568b0 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -11,7 +11,7 @@ import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import './ComparisonBox.scss';
-import { DocumentView, DocumentViewProps } from './DocumentView';
+import { DocumentView, DocumentViewInternalProps, DocumentViewProps } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { PinProps, PresBox } from './trails';
 
@@ -88,7 +88,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
 
     @action
     private onPointerMove = ({ movementX }: PointerEvent) => {
-        const width = movementX * this._props.ScreenToLocalTransform().Scale + (this.clipWidth / 100) * this._props.PanelWidth();
+        const width = movementX * this.ScreenToLocalBoxXf().Scale + (this.clipWidth / 100) * this._props.PanelWidth();
         if (width && width > 5 && width < this._props.PanelWidth()) {
             this.layoutDoc[this.clipWidthKey] = (width * 100) / this._props.PanelWidth();
         }
@@ -148,7 +148,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
             e => this.clearDoc(which)
         );
     };
-    docStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
+    docStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
         if (property === StyleProp.PointerEvents) return 'none';
         return this._props.styleProvider?.(doc, props, property);
     };
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 5365fe1b2..ed44d9269 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -80,7 +80,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
     };
 
     @computed get viewScale() {
-        return this._props.docView?.()?._props.ScreenToLocalTransform().Scale || 1;
+        return this._props.docView?.()?.screenToViewTransform().Scale || 1;
     }
     @computed get rowHeight() {
         console.log('scale = ' + this.viewScale + '  table = ' + this._tableHeight + ' ids = ' + this._tableDataIds.length);
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 165057d21..c549a146a 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -257,7 +257,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
     }
 
     render() {
-        if (this._props.hideCount?.()) return null;
+        if (this.props.hideCount?.()) return null;
         const menuTitle = this._props.StartLink ? 'Drag or tap to start link' : 'Tap to complete link';
         const buttonTitle = 'Tap to view links; double tap to open link collection';
         const title = this._props.ShowCount ? buttonTitle : menuTitle;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7ec0382e4..7f1e547e4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -110,7 +110,7 @@ export interface DocFocusOptions {
     easeFunc?: 'linear' | 'ease'; // transition method for scrolling
 }
 export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => Opt<number>;
-export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => any;
+export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => any;
 export interface DocComponentView {
     fieldKey?: string;
     annotationKey?: string;
@@ -394,11 +394,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             const views = SelectionManager.Views.filter(dv => dv.docView?._mainCont.current);
             const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [this._props.DocumentView()];
             const dragData = new DragManager.DocumentDragData(selected.map(dv => dv.Document));
-            const [left, top] = this._props.ScreenToLocalTransform().scale(this.NativeDimScaling).inverse().transformPoint(0, 0);
-            dragData.offset = this._props
-                .ScreenToLocalTransform()
-                .scale(this.NativeDimScaling)
-                .transformDirection(x - left, y - top);
+            const screenXf = this.props.DocumentView().screenToViewTransform();
+            const [left, top] = screenXf.inverse().transformPoint(0, 0);
+            dragData.offset = screenXf.transformDirection(x - left, y - top);
             dragData.dropAction = dropAction;
             dragData.treeViewDoc = this._props.treeViewDoc;
             dragData.removeDocument = this._props.removeDocument;
@@ -881,7 +879,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
     rootSelected = () => this._rootSelected;
     panelHeight = () => this._props.PanelHeight() - this.headerMargin;
-    contentScreenToLocal = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
+    screenToLocalContent = () => this.ScreenToLocalBoxXf().translate(0, -this.headerMargin);
     onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
     setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = height);
     setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
@@ -925,7 +923,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     PanelHeight={this.panelHeight}
                     setHeight={this.setHeight}
                     isContentActive={this.isContentActive}
-                    ScreenToLocalTransform={this.contentScreenToLocal}
+                    ScreenToLocalTransform={this.screenToLocalContent}
                     rootSelected={this.rootSelected}
                     onClick={this.onClickFunc}
                     setTitleFocus={this.setTitleFocus}
@@ -938,7 +936,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     anchorPanelWidth = () => this._props.PanelWidth() || 1;
     anchorPanelHeight = () => this._props.PanelHeight() || 1;
-    anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
+    anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
         // prettier-ignore
         switch (property.split(':')[0]) {
             case StyleProp.ShowTitle:     return '';
@@ -1073,7 +1071,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     };
 
-    captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
+    captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
     @observable _changingTitleField = false;
     @observable _dropDownInnerWidth = 0;
     fieldsDropdown = (inputOptions: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => {
@@ -1431,11 +1429,20 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
         return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined;
     }
     @computed get hideLinkButton() {
-        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HideLinkBtn + (this.IsSelected ? ':selected' : ''));
+        return (
+            this._props.hideLinkButton ||
+            this._props.renderDepth === -1 || //
+            (this.IsSelected && this._props.renderDepth) ||
+            !this._isHovering ||
+            (!this.IsSelected && this.layoutDoc.layout_hideLinkButton) ||
+            SnappingManager.IsDragging ||
+            SnappingManager.IsResizing
+        );
     }
-    hideLinkCount = () => false; // this._props.renderDepth === -1 || (this.IsSelected && this._props.renderDepth) || !this._isHovering || this.hideLinkButton;
+    hideLinkCount = () => (this.hideLinkButton ? true : false);
+
     @computed get linkCountView() {
-        return <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.scaleToScreenSpace} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
+        return <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
     }
     @computed get docViewPath(): DocumentView[] {
         return this._props.docViewPath ? [...this._props.docViewPath(), this] : [this];
@@ -1508,7 +1515,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
         if (this.docView._componentView?.screenBounds?.()) {
             return this.docView._componentView.screenBounds();
         }
-        const xf = this.docView._props.ScreenToLocalTransform().scale(this.nativeScaling).inverse();
+        const xf = this.docView.ScreenToLocalBoxXf().scale(this.nativeScaling).inverse();
         const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
 
         if (this.docView._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
@@ -1566,7 +1573,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
     };
 
     layout_fitWidthFunc = (doc: Doc) => BoolCast(this.layout_fitWidth);
-    scaleToScreenSpace = () => this._props.ScreenToLocalTransform().Scale;
+    screenToLocalScale = () => this._props.ScreenToLocalTransform().Scale;
     docViewPathFunc = () => this.docViewPath;
     isSelected = () => this.IsSelected;
     select = (extendSelection: boolean, focusSelection?: boolean) => {
@@ -1589,7 +1596,14 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
     PanelHeight = () => this.panelHeight;
     NativeDimScaling = () => this.nativeScaling;
     selfView = () => this;
-    screenToNativeLocalTransform = () =>
+    /**
+     * @returns Transform to the document view (in the coordinate system of whatever contains the DocumentView)
+     */
+    screenToViewTransform = () => this._props.ScreenToLocalTransform();
+    /**
+     * @returns Transform to the coordinate system of the contents of the document view (includes native dimension scaling and centering)
+     */
+    screenToContentsTransform = () =>
         this._props
             .ScreenToLocalTransform()
             .translate(-this.centeringX, -this.centeringY)
@@ -1676,7 +1690,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                             isSelected={this.isSelected}
                             select={this.select}
                             layout_fitWidth={this.layout_fitWidthFunc}
-                            ScreenToLocalTransform={this.screenToNativeLocalTransform}
+                            ScreenToLocalTransform={this.screenToContentsTransform}
                             focus={this._props.focus || emptyFunction}
                             ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))}
                         />
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 28c614786..a5853499f 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -90,7 +90,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         this._disposers.sizer = reaction(
             () => ({
                 forceFull: this._props.renderDepth < 1 || this.layoutDoc._showFullRes,
-                scrSize: (this._props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth) * NumCast(this.layoutDoc._freeform_scale, 1),
+                scrSize: (this.ScreenToLocalBoxXf().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth) * NumCast(this.layoutDoc._freeform_scale, 1),
                 selected: this._props.isSelected(),
             }),
             ({ forceFull, scrSize, selected }) => (this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o'),
@@ -158,7 +158,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     @undoBatch
     setNativeSize = action(() => {
-        const scaling = (this._props.DocumentView?.()._props.ScreenToLocalTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
+        const scaling = (this._props.DocumentView?.().screenToViewTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
         const nscale = NumCast(this._props.PanelWidth()) / scaling;
         const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
         this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw;
@@ -366,7 +366,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         TraceMobx();
         return <div className="imageBox-annotationLayer" style={{ height: this._props.PanelHeight() }} ref={this._annotationLayer} />;
     }
-    screenToLocalTransform = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this._props.ScreenToLocalTransform().Scale);
+    screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.ScreenToLocalBoxXf().Scale);
     marqueeDown = (e: React.PointerEvent) => {
         if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             setupMoveUpEvents(
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 134f2e14a..7f1d41547 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -38,8 +38,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
     screenBounds = () => {
         if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.CollectionFreeFormView) {
-            const a_invXf = this.anchor1._props.ScreenToLocalTransform().inverse();
-            const b_invXf = this.anchor2._props.ScreenToLocalTransform().inverse();
+            const a_invXf = this.anchor1.screenToViewTransform().inverse();
+            const b_invXf = this.anchor2.screenToViewTransform().inverse();
             const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(this.anchor1.Document._width), NumCast(this.anchor1.Document._height)) };
             const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(this.anchor2.Document._width), NumCast(this.anchor2.Document._height)) };
 
@@ -67,9 +67,9 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     const b = (this.anchor2 ?? this.anchor1)!;
 
                     const parxf = this._props.docViewPath()[this._props.docViewPath().length - 2].ComponentView as CollectionFreeFormView;
-                    const this_xf = parxf?.screenToLocalXf ?? Transform.Identity; //this._props.ScreenToLocalTransform();
-                    const a_invXf = a._props.ScreenToLocalTransform().inverse();
-                    const b_invXf = b._props.ScreenToLocalTransform().inverse();
+                    const this_xf = parxf?.screenToFreeformContentsXf ?? Transform.Identity; //this.ScreenToLocalTransform();
+                    const a_invXf = a.screenToViewTransform().inverse();
+                    const b_invXf = b.screenToViewTransform().inverse();
                     const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(a.Document._width), NumCast(a.Document._height)) };
                     const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(b.Document._width), NumCast(b.Document._height)) };
                     const a_bds = { tl: this_xf.transformPoint(a_scrBds.tl[0], a_scrBds.tl[1]), br: this_xf.transformPoint(a_scrBds.br[0], a_scrBds.br[1]) };
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index ffd52fb0e..41befbbfe 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -431,7 +431,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
-    scrollXf = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+    scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
     transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
     opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter];
     infoWidth = () => this._props.PanelWidth() / 5;
@@ -531,7 +531,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPinOrRoute.latitude, this.selectedPinOrRoute.longitude));
         const x = point.x + (this._props.PanelWidth() - this.sidebarWidth()) / 2;
         const y = point.y + this._props.PanelHeight() / 2 + 32;
-        const cpt = this._props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+        const cpt = this.ScreenToLocalBoxXf().inverse().transformPoint(x, y);
         MapAnchorMenu.Instance.jumpTo(cpt[0], cpt[1], true);
 
         document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
@@ -820,7 +820,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 let target = document.elementFromPoint(e.x, e.y); // element for specified x and y coordinates
                 while (target) {
                     if (target === this._ref.current) {
-                        const cpt = this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+                        const cpt = this.ScreenToLocalBoxXf().transformPoint(e.clientX, e.clientY);
                         const x = cpt[0] - (this._props.PanelWidth() - this.sidebarWidth()) / 2;
                         const y = cpt[1] - 20 /* height of search bar */ - this._props.PanelHeight() / 2;
                         const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
@@ -1549,7 +1549,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             return null;
         }
         const scale = this._props.NativeDimScaling?.() || 1;
-        const parscale = scale === 1 ? 1 : this._props.ScreenToLocalTransform().Scale ?? 1;
+        const parscale = scale === 1 ? 1 : this.ScreenToLocalBoxXf().Scale ?? 1;
 
         const renderAnnotations = (childFilters?: () => string[]) => null;
         return (
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index 722a347f1..9734d9db1 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -517,7 +517,7 @@
 
 //     panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
 //     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
-//     scrollXf = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+//     scrollXf = () => this.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
 //     transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
 //     opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter];
 //     infoWidth = () => this._props.PanelWidth() / 5;
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index 2c31bbab7..8b22a1531 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -280,7 +280,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
 
     panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
-    scrollXf = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+    scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
     transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
     opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter];
     infoWidth = () => this._props.PanelWidth() / 5;
@@ -401,7 +401,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
         const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPin.latitude, this.selectedPin.longitude));
         const x = point.x + (this._props.PanelWidth() - this.sidebarWidth()) / 2;
         const y = point.y + this._props.PanelHeight() / 2 + 32;
-        const cpt = this._props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+        const cpt = this.ScreenToLocalBoxXf().inverse().transformPoint(x, y);
         MapAnchorMenu.Instance.jumpTo(cpt[0], cpt[1], true);
 
         document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
@@ -677,7 +677,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
                 let target = document.elementFromPoint(e.x, e.y);
                 while (target) {
                     if (target === this._ref.current) {
-                        const cpt = this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+                        const cpt = this.ScreenToLocalBoxXf().transformPoint(e.clientX, e.clientY);
                         const x = cpt[0] - (this._props.PanelWidth() - this.sidebarWidth()) / 2;
                         const y = cpt[1] - 32 /* height of search bar */ - this._props.PanelHeight() / 2;
                         const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 7f1d6b049..55a459a5c 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -502,7 +502,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     sidebarNativeHeightFunc = () => this.sidebarNativeHeight;
     sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
     sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey);
-    sidebarScreenToLocal = () => this._props.ScreenToLocalTransform().translate((this.sidebarWidth() - this._props.PanelWidth()) / this.pdfScale, 0);
+    sidebarScreenToLocal = () => this.ScreenToLocalBoxXf().translate((this.sidebarWidth() - this._props.PanelWidth()) / this.pdfScale, 0);
     @computed get sidebarCollection() {
         const renderComponent = (tag: string) => {
             const ComponentTag = tag === CollectionViewType.Freeform ? CollectionFreeFormView : CollectionStackingView;
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 36527c311..f74e6fb2b 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -313,7 +313,6 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                             removeDocument={returnFalse}
                             moveDocument={returnFalse}
                             addDocument={returnFalse}
-                            ScreenToLocalTransform={this._props.ScreenToLocalTransform}
                             renderDepth={this._props.renderDepth + 1}>
                             <>
                                 {this.threed}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 7ab954b3f..ce73d9f37 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -682,7 +682,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             action(encodeURIComponent => {
                 this._clicking = false;
                 if (this._props.isContentActive()) {
-                    // const local = this._props.ScreenToLocalTransform().scale(this._props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
+                    // const local = this.ScreenToLocalTransform().scale(this._props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
                     // this.layoutDoc._layout_timelineHeightPercent = Math.max(0, Math.min(100, local[1] / this._props.PanelHeight() * 100));
 
                     this.layoutDoc._layout_timelineHeightPercent = 80;
@@ -922,7 +922,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     // renders video controls
     componentUI = (boundsLeft: number, boundsTop: number) => {
-        const xf = this._props.ScreenToLocalTransform().inverse();
+        const xf = this.ScreenToLocalBoxXf().inverse();
         const height = this._props.PanelHeight();
         const vidHeight = (height * this.heightPercent) / 100 / this.scaling();
         const vidWidth = this._props.PanelWidth() / this.scaling();
@@ -940,7 +940,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 <div
                     className="videoBox-ui"
                     style={{
-                        transform: `rotate(${this._props.ScreenToLocalTransform().inverse().RotateDeg}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
+                        transform: `rotate(${this.ScreenToLocalBoxXf().inverse().RotateDeg}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
                         left: xPos,
                         top: yMid,
                         height: uiHeight,
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index a61837dad..3ad9f7634 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -33,7 +33,7 @@ import { Annotation } from '../pdf/Annotation';
 import { GPTPopup } from '../pdf/GPTPopup/GPTPopup';
 import { SidebarAnnos } from '../SidebarAnnos';
 import { StyleProp } from '../StyleProvider';
-import { DocComponentView, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
+import { DocComponentView, DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { LinkInfo } from './LinkDocPreview';
 import { PinProps, PresBox } from './trails';
@@ -250,7 +250,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @action
     createTextAnnotation = (sel: Selection, selRange: Range | undefined) => {
         if (this._mainCont.current && selRange) {
-            if (this.dataDoc[this._props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToNativeLocalTransform().RotateDeg)}deg)`;
+            if (this.dataDoc[this._props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToContentsTransform().RotateDeg)}deg)`;
             const clientRects = selRange.getClientRects();
             for (let i = 0; i < clientRects.length; i++) {
                 const rect = clientRects.item(i);
@@ -258,7 +258,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 if (rect && rect.width !== this._mainCont.current.clientWidth) {
                     const annoBox = document.createElement('div');
                     annoBox.className = 'marqueeAnnotator-annotationBox';
-                    const scale = this._url ? 1 : this._props.ScreenToLocalTransform().Scale;
+                    const scale = this._url ? 1 : this.ScreenToLocalBoxXf().Scale;
                     // transforms the positions from screen onto the pdf div
                     annoBox.style.top = ((rect.top - mainrect.translateY) * scale + (this._url ? this._mainCont.current.scrollTop : NumCast(this.layoutDoc.layout_scrollTop))).toString();
                     annoBox.style.left = ((rect.left - mainrect.translateX) * scale).toString();
@@ -771,7 +771,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @observable lighttext = false;
 
     @computed get urlContent() {
-        if (this._props.ScreenToLocalTransform().Scale > 25) return <div></div>;
+        if (this.ScreenToLocalBoxXf().Scale > 25) return <div></div>;
         setTimeout(
             action(() => {
                 if (this._initialScroll === undefined && !this._webPageHasBeenRendered) {
@@ -1061,11 +1061,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
     panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth;
     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
-    scrollXf = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+    scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
     transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
     opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed ? [] : [Utils.OpaqueBackgroundFilter])];
-    childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
+    childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
             if (this.inlineTextAnnotations.includes(doc)) return 'none';
         }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 8bf8abafa..66802d198 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,7 +1,6 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { isEqual } from 'lodash';
 import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { baseKeymap, selectAll } from 'prosemirror-commands';
@@ -21,12 +20,10 @@ import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
 import { PrefetchProxy } from '../../../../fields/Proxy';
 import { RichTextField } from '../../../../fields/RichTextField';
-import { RichTextUtils } from '../../../../fields/RichTextUtils';
 import { ComputedField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
-import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
 import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
 import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
@@ -46,7 +43,6 @@ import { CollectionTreeView } from '../../collections/CollectionTreeView';
 import { ContextMenu } from '../../ContextMenu';
 import { ContextMenuProps } from '../../ContextMenuItem';
 import { ViewBoxAnnotatableComponent } from '../../DocComponent';
-import { DocumentButtonBar } from '../../DocumentButtonBar';
 import { Colors } from '../../global/globalEnums';
 import { LightboxView } from '../../LightboxView';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
@@ -71,9 +67,7 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
 import { RichTextRules } from './RichTextRules';
 import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
-export const GoogleRef = 'googleDocId';
 // import * as applyDevTools from 'prosemirror-dev-tools';
-type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
 
 export interface FormattedTextBoxProps {}
 @observer
@@ -1189,15 +1183,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 this._cachedLinks = newLinks;
             }
         );
-        this._disposers.buttonBar = reaction(
-            () => DocumentButtonBar.Instance,
-            instance => {
-                if (instance) {
-                    this.pullFromGoogleDoc(this.checkState);
-                    this.dataDoc[GoogleRef] && this.dataDoc.googleDocUnchanged && runInAction(() => (instance.isAnimatingFetch = true));
-                }
-            }
-        );
         this._disposers.editorState = reaction(
             () => {
                 const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc?.proto), this.fieldKey) ? DocCast(this.layoutDoc?.proto) : this?.dataDoc;
@@ -1218,24 +1203,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 }
             }
         );
-        this._disposers.pullDoc = reaction(
-            () => this._props.Document[Pulls],
-            () => {
-                if (!DocumentButtonBar.hasPulledHack) {
-                    DocumentButtonBar.hasPulledHack = true;
-                    this.pullFromGoogleDoc(this.dataDoc.googleDocUnchanged ? this.checkState : this.updateState);
-                }
-            }
-        );
-        this._disposers.pushDoc = reaction(
-            () => this._props.Document[Pushes],
-            () => {
-                if (!DocumentButtonBar.hasPushedHack) {
-                    DocumentButtonBar.hasPushedHack = true;
-                    this.pushToGoogleDoc();
-                }
-            }
-        );
 
         this._disposers.search = reaction(
             () => Doc.IsSearchMatch(this.Document),
@@ -1295,80 +1262,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         setTimeout(this.tryUpdateScrollHeight, 250);
     }
 
-    pushToGoogleDoc = async () => {
-        this.pullFromGoogleDoc(async (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
-            const modes = GoogleApiClientUtils.Docs.WriteMode;
-            let mode = modes.Replace;
-            let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], 'string');
-            if (!reference) {
-                mode = modes.Insert;
-                reference = { title: StrCast(this.dataDoc.title) };
-            }
-            const redo = async () => {
-                if (this._editorView && reference) {
-                    const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state);
-                    const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
-                    response?.documentId && (this.dataDoc[GoogleRef] = response.documentId);
-                    const pushSuccess = response !== undefined && !('errors' in response);
-                    dataDoc.googleDocUnchanged = pushSuccess;
-                    DocumentButtonBar.Instance.startPushOutcome(pushSuccess);
-                }
-            };
-            const undo = () => {
-                if (exportState && reference) {
-                    const content: GoogleApiClientUtils.Docs.Content = {
-                        text: exportState.text,
-                        requests: [],
-                    };
-                    GoogleApiClientUtils.Docs.write({ reference, content, mode });
-                }
-            };
-            UndoManager.AddEvent({ undo, redo, prop: '' });
-            redo();
-        });
-    };
-
-    pullFromGoogleDoc = async (handler: PullHandler) => {
-        const dataDoc = this.dataDoc;
-        const documentId = StrCast(dataDoc[GoogleRef]);
-        let exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>;
-        if (documentId) {
-            exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc);
-        }
-        exportState && UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
-    };
-
-    updateState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
-        let pullSuccess = false;
-        if (exportState !== undefined) {
-            pullSuccess = true;
-            dataDoc[this.fieldKey] = new RichTextField(JSON.stringify(exportState.state.toJSON()));
-            setTimeout(() => {
-                if (this._editorView) {
-                    const state = this._editorView.state;
-                    const end = state.doc.content.size - 1;
-                    this._editorView.dispatch(state.tr.setSelection(TextSelection.create(state.doc, end, end)));
-                }
-            }, 0);
-            dataDoc.title = exportState.title;
-            this.dataDoc.title_custom = true;
-            dataDoc.googleDocUnchanged = true;
-        } else {
-            delete dataDoc[GoogleRef];
-        }
-        DocumentButtonBar.Instance.startPullOutcome(pullSuccess);
-    };
-
-    checkState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
-        if (exportState && this._editorView) {
-            const equalContent = isEqual(this._editorView.state.doc, exportState.state.doc);
-            const equalTitles = dataDoc.title === exportState.title;
-            const unchanged = equalContent && equalTitles;
-            dataDoc.googleDocUnchanged = unchanged;
-            DocumentButtonBar.Instance.setPullState(unchanged);
-        }
-    };
-
     clipboardTextSerializer = (slice: Slice): string => {
         let text = '',
             separated = true;
@@ -1462,7 +1355,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined;
                     if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) {
                         const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
-                        const scrollPos = scrollRef.scrollTop + shift * self._props.ScreenToLocalTransform().Scale;
+                        const scrollPos = scrollRef.scrollTop + shift * self.ScreenToLocalBoxXf().Scale;
                         if (this._focusSpeed !== undefined) {
                             scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed, scrollRef, scrollPos, 'ease', this._scrollStopper));
                         } else {
diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx
index 1a92acea1..7d0086c0c 100644
--- a/src/client/views/nodes/importBox/ImportElementBox.tsx
+++ b/src/client/views/nodes/importBox/ImportElementBox.tsx
@@ -13,7 +13,7 @@ export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return FieldView.LayoutString(ImportElementBox, fieldKey);
     }
 
-    screenToLocalXf = () => this._props.ScreenToLocalTransform().scale(1 * (this._props.NativeDimScaling?.() || 1));
+    screenToLocalXf = () => this.ScreenToLocalBoxXf().scale(1 * (this._props.NativeDimScaling?.() || 1));
     @computed get mainItem() {
         return (
             <div style={{ backgroundColor: 'pink' }}>
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 0305689e7..6fa64a765 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -979,7 +979,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     enterMinimize = () => {
         this.updateCurrentPresentation(this.Document);
         clearTimeout(this._presTimer);
-        const pt = this._props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+        const pt = this.ScreenToLocalBoxXf().inverse().transformPoint(0, 0);
         this._props.removeDocument?.(this.layoutDoc);
         return PresBox.OpenPresMinimized(this.Document, [pt[0] + (this._props.PanelWidth() - 250), pt[1] + 10]);
     };
@@ -1076,7 +1076,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     childLayoutTemplate = () => Docs.Create.PresElementBoxDocument();
     removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.Document, this.fieldKey, doc);
-    getTransform = () => this._props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
+    getTransform = () => this.ScreenToLocalBoxXf().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
     panelHeight = () => this._props.PanelHeight() - 40;
     /**
      * For sorting the array so that the order is maintained when it is dropped.
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index ec5d090dd..4945d66c8 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -19,7 +19,7 @@ import { TreeView } from '../../collections/TreeView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
-import { DocumentView, DocumentViewProps } from '../../nodes/DocumentView';
+import { DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../../nodes/FieldView';
 import { StyleProp } from '../../StyleProvider';
 import { PresBox } from './PresBox';
@@ -97,7 +97,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     presExpandDocumentClick = () => (this.slideDoc.presentation_expandInlineButton = !this.slideDoc.presentation_expandInlineButton);
     embedHeight = () => this.collapsedHeight + this.expandViewHeight;
     embedWidth = () => this._props.PanelWidth() / 2;
-    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
+    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
         return property === StyleProp.Opacity ? 1 : this._props.styleProvider?.(doc, props, property);
     };
     /**
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index f41094010..4a7e35c16 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -17,11 +17,11 @@ import { SnappingManager } from '../../util/SnappingManager';
 import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
-import { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentViewInternalProps, DocumentViewProps } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { LinkInfo } from '../nodes/LinkDocPreview';
 import { ObservableReactComponent } from '../ObservableReactComponent';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp, testDocProps } from '../StyleProvider';
 import { AnchorMenu } from './AnchorMenu';
 import { Annotation } from './Annotation';
 import { GPTPopup } from './GPTPopup/GPTPopup';
@@ -53,7 +53,7 @@ interface IViewerProps extends FieldViewProps {
 export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     static _annotationStyle: any = addStyleSheet();
 
-    constructor(props: any) {
+    constructor(props: IViewerProps) {
         super(props);
         makeObservable(this);
     }
@@ -419,7 +419,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     @action
     createTextAnnotation = (sel: Selection, selRange: Range) => {
         if (this._mainCont.current) {
-            this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToNativeLocalTransform().RotateDeg)}deg)`;
+            this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToContentsTransform().RotateDeg)}deg)`;
             const boundingRect = this._mainCont.current.getBoundingClientRect();
             const clientRects = selRange.getClientRects();
             for (let i = 0; i < clientRects.length; i++) {
@@ -491,16 +491,17 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     }
 
     getScrollHeight = () => this._scrollHeight;
-    scrollXf = () => (this._mainCont.current ? this._props.ScreenToLocalTransform().translate(0, NumCast(this._props.layoutDoc._layout_scrollTop)) : this._props.ScreenToLocalTransform());
+    scrollXf = () => this._props.ScreenToLocalTransform().translate(0, this._mainCont.current ? NumCast(this._props.layoutDoc._layout_scrollTop) : 0);
     overlayTransform = () => this.scrollXf().scale(1 / NumCast(this._props.layoutDoc._freeform_scale, 1));
     panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1);
     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
     transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
     opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed && this._props.isContentActive() ? [] : [Utils.OpaqueBackgroundFilter])];
-    childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
+    childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
+            const docProps = testDocProps(props) ? props : undefined;
             if (this.inlineTextAnnotations.includes(doc) || this._props.isContentActive() === false) return 'none';
-            const isInk = doc.layout_isSvg && !props?.LayoutTemplateString;
+            const isInk = doc.layout_isSvg && !docProps?.LayoutTemplateString;
             return isInk ? 'visiblePainted' : 'all';
         }
         return this._props.styleProvider?.(doc, props, property);
-- 
cgit v1.2.3-70-g09d2


From ae27dd1689ae1716591aab094e6d41f3a0160fef Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 4 Jan 2024 13:11:22 -0500
Subject: fixed ffmpeg for uploading videos. fixed getView() to take focus
 options so that if a sidebar or timeline has to be opened, the didMove flag
 can be set properly.  added video snapshot frame to top menu.  fixed using
 '^' to stop and start a selection region on timelines.

---
 src/client/documents/Documents.ts                  |   2 +-
 src/client/util/CurrentUserUtils.ts                |  12 +-
 src/client/util/DocumentManager.ts                 |   2 +-
 src/client/util/PingManager.ts                     |  20 +--
 src/client/views/GlobalKeyHandler.ts               |   8 +-
 src/client/views/UndoStack.tsx                     |  10 +-
 .../views/collections/CollectionDockingView.tsx    |   2 +-
 .../collections/CollectionStackedTimeline.tsx      |  40 ++++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   4 +-
 .../collectionLinear/CollectionLinearView.tsx      |   2 +-
 src/client/views/global/globalScripts.ts           |  11 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |   9 +-
 src/client/views/nodes/DocumentView.tsx            |   9 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   9 +-
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |   9 +-
 src/client/views/nodes/PDFBox.tsx                  |   7 +-
 src/client/views/nodes/VideoBox.scss               |   6 +-
 src/client/views/nodes/VideoBox.tsx                | 158 +++------------------
 src/client/views/nodes/WebBox.tsx                  |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   7 +-
 src/server/DashUploadUtils.ts                      |  15 +-
 21 files changed, 129 insertions(+), 215 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 79285deb5..92a9ddfe8 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -209,7 +209,7 @@ export class DocumentOptions {
     author?: string; // STRt = new StrInfo('creator of document'); // bcz: don't change this. Otherwise, the userDoc's field Infos will have a FieldInfo assigned to its author field which will render it unreadable
     author_date?: DATEt = new DateInfo('date the document was created', true);
     annotationOn?: DOCt = new DocInfo('document annotated by this document', false);
-    embedContainer?: DOCt = new DocInfo('document that displays (contains) this discument', false);
+    _embedContainer?: DOCt = new DocInfo('document that displays (contains) this discument', false);
     rootDocument?: DOCt = new DocInfo('document that supplies the information needed for a rendering template (eg, pres slide for PresElement)');
     color?: STRt = new StrInfo('foreground color data doc', false);
     hidden?: BOOLt = new BoolInfo('whether the document is not rendered by its collection', false);
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 3f28f44fc..4391e87d6 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -309,7 +309,7 @@ export class CurrentUserUtils {
             { toolTip: "Tap or drag to create a bullet slide",    title: "PPT Slide", icon: "file",     dragFactory: doc.emptySlide as Doc,      clickFactory: DocCast(doc.emptySlide),      openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
             { toolTip: "Tap or drag to create a data note",       title: "DataNote", icon: "window-maximize",dragFactory: doc.emptyHeader as Doc,clickFactory: DocCast(doc.emptyHeader),     openFactoryAsDelegate: true,            funcs: { hidden: "IsNoviceMode()"} },
             { toolTip: "Toggle a Calculator REPL",                title: "replviewer",  icon: "calculator",                                      clickFactory: '<ScriptingRepl />' as any,   openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script
-           // { toolTip: "Toggle an UndoStack",                     title: "undostacker", icon: "calculator",                                      clickFactory: "<UndoStack>" as any,         openFactoryLocation: OpenWhere.overlay},
+           // { toolTip: "Toggle an UndoStack",                     title: "undostacker", icon: "calculator",                                      clickFactory: "<UndoStack />" as any,         openFactoryLocation: OpenWhere.overlay},
           ].map(tuple => (
               { openFactoryLocation: OpenWhere.addRight, 
                 scripts: { onClick:     'openDoc(copyDragFactory(this.clickFactory,this.openFactoryAsDelegate), this.openFactoryLocation)', 
@@ -695,11 +695,16 @@ export class CurrentUserUtils {
 
     static webTools() {
         return [
-            { title: "Back",    toolTip: "Go back",         btnType: ButtonType.ClickButton,  icon: "arrow-left",              scripts: { onClick: '{ return webBack(_readOnly_); }' }},
-            { title: "Forward", toolTip: "Go forward",      btnType: ButtonType.ClickButton,  icon: "arrow-right",             scripts: { onClick: '{ return webForward(_readOnly_); }'}},
+            { title: "Back",    toolTip: "Go back",         btnType: ButtonType.ClickButton,  icon: "arrow-left",              scripts: { onClick: '{ return webBack(); }' }},
+            { title: "Forward", toolTip: "Go forward",      btnType: ButtonType.ClickButton,  icon: "arrow-right",             scripts: { onClick: '{ return webForward(); }'}},
             { title: "URL",     toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} },
         ];
     }
+    static videoTools() {
+        return [
+            { title: "Snapshot",toolTip: "Take snapshot of current frame", btnType: ButtonType.ClickButton,  icon: "camera",                  scripts: { onClick: '{ return videoSnapshot(); }' }},
+        ];
+    }
     static contextMenuTools():Button[] {
         return [
             { btnList: new List<string>([CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree,
@@ -721,6 +726,7 @@ export class CurrentUserUtils {
             { title: "View",    icon: "View",          toolTip: "View tools",           subMenu: CurrentUserUtils.viewTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Stack",   icon: "View",          toolTip: "Stacking tools",       subMenu: CurrentUserUtils.stackTools(),  expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
+            { title: "Video",   icon: "Video",         toolTip: "Video functions",      subMenu: CurrentUserUtils.videoTools(),  expertMode: false, toolType:DocumentType.VID,            funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
             { title: "Schema",  icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Schema is selected
            ];
     }
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 0fab42895..7fef5cc79 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -281,7 +281,7 @@ export class DocumentManager {
         docContextPath.shift();
         const childViewIterator = async (docView: DocumentView) => {
             const innerDoc = docContextPath.shift();
-            return { focused: false, viewSpec: innerDoc, childDocView: innerDoc && !innerDoc.layout_unrendered ? (await docView.ComponentView?.getView?.(innerDoc)) ?? this.getDocumentView(innerDoc) : undefined };
+            return { focused: false, viewSpec: innerDoc, childDocView: innerDoc && !innerDoc.layout_unrendered ? (await docView.ComponentView?.getView?.(innerDoc, options)) ?? this.getDocumentView(innerDoc) : undefined };
         };
 
         if (rootContextView) {
diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts
index 865f8bc02..7638e2ce0 100644
--- a/src/client/util/PingManager.ts
+++ b/src/client/util/PingManager.ts
@@ -4,11 +4,20 @@ import { CurrentUserUtils } from './CurrentUserUtils';
 export class PingManager {
     // create static instance and getter for global use
     @observable static _instance: PingManager;
+    @observable IsBeating = true;
     static get Instance(): PingManager {
         return PingManager._instance;
     }
 
-    @observable IsBeating = true;
+    // not used now, but may need to clear interval
+    private _interval: NodeJS.Timeout | null = null;
+    INTERVAL_SECONDS = 1;
+    constructor() {
+        makeObservable(this);
+        PingManager._instance = this;
+        this._interval = setInterval(this.sendPing, this.INTERVAL_SECONDS * 1000);
+    }
+
     private setIsBeating = action((status: boolean) => {
         this.IsBeating = status;
         setTimeout(this.showAlert, 100);
@@ -28,13 +37,4 @@ export class PingManager {
             }
         }
     };
-
-    // not used now, but may need to clear interval
-    private _interval: NodeJS.Timeout | null = null;
-    INTERVAL_SECONDS = 1;
-    constructor() {
-        makeObservable(this);
-        PingManager._instance = this;
-        this._interval = setInterval(this.sendPing, this.INTERVAL_SECONDS * 1000);
-    }
 }
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 77b831e51..d134d9e7b 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -128,10 +128,12 @@ export class KeyManager {
                 var doDeselect = true;
                 if (SnappingManager.IsDragging) {
                     DragManager.AbortDrag();
-                } else if (CollectionDockingView.Instance?.HasFullScreen) {
+                } 
+                if (CollectionDockingView.Instance?.HasFullScreen) {
                     CollectionDockingView.Instance?.CloseFullScreen();
-                } else if (CollectionStackedTimeline.SelectingRegion) {
-                    CollectionStackedTimeline.SelectingRegion = undefined;
+                } 
+                if (CollectionStackedTimeline.SelectingRegions.size) {
+                    CollectionStackedTimeline.StopSelecting();
                     doDeselect = false;
                 } else {
                     doDeselect = !ContextMenu.Instance.closeMenu();
diff --git a/src/client/views/UndoStack.tsx b/src/client/views/UndoStack.tsx
index ea038250e..068143225 100644
--- a/src/client/views/UndoStack.tsx
+++ b/src/client/views/UndoStack.tsx
@@ -8,19 +8,13 @@ import { SettingsManager } from '../util/SettingsManager';
 import { UndoManager } from '../util/UndoManager';
 import './UndoStack.scss';
 
-interface UndoStackProps {
-    width?: number;
-    height?: number;
-    inline?: boolean;
-}
+interface UndoStackProps {}
 @observer
 export class UndoStack extends React.Component<UndoStackProps> {
-    @observable static HideInline: boolean = false;
-    @observable static Expand: boolean = false;
     render() {
         const background = UndoManager.batchCounter.get() ? 'yellow' : SettingsManager.userVariantColor;
         const color = UndoManager.batchCounter.get() ? 'black' : SettingsManager.userColor;
-        return this.props.inline && UndoStack.HideInline ? null : (
+        return (
             <Tooltip title={'undo stack (if it stays yellow, undo is broken - you should reload Dash)'}>
                 <div>
                     <div className="undoStack-outerContainer">
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index c46f54c70..874cdffd9 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -615,7 +615,7 @@ ScriptingGlobals.add(
                 // prettier-ignore
                 switch (doc) {
                     case '<ScriptingRepl />': return OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' });
-                    case "<UndoStack>": return OverlayView.Instance.addWindow(<UndoStack />, { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' });
+                    case "<UndoStack />": return OverlayView.Instance.addWindow(<UndoStack />, { x: 300, y: 100, width: 200, height: 200, title: 'Undo stack' });
                 }
                 Doc.AddToMyOverlay(doc);
         }
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index d99b4f9de..22a67c501 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -56,8 +56,11 @@ export enum TrimScope {
 
 @observer
 export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() {
-    @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined;
-    @observable public static CurrentlyPlaying: DocumentView[] = [];
+    public static SelectingRegions: Set<CollectionStackedTimeline> = new Set();
+    public static StopSelecting() {
+        this.SelectingRegions.forEach(action(region => (region._selectingRegion = false)));
+        this.SelectingRegions.clear();
+    }
     constructor(props: any) {
         super(props);
         makeObservable(this);
@@ -69,6 +72,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     private _timeline: HTMLDivElement | null = null; // ref to actual timeline div
     private _timelineWrapper: HTMLDivElement | null = null; // ref to timeline wrapper div for zooming and scrolling
     private _markerStart: number = 0;
+    @observable public static CurrentlyPlaying: DocumentView[] = [];
+    @observable _selectingRegion = false;
     @observable _markerEnd: number | undefined = undefined;
     @observable _trimming: number = TrimScope.None;
     @observable _trimStart: number = 0; // trim controls start pos
@@ -123,8 +128,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
 
     componentWillUnmount() {
         document.removeEventListener('keydown', this.keyEvents, true);
-        if (CollectionStackedTimeline.SelectingRegion === this) {
-            runInAction(() => (CollectionStackedTimeline.SelectingRegion = undefined));
+        if (this._selectingRegion) {
+            runInAction(() => (this._selectingRegion = false));
+            CollectionStackedTimeline.SelectingRegions.delete(this);
         }
     }
 
@@ -154,6 +160,15 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         this._zoomFactor = zoom;
     }
 
+    makeDocUnfiltered = (doc: Doc) => this.childDocList?.some(item => item === doc);
+
+    getView = async (doc: Doc, options: DocFocusOptions): Promise<Opt<DocumentView>> =>
+        new Promise<Opt<DocumentView>>(res => {
+            if (doc.hidden) options.didMove = !(doc.hidden = false);
+            const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
+            findDoc(dv => res(dv));
+        });
+
     anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this._props.startTag]));
     anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this._props.endTag], val) ?? null);
 
@@ -191,14 +206,16 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                     this._props.playing() ? this._props.Pause() : this._props.Play();
                     break;
                 case '^':
-                    if (!CollectionStackedTimeline.SelectingRegion) {
+                    if (!this._selectingRegion) {
                         this._markerStart = this._markerEnd = this.currentTime;
-                        CollectionStackedTimeline.SelectingRegion = this;
+                        this._selectingRegion = true;
+                        CollectionStackedTimeline.SelectingRegions.add(this);
                     } else {
                         this._markerEnd = this.currentTime;
                         CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
                         this._markerEnd = undefined;
-                        CollectionStackedTimeline.SelectingRegion = undefined;
+                        this._selectingRegion = false;
+                        CollectionStackedTimeline.SelectingRegions.delete(this);
                     }
                     e.stopPropagation();
                     break;
@@ -405,6 +422,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                 backgroundColor: 'rgba(128, 128, 128, 0.5)',
                 layout_hideLinkButton: true,
                 onClick: FollowLinkScript(),
+                _embedContainer: doc,
                 annotationOn: doc,
                 _isTimelineLabel: true,
                 layout_borderRounding: anchorEndTime === undefined ? '100%' : undefined,
@@ -511,7 +529,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
 
     // renders selection region on timeline
     @computed get selectionContainer() {
-        const markerEnd = CollectionStackedTimeline.SelectingRegion === this ? this.currentTime : this._markerEnd;
+        const markerEnd = this._selectingRegion ? this.currentTime : this._markerEnd;
         return markerEnd === undefined ? null : (
             <div
                 className="collectionStackedTimeline-selector"
@@ -532,9 +550,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
             anchorEndTime: number;
             level: number;
         }[] = [];
-        const drawAnchors = this.childDocs.map(anchor => ({
-            level: this.getLevel(anchor, overlaps),
-            anchor,
+        const drawAnchors = this.childLayoutPairs.map(pair => ({
+            level: this.getLevel(pair.layout, overlaps),
+            anchor: pair.layout,
         }));
         const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2;
         return this.clipDuration === 0 ? null : (
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a3cedb9a0..8268a47d8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -321,9 +321,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }
     };
 
-    getView = async (doc: Doc): Promise<Opt<DocumentView>> =>
+    getView = async (doc: Doc, options: DocFocusOptions): Promise<Opt<DocumentView>> =>
         new Promise<Opt<DocumentView>>(res => {
-            if (doc.hidden && this._lightboxDoc !== doc) doc.hidden = false;
+            if (doc.hidden && this._lightboxDoc !== doc) options.didMove = !(doc.hidden = false);
             const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
             findDoc(dv => res(dv));
         });
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index f1fb68003..d105b04f7 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -147,7 +147,7 @@ export class CollectionLinearView extends CollectionSubView() {
         switch (doc.layout) {
             case '<LinkingUI>':          return this.getLinkUI();
             case '<CurrentlyPlayingUI>': return this.getCurrentlyPlayingUI();
-            case '<UndoStack>':          return <UndoStack key={doc[Id]} width={200} height={40} inline={true} />;
+            case '<UndoStack>':          return <UndoStack />;
             case '<Branching>':          return Doc.UserDoc().isBranchingMode  ? <BranchingTrailManager /> : null;
         }
 
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 989dd1db0..e57ef4871 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -19,6 +19,7 @@ import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocum
 import { DocumentView } from '../nodes/DocumentView';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
 import { WebBox } from '../nodes/WebBox';
+import { VideoBox } from '../nodes/VideoBox';
 
 ScriptingGlobals.add(function IsNoneSelected() {
     return SelectionManager.Views.length <= 0;
@@ -390,14 +391,16 @@ ScriptingGlobals.add(function webForward(checkResult?: boolean) {
     }
     selected?.forward();
 });
-ScriptingGlobals.add(function webBack(checkResult?: boolean) {
+ScriptingGlobals.add(function webBack() {
     const selected = SelectionManager.Views.lastElement()?.ComponentView as WebBox;
-    if (checkResult) {
-        return selected?.back(checkResult) ? undefined : 'lightGray';
-    }
     selected?.back();
 });
 
+ScriptingGlobals.add(function videoSnapshot() {
+    const selected = SelectionManager.Views.lastElement()?.ComponentView as VideoBox;
+    selected?.Snapshot();
+});
+
 /** Schema
  *      toggleSchemaPreview
  **/
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 76cc010f6..5a55ca764 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -20,7 +20,7 @@ import { SidebarAnnos } from '../../SidebarAnnos';
 import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
-import { DocumentView } from '../DocumentView';
+import { DocFocusOptions, DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { PinProps } from '../trails';
 import './DataVizBox.scss';
@@ -228,8 +228,11 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar')
         );
     };
-    getView = async (doc: Doc) => {
-        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar();
+    getView = async (doc: Doc, options: DocFocusOptions) => {
+        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
+            options.didMove = true;
+            this.toggleSidebar();
+        }
         return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
     };
     @computed get sidebarWidthPercent() {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7f1e547e4..6cc61ec62 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -43,7 +43,6 @@ import { GestureOverlay } from '../GestureOverlay';
 import { LightboxView } from '../LightboxView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { StyleProp } from '../StyleProvider';
-import { UndoStack } from '../UndoStack';
 import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
 import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView';
 import { DocumentLinksButton } from './DocumentLinksButton';
@@ -119,7 +118,7 @@ export interface DocComponentView {
     restoreView?: (viewSpec: Doc) => boolean;
     scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: DocFocusOptions) => Opt<number>; // returns the duration of the focus
     brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number) => void; // highlight a region of a view (used by freeforms)
-    getView?: (doc: Doc) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined
+    getView?: (doc: Doc, options: DocFocusOptions) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined
     addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox
     addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections)
     select?: (ctrlKey: boolean, shiftKey: boolean) => void;
@@ -520,11 +519,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
         this._longPressSelector = setTimeout(() => {
             if (DocumentView.LongPress) {
-                if (this.Document.undoIgnoreFields) {
-                    runInAction(() => (UndoStack.HideInline = !UndoStack.HideInline));
-                } else {
-                    this._props.select(false);
-                }
+                this._props.select(false);
             }
         }, 1000);
         if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this._props.DocumentView();
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 41befbbfe..3575b21e4 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -27,7 +27,7 @@ import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocC
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
 import { Colors } from '../../global/globalEnums';
-import { DocumentView } from '../DocumentView';
+import { DocFocusOptions, DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../trails';
@@ -504,8 +504,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     };
 
-    getView = async (doc: Doc) => {
-        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar();
+    getView = async (doc: Doc, options: DocFocusOptions) => {
+        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
+            this.toggleSidebar();
+            options.didMove = true;
+        }
         return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
     };
     /*
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index 8b22a1531..ea8496c99 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -20,7 +20,7 @@ import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocC
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
 import { Colors } from '../../global/globalEnums';
-import { DocumentView } from '../DocumentView';
+import { DocFocusOptions, DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { MapAnchorMenu } from '../MapBox/MapAnchorMenu';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
@@ -374,8 +374,11 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
         }
     };
 
-    getView = async (doc: Doc) => {
-        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar();
+    getView = async (doc: Doc, options: DocFocusOptions) => {
+        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
+            this.toggleSidebar();
+            options.didMove = true;
+        }
         return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
     };
     /*
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 55a459a5c..959d5d88d 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -224,8 +224,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return this._pdfViewer?.scrollFocus(anchor, NumCast(anchor.y, NumCast(anchor.config_scrollTop)), options);
     };
 
-    getView = async (doc: Doc) => {
-        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false);
+    getView = async (doc: Doc, options: DocFocusOptions) => {
+        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
+            options.didMove = true;
+            this.toggleSidebar(false);
+        }
         return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
     };
 
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index ae923ad60..460155446 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -180,9 +180,9 @@
     }
 }
 
-video::-webkit-media-controls {
-    display: none !important;
-}
+// video::-webkit-media-controls {
+//     display: none !important;
+// }
 
 input[type='range'] {
     -webkit-appearance: none;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index ce73d9f37..fb42286af 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,9 +1,9 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction, untracked } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { basename } from 'path';
 import * as React from 'react';
-import { Doc, StrListCast } from '../../../fields/Doc';
+import { Doc, Opt, StrListCast } from '../../../fields/Doc';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
@@ -12,23 +12,20 @@ import { AudioField, ImageField, VideoField } from '../../../fields/URLField';
 import { emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
-import { Networking } from '../../Network';
 import { DocumentManager } from '../../util/DocumentManager';
 import { FollowLinkScript } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
 import { ReplayMovements } from '../../util/ReplayMovements';
-import { SelectionManager } from '../../util/SelectionManager';
-import { SnappingManager } from '../../util/SnappingManager';
 import { undoBatch } from '../../util/UndoManager';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { StyleProp } from '../StyleProvider';
-import { DocFocusOptions, DocumentView, OpenWhere } from './DocumentView';
+import { DocFocusOptions, DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { RecordingBox } from './RecordingBox';
 import { PinProps, PresBox } from './trails';
@@ -40,7 +37,7 @@ import './VideoBox.scss';
  * Supporting Components: CollectionStackedTimeline
  *
  * VideoBox is a node that supports the playback of video files in Dash.
- * When a video file or YouTube video is importeed into Dash, it is immediately rendered as a VideoBox document.
+ * When a video file is importeed into Dash, it is immediately rendered as a VideoBox document.
  * CollectionStackedTimline handles AudioBox and VideoBox shared behavior, but VideoBox handles playing, pausing, etc because it contains <video> element
  * User can trim video: nondestructive, just sets new bounds for playback and rendering timeline
  * Like images, users can zoom and pan and it has an overlay layer allowing for annotations on top of the video at different times
@@ -51,16 +48,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(VideoBox, fieldKey);
     }
-    static _youtubeIframeCounter: number = 0;
     static heightPercent = 80; // height of video relative to videoBox when timeline is open
     static numThumbnails = 20;
     private unmounting = false;
     private _disposers: { [name: string]: IReactionDisposer } = {};
-    private _youtubePlayer: YT.Player | undefined = undefined;
     private _videoRef: HTMLVideoElement | null = null; // <video> ref
     private _contentRef: HTMLDivElement | null = null; // ref to div that wraps video and controls for full screen
-    private _youtubeIframeId: number = -1;
-    private _youtubeContentCreated = false;
     private _audioPlayer: HTMLAudioElement | null = null;
     private _marqueeref = React.createRef<MarqueeAnnotator>();
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); // outermost div
@@ -74,12 +67,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this._props.setContentView?.(this);
     }
 
-    @observable _stackedTimeline: any = undefined; // CollectionStackedTimeline ref
-    @observable static _nativeControls: boolean = false; // default html controls
+    @observable _stackedTimeline: CollectionStackedTimeline | undefined = undefined; // CollectionStackedTimeline ref
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @observable _screenCapture = false;
     @observable _clicking = false; // used for transition between showing/hiding timeline
-    @observable _forceCreateYouTubeIFrame = false;
     @observable _playTimer?: NodeJS.Timeout = undefined;
     @observable _fullScreen = false;
     @observable _playing = false;
@@ -99,11 +90,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     // @computed get rawDuration() { return NumCast(this.dataDoc[this.fieldKey + "_duration"]); }
     @observable rawDuration: number = 0;
 
-    @computed get youtubeVideoId() {
-        const field = Cast(this.dataDoc[this._props.fieldKey], VideoField);
-        return field && field.url.href.indexOf('youtube') !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split('/')) : '';
-    }
-
     // returns the path of the audio file
     @computed get audiopath() {
         const field = Cast(this.Document[this._props.fieldKey + '_audio'], AudioField, null);
@@ -130,17 +116,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     componentDidMount() {
         this.unmounting = false;
         this._props.setContentView?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link.
-        if (this.youtubeVideoId) {
-            const youtubeaspect = 400 / 315;
-            const nativeWidth = Doc.NativeWidth(this.layoutDoc);
-            const nativeHeight = Doc.NativeHeight(this.layoutDoc);
-            if (!nativeWidth || !nativeHeight) {
-                if (!nativeWidth) Doc.SetNativeWidth(this.dataDoc, 600);
-                Doc.SetNativeHeight(this.dataDoc, (nativeWidth || 600) / youtubeaspect);
-                this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect;
-            }
-        }
-        this.player && this.setPlayheadTime(this.timeline.clipStart || 0);
+        this.player && this.setPlayheadTime(this.timeline?.clipStart || 0);
         document.addEventListener('keydown', this.keyEvents, true);
 
         if (this.presentation) {
@@ -201,8 +177,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 this._audioPlayer && this.player && (this._audioPlayer.currentTime = this.player?.currentTime);
                 update && this.player && this.playFrom(start, undefined, true);
                 update && this._audioPlayer?.play();
-                update && this._youtubePlayer?.playVideo();
-                this._youtubePlayer && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 5));
             } catch (e) {
                 console.log('Video Play Exception:', e);
             }
@@ -212,11 +186,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     // goes to time
     @action public Seek(time: number) {
-        try {
-            this._youtubePlayer?.seekTo(Math.round(time), true);
-        } catch (e) {
-            console.log('Video Seek Exception:', e);
-        }
         this.player && (this.player.currentTime = time);
         this._audioPlayer && (this._audioPlayer.currentTime = time);
         // TODO: revisit this and clean it
@@ -242,13 +211,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         try {
             update && this.player?.pause();
             update && this._audioPlayer?.pause();
-            update && this._youtubePlayer?.pauseVideo();
-            this._youtubePlayer && this._playTimer && clearInterval(this._playTimer);
-            this._youtubePlayer?.seekTo(this._youtubePlayer?.getCurrentTime(), true);
         } catch (e) {
             console.log('Video Pause Exception:', e);
         }
-        this._youtubePlayer && SelectionManager.DeselectAll(); // if we don't deselect the player, then we get an annoying YouTube spinner I guess telling us we're paused.
         this._playTimer = undefined;
         this.updateTimecode();
         if (!this._finished) {
@@ -270,11 +235,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             this._fullScreen = true;
             this.player && this._contentRef && this._contentRef.requestFullscreen();
         }
-        try {
-            this._youtubePlayer && this._props.addDocTab(this.Document, OpenWhere.add);
-        } catch (e) {
-            console.log('Video FullScreen Exception:', e);
-        }
     };
 
     // fades out controls in fullscreen after mouse stops moving
@@ -334,22 +294,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             });
             this._props.addDocument?.(b);
             DocUtils.MakeLink(b, this.Document, { link_relationship: 'video snapshot' });
-            Networking.PostToServer('/youtubeScreenshot', {
-                id: this.youtubeVideoId,
-                timecode: this.layoutDoc._layout_currentTimecode,
-            }).then(response => {
-                const resolved = response?.accessPaths?.agnostic?.client;
-                if (resolved) {
-                    this._props.removeDocument?.(b);
-                    this.createSnapshotLink(resolved);
-                }
-            });
         } else {
             //convert to desired file format
             const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
             // if you want to preview the captured image,
             const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, '');
-            const encodedFilename = encodeURIComponent('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString().replace(/\./, '_'));
+            const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[\.\/\?\=]/g, '_'));
             const filename = basename(encodedFilename);
             Utils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY));
         }
@@ -416,16 +366,18 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @action
     updateTimecode = () => {
         !this.unmounting && this.player && (this.layoutDoc._layout_currentTimecode = this.player.currentTime);
-        try {
-            this._youtubePlayer && (this.layoutDoc._layout_currentTimecode = this._youtubePlayer.getCurrentTime?.());
-        } catch (e) {
-            console.log('Video Timecode Exception:', e);
-        }
     };
 
-    // getView = async (doc: Doc) => {
-    //     return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
-    // };
+    getView = (doc: Doc, options: DocFocusOptions) => {
+        if (this._stackedTimeline?.makeDocUnfiltered(doc)) {
+            if (this.heightPercent === 100) {
+                this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
+                options.didMove = true;
+            }
+            return this._stackedTimeline.getView(doc, options);
+        }
+        return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+    };
 
     // extracts video thumbnails and saves them as field of doc
     getVideoThumbnails = () => {
@@ -518,7 +470,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 icon: 'expand-arrows-alt',
             });
             subitems.push({ description: (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : 'Auto play') + ' anchors onClick', event: () => (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors), icon: 'expand-arrows-alt' });
-            // subitems.push({ description: "Toggle Native Controls", event: action(() => VideoBox._nativeControls = !VideoBox._nativeControls), icon: "expand-arrows-alt" });
             // subitems.push({ description: "Start Trim All", event: () => this.startTrim(TrimScope.All), icon: "expand-arrows-alt" });
             // subitems.push({ description: "Start Trim Clip", event: () => this.startTrim(TrimScope.Clip), icon: "expand-arrows-alt" });
             // subitems.push({ description: "Stop Trim", event: () => this.finishTrim(), icon: "expand-arrows-alt" });
@@ -581,7 +532,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         ref={this.setVideoRef}
                         style={this._fullScreen ? this.fullScreenSize() : this.isCropped ? { width: 'max-content', height: 'max-content', transform: `scale(${1 / NumCast(this.layoutDoc._freeform_scale)})`, transformOrigin: 'top left' } : {}}
                         onCanPlay={this.videoLoad}
-                        controls={VideoBox._nativeControls}
+                        controls={false}
                         onPlay={() => this.Play()}
                         onSeeked={this.updateTimecode}
                         onPause={() => this.Pause()}
@@ -600,54 +551,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         );
     }
 
-    @action youtubeIframeLoaded = (e: any) => {
-        if (!this._youtubeContentCreated) {
-            this._forceCreateYouTubeIFrame = !this._forceCreateYouTubeIFrame;
-            return;
-        } else this._youtubeContentCreated = false;
-
-        this.loadYouTube(e.target);
-    };
-
-    loadYouTube = (iframe: any) => {
-        let started = true;
-        const onYoutubePlayerStateChange = (event: any) =>
-            runInAction(() => {
-                if (started && event.data === YT.PlayerState.PLAYING) {
-                    started = false;
-                    this._youtubePlayer?.unMute();
-                    //this.Pause();
-                    return;
-                }
-                if (event.data === YT.PlayerState.PLAYING && !this._playing) this.Play(false);
-                if (event.data === YT.PlayerState.PAUSED && this._playing) this.Pause(false);
-            });
-        const onYoutubePlayerReady = (event: any) => {
-            this._disposers.reactionDisposer?.();
-            this._disposers.youtubeReactionDisposer?.();
-            this._disposers.reactionDisposer = reaction(
-                () => this.layoutDoc._layout_currentTimecode,
-                () => !this._playing && this.Seek(NumCast(this.layoutDoc._layout_currentTimecode))
-            );
-            this._disposers.youtubeReactionDisposer = reaction(
-                () => Doc.ActiveTool === InkTool.None && this._props.isSelected() && !SnappingManager.IsDragging && !SnappingManager.IsResizing,
-                interactive => (iframe.style.pointerEvents = interactive ? 'all' : 'none'),
-                { fireImmediately: true }
-            );
-        };
-        if (typeof YT === undefined) setTimeout(() => this.loadYouTube(iframe), 100);
-        else {
-            (YT as any)?.ready(() => {
-                this._youtubePlayer = new YT.Player(`${this.youtubeVideoId + this._youtubeIframeId}-player`, {
-                    events: {
-                        onReady: this._props.dontRegisterView ? undefined : onYoutubePlayerReady,
-                        onStateChange: this._props.dontRegisterView ? undefined : onYoutubePlayerStateChange,
-                    },
-                });
-            });
-        }
-    };
-
     // for play button
     onPlayDown = () => (this._playing ? this.Pause() : this.Play());
 
@@ -723,25 +626,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         }
     };
 
-    @computed get youtubeContent() {
-        this._youtubeIframeId = VideoBox._youtubeIframeCounter++;
-        this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true;
-        const classname = 'videoBox-content-YouTube' + (this._fullScreen ? '-fullScreen' : '');
-        const start = untracked(() => Math.round(NumCast(this.layoutDoc._layout_currentTimecode)));
-        return (
-            <iframe
-                key={this._youtubeIframeId}
-                id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
-                onPointerLeave={this.updateTimecode}
-                onLoad={this.youtubeIframeLoaded}
-                className={classname}
-                width={Doc.NativeWidth(this.layoutDoc) || 640}
-                height={Doc.NativeHeight(this.layoutDoc) || 390}
-                src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=0&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._nativeControls ? 1 : 0}`}
-            />
-        );
-    }
-
     // for annotating, adds doc with time info
     @action.bound
     addDocWithTimecode(doc: Doc | Doc[]): boolean {
@@ -1090,7 +974,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                             removeDocument={this.removeDocument}
                             moveDocument={this.moveDocument}
                             addDocument={this.addDocWithTimecode}>
-                            {this.youtubeVideoId ? this.youtubeContent : this.content}
+                            {this.content}
                         </CollectionFreeFormView>
                     </div>
                     {this.annotationLayer}
@@ -1225,5 +1109,3 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         );
     }
 }
-
-VideoBox._nativeControls = false;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 3ad9f7634..c3be2b390 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -296,7 +296,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
 
     @action
-    getView = (doc: Doc) => {
+    getView = (doc: Doc, options: DocFocusOptions) => {
         if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this._props.DocumentView?.()));
         if (this.Document.layout_fieldKey === 'layout_icon') this._props.DocumentView?.().iconify();
         const webUrl = WebCast(doc.config_data)?.url;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 66802d198..3be3aeba3 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1056,9 +1056,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         return anchorDoc ?? this.Document;
     }
 
-    getView = async (doc: Doc) => {
+    getView = async (doc: Doc, options: DocFocusOptions) => {
         if (DocListCast(this.dataDoc[this.SidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
-            !this.SidebarShown && this.toggleSidebar(false);
+            if (!this.SidebarShown) {
+                this.toggleSidebar(false);
+                options.didMove = true;
+            }
             setTimeout(() => this._sidebarRef?.current?.makeDocUnfiltered(doc));
         }
         return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 50f202e69..b1a7a9c5e 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -2,7 +2,7 @@ import axios from 'axios';
 import { green, red } from 'colors';
 import { ExifImage } from 'exif';
 import * as exifr from 'exifr';
-import { FfmpegCommand, ffprobe } from 'fluent-ffmpeg';
+import * as ffmpeg from 'fluent-ffmpeg';
 import * as formidable from 'formidable';
 import { File } from 'formidable';
 import * as fs from 'fs';
@@ -95,9 +95,8 @@ export namespace DashUploadUtils {
         const outputFilePath = path.join(pathToDirectory(Directory.videos), outputFileName);
 
         // concatenate the videos
-        await new Promise((resolve, reject) => {
-            var merge = new FfmpegCommand();
-            merge
+        await new Promise((resolve, reject) =>
+            ffmpeg()
                 .input(textFilePath)
                 .inputOptions(['-f concat', '-safe 0'])
                 // .outputOptions('-c copy')
@@ -107,8 +106,8 @@ export namespace DashUploadUtils {
                     console.log(err);
                     reject();
                 })
-                .on('end', resolve);
-        });
+                .on('end', resolve)
+        );
 
         // delete concat.txt from the file system
         unlinkSync(textFilePath);
@@ -221,7 +220,7 @@ export namespace DashUploadUtils {
                 if (format.includes('x-matroska')) {
                     console.log('case video');
                     await new Promise(res =>
-                        new FfmpegCommand(file.filepath)
+                        ffmpeg(file.filepath)
                             .videoCodec('copy') // this will copy the data instead of reencode it
                             .save(file.filepath.replace('.mkv', '.mp4'))
                             .on('end', res)
@@ -233,7 +232,7 @@ export namespace DashUploadUtils {
                 if (format.includes('quicktime')) {
                     let abort = false;
                     await new Promise<void>(res =>
-                        ffprobe(file.filepath, (err: any, metadata: any) => {
+                        ffmpeg.ffprobe(file.filepath, (err: any, metadata: any) => {
                             if (metadata.streams.some((stream: any) => stream.codec_name === 'hevc')) {
                                 abort = true;
                             }
-- 
cgit v1.2.3-70-g09d2


From 5b5730a7df073659cbb6c326f748f7fcbe6625e8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 18 Jan 2024 15:05:53 -0500
Subject: lots of changes to try to simplify API for viewPaths and related

---
 src/Utils.ts                                       |   3 +-
 src/client/documents/Documents.ts                  |   6 +-
 src/client/util/CurrentUserUtils.ts                |   4 +-
 src/client/util/DocumentManager.ts                 |  15 +-
 .../util/Import & Export/DirectoryImportBox.tsx    |   2 +-
 src/client/views/DocComponent.tsx                  |  56 +++-
 src/client/views/DocumentButtonBar.tsx             |   2 +-
 src/client/views/DocumentDecorations.tsx           |   6 +-
 src/client/views/GestureOverlay.tsx                |   2 +-
 src/client/views/InkControlPtHandles.tsx           |  12 +-
 src/client/views/InkStrokeProperties.ts            |   3 +-
 src/client/views/InkTangentHandles.tsx             |   4 +-
 src/client/views/InkingStroke.tsx                  |   9 +-
 src/client/views/LightboxView.tsx                  |   2 +-
 src/client/views/MainView.tsx                      |  47 +---
 src/client/views/MarqueeAnnotator.tsx              |   2 +-
 src/client/views/OverlayView.tsx                   |   2 +-
 src/client/views/PreviewCursor.tsx                 |  25 +-
 src/client/views/PropertiesButtons.tsx             |  46 ++-
 src/client/views/PropertiesDocContextSelector.tsx  |   2 +-
 src/client/views/PropertiesView.tsx                |   2 +-
 src/client/views/StyleProvider.tsx                 |  33 ++-
 src/client/views/TemplateMenu.tsx                  |   2 -
 .../views/collections/CollectionCarouselView.tsx   |   4 +-
 .../views/collections/CollectionDockingView.tsx    |   2 +-
 src/client/views/collections/CollectionMenu.tsx    |   2 -
 .../views/collections/CollectionNoteTakingView.tsx |  10 +-
 .../views/collections/CollectionPileView.tsx       |   2 +-
 .../collections/CollectionStackedTimeline.tsx      |   4 +-
 .../views/collections/CollectionStackingView.tsx   |  12 +-
 src/client/views/collections/CollectionSubView.tsx |   4 +-
 .../views/collections/CollectionTreeView.tsx       |   4 +-
 src/client/views/collections/CollectionView.tsx    |  10 +-
 src/client/views/collections/TabDocView.tsx        |   9 +-
 src/client/views/collections/TreeView.tsx          |  12 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  51 ++--
 .../collectionLinear/CollectionLinearView.tsx      |   6 +-
 .../CollectionMulticolumnView.tsx                  |   6 +-
 .../CollectionMultirowView.tsx                     |   7 +-
 .../collectionSchema/CollectionSchemaView.tsx      |   7 +-
 .../collections/collectionSchema/SchemaRowBox.tsx  |   8 +-
 .../collectionSchema/SchemaTableCell.tsx           |   1 -
 src/client/views/global/globalScripts.ts           |   4 +-
 src/client/views/linking/LinkMenuItem.tsx          |   4 +-
 src/client/views/linking/LinkPopup.tsx             |   2 -
 src/client/views/newlightbox/NewLightboxView.tsx   |   2 +-
 src/client/views/nodes/AudioBox.tsx                |   4 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   8 +-
 src/client/views/nodes/ComparisonBox.tsx           |   5 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |  41 +--
 src/client/views/nodes/DocumentContentsView.tsx    |  11 +-
 src/client/views/nodes/DocumentIcon.tsx            |   2 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |   4 +-
 src/client/views/nodes/DocumentView.tsx            | 308 +++++++++++----------
 src/client/views/nodes/EquationBox.tsx             |   2 +-
 src/client/views/nodes/FieldView.tsx               |   9 +-
 src/client/views/nodes/ImageBox.tsx                |   8 +-
 src/client/views/nodes/KeyValuePair.tsx            |  65 ++---
 src/client/views/nodes/LinkAnchorBox.tsx           |  14 +-
 src/client/views/nodes/LinkBox.tsx                 |   9 +-
 src/client/views/nodes/LinkDocPreview.tsx          |  12 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   4 +-
 src/client/views/nodes/MapBox/MapPushpinBox.tsx    |   4 +-
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |   4 +-
 src/client/views/nodes/PDFBox.tsx                  |   8 +-
 src/client/views/nodes/VideoBox.tsx                |  18 +-
 src/client/views/nodes/WebBox.tsx                  |  36 +--
 .../views/nodes/formattedText/DashDocView.tsx      |   4 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  16 +-
 .../formattedText/FormattedTextBoxComment.tsx      |   3 +-
 src/client/views/nodes/trails/PresBox.tsx          |  12 +-
 src/client/views/nodes/trails/PresElementBox.tsx   |  10 +-
 src/client/views/pdf/PDFViewer.tsx                 |  12 +-
 src/client/views/topbar/TopBar.tsx                 |   2 -
 src/mobile/MobileInterface.tsx                     |   2 +-
 src/server/DashStats.ts                            |   4 +-
 77 files changed, 566 insertions(+), 537 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index b5c218e01..852083834 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -7,6 +7,7 @@ import { Socket } from '../node_modules/socket.io/dist/index';
 import { DocumentType } from './client/documents/DocumentTypes';
 import { Colors } from './client/views/global/globalEnums';
 import { Message } from './server/Message';
+import { DocumentView } from './client/views/nodes/DocumentView';
 
 export namespace Utils {
     export let CLICK_TIME = 300;
@@ -583,7 +584,7 @@ export function returnEmptyDoclist() {
     return [] as any[];
 }
 
-export let emptyPath = [];
+export let emptyPath: DocumentView[] = [];
 
 export function emptyFunction() {
     return undefined;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 92a9ddfe8..40cffcf45 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -244,7 +244,6 @@ export class DocumentOptions {
     _layout_reflowVertical?: BOOLt = new BoolInfo('native height can be changed independent of width by dragging decoration resizers');
     _layout_reflowHorizontal?: BOOLt = new BoolInfo('whether a doc with a native size can be horizonally resized, causing some form of reflow');
     layout_boxShadow?: string; // box-shadow css string OR "standard" to use dash standard box shadow
-    layout_maxAutoHeight?: NUMt = new NumInfo('maximum height for newly created (eg, from pasting) text documents', false);
     _layout_autoHeight?: BOOLt = new BoolInfo('whether document automatically resizes vertically to display contents');
     _layout_curPage?: NUMt = new NumInfo('current page of a PDF or other? paginated document', false);
     _layout_currentTimecode?: NUMt = new NumInfo('the current timecode of a time-based document (e.g., current time of a video)  value is in seconds', false);
@@ -290,7 +289,7 @@ export class DocumentOptions {
     dontPlayLinkOnSelect?: BOOLt = new BoolInfo('whether an audio/video should start playing when a link is followed to it.');
     openFactoryLocation?: string; // an OpenWhere value to place the factory created document
     openFactoryAsDelegate?: boolean; //
-    updateContentsScript?: ScriptField; // reactive script invoked when viewing a document that can update contents of a collection (or do anything)
+    onViewMounted?: ScriptField; // reactive script invoked Doc is viewed (used by showBackLinks view to update collection of links to Doc)
     toolTip?: string; // tooltip to display on hover
     toolType?: string; // type of pen tool
     expertMode?: BOOLt = new BoolInfo('something available only in expert (not novice) mode');
@@ -1895,12 +1894,11 @@ export namespace DocUtils {
         }
     }
 
-    export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) {
+    export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, backgroundColor?: string) {
         const tbox = Docs.Create.TextDocument('', {
             _xMargin: noMargins ? 0 : undefined,
             _yMargin: noMargins ? 0 : undefined,
             annotationOn,
-            layout_maxAutoHeight: maxHeight,
             backgroundColor,
             _width: width || 200,
             _height: 35,
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 4391e87d6..2dfd91314 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -111,7 +111,7 @@ export class CurrentUserUtils {
         const tempClicks = DocCast(doc[field]);
         const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, isSystem: true};
         const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
-            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCastAsync(documentView?.props.docViewPath().lastElement()?.Document.target).then((target) => target && (target.proto.data = new List([self])))"},
+            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCastAsync(documentView?.containerViewPath().lastElement()?.Document.target).then((target) => target && (target.proto.data = new List([self])))"},
             { opts: { title: "Open Detail On Right",  targetScriptKey: "onChildDoubleClick"}, script: `openDoc(this.doubleClickView.${OpenWhere.addRight})`}];
         const reqdClickList = reqdTempOpts.map(opts => {
             const allOpts = {...reqdClickOpts, ...opts.opts};
@@ -285,7 +285,7 @@ export class CurrentUserUtils {
                                                                                                 treeView_HasOverlay: true, _text_fontSize: "20px", _layout_autoHeight: true, 
                                                                                                 dropAction:'move', treeView_Type: TreeViewType.outline, 
                                                                                                 backgroundColor: "white", _xMargin: 0, _yMargin: 0, _createDocOnCR: true 
-                                                                                            }, funcs: {title: 'this.text?.Text'}},
+                                                                                            }, funcs: {title: 'pptSlide'}},//'this.text?.Text'}},
             ];
 
         emptyThings.forEach(thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, thing.scripts, thing.funcs));
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 7fef5cc79..07357a764 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -153,8 +153,8 @@ export class DocumentManager {
         return passes.reduce(
             (toReturn, pass) =>
                 toReturn ??
-                docViewArray.filter(view => view.Document === target).find(view => !pass || view._props.docViewPath().lastElement() === preferredCollection) ??
-                docViewArray.filter(view => Doc.AreProtosEqual(view.Document, target)).find(view => !pass || view._props.docViewPath().lastElement() === preferredCollection),
+                docViewArray.filter(view => view.Document === target).find(view => !pass || view.containerViewPath?.().lastElement() === preferredCollection) ??
+                docViewArray.filter(view => Doc.AreProtosEqual(view.Document, target)).find(view => !pass || view.containerViewPath?.().lastElement() === preferredCollection),
             undefined as Opt<DocumentView>
         );
     }
@@ -224,7 +224,7 @@ export class DocumentManager {
     }
 
     public static removeOverlayViews() {
-        DocumentManager._overlayViews?.forEach(action(view => (view.textHtmlOverlay = undefined)));
+        DocumentManager._overlayViews?.forEach(view => view.setTextHtmlOverlay(undefined, undefined));
         DocumentManager._overlayViews?.clear();
     }
     static _overlayViews = new ObservableSet<DocumentView>();
@@ -274,7 +274,7 @@ export class DocumentManager {
             // even if we found the document view, if the target is a lightbox, we try to open it in the lightbox to preserve lightbox semantics (eg, there's only one active doc in the lightbox)
             const target = DocCast(targetDoc.annotationOn, targetDoc);
             const contextView = this.getDocumentView(DocCast(target.embedContainer));
-            if (contextView?.docView?._componentView?.addDocTab?.(target, OpenWhere.lightbox)) {
+            if (contextView?.ComponentView?.addDocTab?.(target, OpenWhere.lightbox)) {
                 await new Promise<void>(waitres => setTimeout(() => waitres()));
             }
         }
@@ -327,15 +327,12 @@ export class DocumentManager {
 
             if (options.zoomTextSelections && Doc.UnhighlightTimer && contextView && targetDoc.text_html) {
                 // if the docView is a text anchor, the contextView is the PDF/Web/Text doc
-                contextView.htmlOverlayEffect = options.effect;
-                contextView.textHtmlOverlayTime = options.zoomTime;
-                contextView.textHtmlOverlay = StrCast(targetDoc.text_html);
+                contextView.setTextHtmlOverlay(StrCast(targetDoc.text_html), options.effect);
                 DocumentManager._overlayViews.add(contextView);
             }
             Doc.AddUnHighlightWatcher(() => {
                 docView.Document[Animation] = undefined;
                 DocumentManager.removeOverlayViews();
-                contextView && (contextView.htmlOverlayEffect = undefined);
             });
         }
     }
@@ -344,7 +341,7 @@ export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCe
     const func = () => {
         const cv = DocumentManager.Instance.getDocumentView(containingDoc);
         const dv = DocumentManager.Instance.getDocumentView(doc, cv);
-        if (dv && (!containingDoc || dv._props.docViewPath().lastElement()?.Document === containingDoc)) {
+        if (dv && (!containingDoc || dv.containerViewPath?.().lastElement()?.Document === containingDoc)) {
             DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.Document));
         } else {
             const container = DocCast(containingDoc ?? doc.embedContainer ?? Doc.BestEmbedding(doc));
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index b6dbea33a..398ba3c04 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -156,7 +156,7 @@
 //             x: NumCast(doc.x),
 //             y: NumCast(doc.y) + offset,
 //         };
-//         const parent = this.props.DocumentView?.()._props.docViewPath().lastElement();
+//         const parent = this.props.DocumentView?.().containerViewPath().lastElement();
 //         if (parent?.Document.type === DocumentType.COL) {
 //             let importContainer: Doc;
 //             if (docs.length < 50) {
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index dfc298840..fb8ef7c36 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,6 +1,6 @@
 import { action, computed, makeObservable, observable } from 'mobx';
 import * as React from 'react';
-import { returnFalse } from '../../Utils';
+import { emptyPath, returnFalse } from '../../Utils';
 import { DateField } from '../../fields/DateField';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols';
@@ -9,17 +9,20 @@ import { GetEffectiveAcl, inheritParentAcls } from '../../fields/util';
 import { DocumentType } from '../documents/DocumentTypes';
 import { DocUtils } from '../documents/Documents';
 import { DocumentManager } from '../util/DocumentManager';
+import { Transform } from '../util/Transform';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import { CollectionFreeFormView } from './collections/collectionFreeForm';
 import { DocumentView } from './nodes/DocumentView';
-import { Transform } from '../util/Transform';
 
-///  DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
+/**
+ * DocComponent returns a generic React base class used by Doc views (not the 'Box' views that render the contents of Doc views)
+ *    (e.g.,CollectionFreeFormDocumentView, DocumentViewInternal)
+ *
+ * */
 export interface DocComponentProps {
     Document: Doc;
     LayoutTemplate?: () => Opt<Doc>;
     LayoutTemplateString?: string;
-    ScreenToLocalTransform: () => Transform;
 }
 export function DocComponent<P extends DocComponentProps>() {
     class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
@@ -28,8 +31,6 @@ export function DocComponent<P extends DocComponentProps>() {
             makeObservable(this);
         }
 
-        ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform();
-
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         get Document() {
             return this._props.Document;
@@ -46,21 +47,40 @@ export function DocComponent<P extends DocComponentProps>() {
     return Component;
 }
 
-/// FieldViewBoxProps  -  a generic base class for field views that are not annotatable (e.g. InkingStroke, ColorBox)
-interface ViewBoxBaseProps {
+/**
+ *  ViewBoxBaseComponent  -  base class for non-annotatable views that render the interior contents of a DocumentView
+ *                           (e.g. InkingStroke, ColorBox)
+ */
+export interface ViewBoxBaseProps {
     Document: Doc;
     TemplateDataDocument?: Doc;
     DocumentView?: () => DocumentView;
+    containerViewPath?: () => DocumentView[];
     fieldKey: string;
     isSelected: () => boolean;
     isContentActive: () => boolean | undefined;
     ScreenToLocalTransform: () => Transform;
     renderDepth: number;
 }
+function returnEmptyDocViewList() {
+    return [] as DocumentView[];
+}
 export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
     class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
         ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform();
 
+        get DocumentView() {
+            return this._props.DocumentView;
+        }
+        get docViewPath() {
+            return this.DocumentView?.().docViewPath ?? emptyPath;
+        }
+        get docViewPathFunc() {
+            return this.DocumentView?.().docViewPathFunc ?? returnEmptyDocViewList;
+        }
+        get containerViewPath() {
+            return this._props.containerViewPath;
+        }
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         get Document() {
             return this._props.Document;
@@ -81,10 +101,15 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
     return Component;
 }
 
-///  DocAnnotatbleComponent -return a base class for React views of document fields that are annotatable *and* interactive when selected (e.g., pdf, image)
+/**
+ * DocAnnotatableComponent - base class for annotatable views that render the interior contents of a DocumentView
+ *                       (e.g., PdfBox, ImageBox)
+ * These views should be interactive (respond to pointerEvents) when their conatainer DocumentView is selected
+ */
 export interface ViewBoxAnnotatableProps {
     Document: Doc;
     TemplateDataDocument?: Doc;
+    DocumentView?: () => DocumentView;
     fieldKey: string;
     filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
     isContentActive: () => boolean | undefined;
@@ -106,6 +131,19 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
 
         @observable _annotationKeySuffix = () => 'annotations';
         @observable _isAnyChildContentActive = false;
+
+        get DocumentView() {
+            return this._props.DocumentView;
+        }
+        get docViewPath() {
+            return this.DocumentView?.().docViewPath ?? emptyPath;
+        }
+        get docViewPathFunc() {
+            return this.DocumentView?.().docViewPathFunc ?? returnEmptyDocViewList;
+        }
+        get containerViewPath() {
+            return this.DocumentView?.().containerViewPath ?? returnEmptyDocViewList;
+        }
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         @computed get Document() {
             return this._props.Document;
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index bb28d9ec9..8a4b42ae0 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -72,7 +72,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                 <div
                     className="documentButtonBar-icon documentButtonBar-follow"
                     style={{ backgroundColor: followLink ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: followLink ? Colors.BLACK : Colors.WHITE }}
-                    onClick={undoBatch(e => this._props.views().map(view => view?.docView?.toggleFollowLink(undefined, false)))}>
+                    onClick={undoBatch(e => this._props.views().map(view => view?.toggleFollowLink(undefined, false)))}>
                     <div className="documentButtonBar-followTypes">
                         {followBtn(
                             true,
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 788e78ed2..c96253405 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -303,7 +303,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         SelectionManager.DeselectAll();
     };
 
-    onSelectContainerDocClick = () => SelectionManager.Views?.[0]?._props.docViewPath?.().lastElement()?.select(false);
+    onSelectContainerDocClick = () => SelectionManager.Views?.[0]?.containerViewPath?.().lastElement()?.select(false);
     /**
      * sets up events when user clicks on the border radius editor
      */
@@ -677,7 +677,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             seldocview._props.hideDeleteButton ||
             seldocview.Document.hideDeleteButton ||
             SelectionManager.Views.some(docView => {
-                const collectionAcl = docView._props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView._props.docViewPath().lastElement().dataDoc) : AclEdit;
+                const collectionAcl = docView.containerViewPath?.()?.lastElement() ? GetEffectiveAcl(docView.containerViewPath?.().lastElement().dataDoc) : AclEdit;
                 return collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.Document) !== AclAdmin;
             });
         const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
@@ -805,7 +805,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                     <div key="br" className="documentDecorations-bottomRightResizer" onPointerDown={this.onPointerDown} />
                                 </>
                             )}
-                            {seldocview._props.renderDepth <= 1 || !seldocview._props.docViewPath().lastElement() ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectContainerDocClick, 'tap to select containing document')}
+                            {seldocview._props.renderDepth <= 1 || !seldocview.containerViewPath?.().lastElement() ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectContainerDocClick, 'tap to select containing document')}
                             {useRounding && (
                                 <div
                                     className="documentDecorations-borderRadius"
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index b1b04237c..3c7b5eeca 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -476,7 +476,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
                 isContentActive={returnFalse}
                 renderDepth={0}
                 styleProvider={returnEmptyString}
-                docViewPath={returnEmptyDoclist}
+                containerViewPath={returnEmptyDoclist}
                 focus={emptyFunction}
                 whenChildContentsActiveChanged={emptyFunction}
                 bringToFront={emptyFunction}
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index a5c2d99d2..7dd57e04d 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -27,7 +27,7 @@ export interface InkControlProps {
 export class InkControlPtHandles extends React.Component<InkControlProps> {
     @observable private _overControl = -1;
     get docView() {
-        return this.props.inkView.props.docViewPath().lastElement();
+        return this.props.inkView.DocumentView?.();
     }
 
     componentDidMount() {
@@ -58,11 +58,11 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
                     if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
                     const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] });
                     const inkMoveStart = ptFromScreen({ X: 0, Y: 0 });
-                    InkStrokeProperties.Instance.moveControlPtHandle(this.docView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex, origInk);
+                    this.docView && InkStrokeProperties.Instance.moveControlPtHandle(this.docView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex, origInk);
                     return false;
                 }),
                 action(() => {
-                    if (this.props.inkView.controlUndo) {
+                    if (this.props.inkView.controlUndo && this.docView) {
                         InkStrokeProperties.Instance.snapControl(this.docView, controlIndex);
                     }
                     this.props.inkView.controlUndo?.end();
@@ -78,11 +78,11 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
                         } else {
                             if (brokenIndices?.includes(equivIndex)) {
                                 if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
-                                InkStrokeProperties.Instance.snapHandleTangent(this.docView, equivIndex, handleIndexA, handleIndexB);
+                                this.docView && InkStrokeProperties.Instance.snapHandleTangent(this.docView, equivIndex, handleIndexA, handleIndexB);
                             }
                             if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) {
                                 if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
-                                InkStrokeProperties.Instance.snapHandleTangent(this.docView, controlIndex, handleIndexA, handleIndexB);
+                                this.docView && InkStrokeProperties.Instance.snapHandleTangent(this.docView, controlIndex, handleIndexA, handleIndexB);
                             }
                         }
                         this.props.inkView.controlUndo?.end();
@@ -113,7 +113,7 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
     @action
     onDelete = (e: KeyboardEvent) => {
         if (['-', 'Backspace', 'Delete'].includes(e.key)) {
-            InkStrokeProperties.Instance.deletePoints(this.docView, e.shiftKey);
+            this.docView && InkStrokeProperties.Instance.deletePoints(this.docView, e.shiftKey);
             e.stopPropagation();
         }
     };
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index f31ea2994..52ea89cde 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -241,6 +241,7 @@ export class InkStrokeProperties {
      */
     @undoBatch
     moveControlPtHandle = (inkView: DocumentView, deltaX: number, deltaY: number, controlIndex: number, origInk?: InkData) =>
+        inkView &&
         this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
             const order = controlIndex % 4;
             const closed = InkingStroke.IsClosed(ink);
@@ -369,7 +370,7 @@ export class InkStrokeProperties {
 
     snapToAllCurves = (screenDragPt: { X: number; Y: number }, inkView: DocumentView, snapData: { nearestPt: { X: number; Y: number }; distance: number }, ink: InkData, controlIndex: number) => {
         const containingCollection = inkView.CollectionFreeFormView;
-        const containingDocView = containingCollection?.props.DocumentView?.();
+        const containingDocView = containingCollection?.DocumentView?.();
         containingCollection?.childDocs
             .filter(doc => doc.type === DocumentType.INK)
             .forEach(doc => {
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index a00c26a07..c20399698 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -23,7 +23,7 @@ export interface InkHandlesProps {
 @observer
 export class InkTangentHandles extends React.Component<InkHandlesProps> {
     get docView() {
-        return this.props.inkView.props.docViewPath().lastElement();
+        return this.props.inkView.DocumentView?.();
     }
     /**
      * Handles the movement of a selected handle point when the user clicks and drags.
@@ -42,7 +42,7 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
                 if (e.altKey) this.onBreakTangent(controlIndex);
                 const inkMoveEnd = this.props.inkView.ptFromScreen({ X: delta[0], Y: delta[1] });
                 const inkMoveStart = this.props.inkView.ptFromScreen({ X: 0, Y: 0 });
-                InkStrokeProperties.Instance.moveTangentHandle(this.docView, -(inkMoveEnd.X - inkMoveStart.X), -(inkMoveEnd.Y - inkMoveStart.Y), handleIndex, oppositeHandleIndex, controlIndex);
+                this.docView && InkStrokeProperties.Instance.moveTangentHandle(this.docView, -(inkMoveEnd.X - inkMoveStart.X), -(inkMoveEnd.Y - inkMoveStart.Y), handleIndex, oppositeHandleIndex, controlIndex);
                 return false;
             }),
             action(() => {
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index c652e8966..11b05352d 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -34,7 +34,7 @@ import { InteractionUtils } from '../util/InteractionUtils';
 import { SnappingManager } from '../util/SnappingManager';
 import { UndoManager } from '../util/UndoManager';
 import { ContextMenu } from './ContextMenu';
-import { ViewBoxBaseComponent } from './DocComponent';
+import { ViewBoxBaseComponent, ViewBoxBaseProps } from './DocComponent';
 import { Colors } from './global/globalEnums';
 import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
 import './InkStroke.scss';
@@ -48,7 +48,7 @@ import { StyleProp } from './StyleProvider';
 import { Transform } from '../util/Transform';
 const { default: { INK_MASK_SIZE } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
 @observer
-export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
+export class InkingStroke extends ViewBoxBaseComponent<ViewBoxBaseProps & FieldViewProps>() {
     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);
@@ -122,7 +122,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     onPointerDown = (e: React.PointerEvent) => {
         this._handledClick = false;
-        const inkView = this._props.docViewPath().lastElement();
+        const inkView = this.DocumentView?.();
+        if (!inkView) return;
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
         const screenPts = inkData
             .map(point =>
@@ -162,7 +163,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                     InkStrokeProperties.Instance._currentPoint = -1;
                     this._handledClick = true; // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
                     if (isEditing) {
-                        this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance.addPoints(this._props.docViewPath().lastElement(), this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
+                        this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance.addPoints(inkView, this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
                     }
                 }
             }),
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index ab3be3d7a..aec9e32fc 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -257,7 +257,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
                             styleProvider={DefaultStyleProvider}
                             ScreenToLocalTransform={this.lightboxScreenToLocal}
                             renderDepth={0}
-                            docViewPath={returnEmptyDoclist}
+                            containerViewPath={returnEmptyDoclist}
                             childFilters={returnEmptyFilter}
                             childFiltersByRanges={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 35ffab337..cd6aeea41 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -606,7 +606,7 @@ export class MainView extends ObservableReactComponent<{}> {
                     Document={this.headerBarDoc}
                     addDocTab={DocumentViewInternal.addDocTabFunc}
                     pinToPres={emptyFunction}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     styleProvider={DefaultStyleProvider}
                     addDocument={this.addHeaderDoc}
                     removeDocument={this.removeHeaderDoc}
@@ -641,7 +641,7 @@ export class MainView extends ObservableReactComponent<{}> {
                     addDocument={undefined}
                     addDocTab={DocumentViewInternal.addDocTabFunc}
                     pinToPres={emptyFunction}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     styleProvider={this._hideUI ? DefaultStyleProvider : undefined}
                     isContentActive={returnTrue}
                     removeDocument={undefined}
@@ -735,7 +735,7 @@ export class MainView extends ObservableReactComponent<{}> {
                         addDocument={undefined}
                         addDocTab={DocumentViewInternal.addDocTabFunc}
                         pinToPres={TabDocView.PinDoc}
-                        docViewPath={returnEmptyDoclist}
+                        containerViewPath={returnEmptyDoclist}
                         styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem || this._sidebarContent.proto === Doc.MyTrails ? DashboardStyleProvider : DefaultStyleProvider}
                         removeDocument={returnFalse}
                         ScreenToLocalTransform={this.mainContainerXf}
@@ -769,7 +769,7 @@ export class MainView extends ObservableReactComponent<{}> {
                     PanelWidth={this.leftMenuWidth}
                     PanelHeight={this.leftMenuHeight}
                     renderDepth={0}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     focus={emptyFunction}
                     styleProvider={DefaultStyleProvider}
                     isContentActive={returnTrue}
@@ -900,14 +900,13 @@ export class MainView extends ObservableReactComponent<{}> {
                     Document={Doc.MyDockedBtns}
                     fieldKey="data"
                     dropAction="embed"
-                    setHeight={returnFalse}
                     styleProvider={DefaultStyleProvider}
                     bringToFront={emptyFunction}
                     select={emptyFunction}
                     isAnyChildContentActive={returnFalse}
                     isContentActive={emptyFunction}
                     isSelected={returnFalse}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     moveDocument={this.moveButtonDoc}
                     addDocument={this.addButtonDoc}
                     addDocTab={DocumentViewInternal.addDocTabFunc}
@@ -969,41 +968,6 @@ export class MainView extends ObservableReactComponent<{}> {
         );
     }
 
-    @observable mapBoxHackBool = false;
-    @computed get mapBoxHack() {
-        return this.mapBoxHackBool ? null : (
-            <MapBox
-                ref={action((r: any) => r && (this.mapBoxHackBool = true))}
-                fieldKey="data"
-                select={returnFalse}
-                isSelected={returnFalse}
-                Document={this.headerBarDoc}
-                addDocTab={returnFalse}
-                pinToPres={emptyFunction}
-                docViewPath={returnEmptyDoclist}
-                styleProvider={DefaultStyleProvider}
-                addDocument={returnFalse}
-                removeDocument={returnFalse}
-                fitContentsToBox={returnTrue}
-                isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events)
-                isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive
-                ScreenToLocalTransform={Transform.Identity}
-                childHideResizeHandles={true}
-                childDragAction="move"
-                dontRegisterView={true}
-                PanelWidth={this.headerBarDocWidth}
-                PanelHeight={this.headerBarDocHeight}
-                renderDepth={0}
-                focus={emptyFunction}
-                whenChildContentsActiveChanged={emptyFunction}
-                bringToFront={emptyFunction}
-                childFilters={returnEmptyFilter}
-                childFiltersByRanges={returnEmptyFilter}
-                searchFilterDocs={returnEmptyDoclist}
-            />
-        );
-    }
-
     render() {
         return (
             <div
@@ -1070,7 +1034,6 @@ export class MainView extends ObservableReactComponent<{}> {
                 <LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
                 <CollectionFreeFormLinksView />
                 <OverlayView />
-                {/* {this.mapBoxHack} */}
                 <GPTPopup key="gptpopup" />
                 <SchemaCSVPopUp key="schemacsvpopup" />
                 <GenerativeFill imageEditorOpen={ImageBox.imageEditorOpen} imageEditorSource={ImageBox.imageEditorSource} imageRootDoc={ImageBox.imageRootDoc} addDoc={ImageBox.addDoc} />
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 9d828364d..cfdc648b4 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -198,7 +198,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
             const sourceAnchorCreator = () => this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true); // hyperlink color
 
             const targetCreator = (annotationOn: Doc | undefined) => {
-                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
+                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, undefined, annotationOn, 'yellow');
                 FormattedTextBox.SetSelectOnLoad(target);
                 return target;
             };
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 915c3c18f..89527c415 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -234,7 +234,7 @@ export class OverlayView extends ObservableReactComponent<{}> {
                         whenChildContentsActiveChanged={emptyFunction}
                         focus={emptyFunction}
                         styleProvider={DefaultStyleProvider}
-                        docViewPath={returnEmptyDoclist}
+                        containerViewPath={returnEmptyDoclist}
                         addDocTab={DocumentViewInternal.addDocTabFunc}
                         pinToPres={emptyFunction}
                         childFilters={returnEmptyFilter}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index a69a5f5e8..456b753b4 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -60,18 +60,17 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
                 } else if (re.test(plain)) {
                     const url = plain;
                     if (!url.startsWith(window.location.href)) {
-                        undoBatch(
-                            () =>
-                                this._addDocument?.(
-                                    Docs.Create.WebDocument(url, {
-                                        title: url,
-                                        _width: 500,
-                                        _height: 300,
-                                        data_useCors: true,
-                                        x: newPoint[0],
-                                        y: newPoint[1],
-                                    })
-                                )
+                        undoBatch(() =>
+                            this._addDocument?.(
+                                Docs.Create.WebDocument(url, {
+                                    title: url,
+                                    _width: 500,
+                                    _height: 300,
+                                    data_useCors: true,
+                                    x: newPoint[0],
+                                    y: newPoint[1],
+                                })
+                            )
                         )();
                     } else alert('cannot paste dash into itself');
                 } else if (plain.startsWith('__DashDocId(') || plain.startsWith('__DashCloneId(')) {
@@ -86,7 +85,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
                 } else {
                     FormattedTextBox.PasteOnLoad = e;
                     if (e.clipboardData.getData('dash/pdfAnchor')) e.preventDefault();
-                    UndoManager.RunInBatch(() => this._addLiveTextDoc?.(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined, undefined, 750)), 'paste');
+                    UndoManager.RunInBatch(() => this._addLiveTextDoc?.(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined, undefined)), 'paste');
                 }
             }
             //pasting in images
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 5905f8dbc..bba6285c2 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -345,9 +345,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     @undoBatch
     handlePerspectiveChange = (e: any) => {
         this.selectedDoc && (this.selectedDoc._type_collection = e.target.value);
-        SelectionManager.Views.filter(dv => dv.docView)
-            .map(dv => dv.docView!)
-            .forEach(docView => (docView.layoutDoc._type_collection = e.target.value));
+        SelectionManager.Views.forEach(docView => (docView.layoutDoc._type_collection = e.target.value));
     };
     @computed get onClickVal() {
         const linkButton = IsFollowLinkScript(this.selectedDoc.onClick);
@@ -408,28 +406,26 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     @undoBatch
     @action
     handleOptionChange = (onClick: string) => {
-        SelectionManager.Views.filter(dv => dv.docView)
-            .map(dv => dv.docView!)
-            .forEach(docView => {
-                const linkButton = IsFollowLinkScript(docView.Document.onClick);
-                docView.noOnClick();
-                switch (onClick) {
-                    case 'enterPortal':
-                        docView.makeIntoPortal();
-                        break;
-                    case 'toggleDetail':
-                        docView.setToggleDetail();
-                        break;
-                    case 'linkInPlace':
-                        docView.toggleFollowLink(false, false);
-                        docView.Document.followLinkLocation = linkButton ? OpenWhere.lightbox : undefined;
-                        break;
-                    case 'linkOnRight':
-                        docView.toggleFollowLink(false, false);
-                        docView.Document.followLinkLocation = linkButton ? OpenWhere.addRight : undefined;
-                        break;
-                }
-            });
+        SelectionManager.Views.forEach(docView => {
+            const linkButton = IsFollowLinkScript(docView.Document.onClick);
+            docView.noOnClick();
+            switch (onClick) {
+                case 'enterPortal':
+                    docView.makeIntoPortal();
+                    break;
+                case 'toggleDetail':
+                    docView.setToggleDetail();
+                    break;
+                case 'linkInPlace':
+                    docView.toggleFollowLink(false, false);
+                    docView.Document.followLinkLocation = linkButton ? OpenWhere.lightbox : undefined;
+                    break;
+                case 'linkOnRight':
+                    docView.toggleFollowLink(false, false);
+                    docView.Document.followLinkLocation = linkButton ? OpenWhere.addRight : undefined;
+                    break;
+            }
+        });
     };
 
     @undoBatch
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index a710e7816..54f141a36 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -27,7 +27,7 @@ export class PropertiesDocContextSelector extends ObservableReactComponent<Prope
     @computed get _docs() {
         if (!this._props.DocView) return [];
         const target = this._props.DocView._props.Document;
-        const targetContext = this._props.DocView._props.docViewPath().lastElement()?.Document;
+        const targetContext = this._props.DocView.containerViewPath?.().lastElement()?.Document;
         const embeddings = DocListCast(target.proto_embeddings);
         const containerProtos = embeddings.filter(embedding => embedding.embedContainer && embedding.embedContainer instanceof Doc).reduce((set, embedding) => set.add(Cast(embedding.embedContainer, Doc, null)), new Set<Doc>());
         const containerSets = Array.from(containerProtos.keys()).map(container => DocListCast(container.proto_embeddings));
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 2fcb5d12a..13057ffbf 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -299,7 +299,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                         renderDepth={1}
                         fitContentsToBox={returnTrue}
                         styleProvider={DefaultStyleProvider}
-                        docViewPath={returnEmptyDoclist}
+                        containerViewPath={returnEmptyDoclist}
                         dontCenter={'y'}
                         isDocumentActive={returnFalse}
                         isContentActive={emptyFunction}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index d077543f1..572971391 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -20,7 +20,7 @@ import { SnappingManager } from '../util/SnappingManager';
 import { undoBatch, UndoManager } from '../util/UndoManager';
 import { TreeSort } from './collections/TreeSort';
 import { Colors } from './global/globalEnums';
-import { DocumentViewInternalProps, DocumentViewProps } from './nodes/DocumentView';
+import { DocumentViewProps } from './nodes/DocumentView';
 import { FieldViewProps } from './nodes/FieldView';
 import { KeyValueBox } from './nodes/KeyValueBox';
 import { PropertiesView } from './PropertiesView';
@@ -80,12 +80,14 @@ export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
 
 // a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab
 //
-export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any {
+export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any {
     const remoteDocHeader = 'author;author_date;noMargin';
     const docProps = testDocProps(props) ? props : undefined;
     const fieldProps = testFieldProps(props) ? props : undefined;
     const isCaption = property.includes(':caption');
     const isAnchor = property.includes(':anchor');
+    const isNonTransparent = property.includes(':nonTransparent');
+    const isNonTransparentLevel = isNonTransparent ? Number(property.replace(/.*:nonTransparent([0-9]+).*/, '$1')) : 0; // property.includes(':nonTransparent');
     const isContent = property.includes(':content');
     const isAnnotated = property.includes(':annotated');
     const isInk = () => doc?._layout_isSvg && !docProps?.LayoutTemplateString;
@@ -95,7 +97,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInter
     const fieldKey = fieldProps?.fieldKey ? fieldProps.fieldKey + '_' : isCaption ? 'caption_' : '';
     const lockedPosition = () => doc && BoolCast(doc._lockedPosition);
     const titleHeight = () => props?.styleProvider?.(doc, props, StyleProp.TitleHeight);
-    const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor);
+    const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor + ':nonTransparent' + (isNonTransparentLevel + 1));
     const color = () => props?.styleProvider?.(doc, props, StyleProp.Color);
     const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity);
     const layout_showTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle);
@@ -140,7 +142,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInter
         case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?._text_fontFamily, StrCast(Doc.UserDoc().fontFamily)));
         case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(doc?._text_fontWeight, StrCast(Doc.UserDoc().fontWeight)));
         case StyleProp.FillColor:  return StrCast(doc?._fillColor, StrCast(doc?.fillColor, 'transparent'));
-        case StyleProp.ShowCaption:return doc?._type_collection === CollectionViewType.Carousel || props?.hideCaptions ? undefined : StrCast(doc?._layout_showCaption);
+        case StyleProp.ShowCaption:return doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
         case StyleProp.TitleHeight:
             return (props?.DocumentView?.().screenToViewTransform().Scale ?? 1) * NumCast(Doc.UserDoc().headerHeight,30)
         case StyleProp.ShowTitle:
@@ -167,8 +169,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInter
             if (doc?.type === DocumentType.FONTICON) return SettingsManager.userColor;
             const docColor: Opt<string> = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color));
             if (docColor) return docColor;
-            const docView = props?.DocumentView?.();
-            const backColor = backgroundCol() || docView?._props.styleProvider?.(docView._props.treeViewDoc, docView.docView?._props, StyleProp.BackgroundColor);
+            const parView = props?.DocumentView?.();
+            const backColor = backgroundCol();
             return backColor ? lightOrDark(backColor) : undefined;
         case StyleProp.BorderRounding:
             return StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.layout_borderRounding, doc?._type_collection === CollectionViewType.Pile ? '50%' : ''));
@@ -198,18 +200,18 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInter
                 : 0;
         case StyleProp.BackgroundColor: {
             if (SettingsManager.Instance.LastPressedBtn === doc) return SettingsManager.userColor; // hack to indicate active menu panel item
-            let docColor: Opt<string> = StrCast(doc?.[fieldKey + '_backgroundColor'], StrCast(doc?._backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : ''));
+            let docColor: Opt<string> = StrCast(doc?.[fieldKey + 'backgroundColor'], StrCast(doc?._backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : ''));
             // prettier-ignore
             switch (doc?.type) {
                 case DocumentType.PRESELEMENT: docColor = docColor || ""; break;
                 case DocumentType.PRES:        docColor = docColor || 'transparent'; break;
                 case DocumentType.FONTICON:    docColor = boxBackground ? undefined : docColor || Colors.DARK_GRAY; break;
-                case DocumentType.RTF:         docColor = docColor ||  Colors.LIGHT_GRAY; break;
+                case DocumentType.RTF:         docColor = docColor || Colors.LIGHT_GRAY; break;
                 case DocumentType.LINK:        docColor = (isAnchor ? docColor : undefined); break;
                 case DocumentType.INK:         docColor = doc?.stroke_isInkMask ? 'rgba(0,0,0,0.7)' : undefined; break;
                 case DocumentType.EQUATION:    docColor = docColor || 'transparent'; break;
                 case DocumentType.LABEL:       docColor = docColor || Colors.LIGHT_GRAY; break;
-                case DocumentType.BUTTON:      docColor = docColor ||  Colors.LIGHT_GRAY; break;
+                case DocumentType.BUTTON:      docColor = docColor || Colors.LIGHT_GRAY; break;
                 case DocumentType.IMG:
                 case DocumentType.WEB:
                 case DocumentType.PDF:
@@ -230,6 +232,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInter
                 //if (doc._type_collection !== CollectionViewType.Freeform && doc._type_collection !== CollectionViewType.Time) return "rgb(62,62,62)";
                 default: docColor = docColor || (Colors.WHITE);
             }
+            if (isNonTransparent && isNonTransparentLevel < 9 && (!docColor || docColor === 'transparent') && doc?.embedContainer && props?.styleProvider) {
+                return props.styleProvider(DocCast(doc.embedContainer), props, StyleProp.BackgroundColor+":nonTransparent"+(isNonTransparentLevel+1));
+            }
             return (docColor && !doc) ? DashColor(docColor).fade(0.5).toString() : docColor;
         }
         case StyleProp.BoxShadow: {
@@ -252,7 +257,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInter
                 default:
                     return doc.z
                         ? `#9c9396  ${StrCast(doc?.layout_boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow
-                        : props?.docViewPath().lastElement()?.Document._freeform_useClusters
+                        : props?.DocumentView?.().containerViewPath?.().lastElement()?.Document._freeform_useClusters
                         ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
                         : NumCast(doc.group, -1) !== -1
                         ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
@@ -277,7 +282,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInter
             return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
         case StyleProp.Decorations:
             const lock = () => {
-                if (props?.docViewPath().lastElement()?.Document?._type_collection === CollectionViewType.Freeform) {
+                if (props?.DocumentView?.().containerViewPath?.().lastElement()?.Document?._type_collection === CollectionViewType.Freeform) {
                     return doc?.pointerEvents !== 'none' ? null : (
                         <div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
                             <FontAwesomeIcon icon='lock' size="lg" />
@@ -321,7 +326,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInter
                                 "this view inherits filters from one of its parents"}
                             color={SettingsManager.userColor}
                             background={showFilterIcon}
-                            items={[ ...(dashView ? [dashView]: []), ...(props?.docViewPath?.()??[]), ...(props?.DocumentView?[props?.DocumentView?.()]:[])]
+                            items={[ ...(dashView ? [dashView]: []), ...(props?.DocumentView?.().docViewPath??[])]
                                     .filter(dv => StrListCast(dv?.Document.childFilters).length || StrListCast(dv?.Document.childRangeFilters).length)
                                     .map(dv => ({
                                 text: StrCast(dv?.Document.title),
@@ -339,7 +344,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInter
                 const audioIconColors: { [key: string]: string } = { recording: 'red', playing: 'green', stopped: 'blue' };
                 return (
                     <Tooltip title={<div>{StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations_text']).lastElement()}</div>}>
-                        <div className="styleProvider-audio" onPointerDown={() => DocumentManager.Instance.getFirstDocumentView(doc)?.docView?.playAnnotation()}>
+                        <div className="styleProvider-audio" onPointerDown={() => DocumentManager.Instance.getFirstDocumentView(doc)?.playAnnotation()}>
                             <FontAwesomeIcon className="documentView-audioFont" style={{ color: audioIconColors[audioAnnoState(doc)] }} icon={'file-audio'} size="sm" />
                         </div>
                     </Tooltip>
@@ -374,7 +379,7 @@ export function DashboardToggleButton(doc: Doc, field: string, onIcon: IconProp,
 /**
  * add hide button decorations for the "Dashboards" flyout TreeView
  */
-export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewInternalProps>, property: string) {
+export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) {
     if (doc && property.split(':')[0] === StyleProp.Decorations) {
         return doc._type_collection === CollectionViewType.Docking || Doc.IsSystem(doc)
             ? null
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index b42f3c1e9..e5154efcb 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -114,8 +114,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 <CollectionTreeView
                     Document={Doc.MyTemplates}
                     styleProvider={DefaultStyleProvider}
-                    setHeight={returnFalse}
-                    docViewPath={returnEmptyDoclist}
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index a125f1356..208fc45b5 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -7,7 +7,7 @@ import { Doc, Opt } from '../../../fields/Doc';
 import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { StyleProp } from '../StyleProvider';
-import { DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import './CollectionCarouselView.scss';
 import { CollectionSubView } from './CollectionSubView';
@@ -41,7 +41,7 @@ export class CollectionCarouselView extends CollectionSubView() {
         e.stopPropagation();
         this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
     };
-    captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+    captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<FieldViewProps>, property: string): any => {
         // first look for properties on the document in the carousel, then fallback to properties on the container
         const childValue = doc?.['caption-' + property] ? this._props.styleProvider?.(doc, captionProps, property) : undefined;
         return childValue ?? this._props.styleProvider?.(this.layoutDoc, captionProps, property);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 874cdffd9..1f867fc44 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -431,7 +431,7 @@ export class CollectionDockingView extends CollectionSubView() {
     };
 
     public CaptureThumbnail() {
-        const content = this._props.DocumentView?.()?.ContentDiv;
+        const content = this.DocumentView?.()?.ContentDiv;
         if (content) {
             const _width = Number(getComputedStyle(content).width.replace('px', ''));
             const _height = Number(getComputedStyle(content).height.replace('px', ''));
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 98ae01591..a54a5ec81 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -97,14 +97,12 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
                     Document={selDoc}
                     fieldKey="data"
                     dropAction="embed"
-                    setHeight={returnFalse}
                     styleProvider={DefaultStyleProvider}
                     bringToFront={emptyFunction}
                     select={emptyFunction}
                     isContentActive={returnTrue}
                     isAnyChildContentActive={returnFalse}
                     isSelected={returnFalse}
-                    docViewPath={returnEmptyDoclist}
                     moveDocument={returnFalse}
                     addDocument={returnFalse}
                     addDocTab={DocumentViewInternal.addDocTabFunc}
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 302ccd2db..0795104d9 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -18,7 +18,7 @@ import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { LightboxView } from '../LightboxView';
-import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
@@ -203,7 +203,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         }
     };
 
-    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
+    styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
         switch (property) {
             case StyleProp.BoxShadow:
                 if (doc && DragManager.docsBeingDragged.includes(doc)) {
@@ -238,7 +238,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 PanelWidth={width}
                 PanelHeight={height}
                 styleProvider={this.styleProvider}
-                docViewPath={this._props.docViewPath}
+                containerViewPath={this.docViewPathFunc}
                 layout_fitWidth={this._props.childLayoutFitWidth}
                 isContentActive={emptyFunction}
                 onKey={this.onKeyDown}
@@ -511,7 +511,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                             action((entries: any) => {
                                 if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.IsDragging) {
                                     const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))));
-                                    if (!LightboxView.IsLightboxDocView(this._props.docViewPath())) {
+                                    if (!LightboxView.IsLightboxDocView(this.docViewPath)) {
                                         this._props.setHeight?.(height);
                                     }
                                 }
@@ -643,7 +643,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 onDragOver={e => this.onPointerMove(true, e.clientX, e.clientY)}
                 onDrop={this.onExternalDrop.bind(this)}
                 onContextMenu={this.onContextMenu}
-                onWheel={e => this._props.isContentActive(true) && e.stopPropagation()}>
+                onWheel={e => this._props.isContentActive() && e.stopPropagation()}>
                 {this.renderedSections}
             </div>
         );
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index b1d379631..d0df77cbe 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -48,7 +48,7 @@ export class CollectionPileView extends CollectionSubView() {
     removePileDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
         (doc instanceof Doc ? [doc] : doc).forEach(d => Doc.deiconifyView(d));
         const ret = this._props.moveDocument?.(doc, targetCollection, addDoc) || false;
-        if (ret && !DocListCast(this.dataDoc[this.fieldKey ?? 'data']).length) this._props.DocumentView?.()._props.removeDocument?.(this.Document);
+        if (ret && !DocListCast(this.dataDoc[this.fieldKey ?? 'data']).length) this.DocumentView?.()._props.removeDocument?.(this.Document);
         return ret;
     };
 
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 22a67c501..1239a038a 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -23,7 +23,7 @@ import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { CollectionSubView } from '../collections/CollectionSubView';
 import { LightboxView } from '../LightboxView';
 import { AudioWaveform } from '../nodes/audio/AudioWaveform';
-import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
+import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
 import { LabelBox } from '../nodes/LabelBox';
 import { VideoBox } from '../nodes/VideoBox';
 import { ObservableReactComponent } from '../ObservableReactComponent';
@@ -825,7 +825,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
                     ref={action((r: DocumentView | null) => (anchor.view = r))}
                     Document={mark}
                     TemplateDataDocument={undefined}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     pointerEvents={this.noEvents ? returnNone : undefined}
                     styleProvider={this._props.styleProvider}
                     renderDepth={this._props.renderDepth + 1}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 995f071ca..11ca62b39 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -24,7 +24,7 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
 import { LightboxView } from '../LightboxView';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
-import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
@@ -265,7 +265,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         return undefined;
     };
 
-    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
+    styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
         if (property === StyleProp.Opacity && doc) {
             if (this._props.childOpacity) {
                 return this._props.childOpacity();
@@ -325,9 +325,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 renderDepth={this._props.renderDepth + 1}
                 PanelWidth={panelWidth}
                 PanelHeight={panelHeight}
-                pointerEvents={this._props.DocumentView?.()._props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
+                pointerEvents={this.DocumentView?.()._props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
                 styleProvider={this.styleProvider}
-                docViewPath={this._props.docViewPath}
+                containerViewPath={this.docViewPathFunc}
                 layout_fitWidth={this.childFitWidth}
                 isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive}
                 onKey={this.onKeyDown}
@@ -540,7 +540,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                             action((entries: any) => {
                                 if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.IsDragging) {
                                     const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))));
-                                    if (!LightboxView.IsLightboxDocView(this._props.docViewPath())) {
+                                    if (!LightboxView.IsLightboxDocView(this.docViewPath)) {
                                         this._props.setHeight?.(height);
                                     }
                                 }
@@ -681,7 +681,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                     renderDepth={this._props.renderDepth}
                     focus={emptyFunction}
                     styleProvider={this._props.styleProvider}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
                     childFilters={this._props.childFilters}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 570330174..2e0a75281 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -21,7 +21,7 @@ import { ImageUtils } from '../../util/Import & Export/ImageUtils';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { UndoManager, undoBatch } from '../../util/UndoManager';
-import { ViewBoxBaseComponent } from '../DocComponent';
+import { ViewBoxBaseComponent, ViewBoxBaseProps } from '../DocComponent';
 import { LoadingBox } from '../nodes/LoadingBox';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { CollectionView, CollectionViewProps } from './CollectionView';
@@ -31,7 +31,7 @@ export interface SubCollectionViewProps extends CollectionViewProps {
 }
 
 export function CollectionSubView<X>(moreProps?: X) {
-    class CollectionSubView extends ViewBoxBaseComponent<X & SubCollectionViewProps>() {
+    class CollectionSubView extends ViewBoxBaseComponent<X & SubCollectionViewProps & ViewBoxBaseProps>() {
         private dropDisposer?: DragManager.DragDropDisposer;
         private gestureDisposer?: GestureUtils.GestureEventDisposer;
         protected _mainCont?: HTMLDivElement;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 18e0b98ef..76b934802 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -169,7 +169,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             this._props.removeDocument?.(doc);
             if (ind > 0 && prev) {
                 FormattedTextBox.SetSelectOnLoad(prev);
-                DocumentManager.Instance.getDocumentView(prev, this._props.DocumentView?.())?.select(false);
+                DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false);
             }
             return true;
         }
@@ -347,7 +347,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                     renderDepth={this._props.renderDepth + 1}
                     focus={emptyFunction}
                     styleProvider={this._props.styleProvider}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
                     childFilters={this._props.childFilters}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 4a239e4b1..7c9ef9f48 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -232,13 +232,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
     childLayoutTemplate = () => this._props.childLayoutTemplate?.() || Cast(this.Document.childLayoutTemplate, Doc, null);
     isContentActive = (outsideReaction?: boolean) => this._isContentActive;
 
-    pointerEvents = () => {
-        const viewPath = this._props.DocumentView?.()?._props.docViewPath();
-        return (
-            this.layoutDoc._lockedPosition && //
-            viewPath?.lastElement()?.Document?._type_collection === CollectionViewType.Freeform
-        );
-    };
+    pointerEvents = () =>
+        this.layoutDoc._lockedPosition && //
+        this.Document?._type_collection === CollectionViewType.Freeform;
 
     render() {
         TraceMobx();
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 80808be92..2441c67e6 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -29,7 +29,7 @@ import { LightboxView } from '../LightboxView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
 import { Colors } from '../global/globalEnums';
-import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
 import { DashFieldView } from '../nodes/formattedText/DashFieldView';
 import { PinProps, PresBox, PresMovement } from '../nodes/trails';
@@ -469,7 +469,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                     dontCenter={'y'}
                     whenChildContentsActiveChanged={this.whenChildContentActiveChanges}
                     focus={this.focusFunc}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     bringToFront={emptyFunction}
                     pinToPres={TabDocView.PinDoc}
                 />
@@ -528,7 +528,7 @@ class TabMiniThumb extends React.Component<TabMiniThumbProps> {
 }
 @observer
 export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps> {
-    static miniStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+    static miniStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => {
         if (doc) {
             switch (property.split(':')[0]) {
                 default:
@@ -595,10 +595,9 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
             <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}>
                 <CollectionFreeFormView
                     Document={this._props.document}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
                     noOverlay={true} // don't render overlay Docs since they won't scale
-                    setHeight={returnFalse}
                     isContentActive={emptyFunction}
                     isAnyChildContentActive={returnFalse}
                     select={emptyFunction}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 01b80e209..205d536de 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -27,7 +27,7 @@ import { UndoManager, undoBatch, undoable } from '../../util/UndoManager';
 import { EditableView } from '../EditableView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { StyleProp } from '../StyleProvider';
-import { DocumentView, DocumentViewInternal, DocumentViewInternalProps, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
@@ -198,7 +198,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         const ind = DocListCast(this.dataDoc[key]).indexOf(doc instanceof Doc ? doc : doc.lastElement());
 
         const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
-        res && ind > 0 && DocumentManager.Instance.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.treeView._props.DocumentView?.())?.select(false);
+        res && ind > 0 && DocumentManager.Instance.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.treeView.DocumentView?.())?.select(false);
         return res;
     };
 
@@ -867,7 +867,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             e.preventDefault();
         }
     };
-    titleStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+    titleStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
         if (!doc || doc !== this.Document) return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
 
         const treeView = this.treeView;
@@ -899,7 +899,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         }
         return treeView._props.styleProvider?.(doc, props, property);
     };
-    embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+    embeddedStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
         if (property.startsWith(StyleProp.Decorations)) return null;
         return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
     };
@@ -977,7 +977,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 hideResizeHandles={this.treeView.outlineMode}
                 styleProvider={this.titleStyleProvider}
                 onClickScriptDisable="never" // tree docViews have a script to show fields, etc.
-                docViewPath={this.treeView._props.docViewPath}
+                containerViewPath={this.treeView.docViewPathFunc}
                 treeViewDoc={this.treeView.Document}
                 addDocument={undefined}
                 addDocTab={this._props.addDocTab}
@@ -1084,7 +1084,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                     ScreenToLocalTransform={this.docTransform}
                     renderDepth={this._props.renderDepth + 1}
                     treeViewDoc={this.treeView?.Document}
-                    docViewPath={this.treeView._props.docViewPath}
+                    containerViewPath={this.treeView.docViewPathFunc}
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8268a47d8..a8f22aaae 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -38,7 +38,7 @@ import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } fr
 import { LightboxView } from '../../LightboxView';
 import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewWrapper } from '../../nodes/CollectionFreeFormDocumentView';
 import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
-import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView';
 import { FieldViewProps } from '../../nodes/FieldView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../../nodes/trails/PresBox';
@@ -162,7 +162,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return this._props.isAnnotationOverlay || this._props.originTopLeft ? 0 : this._props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
     }
     @computed get cachedCenteringShiftY(): number {
-        const dv = this._props.DocumentView?.();
+        const dv = this.DocumentView?.();
         const fitWidth = this._props.layout_fitWidth?.(this.Document) ?? dv?.layoutDoc.layout_fitWidth;
         const scaling = !this.nativeDimScaling ? 1 : this.nativeDimScaling;
         // if freeform has a native aspect, then the panel height needs to be adjusted to match it
@@ -250,7 +250,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     selectDocuments = (docs: Doc[]) => {
         SelectionManager.DeselectAll();
-        docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this._props.DocumentView?.())).forEach(dv => dv && SelectionManager.SelectView(dv, true));
+        docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())).forEach(dv => dv && SelectionManager.SelectView(dv, true));
     };
     addDocument = (newBox: Doc | Doc[]) => {
         let retVal = false;
@@ -409,7 +409,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @undoBatch
     internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData) {
-        if (linkDragData.linkDragView.props.docViewPath().includes(this._props.docViewPath().lastElement())) {
+        if (this.DocumentView?.() && linkDragData.linkDragView.containerViewPath?.().includes(this.DocumentView())) {
             const [x, y] = this.screenToFreeformContentsXf.transformPoint(de.x, de.y);
             let added = false;
             //  do nothing if link is dropped into any freeform view parent of dragged document
@@ -485,7 +485,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             const ptsParent = e;
             if (ptsParent) {
                 const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
-                const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this._props.DocumentView?.())!);
+                const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.DocumentView?.())!);
                 const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
                 const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'embed' : undefined);
                 de.moveDocument = this._props.moveDocument;
@@ -586,7 +586,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }
     };
 
-    clusterStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
+    clusterStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => {
         let styleProp = this._props.styleProvider?.(doc, props, property); // bcz: check 'props'  used to be renderDepth + 1
         if (doc && this.childDocList?.includes(doc))
             switch (property) {
@@ -629,7 +629,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this._downY = this._lastY = e.pageY;
         this._downTime = Date.now();
         const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode;
-        if (e.button === 0 && (!(e.ctrlKey && !e.metaKey) || scrollMode !== freeformScrollMode.Pan) && this._props.isContentActive(true)) {
+        if (e.button === 0 && (!(e.ctrlKey && !e.metaKey) || scrollMode !== freeformScrollMode.Pan) && this._props.isContentActive()) {
             if (!this.Document.isGroup) {
                 // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag
                 // prettier-ignore
@@ -704,7 +704,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     @action
     onEraserUp = (e: PointerEvent): void => {
-        this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.Document));
+        this._deleteList.forEach(ink => ink._props.removeDocument?.(ink.Document));
         this._deleteList = [];
         this._batch?.end();
     };
@@ -714,7 +714,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (this._lightboxDoc) this._lightboxDoc = undefined;
         if (Utils.isClick(e.pageX, e.pageY, this._downX, this._downY, this._downTime)) {
             if (this.onBrowseClickHandler()) {
-                this.onBrowseClickHandler().script.run({ documentView: this._props.DocumentView?.(), clientX: e.clientX, clientY: e.clientY });
+                this.onBrowseClickHandler().script.run({ documentView: this.DocumentView?.(), clientX: e.clientX, clientY: e.clientY });
                 e.stopPropagation();
                 e.preventDefault();
             } else if (this.isContentActive() && e.shiftKey) {
@@ -737,7 +737,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const ctrlKey = e.ctrlKey && !e.shiftKey;
         const shiftKey = e.shiftKey && !e.ctrlKey;
         PresBox.Instance?.pauseAutoPres();
-        this._props.DocumentView?.().clearViewTransition();
+        this.DocumentView?.().clearViewTransition();
         const [dxi, dyi] = this.screenToFreeformContentsXf.transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
         const { x: dx, y: dy } = Utils.rotPt(dxi, dyi, this.ScreenToLocalBoxXf().Rotate);
         this.setPan(NumCast(this.Document[this.panXFieldKey]) - (ctrlKey ? 0 : dx), NumCast(this.Document[this.panYFieldKey]) - (shiftKey ? 0 : dy), 0, true);
@@ -808,7 +808,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) };
 
         return this.childDocs
-            .map(doc => DocumentManager.Instance.getDocumentView(doc, this._props.DocumentView?.()))
+            .map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.()))
             .filter(inkView => inkView?.ComponentView instanceof InkingStroke)
             .map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
             .filter(
@@ -905,7 +905,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this.childDocs
             .filter(doc => doc.type === DocumentType.INK && !doc.dontIntersect)
             .forEach(doc => {
-                const otherInk = DocumentManager.Instance.getDocumentView(doc, this._props.DocumentView?.())?.ComponentView as InkingStroke;
+                const otherInk = DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())?.ComponentView as InkingStroke;
                 const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
                 const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point));
                 const otherCtrlPts = otherScreenPts.map(spt => (ink.ComponentView as InkingStroke).ptFromScreen(spt));
@@ -979,7 +979,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         freeformScrollMode.Zoom : freeformScrollMode.Pan // prettier-ignore
         ) {
             case freeformScrollMode.Pan:
-                if (((!e.metaKey && !e.altKey) || Doc.UserDoc().freeformScrollMode === freeformScrollMode.Zoom) && this._props.isContentActive(true)) {
+                if (((!e.metaKey && !e.altKey) || Doc.UserDoc().freeformScrollMode === freeformScrollMode.Zoom) && this._props.isContentActive()) {
                     const deltaX = e.shiftKey ? e.deltaX : e.ctrlKey ? 0 : e.deltaX;
                     const deltaY = e.shiftKey ? 0 : e.ctrlKey ? e.deltaY : e.deltaY;
                     this.scrollPan({ deltaX: -deltaX * this.screenToFreeformContentsXf.Scale, deltaY: e.shiftKey ? 0 : -deltaY * this.screenToFreeformContentsXf.Scale });
@@ -987,7 +987,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 }
             default:
             case freeformScrollMode.Zoom:
-                if ((e.ctrlKey || !scrollable) && this._props.isContentActive(true)) {
+                if ((e.ctrlKey || !scrollable) && this._props.isContentActive()) {
                     this.zoom(e.clientX, e.clientY, Math.max(-1, Math.min(1, e.deltaY))); // if (!this._props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
                     e.preventDefault();
                 }
@@ -1065,7 +1065,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     nudge = (x: number, y: number, nudgeTime: number = 500) => {
-        const collectionDoc = this._props.docViewPath().lastElement().Document;
+        const collectionDoc = this.Document;
         if (collectionDoc?._type_collection !== CollectionViewType.Freeform) {
             this.setPan(
                 NumCast(this.layoutDoc[this.panXFieldKey]) + ((this._props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
@@ -1244,7 +1244,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 moveDocument={this._props.moveDocument}
                 pinToPres={this._props.pinToPres}
                 whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
-                docViewPath={this._props.docViewPath}
+                containerViewPath={this.docViewPathFunc}
                 styleProvider={this.clusterStyleProvider}
                 dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
                 bringToFront={this.bringToFront}
@@ -1540,7 +1540,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     updateIcon = () =>
         CollectionFreeFormView.UpdateIcon(
             this.layoutDoc[Id] + '-icon' + new Date().getTime(),
-            this._props.docViewPath().lastElement().ContentDiv!,
+            this.DocumentView?.().ContentDiv!,
             NumCast(this.layoutDoc._width),
             NumCast(this.layoutDoc._height),
             this._props.PanelWidth(),
@@ -1741,7 +1741,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     incrementalRendering = () => this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])).length !== 0;
 
     incrementalRender = action(() => {
-        if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath())) {
+        if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.docViewPath)) {
             const layout_unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
             const loadIncrement = 5;
             for (var i = 0; i < Math.min(layout_unrendered.length, loadIncrement); i++) {
@@ -1755,7 +1755,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     // which will be a DocumentView.  In this sitation, this freeform views acts as an annotation overlay for
     // the underlying DocumentView and will pan and scoll with the underlying Documen tView.
     @computed get underlayViews() {
-        return this._props.children ? [toJS(this._props.children)] : [];
+        return this._props.children ? this._props.children : [];
     }
 
     @computed get placeholder() {
@@ -1798,7 +1798,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 brushedView={this.brushedView}
                 isAnnotationOverlay={this.isAnnotationOverlay}
                 transform={this.PanZoomCenterXf}
-                transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this._props.DocumentView?.()?.Document._viewTransition, 'string', null))}
+                transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null))}
                 viewDefDivClick={this._props.viewDefDivClick}>
                 {this.underlayViews}
                 {this.contentViews}
@@ -1896,6 +1896,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         <DocumentView
                             {...this._props}
                             Document={this._lightboxDoc}
+                            containerViewPath={this.docViewPathFunc}
                             TemplateDataDocument={undefined}
                             PanelWidth={this.lightboxPanelWidth}
                             PanelHeight={this.lightboxPanelHeight}
@@ -1942,15 +1943,15 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
         DocumentManager.Instance.showDocument(dv.Document, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => {
             if (!focused) {
                 const selfFfview = !dv.Document.isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
-                let containers = dv.props.docViewPath();
+                let containers = dv.containerViewPath?.() ?? [];
                 let parFfview = dv.CollectionFreeFormView;
                 for (var cont of containers) {
                     parFfview = parFfview ?? cont.CollectionFreeFormView;
                 }
-                while (parFfview?.Document.isGroup) parFfview = parFfview.props.DocumentView?.().CollectionFreeFormView;
+                while (parFfview?.Document.isGroup) parFfview = parFfview.DocumentView?.().CollectionFreeFormView;
                 const ffview = selfFfview && selfFfview.layoutDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it.  Otherwise, zoom out on the parent ffview
                 ffview?.zoomSmoothlyAboutPt(ffview.screenToFreeformContentsXf.transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
-                Doc.linkFollowHighlight(dv?.props.Document, false);
+                Doc.linkFollowHighlight(dv?.Document, false);
             }
         });
 }
@@ -1968,13 +1969,13 @@ ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) {
 });
 ScriptingGlobals.add(function pinWithView(pinContent: boolean) {
     SelectionManager.Views.forEach(view =>
-        view.props.pinToPres(view.Document, {
+        view._props.pinToPres(view.Document, {
             currentFrame: Cast(view.Document.currentFrame, 'number', null),
             pinData: {
                 poslayoutview: pinContent,
                 dataview: pinContent,
             },
-            pinViewport: MarqueeView.CurViewBounds(view.Document, view.props.PanelWidth(), view.props.PanelHeight()),
+            pinViewport: MarqueeView.CurViewBounds(view.Document, view._props.PanelWidth(), view._props.PanelHeight()),
         })
     );
 });
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index d105b04f7..54ade031b 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -147,8 +147,8 @@ export class CollectionLinearView extends CollectionSubView() {
         switch (doc.layout) {
             case '<LinkingUI>':          return this.getLinkUI();
             case '<CurrentlyPlayingUI>': return this.getCurrentlyPlayingUI();
-            case '<UndoStack>':          return <UndoStack />;
-            case '<Branching>':          return Doc.UserDoc().isBranchingMode  ? <BranchingTrailManager /> : null;
+            case '<UndoStack>':          return <UndoStack key={doc[Id]}/>;
+            case '<Branching>':          return Doc.UserDoc().isBranchingMode  ? <BranchingTrailManager key={doc[Id]} /> : null;
         }
 
         const nested = doc._type_collection === CollectionViewType.Linear;
@@ -189,7 +189,7 @@ export class CollectionLinearView extends CollectionSubView() {
                     dontRegisterView={BoolCast(this.Document.childDontRegisterViews)}
                     focus={emptyFunction}
                     styleProvider={this._props.styleProvider}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={this.docViewPathFunc}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
                     childFilters={this._props.childFilters}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 563084af8..dd1305465 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -127,7 +127,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
     private get totalRatioAllocation(): number | undefined {
         const layoutInfoLen = this.resolvedLayoutInformation.widthSpecifiers.length;
         if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
-            return this._props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1)) - 2 * NumCast(this._props.Document._xMargin);
+            return this._props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1)) - 2 * NumCast(this.Document._xMargin);
         }
     }
 
@@ -259,14 +259,14 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 Document={childLayout}
                 TemplateDataDocument={childLayout.resolvedDataDoc as Doc}
                 styleProvider={this._props.styleProvider}
-                docViewPath={this._props.docViewPath}
+                containerViewPath={this.docViewPathFunc}
                 LayoutTemplate={this._props.childLayoutTemplate}
                 LayoutTemplateString={this._props.childLayoutString}
                 renderDepth={this._props.renderDepth + 1}
                 PanelWidth={width}
                 PanelHeight={height}
                 rootSelected={this.rootSelected}
-                dragAction={(this._props.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
+                dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
                 onClick={this.onChildClickHandler}
                 onDoubleClick={this.onChildDoubleClickHandler}
                 suppressSetHeight={true}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index bf0d39197..48c4173d6 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -122,7 +122,7 @@ export class CollectionMultirowView extends CollectionSubView() {
     private get totalRatioAllocation(): number | undefined {
         const layoutInfoLen = this.resolvedLayoutInformation.heightSpecifiers.length;
         if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
-            return this._props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this._props.Document._yMargin);
+            return this._props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this.Document._yMargin);
         }
     }
 
@@ -254,14 +254,14 @@ export class CollectionMultirowView extends CollectionSubView() {
                 Document={layout}
                 TemplateDataDocument={layout.resolvedDataDoc as Doc}
                 styleProvider={this._props.styleProvider}
-                docViewPath={this._props.docViewPath}
+                containerViewPath={this.docViewPathFunc}
                 LayoutTemplate={this._props.childLayoutTemplate}
                 LayoutTemplateString={this._props.childLayoutString}
                 renderDepth={this._props.renderDepth + 1}
                 PanelWidth={width}
                 PanelHeight={height}
                 rootSelected={this.rootSelected}
-                dropAction={StrCast(this.Document.childDragAction) as dropActionType}
+                dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
                 onClick={this.onChildClickHandler}
                 onDoubleClick={this.onChildDoubleClickHandler}
                 ScreenToLocalTransform={dxf}
@@ -270,7 +270,6 @@ export class CollectionMultirowView extends CollectionSubView() {
                 hideResizeHandles={layout.layout_fitWidth || this._props.childHideResizeHandles ? true : false}
                 hideDecorationTitle={this._props.childHideDecorationTitle}
                 fitContentsToBox={this._props.fitContentsToBox}
-                dragAction={this._props.childDragAction}
                 focus={this._props.focus}
                 childFilters={this.childDocFilters}
                 childFiltersByRanges={this.childDocRangeFilters}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 492aed0ea..09352d7a4 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -16,7 +16,7 @@ import { undoable, undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
-import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../../nodes/DocumentView';
+import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
 import { KeyValueBox } from '../../nodes/KeyValueBox';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DefaultStyleProvider, StyleProp } from '../../StyleProvider';
@@ -907,7 +907,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                                 childFiltersByRanges={this.childDocRangeFilters}
                                 searchFilterDocs={this.searchFilterDocs}
                                 styleProvider={DefaultStyleProvider}
-                                docViewPath={returnEmptyDoclist}
+                                containerViewPath={returnEmptyDoclist}
                                 moveDocument={this._props.moveDocument}
                                 addDocument={this.addRow}
                                 removeDocument={this._props.removeDocument}
@@ -962,7 +962,7 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
 
     tableWidthFunc = () => this._props.schema.tableWidth;
     screenToLocalXf = () => this._props.schema.ScreenToLocalBoxXf().translate(0, -this._props.rowHeight() - this._props.index * this._props.rowHeight());
-    noOpacityStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
+    noOpacityStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => {
         if (property === StyleProp.Opacity) return 1;
         return DefaultStyleProvider(doc, props, property);
     };
@@ -971,6 +971,7 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
             <DocumentView
                 key={this._props.doc[Id]}
                 {...this._props.schema._props}
+                containerViewPath={this._props.schema.docViewPathFunc}
                 LayoutTemplate={this._props.schema._props.childLayoutTemplate}
                 LayoutTemplateString={SchemaRowBox.LayoutString(this._props.schema._props.fieldKey, this._props.index)}
                 Document={this._props.doc}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 5a3be826b..8e1fba699 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -12,7 +12,7 @@ import { DragManager } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoable } from '../../../util/UndoManager';
-import { ViewBoxBaseComponent } from '../../DocComponent';
+import { ViewBoxBaseComponent, ViewBoxBaseProps } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
 import { OpenWhere } from '../../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../../nodes/FieldView';
@@ -24,7 +24,7 @@ interface SchemaRowBoxProps {
     rowIndex: number;
 }
 @observer
-export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRowBoxProps>() {
+export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRowBoxProps & ViewBoxBaseProps>() {
     public static LayoutString(fieldKey: string, rowIndex: number) {
         return FieldView.LayoutString(SchemaRowBox, fieldKey).replace('fieldKey', `rowIndex={${rowIndex}} fieldKey`);
     }
@@ -38,11 +38,11 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo
     bounds = () => this._ref?.getBoundingClientRect();
 
     @computed get schemaView() {
-        return this._props.DocumentView?.()._props.docViewPath().lastElement()?.ComponentView as CollectionSchemaView;
+        return this.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionSchemaView;
     }
 
     @computed get schemaDoc() {
-        return this._props.DocumentView?.()._props.docViewPath().lastElement()?.Document;
+        return this.DocumentView?.().containerViewPath?.().lastElement()?.Document;
     }
 
     @computed get rowIndex() {
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 85269028b..422c4155d 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -77,7 +77,6 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
             childFiltersByRanges: returnEmptyFilter,
             searchFilterDocs: returnEmptyDoclist,
             styleProvider: DefaultStyleProvider,
-            docViewPath: returnEmptyDoclist,
             isSelected: returnFalse,
             setHeight: returnFalse,
             select: emptyFunction,
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index e57ef4871..3084a7972 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -43,14 +43,14 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
         if (checkResult) {
             const selView = selectedViews.lastElement();
             const fieldKey = selView.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
-            const layoutFrameNumber = Cast(selView._props.docViewPath().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+            const layoutFrameNumber = Cast(selView.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
             const contentFrameNumber = Cast(selView.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
             return CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] ?? 'transparent';
         }
         selectedViews.some(dv => dv.ComponentView instanceof InkingStroke) && SetActiveFillColor(color ?? 'transparent');
         selectedViews.forEach(dv => {
             const fieldKey = dv.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
-            const layoutFrameNumber = Cast(dv._props.docViewPath().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+            const layoutFrameNumber = Cast(dv.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
             const contentFrameNumber = Cast(dv.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
             if (contentFrameNumber !== undefined) {
                 const obj: { [key: string]: Opt<string> } = {};
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 85e97f95f..dc4aee1ca 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -136,6 +136,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
 
     deleteLink = (e: React.PointerEvent): void => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this._props.linkDoc))));
     @observable _hover = false;
+    docView = () => this.props.docView;
     render() {
         const destinationIcon = Doc.toIcon(this._props.destinationDoc) as any as IconProp;
 
@@ -179,7 +180,8 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
                                 this._props.linkDoc &&
                                 this._props.clearLinkEditor &&
                                 LinkInfo.SetLinkInfo({
-                                    docProps: this._props.docView._props,
+                                    DocumentView: this.docView,
+                                    styleProvider: this._props.docView._props.styleProvider,
                                     linkSrc: this._props.sourceDoc,
                                     linkDoc: this._props.linkDoc,
                                     showHeader: false,
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index 7e344dd7a..31841c596 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -69,7 +69,6 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
                         isSelected={returnTrue}
                         isContentActive={returnTrue}
                         select={returnTrue}
-                        setHeight={returnFalse}
                         addDocument={undefined}
                         addDocTab={returnTrue}
                         pinToPres={emptyFunction}
@@ -81,7 +80,6 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
                         PanelHeight={this.getPHeight}
                         renderDepth={0}
                         focus={emptyFunction}
-                        docViewPath={returnEmptyDoclist}
                         whenChildContentsActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
                         childFilters={returnEmptyFilter}
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
index 3d159c3e3..6760a0b1e 100644
--- a/src/client/views/newlightbox/NewLightboxView.tsx
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -302,7 +302,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
                         styleProvider={DefaultStyleProvider}
                         ScreenToLocalTransform={this.newLightboxScreenToLocal}
                         renderDepth={0}
-                        docViewPath={returnEmptyDoclist}
+                        containerViewPath={returnEmptyDoclist}
                         childFilters={this.docFilters}
                         childFiltersByRanges={returnEmptyFilter}
                         searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 908cd5dc0..1729af191 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -211,7 +211,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     // removes from currently playing display
     @action
     removeCurrentlyPlaying = () => {
-        const docView = this._props.DocumentView?.();
+        const docView = this.DocumentView?.();
         if (CollectionStackedTimeline.CurrentlyPlaying && docView) {
             const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView);
             index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
@@ -221,7 +221,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     // adds doc to currently playing display
     @action
     addCurrentlyPlaying = () => {
-        const docView = this._props.DocumentView?.();
+        const docView = this.DocumentView?.();
         if (!CollectionStackedTimeline.CurrentlyPlaying) {
             CollectionStackedTimeline.CurrentlyPlaying = [];
         }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index ad5aabc21..83cabf355 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -15,7 +15,7 @@ import { DocComponent } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import './CollectionFreeFormDocumentView.scss';
-import { DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere } from './DocumentView';
+import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
 import { FieldViewProps } from './FieldView';
 
 export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentViewProps {
@@ -149,7 +149,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return this._props.CollectionFreeFormView;
     }
 
-    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
+    styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
         if (doc === this.layoutDoc) {
             switch (property) {
                 case StyleProp.Opacity:         return this._props.w_Opacity(); // only change the opacity for this specific document, not its children
@@ -229,7 +229,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     @action public float = () => {
         const topDoc = this.Document;
-        const containerDocView = this._props.docViewPath().lastElement();
+        const containerDocView = this._props.containerViewPath?.().lastElement();
         const screenXf = containerDocView?.screenToContentsTransform();
         if (screenXf) {
             SelectionManager.DeselectAll();
@@ -252,7 +252,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     };
 
     nudge = (x: number, y: number) => {
-        const [locX, locY] = this.ScreenToLocalBoxXf().transformDirection(x, y);
+        const [locX, locY] = this._props.ScreenToLocalTransform().transformDirection(x, y);
         this._props.Document.x = this._props.w_X() + locX;
         this._props.Document.y = this._props.w_Y() + locY;
     };
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 5e7e568b0..b171db17c 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -11,7 +11,7 @@ import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import './ComparisonBox.scss';
-import { DocumentView, DocumentViewInternalProps, DocumentViewProps } from './DocumentView';
+import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { PinProps, PresBox } from './trails';
 
@@ -148,7 +148,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
             e => this.clearDoc(which)
         );
     };
-    docStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+    docStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => {
         if (property === StyleProp.PointerEvents) return 'none';
         return this._props.styleProvider?.(doc, props, property);
     };
@@ -178,6 +178,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                         {...this._props}
                         Document={targetDoc}
                         TemplateDataDocument={undefined}
+                        containerViewPath={this.docViewPathFunc}
                         moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
                         removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2}
                         NativeWidth={returnZero}
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 5a55ca764..5ceec98cb 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Colors, Toggle, ToggleType, Type } from 'browndash-components';
-import { ObservableMap, action, computed, observable, runInAction } from 'mobx';
+import { ObservableMap, action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils';
@@ -14,10 +14,9 @@ import { TraceMobx } from '../../../../fields/util';
 import { Docs } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { UndoManager, undoable } from '../../../util/UndoManager';
-import { ViewBoxAnnotatableComponent } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
 import { MarqueeAnnotator } from '../../MarqueeAnnotator';
 import { SidebarAnnos } from '../../SidebarAnnos';
-import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
 import { DocFocusOptions, DocumentView } from '../DocumentView';
@@ -37,22 +36,28 @@ export enum DataVizView {
 }
 
 @observer
-export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
+export class DataVizBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
-    private _ffref = React.createRef<CollectionFreeFormView>();
     private _marqueeref = React.createRef<MarqueeAnnotator>();
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
     crop: ((region: Doc | undefined, addCrop?: boolean) => Doc | undefined) | undefined;
-    @observable schemaDataVizChildren: any = undefined;
+    @observable _schemaDataVizChildren: any = undefined;
     @observable _marqueeing: number[] | undefined = undefined;
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
+
+    constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
+        super(props);
+        makeObservable(this);
+        this._props.setContentView?.(this);
+    }
+
     @computed get annotationLayer() {
         TraceMobx();
         return <div className="dataVizBox-annotationLayer" style={{ height: this._props.PanelHeight(), width: this._props.PanelWidth() }} ref={this._annotationLayer} />;
     }
     marqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && NumCast(this.Document._freeform_scale, 1) <= NumCast(this.Document.freeform_scaleMin, 1) && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && NumCast(this.Document._freeform_scale, 1) <= NumCast(this.Document.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             setupMoveUpEvents(
                 this,
                 e,
@@ -211,12 +216,12 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         .ScreenToLocalTransform()
                         .scale(this._props.NativeDimScaling?.() || 1)
                         .transformDirection(delta[0], delta[1]);
-                    const fullWidth = this._props.width;
-                    const mapWidth = fullWidth! - this.sidebarWidth();
+                    const fullWidth = NumCast(this.layoutDoc._width);
+                    const mapWidth = fullWidth - this.sidebarWidth();
                     if (this.sidebarWidth() + localDelta[0] > 0) {
                         this.layoutDoc._layout_showSidebar = true;
-                        this.layoutDoc._width = fullWidth! + localDelta[0];
-                        this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth! + localDelta[0])).toString() + '%';
+                        this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%';
+                        this.layoutDoc._width = fullWidth + localDelta[0];
                     } else {
                         this.layoutDoc._layout_showSidebar = false;
                         this.layoutDoc._width = mapWidth;
@@ -269,12 +274,12 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             axes: this.axes,
             //width: this.SidebarShown? this._props.PanelWidth()*.9/1.2: this._props.PanelWidth() * 0.9,
             height: (this._props.PanelHeight() / scale - 32) /* height of 'change view' button */ * 0.9,
-            width: (this._props.PanelWidth() / scale) * 0.9,
+            width: ((this._props.PanelWidth() - this.sidebarWidth()) / scale) * 0.9,
             margin: { top: 10, right: 25, bottom: 75, left: 45 },
         };
         if (!this.records.length) return 'no data/visualization';
         switch (this.dataVizView) {
-            case DataVizView.TABLE:     return <TableBox  {...sharedProps} docView={this._props.DocumentView} selectAxes={this.selectAxes} />;
+            case DataVizView.TABLE:     return <TableBox  {...sharedProps} docView={this.DocumentView} selectAxes={this.selectAxes} />;
             case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} vizBox={this} />;
             case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
             case DataVizView.PIECHART:  return <PieChart  {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} 
@@ -285,7 +290,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         if ((this.Document._freeform_scale || 1) !== 1) return;
-        if (!e.altKey && e.button === 0 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             this._props.select(false);
             MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
             this._marqueeing = [e.clientX, e.clientY];
@@ -345,7 +350,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     render() {
         if (this.layoutDoc && this.layoutDoc.dataViz_asSchema) {
-            this.schemaDataVizChildren = DocListCast(DocCast(this.layoutDoc.dataViz_asSchema)[Doc.LayoutFieldKey(DocCast(this.layoutDoc.dataViz_asSchema))]).length;
+            this._schemaDataVizChildren = DocListCast(DocCast(this.layoutDoc.dataViz_asSchema)[Doc.LayoutFieldKey(DocCast(this.layoutDoc.dataViz_asSchema))]).length;
             this.updateSchemaViz();
         }
 
@@ -366,7 +371,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 }}
                 onWheel={e => e.stopPropagation()}
                 ref={this._mainCont}>
-                <div className={'datatype-button'}>
+                <div className="datatype-button">
                     <Toggle text={'  TABLE  '} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.TABLE)} toggleStatus={this.layoutDoc._dataViz === DataVizView.TABLE} />
                     <Toggle text={'LINECHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.LINECHART)} toggleStatus={this.layoutDoc._dataViz === DataVizView.LINECHART} />
                     <Toggle text={'HISTOGRAM'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.HISTOGRAM)} toggleStatus={this.layoutDoc._dataViz === DataVizView.HISTOGRAM} />
@@ -414,7 +419,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 </div>
                 {this.sidebarHandle}
                 {this.annotationLayer}
-                {!this._mainCont.current || !this._annotationLayer.current ? null : (
+                {!this._mainCont.current || !this.DocumentView || !this._annotationLayer.current ? null : (
                     <MarqueeAnnotator
                         ref={this._marqueeref}
                         Document={this.Document}
@@ -422,7 +427,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         scrollTop={0}
                         annotationLayerScrollTop={NumCast(this.Document._layout_scrollTop)}
                         scaling={returnOne}
-                        docView={this._props.DocumentView!}
+                        docView={this.DocumentView}
                         addDocument={this.sidebarAddDocument}
                         finishMarquee={this.finishMarquee}
                         savedAnnotations={this.savedAnnotations}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 4cc7fe4c8..0d76959af 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -116,11 +116,12 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
 
 @observer
 export class DocumentContentsView extends ObservableReactComponent<
-    DocumentViewProps &
-        FieldViewProps & {
-            setHeight?: (height: number) => void;
-            layout_fieldKey: string;
-        }
+    FieldViewProps & {
+        layout_fieldKey: string;
+        LayoutTemplateString?: string;
+        onClick?: () => ScriptField;
+        LayoutTemplate?: () => Opt<Doc>;
+    }
 > {
     constructor(props: any) {
         super(props);
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index dfd610581..25f56e69a 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -25,7 +25,7 @@ export class DocumentIcon extends ObservableReactComponent<DocumentIconProps> {
     }
 
     static get DocViews() {
-        return LightboxView.LightboxDoc ? DocumentManager.Instance.DocumentViews.filter(v => LightboxView.IsLightboxDocView(v._props.docViewPath())) : DocumentManager.Instance.DocumentViews;
+        return LightboxView.LightboxDoc ? DocumentManager.Instance.DocumentViews.filter(v => LightboxView.IsLightboxDocView(v.docViewPath)) : DocumentManager.Instance.DocumentViews;
     }
     render() {
         const view = this._props.view;
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index c549a146a..f5949a451 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -152,8 +152,8 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
                 DocumentLinksButton.AnnotationUri = undefined;
                 //!this._props.StartLink
             } else if (startLink !== endLink) {
-                endLink = endLinkView?.docView?._componentView?.getAnchor?.(true, pinProps) || endLink;
-                startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.(true) || startLink;
+                endLink = endLinkView?.ComponentView?.getAnchor?.(true, pinProps) || endLink;
+                startLink = DocumentLinksButton.StartLinkView?.ComponentView?.getAnchor?.(true) || startLink;
                 const linkDoc = DocUtils.MakeLink(startLink, endLink, { link_relationship: DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined });
 
                 LinkManager.currentLink = linkDoc;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6cc61ec62..5345ef0d8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -109,7 +109,7 @@ export interface DocFocusOptions {
     easeFunc?: 'linear' | 'ease'; // transition method for scrolling
 }
 export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => Opt<number>;
-export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => any;
+export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => any;
 export interface DocComponentView {
     fieldKey?: string;
     annotationKey?: string;
@@ -143,20 +143,31 @@ export interface DocComponentView {
     snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number };
     search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
 }
-// These props are passed to both FieldViews and DocumentViews
+/**
+ * props that DocumentViews, DocumentInternalViews and FieldViews all can use
+ * */
 export interface DocumentViewSharedProps {
-    renderDepth: number;
     Document: Doc;
     TemplateDataDocument?: Doc;
+    renderDepth: number;
     scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
-    DocumentView?: () => DocumentView;
+    treeViewDoc?: Doc;
+    xPadding?: number;
+    yPadding?: number;
+    dontRegisterView?: boolean;
+    dropAction?: dropActionType;
+    dragAction?: dropActionType;
+    forceAutoHeight?: boolean;
+    ignoreAutoHeight?: boolean;
+    disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
     CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
     fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
     isGroupActive?: () => string | undefined; // is this document part of a group that is active
     setContentView?: (view: DocComponentView) => any;
     PanelWidth: () => number;
     PanelHeight: () => number;
-    docViewPath: () => DocumentView[];
+    isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
+    isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
     childFilters: () => string[];
     childFiltersByRanges: () => string[];
     styleProvider: Opt<StyleProviderFunc>;
@@ -178,29 +189,12 @@ export interface DocumentViewSharedProps {
     waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
     defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
     pointerEvents?: () => Opt<string>;
-    treeViewDoc?: Doc;
-    xPadding?: number;
-    yPadding?: number;
-    dontRegisterView?: boolean;
-    childHideDecorationTitle?: boolean;
-    childHideResizeHandles?: boolean;
-    childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
-    dropAction?: dropActionType;
-    dragAction?: dropActionType;
-    dragWhenActive?: boolean;
-    dontHideOnDrag?: boolean;
-    hideLinkButton?: boolean;
-    hideCaptions?: boolean;
-    ignoreAutoHeight?: boolean;
-    forceAutoHeight?: boolean;
-    suppressSetHeight?: boolean;
-    disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
-    onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
 }
 
-// these props are specific to DocuentViews
+/**
+ * props that are used by DocumentViews and DocumentInternalViews but not by the contents of those views (FieldViews).
+ */
 export interface DocumentViewProps extends DocumentViewSharedProps {
-    // properties specific to DocumentViews but not to FieldView
     hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected
     hideResizeHandles?: boolean; // whether to suppress resized handles on doc decorations when this document is selected
     hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
@@ -209,15 +203,22 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     hideOpenButton?: boolean;
     hideDeleteButton?: boolean;
     hideLinkAnchors?: boolean;
+    hideLinkButton?: boolean;
+    hideCaptions?: boolean;
     contentPointerEvents?: 'none' | 'all' | undefined; // pointer events allowed for content of a document view.  eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
     LayoutTemplateString?: string;
     dontCenter?: 'x' | 'y' | 'xy';
-    isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
-    isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
+    childHideDecorationTitle?: boolean;
+    childHideResizeHandles?: boolean;
+    childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
+    dragWhenActive?: boolean;
+    dontHideOnDrag?: boolean;
+    suppressSetHeight?: boolean;
+    onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
     NativeWidth?: () => number;
     NativeHeight?: () => number;
-    NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to FieldViewProps
     LayoutTemplate?: () => Opt<Doc>;
+    containerViewPath?: () => DocumentView[];
     contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[];
     onClick?: () => ScriptField;
     onDoubleClick?: () => ScriptField;
@@ -229,12 +230,18 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     dragEnding?: () => void;
 }
 
-// these props are only available in DocumentViewIntenral
-export interface DocumentViewInternalProps extends DocumentViewProps {
-    isSelected: () => boolean;
+/**
+ * props used by DocInternalViews and FieldViews but not DocumentViews
+ * these props correspond to things that the DocumentView creates and thus doesn't need to receive as a prop
+ */
+export interface DocumentViewInternalSharedProps {
+    DocumentView?: () => DocumentView;
     select: (ctrlPressed: boolean, shiftPress?: boolean) => void;
-    DocumentView: () => DocumentView;
-    viewPath: () => DocumentView[];
+    isSelected: () => boolean;
+    NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
+}
+export interface DocumentViewInternalProps extends DocumentViewProps, DocumentViewInternalSharedProps {
+    docViewPath: () => DocumentView[];
 }
 
 @observer
@@ -265,17 +272,24 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         return this._animateScaleTime ?? 100;
     }
     public get displayName() {
-        return 'DocumentViewInternal(' + this._props.Document.title + ')';
+        return 'DocumentViewInternal(' + this.Document.title + ')';
     } // this makes mobx trace() statements more descriptive
 
+    public get DocumentView() {
+        return this._props.DocumentView!;
+    }
+
     public get ContentDiv() {
         return this._mainCont.current;
     }
     public get LayoutFieldKey() {
         return Doc.LayoutFieldKey(this.layoutDoc);
     }
+    @computed get styleProps(): FieldViewProps {
+        return { ...this._props, fieldKey: '' };
+    }
     @computed get layout_showTitle() {
-        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.ShowTitle) as Opt<string>;
+        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.ShowTitle) as Opt<string>;
     }
     @computed get NativeDimScaling() {
         return this._props.NativeDimScaling?.() || 1;
@@ -284,32 +298,32 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png');
     }
     @computed get opacity() {
-        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Opacity);
+        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.Opacity);
     }
     @computed get boxShadow() {
-        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BoxShadow);
+        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.BoxShadow);
     }
     @computed get borderRounding() {
-        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding);
+        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.BorderRounding);
     }
     @computed get widgetDecorations() {
         TraceMobx();
-        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Decorations);
+        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.Decorations);
     }
     @computed get backgroundBoxColor() {
-        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor + ':box');
+        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.BackgroundColor + ':box');
     }
     @computed get docContents() {
-        return this._props.styleProvider?.(this.Document, this._props, StyleProp.DocContents);
+        return this._props.styleProvider?.(this.Document, this.styleProps, StyleProp.DocContents);
     }
     @computed get headerMargin() {
-        return this._props?.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin) || 0;
+        return this._props?.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.HeaderMargin) || 0;
     }
     @computed get layout_showCaption() {
-        return this._props?.styleProvider?.(this.layoutDoc, this._props, StyleProp.ShowCaption) || 0;
+        return this._props?.hideCaptions ? undefined : this._props?.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.ShowCaption) || 0;
     }
     @computed get titleHeight() {
-        return this._props?.styleProvider?.(this.layoutDoc, this._props, StyleProp.TitleHeight) || 0;
+        return this._props?.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.TitleHeight) || 0;
     }
     @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined = undefined;
     @computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined {
@@ -363,7 +377,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             { fireImmediately: true }
         );
         this._disposers.pointerevents = reaction(
-            () => this._props.styleProvider?.(this.Document, this._props, StyleProp.PointerEvents),
+            () => this._props.styleProvider?.(this.Document, this.styleProps, StyleProp.PointerEvents),
             pointerevents => (this._pointerEvents = pointerevents),
             { fireImmediately: true }
         );
@@ -378,32 +392,32 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     setupHandlers() {
         this.cleanupHandlers(false);
         if (this._mainCont.current) {
-            this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this._props.Document, this.preDropFunc);
+            this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.Document, this.preDropFunc);
         }
     }
 
     cleanupHandlers(unbrush: boolean) {
         this._dropDisposer?.();
-        unbrush && Doc.UnBrushDoc(this._props.Document);
+        unbrush && Doc.UnBrushDoc(this.Document);
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
 
     startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
         if (this._mainCont.current) {
-            const views = SelectionManager.Views.filter(dv => dv.docView?._mainCont.current);
-            const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [this._props.DocumentView()];
+            const views = SelectionManager.Views.filter(dv => dv.ContentDiv);
+            const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [this.DocumentView()];
             const dragData = new DragManager.DocumentDragData(selected.map(dv => dv.Document));
-            const screenXf = this.props.DocumentView().screenToViewTransform();
+            const screenXf = this.DocumentView().screenToViewTransform();
             const [left, top] = screenXf.inverse().transformPoint(0, 0);
             dragData.offset = screenXf.transformDirection(x - left, y - top);
             dragData.dropAction = dropAction;
             dragData.treeViewDoc = this._props.treeViewDoc;
             dragData.removeDocument = this._props.removeDocument;
             dragData.moveDocument = this._props.moveDocument;
-            dragData.draggedViews = [this._props.DocumentView()];
+            dragData.draggedViews = [this.DocumentView()];
             dragData.canEmbed = this.Document.dragAction ?? this._props.dragAction ? true : false;
             DragManager.StartDocumentDrag(
-                selected.map(dv => dv.docView!._mainCont.current!),
+                selected.map(dv => dv.ContentDiv!),
                 dragData,
                 x,
                 y,
@@ -443,7 +457,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     const func = () => this.onDoubleClickHandler.script.run( {
                             this: this.Document,
                             scriptContext: this._props.scriptContext,
-                            documentView: this._props.DocumentView(),
+                            documentView: this.DocumentView(),
                             clientX, clientY,  altKey,  shiftKey,  ctrlKey,
                             value: undefined,
                         }, console.log  );
@@ -451,7 +465,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 } else if (!Doc.IsSystem(this.Document) && (defaultDblclick === undefined || defaultDblclick === 'default')) {
                     UndoManager.RunInBatch(() => LightboxView.Instance.AddDocTab(this.Document, OpenWhere.lightbox), 'double tap');
                     SelectionManager.DeselectAll();
-                    Doc.UnBrushDoc(this._props.Document);
+                    Doc.UnBrushDoc(this.Document);
                 } else {
                     this._singleClickFunc?.();
                 }
@@ -474,7 +488,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                                 this: this.Document,
                                 _readOnly_: false,
                                 scriptContext: this._props.scriptContext,
-                                documentView: this._props.DocumentView(),
+                                documentView: this.DocumentView(),
                                 clientX,
                                 clientY,
                                 shiftKey,
@@ -498,7 +512,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 const sendToBack = e.altKey;
                 this._singleClickFunc =
                     // prettier-ignore
-                    clickFunc ?? (() => (sendToBack ? this._props.DocumentView()._props.bringToFront(this.Document, true) : 
+                    clickFunc ?? (() => (sendToBack ? this.DocumentView()._props.bringToFront(this.Document, true) : 
                                                       this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ?? 
                                         this._props.select(e.ctrlKey||e.shiftKey, e.metaKey)));
                 const waitFordblclick = this._props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
@@ -522,7 +536,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 this._props.select(false);
             }
         }, 1000);
-        if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this._props.DocumentView();
+        if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this.DocumentView();
 
         this._downX = e.clientX;
         this._downY = e.clientY;
@@ -600,7 +614,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         this.Document.onClick = Doc.GetProto(this.Document).onClick = undefined;
     };
 
-    @undoBatch deleteClicked = () => this._props.removeDocument?.(this._props.Document);
+    @undoBatch deleteClicked = () => this._props.removeDocument?.(this.Document);
     @undoBatch setToggleDetail = () =>
         (this.Document.onClick = ScriptField.MakeScript(
             `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
@@ -612,7 +626,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @undoBatch
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (this._props.dontRegisterView || this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false;
-        if (this._props.Document === Doc.ActiveDashboard) {
+        if (this.Document === Doc.ActiveDashboard) {
             e.stopPropagation();
             e.preventDefault();
             alert(
@@ -634,7 +648,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]);
                     if (de.complete.linkDocument) {
                         de.complete.linkDocument.layout_isSvg = true;
-                        this._props.DocumentView().CollectionFreeFormView?.addDocument(de.complete.linkDocument);
+                        this.DocumentView().CollectionFreeFormView?.addDocument(de.complete.linkDocument);
                     }
                 }
                 e.stopPropagation();
@@ -646,16 +660,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     @undoBatch
     makeIntoPortal = () => {
-        const portalLink = this.allLinks.find(d => d.link_anchor_1 === this._props.Document && d.link_relationship === 'portal to:portal from');
+        const portalLink = this.allLinks.find(d => d.link_anchor_1 === this.Document && d.link_relationship === 'portal to:portal from');
         if (!portalLink) {
             DocUtils.MakeLink(
-                this._props.Document,
+                this.Document,
                 Docs.Create.FreeformDocument([], {
                     _width: NumCast(this.layoutDoc._width) + 10,
                     _height: Math.max(NumCast(this.layoutDoc._height), NumCast(this.layoutDoc._width) + 10),
                     _isLightbox: true,
                     _layout_fitWidth: true,
-                    title: StrCast(this._props.Document.title) + ' [Portal]',
+                    title: StrCast(this.Document.title) + ' [Portal]',
                 }),
                 { link_relationship: 'portal to:portal from' }
             );
@@ -686,7 +700,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (e && this.layoutDoc._layout_hideContextMenu && Doc.noviceMode) {
             e.preventDefault();
             e.stopPropagation();
-            //!this._props.isSelected(true) && SelectionManager.SelectView(this._props.DocumentView(), false);
+            //!this._props.isSelected(true) && SelectionManager.SelectView(this.DocumentView(), false);
         }
         // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
         if (e) {
@@ -708,7 +722,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
         if (e && !(e.nativeEvent as any).dash) {
             const onDisplay = () => {
-                if (this.Document.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this._props.isSelected() && SelectionManager.SelectView(this._props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
+                if (this.Document.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this._props.isSelected() && SelectionManager.SelectView(this.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
                 setTimeout(() => simulateMouseClick(document.elementFromPoint(e.clientX, e.clientY), e.clientX, e.clientY, e.screenX, e.screenY));
             };
             if (navigator.userAgent.includes('Macintosh')) {
@@ -719,14 +733,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             return;
         }
 
-        const customScripts = Cast(this._props.Document.contextMenuScripts, listSpec(ScriptField), []);
+        const customScripts = Cast(this.Document.contextMenuScripts, listSpec(ScriptField), []);
         StrListCast(this.Document.contextMenuLabels).forEach((label, i) =>
             cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.Document, scriptContext: this._props.scriptContext }), icon: 'sticky-note' })
         );
         this._props.contextMenuItems?.().forEach(item => item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.Document, scriptContext: this._props.scriptContext }), icon: item.icon as IconProp }));
 
-        if (!this._props.Document.isFolder) {
-            const templateDoc = Cast(this._props.Document[StrCast(this._props.Document.layout_fieldKey)], Doc, null);
+        if (!this.Document.isFolder) {
+            const templateDoc = Cast(this.Document[StrCast(this.Document.layout_fieldKey)], Doc, null);
             const appearance = cm.findByDescription('Appearance...');
             const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : [];
 
@@ -764,7 +778,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
 
                         onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
-                        !Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this._props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
+                        !Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
                         !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
                     } else if (LinkManager.Links(this.Document).length) {
                         onClicks.push({ description: 'Restore On Click default', event: () => this.noOnClick(), icon: 'link' });
@@ -786,15 +800,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             const moreItems = more && 'subitems' in more ? more.subitems : [];
             if (!Doc.IsSystem(this.Document)) {
                 if (!Doc.noviceMode) {
-                    moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this._props.Document, this._props.TemplateDataDocument), icon: 'concierge-bell' });
+                    moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this.Document, this._props.TemplateDataDocument), icon: 'concierge-bell' });
                     moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => (this.Document._chromeHidden = !this.Document._chromeHidden), icon: 'project-diagram' });
 
-                    if (Cast(Doc.GetProto(this._props.Document).data, listSpec(Doc))) {
-                        moreItems.push({ description: 'Export to Google Photos Album', event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this._props.Document }).then(console.log), icon: 'caret-square-right' });
-                        moreItems.push({ description: 'Tag Child Images via Google Photos', event: () => GooglePhotos.Query.TagChildImages(this._props.Document), icon: 'caret-square-right' });
-                        moreItems.push({ description: 'Write Back Link to Album', event: () => GooglePhotos.Transactions.AddTextEnrichment(this._props.Document), icon: 'caret-square-right' });
+                    if (Cast(Doc.GetProto(this.Document).data, listSpec(Doc))) {
+                        moreItems.push({ description: 'Export to Google Photos Album', event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.Document }).then(console.log), icon: 'caret-square-right' });
+                        moreItems.push({ description: 'Tag Child Images via Google Photos', event: () => GooglePhotos.Query.TagChildImages(this.Document), icon: 'caret-square-right' });
+                        moreItems.push({ description: 'Write Back Link to Album', event: () => GooglePhotos.Transactions.AddTextEnrichment(this.Document), icon: 'caret-square-right' });
                     }
-                    moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this._props.Document)), icon: 'fingerprint' });
+                    moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this.Document)), icon: 'fingerprint' });
                 }
             }
 
@@ -802,14 +816,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
         const constantItems: ContextMenuProps[] = [];
         if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking) {
-            constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this._props.Document) });
-            (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this._props.DocumentView()), icon: 'users' });
-            if (this._props.removeDocument && Doc.ActiveDashboard !== this._props.Document) {
+            constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this.Document) });
+            (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.DocumentView()), icon: 'users' });
+            if (this._props.removeDocument && Doc.ActiveDashboard !== this.Document) {
                 // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
                 constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
             }
         }
-        constantItems.push({ description: 'Show Metadata', event: () => this._props.addDocTab(this._props.Document, OpenWhere.addRightKeyvalue), icon: 'table-columns' });
+        constantItems.push({ description: 'Show Metadata', event: () => this._props.addDocTab(this.Document, OpenWhere.addRightKeyvalue), icon: 'table-columns' });
         cm.addItem({ description: 'General...', noexpand: false, subitems: constantItems, icon: 'question' });
 
         const help = cm.findByDescription('Help...');
@@ -820,7 +834,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
         let documentationDescription: string | undefined = undefined;
         let documentationLink: string | undefined = undefined;
-        switch (this._props.Document.type) {
+        switch (this.Document.type) {
             case DocumentType.COL:
                 documentationDescription = 'See collection documentation';
                 documentationLink = 'https://brown-dash.github.io/Dash-Documentation/views/';
@@ -874,7 +888,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
     rootSelected = () => this._rootSelected;
     panelHeight = () => this._props.PanelHeight() - this.headerMargin;
-    screenToLocalContent = () => this.ScreenToLocalBoxXf().translate(0, -this.headerMargin);
+    screenToLocalContent = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
     onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
     setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = height);
     setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
@@ -912,7 +926,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     {...this._props}
                     fieldKey=""
                     pointerEvents={this.contentPointerEvents}
-                    docViewPath={this._props.viewPath}
                     setContentView={this.setContentView}
                     childFilters={this.childFilters}
                     PanelHeight={this.panelHeight}
@@ -931,7 +944,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     anchorPanelWidth = () => this._props.PanelWidth() || 1;
     anchorPanelHeight = () => this._props.PanelHeight() || 1;
-    anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+    anchorStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => {
         // prettier-ignore
         switch (property.split(':')[0]) {
             case StyleProp.ShowTitle:     return '';
@@ -976,7 +989,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     {...this._props}
                     isContentActive={returnFalse}
                     Document={link}
-                    docViewPath={this._props.viewPath}
+                    containerViewPath={this._props.docViewPath}
                     PanelWidth={this.anchorPanelWidth}
                     PanelHeight={this.anchorPanelHeight}
                     dontRegisterView={false}
@@ -1066,7 +1079,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     };
 
-    captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
+    captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
     @observable _changingTitleField = false;
     @observable _dropDownInnerWidth = 0;
     fieldsDropdown = (inputOptions: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => {
@@ -1266,10 +1279,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     }
     @computed get highlighting() {
-        return this._props.styleProvider?.(this.Document, this._props, StyleProp.Highlighting);
+        return this._props.styleProvider?.(this.Document, { ...this._props, fieldKey: '' }, StyleProp.Highlighting);
     }
     @computed get borderPath() {
-        return this._props.styleProvider?.(this.Document, this._props, StyleProp.BorderPath);
+        return this._props.styleProvider?.(this.Document, { ...this._props, fieldKey: '' }, StyleProp.BorderPath);
     }
     render() {
         TraceMobx();
@@ -1332,14 +1345,13 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
     @computed public static get exploreMode() {
         return () => (SnappingManager.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
     }
-    @observable public docView: DocumentViewInternal | undefined | null = undefined;
-    @observable public textHtmlOverlay: Opt<string> = undefined;
-    @observable public textHtmlOverlayTime: Opt<number> = undefined;
+    @observable private _docViewInternal: DocumentViewInternal | undefined | null = undefined;
+    @observable private _htmlOverlayText: Opt<string> = undefined;
     @observable private _isHovering = false;
+    private _htmlOverlayEffect: Opt<Doc>;
 
-    public htmlOverlayEffect: Opt<Doc>;
     public get displayName() {
-        return 'DocumentView(' + this._props.Document?.title + ')';
+        return 'DocumentView(' + this.Document?.title + ')';
     } // this makes mobx trace() statements more descriptive
     public ContentRef = React.createRef<HTMLDivElement>();
     public ViewTimer: NodeJS.Timeout | undefined; // timer for res
@@ -1349,10 +1361,26 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
         this.ViewTimer && clearTimeout(this.ViewTimer);
         this.layoutDoc._viewTransition = undefined;
     };
-    public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource);
-
-    public showContextMenu = (pageX: number, pageY: number) => this.docView?.onContextMenu(undefined, pageX, pageY);
-
+    playAnnotation = () => this._docViewInternal?.playAnnotation();
+    noOnClick = () => this._docViewInternal?.noOnClick();
+    makeIntoPortal = () => this._docViewInternal?.makeIntoPortal();
+    setToggleDetail = () => this._docViewInternal?.setToggleDetail();
+    onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => this._docViewInternal?.onContextMenu?.(e, pageX, pageY);
+    cleanupPointerEvents = () => this._docViewInternal?.cleanupPointerEvents();
+    public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this._docViewInternal?.startDragging(x, y, dropAction, hideSource);
+
+    public showContextMenu = (pageX: number, pageY: number) => this._docViewInternal?.onContextMenu(undefined, pageX, pageY);
+
+    public setTextHtmlOverlay = action((text: string | undefined, effect?: Doc) => {
+        this._htmlOverlayText = text;
+        this._htmlOverlayEffect = effect;
+    });
+    public setAnimateScaling = action((scale: number, time?: number) => {
+        if (this._docViewInternal) {
+            this._docViewInternal._animateScalingTo = scale;
+            this._docViewInternal._animateScaleTime = time;
+        }
+    });
     public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => {
         this.AnimEffectTimer && clearTimeout(this.AnimEffectTimer);
         this.Document[Animation] = presEffect;
@@ -1391,10 +1419,11 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
         DocServer.GetRefField(docId).then(docx =>
             LightboxView.Instance.SetLightboxDoc(
                 (docx as Doc) ?? // reuse existing pivot view of documents, or else create a new collection
-                Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, updateContentsScript: ScriptField.MakeScript('updateLinkCollection(this, this.target)') }, docId)
+                Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, onViewMounted: ScriptField.MakeScript('updateLinkCollection(this, this.target)') }, docId)
             )
         );
     }
+    toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => this._docViewInternal?.toggleFollowLink(zoom, setTargetToggle);
 
     get Document() {
         return this._props.Document;
@@ -1403,19 +1432,19 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
         return this._props.renderDepth === 0;
     }
     get dataDoc() {
-        return this.docView?.dataDoc ?? this.Document;
+        return this._docViewInternal?.dataDoc ?? this.Document;
     }
     get ContentDiv() {
-        return this.docView?.ContentDiv;
+        return this._docViewInternal?.ContentDiv;
     }
     get ComponentView() {
-        return this.docView?._componentView;
+        return this._docViewInternal?._componentView;
     }
     get allLinks() {
-        return (this.docView?.allLinks || []).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document);
+        return (this._docViewInternal?.allLinks || []).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document);
     }
     get LayoutFieldKey() {
-        return this.docView?.LayoutFieldKey || 'layout';
+        return this._docViewInternal?.LayoutFieldKey || 'layout';
     }
     @computed get layout_fitWidth() {
         return this._props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth;
@@ -1439,8 +1468,17 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
     @computed get linkCountView() {
         return <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
     }
+    /**
+     * path of DocumentViews terminating in the DocumentView that contains this DocumentView
+     */
+    @computed get containerViewPath() {
+        return this._props.containerViewPath;
+    }
+    /**
+     * path of DocumentViews terminating in this DocumentView
+     */
     @computed get docViewPath(): DocumentView[] {
-        return this._props.docViewPath ? [...this._props.docViewPath(), this] : [this];
+        return this.containerViewPath ? [...this.containerViewPath(), this] : [this];
     }
     @computed get layoutDoc() {
         return Doc.Layout(this.Document, this._props.LayoutTemplate?.());
@@ -1502,19 +1540,19 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
         return this._props.CollectionFreeFormDocumentView?.();
     }
 
-    public toggleNativeDimensions = () => this.docView && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this._props.PanelWidth(), this._props.PanelHeight());
+    public toggleNativeDimensions = () => this._docViewInternal && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this._docViewInternal.NativeDimScaling, this._props.PanelWidth(), this._props.PanelHeight());
     public getBounds = () => {
-        if (!this.docView?.ContentDiv || this._props.treeViewDoc || Doc.AreProtosEqual(this._props.Document, Doc.UserDoc())) {
+        if (!this._docViewInternal?.ContentDiv || this._props.treeViewDoc || Doc.AreProtosEqual(this.Document, Doc.UserDoc())) {
             return undefined;
         }
-        if (this.docView._componentView?.screenBounds?.()) {
-            return this.docView._componentView.screenBounds();
+        if (this._docViewInternal._componentView?.screenBounds?.()) {
+            return this._docViewInternal._componentView.screenBounds();
         }
-        const xf = this.docView.ScreenToLocalBoxXf().scale(this.nativeScaling).inverse();
+        const xf = this._docViewInternal._props.ScreenToLocalTransform().scale(this.nativeScaling).inverse();
         const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
 
-        if (this.docView._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
-            const docuBox = this.docView.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
+        if (this._docViewInternal._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
+            const docuBox = this._docViewInternal.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
             if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
         }
         return { left, top, right, bottom };
@@ -1522,11 +1560,11 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
 
     public iconify(finished?: () => void, animateTime?: number) {
         this.ComponentView?.updateIcon?.();
-        const animTime = this.docView?._animateScaleTime;
-        runInAction(() => this.docView && animateTime !== undefined && (this.docView._animateScaleTime = animateTime));
+        const animTime = this._docViewInternal?._animateScaleTime;
+        runInAction(() => this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime));
         const finalFinished = action(() => {
             finished?.();
-            this.docView && (this.docView._animateScaleTime = animTime);
+            this._docViewInternal && (this._docViewInternal._animateScaleTime = animTime);
         });
         const layout_fieldKey = Cast(this.Document.layout_fieldKey, 'string', null);
         if (layout_fieldKey !== 'layout_icon') {
@@ -1541,12 +1579,12 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
     }
     @undoBatch
     setCustomView = (custom: boolean, layout: string): void => {
-        Doc.setNativeView(this._props.Document);
-        custom && DocUtils.makeCustomViewClicked(this._props.Document, Docs.Create.StackingDocument, layout, undefined);
+        Doc.setNativeView(this.Document);
+        custom && DocUtils.makeCustomViewClicked(this.Document, Docs.Create.StackingDocument, layout, undefined);
     };
 
     switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
-        runInAction(() => this.docView && (this.docView._animateScalingTo = 0.1)); // shrink doc
+        runInAction(() => this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1)); // shrink doc
         setTimeout(
             action(() => {
                 if (useExistingLayout && custom && this.Document['layout_' + view]) {
@@ -1554,16 +1592,16 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                 } else {
                     this.setCustomView(custom, view);
                 }
-                this.docView && (this.docView._animateScalingTo = 1); // expand it
+                this._docViewInternal && (this._docViewInternal._animateScalingTo = 1); // expand it
                 setTimeout(
                     action(() => {
-                        this.docView && (this.docView._animateScalingTo = 0);
+                        this._docViewInternal && (this._docViewInternal._animateScalingTo = 0);
                         finished?.();
                     }),
-                    this.docView ? Math.max(0, this.docView.animateScaleTime - 10) : 0
+                    this._docViewInternal ? Math.max(0, this._docViewInternal.animateScaleTime - 10) : 0
                 );
             }),
-            this.docView ? Math.max(0, this.docView?.animateScaleTime - 10) : 0
+            this._docViewInternal ? Math.max(0, this._docViewInternal?.animateScaleTime - 10) : 0
         );
     };
 
@@ -1606,29 +1644,21 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
 
     componentDidMount() {
         runInAction(() => this.Document[DocViews].add(this));
-        this._disposers.updateContentsScript = reaction(() => ScriptCast(this.Document.updateContentsScript)?.script?.run({ this: this.Document }).result, emptyFunction);
-        this._disposers.height = reaction(
-            // increase max auto height if document has been resized to be greater than current max
-            () => NumCast(this.layoutDoc._height),
-            action(height => {
-                const docMax = NumCast(this.layoutDoc.layout_maxAutoHeight);
-                if (docMax && docMax < height) this.layoutDoc.layout_maxAutoHeight = height;
-            })
-        );
-        !BoolCast(this._props.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.AddView(this);
+        this._disposers.onViewMounted = reaction(() => ScriptCast(this.Document.onViewMounted)?.script?.run({ this: this.Document }).result, emptyFunction);
+        !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.AddView(this);
     }
 
     componentWillUnmount() {
-        this.Document[DocViews].delete(this);
+        runInAction(() => this.Document[DocViews].delete(this));
         Object.values(this._disposers).forEach(disposer => disposer?.());
-        !BoolCast(this._props.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
+        !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
     }
     // want the htmloverlay to be able to fade in but we also want it to be display 'none' until it is needed.
     // unfortunately, CSS can't transition animate any properties for something that is display 'none'.
     // so we need to first activate the div, then, after a render timeout, start the opacity transition.
     @observable enableHtmlOverlayTransitions: boolean = false;
     @computed get htmlOverlay() {
-        const effect = StrCast(this.htmlOverlayEffect?.presentation_effect, StrCast(this.htmlOverlayEffect?.followLinkAnimEffect));
+        const effect = StrCast(this._htmlOverlayEffect?.presentation_effect, StrCast(this._htmlOverlayEffect?.followLinkAnimEffect));
         return (
             <div
                 className="documentView-htmlOverlay"
@@ -1638,13 +1668,13 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                         setTimeout(action(() => (this.enableHtmlOverlayTransitions = val)));
                     }
                 }}
-                style={{ display: !this.textHtmlOverlay ? 'none' : undefined }}>
+                style={{ display: !this._htmlOverlayText ? 'none' : undefined }}>
                 <div className="documentView-htmlOverlayInner" style={{ transition: `all 500ms`, opacity: this.enableHtmlOverlayTransitions ? 0.9 : 0 }}>
                     {DocumentViewInternal.AnimationEffect(
                         <div className="webBox-textHighlight">
-                            <ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this.textHtmlOverlay)} />
+                            <ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this._htmlOverlayText)} />
                         </div>,
-                        { ...(this.htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc,
+                        { ...(this._htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc,
                         this.Document
                     )}
                 </div>
@@ -1663,7 +1693,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
 
         return (
             <div className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}>
-                {!this._props.Document || !this._props.PanelWidth() ? null : (
+                {!this.Document || !this._props.PanelWidth() ? null : (
                     <div
                         className="contentFittingDocumentView-previewDoc"
                         ref={this.ContentRef}
@@ -1676,7 +1706,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                         <DocumentViewInternal
                             {...this._props}
                             DocumentView={this.selfView}
-                            viewPath={this.docViewPathFunc}
+                            docViewPath={this.docViewPathFunc}
                             PanelWidth={this.PanelWidth}
                             PanelHeight={this.PanelHeight}
                             NativeWidth={this.NativeWidth}
@@ -1687,7 +1717,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                             layout_fitWidth={this.layout_fitWidthFunc}
                             ScreenToLocalTransform={this.screenToContentsTransform}
                             focus={this._props.focus || emptyFunction}
-                            ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))}
+                            ref={action((r: DocumentViewInternal | null) => r && (this._docViewInternal = r))}
                         />
                         {this.htmlOverlay}
                         {this.infoUI}
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index ff92c701f..ebad257ac 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -27,7 +27,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     componentDidMount() {
         this._props.setContentView?.(this);
-        if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath()))) {
+        if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.docViewPath))) {
             this._props.select(false);
 
             this._ref.current!.mathField.focus();
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 008f10f26..b456807d7 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -6,23 +6,18 @@ import { Doc, Field, FieldResult, Opt } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { ScriptField } from '../../../fields/ScriptField';
 import { WebField } from '../../../fields/URLField';
-import { DocumentViewSharedProps } from './DocumentView';
+import { DocumentView, DocumentViewInternalSharedProps, DocumentViewSharedProps } from './DocumentView';
 
 //
 // these properties get assigned through the render() method of the DocumentView when it creates this node.
 // However, that only happens because the properties are "defined" in the markup for the field view.
 // See the LayoutString method on each field view :   ImageBox, FormattedTextBox, etc.
 //
-export interface FieldViewProps extends DocumentViewSharedProps {
+export interface FieldViewProps extends DocumentViewSharedProps, DocumentViewInternalSharedProps {
     // FieldView specific props that are not part of DocumentView props
     fieldKey: string;
 
-    select: (isCtrlPressed: boolean) => void;
-    isContentActive: (outsideReaction?: boolean) => boolean | undefined;
-    isDocumentActive?: () => boolean | undefined;
-    isSelected: () => boolean;
     setHeight?: (height: number) => void;
-    NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to DocumentViewInternalsProps
     onBrowseClick?: () => ScriptField | undefined;
     onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
     pointerEvents?: () => Opt<string>;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index a5853499f..4483d12ce 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -158,7 +158,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     @undoBatch
     setNativeSize = action(() => {
-        const scaling = (this._props.DocumentView?.().screenToViewTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
+        const scaling = (this.DocumentView?.().screenToViewTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
         const nscale = NumCast(this._props.PanelWidth()) / scaling;
         const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
         this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw;
@@ -368,7 +368,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     }
     screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.ScreenToLocalBoxXf().Scale);
     marqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             setupMoveUpEvents(
                 this,
                 e,
@@ -444,7 +444,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     {this.content}
                 </CollectionFreeFormView>
                 {this.annotationLayer}
-                {!this._mainCont.current || !this._annotationLayer.current ? null : (
+                {!this._mainCont.current || !this.DocumentView || !this._annotationLayer.current ? null : (
                     <MarqueeAnnotator
                         Document={this.Document}
                         ref={this._marqueeref}
@@ -452,7 +452,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         annotationLayerScrollTop={0}
                         scaling={returnOne}
                         annotationLayerScaling={this._props.NativeDimScaling}
-                        docView={this._props.DocumentView!}
+                        docView={this.DocumentView}
                         addDocument={this.addDocument}
                         finishMarquee={this.finishMarquee}
                         savedAnnotations={this.savedAnnotations}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 7bc9d3f85..b2aab2422 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -61,33 +61,11 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
     };
 
     render() {
-        const props: FieldViewProps = {
-            Document: this._props.doc,
-            childFilters: returnEmptyFilter,
-            childFiltersByRanges: returnEmptyFilter,
-            searchFilterDocs: returnEmptyDoclist,
-            styleProvider: DefaultStyleProvider,
-            docViewPath: returnEmptyDoclist,
-            fieldKey: this._props.keyName,
-            isSelected: returnFalse,
-            setHeight: returnFalse,
-            select: emptyFunction,
-            bringToFront: emptyFunction,
-            renderDepth: 1,
-            isContentActive: returnFalse,
-            whenChildContentsActiveChanged: emptyFunction,
-            ScreenToLocalTransform: Transform.Identity,
-            focus: emptyFunction,
-            PanelWidth: this._props.PanelWidth,
-            PanelHeight: this._props.PanelHeight,
-            addDocTab: returnFalse,
-            pinToPres: returnZero,
-        };
         // let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")";
         let protoCount = 0;
-        let doc: Doc | undefined = props.Document;
+        let doc = this._props.doc;
         while (doc) {
-            if (Object.keys(doc).includes(props.fieldKey)) {
+            if (Object.keys(doc).includes(this._props.keyName)) {
                 break;
             }
             protoCount++;
@@ -106,17 +84,17 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
                             style={hover}
                             className="keyValuePair-td-key-delete"
                             onClick={undoBatch(() => {
-                                if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1) {
-                                    delete props.Document[props.fieldKey];
-                                } else delete DocCast(props.Document.proto)?.[props.fieldKey];
+                                if (Object.keys(this._props.doc).indexOf(this._props.keyName) !== -1) {
+                                    delete this._props.doc[this._props.keyName];
+                                } else delete DocCast(this._props.doc.proto)?.[this._props.keyName];
                             })}>
                             X
                         </button>
                         <input className="keyValuePair-td-key-check" type="checkbox" style={hover} onChange={this.handleCheck} ref={this.checkbox} />
-                        <Tooltip title={Object.entries(new DocumentOptions()).find((pair: [string, FInfo]) => pair[0].replace(/^_/, '') === props.fieldKey)?.[1].description ?? ''}>
-                            <div className="keyValuePair-keyField" style={{ marginLeft: 20 * (props.fieldKey.match(/_/g)?.length || 0), color: keyStyle }}>
+                        <Tooltip title={Object.entries(new DocumentOptions()).find((pair: [string, FInfo]) => pair[0].replace(/^_/, '') === this._props.keyName)?.[1].description ?? ''}>
+                            <div className="keyValuePair-keyField" style={{ marginLeft: 20 * (this._props.keyName.match(/_/g)?.length || 0), color: keyStyle }}>
                                 {'('.repeat(parenCount)}
-                                {props.fieldKey}
+                                {this._props.keyName}
                                 {')'.repeat(parenCount)}
                             </div>
                         </Tooltip>
@@ -124,7 +102,32 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
                 </td>
                 <td className="keyValuePair-td-value" style={{ width: `${100 - this._props.keyWidth}%` }} onContextMenu={this.onContextMenu}>
                     <div className="keyValuePair-td-value-container">
-                        <EditableView contents={undefined} fieldContents={props} GetValue={() => Field.toKeyValueString(props.Document, props.fieldKey)} SetValue={(value: string) => KeyValueBox.SetField(props.Document, props.fieldKey, value)} />
+                        <EditableView
+                            contents={undefined}
+                            fieldContents={{
+                                Document: this._props.doc,
+                                childFilters: returnEmptyFilter,
+                                childFiltersByRanges: returnEmptyFilter,
+                                searchFilterDocs: returnEmptyDoclist,
+                                styleProvider: DefaultStyleProvider,
+                                fieldKey: this._props.keyName,
+                                isSelected: returnFalse,
+                                setHeight: returnFalse,
+                                select: emptyFunction,
+                                bringToFront: emptyFunction,
+                                renderDepth: 1,
+                                isContentActive: returnFalse,
+                                whenChildContentsActiveChanged: emptyFunction,
+                                ScreenToLocalTransform: Transform.Identity,
+                                focus: emptyFunction,
+                                PanelWidth: this._props.PanelWidth,
+                                PanelHeight: this._props.PanelHeight,
+                                addDocTab: returnFalse,
+                                pinToPres: returnZero,
+                            }}
+                            GetValue={() => Field.toKeyValueString(this._props.doc, this._props.keyName)}
+                            SetValue={(value: string) => KeyValueBox.SetField(this._props.doc, this._props.keyName, value)}
+                        />
                     </div>
                 </td>
             </tr>
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index b86ba72a0..864c1955b 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -7,13 +7,13 @@ import { TraceMobx } from '../../../fields/util';
 import { DragManager } from '../../util/DragManager';
 import { LinkFollower } from '../../util/LinkFollower';
 import { SelectionManager } from '../../util/SelectionManager';
-import { ViewBoxBaseComponent } from '../DocComponent';
+import { ViewBoxBaseComponent, ViewBoxBaseProps } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import { FieldView, FieldViewProps } from './FieldView';
 import './LinkAnchorBox.scss';
 import { LinkInfo } from './LinkDocPreview';
 const { default: { MEDIUM_GRAY }, } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
-export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
+export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps & ViewBoxBaseProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(LinkAnchorBox, fieldKey);
     }
@@ -33,12 +33,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @computed get linkSource() {
-        return this._props.docViewPath()[this._props.docViewPath().length - 2].Document; // this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.LinkSource);
+        return this.containerViewPath?.().lastElement().Document; // this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.LinkSource);
     }
 
     onPointerDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => {
-            if (doubleTap) LinkFollower.FollowLink(this.Document, this.linkSource, false);
+        const linkSource = this.linkSource;
+        linkSource && setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => {
+            if (doubleTap) LinkFollower.FollowLink(this.Document, linkSource, false);
             else this._props.select(false);
         });
     };
@@ -86,7 +87,8 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 className={`linkAnchorBox-cont${small ? '-small' : ''}`}
                 onPointerEnter={e =>
                     LinkInfo.SetLinkInfo({
-                        docProps: this._props,
+                        DocumentView: this.DocumentView,
+                        styleProvider: this._props.styleProvider,
                         linkSrc: this.linkSource,
                         linkDoc: this.Document,
                         showHeader: true,
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 7f1d41547..4221f464d 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -29,12 +29,12 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed get anchor1() {
         const anchor1 = DocCast(this.dataDoc.link_anchor_1);
         const anchor_1 = anchor1?.layout_unrendered ? DocCast(anchor1.annotationOn) : anchor1;
-        return DocumentManager.Instance.getDocumentView(anchor_1, this._props.docViewPath()[this._props.docViewPath().length - 2]); // this._props.docViewPath().lastElement());
+        return DocumentManager.Instance.getDocumentView(anchor_1, this.containerViewPath?.().lastElement());
     }
     @computed get anchor2() {
         const anchor2 = DocCast(this.dataDoc.link_anchor_2);
         const anchor_2 = anchor2?.layout_unrendered ? DocCast(anchor2.annotationOn) : anchor2;
-        return DocumentManager.Instance.getDocumentView(anchor_2, this._props.docViewPath()[this._props.docViewPath().length - 2]); // this._props.docViewPath().lastElement());
+        return DocumentManager.Instance.getDocumentView(anchor_2, this.containerViewPath?.().lastElement());
     }
     screenBounds = () => {
         if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.CollectionFreeFormView) {
@@ -66,7 +66,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     const a = (this.anchor1 ?? this.anchor2)!;
                     const b = (this.anchor2 ?? this.anchor1)!;
 
-                    const parxf = this._props.docViewPath()[this._props.docViewPath().length - 2].ComponentView as CollectionFreeFormView;
+                    const parxf = this.containerViewPath?.().lastElement().ComponentView as CollectionFreeFormView;
                     const this_xf = parxf?.screenToFreeformContentsXf ?? Transform.Identity; //this.ScreenToLocalTransform();
                     const a_invXf = a.screenToViewTransform().inverse();
                     const b_invXf = b.screenToViewTransform().inverse();
@@ -161,12 +161,11 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return (
             <div className={`linkBox-container${this._props.isContentActive() ? '-interactive' : ''}`} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }}>
                 <ComparisonBox
-                    {...this._props}
+                    {...this._props} //
                     fieldKey="link_anchor"
                     setHeight={emptyFunction}
                     dontRegisterView={true}
                     renderDepth={this._props.renderDepth + 1}
-                    isContentActive={this._props.isContentActive}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
                     moveDocument={returnFalse}
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index ea23ecbea..ed4878d9d 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -18,8 +18,9 @@ import { SearchUtil } from '../../util/SearchUtil';
 import { SettingsManager } from '../../util/SettingsManager';
 import { Transform } from '../../util/Transform';
 import { ObservableReactComponent } from '../ObservableReactComponent';
-import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView';
+import { DocumentView, DocumentViewSharedProps, OpenWhere, StyleProviderFunc } from './DocumentView';
 import './LinkDocPreview.scss';
+import { FieldViewProps } from './FieldView';
 
 export class LinkInfo {
     private static _instance: Opt<LinkInfo>;
@@ -43,7 +44,8 @@ export class LinkInfo {
 interface LinkDocPreviewProps {
     linkDoc?: Doc;
     linkSrc?: Doc;
-    docProps: DocumentViewSharedProps;
+    DocumentView?: () => DocumentView;
+    styleProvider?: StyleProviderFunc;
     location: number[];
     hrefs?: string[];
     showHeader?: boolean;
@@ -152,7 +154,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
             action(() => {
                 LinkManager.currentLink = this._linkDoc;
                 LinkManager.currentLinkAnchor = this._linkSrc;
-                this._props.docProps.DocumentView?.().select(false);
+                this._props.DocumentView?.().select(false);
                 if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
                     SettingsManager.Instance.propertiesWidth = 250;
                 }
@@ -269,8 +271,8 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
                             }}
                             Document={this._targetDoc!}
                             moveDocument={returnFalse}
-                            styleProvider={this._props.docProps?.styleProvider}
-                            docViewPath={returnEmptyDoclist}
+                            styleProvider={this._props.styleProvider}
+                            containerViewPath={returnEmptyDoclist}
                             ScreenToLocalTransform={Transform.Identity}
                             isDocumentActive={returnFalse}
                             isContentActive={returnFalse}
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 3575b21e4..56fb157da 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -379,11 +379,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         });
 
         const targetCreator = (annotationOn: Doc | undefined) => {
-            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
+            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, 'yellow');
             FormattedTextBox.SetSelectOnLoad(target);
             return target;
         };
-        const docView = this._props.DocumentView?.();
+        const docView = this.DocumentView?.();
         docView &&
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                 dragComplete: e => {
diff --git a/src/client/views/nodes/MapBox/MapPushpinBox.tsx b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
index 34e237007..fc5b4dd18 100644
--- a/src/client/views/nodes/MapBox/MapPushpinBox.tsx
+++ b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
@@ -18,10 +18,10 @@ export class MapPushpinBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     get mapBoxView() {
-        return this._props.DocumentView?.()?._props.docViewPath().lastElement()?.ComponentView as MapBox;
+        return this.DocumentView?.()?.containerViewPath?.().lastElement()?.ComponentView as MapBox;
     }
     get mapBox() {
-        return this._props.DocumentView?.()._props.docViewPath().lastElement()?.Document;
+        return this.DocumentView?.().containerViewPath?.().lastElement()?.Document;
     }
 
     render() {
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index ea8496c99..e079f7457 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -228,11 +228,11 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
         });
 
         const targetCreator = (annotationOn: Doc | undefined) => {
-            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
+            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, 'yellow');
             FormattedTextBox.SetSelectOnLoad(target);
             return target;
         };
-        const docView = this._props.DocumentView?.();
+        const docView = this.DocumentView?.();
         docView &&
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                 dragComplete: e => {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 959d5d88d..71c1b6a4c 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -102,7 +102,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         Doc.GetProto(region).followLinkToggle = true;
         this.addDocument(region);
 
-        const docViewContent = this._props.docViewPath().lastElement().ContentDiv!;
+        const docViewContent = this.DocumentView?.().ContentDiv!;
         const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
         newDiv.style.width = NumCast(this.layoutDoc._width).toString();
         newDiv.style.height = NumCast(this.layoutDoc._height).toString();
@@ -163,7 +163,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     updateIcon = () => {
         // currently we render pdf icons as text labels
-        const docViewContent = this._props.docViewPath().lastElement().ContentDiv!;
+        const docViewContent = this.DocumentView?.().ContentDiv!;
         const filename = this.layoutDoc[Id] + '-icon' + new Date().getTime();
         this._pdfViewer?._mainCont.current &&
             CollectionFreeFormView.UpdateIcon(
@@ -304,7 +304,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     setPdfViewer = (pdfViewer: PDFViewer) => {
         this._pdfViewer = pdfViewer;
-        const docView = this._props.DocumentView?.();
+        const docView = this.DocumentView?.();
         if (this._initialScrollTarget && docView) {
             this.focus(this._initialScrollTarget, { instant: true });
             this._initialScrollTarget = undefined;
@@ -525,7 +525,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     removeDocument={this.removeDocument}
                 />
             ) : (
-                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this._props.DocumentView?.()!, false), true)}>
+                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}>
                     <ComponentTag
                         {...this._props}
                         setContentView={emptyFunction} // override setContentView to do nothing
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index fb42286af..1913ab032 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -21,7 +21,7 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
 import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { StyleProp } from '../StyleProvider';
@@ -44,7 +44,7 @@ import './VideoBox.scss';
  */
 
 @observer
-export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
+export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(VideoBox, fieldKey);
     }
@@ -608,7 +608,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     // removes from currently playing display
     @action
     removeCurrentlyPlaying = () => {
-        const docView = this._props.DocumentView?.();
+        const docView = this.DocumentView?.();
         if (CollectionStackedTimeline.CurrentlyPlaying && docView) {
             const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView);
             index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
@@ -617,7 +617,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     // adds doc to currently playing display
     @action
     addCurrentlyPlaying = () => {
-        const docView = this._props.DocumentView?.();
+        const docView = this.DocumentView?.();
         if (!CollectionStackedTimeline.CurrentlyPlaying) {
             CollectionStackedTimeline.CurrentlyPlaying = [];
         }
@@ -749,7 +749,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     // starts marquee selection
     marqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) === 1 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) === 1 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) {
             setupMoveUpEvents(
                 this,
                 e,
@@ -941,7 +941,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 style={{
                     pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
                     borderRadius,
-                    overflow: this._props.docViewPath?.().slice(-1)[0].layout_fitWidth ? 'auto' : undefined,
+                    overflow: this.DocumentView?.().layout_fitWidth ? 'auto' : undefined,
                 }}>
                 <div className="videoBox-viewer" onPointerDown={this.marqueeDown}>
                     <div
@@ -978,7 +978,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         </CollectionFreeFormView>
                     </div>
                     {this.annotationLayer}
-                    {!this._mainCont.current || !this._annotationLayer.current ? null : (
+                    {!this._mainCont.current || !this.DocumentView || !this._annotationLayer.current ? null : (
                         <MarqueeAnnotator
                             ref={this._marqueeref}
                             Document={this.Document}
@@ -986,7 +986,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                             annotationLayerScrollTop={0}
                             scaling={returnOne}
                             annotationLayerScaling={this._props.NativeDimScaling}
-                            docView={this._props.DocumentView!}
+                            docView={this.DocumentView}
                             containerOffset={this.marqueeOffset}
                             addDocument={this.addDocWithTimecode}
                             finishMarquee={this.finishMarquee}
@@ -1004,7 +1004,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     }
 
     @computed get UIButtons() {
-        const bounds = this._props.docViewPath().lastElement().getBounds();
+        const bounds = this.DocumentView?.().getBounds();
         const width = (bounds?.right || 0) - (bounds?.left || 0);
         const curTime = NumCast(this.layoutDoc._layout_currentTimecode);
         return (
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c3be2b390..86709e4d7 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -33,7 +33,7 @@ import { Annotation } from '../pdf/Annotation';
 import { GPTPopup } from '../pdf/GPTPopup/GPTPopup';
 import { SidebarAnnos } from '../SidebarAnnos';
 import { StyleProp } from '../StyleProvider';
-import { DocComponentView, DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere } from './DocumentView';
+import { DocComponentView, DocFocusOptions, DocumentView, OpenWhere } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { LinkInfo } from './LinkDocPreview';
 import { PinProps, PresBox } from './trails';
@@ -250,7 +250,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @action
     createTextAnnotation = (sel: Selection, selRange: Range | undefined) => {
         if (this._mainCont.current && selRange) {
-            if (this.dataDoc[this._props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToContentsTransform().RotateDeg)}deg)`;
+            if (this.dataDoc[this._props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this.DocumentView!().screenToContentsTransform().RotateDeg)}deg)`;
             const clientRects = selRange.getClientRects();
             for (let i = 0; i < clientRects.length; i++) {
                 const rect = clientRects.item(i);
@@ -274,7 +274,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
         // clear selection
         this._textAnnotationCreator = undefined;
-        if (sel.empty) sel.empty(); // Chrome
+        if (sel.empty)
+            sel.empty(); // Chrome
         else if (sel.removeAllRanges) sel.removeAllRanges(); // Firefox
         return this._savedAnnotations;
     };
@@ -297,8 +298,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     getView = (doc: Doc, options: DocFocusOptions) => {
-        if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this._props.DocumentView?.()));
-        if (this.Document.layout_fieldKey === 'layout_icon') this._props.DocumentView?.().iconify();
+        if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this.DocumentView?.()));
+        if (this.Document.layout_fieldKey === 'layout_icon') this.DocumentView?.().iconify();
         const webUrl = WebCast(doc.config_data)?.url;
         if (this._url && webUrl && webUrl.href !== this._url) this.setData(webUrl.href);
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false);
@@ -343,7 +344,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     iframeUp = (e: PointerEvent) => {
         this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value
         this._textAnnotationCreator = undefined;
-        this._props.docViewPath().lastElement()?.docView?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
+        this.DocumentView?.()?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
         if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) {
             const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
             const scale = (this._props.NativeDimScaling?.() || 1) * mainContBounds.scale;
@@ -364,7 +365,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         e.stopPropagation();
         const sel = window.getSelection();
         this._textAnnotationCreator = undefined;
-        if (sel?.empty) sel.empty(); // Chrome
+        if (sel?.empty)
+            sel.empty(); // Chrome
         else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
         // bcz: NEED TO unrotate e.clientX and e.clientY
         const word = getWordAtPoint(e.target, e.clientX, e.clientY);
@@ -556,7 +558,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             clearStyleSheetRules(WebBox.webStyleSheet);
             this._scrollTimer = undefined;
             const newScrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop;
-            if (!LinkInfo.Instance?.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath()))) {
+            if (!LinkInfo.Instance?.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.docViewPath))) {
                 this.layoutDoc.thumb = undefined;
                 this.layoutDoc.thumbScrollTop = undefined;
                 this.layoutDoc.thumbNativeWidth = undefined;
@@ -723,10 +725,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     onMarqueeDown = (e: React.PointerEvent) => {
         const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
         this._textAnnotationCreator = undefined;
-        if (sel?.empty) sel.empty(); // Chrome
+        if (sel?.empty)
+            sel.empty(); // Chrome
         else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
         this.marqueeing = [e.clientX, e.clientY];
-        if (!e.altKey && e.button === 0 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             setupMoveUpEvents(
                 this,
                 e,
@@ -750,7 +753,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         this.marqueeing = undefined;
         this._textAnnotationCreator = undefined;
         const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
-        if (sel?.empty) sel.empty(); // Chrome
+        if (sel?.empty)
+            sel.empty(); // Chrome
         else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
         this._setPreviewCursor?.(x ?? 0, y ?? 0, false, !this._marqueeref.current?.isEmpty, this.Document);
         if (x !== undefined && y !== undefined) {
@@ -762,7 +766,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 setTimeout(() => {
                     // if menu comes up right away, the down event can still be active causing a menu item to be selected
                     this.specificContextMenu(undefined as any);
-                    this._props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y);
+                    this.DocumentView?.().onContextMenu(undefined, x, y);
                 });
             }
         }
@@ -908,7 +912,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     });
     @action
     onZoomWheel = (e: React.WheelEvent) => {
-        if (this._props.isContentActive(true)) {
+        if (this._props.isContentActive()) {
             e.stopPropagation();
         }
     };
@@ -1065,7 +1069,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
     transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
     opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed ? [] : [Utils.OpaqueBackgroundFilter])];
-    childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+    childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
             if (this.inlineTextAnnotations.includes(doc)) return 'none';
         }
@@ -1099,7 +1103,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     onContextMenu={this.specificContextMenu}>
                     {this.webpage}
                 </div>
-                {!this._mainCont.current || !this._annotationLayer.current ? null : (
+                {!this._mainCont.current || !this.DocumentView || !this._annotationLayer.current ? null : (
                     <div style={{ position: 'absolute', height: '100%', width: '100%', pointerEvents: this.marqueeing ? 'all' : 'none' }}>
                         <MarqueeAnnotator
                             ref={this._marqueeref}
@@ -1109,7 +1113,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             annotationLayerScrollTop={0}
                             scaling={this._props.NativeDimScaling}
                             addDocument={this.addDocumentWrapper}
-                            docView={this._props.DocumentView!}
+                            docView={this.DocumentView}
                             finishMarquee={this.finishMarquee}
                             savedAnnotations={this.savedAnnotationsCreator}
                             selectionText={this.selectionText}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 6a2cd359c..cc58c2071 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -6,7 +6,7 @@ import * as ReactDOM from 'react-dom/client';
 import { Doc } from '../../../../fields/Doc';
 import { Height, Width } from '../../../../fields/DocSymbols';
 import { NumCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, Utils } from '../../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnFalse, Utils } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { Transform } from '../../../util/Transform';
@@ -207,7 +207,7 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
                     isDocumentActive={returnFalse}
                     isContentActive={this.isContentActive}
                     styleProvider={this._textBox._props.styleProvider}
-                    docViewPath={this._textBox._props.docViewPath}
+                    containerViewPath={this._textBox.docViewPathFunc ?? returnEmptyDoclist}
                     ScreenToLocalTransform={this.getDocTransform}
                     addDocTab={this._textBox._props.addDocTab}
                     pinToPres={returnFalse}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 555b752f0..3f2ab0ec5 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -148,7 +148,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     }
 
     createPivotForField = (e: React.MouseEvent) => {
-        let container = this._props.tbox._props.DocumentView?.()._props.docViewPath().lastElement();
+        let container = this._props.tbox.containerViewPath?.().lastElement();
         if (container) {
             const embedding = Doc.MakeEmbedding(container.Document);
             embedding._type_collection = CollectionViewType.Time;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ae26f170b..6a6cec094 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -42,7 +42,7 @@ import { CollectionStackingView } from '../../collections/CollectionStackingView
 import { CollectionTreeView } from '../../collections/CollectionTreeView';
 import { ContextMenu } from '../../ContextMenu';
 import { ContextMenuProps } from '../../ContextMenuItem';
-import { ViewBoxAnnotatableComponent } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
 import { LightboxView } from '../../LightboxView';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
@@ -71,7 +71,7 @@ import { SummaryView } from './SummaryView';
 
 export interface FormattedTextBoxProps {}
 @observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps & FormattedTextBoxProps>() {
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps & FormattedTextBoxProps>() {
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(FormattedTextBox, fieldStr);
     }
@@ -123,7 +123,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     }
 
     @computed get noSidebar() {
-        return this._props.docViewPath().lastElement()?._props.hideDecorationTitle || this._props.noSidebar || this.Document._layout_noSidebar;
+        return this.DocumentView?.()._props.hideDecorationTitle || this._props.noSidebar || this.Document._layout_noSidebar;
     }
     @computed get layout_sidebarWidthPercent() {
         return this._showSidebar ? '20%' : StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
@@ -310,7 +310,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 return target;
             };
 
-            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this._props.docViewPath().lastElement(), () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
+            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.DocumentView?.()!, () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
         });
         const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
         this._props.rootSelected?.() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
@@ -1410,7 +1410,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             (this._editorView as any).TextView = this;
         }
 
-        const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath()));
+        const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.docViewPath));
         if (this._editorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
             const selLoadChar = FormattedTextBox.SelectOnLoadChar;
             FormattedTextBox.SelectOnLoad = undefined;
@@ -1531,7 +1531,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
         if (!state || !editor || !this.ProseRef?.children[0].className.includes('-focused')) return;
         if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
-        else if (this._props.isContentActive(true)) {
+        else if (this._props.isContentActive()) {
             const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
             let xpos = pcords?.pos || 0;
             while (xpos > 0 && !state.doc.resolve(xpos).node()?.isTextblock) {
@@ -1764,7 +1764,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 return toNum(height) + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom));
             };
             const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + toHgt(child), margins);
-            const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.layout_maxAutoHeight, proseHeight), proseHeight);
+            const scrollHeight = this.ProseRef && proseHeight;
             if (this._props.setHeight && scrollHeight && !this._props.dontRegisterView) {
                 // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
                 const setScrollHeight = () => (this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight);
@@ -1852,7 +1852,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     setHeight={this.setSidebarHeight}
                 />
             ) : (
-                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this._props.DocumentView?.()!, false), true)}>
+                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}>
                     <ComponentTag
                         {...this._props}
                         ref={this._sidebarTagRef as any}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index be8736525..ce17af6ca 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -134,7 +134,8 @@ export class FormattedTextBoxComment {
             //nbef &&
             naft &&
                 LinkInfo.SetLinkInfo({
-                    docProps: textBox.props,
+                    DocumentView: textBox.DocumentView,
+                    styleProvider: textBox._props.styleProvider,
                     linkSrc: textBox.Document,
                     linkDoc: linkDoc ? (DocServer.GetCachedRefField(linkDoc) as Doc) : undefined,
                     location: (pos => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))),
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 6fa64a765..645ac08e1 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -294,13 +294,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         listItemDoc.presentation_effect = this.activeItem.presBulletEffect;
                         listItemDoc.presentation_transition = 500;
                         targetView?.setAnimEffect(listItemDoc, 500);
-                        if (targetView?.docView && this.activeItem.presBulletExpand) {
-                            targetView.docView._animateScalingTo = 1.2;
-                            targetView.docView._animateScaleTime = 400;
-                            Doc.AddUnHighlightWatcher(() => {
-                                targetView.docView!._animateScaleTime = undefined;
-                                targetView!.docView!._animateScalingTo = 0;
-                            });
+                        if (targetView && this.activeItem.presBulletExpand) {
+                            targetView.setAnimateScaling(1.2, 400);
+                            Doc.AddUnHighlightWatcher(() => targetView?.setAnimateScaling(0, undefined));
                         }
                         listItemDoc.opacity = undefined;
                         this.activeItem.presentation_indexed = presIndexed + 1;
@@ -2622,7 +2618,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 ScreenToLocalTransform={this.getTransform}
                                 AddToMap={this.AddToMap}
                                 RemFromMap={this.RemFromMap}
-                                hierarchyIndex={emptyPath}
+                                hierarchyIndex={emptyPath as any as number[]}
                             />
                         ) : null}
                     </div>
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 4945d66c8..8cf01b9de 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -19,7 +19,7 @@ import { TreeView } from '../../collections/TreeView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
-import { DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../../nodes/DocumentView';
+import { DocumentView } from '../../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../../nodes/FieldView';
 import { StyleProp } from '../../StyleProvider';
 import { PresBox } from './PresBox';
@@ -50,7 +50,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     // the presentation view that renders this slide
     @computed get presBoxView() {
-        return this._props.DocumentView?.()?._props.docViewPath().lastElement()?.ComponentView as PresBox;
+        return this.containerViewPath?.().lastElement()?.ComponentView as PresBox;
     }
 
     // the presentation view document that renders this slide
@@ -97,7 +97,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     presExpandDocumentClick = () => (this.slideDoc.presentation_expandInlineButton = !this.slideDoc.presentation_expandInlineButton);
     embedHeight = () => this.collapsedHeight + this.expandViewHeight;
     embedWidth = () => this._props.PanelWidth() / 2;
-    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+    styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
         return property === StyleProp.Opacity ? 1 : this._props.styleProvider?.(doc, props, property);
     };
     /**
@@ -116,7 +116,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     hideLinkButton={true}
                     ScreenToLocalTransform={Transform.Identity}
                     renderDepth={this._props.renderDepth + 1}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     childFilters={this._props.childFilters}
                     childFiltersByRanges={this._props.childFiltersByRanges}
                     searchFilterDocs={this._props.searchFilterDocs}
@@ -196,7 +196,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const dragArray = this.presBoxView?._dragArray ?? [];
         const dragData = new DragManager.DocumentDragData(this.presBoxView?.sortArray() ?? []);
         if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.slideDoc);
-        dragData.treeViewDoc = this.presBox?._type_collection === CollectionViewType.Tree ? this.presBox : undefined; // this._props.DocumentView?.()?._props.treeViewDoc;
+        dragData.treeViewDoc = this.presBox?._type_collection === CollectionViewType.Tree ? this.presBox : undefined; // this.DocumentView?.()?._props.treeViewDoc;
         dragData.moveDocument = this._props.moveDocument;
         const dragItem: HTMLElement[] = [];
         const classesToRestore = new Map<HTMLElement, string>();
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 4a7e35c16..89e5944f2 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -17,7 +17,7 @@ import { SnappingManager } from '../../util/SnappingManager';
 import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
-import { DocFocusOptions, DocumentViewInternalProps, DocumentViewProps } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { LinkInfo } from '../nodes/LinkDocPreview';
 import { ObservableReactComponent } from '../ObservableReactComponent';
@@ -30,7 +30,7 @@ const _global = (window /* browser */ || global) /* node */ as any;
 
 //pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
 // The workerSrc property shall be specified.
-Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.0.269/build/pdf.worker.mjs';
+Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.0.379/build/pdf.worker.mjs';
 
 interface IViewerProps extends FieldViewProps {
     Document: Doc;
@@ -364,10 +364,10 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
         this._downX = e.clientX;
         this._downY = e.clientY;
         if ((this._props.Document._freeform_scale || 1) !== 1) return;
-        if ((e.button !== 0 || e.altKey) && this._props.isContentActive(true)) {
+        if ((e.button !== 0 || e.altKey) && this._props.isContentActive()) {
             this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this._props.Document);
         }
-        if (!e.altKey && e.button === 0 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             this._props.select(false);
             MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
             this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
@@ -465,7 +465,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
 
     @action
     onZoomWheel = (e: React.WheelEvent) => {
-        if (this._props.isContentActive(true)) {
+        if (this._props.isContentActive()) {
             e.stopPropagation();
             if (e.ctrlKey) {
                 const curScale = Number(this._pdfViewer.currentScaleValue);
@@ -497,7 +497,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
     transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
     opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed && this._props.isContentActive() ? [] : [Utils.OpaqueBackgroundFilter])];
-    childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+    childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
             const docProps = testDocProps(props) ? props : undefined;
             if (this.inlineTextAnnotations.includes(doc) || this._props.isContentActive() === false) return 'none';
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 575d5849e..188b2eba4 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -102,14 +102,12 @@ export class TopBar extends React.Component {
                     Document={selDoc}
                     fieldKey="data"
                     dropAction="embed"
-                    setHeight={returnFalse}
                     styleProvider={DefaultStyleProvider}
                     bringToFront={emptyFunction}
                     select={emptyFunction}
                     isContentActive={returnTrue}
                     isAnyChildContentActive={returnFalse}
                     isSelected={returnFalse}
-                    docViewPath={returnEmptyDoclist}
                     moveDocument={returnFalse}
                     addDocument={returnFalse}
                     addDocTab={DocumentViewInternal.addDocTabFunc}
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index a1ce55314..9d68ea731 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -396,7 +396,7 @@ export class MobileInterface extends React.Component {
                     isContentActive={emptyFunction}
                     focus={emptyFunction}
                     styleProvider={this.whitebackground}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
                     childFilters={returnEmptyFilter}
diff --git a/src/server/DashStats.ts b/src/server/DashStats.ts
index b6164832f..a9e6af67c 100644
--- a/src/server/DashStats.ts
+++ b/src/server/DashStats.ts
@@ -15,7 +15,8 @@ export namespace DashStats {
     export const SAMPLING_INTERVAL = 1000; // in milliseconds (ms) - Time interval to update the frontend.
     export const RATE_INTERVAL = 10; // in seconds (s) - Used to calculate rate
 
-    const statsCSVFilename = './src/server/stats/userLoginStats.csv';
+    const statsCSVDirectory = './src/server/stats/';
+    const statsCSVFilename = statsCSVDirectory + 'userLoginStats.csv';
     const columns = ['USERNAME', 'ACTION', 'TIME'];
 
     /**
@@ -153,6 +154,7 @@ export namespace DashStats {
                 TIME: currentDate.toISOString(),
             };
 
+            if (!fs.existsSync(statsCSVDirectory)) fs.mkdirSync(statsCSVDirectory);
             let statsFile = fs.createWriteStream(statsCSVFilename, { flags: 'a' });
             statsFile.write(convertToCSV(toWrite));
             statsFile.end();
-- 
cgit v1.2.3-70-g09d2


From af380979349308077e13fc12a2d09255b7f05f28 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 23 Jan 2024 16:11:42 -0500
Subject: reorganization of DocumentView, DocumentViewInternal and FieldView
 methods and props. fix for selection bug after following a link.  migrating
 to use [DocData] instad of GetProto()

---
 src/client/documents/Documents.ts                  |   15 +-
 src/client/util/CurrentUserUtils.ts                |    2 +-
 src/client/util/DocumentManager.ts                 |   19 +-
 src/client/util/DragManager.ts                     |    7 +-
 src/client/util/DropConverter.ts                   |    9 +-
 src/client/util/HypothesisUtils.ts                 |   18 +-
 src/client/util/LinkFollower.ts                    |    9 +-
 src/client/util/LinkManager.ts                     |   12 +-
 src/client/util/SearchUtil.ts                      |  155 +--
 src/client/util/SettingsManager.tsx                |    5 +-
 src/client/views/DashboardView.tsx                 |   12 +-
 src/client/views/DocComponent.tsx                  |  125 +-
 src/client/views/DocumentDecorations.tsx           |   16 +-
 src/client/views/InkingStroke.tsx                  |   14 +-
 src/client/views/LightboxView.tsx                  |    2 +-
 src/client/views/MainView.tsx                      |   29 +-
 src/client/views/MarqueeAnnotator.tsx              |    9 +-
 src/client/views/PropertiesView.tsx                |    5 +-
 src/client/views/ScriptBox.tsx                     |    5 +-
 src/client/views/SidebarAnnos.tsx                  |    9 +-
 src/client/views/StyleProvider.tsx                 |   12 +-
 src/client/views/TemplateMenu.tsx                  |    6 +-
 .../views/collections/CollectionCalendarView.tsx   |    4 +-
 .../views/collections/CollectionCarousel3DView.tsx |    7 +-
 .../views/collections/CollectionCarouselView.tsx   |    6 +-
 .../views/collections/CollectionDockingView.tsx    |   18 +-
 src/client/views/collections/CollectionMenu.tsx    |   25 +-
 .../views/collections/CollectionNoteTakingView.tsx |   20 +-
 .../collections/CollectionStackedTimeline.tsx      |   27 +-
 .../views/collections/CollectionStackingView.tsx   |   28 +-
 src/client/views/collections/CollectionSubView.tsx |   33 +-
 .../views/collections/CollectionTimeView.tsx       |    7 +-
 .../views/collections/CollectionTreeView.tsx       |    2 +-
 src/client/views/collections/CollectionView.tsx    |    7 +-
 src/client/views/collections/TabDocView.tsx        |   10 +-
 src/client/views/collections/TreeView.tsx          |   19 +-
 .../CollectionFreeFormLinkView.tsx                 |    6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   90 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |    7 +-
 .../collectionGrid/CollectionGridView.tsx          |    6 +-
 .../CollectionMulticolumnView.tsx                  |    4 +-
 .../CollectionMultirowView.tsx                     |    4 +-
 .../collectionMulticolumn/MulticolumnResizer.tsx   |    6 +-
 .../collectionMulticolumn/MultirowResizer.tsx      |    6 +-
 .../collectionSchema/CollectionSchemaView.tsx      |   10 +-
 .../collections/collectionSchema/SchemaRowBox.tsx  |   10 +-
 .../collectionSchema/SchemaTableCell.tsx           |    5 +-
 src/client/views/linking/LinkMenu.tsx              |    2 +-
 src/client/views/linking/LinkMenuGroup.tsx         |   12 +-
 src/client/views/newlightbox/NewLightboxView.tsx   |    2 +-
 src/client/views/nodes/AudioBox.tsx                |   15 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   10 +-
 src/client/views/nodes/ComparisonBox.tsx           |   15 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |   16 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   29 +-
 src/client/views/nodes/DocumentIcon.tsx            |    2 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |   40 +-
 src/client/views/nodes/DocumentView.tsx            | 1214 ++++++++------------
 src/client/views/nodes/EquationBox.tsx             |    4 +-
 src/client/views/nodes/FieldView.tsx               |  109 +-
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |    2 +-
 src/client/views/nodes/FunctionPlotBox.tsx         |    2 +-
 src/client/views/nodes/ImageBox.tsx                |   25 +-
 src/client/views/nodes/KeyValueBox.tsx             |    2 +-
 src/client/views/nodes/KeyValuePair.tsx            |    4 +-
 src/client/views/nodes/LabelBox.tsx                |   23 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |   10 +-
 src/client/views/nodes/LinkBox.tsx                 |    7 +-
 src/client/views/nodes/LinkDescriptionPopup.tsx    |   31 +-
 src/client/views/nodes/LinkDocPreview.tsx          |    6 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   16 +-
 src/client/views/nodes/MapBox/MapBox2.tsx          |   12 +-
 src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx |    5 +-
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |   26 +-
 src/client/views/nodes/PDFBox.tsx                  |   28 +-
 .../views/nodes/RecordingBox/RecordingBox.tsx      |   12 +-
 src/client/views/nodes/ScreenshotBox.tsx           |   13 +-
 src/client/views/nodes/ScriptingBox.tsx            |    8 +-
 src/client/views/nodes/VideoBox.tsx                |   42 +-
 src/client/views/nodes/WebBox.tsx                  |   20 +-
 src/client/views/nodes/calendarBox/CalendarBox.tsx |   10 +-
 .../views/nodes/formattedText/DashDocView.tsx      |   12 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   45 +-
 .../views/nodes/importBox/ImportElementBox.tsx     |    6 +-
 src/client/views/nodes/trails/PresBox.tsx          |   37 +-
 src/client/views/nodes/trails/PresElementBox.tsx   |    2 +-
 src/client/views/pdf/PDFViewer.tsx                 |    9 +-
 src/client/views/search/SearchBox.tsx              |    8 +-
 src/client/views/selectedDoc/SelectedDocView.tsx   |    8 +-
 src/client/views/webcam/DashWebRTCVideo.tsx        |   16 +-
 src/fields/Doc.ts                                  |   52 +-
 91 files changed, 1304 insertions(+), 1527 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 40cffcf45..bcc016391 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -205,6 +205,7 @@ export class DocumentOptions {
     type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection
     _type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection
     title?: STRt = new StrInfo('title of document', true);
+    title_custom?: BOOLt = new BoolInfo('whether title is a default or has been intentionally set');
     caption?: RichTextField;
     author?: string; // STRt = new StrInfo('creator of document'); // bcz: don't change this. Otherwise, the userDoc's field Infos will have a FieldInfo assigned to its author field which will render it unreadable
     author_date?: DATEt = new DateInfo('date the document was created', true);
@@ -1454,17 +1455,17 @@ export namespace DocUtils {
                 TaskCompletionBox.popupY = showPopup[1] - 33;
                 TaskCompletionBox.taskCompleted = true;
 
-                LinkDescriptionPopup.popupX = showPopup[0];
-                LinkDescriptionPopup.popupY = showPopup[1];
-                LinkDescriptionPopup.descriptionPopup = true;
+                LinkDescriptionPopup.Instance.popupX = showPopup[0];
+                LinkDescriptionPopup.Instance.popupY = showPopup[1];
+                LinkDescriptionPopup.Instance.display = true;
 
                 const rect = document.body.getBoundingClientRect();
-                if (LinkDescriptionPopup.popupX + 200 > rect.width) {
-                    LinkDescriptionPopup.popupX -= 190;
+                if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) {
+                    LinkDescriptionPopup.Instance.popupX -= 190;
                     TaskCompletionBox.popupX -= 40;
                 }
-                if (LinkDescriptionPopup.popupY + 100 > rect.height) {
-                    LinkDescriptionPopup.popupY -= 40;
+                if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) {
+                    LinkDescriptionPopup.Instance.popupY -= 40;
                     TaskCompletionBox.popupY -= 40;
                 }
 
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 0b15e3add..c4af20b9b 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -275,7 +275,7 @@ export class CurrentUserUtils {
             {key: "Map",        creator: opts => Docs.Create.MapDocument([], opts),         opts: { _width: 800, _height: 600, _layout_fitWidth: true, }},
             {key: "Screengrab", creator: Docs.Create.ScreenshotDocument,                    opts: { _width: 400, _height: 200 }},
             {key: "WebCam",     creator: opts => Docs.Create.WebCamDocument("", opts),      opts: { _width: 400, _height: 200, recording:true, isSystem: true, cloneFieldFilter: new List<string>(["isSystem"]) }},
-            {key: "Button",     creator: Docs.Create.ButtonDocument,                        opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, waitForDoubleClickToClick: 'never'}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}},
+            {key: "Button",     creator: Docs.Create.ButtonDocument,                        opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title_custom: true,  waitForDoubleClickToClick: 'never'}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}},
             {key: "Script",     creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
             {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts),   opts: { _width: 300, _height: 300 }},
             {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _layout_autoHeight: true, treeView_HideUnrendered: true}},
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index e81a99e40..f730d17fe 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,6 +1,7 @@
+import { Howl } from 'howler';
 import { action, computed, makeObservable, observable, ObservableSet, observe } from 'mobx';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { AclAdmin, AclEdit, Animation } from '../../fields/DocSymbols';
+import { AclAdmin, AclEdit, Animation, DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { listSpec } from '../../fields/Schema';
 import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
@@ -10,13 +11,13 @@ import { CollectionViewType } from '../documents/DocumentTypes';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
 import { TabDocView } from '../views/collections/TabDocView';
 import { LightboxView } from '../views/LightboxView';
-import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
+import { FocusViewOptions } from '../views/nodes/FieldView';
 import { KeyValueBox } from '../views/nodes/KeyValueBox';
 import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
 import { PresBox } from '../views/nodes/trails';
 import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
-import { Howl } from 'howler';
 
 export class DocumentManager {
     private static _instance: DocumentManager;
@@ -135,7 +136,7 @@ export class DocumentManager {
         });
         if (toReturn.length === 0) {
             DocumentManager.Instance.DocumentViews.forEach(view => {
-                if (Doc.GetProto(view.Document)?.[Id] === id) {
+                if (view.Document[DocData]?.[Id] === id) {
                     toReturn.push(view);
                 }
             });
@@ -236,7 +237,7 @@ export class DocumentManager {
     // shows a documentView by:
     // traverses down through the viewPath of contexts to the view:
     //           focusing on each context
-    public showDocumentView = async (targetDocView: DocumentView, options: DocFocusOptions) => {
+    public showDocumentView = async (targetDocView: DocumentView, options: FocusViewOptions) => {
         const docViewPath = [...(targetDocView.containerViewPath?.() ?? []), targetDocView];
         let rootContextView = docViewPath.shift();
         await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined, focused: false })));
@@ -252,7 +253,7 @@ export class DocumentManager {
     //           and finally restoring the targetDoc to the viewSpec specified by the last document which may either be the targetDoc, or a viewSpec that describes the targetDoc configuration
     public showDocument = async (
         targetDoc: Doc, // document to display
-        options: DocFocusOptions, // options for how to navigate to target
+        options: FocusViewOptions, // options for how to navigate to target
         finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done.
     ) => {
         Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc);
@@ -293,7 +294,7 @@ export class DocumentManager {
 
     focusViewsInPath = async (
         docView: DocumentView, //
-        options: DocFocusOptions,
+        options: FocusViewOptions,
         iterator: (docView: DocumentView) => Promise<{ viewSpec: Opt<Doc>; childDocView: Opt<DocumentView>; focused: boolean }>
     ) => {
         let contextView: DocumentView | undefined; // view containing context that contains target
@@ -313,7 +314,7 @@ export class DocumentManager {
     };
 
     @action
-    restoreDocView(viewSpec: Opt<Doc>, docView: DocumentView, options: DocFocusOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
+    restoreDocView(viewSpec: Opt<Doc>, docView: DocumentView, options: FocusViewOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
         if (viewSpec && docView) {
             //if (docView.ComponentView instanceof FormattedTextBox)
             //viewSpec !== docView.Document &&
@@ -337,7 +338,7 @@ export class DocumentManager {
         }
     }
 }
-export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) {
+export function DocFocusOrOpen(doc: Doc, options: FocusViewOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) {
     const func = () => {
         const cv = DocumentManager.Instance.getDocumentView(containingDoc);
         const dv = DocumentManager.Instance.getDocumentView(doc, cv);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 9ede18ed5..70c40c54e 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -13,6 +13,7 @@ import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
 import { SnappingManager } from './SnappingManager';
 import { UndoManager } from './UndoManager';
+import { DocData } from '../../fields/DocSymbols';
 const { default : { contextMenuZindex } } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
 
 export type dropActionType = 'embed' | 'copy' | 'move' | 'add' | 'same' | 'inSame' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove
@@ -222,7 +223,7 @@ export namespace DragManager {
                                   : docDragData.dropAction === 'add'
                                     ? d
                                     : docDragData.dropAction === 'proto'
-                                      ? Doc.GetProto(d)
+                                      ? d[DocData]
                                       : docDragData.dropAction === 'copy'
                                         ? (await Doc.MakeClone(d)).clone
                                         : d
@@ -250,9 +251,9 @@ export namespace DragManager {
     export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
         const finishDrag = (e: DragCompleteEvent) => {
             const bd = Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) });
-            params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields
+            params.map(p => Object.keys(vars).indexOf(p) !== -1 && (bd[DocData][p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields
             initialize?.(bd);
-            Doc.GetProto(bd)['onClick-paramFieldKeys'] = new List<string>(params);
+            bd[DocData]['onClick-paramFieldKeys'] = new List<string>(params);
             e.docDragData && (e.docDragData.droppedDocuments = [bd]);
             return e;
         };
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 2c371f28e..f62ec8f83 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -1,4 +1,5 @@
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
 import { ObjectField } from '../../fields/ObjectField';
 import { RichTextField } from '../../fields/RichTextField';
 import { listSpec } from '../../fields/Schema';
@@ -12,7 +13,7 @@ import { DragManager } from './DragManager';
 import { ScriptingGlobals } from './ScriptingGlobals';
 
 export function MakeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = undefined, templateField: string = '') {
-    if (templateField) Doc.GetProto(doc).title = templateField; /// the title determines which field is being templated
+    if (templateField) doc[DocData].title = templateField; /// the title determines which field is being templated
     doc.isTemplateDoc = makeTemplate(doc, first, rename);
     return doc;
 }
@@ -33,7 +34,7 @@ function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = und
     let any = false;
     docs.forEach(d => {
         if (!StrCast(d.title).startsWith('-')) {
-            any = Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)) || any;
+            any = Doc.MakeMetadataFieldTemplate(d, layoutDoc[DocData]) || any;
         } else if (d.type === DocumentType.COL || d.data instanceof RichTextField) {
             any = makeTemplate(d, false) || any;
         }
@@ -41,12 +42,12 @@ function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = und
     if (first) {
         if (!docs.length) {
             // bcz: feels hacky : if the root level document has items, it's not a field template
-            any = Doc.MakeMetadataFieldTemplate(doc, Doc.GetProto(layoutDoc)) || any;
+            any = Doc.MakeMetadataFieldTemplate(doc, layoutDoc[DocData]) || any;
         }
     }
     if (layoutDoc[fieldKey] instanceof RichTextField || layoutDoc[fieldKey] instanceof ImageField) {
         if (!StrCast(layoutDoc.title).startsWith('-')) {
-            any = Doc.MakeMetadataFieldTemplate(layoutDoc, Doc.GetProto(layoutDoc));
+            any = Doc.MakeMetadataFieldTemplate(layoutDoc, layoutDoc[DocData]);
         }
     }
     rename && (doc.title = rename);
diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts
index f46c2d431..c5f307f44 100644
--- a/src/client/util/HypothesisUtils.ts
+++ b/src/client/util/HypothesisUtils.ts
@@ -30,15 +30,15 @@ export namespace Hypothesis {
         if (currentDoc && Cast(currentDoc.data, WebField)?.url.href === uri) return currentDoc; // always check first whether the currently selected doc is the annotation's source, only use Search otherwise
 
         const results: Doc[] = [];
-        await SearchUtil.Search('web', true).then(
-            action(async (res: SearchUtil.DocSearchResult) => {
-                const docs = res.docs;
-                const filteredDocs = docs.filter(doc => doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data);
-                filteredDocs.forEach(doc => {
-                    uri === Cast(doc.data, WebField)?.url.href && results.push(doc); // TODO check visited sites history?
-                });
-            })
-        );
+        // await SearchUtil.Search('web', true).then(
+        //     action(async (res: SearchUtil.DocSearchResult) => {
+        //         const docs = res.docs;
+        //         const filteredDocs = docs.filter(doc => doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data);
+        //         filteredDocs.forEach(doc => {
+        //             uri === Cast(doc.data, WebField)?.url.href && results.push(doc); // TODO check visited sites history?
+        //         });
+        //     })
+        // );
 
         const onScreenResults = results.filter(doc => DocumentManager.Instance.getFirstDocumentView(doc));
         return onScreenResults.length ? onScreenResults[0] : results.length ? results[0] : undefined; // prioritize results that are currently on the screen
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 2df4d1ca8..20261859c 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -1,16 +1,17 @@
-import { action, observable, runInAction } from 'mobx';
+import { action, runInAction } from 'mobx';
 import { Doc, DocListCast, Field, FieldResult, Opt } from '../../fields/Doc';
 import { ScriptField } from '../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
 import { DocumentType } from '../documents/DocumentTypes';
-import { DocFocusOptions, OpenWhere } from '../views/nodes/DocumentView';
+import { OpenWhere } from '../views/nodes/DocumentView';
+import { FocusViewOptions } from '../views/nodes/FieldView';
 import { PresBox } from '../views/nodes/trails';
 import { DocumentManager } from './DocumentManager';
 import { LinkManager } from './LinkManager';
 import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
-import { UndoManager } from './UndoManager';
 import { SnappingManager } from './SnappingManager';
+import { UndoManager } from './UndoManager';
 /*
  * link doc:
  * - link_anchor_1: doc
@@ -72,7 +73,7 @@ export class LinkFollower {
             if (target) {
                 const doFollow = (canToggle?: boolean) => {
                     const toggleTarget = canToggle && BoolCast(sourceDoc.followLinkToggle);
-                    const options: DocFocusOptions = {
+                    const options: FocusViewOptions = {
                         playAudio: BoolCast(srcAnchor.followLinkAudio),
                         playMedia: BoolCast(srcAnchor.followLinkVideo),
                         toggleTarget,
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index ccb3c6b98..353f28a92 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,7 +1,7 @@
 import { action, makeObservable, observable, observe, runInAction } from 'mobx';
 import { computedFn } from 'mobx-utils';
 import { Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc';
-import { DirectLinks } from '../../fields/DocSymbols';
+import { DirectLinks, DocData } from '../../fields/DocSymbols';
 import { FieldLoader } from '../../fields/FieldLoader';
 import { List } from '../../fields/List';
 import { ProxyField } from '../../fields/Proxy';
@@ -58,8 +58,8 @@ export class LinkManager {
                                 link &&
                                     action(lAnchProtoProtos => {
                                         Doc.AddDocToList(Doc.UserDoc(), 'links', link);
-                                        lAnchs[0] && Doc.GetProto(lAnchs[0])[DirectLinks].add(link);
-                                        lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].add(link);
+                                        lAnchs[0] && lAnchs[0][DocData][DirectLinks].add(link);
+                                        lAnchs[1] && lAnchs[1][DocData][DirectLinks].add(link);
                                     })
                             )
                         )
@@ -74,8 +74,8 @@ export class LinkManager {
                         Promise.all(lAnchs.map(lAnch => PromiseValue(lAnch?.proto as Doc))).then((lAnchProtos: Opt<Doc>[]) =>
                             Promise.all(lAnchProtos.map(lAnchProto => PromiseValue(lAnchProto?.proto as Doc))).then(
                                 action(lAnchProtoProtos => {
-                                    link && lAnchs[0] && Doc.GetProto(lAnchs[0])[DirectLinks].delete(link);
-                                    link && lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].delete(link);
+                                    link && lAnchs[0] && lAnchs[0][DocData][DirectLinks].delete(link);
+                                    link && lAnchs[1] && lAnchs[1][DocData][DirectLinks].delete(link);
                                 })
                             )
                         )
@@ -162,7 +162,7 @@ export class LinkManager {
         return this.relatedLinker(anchor);
     } // finds all links that contain the given anchor
     public getAllDirectLinks(anchor?: Doc): Doc[] {
-        return anchor ? Array.from(Doc.GetProto(anchor)[DirectLinks]) : [];
+        return anchor ? Array.from(anchor[DirectLinks]) : [];
     } // finds all links that contain the given anchor
 
     relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] {
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index e51770c25..2cc64f415 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -1,10 +1,7 @@
-import * as rp from 'request-promise';
-import { DocServer } from '../DocServer';
 import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
-import { Utils } from '../../Utils';
-import { DocumentType } from '../documents/DocumentTypes';
 import { StrCast } from '../../fields/Types';
+import { DocumentType } from '../documents/DocumentTypes';
 
 export namespace SearchUtil {
     export type HighlightingResult = { [id: string]: { [key: string]: string[] } };
@@ -110,154 +107,4 @@ export namespace SearchUtil {
             depth++;
         }
     }
-    export interface IdSearchResult {
-        ids: string[];
-        lines: string[][];
-        numFound: number;
-        highlighting: HighlightingResult | undefined;
-    }
-
-    export interface DocSearchResult {
-        docs: Doc[];
-        lines: string[][];
-        numFound: number;
-        highlighting: HighlightingResult | undefined;
-    }
-
-    export interface SearchParams {
-        hl?: string;
-        'hl.fl'?: string;
-        start?: number;
-        rows?: number;
-        fq?: string;
-        sort?: string;
-        allowEmbeddings?: boolean;
-        onlyEmbeddings?: boolean;
-        facet?: string;
-        'facet.field'?: string;
-    }
-    export function Search(query: string, returnDocs: true, options?: SearchParams): Promise<DocSearchResult>;
-    export function Search(query: string, returnDocs: false, options?: SearchParams): Promise<IdSearchResult>;
-    export async function Search(query: string, returnDocs: boolean, options: SearchParams = {}) {
-        query = query || '*'; //If we just have a filter query, search for * as the query
-        const rpquery = Utils.prepend('/dashsearch');
-        let replacedQuery = query.replace(/type_t:([^ )])/g, (substring, arg) => `{!join from=id to=proto_i}*:* AND ${arg}`);
-        if (options.onlyEmbeddings) {
-            const header = query.match(/_[atnb]?:/) ? replacedQuery : 'DEFAULT:' + replacedQuery;
-            replacedQuery = `{!join from=id to=proto_i}* AND ${header}`;
-        }
-        //console.log("Q: " + replacedQuery + " fq: " + options.fq);
-        const gotten = await rp.get(rpquery, { qs: { ...options, q: replacedQuery } });
-        const result: IdSearchResult = gotten.startsWith('<') ? { ids: [], docs: [], numFound: 0, lines: [] } : JSON.parse(gotten);
-        if (!returnDocs) {
-            return result;
-        }
-
-        const { ids, highlighting } = result;
-
-        const txtresult =
-            query !== '*' &&
-            JSON.parse(
-                await rp.get(Utils.prepend('/textsearch'), {
-                    qs: { ...options, q: query.replace(/^[ \+\?\*\|]*/, '') }, // a leading '+' leads to a server crash since findInFiles doesn't handle regex failures
-                })
-            );
-
-        const fileids = txtresult ? txtresult.ids : [];
-        const newIds: string[] = [];
-        const newLines: string[][] = [];
-        // bcz: we stopped storing fileUpload id's, so this won't find anything
-        // if (fileids) {
-        //     await Promise.all(
-        //         fileids.map(async (tr: string, i: number) => {
-        //             const docQuery = 'fileUpload_t:' + tr.substr(0, 7); //If we just have a filter query, search for * as the query
-        //             const docResult = JSON.parse(await rp.get(Utils.prepend('/dashsearch'), { qs: { ...options, q: docQuery } }));
-        //             newIds.push(...docResult.ids);
-        //             newLines.push(...docResult.ids.map((dr: any) => txtresult.lines[i]));
-        //         })
-        //     );
-        // }
-
-        const theDocs: Doc[] = [];
-        const theLines: string[][] = [];
-        const textDocMap = await DocServer.GetRefFields(newIds);
-        const textDocs = newIds.map((id: string) => textDocMap[id]).map(doc => doc as Doc);
-        for (let i = 0; i < textDocs.length; i++) {
-            const testDoc = textDocs[i];
-            if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1) {
-                theDocs.push(Doc.GetProto(testDoc));
-                theLines.push(newLines[i].map(line => line.replace(query, query.toUpperCase())));
-            }
-        }
-
-        const docMap = await DocServer.GetRefFields(ids);
-        const docs = ids.map((id: string) => docMap[id]).map(doc => doc as Doc);
-        for (let i = 0; i < ids.length; i++) {
-            const testDoc = docs[i];
-            if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && (options.allowEmbeddings || testDoc.proto === undefined || theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1)) {
-                theDocs.push(testDoc);
-                theLines.push([]);
-            } else {
-                result.numFound--;
-            }
-        }
-
-        return { docs: theDocs, numFound: Math.max(0, result.numFound), highlighting, lines: theLines };
-    }
-
-    export async function GetEmbeddingsOfDocument(doc: Doc): Promise<Doc[]>;
-    export async function GetEmbeddingsOfDocument(doc: Doc, returnDocs: false): Promise<string[]>;
-    export async function GetEmbeddingsOfDocument(doc: Doc, returnDocs = true): Promise<Doc[] | string[]> {
-        const proto = Doc.GetProto(doc);
-        const protoId = proto[Id];
-        if (returnDocs) {
-            return (await Search('', returnDocs, { fq: `proto_i:"${protoId}"`, allowEmbeddings: true })).docs;
-        } else {
-            return (await Search('', returnDocs, { fq: `proto_i:"${protoId}"`, allowEmbeddings: true })).ids;
-        }
-        // return Search(`{!join from=id to=proto_i}id:${protoId}`, true);
-    }
-
-    export async function GetViewsOfDocument(doc: Doc): Promise<Doc[]> {
-        const results = await Search('', true, { fq: `proto_i:"${doc[Id]}"` });
-        return results.docs;
-    }
-
-    export async function GetContextsOfDocument(doc: Doc): Promise<{ contexts: Doc[]; embeddingContexts: Doc[] }> {
-        const docContexts = (await Search('', true, { fq: `data_l:"${doc[Id]}"` })).docs;
-        const embeddings = await GetEmbeddingsOfDocument(doc, false);
-        const embeddingContexts = await Promise.all(embeddings.map(doc => Search('', true, { fq: `data_l:"${doc}"` })));
-        const contexts = { contexts: docContexts, embeddingContexts: [] as Doc[] };
-        embeddingContexts.forEach(result => contexts.embeddingContexts.push(...result.docs));
-        return contexts;
-    }
-
-    export async function GetContextIdsOfDocument(doc: Doc): Promise<{ contexts: string[]; embeddingContexts: string[] }> {
-        const docContexts = (await Search('', false, { fq: `data_l:"${doc[Id]}"` })).ids;
-        const embeddings = await GetEmbeddingsOfDocument(doc, false);
-        const embeddingContexts = await Promise.all(embeddings.map(doc => Search('', false, { fq: `data_l:"${doc}"` })));
-        const contexts = { contexts: docContexts, embeddingContexts: [] as string[] };
-        embeddingContexts.forEach(result => contexts.embeddingContexts.push(...result.ids));
-        return contexts;
-    }
-
-    export async function GetAllDocs() {
-        const query = '*';
-        const response = await rp.get(Utils.prepend('/dashsearch'), {
-            qs: { start: 0, rows: 10000, q: query },
-        });
-        const result: IdSearchResult = JSON.parse(response);
-        const { ids, numFound, highlighting } = result;
-        const docMap = await DocServer.GetRefFields(ids);
-        const docs: Doc[] = [];
-        for (const id of ids) {
-            const field = docMap[id];
-            if (field instanceof Doc) {
-                docs.push(field);
-            }
-        }
-        return docs;
-        // const docs = ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc);
-        // return docs as Doc[];
-    }
 }
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 0233c4051..5bf9e5b00 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -5,13 +5,13 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { BsGoogle } from 'react-icons/bs';
 import { FaFillDrip, FaPalette } from 'react-icons/fa';
+import { Utils, addStyleSheet, addStyleSheetRule } from '../../Utils';
 import { Doc, Opt } from '../../fields/Doc';
 import { DashVersion } from '../../fields/DocSymbols';
 import { BoolCast, Cast, NumCast, StrCast } from '../../fields/Types';
-import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils';
-import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { DocServer } from '../DocServer';
 import { Networking } from '../Network';
+import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { GestureOverlay } from '../views/GestureOverlay';
 import { MainViewModal } from '../views/MainViewModal';
 import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox';
@@ -47,7 +47,6 @@ export class SettingsManager extends React.Component<{}> {
     @observable activeTab = 'Accounts';
 
     @observable public propertiesWidth: number = 0;
-    @observable public headerBarHeight: number = 0;
 
     constructor(props: {}) {
         super(props);
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 523721b84..472d419fc 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -5,7 +5,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { FaPlus } from 'react-icons/fa';
 import { Doc, DocListCast } from '../../fields/Doc';
-import { AclPrivate, DocAcl } from '../../fields/DocSymbols';
+import { AclPrivate, DocAcl, DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { PrefetchProxy } from '../../fields/Proxy';
@@ -64,7 +64,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
 
     getDashboards = (whichGroup: DashboardGroup) => {
         if (whichGroup === DashboardGroup.MyDashboards) {
-            return DocListCast(Doc.MyDashboards.data).filter(dashboard => Doc.GetProto(dashboard).author === Doc.CurrentUserEmail);
+            return DocListCast(Doc.MyDashboards.data).filter(dashboard => dashboard[DocData].author === Doc.CurrentUserEmail);
         }
         return DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc.dockingConfig);
     };
@@ -171,7 +171,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
                                               }
                                           />
                                           <div className="info">
-                                              <EditableText type={Type.PRIM} color={color} val={StrCast(dashboard.title)} setVal={val => (Doc.GetProto(dashboard).title = val)} />
+                                              <EditableText type={Type.PRIM} color={color} val={StrCast(dashboard.title)} setVal={val => (dashboard[DocData].title = val)} />
                                               {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ? <div>unviewed</div> : <div></div>}
                                               <div
                                                   className="more"
@@ -353,7 +353,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
                 },
             ],
         };
-        if (dashboard.dockingConfig && dashboard.dockingConfig !== Doc.GetProto(dashboard).dockingConfig) dashboard.dockingConfig = JSON.stringify(reset);
+        if (dashboard.dockingConfig && dashboard.dockingConfig !== dashboard[DocData].dockingConfig) dashboard.dockingConfig = JSON.stringify(reset);
         else Doc.SetInPlace(dashboard, 'dockingConfig', JSON.stringify(reset), true);
         return reset;
     };
@@ -391,9 +391,9 @@ export class DashboardView extends ObservableReactComponent<{}> {
         Doc.ActivePresentation = undefined;
     };
 
-    public static SetupDashboardCalendars(dashboardDoc: Doc){
+    public static SetupDashboardCalendars(dashboardDoc: Doc) {
         // this section is creating the button document itself  ===  myTrails = new Button
-        
+
         //  create a a list of calendars (as a CalendarCollectionDocument) and store it on the new dashboard
         const reqdOpts: DocumentOptions = {
             title: 'My Calendars',
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 6aba4a042..0e5a4f013 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -2,22 +2,62 @@ import { action, computed, makeObservable, observable } from 'mobx';
 import * as React from 'react';
 import { returnFalse } from '../../Utils';
 import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols';
 import { List } from '../../fields/List';
 import { GetEffectiveAcl, inheritParentAcls } from '../../fields/util';
 import { DocumentType } from '../documents/DocumentTypes';
 import { DocUtils } from '../documents/Documents';
 import { DocumentManager } from '../util/DocumentManager';
-import { Transform } from '../util/Transform';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import { CollectionFreeFormView } from './collections/collectionFreeForm';
-import { DocumentView } from './nodes/DocumentView';
+import { FieldViewProps, FocusViewOptions } from './nodes/FieldView';
+import { DocumentView, OpenWhere } from './nodes/DocumentView';
+import { PinProps } from './nodes/trails';
+import { RefField } from '../../fields/RefField';
 
 /**
- * DocComponent returns a generic React base class used by Doc views (not the 'Box' views that render the contents of Doc views)
- *    (e.g.,CollectionFreeFormDocumentView, DocumentViewInternal)
- *
+ * Shared interface among all viewBox'es (ie, react classes that render the contents of a Doc)
+ * Many of these methods only make sense for specific viewBox'es, but they should be written to
+ * be as general as possible
+ */
+export interface ViewBoxInterface {
+    fieldKey?: string;
+    annotationKey?: string;
+    updateIcon?: () => void; // updates the icon representation of the document
+    getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
+    restoreView?: (viewSpec: Doc) => boolean;
+    scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: FocusViewOptions) => Opt<number>; // returns the duration of the focus
+    brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number) => void; // highlight a region of a view (used by freeforms)
+    getView?: (doc: Doc, options: FocusViewOptions) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined
+    addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox
+    addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections)
+    select?: (ctrlKey: boolean, shiftKey: boolean) => void;
+    focus?: (textAnchor: Doc, options: FocusViewOptions) => Opt<number>;
+    isAnyChildContentActive?: () => boolean; // is any child content of the document active
+    onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected
+    getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
+    setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
+    playFrom?: (time: number, endTime?: number) => void;
+    Pause?: () => void; // pause a media document (eg, audio/video)
+    IsPlaying?: () => boolean; // is a media document playing
+    TogglePause?: (keep?: boolean) => void; // toggle media document playing state
+    setFocus?: () => void; // sets input focus to the componentView
+    setData?: (data: Field | Promise<RefField | undefined>) => boolean;
+    componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
+    dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void;
+    incrementalRendering?: () => void;
+    infoUI?: () => JSX.Element | null;
+    screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>;
+    ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
+    ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
+    snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number };
+    search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
+}
+/**
+ * DocComponent returns a React base class used by Doc views with accessors for unpacking he Document,layoutDoc, and dataDoc's
+ *  (note: this should not be used for the 'Box' views that render the contents of Doc views)
+ *  Example derived views: CollectionFreeFormDocumentView, DocumentView, DocumentViewInternal)
  * */
 export interface DocComponentProps {
     Document: Doc;
@@ -26,7 +66,7 @@ export interface DocComponentProps {
 }
 export function DocComponent<P extends DocComponentProps>() {
     class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
-        constructor(props: any) {
+        constructor(props: P) {
             super(props);
             makeObservable(this);
         }
@@ -41,28 +81,25 @@ export function DocComponent<P extends DocComponentProps>() {
         }
         // This is the data part of a document -- ie, the data that is constant across all views of the document
         @computed get dataDoc() {
-            return this._props.Document[DocData];
+            return this.Document[DocData];
         }
     }
     return Component;
 }
 
 /**
- *  ViewBoxBaseComponent  -  base class for non-annotatable views that render the interior contents of a DocumentView
- *                           (e.g. InkingStroke, ColorBox)
+ *   base class for non-annotatable views that render the interior contents of a DocumentView.
+ *   this unpacks the Document/layout/data docs as well as the fieldKey being rendered,
+ *   and provides accessors for DocumentView and ScreenToLocalBoxXf
+ *   Example views include: InkingStroke, FontIconBox, EquationBox, etc
  */
-export interface ViewBoxBaseProps {
-    Document: Doc;
-    TemplateDataDocument?: Doc;
-    DocumentView?: () => DocumentView;
-    fieldKey: string;
-    isSelected: () => boolean;
-    isContentActive: () => boolean | undefined;
-    ScreenToLocalTransform: () => Transform;
-    renderDepth: number;
-}
-export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
+export function ViewBoxBaseComponent<P extends FieldViewProps>() {
     class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
+        constructor(props: P) {
+            super(props);
+            makeObservable(this);
+        }
+
         ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform();
 
         get DocumentView() {
@@ -89,36 +126,27 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
 }
 
 /**
- * DocAnnotatableComponent - base class for annotatable views that render the interior contents of a DocumentView
- *                       (e.g., PdfBox, ImageBox)
- * These views should be interactive (respond to pointerEvents) when their conatainer DocumentView is selected
+ * base class for annotatable views that render the interior contents of a DocumentView
+ * This does what ViewBoxBaseComponent does and additionally provides accessor for the
+ * field key where annotations are stored as well as add/move/remove methods for handing
+ * annotations.
+ * This also provides methods to determine when the contents should be interactive
+ * (respond to pointerEvents) such as when the DocumentView container is selected or a
+ * peer child of the container is selected
+ * Example views include: PDFBox, ImageBox, MapBox, etc
  */
-export interface ViewBoxAnnotatableProps {
-    Document: Doc;
-    TemplateDataDocument?: Doc;
-    DocumentView?: () => DocumentView;
-    fieldKey: string;
-    filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
-    isContentActive: () => boolean | undefined;
-    select: (isCtrlPressed: boolean) => void;
-    whenChildContentsActiveChanged: (isActive: boolean) => void;
-    ScreenToLocalTransform: () => Transform;
-    isSelected: () => boolean;
-    renderDepth: number;
-    isAnnotationOverlay?: boolean;
-}
-export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>() {
+export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
     class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
-        constructor(props: any) {
+        @observable _annotationKeySuffix = () => 'annotations';
+        @observable _isAnyChildContentActive = false;
+
+        constructor(props: P) {
             super(props);
             makeObservable(this);
         }
 
         ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform();
 
-        @observable _annotationKeySuffix = () => 'annotations';
-        @observable _isAnyChildContentActive = false;
-
         get DocumentView() {
             return this._props.DocumentView;
         }
@@ -139,9 +167,6 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
         @computed get fieldKey() {
             return this._props.fieldKey;
         }
-
-        isAnyChildContentActive = () => this._isAnyChildContentActive;
-
         @computed public get annotationKey() {
             return this.fieldKey + (this._annotationKeySuffix() ? '_' + this._annotationKeySuffix() : '');
         }
@@ -162,7 +187,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
                 toRemove.forEach(doc => {
                     leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
                     Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
-                    Doc.RemoveDocFromList(Doc.GetProto(doc), 'proto_embeddings', doc);
+                    Doc.RemoveDocFromList(doc[DocData], 'proto_embeddings', doc);
                     doc.embedContainer = undefined;
                     if (recent) {
                         doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true);
@@ -210,8 +235,8 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
                 if ([AclAugment, AclEdit, AclAdmin].includes(effectiveAcl)) {
                     added.forEach(doc => {
                         doc._dragOnlyWithinContainer = undefined;
-                        if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.Document;
-                        else Doc.GetProto(doc).annotationOn = undefined;
+                        if (annotationKey ?? this._annotationKeySuffix()) doc[DocData].annotationOn = this.Document;
+                        else doc[DocData].annotationOn = undefined;
                         Doc.SetContainer(doc, this.Document);
                         inheritParentAcls(targetDataDoc, doc, true);
                     });
@@ -225,6 +250,8 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
             return true;
         };
 
+        isAnyChildContentActive = () => this._isAnyChildContentActive;
+
         whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
     }
     return Component;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 8db972ef0..5ef62b2c5 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -8,7 +8,7 @@ import { FaUndo } from 'react-icons/fa';
 import { Utils, emptyFunction, lightOrDark, numberValue, returnFalse, setupMoveUpEvents } from '../../Utils';
 import { DateField } from '../../fields/DateField';
 import { Doc, DocListCast, Field, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit } from '../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit, DocData } from '../../fields/DocSymbols';
 import { InkField } from '../../fields/InkField';
 import { RichTextField } from '../../fields/RichTextField';
 import { ScriptField } from '../../fields/ScriptField';
@@ -101,7 +101,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             { x: 0, y: 0, r: 0, b: 0 }
             : SelectionManager.Views
                 .filter(dv => dv._props.renderDepth > 0)
-                .map(dv => dv.getBounds())
+                .map(dv => dv.getBounds)
                 .reduce((bounds, rect) => !rect ? bounds
                             : { x: Math.min(rect.left, bounds.x),
                                 y: Math.min(rect.top, bounds.y),
@@ -203,7 +203,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const containers = new Set<Doc | undefined>();
         SelectionManager.Views.forEach(v => containers.add(DocCast(v.Document.embedContainer)));
         if (containers.size > 1) return false;
-        const { left, top } = dragDocView.getBounds() || { left: 0, top: 0 };
+        const { left, top } = dragDocView.getBounds || { left: 0, top: 0 };
         const dragData = new DragManager.DocumentDragData(
             SelectionManager.Views.map(dv => dv.Document),
             dragDocView._props.dropAction
@@ -541,17 +541,17 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             }
 
             if (['right', 'left'].includes(opts.dragHdl) && modifyNativeDim && Doc.NativeWidth(doc)) {
-                const setData = Doc.NativeWidth(Doc.GetProto(doc)) === doc.nativeWidth;
+                const setData = Doc.NativeWidth(doc[DocData]) === doc.nativeWidth;
                 doc.nativeWidth = scale.x * Doc.NativeWidth(doc);
-                if (setData) Doc.SetNativeWidth(Doc.GetProto(doc), NumCast(doc.nativeWidth));
+                if (setData) Doc.SetNativeWidth(doc[DocData], NumCast(doc.nativeWidth));
                 if (doc.layout_reflowVertical && !NumCast(doc.nativeHeight)) {
                     doc._nativeHeight = (initHeight / initWidth) * nwidth; // initializes the nativeHeight for a PDF
                 }
             }
             if (['bottom', 'top'].includes(opts.dragHdl) && modifyNativeDim && Doc.NativeHeight(doc)) {
-                const setData = Doc.NativeHeight(Doc.GetProto(doc)) === doc.nativeHeight;
+                const setData = Doc.NativeHeight(doc[DocData]) === doc.nativeHeight;
                 doc._nativeHeight = scale.y * Doc.NativeHeight(doc);
-                if (setData) Doc.SetNativeHeight(Doc.GetProto(doc), NumCast(doc._nativeHeight));
+                if (setData) Doc.SetNativeHeight(doc[DocData], NumCast(doc._nativeHeight));
             }
 
             doc._width = Math.max(1, NumCast(doc._width) * scale.x);
@@ -601,7 +601,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         this._inkDragDocs
             .map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
             .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
-                Doc.GetProto(doc).data = new InkField(inkPts.map(
+                doc[DocData].data = new InkField(inkPts.map(
                     (ipt) => ({// (new x — oldx) + newWidth * (oldxpoint /oldWidth)
                         X: NumCast(doc.x) - x + (NumCast(doc.width) * ipt.X) / width,
                         Y: NumCast(doc.y) - y + (NumCast(doc.height) * ipt.Y) / height,
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 11b05352d..92644d3c5 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -34,21 +34,19 @@ import { InteractionUtils } from '../util/InteractionUtils';
 import { SnappingManager } from '../util/SnappingManager';
 import { UndoManager } from '../util/UndoManager';
 import { ContextMenu } from './ContextMenu';
-import { ViewBoxBaseComponent, ViewBoxBaseProps } from './DocComponent';
+import { ViewBoxBaseComponent, ViewBoxInterface } from './DocComponent';
 import { Colors } from './global/globalEnums';
 import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
 import './InkStroke.scss';
 import { InkStrokeProperties } from './InkStrokeProperties';
 import { InkTangentHandles } from './InkTangentHandles';
-import { DocComponentView } from './nodes/DocumentView';
 import { FieldView, FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { PinProps, PresBox } from './nodes/trails';
 import { StyleProp } from './StyleProvider';
-import { Transform } from '../util/Transform';
 const { default: { INK_MASK_SIZE } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
 @observer
-export class InkingStroke extends ViewBoxBaseComponent<ViewBoxBaseProps & FieldViewProps>() {
+export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() implements ViewBoxInterface {
     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);
@@ -64,7 +62,7 @@ export class InkingStroke extends ViewBoxBaseComponent<ViewBoxBaseProps & FieldV
     @observable _nearestScrPt?: { X: number; Y: number }; // nearst screen point on the ink stroke ""
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         this._disposers.selfDisper = reaction(
             () => this._props.isSelected(), // react to stroke being deselected by turning off ink handles
             selected => !selected && (InkStrokeProperties.Instance._controlButton = false)
@@ -332,8 +330,8 @@ export class InkingStroke extends ViewBoxBaseComponent<ViewBoxBaseProps & FieldV
         );
     };
 
-    _subContentView: DocComponentView | undefined;
-    setSubContentView = (doc: DocComponentView) => (this._subContentView = doc);
+    _subContentView: ViewBoxInterface | undefined;
+    setSubContentView = (doc: ViewBoxInterface) => (this._subContentView = doc);
     @computed get fillColor() {
         const isInkMask = BoolCast(this.layoutDoc.stroke_isInkMask);
         return isInkMask ? DashColor(StrCast(this.layoutDoc.fillColor, 'transparent')).blacken(0).rgb().toString() : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FillColor) ?? 'transparent';
@@ -462,7 +460,7 @@ export class InkingStroke extends ViewBoxBaseComponent<ViewBoxBaseProps & FieldV
                         <FormattedTextBox
                             {...this._props}
                             setHeight={undefined}
-                            setContentView={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
+                            setContentViewBox={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
                             yPadding={10}
                             xPadding={10}
                             fieldKey="text"
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 3b86d24a6..530fd3121 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -267,7 +267,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
                             addDocTab={this.AddDocTab}
                             pinToPres={TabDocView.PinDoc}
                             bringToFront={emptyFunction}
-                            onBrowseClick={DocumentView.exploreMode}
+                            onBrowseClickScript={DocumentView.exploreMode}
                             focus={emptyFunction}
                         />
                     </GestureOverlay>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d700ea020..9061e98d4 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -15,6 +15,7 @@ import { DocServer } from '../DocServer';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { Docs } from '../documents/Documents';
+import { CalendarManager } from '../util/CalendarManager';
 import { CaptureManager } from '../util/CaptureManager';
 import { DocumentManager } from '../util/DocumentManager';
 import { DragManager } from '../util/DragManager';
@@ -31,7 +32,6 @@ import { SnappingManager } from '../util/SnappingManager';
 import { Transform } from '../util/Transform';
 import { ReportManager } from '../util/reportManager/ReportManager';
 import { ComponentDecorations } from './ComponentDecorations';
-import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp';
 import { ContextMenu } from './ContextMenu';
 import { DashboardView } from './DashboardView';
 import { DictationOverlay } from './DictationOverlay';
@@ -55,11 +55,13 @@ import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOpti
 import { CollectionLinearView } from './collections/collectionLinear';
 import { LinkMenu } from './linking/LinkMenu';
 import { AudioBox } from './nodes/AudioBox';
+import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp';
 import { DocButtonState } from './nodes/DocumentLinksButton';
 import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from './nodes/DocumentView';
 import { ImageBox } from './nodes/ImageBox';
 import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
 import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview';
+import { DirectionsAnchorMenu } from './nodes/MapBox/DirectionsAnchorMenu';
 import { MapAnchorMenu } from './nodes/MapBox/MapAnchorMenu';
 import { TaskCompletionBox } from './nodes/TaskCompletedBox';
 import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
@@ -70,9 +72,8 @@ import { PresBox } from './nodes/trails';
 import { AnchorMenu } from './pdf/AnchorMenu';
 import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
 import { TopBar } from './topbar/TopBar';
+import { DocData } from '../../fields/DocSymbols';
 const { default: { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
-import { DirectionsAnchorMenu } from './nodes/MapBox/DirectionsAnchorMenu';
-import { CalendarManager } from '../util/CalendarManager';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
@@ -130,9 +131,19 @@ export class MainView extends ObservableReactComponent<{}> {
     @computed public get mainFreeform(): Opt<Doc> {
         return (docs => (docs?.length > 1 ? docs[1] : undefined))(DocListCast(this.mainContainer!.data));
     }
+    @observable public headerBarHeight: number = 0;
+    headerBarHeightFunc = () => this.headerBarHeight;
 
+    @action
+    toggleTopBar = () => {
+        if (this.headerBarHeight > 0) {
+            this.headerBarHeight = 0;
+        } else {
+            this.headerBarHeight = 60;
+        }
+    };
     headerBarDocWidth = () => this.mainDocViewWidth();
-    headerBarDocHeight = () => (this._hideUI ? 0 : SettingsManager.Instance?.headerBarHeight ?? 0);
+    headerBarDocHeight = () => (this._hideUI ? 0 : this.headerBarHeight ?? 0);
     topMenuHeight = () => (this._hideUI ? 0 : 35);
     topMenuWidth = returnZero; // value is ignored ...
     leftMenuWidth = () => (this._hideUI ? 0 : Number(LEFT_MENU_WIDTH.replace('px', '')));
@@ -630,9 +641,11 @@ export class MainView extends ObservableReactComponent<{}> {
         );
     }
     @computed get mainDocView() {
+        const headerBar = this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView;
+        console.log('Header = ' + this._hideUI + ' ' + this.headerBarDocHeight?.() + ' ' + headerBar);
         return (
             <>
-                {this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView}
+                {headerBar}
                 <DocumentView
                     key="main"
                     Document={this.mainContainer!}
@@ -783,7 +796,7 @@ export class MainView extends ObservableReactComponent<{}> {
 
     @action
     selectMenu = (button: Doc) => {
-        const title = StrCast(Doc.GetProto(button).title);
+        const title = StrCast(button[DocData].title);
         const willOpen = !this._leftMenuFlyoutWidth || this._panelContent !== title;
         this.closeFlyout();
         if (willOpen) {
@@ -998,7 +1011,7 @@ export class MainView extends ObservableReactComponent<{}> {
                 <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfSidebarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
                 <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
                 {this._hideUI ? null : <TopBar />}
-                {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
+                <LinkDescriptionPopup />
                 {DocButtonState.Instance.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocButtonState.Instance.LinkEditorDocView = undefined))} docView={DocButtonState.Instance.LinkEditorDocView} /> : null}
                 {LinkInfo.Instance?.LinkInfo ? <LinkDocPreview {...LinkInfo.Instance.LinkInfo} /> : null}
 
@@ -1008,7 +1021,7 @@ export class MainView extends ObservableReactComponent<{}> {
                         default:
                         case 'dashboard': return (<>
                                         <div key="dashdiv" style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 2001 }}>
-                                            <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
+                                            <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} toggleTopBar={this.toggleTopBar} topBarHeight={this.headerBarHeightFunc}/>
                                         </div>
                                         {this.mainDashboardArea}
                                     </> );
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index d0516336c..a4303c3aa 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -118,7 +118,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
             })
         );
 
-        const textRegionAnnoProto = Doc.GetProto(textRegionAnno);
+        const textRegionAnnoProto = textRegionAnno[DocData];
         textRegionAnnoProto.y = Math.max(minY, 0);
         textRegionAnnoProto.x = Math.max(minX, 0);
         textRegionAnnoProto.height = Math.max(maxY, 0) - Math.max(minY, 0);
@@ -226,9 +226,10 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
                   DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                       dragComplete: e => {
                           if (!e.aborted && e.linkDocument) {
-                              Doc.GetProto(e.linkDocument).link_relationship = 'cropped image';
-                              Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.Document.title;
-                              Doc.GetProto(e.linkDocument).link_displayLine = false;
+                              const linkDocData = e.linkDocument[DocData];
+                              linkDocData.link_relationship = 'cropped image';
+                              linkDocData.title = 'crop: ' + this.props.Document.title;
+                              linkDocData.link_displayLine = false;
                           }
                       },
                   });
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 13057ffbf..952c8f0b4 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -37,7 +37,8 @@ import { PropertiesDocContextSelector } from './PropertiesDocContextSelector';
 import { PropertiesSection } from './PropertiesSection';
 import './PropertiesView.scss';
 import { DefaultStyleProvider } from './StyleProvider';
-import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView';
+import { DocumentView, OpenWhere } from './nodes/DocumentView';
+import { StyleProviderFuncType } from './nodes/FieldView';
 import { KeyValueBox } from './nodes/KeyValueBox';
 import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
 const _global = (window /* browser */ || global) /* node */ as any;
@@ -45,7 +46,7 @@ const _global = (window /* browser */ || global) /* node */ as any;
 interface PropertiesViewProps {
     width: number;
     height: number;
-    styleProvider?: StyleProviderFunc;
+    styleProvider?: StyleProviderFuncType;
     addDocTab: (doc: Doc, where: OpenWhere) => boolean;
 }
 
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index 086f40e96..623201ed1 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -11,6 +11,7 @@ import { EditableView } from './EditableView';
 import { DocumentIconContainer } from './nodes/DocumentIcon';
 import { OverlayView } from './OverlayView';
 import './ScriptBox.scss';
+import { DocData } from '../../fields/DocSymbols';
 
 export interface ScriptBoxProps {
     onSave: (text: string, onError: (error: string) => void) => void;
@@ -102,7 +103,7 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
                 onCancel={overlayDisposer}
                 onSave={(text, onError) => {
                     if (!text) {
-                        Doc.GetProto(doc)[fieldKey] = undefined;
+                        doc[DocData][fieldKey] = undefined;
                     } else {
                         const script = CompileScript(text, {
                             params: { this: Doc.name, ...contextParams },
@@ -125,7 +126,7 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
                         div.innerHTML = 'button';
                         params.length && DragManager.StartButtonDrag([div], text, doc.title + '-instance', {}, params, (button: Doc) => {}, clientX, clientY);
 
-                        Doc.GetProto(doc)[fieldKey] = new ScriptField(script);
+                        doc[DocData][fieldKey] = new ScriptField(script);
                         overlayDisposer();
                     }
                 }}
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 0f4a4260c..1e9272e93 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -18,6 +18,7 @@ import { StyleProp } from './StyleProvider';
 import { CollectionStackingView } from './collections/CollectionStackingView';
 import { FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { DocData } from '../../fields/DocSymbols';
 
 interface ExtraProps {
     fieldKey: string;
@@ -90,7 +91,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
             .map(data => {
                 const key = data.split(':')[0];
                 const val = Field.Copy(this.allMetadata.get(key));
-                Doc.GetProto(target)[key] = val;
+                target[DocData][key] = val;
                 return {
                     type: 'dashField',
                     attrs: { fieldKey: key, docId: '', hideKey: false, editable: true },
@@ -98,7 +99,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
                 };
             });
 
-        if (!anchor.text) Doc.GetProto(anchor).text = '-selection-';
+        if (!anchor.text) anchor[DocData].text = '-selection-';
         const textLines: any = [
             {
                 type: 'paragraph',
@@ -133,7 +134,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
         };
         if (taggedContent.length) textLines.push(metadatatext);
         if (textLines.length) {
-            Doc.GetProto(target).text = new RichTextField(
+            target[DocData].text = new RichTextField(
                 JSON.stringify({
                     doc: {
                         type: 'doc',
@@ -234,7 +235,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
                 <div style={{ width: '100%', height: `calc(100% - 38px)`, position: 'relative' }}>
                     <CollectionStackingView
                         {...this._props}
-                        setContentView={emptyFunction}
+                        setContentViewBox={emptyFunction}
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
                         ref={this._stackRef}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 1cb5ff249..2aba439c8 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -24,6 +24,7 @@ import { FieldViewProps } from './nodes/FieldView';
 import { KeyValueBox } from './nodes/KeyValueBox';
 import { PropertiesView } from './PropertiesView';
 import './StyleProvider.scss';
+import { DocumentViewProps } from './nodes/DocumentView';
 
 export enum StyleProp {
     TreeViewIcon = 'treeView_Icon',
@@ -65,13 +66,12 @@ export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
 
 // a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab
 //
-export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any {
+export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & DocumentViewProps>, property: string): any {
     const remoteDocHeader = 'author;author_date;noMargin';
     const isCaption = property.includes(':caption');
     const isAnchor = property.includes(':anchor');
     const isNonTransparent = property.includes(':nonTransparent');
     const isNonTransparentLevel = isNonTransparent ? Number(property.replace(/.*:nonTransparent([0-9]+).*/, '$1')) : 0; // property.includes(':nonTransparent');
-    const isContent = property.includes(':content');
     const isAnnotated = property.includes(':annotated');
     const isInk = () => doc?._layout_isSvg && !props?.LayoutTemplateString;
     const isOpen = property.includes(':open');
@@ -125,7 +125,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>,
         case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?._text_fontFamily, StrCast(Doc.UserDoc().fontFamily)));
         case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(doc?._text_fontWeight, StrCast(Doc.UserDoc().fontWeight)));
         case StyleProp.FillColor:  return StrCast(doc?._fillColor, StrCast(doc?.fillColor, 'transparent'));
-        case StyleProp.ShowCaption:return doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
+        case StyleProp.ShowCaption:return props?.hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
         case StyleProp.TitleHeight:return (props?.ScreenToLocalTransform().Scale ?? 1) * NumCast(Doc.UserDoc().headerHeight,30);
         case StyleProp.ShowTitle:
             return (
@@ -244,7 +244,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>,
                         ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (props?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
                         : lockedPosition()
                         ? undefined // if it's a background & has a cluster color, make the shadow spread really big
-                        : StrCast(doc.layout_fieldKey).includes('_inline')  // if doc is an inline document in a text box
+                        : fieldKey.includes('_inline')  // if doc is an inline document in a text box
                         ? `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0vw 0vw 0.1vw')}`
                         : DocCast(doc.embedContainer)?.type=== DocumentType.RTF // if doc is embedded in a text document (but not an inline)
                         ? `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}`
@@ -316,11 +316,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>,
             };
             const audio = () => {
                 const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, 'stopped');
-                const audioAnnosCount = (doc: Doc) => StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations']).length;
+                const audioAnnosCount = (doc: Doc) => StrListCast(doc[fieldKey + 'audioAnnotations']).length;
                 if (!doc || props?.renderDepth === -1 || !audioAnnosCount(doc)) return null;
                 const audioIconColors: { [key: string]: string } = { recording: 'red', playing: 'green', stopped: 'blue' };
                 return (
-                    <Tooltip title={<div>{StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations_text']).lastElement()}</div>}>
+                    <Tooltip title={<div>{StrListCast(doc[fieldKey + 'audioAnnotations_text']).lastElement()}</div>}>
                         <div className="styleProvider-audio" onPointerDown={() => DocumentManager.Instance.getFirstDocumentView(doc)?.playAnnotation()}>
                             <FontAwesomeIcon className="documentView-audioFont" style={{ color: audioIconColors[audioAnnoState(doc)] }} icon={'file-audio'} size="sm" />
                         </div>
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index e5154efcb..ba7120b93 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -11,9 +11,10 @@ import { ScriptingGlobals } from '../util/ScriptingGlobals';
 import { Transform } from '../util/Transform';
 import { undoBatch } from '../util/UndoManager';
 import { CollectionTreeView } from './collections/CollectionTreeView';
-import { DocumentView } from './nodes/DocumentView';
+import { DocumentView, returnEmptyDocViewList } from './nodes/DocumentView';
 import { DefaultStyleProvider } from './StyleProvider';
 import './TemplateMenu.scss';
+import { DocData } from '../../fields/DocSymbols';
 
 @observer
 class TemplateToggle extends React.Component<{ template: string; checked: boolean; toggle: (event: React.ChangeEvent<HTMLInputElement>, template: string) => void }> {
@@ -79,7 +80,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     };
     componentDidMount() {
         !this._addedKeys && (this._addedKeys = new ObservableSet());
-        [...Array.from(Object.keys(Doc.GetProto(this.props.docViews[0].Document))), ...Array.from(Object.keys(this.props.docViews[0].Document))]
+        [...Array.from(Object.keys(this.props.docViews[0].Document[DocData])), ...Array.from(Object.keys(this.props.docViews[0].Document))]
             .filter(key => key.startsWith('layout_'))
             .map(key => runInAction(() => this._addedKeys.add(key.replace('layout_', ''))));
     }
@@ -113,6 +114,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 {templateMenu}
                 <CollectionTreeView
                     Document={Doc.MyTemplates}
+                    docViewPath={returnEmptyDocViewList}
                     styleProvider={DefaultStyleProvider}
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx
index 4c0a917f5..cbcc980a9 100644
--- a/src/client/views/collections/CollectionCalendarView.tsx
+++ b/src/client/views/collections/CollectionCalendarView.tsx
@@ -62,7 +62,7 @@ export class CollectionCalendarView extends CollectionSubView() {
     screenToLocalTransform = () =>
         this._props
             .ScreenToLocalTransform()
-            .translate(Doc.NativeWidth(this._props.Document), 0)
+            .translate(Doc.NativeWidth(this.Document), 0)
             .scale(this._props.NativeDimScaling?.() || 1);
 
     get calendarsKey() {
@@ -74,7 +74,7 @@ export class CollectionCalendarView extends CollectionSubView() {
             <div className="collectionCalendarView">
                 <CollectionStackingView
                     {...this._props}
-                    setContentView={emptyFunction}
+                    setContentViewBox={emptyFunction}
                     ref={this._stackRef}
                     PanelHeight={this.panelHeight}
                     PanelWidth={this._props.PanelWidth}
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 8e072e235..2d2cb4860 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -10,7 +10,8 @@ import { DocumentType } from '../../documents/DocumentTypes';
 import { DragManager } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { StyleProp } from '../StyleProvider';
-import { DocFocusOptions, DocumentView } from '../nodes/DocumentView';
+import { DocumentView } from '../nodes/DocumentView';
+import { FocusViewOptions } from '../nodes/FieldView';
 import './CollectionCarousel3DView.scss';
 import { CollectionSubView } from './CollectionSubView';
 const { default: { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
@@ -49,7 +50,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
             .translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, -((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2)
             .scale(1 / this.centerScale);
 
-    focus = (anchor: Doc, options: DocFocusOptions) => {
+    focus = (anchor: Doc, options: FocusViewOptions) => {
         const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
         if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return;
         options.didMove = true;
@@ -70,7 +71,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
                     NativeWidth={returnZero}
                     NativeHeight={returnZero}
                     layout_fitWidth={undefined}
-                    onDoubleClick={this.onChildDoubleClick}
+                    onDoubleClickScript={this.onChildDoubleClick}
                     renderDepth={this._props.renderDepth + 1}
                     LayoutTemplate={this._props.childLayoutTemplate}
                     LayoutTemplateString={this._props.childLayoutString}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 208fc45b5..79f5baabe 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -66,9 +66,9 @@ export class CollectionCarouselView extends CollectionSubView() {
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
                         layout_fitWidth={undefined}
-                        setContentView={undefined}
-                        onDoubleClick={this.onContentDoubleClick}
-                        onClick={this.onContentClick}
+                        setContentViewBox={undefined}
+                        onDoubleClickScript={this.onContentDoubleClick}
+                        onClickScript={this.onContentClick}
                         isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive}
                         isContentActive={this._props.childContentsActive ?? this._props.isContentActive() === false ? returnFalse : emptyFunction}
                         hideCaptions={!!carouselShowsCaptions} // hide captions if the carousel is configured to show the captions
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 1f867fc44..87973fd81 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
 import * as GoldenLayout from '../../../client/goldenLayout';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
-import { AclAdmin, AclEdit } from '../../../fields/DocSymbols';
+import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -277,8 +277,8 @@ export class CollectionDockingView extends CollectionSubView() {
     }
     setupGoldenLayout = async () => {
         if (this._unmounting) return;
-        //const config = StrCast(this._props.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this._props.Document)));
-        const config = StrCast(this._props.Document.dockingConfig);
+        //const config = StrCast(this.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.Document)));
+        const config = StrCast(this.Document.dockingConfig);
         if (config) {
             const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g);
             const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) ?? [];
@@ -322,7 +322,7 @@ export class CollectionDockingView extends CollectionSubView() {
             );
             new _global.ResizeObserver(this.onResize).observe(this._containerRef.current);
             this._reactionDisposer = reaction(
-                () => StrCast(this._props.Document.dockingConfig),
+                () => StrCast(this.Document.dockingConfig),
                 config => {
                     if (!this._goldenLayout || this._ignoreStateChange !== config) {
                         //  bcz: TODO! really need to diff config with ignoreStateChange and modify the current goldenLayout instead of building a new one.
@@ -381,7 +381,7 @@ export class CollectionDockingView extends CollectionSubView() {
                   .map(id => DocServer.GetCachedRefField(id))
                   .filter(f => f)
                   .map(f => f as Doc);
-        const changesMade = this._props.Document.dockingConfig !== json;
+        const changesMade = this.Document.dockingConfig !== json;
         if (changesMade) {
             if (![AclAdmin, AclEdit].includes(GetEffectiveAcl(this.dataDoc))) {
                 this.layoutDoc.dockingConfig = json;
@@ -449,7 +449,7 @@ export class CollectionDockingView extends CollectionSubView() {
         if (clone) {
             const cloned = await Doc.MakeClone(doc);
             Array.from(cloned.map.entries()).map(entry => (json = json.replace(entry[0], entry[1][Id])));
-            Doc.GetProto(cloned.clone).dockingConfig = json;
+            cloned.clone[DocData].dockingConfig = json;
             return DashboardView.openDashboard(cloned.clone);
         }
         const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
@@ -463,7 +463,7 @@ export class CollectionDockingView extends CollectionSubView() {
             const newtab = origtabdocs.length ? Doc.MakeCopy(origtab, true, undefined, true) : Doc.MakeEmbedding(origtab);
             const newtabdocs = origtabdocs.map(origtabdoc => Doc.MakeEmbedding(origtabdoc));
             if (newtabdocs.length) {
-                Doc.GetProto(newtab).data = new List<Doc>(newtabdocs);
+                newtab[DocData].data = new List<Doc>(newtabdocs);
                 newtabdocs.forEach(ntab => Doc.SetContainer(ntab, newtab));
             }
             json = json.replace(origtab[Id], newtab[Id]);
@@ -479,7 +479,7 @@ export class CollectionDockingView extends CollectionSubView() {
     stateChanged = () => {
         this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
         const json = JSON.stringify(this._goldenLayout.toConfig());
-        const changesMade = this._props.Document.dockingConfig !== json;
+        const changesMade = this.Document.dockingConfig !== json;
         return changesMade;
     };
 
@@ -490,7 +490,7 @@ export class CollectionDockingView extends CollectionSubView() {
             // if you close a tab that is not embedded somewhere else (an embedded Doc can be opened simultaneously in a tab), then add the tab to recently closed
             if (tab.DashDoc.embedContainer === this.Document) tab.DashDoc.embedContainer = undefined;
             if (!tab.DashDoc.embedContainer) Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
-            Doc.RemoveDocFromList(Doc.GetProto(tab.DashDoc), 'proto_embeddings', tab.DashDoc);
+            Doc.RemoveDocFromList(tab.DashDoc[DocData], 'proto_embeddings', tab.DashDoc);
         }
         if (CollectionDockingView.Instance) {
             const dview = CollectionDockingView.Instance.Document;
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index a54a5ec81..2dad377e0 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -19,14 +19,17 @@ import { undoBatch } from '../../util/UndoManager';
 import { AntimodeMenu } from '../AntimodeMenu';
 import { EditableView } from '../EditableView';
 import { MainView } from '../MainView';
-import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView';
 import { DefaultStyleProvider } from '../StyleProvider';
 import { CollectionLinearView } from './collectionLinear';
 import './CollectionMenu.scss';
+import { DocData } from '../../../fields/DocSymbols';
 
 interface CollectionMenuProps {
     panelHeight: () => number;
     panelWidth: () => number;
+    toggleTopBar: () => void;
+    topBarHeight: () => number;
 }
 
 @observer
@@ -65,15 +68,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
         }
     };
 
-    @action
-    toggleTopBar = () => {
-        if (SettingsManager.Instance.headerBarHeight > 0) {
-            SettingsManager.Instance.headerBarHeight = 0;
-        } else {
-            SettingsManager.Instance.headerBarHeight = 60;
-        }
-    };
-
     @action
     toggleProperties = () => {
         if (MainView.Instance.propertiesWidth() > 0) {
@@ -95,6 +89,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
             <div className="collectionMenu-contMenuButtons" ref={this._docBtnRef} style={{ height: this._props.panelHeight() }}>
                 <CollectionLinearView
                     Document={selDoc}
+                    docViewPath={returnEmptyDocViewList}
                     fieldKey="data"
                     dropAction="embed"
                     styleProvider={DefaultStyleProvider}
@@ -123,8 +118,8 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
     }
 
     render() {
-        const headerIcon = SettingsManager.Instance.headerBarHeight > 0 ? 'angle-double-up' : 'angle-double-down';
-        const headerTitle = SettingsManager.Instance.headerBarHeight > 0 ? 'Close Header Bar' : 'Open Header Bar';
+        const headerIcon = this.props.topBarHeight() > 0 ? 'angle-double-up' : 'angle-double-down';
+        const headerTitle = this.props.topBarHeight() > 0 ? 'Close Header Bar' : 'Open Header Bar';
         const propIcon = SettingsManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
         const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Properties' : 'Open Properties';
 
@@ -134,8 +129,8 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
                     toggleType={ToggleType.BUTTON}
                     type={Type.PRIM}
                     color={SettingsManager.userColor}
-                    onClick={this.toggleTopBar}
-                    toggleStatus={SettingsManager.Instance.headerBarHeight > 0}
+                    onClick={this.props.toggleTopBar}
+                    toggleStatus={this.props.topBarHeight() > 0}
                     icon={<FontAwesomeIcon icon={headerIcon} size="lg" />}
                     tooltip={headerTitle}
                 />
@@ -219,7 +214,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         params: ['target', 'source'],
         title: 'set content',
         script: 'getProto(this.target).data = copyField(this.source);',
-        immediate: undoBatch((source: Doc[]) => (Doc.GetProto(this.target).data = new List<Doc>(source))),
+        immediate: undoBatch((source: Doc[]) => (this.target[DocData].data = new List<Doc>(source))),
         initialize: emptyFunction,
     };
     _onClickCommand = {
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 363db8850..e6ce5baab 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -1,6 +1,6 @@
-import * as React from 'react';
 import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc, Field, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Copy, Id } from '../../../fields/FieldSymbols';
@@ -18,8 +18,8 @@ import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { LightboxView } from '../LightboxView';
-import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
-import { FieldViewProps } from '../nodes/FieldView';
+import { DocumentView } from '../nodes/DocumentView';
+import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
 import './CollectionNoteTakingView.scss';
@@ -51,7 +51,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     }
 
     @computed get chromeHidden() {
-        return BoolCast(this.layoutDoc.chromeHidden) || this._props.onBrowseClick?.() ? true : false;
+        return BoolCast(this.layoutDoc.chromeHidden) || this._props.onBrowseClickScript?.() ? true : false;
     }
     // columnHeaders returns the list of SchemaHeaderFields currently being used by the layout doc to render the columns
     @computed get colHeaderData() {
@@ -189,7 +189,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     };
 
     // let's dive in and get the actual document we want to drag/move around
-    focusDocument = (doc: Doc, options: DocFocusOptions) => {
+    focusDocument = (doc: Doc, options: FocusViewOptions) => {
         Doc.BrushDoc(doc);
         const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
         if (found) {
@@ -254,9 +254,9 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 rootSelected={this.rootSelected}
                 layout_showTitle={this._props.childlayout_showTitle}
                 dragAction={StrCast(this.layoutDoc.childDragAction) as dropActionType}
-                onClick={this.onChildClickHandler}
-                onBrowseClick={this._props.onBrowseClick}
-                onDoubleClick={this.onChildDoubleClickHandler}
+                onClickScript={this.onChildClickHandler}
+                onBrowseClickScript={this._props.onBrowseClickScript}
+                onDoubleClickScript={this.onChildDoubleClickHandler}
                 ScreenToLocalTransform={noteTakingDocTransform}
                 focus={this.focusDocument}
                 childFilters={this.childDocFilters}
@@ -410,7 +410,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         if ((e.ctrlKey || fieldProps.Document._createDocOnCR) && ['Enter'].includes(e.key)) {
             e.stopPropagation?.();
             const newDoc = Doc.MakeCopy(fieldProps.Document, true);
-            Doc.GetProto(newDoc).text = undefined;
+            newDoc[DocData].text = undefined;
             FormattedTextBox.SetSelectOnLoad(newDoc);
             return this.addDocument?.(newDoc);
         }
@@ -442,7 +442,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 }
                 return true;
             }
-        } else if (de.complete.linkDragData?.dragDocument.embedContainer === this._props.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
+        } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
             const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' });
             if (!this._props.addDocument?.(source)) e.preventDefault();
             de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: 'doc annotation' }); // TODODO this is where in text links get passed
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 1239a038a..1394e62ba 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -3,6 +3,7 @@ import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
 import { Doc, Opt } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
@@ -23,17 +24,17 @@ import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { CollectionSubView } from '../collections/CollectionSubView';
 import { LightboxView } from '../LightboxView';
 import { AudioWaveform } from '../nodes/audio/AudioWaveform';
-import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
+import { DocumentView, OpenWhere } from '../nodes/DocumentView';
+import { FocusFuncType, FocusViewOptions, StyleProviderFuncType } from '../nodes/FieldView';
 import { LabelBox } from '../nodes/LabelBox';
 import { VideoBox } from '../nodes/VideoBox';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import './CollectionStackedTimeline.scss';
-import { FieldViewProps } from '../nodes/FieldView';
 
 export type CollectionStackedTimelineProps = {
     Play: () => void;
     Pause: () => void;
-    playLink: (linkDoc: Doc, options: DocFocusOptions) => void;
+    playLink: (linkDoc: Doc, options: FocusViewOptions) => void;
     playFrom: (seekTimeInSeconds: number, endTime?: number) => void;
     playing: () => boolean;
     thumbnails?: () => string[];
@@ -162,7 +163,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
 
     makeDocUnfiltered = (doc: Doc) => this.childDocList?.some(item => item === doc);
 
-    getView = async (doc: Doc, options: DocFocusOptions): Promise<Opt<DocumentView>> =>
+    getView = async (doc: Doc, options: FocusViewOptions): Promise<Opt<DocumentView>> =>
         new Promise<Opt<DocumentView>>(res => {
             if (doc.hidden) options.didMove = !(doc.hidden = false);
             const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
@@ -427,8 +428,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                 _isTimelineLabel: true,
                 layout_borderRounding: anchorEndTime === undefined ? '100%' : undefined,
             });
-        Doc.GetProto(anchor)[startTag] = anchorStartTime;
-        Doc.GetProto(anchor)[endTag] = anchorEndTime;
+        anchor[DocData][startTag] = anchorStartTime;
+        anchor[DocData][endTag] = anchorEndTime;
         if (addAsAnnotation) {
             if (Cast(dataDoc[fieldKey], listSpec(Doc), null)) {
                 Cast(dataDoc[fieldKey], listSpec(Doc), []).push(anchor);
@@ -579,7 +580,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                             const timespan = Math.max(0, Math.min(end - this.clipStart, this.clipEnd)) - Math.max(0, start - this.clipStart);
                             const width = (timespan / this.clipDuration) * this.timelineContentWidth;
                             const height = this._props.PanelHeight() / maxLevel;
-                            return this._props.Document.hideAnchors ? null : (
+                            return this.Document.hideAnchors ? null : (
                                 <div
                                     className={'collectionStackedTimeline-marker-timeline'}
                                     key={d.anchor[Id]}
@@ -691,8 +692,8 @@ interface StackedTimelineAnchorProps {
     width: number;
     height: number;
     toTimeline: (screen_delta: number, width: number) => number;
-    styleProvider?: StyleProviderFunc;
-    playLink: (linkDoc: Doc, options: DocFocusOptions) => void;
+    styleProvider?: StyleProviderFuncType;
+    playLink: (linkDoc: Doc, options: FocusViewOptions) => void;
     setTime: (time: number) => void;
     startTag: string;
     endTag: string;
@@ -701,7 +702,7 @@ interface StackedTimelineAnchorProps {
     isDocumentActive?: () => boolean | undefined;
     ScreenToLocalTransform: () => Transform;
     _timeline: HTMLDivElement | null;
-    focus: DocFocusFunc;
+    focus: FocusFuncType;
     currentTimecode: () => number;
     isSelected: () => boolean;
     stackedTimeline: CollectionStackedTimeline;
@@ -810,7 +811,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
     // renders anchor LabelBox
     renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) {
         const anchor = observable({ view: undefined as Opt<DocumentView> | null });
-        const focusFunc = (doc: Doc, options: DocFocusOptions): number | undefined => {
+        const focusFunc = (doc: Doc, options: FocusViewOptions): number | undefined => {
             this._props.playLink(mark, options);
             return undefined;
         };
@@ -842,8 +843,8 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
                     searchFilterDocs={returnEmptyDoclist}
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
-                    onClick={script}
-                    onDoubleClick={this._props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
+                    onClickScript={script}
+                    onDoubleClickScript={this._props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
                     ignoreAutoHeight={false}
                     hideResizeHandles={true}
                     bringToFront={emptyFunction}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 9384b7088..0da32a9e0 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -24,8 +24,8 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
 import { LightboxView } from '../LightboxView';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
-import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
-import { FieldViewProps } from '../nodes/FieldView';
+import { DocumentView } from '../nodes/DocumentView';
+import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
 import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow';
@@ -203,7 +203,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
 
     componentDidMount() {
         super.componentDidMount?.();
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
 
         // reset section headers when a new filter is inputted
         this._pivotFieldDisposer = reaction(
@@ -249,7 +249,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     };
 
     // let's dive in and get the actual document we want to drag/move around
-    focusDocument = (doc: Doc, options: DocFocusOptions) => {
+    focusDocument = (doc: Doc, options: FocusViewOptions) => {
         Doc.BrushDoc(doc);
 
         const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
@@ -288,7 +288,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             if (layout_fieldKey !== 'layout' && fieldProps.Document[layout_fieldKey] instanceof Doc) {
                 newDoc[layout_fieldKey] = fieldProps.Document[layout_fieldKey];
             }
-            Doc.GetProto(newDoc).text = undefined;
+            newDoc[DocData].text = undefined;
             FormattedTextBox.SetSelectOnLoad(newDoc);
             return this.addDocument?.(newDoc);
         }
@@ -324,13 +324,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 renderDepth={this._props.renderDepth + 1}
                 PanelWidth={panelWidth}
                 PanelHeight={panelHeight}
-                pointerEvents={this.DocumentView?.()._props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
+                pointerEvents={this.DocumentView?.()._props.onClickScript?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
                 styleProvider={this.styleProvider}
                 containerViewPath={this.childContainerViewPath}
                 layout_fitWidth={this.childFitWidth}
                 isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive}
                 onKey={this.onKeyDown}
-                onBrowseClick={this._props.onBrowseClick}
+                onBrowseClickScript={this._props.onBrowseClickScript}
                 isDocumentActive={this.isContentActive}
                 LayoutTemplate={this._props.childLayoutTemplate}
                 LayoutTemplateString={this._props.childLayoutString}
@@ -341,8 +341,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 rootSelected={this.rootSelected}
                 layout_showTitle={this._props.childlayout_showTitle}
                 dragAction={(this.layoutDoc.childDragAction ?? this._props.childDragAction) as dropActionType}
-                onClick={this.onChildClickHandler}
-                onDoubleClick={this.onChildDoubleClickHandler}
+                onClickScript={this.onChildClickHandler}
+                onDoubleClickScript={this.onChildDoubleClickHandler}
                 ScreenToLocalTransform={stackedDocTransform}
                 focus={this.focusDocument}
                 childFilters={this.childDocFilters}
@@ -468,7 +468,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 }
                 return true;
             }
-        } else if (de.complete.linkDragData?.dragDocument.embedContainer === this._props.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
+        } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
             const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' });
             if (!this._props.addDocument?.(source)) e.preventDefault();
             de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: 'doc annotation' }); // TODODO this is where in text links get passed
@@ -551,7 +551,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 addDocument={this.addDocument}
                 chromeHidden={this.chromeHidden}
                 colHeaderData={this.colHeaderData}
-                Document={this._props.Document}
+                Document={this.Document}
                 TemplateDataDocument={this._props.TemplateDataDocument}
                 renderChildren={this.children}
                 columnWidth={this.columnWidth}
@@ -583,7 +583,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         return (
             <CollectionMasonryViewFieldRow
                 showHandle={first}
-                Document={this._props.Document}
+                Document={this.Document}
                 chromeHidden={this.chromeHidden}
                 pivotField={this.pivotField}
                 unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)}
@@ -670,7 +670,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                     addDocument={this._props.addDocument}
                     moveDocument={this._props.moveDocument}
                     addDocTab={this._props.addDocTab}
-                    onBrowseClick={this._props.onBrowseClick}
+                    onBrowseClickScript={this._props.onBrowseClickScript}
                     pinToPres={emptyFunction}
                     rootSelected={this.rootSelected}
                     removeDocument={this._props.removeDocument}
@@ -738,7 +738,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                         onWheel={e => this.isContentActive() && e.stopPropagation()}>
                         {this.renderedSections}
                         {!this.showAddAGroup ? null : (
-                            <div key={`${this._props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" style={{ width: !this.isStackingView ? '100%' : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
+                            <div key={`${this.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" style={{ width: !this.isStackingView ? '100%' : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
                                 <EditableView {...editableViewProps} />
                             </div>
                         )}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d7c42a975..0aa74aaba 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -21,18 +21,17 @@ import { ImageUtils } from '../../util/Import & Export/ImageUtils';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { UndoManager, undoBatch } from '../../util/UndoManager';
-import { ViewBoxBaseComponent, ViewBoxBaseProps } from '../DocComponent';
+import { ViewBoxBaseComponent } from '../DocComponent';
 import { LoadingBox } from '../nodes/LoadingBox';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { CollectionView, CollectionViewProps } from './CollectionView';
-import { returnEmptyDocViewList } from '../nodes/DocumentView';
 
 export interface SubCollectionViewProps extends CollectionViewProps {
     isAnyChildContentActive: () => boolean;
 }
 
 export function CollectionSubView<X>(moreProps?: X) {
-    class CollectionSubView extends ViewBoxBaseComponent<X & SubCollectionViewProps & ViewBoxBaseProps>() {
+    class CollectionSubView extends ViewBoxBaseComponent<X & SubCollectionViewProps>() {
         private dropDisposer?: DragManager.DragDropDisposer;
         private gestureDisposer?: GestureUtils.GestureEventDisposer;
         protected _mainCont?: HTMLDivElement;
@@ -63,20 +62,16 @@ export function CollectionSubView<X>(moreProps?: X) {
         }
 
         get dataDoc() {
-            return this._props.TemplateDataDocument instanceof Doc && this._props.Document.isTemplateForField
-                ? Doc.GetProto(this._props.TemplateDataDocument)
-                : this._props.Document.resolvedDataDoc
-                  ? this._props.Document
-                  : Doc.GetProto(this._props.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
+            return this._props.TemplateDataDocument instanceof Doc && this.Document.isTemplateForField ? Doc.GetProto(this._props.TemplateDataDocument) : this.Document.resolvedDataDoc ? this.Document : Doc.GetProto(this.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
         }
 
         get childContainerViewPath() {
-            return this.DocumentView?.().docViewPath ?? returnEmptyDocViewList;
+            return this.DocumentView?.().docViewPath;
         }
         // this returns whether either the collection is selected, or the template that it is part of is selected
         rootSelected = () => this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.());
 
-        // The data field for rendering this collection will be on the this._props.Document unless we're rendering a template in which case we try to use props.TemplateDataDocument.
+        // The data field for rendering this collection will be on the this.Document unless we're rendering a template in which case we try to use props.TemplateDataDocument.
         // When a document has a TemplateDataDoc but it's not a template, then it contains its own rendering data, but needs to pass the TemplateDataDoc through
         // to its children which may be templates.
         // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
@@ -97,14 +92,14 @@ export function CollectionSubView<X>(moreProps?: X) {
         @computed get childDocList() {
             return Cast(this.dataField, listSpec(Doc));
         }
-        collectionFilters = () => this._focusFilters ?? StrListCast(this._props.Document._childFilters);
-        collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this._props.Document._childFiltersByRanges, listSpec('string'), []);
+        collectionFilters = () => this._focusFilters ?? StrListCast(this.Document._childFilters);
+        collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.Document._childFiltersByRanges, listSpec('string'), []);
         // child filters apply to the descendants of the documents in this collection
         childDocFilters = () => [...(this._props.childFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()];
         // unrecursive filters apply to the documents in the collection, but no their children.  See Utils.noRecursionHack
         unrecursiveDocFilters = () => [...(this._props.childFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])];
         childDocRangeFilters = () => [...(this._props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()];
-        searchFilterDocs = () => this._props.searchFilterDocs?.() ?? DocListCast(this._props.Document._searchFilterDocs);
+        searchFilterDocs = () => this._props.searchFilterDocs?.() ?? DocListCast(this.Document._searchFilterDocs);
         @computed.struct get childDocs() {
             TraceMobx();
             let rawdocs: (Doc | Promise<Doc>)[] = [];
@@ -127,7 +122,7 @@ export function CollectionSubView<X>(moreProps?: X) {
             const childDocFilters = this.childDocFilters();
             const childFiltersByRanges = this.childDocRangeFilters();
             const searchDocs = this.searchFilterDocs();
-            if (this._props.Document.dontRegisterView || (!childDocFilters.length && !this.unrecursiveDocFilters().length && !childFiltersByRanges.length && !searchDocs.length)) {
+            if (this.Document.dontRegisterView || (!childDocFilters.length && !this.unrecursiveDocFilters().length && !childFiltersByRanges.length && !searchDocs.length)) {
                 return childDocs.filter(cd => !cd.cookies); // remove any documents that require a cookie if there are no filters to provide one
             }
 
@@ -136,9 +131,9 @@ export function CollectionSubView<X>(moreProps?: X) {
                 // dragging facets
                 const dragged = this._props.childFilters?.().some(f => f.includes(Utils.noDragDocsFilter));
                 if (dragged && SnappingManager.CanEmbed && DragManager.docsBeingDragged.includes(d)) return false;
-                let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this._props.Document).length > 0;
+                let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.Document).length > 0;
                 if (notFiltered) {
-                    notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this._props.Document).length > 0;
+                    notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.Document).length > 0;
                     const fieldKey = Doc.LayoutFieldKey(d);
                     const annos = !Field.toString(Doc.LayoutField(d) as Field).includes(CollectionView.name);
                     const data = d[annos ? fieldKey + '_annotations' : fieldKey];
@@ -171,7 +166,7 @@ export function CollectionSubView<X>(moreProps?: X) {
         @action
         protected async setCursorPosition(position: [number, number]) {
             let ind;
-            const doc = this._props.Document;
+            const doc = this.Document;
             const id = Doc.UserDoc()[Id];
             const email = Doc.CurrentUserEmail;
             const pos = { x: position[0], y: position[1] };
@@ -207,7 +202,7 @@ export function CollectionSubView<X>(moreProps?: X) {
                 const dropAction = this.layoutDoc.dropAction as dropActionType;
                 // if the dropEvent's dragAction is, say 'embed', but we're just dragging within a collection, we may not actually want to make an embedding.
                 // so we check if our collection has a dropAction set on it and if so, we use that instead.
-                if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this._props.Document && this.childDocs.includes(d))) {
+                if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this.Document && this.childDocs.includes(d))) {
                     de.complete.docDragData.dropAction = dropAction;
                 }
                 e.stopPropagation();
@@ -471,7 +466,7 @@ export function CollectionSubView<X>(moreProps?: X) {
             }
             if (generatedDocuments.length) {
                 // Creating a dash document
-                const isFreeformView = this._props.Document._type_collection === CollectionViewType.Freeform;
+                const isFreeformView = this.Document._type_collection === CollectionViewType.Freeform;
                 const set = !isFreeformView
                     ? generatedDocuments
                     : generatedDocuments.length > 1
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 7036ec41c..ee5147428 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -16,7 +16,8 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
-import { DocFocusOptions, DocumentView } from '../nodes/DocumentView';
+import { DocumentView } from '../nodes/DocumentView';
+import { FocusViewOptions } from '../nodes/FieldView';
 import { PresBox } from '../nodes/trails';
 import { CollectionSubView } from './CollectionSubView';
 import './CollectionTimeView.scss';
@@ -38,7 +39,7 @@ export class CollectionTimeView extends CollectionSubView() {
     }
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         runInAction(() => {
             this._childClickedScript = ScriptField.MakeScript('openInLightbox(this)', { this: Doc.name });
             this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' });
@@ -68,7 +69,7 @@ export class CollectionTimeView extends CollectionSubView() {
     };
 
     @action
-    scrollPreview = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: DocFocusOptions) => {
+    scrollPreview = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: FocusViewOptions) => {
         // if in preview, then override document's fields with view spec
         this._focusFilters = StrListCast(anchor.config_docFilters);
         this._focusRangeFilters = StrListCast(anchor.config_docRangeFilters);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 76b934802..4696d7948 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -447,7 +447,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                 {!(this.Document instanceof Doc) || !this.treeChildren ? null : this.Document.treeView_HasOverlay ? (
                     <CollectionFreeFormView
                         {...this._props}
-                        setContentView={emptyFunction}
+                        setContentViewBox={emptyFunction}
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
                         pointerEvents={this._props.isContentActive() && SnappingManager.IsDragging ? returnAll : returnNone}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 7c9ef9f48..26ff5cc06 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -13,7 +13,7 @@ import { dropActionType } from '../../util/DragManager';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { OpenWhere } from '../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import { CollectionCalendarView } from './CollectionCalendarView';
@@ -33,7 +33,7 @@ import { CollectionLinearView } from './collectionLinear';
 import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
 import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
 import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView';
-interface CollectionViewProps_ extends FieldViewProps {
+export interface CollectionViewProps extends React.PropsWithChildren<FieldViewProps> {
     isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc)
     isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently)
     layoutEngine?: () => string;
@@ -63,9 +63,8 @@ interface CollectionViewProps_ extends FieldViewProps {
     RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
     hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views)
 }
-export interface CollectionViewProps extends React.PropsWithChildren<CollectionViewProps_> {}
 @observer
-export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & CollectionViewProps>() {
+export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(CollectionView, fieldStr);
     }
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index f00e42360..da15f8dc5 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -29,7 +29,8 @@ import { LightboxView } from '../LightboxView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
 import { Colors } from '../global/globalEnums';
-import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView';
+import { DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView';
+import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
 import { DashFieldView } from '../nodes/formattedText/DashFieldView';
 import { PinProps, PresBox, PresMovement } from '../nodes/trails';
@@ -37,7 +38,6 @@ import { CollectionDockingView } from './CollectionDockingView';
 import { CollectionView } from './CollectionView';
 import './TabDocView.scss';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
-import { FieldViewProps } from '../nodes/FieldView';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 interface TabDocViewProps {
@@ -118,7 +118,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
             titleEle.onchange = (e: any) => {
                 undoable(() => {
                     titleEle.size = e.currentTarget.value.length + 3;
-                    Doc.GetProto(doc).title = e.currentTarget.value;
+                    doc[DocData].title = e.currentTarget.value;
                 }, 'edit tab title')();
             };
 
@@ -416,7 +416,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
         return tab !== undefined;
     };
     @action
-    focusFunc = (doc: Doc, options: DocFocusOptions) => {
+    focusFunc = (doc: Doc, options: FocusViewOptions) => {
         if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) {
             this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
         }
@@ -452,7 +452,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                     hideTitle={this._props.keyValue}
                     Document={this._document}
                     TemplateDataDocument={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined}
-                    onBrowseClick={DocumentView.exploreMode}
+                    onBrowseClickScript={DocumentView.exploreMode}
                     waitForDoubleClickToClick={this.waitForDoubleClick}
                     isContentActive={this.isContentActive}
                     isDocumentActive={returnFalse}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 9f40edee1..38a2fe978 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -27,8 +27,8 @@ import { UndoManager, undoBatch, undoable } from '../../util/UndoManager';
 import { EditableView } from '../EditableView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { StyleProp } from '../StyleProvider';
-import { DocumentView, DocumentViewInternal, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
-import { FieldViewProps } from '../nodes/FieldView';
+import { DocumentView, DocumentViewInternal, OpenWhere } from '../nodes/DocumentView';
+import { FieldViewProps, StyleProviderFuncType } from '../nodes/FieldView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
@@ -62,7 +62,7 @@ export interface TreeViewProps {
     ScreenToLocalTransform: () => Transform;
     contextMenuItems?: { script: ScriptField; filter: ScriptField; icon: string; label: string }[];
     dontRegisterView?: boolean;
-    styleProvider?: StyleProviderFunc | undefined;
+    styleProvider?: StyleProviderFuncType | undefined;
     treeViewHideHeaderFields: () => boolean;
     renderedIds: string[]; // list of document ids rendered used to avoid unending expansion of items in a cycle
     onCheckedClick?: () => ScriptField;
@@ -367,8 +367,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             _width: 1000,
             _height: 10,
         });
-        Doc.GetProto(bullet).title = ComputedField.MakeFunction('this.text?.Text');
-        Doc.GetProto(bullet).data = new List<Doc>([]);
+        const bulletData = bullet[DocData];
+        bulletData.title = ComputedField.MakeFunction('this.text?.Text');
+        bulletData.data = new List<Doc>([]);
         DocumentManager.Instance.AddViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.());
 
         return bullet;
@@ -982,8 +983,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 addDocument={undefined}
                 addDocTab={this._props.addDocTab}
                 pinToPres={this.treeView._props.pinToPres}
-                onClick={this.onChildClick}
-                onDoubleClick={this.onChildDoubleClick}
+                onClickScript={this.onChildClick}
+                onDoubleClickScript={this.onChildDoubleClick}
                 dragAction={this._props.dragAction}
                 moveDocument={this.move}
                 removeDocument={this._props.removeDoc}
@@ -1076,7 +1077,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                     fitContentsToBox={returnTrue}
                     hideDecorationTitle={this.treeView.outlineMode}
                     hideResizeHandles={this.treeView.outlineMode}
-                    onClick={this.onChildClick}
+                    onClickScript={this.onChildClick}
                     focus={this.refocus}
                     onKey={this.onKeyDown}
                     hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)}
@@ -1209,7 +1210,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         move: DragManager.MoveFunction,
         dragAction: dropActionType,
         addDocTab: (doc: Doc, where: OpenWhere) => boolean,
-        styleProvider: undefined | StyleProviderFunc,
+        styleProvider: undefined | StyleProviderFuncType,
         screenToLocalXf: () => Transform,
         isContentActive: (outsideReaction?: boolean) => boolean,
         panelWidth: () => number,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 5204633ea..f0a31a8c6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -130,7 +130,7 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent<Collect
                 LinkManager.currentLink = this._props.LinkDocs[0];
                 this.toggleProperties();
                 // OverlayView.Instance.addElement(
-                //     <LinkEditor sourceDoc={this._props.A._props.Document} linkDoc={this._props.LinkDocs[0]}
+                //     <LinkEditor sourceDoc={this._props.A.Document} linkDoc={this._props.LinkDocs[0]}
                 //         showLinks={action(() => { })}
                 //     />, { x: 300, y: 300 });
             })
@@ -204,8 +204,8 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent<Collect
         const atop = this.visibleY(adiv);
         const btop = this.visibleY(bdiv);
         if (!a.width || !b.width) return undefined;
-        const aDocBounds = (A._props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 };
-        const bDocBounds = (B._props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 };
+        const aDocBounds = (A._props as any).DocumentView?.().getBounds || { left: 0, right: 0, top: 0, bottom: 0 };
+        const bDocBounds = (B._props as any).DocumentView?.().getBounds || { left: 0, right: 0, top: 0, bottom: 0 };
         const aleft = this.visibleX(adiv);
         const bleft = this.visibleX(bdiv);
         const aclipped = aleft !== a.left || atop !== a.top;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c4358747b..7203041e0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
 import { Bezier } from 'bezier-js';
 import { Colors } from 'browndash-components';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction, toJS } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
@@ -38,8 +38,8 @@ import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } fr
 import { LightboxView } from '../../LightboxView';
 import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewWrapper } from '../../nodes/CollectionFreeFormDocumentView';
 import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
-import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView';
-import { FieldViewProps } from '../../nodes/FieldView';
+import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
+import { FocusViewOptions, FieldViewProps } from '../../nodes/FieldView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../../nodes/trails/PresBox';
 import { CreateImage } from '../../nodes/WebBoxRenderer';
@@ -54,7 +54,7 @@ import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCurso
 import './CollectionFreeFormView.scss';
 import { MarqueeView } from './MarqueeView';
 
-export type collectionFreeformViewProps = {
+export interface collectionFreeformViewProps {
     NativeWidth?: () => number;
     NativeHeight?: () => number;
     originTopLeft?: boolean;
@@ -65,12 +65,12 @@ export type collectionFreeformViewProps = {
     noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
     engineProps?: any;
     getScrollHeight?: () => number | undefined;
-};
+}
 
 @observer
 export class CollectionFreeFormView extends CollectionSubView<Partial<collectionFreeformViewProps>>() {
     public get displayName() {
-        return 'CollectionFreeFormView(' + this._props.Document.title?.toString() + ')';
+        return 'CollectionFreeFormView(' + this.Document.title?.toString() + ')';
     } // this makes mobx trace() statements more descriptive
 
     constructor(props: any) {
@@ -180,12 +180,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration: number) {
-        if (timer) clearTimeout(timer);
-        return DocumentView.SetViewTransition(docs, 'all', duration, undefined, true);
+        return DocumentView.SetViewTransition(docs, 'all', duration, timer, undefined, true);
     }
     public static updateKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], time: number) {
-        if (timer) clearTimeout(timer);
-        const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, undefined, true);
+        const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, timer, undefined, true);
         const timecode = Math.round(time);
         docs.forEach(doc => {
             CollectionFreeFormDocumentView.animFields.forEach(val => {
@@ -223,7 +221,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable _keyframeEditing = false;
     @action setKeyFrameEditing = (set: boolean) => (this._keyframeEditing = set);
     getKeyFrameEditing = () => this._keyframeEditing;
-    onBrowseClickHandler = () => this._props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick);
+    onBrowseClickHandler = () => this._props.onBrowseClickScript?.() || ScriptCast(this.layoutDoc.onBrowseClick);
     onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
     onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
     elementFunc = () => this._layoutElements;
@@ -288,14 +286,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return dispTime === -1 || curTime === -1 || (curTime - dispTime >= -1e-4 && curTime <= endTime);
     }
 
-    groupFocus = (anchor: Doc, options: DocFocusOptions) => {
+    groupFocus = (anchor: Doc, options: FocusViewOptions) => {
         options.docTransform = new Transform(-NumCast(this.layoutDoc[this.panXFieldKey]) + NumCast(anchor.x), -NumCast(this.layoutDoc[this.panYFieldKey]) + NumCast(anchor.y), 1);
         const res = this._props.focus(this.Document, options);
         options.docTransform = undefined;
         return res;
     };
 
-    focus = (anchor: Doc, options: DocFocusOptions) => {
+    focus = (anchor: Doc, options: FocusViewOptions) => {
         if (this._lightboxDoc) return;
         if (anchor === this.Document) {
             if (options.willZoomCentered && options.zoomScale) {
@@ -321,7 +319,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }
     };
 
-    getView = async (doc: Doc, options: DocFocusOptions): Promise<Opt<DocumentView>> =>
+    getView = async (doc: Doc, options: FocusViewOptions): Promise<Opt<DocumentView>> =>
         new Promise<Opt<DocumentView>>(res => {
             if (doc.hidden && this._lightboxDoc !== doc) options.didMove = !(doc.hidden = false);
             const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
@@ -467,7 +465,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return this.childLayoutPairs
             .map(pair => pair.layout)
             .reduce((cluster, cd) => {
-                const grouping = this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster, -1) : NumCast(cd.group, -1);
+                const grouping = this.Document._freeform_useClusters ? NumCast(cd.layout_cluster, -1) : NumCast(cd.group, -1);
                 if (grouping !== -1) {
                     const layoutDoc = Doc.Layout(cd);
                     const cx = NumCast(cd.x) - this._clusterDistance / 2;
@@ -484,9 +482,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (cluster !== -1) {
             const ptsParent = e;
             if (ptsParent) {
-                const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
+                const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
                 const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.DocumentView?.())!);
-                const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
+                const { left, top } = clusterDocs[0].getBounds || { left: 0, top: 0 };
                 const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'embed' : undefined);
                 de.moveDocument = this._props.moveDocument;
                 de.offset = this.screenToFreeformContentsXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
@@ -506,7 +504,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     updateClusters(_freeform_useClusters: boolean) {
-        this._props.Document._freeform_useClusters = _freeform_useClusters;
+        this.Document._freeform_useClusters = _freeform_useClusters;
         this._clusterSets.length = 0;
         this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c));
     }
@@ -514,7 +512,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @action
     updateClusterDocs(docs: Doc[]) {
         const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
-        if (this._props.Document._freeform_useClusters) {
+        if (this.Document._freeform_useClusters) {
             const docFirst = docs[0];
             docs.map(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)));
             const preferredInd = NumCast(docFirst.layout_cluster);
@@ -557,7 +555,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @action
     updateCluster = (doc: Doc) => {
         const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
-        if (this._props.Document._freeform_useClusters) {
+        if (this.Document._freeform_useClusters) {
             this._clusterSets.forEach(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
             const preferredInd = NumCast(doc.layout_cluster);
             doc.layout_cluster = -1;
@@ -616,7 +614,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     trySelectCluster = (addToSel: boolean) => {
         if (this._hitCluster !== -1) {
             !addToSel && SelectionManager.DeselectAll();
-            const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
+            const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
             this.selectDocuments(eles);
             return true;
         }
@@ -810,7 +808,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return this.childDocs
             .map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.()))
             .filter(inkView => inkView?.ComponentView instanceof InkingStroke)
-            .map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
+            .map(inkView => ({ inkViewBounds: inkView!.getBounds, inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
             .filter(
                 ({ inkViewBounds }) =>
                     inkViewBounds && // bounding box of eraser segment and ink stroke overlap
@@ -959,8 +957,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const localTransform = invTransform.scaleAbout(deltaScale, x, y);
         if (localTransform.Scale >= 0.05 || localTransform.Scale > this.zoomScaling()) {
             const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20);
-            this._props.Document[this.scaleFieldKey] = Math.abs(safeScale);
-            this.setPan(-localTransform.TranslateX / safeScale, (this._props.originTopLeft ? undefined : NumCast(this._props.Document.layout_scrollTop) * safeScale) || -localTransform.TranslateY / safeScale);
+            this.Document[this.scaleFieldKey] = Math.abs(safeScale);
+            this.setPan(-localTransform.TranslateX / safeScale, (this._props.originTopLeft ? undefined : NumCast(this.Document.layout_scrollTop) * safeScale) || -localTransform.TranslateY / safeScale);
         }
     };
 
@@ -968,7 +966,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     onPointerWheel = (e: React.WheelEvent): void => {
         if (this.Document.isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
         PresBox.Instance?.pauseAutoPres();
-        if (this.layoutDoc._Transform || this._props.Document.treeView_OutlineMode === TreeViewType.outline) return;
+        if (this.layoutDoc._Transform || this.Document.treeView_OutlineMode === TreeViewType.outline) return;
         e.stopPropagation();
         const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight);
         const scrollable = this.isAnnotationOverlay && NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling + 1e-4;
@@ -989,7 +987,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             case freeformScrollMode.Zoom:
                 if ((e.ctrlKey || !scrollable) && this._props.isContentActive()) {
                     this.zoom(e.clientX, e.clientY, Math.max(-1, Math.min(1, e.deltaY))); // if (!this._props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
-                    e.preventDefault();
+                    // e.preventDefault();
                 }
                 break;
         }
@@ -1182,13 +1180,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             if (layout_fieldKey !== 'layout' && fieldProps.Document[layout_fieldKey] instanceof Doc) {
                 newDoc[layout_fieldKey] = fieldProps.Document[layout_fieldKey];
             }
-            Doc.GetProto(newDoc).text = undefined;
+            newDoc[DocData].text = undefined;
             FormattedTextBox.SetSelectOnLoad(newDoc);
             return this.addDocument?.(newDoc);
         }
     };
     @computed get childPointerEvents() {
-        const engine = this._props.layoutEngine?.() || StrCast(this._props.Document._layoutEngine);
+        const engine = this._props.layoutEngine?.() || StrCast(this.Document._layoutEngine);
         return SnappingManager.IsResizing
             ? 'none'
             : this._props.childPointerEvents?.() ??
@@ -1210,6 +1208,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 {...OmitKeys(entry, ['replica', 'pair']).omit}
                 key={childLayout[Id] + (entry.replica || '')}
                 Document={childLayout}
+                containerViewPath={this.DocumentView?.().docViewPath}
+                styleProvider={this.clusterStyleProvider}
                 TemplateDataDocument={childData}
                 dragStarting={this.dragStarting}
                 dragEnding={this.dragEnding}
@@ -1223,10 +1223,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 LayoutTemplateString={childLayout.z ? undefined : this._props.childLayoutString}
                 rootSelected={childData ? this.rootSelected : returnFalse}
                 waitForDoubleClickToClick={this._props.waitForDoubleClickToClick}
-                onClick={this.onChildClickHandler}
+                onClickScript={this.onChildClickHandler}
                 onKey={this.onKeyDown}
-                onDoubleClick={this.onChildDoubleClickHandler}
-                onBrowseClick={this.onBrowseClickHandler}
+                onDoubleClickScript={this.onChildDoubleClickHandler}
+                onBrowseClickScript={this.onBrowseClickHandler}
                 ScreenToLocalTransform={childLayout.z ? this.ScreenToLocalBoxXf : this.ScreenToContentsXf}
                 PanelWidth={childLayout[Width]}
                 PanelHeight={childLayout[Height]}
@@ -1242,8 +1242,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 moveDocument={this._props.moveDocument}
                 pinToPres={this._props.pinToPres}
                 whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
-                containerViewPath={this.DocumentView?.().docViewPath}
-                styleProvider={this.clusterStyleProvider}
                 dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
                 bringToFront={this.bringToFront}
                 layout_showTitle={this._props.childlayout_showTitle}
@@ -1322,7 +1320,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     onViewDefDivClick = (e: React.MouseEvent, payload: any) => {
-        (this._props.viewDefDivClick || ScriptCast(this._props.Document.onViewDefDivClick))?.script.run({ this: this._props.Document, payload });
+        (this._props.viewDefDivClick || ScriptCast(this.Document.onViewDefDivClick))?.script.run({ this: this.Document, payload });
         e.stopPropagation();
     };
 
@@ -1377,7 +1375,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         poolData: Map<string, PoolData>,
         engine: (poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) => ViewDefResult[]
     ) {
-        return engine(poolData, this._props.Document, this.childLayoutPairs, [this._props.PanelWidth(), this._props.PanelHeight()], this.viewDefsToJSX, this._props.engineProps);
+        return engine(poolData, this.Document, this.childLayoutPairs, [this._props.PanelWidth(), this._props.PanelHeight()], this.viewDefsToJSX, this._props.engineProps);
     }
 
     doFreeformLayout(poolData: Map<string, PoolData>) {
@@ -1437,7 +1435,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     infoUI = () => (this.Document._hideInfo || this.Document.annotationOn || this._props.renderDepth ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} close={this.closeInfo} />);
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         super.componentDidMount?.();
         setTimeout(
             action(() => {
@@ -1639,15 +1637,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
         const appearance = ContextMenu.Instance.findByDescription('Appearance...');
         const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : [];
-        !this._props.Document.isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' });
-        !this._props.Document.isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' });
+        !this.Document.isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' });
+        !this.Document.isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' });
         !Doc.noviceMode &&
             appearanceItems.push({
                 description: 'Toggle auto arrange',
                 event: () => (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange),
                 icon: 'compress-arrows-alt',
             });
-        if (this._props.setContentView === emptyFunction) {
+        if (this._props.setContentViewBox === emptyFunction) {
             !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
             return;
         }
@@ -1656,7 +1654,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' });
         this._props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
 
-        this._props.Document.isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: this.transcribeStrokes, icon: 'font' });
+        this.Document.isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: this.transcribeStrokes, icon: 'font' });
 
         !Doc.noviceMode ? appearanceItems.push({ description: 'Arrange contents in grid', event: this.layoutDocsInGrid, icon: 'table' }) : null;
         !Doc.noviceMode ? appearanceItems.push({ description: (this.Document._freeform_useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._freeform_useClusters), icon: 'braille' }) : null;
@@ -1680,8 +1678,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @undoBatch
     transcribeStrokes = () => {
-        if (this._props.Document.isGroup && this._props.Document.transcription) {
-            const text = StrCast(this._props.Document.transcription);
+        if (this.Document.isGroup && this.Document.transcription) {
+            const text = StrCast(this.Document.transcription);
             const lines = text.split('\n');
             const height = 30 + 15 * lines.length;
 
@@ -1742,7 +1740,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @computed get placeholder() {
         return (
             <div className="collectionfreeformview-placeholder" style={{ background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) }}>
-                <span className="collectionfreeformview-placeholderSpan">{this._props.Document.annotationOn ? '' : this._props.Document.title?.toString()}</span>
+                <span className="collectionfreeformview-placeholderSpan">{this.Document.annotationOn ? '' : this.Document.title?.toString()}</span>
             </div>
         );
     }
@@ -1882,10 +1880,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                             PanelHeight={this.lightboxPanelHeight}
                             NativeWidth={returnZero}
                             NativeHeight={returnZero}
-                            onClick={this.onChildClickHandler}
+                            onClickScript={this.onChildClickHandler}
                             onKey={this.onKeyDown}
-                            onDoubleClick={this.onChildDoubleClickHandler}
-                            onBrowseClick={this.onBrowseClickHandler}
+                            onDoubleClickScript={this.onChildDoubleClickHandler}
+                            onBrowseClickScript={this.onBrowseClickHandler}
                             childFilters={this.childDocFilters}
                             childFiltersByRanges={this.childDocRangeFilters}
                             searchFilterDocs={this.searchFilterDocs}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 39d828302..c2f8232c6 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -352,9 +352,10 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
         const newCollection = creator
             ? creator(selected, { title: 'nested stack' })
             : ((doc: Doc) => {
-                  Doc.GetProto(doc).data = new List<Doc>(selected);
-                  Doc.GetProto(doc).isGroup = makeGroup;
-                  Doc.GetProto(doc).title = makeGroup ? 'grouping' : 'nested freeform';
+                  const docData = doc[DocData];
+                  docData.data = new List<Doc>(selected);
+                  docData.isGroup = makeGroup;
+                  docData.title = makeGroup ? 'grouping' : 'nested freeform';
                   doc._freeform_panX = doc._freeform_panY = 0;
                   return doc;
               })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 113ffedb3..f25872c2b 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -192,7 +192,7 @@ export class CollectionGridView extends CollectionSubView() {
                 {...this._props}
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
-                setContentView={emptyFunction}
+                setContentViewBox={emptyFunction}
                 Document={layout}
                 TemplateDataDocument={layout.resolvedDataDoc as Doc}
                 isContentActive={this.isChildContentActive}
@@ -200,7 +200,7 @@ export class CollectionGridView extends CollectionSubView() {
                 PanelHeight={height}
                 ScreenToLocalTransform={dxf}
                 whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
-                onClick={this.onChildClickHandler}
+                onClickScript={this.onChildClickHandler}
                 renderDepth={this._props.renderDepth + 1}
                 dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy'
             />
@@ -340,7 +340,7 @@ export class CollectionGridView extends CollectionSubView() {
      * Handles text document creation on double click.
      */
     onPointerDown = (e: React.PointerEvent) => {
-        if (this._props.isContentActive(true)) {
+        if (this._props.isContentActive()) {
             setupMoveUpEvents(
                 this,
                 e,
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 40b4151fe..1fa106b45 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -267,8 +267,8 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 PanelHeight={height}
                 rootSelected={this.rootSelected}
                 dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
-                onClick={this.onChildClickHandler}
-                onDoubleClick={this.onChildDoubleClickHandler}
+                onClickScript={this.onChildClickHandler}
+                onDoubleClickScript={this.onChildDoubleClickHandler}
                 suppressSetHeight={true}
                 ScreenToLocalTransform={dxf}
                 isContentActive={this.isChildContentActive}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 04a4042f1..97a444b8c 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -262,8 +262,8 @@ export class CollectionMultirowView extends CollectionSubView() {
                 PanelHeight={height}
                 rootSelected={this.rootSelected}
                 dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
-                onClick={this.onChildClickHandler}
-                onDoubleClick={this.onChildDoubleClickHandler}
+                onClickScript={this.onChildClickHandler}
+                onDoubleClickScript={this.onChildDoubleClickHandler}
                 ScreenToLocalTransform={dxf}
                 isContentActive={this.isChildContentActive}
                 isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
index c38c6dc4e..d580d9c52 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
@@ -1,16 +1,16 @@
-import { action, observable } from 'mobx';
+import { action } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { NumCast, StrCast } from '../../../../fields/Types';
 import { UndoManager } from '../../../util/UndoManager';
 import { StyleProp } from '../../StyleProvider';
-import { StyleProviderFunc } from '../../nodes/DocumentView';
+import { StyleProviderFuncType } from '../../nodes/FieldView';
 import { DimUnit } from './CollectionMulticolumnView';
 
 interface ResizerProps {
     width: number;
-    styleProvider?: StyleProviderFunc;
+    styleProvider?: StyleProviderFuncType;
     isContentActive?: () => boolean | undefined;
     columnUnitLength(): number | undefined;
     toLeft?: Doc;
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
index 6f1b3b425..73d08d5ef 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
@@ -1,16 +1,16 @@
-import { action, observable } from 'mobx';
+import { action } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { NumCast, StrCast } from '../../../../fields/Types';
 import { UndoManager } from '../../../util/UndoManager';
 import { StyleProp } from '../../StyleProvider';
-import { StyleProviderFunc } from '../../nodes/DocumentView';
+import { StyleProviderFuncType } from '../../nodes/FieldView';
 import { DimUnit } from './CollectionMultirowView';
 
 interface ResizerProps {
     height: number;
-    styleProvider?: StyleProviderFunc;
+    styleProvider?: StyleProviderFuncType;
     isContentActive?: () => boolean | undefined;
     columnUnitLength(): number | undefined;
     toTop?: Doc;
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 227274a53..ec91b25f8 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -16,7 +16,8 @@ import { undoable, undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
-import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
+import { DocumentView } from '../../nodes/DocumentView';
+import { FocusViewOptions, FieldViewProps } from '../../nodes/FieldView';
 import { KeyValueBox } from '../../nodes/KeyValueBox';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DefaultStyleProvider, StyleProp } from '../../StyleProvider';
@@ -24,7 +25,6 @@ import { CollectionSubView } from '../CollectionSubView';
 import './CollectionSchemaView.scss';
 import { SchemaColumnHeader } from './SchemaColumnHeader';
 import { SchemaRowBox } from './SchemaRowBox';
-import { FieldViewProps } from '../../nodes/FieldView';
 
 export enum ColumnType {
     Number,
@@ -147,7 +147,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     }
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         document.addEventListener('keydown', this.onKeyDown);
 
         Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => this.fieldInfos.set(pair[0], pair[1]));
@@ -498,13 +498,13 @@ export class CollectionSchemaView extends CollectionSubView() {
         ContextMenu.Instance.displayMenu(x, y, undefined, true);
     };
 
-    focusDocument = (doc: Doc, options: DocFocusOptions) => {
+    focusDocument = (doc: Doc, options: FocusViewOptions) => {
         Doc.BrushDoc(doc);
         this.scrollToDoc(doc, options);
         return undefined;
     };
 
-    scrollToDoc = (doc: Doc, options: DocFocusOptions) => {
+    scrollToDoc = (doc: Doc, options: FocusViewOptions) => {
         const found = this._tableContentRef && Array.from(this._tableContentRef.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
         if (found) {
             const rect = found.getBoundingClientRect();
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 3f94e7fc1..f2fe0dde7 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -12,7 +12,7 @@ import { DragManager } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoable } from '../../../util/UndoManager';
-import { ViewBoxBaseComponent, ViewBoxBaseProps } from '../../DocComponent';
+import { ViewBoxBaseComponent } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
 import { OpenWhere } from '../../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../../nodes/FieldView';
@@ -20,17 +20,17 @@ import { CollectionSchemaView } from './CollectionSchemaView';
 import './CollectionSchemaView.scss';
 import { SchemaTableCell } from './SchemaTableCell';
 
-interface SchemaRowBoxProps {
+interface SchemaRowBoxProps extends FieldViewProps {
     rowIndex: number;
 }
 @observer
-export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRowBoxProps & ViewBoxBaseProps>() {
+export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
     public static LayoutString(fieldKey: string, rowIndex: number) {
         return FieldView.LayoutString(SchemaRowBox, fieldKey).replace('fieldKey', `rowIndex={${rowIndex}} fieldKey`);
     }
     private _ref: HTMLDivElement | null = null;
 
-    constructor(props: any) {
+    constructor(props: SchemaRowBoxProps) {
         super(props);
         makeObservable(this);
     }
@@ -50,7 +50,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo
     }
 
     componentDidMount(): void {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
 
     select = (ctrlKey: boolean, shiftKey: boolean) => {
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 422c4155d..fda5764dd 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -18,7 +18,7 @@ import { EditableView } from '../../EditableView';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DefaultStyleProvider } from '../../StyleProvider';
 import { Colors } from '../../global/globalEnums';
-import { OpenWhere } from '../../nodes/DocumentView';
+import { OpenWhere, returnEmptyDocViewList } from '../../nodes/DocumentView';
 import { FieldViewProps } from '../../nodes/FieldView';
 import { KeyValueBox } from '../../nodes/KeyValueBox';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
@@ -49,7 +49,7 @@ export interface SchemaTableCellProps {
 
 @observer
 export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellProps> {
-    constructor(props: any) {
+    constructor(props: SchemaTableCellProps) {
         super(props);
         makeObservable(this);
     }
@@ -75,6 +75,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
         const fieldProps: FieldViewProps = {
             childFilters: returnEmptyFilter,
             childFiltersByRanges: returnEmptyFilter,
+            docViewPath: returnEmptyDocViewList,
             searchFilterDocs: returnEmptyDoclist,
             styleProvider: DefaultStyleProvider,
             isSelected: returnFalse,
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index b7376e901..12b83414c 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -61,7 +61,7 @@ export class LinkMenu extends ObservableReactComponent<Props> {
     render() {
         const sourceDoc = this.props.docView.Document;
         const sourceAnchor = this.props.docView.anchorViewDoc ?? sourceDoc;
-        const style = this.props.style ?? (dv => ({ left: dv?.left || 0, top: this.props.docView.topMost ? undefined : (dv?.bottom || 0) + 15, bottom: this.props.docView.topMost ? 20 : undefined, maxWidth: 200 }))(this.props.docView.getBounds());
+        const style = this.props.style ?? (dv => ({ left: dv?.left || 0, top: this.props.docView.topMost ? undefined : (dv?.bottom || 0) + 15, bottom: this.props.docView.topMost ? 20 : undefined, maxWidth: 200 }))(this.props.docView.getBounds);
 
         return (
             <div className="linkMenu" ref={this._linkMenuRef} style={{ ...style, background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 91142b90b..028d3da53 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -47,18 +47,18 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
             const sourceDoc =
                 this.props.docView.anchorViewDoc ??
                 (this.props.docView.Document.type === DocumentType.LINK //
-                    ? this.props.docView.props.LayoutTemplateString?.includes('link_anchor_1')
+                    ? this.props.docView._props.LayoutTemplateString?.includes('link_anchor_1')
                         ? DocCast(linkDoc.link_anchor_1)
                         : DocCast(linkDoc.link_anchor_2)
                     : this.props.sourceDoc);
             const destDoc = !sourceDoc
                 ? undefined
                 : this.props.docView.Document.type === DocumentType.LINK
-                ? this.props.docView.props.LayoutTemplateString?.includes('link_anchor_1')
-                    ? DocCast(linkDoc.link_anchor_2)
-                    : DocCast(linkDoc.link_anchor_1)
-                : LinkManager.getOppositeAnchor(linkDoc, sourceDoc) ||
-                  LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.link_anchor_2, Doc, null).annotationOn === sourceDoc ? Cast(linkDoc.link_anchor_2, Doc, null) : Cast(linkDoc.link_anchor_1, Doc, null));
+                  ? this.props.docView._props.LayoutTemplateString?.includes('link_anchor_1')
+                      ? DocCast(linkDoc.link_anchor_2)
+                      : DocCast(linkDoc.link_anchor_1)
+                  : LinkManager.getOppositeAnchor(linkDoc, sourceDoc) ||
+                    LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.link_anchor_2, Doc, null).annotationOn === sourceDoc ? Cast(linkDoc.link_anchor_2, Doc, null) : Cast(linkDoc.link_anchor_1, Doc, null));
             return !destDoc || !sourceDoc ? null : (
                 <LinkMenuItem
                     key={linkDoc[Id]}
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
index 6760a0b1e..ed575c329 100644
--- a/src/client/views/newlightbox/NewLightboxView.tsx
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -312,7 +312,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
                         addDocTab={this.addDocTab}
                         pinToPres={TabDocView.PinDoc}
                         bringToFront={emptyFunction}
-                        onBrowseClick={DocumentView.exploreMode}
+                        onBrowseClickScript={DocumentView.exploreMode}
                         focus={emptyFunction}
                     />
                 </GestureOverlay>
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 1729af191..2b71fd156 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -5,6 +5,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { DateField } from '../../../fields/DateField';
 import { Doc } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
 import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, DateCast, NumCast } from '../../../fields/Types';
 import { AudioField, nullAudio } from '../../../fields/URLField';
@@ -19,8 +20,7 @@ import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import './AudioBox.scss';
-import { DocFocusOptions } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
+import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
 import { PinProps, PresBox } from './trails';
 
 /**
@@ -121,7 +121,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     @action
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         if (this.path) {
             this.mediaState = media_state.Paused;
             this.setPlayheadTime(NumCast(this.layoutDoc.clipStart));
@@ -374,9 +374,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             action(() => {
                 const newDoc = DocUtils.GetNewTextDoc('', NumCast(this.Document.x), NumCast(this.Document.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height));
                 const textField = Doc.LayoutFieldKey(newDoc);
-                Doc.GetProto(newDoc)[`${textField}_recordingSource`] = this.dataDoc;
-                Doc.GetProto(newDoc)[`${textField}_recordingStart`] = ComputedField.MakeFunction(`this.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
-                Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction(`this.${textField}_recordingSource.mediaState`);
+                const newDocData = newDoc[DocData];
+                newDocData[`${textField}_recordingSource`] = this.dataDoc;
+                newDocData[`${textField}_recordingStart`] = ComputedField.MakeFunction(`this.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
+                newDocData.mediaState = ComputedField.MakeFunction(`this.${textField}_recordingSource.mediaState`);
                 if (Doc.IsInMyOverlay(this.Document)) {
                     newDoc.overlayX = this.Document.x;
                     newDoc.overlayY = NumCast(this.Document.y) + NumCast(this.layoutDoc._height);
@@ -432,7 +433,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     // plays link
-    playLink = (link: Doc, options: DocFocusOptions) => {
+    playLink = (link: Doc, options: FocusViewOptions) => {
         if (link.annotationOn === this.Document) {
             if (!this.layoutDoc.dontAutoPlayFollowedLinks) {
                 this.playFrom(this.timeline?.anchorStart(link) || 0, this.timeline?.anchorEnd(link));
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 73709c17e..0ae4ed62c 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -38,8 +38,8 @@ export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentView
     CollectionFreeFormView: CollectionFreeFormView;
 }
 @observer
-export class CollectionFreeFormDocumentViewWrapper extends ObservableReactComponent<CollectionFreeFormDocumentViewWrapperProps> implements CollectionFreeFormDocumentViewProps {
-    constructor(props: any) {
+export class CollectionFreeFormDocumentViewWrapper extends ObservableReactComponent<CollectionFreeFormDocumentViewWrapperProps> {
+    constructor(props: CollectionFreeFormDocumentViewWrapperProps) {
         super(props);
         makeObservable(this);
     }
@@ -104,7 +104,7 @@ export class CollectionFreeFormDocumentViewWrapper extends ObservableReactCompon
         );
     }
 }
-export interface CollectionFreeFormDocumentViewProps {
+export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     w_X: () => number;
     w_Y: () => number;
     w_Z: () => number;
@@ -125,8 +125,8 @@ export interface CollectionFreeFormDocumentViewProps {
 }
 
 @observer
-export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & DocumentViewProps>() {
-    constructor(props: any) {
+export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps>() {
+    constructor(props: CollectionFreeFormDocumentViewProps) {
         super(props);
         makeObservable(this);
     }
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 500eb52ac..116dc48a6 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -8,20 +8,20 @@ import { DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { DocUtils, Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import './ComparisonBox.scss';
-import { DocumentView, returnEmptyDocViewList } from './DocumentView';
+import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { PinProps, PresBox } from './trails';
 
 @observer
-export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(ComparisonBox, fieldKey);
     }
     private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined];
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
     }
@@ -35,7 +35,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         return '_' + this._props.fieldKey + '_clipWidth';
     }
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
     protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => {
         this._disposers[disposerId]?.();
@@ -152,9 +152,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         if (property === StyleProp.PointerEvents) return 'none';
         return this._props.styleProvider?.(doc, props, property);
     };
-    @computed get childContainerViewPath() {
-        return this.DocumentView?.().docViewPath ?? returnEmptyDocViewList;
-    }
     moveDoc1 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true);
     moveDoc2 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true);
     remDoc1 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true);
@@ -181,7 +178,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                         {...this._props}
                         Document={targetDoc}
                         TemplateDataDocument={undefined}
-                        containerViewPath={this.childContainerViewPath}
+                        containerViewPath={this.DocumentView?.().docViewPath}
                         moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
                         removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2}
                         NativeWidth={returnZero}
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 5ceec98cb..8365f4770 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -14,13 +14,13 @@ import { TraceMobx } from '../../../../fields/util';
 import { Docs } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { UndoManager, undoable } from '../../../util/UndoManager';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
 import { MarqueeAnnotator } from '../../MarqueeAnnotator';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
-import { DocFocusOptions, DocumentView } from '../DocumentView';
-import { FieldView, FieldViewProps } from '../FieldView';
+import { DocumentView } from '../DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
 import { PinProps } from '../trails';
 import './DataVizBox.scss';
 import { Histogram } from './components/Histogram';
@@ -36,7 +36,7 @@ export enum DataVizView {
 }
 
 @observer
-export class DataVizBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _marqueeref = React.createRef<MarqueeAnnotator>();
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
@@ -46,10 +46,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatablePr
     @observable _marqueeing: number[] | undefined = undefined;
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
 
-    constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
 
     @computed get annotationLayer() {
@@ -233,7 +233,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatablePr
             () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar')
         );
     };
-    getView = async (doc: Doc, options: DocFocusOptions) => {
+    getView = async (doc: Doc, options: FocusViewOptions) => {
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
             options.didMove = true;
             this.toggleSidebar();
@@ -254,7 +254,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatablePr
     sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => this.removeDocument(doc, sidebarKey);
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData();
     }
 
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 55859b92b..9538cebb2 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -24,7 +24,6 @@ import { YoutubeBox } from './../../apis/youtube/YoutubeBox';
 import { AudioBox } from './AudioBox';
 import { ComparisonBox } from './ComparisonBox';
 import { DataVizBox } from './DataVizBox/DataVizBox';
-import { DocumentViewProps } from './DocumentView';
 import './DocumentView.scss';
 import { EquationBox } from './EquationBox';
 import { FieldView, FieldViewProps } from './FieldView';
@@ -114,13 +113,11 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
     }
 }
 
+export interface DocumentContentsViewProps extends FieldViewProps {
+    layoutFieldKey: string;
+}
 @observer
-export class DocumentContentsView extends ObservableReactComponent<
-    FieldViewProps & {
-        onClick?: () => ScriptField;
-        LayoutTemplate?: () => Opt<Doc>;
-    }
-> {
+export class DocumentContentsView extends ObservableReactComponent<DocumentContentsViewProps> {
     constructor(props: any) {
         super(props);
         makeObservable(this);
@@ -130,8 +127,8 @@ export class DocumentContentsView extends ObservableReactComponent<
         TraceMobx();
         if (this._props.LayoutTemplateString) return this._props.LayoutTemplateString;
         if (!this.layoutDoc) return '<p>awaiting layout</p>';
-        if (this._props.fieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString());
-        const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.fieldKey ? this._props.fieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
+        if (this._props.layoutFieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString());
+        const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
         if (layout === undefined) return this._props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString();
         if (typeof layout === 'string') return layout;
         return '<p>Loading layout</p>';
@@ -143,26 +140,26 @@ export class DocumentContentsView extends ObservableReactComponent<
         const template: Doc =
             this._props.LayoutTemplate?.() ||
             (this._props.LayoutTemplateString && this._props.Document) ||
-            (this._props.fieldKey && StrCast(this._props.Document[this._props.fieldKey]) && this._props.Document) ||
-            Doc.Layout(this._props.Document, this._props.fieldKey ? Cast(this._props.Document[this._props.fieldKey], Doc, null) : undefined);
+            (this._props.layoutFieldKey && StrCast(this._props.Document[this._props.layoutFieldKey]) && this._props.Document) ||
+            Doc.Layout(this._props.Document, this._props.layoutFieldKey ? Cast(this._props.Document[this._props.layoutFieldKey], Doc, null) : undefined);
         return Doc.expandTemplateLayout(template, this._props.Document);
     }
 
     CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings {
         const docOnlyProps = [
-            // these are the properties in  DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews
+            // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews
             'hideResizeHandles',
             'hideTitle',
             'contentPointerEvents',
-            'radialMenu',
             'LayoutTemplateString',
             'LayoutTemplate',
+            'layoutFieldKey',
             'dontCenter',
             'contextMenuItems',
             //'onClick', // don't need to omit this since it will be set
-            'onDoubleClick',
-            'onPointerDown',
-            'onPointerUp',
+            'onDoubleClickScript',
+            'onPointerDownScript',
+            'onPointerUpScript',
         ];
         const templateDataDoc = this._props.TemplateDataDocument ?? (this.layoutDoc !== this._props.Document ? this._props.Document[DocData] : undefined);
         const list: BindingProps & React.DetailedHTMLProps<React.HtmlHTMLAttributes<HTMLDivElement>, HTMLDivElement> = {
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index 5ce08cdc3..4a22766cc 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -29,7 +29,7 @@ export class DocumentIcon extends ObservableReactComponent<DocumentIconProps> {
     }
     render() {
         const view = this._props.view;
-        const { left, top, right, bottom } = view.getBounds() || { left: 0, top: 0, right: 0, bottom: 0 };
+        const { left, top, right, bottom } = view.getBounds || { left: 0, top: 0, right: 0, bottom: 0 };
 
         return (
             <div
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index f5949a451..d1805308d 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -17,6 +17,7 @@ import { DocumentView } from './DocumentView';
 import { LinkDescriptionPopup } from './LinkDescriptionPopup';
 import { TaskCompletionBox } from './TaskCompletedBox';
 import { PinProps } from './trails';
+import { DocData } from '../../../fields/DocSymbols';
 
 interface DocumentLinksButtonProps {
     View: DocumentView;
@@ -158,41 +159,40 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
 
                 LinkManager.currentLink = linkDoc;
 
-                if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
-                    // if linking from a Hypothes.is annotation
-                    Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
-                    Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
-                    Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
-                    const dashHyperlink = Doc.globalServerPath(startIsAnnotation ? endLink : startLink);
-                    Hypothesis.makeLink(StrCast(startIsAnnotation ? endLink.title : startLink.title), dashHyperlink, DocumentLinksButton.AnnotationId, startIsAnnotation ? startLink : endLink); // edit annotation to add a Dash hyperlink to the linked doc
-                }
-
                 if (linkDoc) {
+                    if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
+                        // if linking from a Hypothes.is annotation
+                        const linkDocData = linkDoc[DocData];
+                        linkDocData.linksToAnnotation = true;
+                        linkDocData.annotationId = DocumentLinksButton.AnnotationId;
+                        linkDocData.annotationUri = DocumentLinksButton.AnnotationUri;
+                        const dashHyperlink = Doc.globalServerPath(startIsAnnotation ? endLink : startLink);
+                        Hypothesis.makeLink(StrCast(startIsAnnotation ? endLink.title : startLink.title), dashHyperlink, DocumentLinksButton.AnnotationId, startIsAnnotation ? startLink : endLink); // edit annotation to add a Dash hyperlink to the linked doc
+                    }
+
                     TaskCompletionBox.textDisplayed = 'Link Created';
                     TaskCompletionBox.popupX = screenX;
                     TaskCompletionBox.popupY = screenY - 133;
                     TaskCompletionBox.taskCompleted = true;
 
-                    if (LinkDescriptionPopup.showDescriptions === 'ON' || !LinkDescriptionPopup.showDescriptions) {
-                        LinkDescriptionPopup.popupX = screenX;
-                        LinkDescriptionPopup.popupY = screenY - 100;
-                        LinkDescriptionPopup.descriptionPopup = true;
+                    if (LinkDescriptionPopup.Instance.showDescriptions === 'ON' || !LinkDescriptionPopup.Instance.showDescriptions) {
+                        LinkDescriptionPopup.Instance.popupX = screenX;
+                        LinkDescriptionPopup.Instance.popupY = screenY - 100;
+                        LinkDescriptionPopup.Instance.display = true;
                     }
 
                     const rect = document.body.getBoundingClientRect();
-                    if (LinkDescriptionPopup.popupX + 200 > rect.width) {
-                        LinkDescriptionPopup.popupX -= 190;
+                    if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) {
+                        LinkDescriptionPopup.Instance.popupX -= 190;
                         TaskCompletionBox.popupX -= 40;
                     }
-                    if (LinkDescriptionPopup.popupY + 100 > rect.height) {
-                        LinkDescriptionPopup.popupY -= 40;
+                    if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) {
+                        LinkDescriptionPopup.Instance.popupY -= 40;
                         TaskCompletionBox.popupY -= 40;
                     }
 
                     setTimeout(
-                        action(() => {
-                            TaskCompletionBox.taskCompleted = false;
-                        }),
+                        action(() => (TaskCompletionBox.taskCompleted = false)),
                         2500
                     );
                 }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 444c300f3..3e5e43b47 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -3,19 +3,17 @@ import { Dropdown, DropdownType, Type } from 'browndash-components';
 import { Howl } from 'howler';
 import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { computedFn } from 'mobx-utils';
 import * as React from 'react';
 import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal';
 import { Utils, emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../Utils';
 import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
-import { AclPrivate, Animation, AudioPlay, DocViews } from '../../../fields/DocSymbols';
+import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
-import { RefField } from '../../../fields/RefField';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { AudioField } from '../../../fields/URLField';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
 import { DocServer } from '../../DocServer';
@@ -33,34 +31,27 @@ import { SelectionManager } from '../../util/SelectionManager';
 import { SettingsManager } from '../../util/SettingsManager';
 import { SharingManager } from '../../util/SharingManager';
 import { SnappingManager } from '../../util/SnappingManager';
-import { Transform } from '../../util/Transform';
-import { UndoManager, undoBatch } from '../../util/UndoManager';
+import { UndoManager, undoBatch, undoable } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { DocComponent } from '../DocComponent';
+import { DocComponent, ViewBoxInterface } from '../DocComponent';
 import { EditableView } from '../EditableView';
 import { GestureOverlay } from '../GestureOverlay';
 import { LightboxView } from '../LightboxView';
-import { ObservableReactComponent } from '../ObservableReactComponent';
 import { StyleProp } from '../StyleProvider';
-import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
 import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView';
 import { DocumentLinksButton } from './DocumentLinksButton';
 import './DocumentView.scss';
-import { FieldViewProps } from './FieldView';
+import { FieldViewProps, FieldViewSharedProps } from './FieldView';
 import { KeyValueBox } from './KeyValueBox';
 import { LinkAnchorBox } from './LinkAnchorBox';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { PresEffect, PresEffectDirection } from './trails';
-import { PinProps, PresBox } from './trails/PresBox';
-
 interface Window {
     MediaRecorder: MediaRecorder;
 }
-
 declare class MediaRecorder {
-    // whatever MediaRecorder has
-    constructor(e: any);
+    constructor(e: any); // whatever MediaRecorder has
 }
 
 export enum OpenWhereMod {
@@ -92,114 +83,7 @@ export enum OpenWhere {
 export function returnEmptyDocViewList() {
     return [] as DocumentView[];
 }
-export interface DocFocusOptions {
-    willPan?: boolean; // determines whether to pan to target document
-    willZoomCentered?: boolean; // determines whether to zoom in on target document.  if zoomScale is 0, this just centers the document
-    zoomScale?: number; // percent of containing frame to zoom into document
-    zoomTime?: number;
-    didMove?: boolean; // whether a document was changed during the showDocument process
-    docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom.  when this happens the docTransform must accumulate all the transforms of each level of the hierarchy
-    instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom)
-    preview?: boolean; // whether changes should be previewed by the componentView or written to the document
-    effect?: Doc; // animation effect for focus
-    noSelect?: boolean; // whether target should be selected after focusing
-    playAudio?: boolean; // whether to play audio annotation on focus
-    playMedia?: boolean; // whether to play start target videos
-    openLocation?: OpenWhere; // where to open a missing document
-    zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections
-    toggleTarget?: boolean; // whether to toggle target on and off
-    anchorDoc?: Doc; // doc containing anchor info to apply at end of focus to target doc
-    easeFunc?: 'linear' | 'ease'; // transition method for scrolling
-}
-export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => Opt<number>;
-export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => any;
-export interface DocComponentView {
-    fieldKey?: string;
-    annotationKey?: string;
-    updateIcon?: () => void; // updates the icon representation of the document
-    getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
-    restoreView?: (viewSpec: Doc) => boolean;
-    scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: DocFocusOptions) => Opt<number>; // returns the duration of the focus
-    brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number) => void; // highlight a region of a view (used by freeforms)
-    getView?: (doc: Doc, options: DocFocusOptions) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined
-    addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox
-    addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections)
-    select?: (ctrlKey: boolean, shiftKey: boolean) => void;
-    focus?: (textAnchor: Doc, options: DocFocusOptions) => Opt<number>;
-    isAnyChildContentActive?: () => boolean; // is any child content of the document active
-    onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected
-    getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
-    setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
-    playFrom?: (time: number, endTime?: number) => void;
-    Pause?: () => void; // pause a media document (eg, audio/video)
-    IsPlaying?: () => boolean; // is a media document playing
-    TogglePause?: (keep?: boolean) => void; // toggle media document playing state
-    setFocus?: () => void; // sets input focus to the componentView
-    setData?: (data: Field | Promise<RefField | undefined>) => boolean;
-    componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
-    dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void;
-    incrementalRendering?: () => void;
-    infoUI?: () => JSX.Element | null;
-    screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>;
-    ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
-    ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
-    snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number };
-    search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
-}
-/**
- * props that DocumentViews, DocumentInternalViews and FieldViews all can use
- * */
-export interface DocumentViewSharedProps {
-    Document: Doc;
-    LayoutTemplateString?: string;
-    TemplateDataDocument?: Doc;
-    renderDepth: number;
-    scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
-    treeViewDoc?: Doc;
-    xPadding?: number;
-    yPadding?: number;
-    dontRegisterView?: boolean;
-    dropAction?: dropActionType;
-    dragAction?: dropActionType;
-    forceAutoHeight?: boolean;
-    ignoreAutoHeight?: boolean;
-    disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
-    CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
-    containerViewPath?: () => DocumentView[];
-    fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
-    isGroupActive?: () => string | undefined; // is this document part of a group that is active
-    setContentView?: (view: DocComponentView) => any;
-    PanelWidth: () => number;
-    PanelHeight: () => number;
-    isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
-    isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
-    childFilters: () => string[];
-    childFiltersByRanges: () => string[];
-    styleProvider: Opt<StyleProviderFunc>;
-    setTitleFocus?: () => void;
-    focus: DocFocusFunc;
-    layout_fitWidth?: (doc: Doc) => boolean | undefined;
-    searchFilterDocs: () => Doc[];
-    layout_showTitle?: () => string;
-    whenChildContentsActiveChanged: (isActive: boolean) => void;
-    rootSelected?: () => boolean; // whether the root of a template has been selected
-    addDocTab: (doc: Doc, where: OpenWhere) => boolean;
-    filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
-    addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
-    removeDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
-    moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => boolean;
-    pinToPres: (document: Doc, pinProps: PinProps) => void;
-    ScreenToLocalTransform: () => Transform;
-    bringToFront: (doc: Doc, sendToBack?: boolean) => void;
-    waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
-    defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
-    pointerEvents?: () => Opt<string>;
-}
-
-/**
- * props that are used by DocumentViews and DocumentInternalViews but not by the contents of those views (FieldViews).
- */
-export interface DocumentViewProps extends DocumentViewSharedProps {
+export interface DocumentViewProps extends FieldViewSharedProps {
     hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected
     hideResizeHandles?: boolean; // whether to suppress resized handles on doc decorations when this document is selected
     hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
@@ -221,35 +105,22 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
     NativeWidth?: () => number;
     NativeHeight?: () => number;
-    LayoutTemplate?: () => Opt<Doc>;
     contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[];
-    onClick?: () => ScriptField;
-    onDoubleClick?: () => ScriptField;
-    onPointerDown?: () => ScriptField;
-    onPointerUp?: () => ScriptField;
-    onBrowseClick?: () => ScriptField | undefined;
-    onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
     dragStarting?: () => void;
     dragEnding?: () => void;
 }
-
-/**
- * props used by DocInternalViews and FieldViews but not DocumentViews
- * these props correspond to things that the DocumentView creates and thus doesn't need to receive as a prop
- */
-export interface DocumentViewInternalSharedProps {
-    select: (ctrlPressed: boolean, shiftPress?: boolean) => void;
-    isSelected: () => boolean;
-    docViewPath: () => DocumentView[];
-    NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
-}
-export interface DocumentViewInternalProps extends DocumentViewProps, DocumentViewInternalSharedProps {
-    docViewPublic: () => DocumentView;
-}
-
 @observer
-export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps>() {
+export class DocumentViewInternal extends DocComponent<FieldViewProps & DocumentViewProps>() {
+    // this makes mobx trace() statements more descriptive
+    public get displayName()  { return 'DocumentViewInternal(' + this.Document.title + ')'; } // prettier-ignore
     public static SelectAfterContextMenu = true; //  whether a document should be selected after it's contextmenu is triggered.
+
+    /**
+     * This function is filled in by MainView to allow non-viewBox views to add Docs as tabs without
+     * needing to know about/reference MainView
+     */
+    public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse;
+
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _doubleClickTimeout: NodeJS.Timeout | undefined;
     private _singleClickFunc: undefined | (() => any);
@@ -262,79 +133,51 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     private _mainCont = React.createRef<HTMLDivElement>();
     private _titleRef = React.createRef<EditableView>();
     private _dropDisposer?: DragManager.DragDropDisposer;
-    constructor(props: any) {
+    constructor(props: FieldViewProps & DocumentViewProps) {
         super(props);
         makeObservable(this);
     }
 
-    @observable _componentView: Opt<DocComponentView> = undefined; // needs to be accessed from DocumentView wrapper class
+    @observable _changingTitleField = false;
+    @observable _titleDropDownInnerWidth = 0; // width of menu dropdown when setting doc title
+    @observable _mounted = false; // turn off all pointer events if component isn't yet mounted (enables nested Docs in alternate UI textboxes that appear on hover which otherwise would grab focus from the text box, reverting to the original UI )
+    @observable _isContentActive: boolean | undefined = undefined;
+    @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined = undefined;
+    @observable _componentView: Opt<ViewBoxInterface> = undefined; // needs to be accessed from DocumentView wrapper class
     @observable _animateScaleTime: Opt<number> = undefined; // milliseconds for animating between views.  defaults to 300 if not uset
     @observable _animateScalingTo = 0;
 
-    public get animateScaleTime() {
-        return this._animateScaleTime ?? 100;
-    }
-    public get displayName() {
-        return 'DocumentViewInternal(' + this.Document.title + ')';
-    } // this makes mobx trace() statements more descriptive
-
-    public get DocumentView() {
-        return this._props.docViewPublic;
-    }
+    get _contentDiv()  { return this._mainCont.current; } // prettier-ignore
+    get _docView()     { return this._props.DocumentView?.(); } // prettier-ignore
+
+    animateScaleTime = () => this._animateScaleTime ?? 100;
+    style = (doc: Doc, sprop: StyleProp | string) => this._props.styleProvider?.(doc, this._props, sprop);
+    @computed get layout_showTitle()   { return this.style(this.layoutDoc, StyleProp.ShowTitle) as Opt<string>; } // prettier-ignore
+    @computed get opacity()            { return this.style(this.layoutDoc, StyleProp.Opacity); } // prettier-ignore
+    @computed get boxShadow()          { return this.style(this.layoutDoc, StyleProp.BoxShadow); } // prettier-ignore
+    @computed get borderRounding()     { return this.style(this.layoutDoc, StyleProp.BorderRounding); } // prettier-ignore
+    @computed get widgetDecorations()  { return this.style(this.layoutDoc, StyleProp.Decorations); } // prettier-ignore
+    @computed get backgroundBoxColor() { return this.style(this.layoutDoc, StyleProp.BackgroundColor + ':box'); } // prettier-ignore
+    @computed get headerMargin()       { return this.style(this.layoutDoc, StyleProp.HeaderMargin) ?? 0; } // prettier-ignore
+    @computed get layout_showCaption() { return this.style(this.layoutDoc, StyleProp.ShowCaption) ?? 0;  } // prettier-ignore
+    @computed get titleHeight()        { return this.style(this.layoutDoc, StyleProp.TitleHeight) ?? 0;  } // prettier-ignore
+    @computed get docContents()        { return this.style(this.Document, StyleProp.DocContents); } // prettier-ignore
+    @computed get highlighting()       { return this.style(this.Document, StyleProp.Highlighting); } // prettier-ignore
+    @computed get borderPath()         { return this.style(this.Document, StyleProp.BorderPath); } // prettier-ignore
 
-    public get ContentDiv() {
-        return this._mainCont.current;
-    }
-    public get LayoutFieldKey() {
-        return Doc.LayoutFieldKey(this.layoutDoc);
-    }
-    @computed get styleProps(): FieldViewProps {
-        return { ...this._props, fieldKey: '' };
-    }
-    @computed get layout_showTitle() {
-        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.ShowTitle) as Opt<string>;
-    }
-    @computed get NativeDimScaling() {
-        return this._props.NativeDimScaling?.() || 1;
-    }
-    @computed get thumb() {
-        return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png');
-    }
-    @computed get opacity() {
-        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.Opacity);
-    }
-    @computed get boxShadow() {
-        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.BoxShadow);
-    }
-    @computed get borderRounding() {
-        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.BorderRounding);
-    }
-    @computed get widgetDecorations() {
-        TraceMobx();
-        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.Decorations);
-    }
-    @computed get backgroundBoxColor() {
-        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.BackgroundColor + ':box');
-    }
-    @computed get docContents() {
-        return this._props.styleProvider?.(this.Document, this.styleProps, StyleProp.DocContents);
-    }
-    @computed get headerMargin() {
-        return this._props?.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.HeaderMargin) || 0;
-    }
-    @computed get layout_showCaption() {
-        return this._props?.hideCaptions ? undefined : this._props?.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.ShowCaption) || 0;
+    @computed get onClickHandler() {
+        return this._props.onClickScript?.() ?? this._props.onBrowseClickScript?.() ?? ScriptCast(this.Document.onClick, ScriptCast(this.layoutDoc.onClick));
     }
-    @computed get titleHeight() {
-        return this._props?.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.TitleHeight) || 0;
+    @computed get onDoubleClickHandler() {
+        return this._props.onDoubleClickScript?.() ?? ScriptCast(this.layoutDoc.onDoubleClick, ScriptCast(this.Document.onDoubleClick));
     }
-    @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined = undefined;
-    @computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined {
-        return this._pointerEvents;
+    @computed get onPointerDownHandler() {
+        return this._props.onPointerDownScript?.() ?? ScriptCast(this.layoutDoc.onPointerDown, ScriptCast(this.Document.onPointerDown));
     }
-    @computed get finalLayoutKey() {
-        return StrCast(this.Document.layout_fieldKey, 'layout');
+    @computed get onPointerUpHandler() {
+        return this._props.onPointerUpScript?.() ?? ScriptCast(this.layoutDoc.onPointerUp, ScriptCast(this.Document.onPointerUp));
     }
+
     @computed get disableClickScriptFunc() {
         const onScriptDisable = this._props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
         // prettier-ignore
@@ -344,23 +187,50 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             (onScriptDisable !== 'never' && (this.rootSelected() || this._componentView?.isAnyChildContentActive?.()))
         );
     }
-    @computed get onClickHandler() {
-        return this._props.onClick?.() ?? this._props.onBrowseClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null));
+    @computed get _rootSelected() {
+        return this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.());
     }
-    @computed get onDoubleClickHandler() {
-        return this._props.onDoubleClick?.() ?? Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick;
+    /// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive
+    @computed get _contentPointerEvents() {
+        TraceMobx();
+        return this._props.contentPointerEvents ??
+            ((!this.disableClickScriptFunc && //
+                this.onClickHandler &&
+                !this._props.onBrowseClickScript?.() &&
+                this.isContentActive() !== true) ||
+                this.isContentActive() === false)
+            ? 'none'
+            : this._pointerEvents;
     }
-    @computed get onPointerDownHandler() {
-        return this._props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown);
+
+    // We need to use allrelatedLinks to get not just links to the document as a whole, but links to
+    // anchors that are not rendered as DocumentViews (marked as 'layout_unrendered' with their 'annotationOn' set to this document).  e.g.,
+    //     - PDF text regions are rendered as an Annotations without generating a DocumentView, '
+    //     - RTF selections are rendered via Prosemirror and have a mark which contains the Document ID for the annotation link
+    //     - and links to PDF/Web docs at a certain scroll location never create an explicit view.
+    // For each of these, we create LinkAnchorBox's on the border of the DocumentView.
+    @computed get directLinks() {
+        TraceMobx();
+        return LinkManager.Instance.getAllRelatedLinks(this.Document).filter(
+            link =>
+                (link.link_matchEmbeddings ? link.link_anchor_1 === this.Document : Doc.AreProtosEqual(link.link_anchor_1 as Doc, this.Document)) ||
+                (link.link_matchEmbeddings ? link.link_anchor_2 === this.Document : Doc.AreProtosEqual(link.link_anchor_2 as Doc, this.Document)) ||
+                ((link.link_anchor_1 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_1 as Doc)?.annotationOn as Doc, this.Document)) ||
+                ((link.link_anchor_2 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_2 as Doc)?.annotationOn as Doc, this.Document))
+        );
     }
-    @computed get onPointerUpHandler() {
-        return this._props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp);
+    @computed get _allLinks() {
+        TraceMobx();
+        return LinkManager.Instance.getAllRelatedLinks(this.Document).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document);
+    }
+
+    @computed get filteredLinks() {
+        return DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
     }
 
     componentWillUnmount() {
         this.cleanupHandlers(true);
     }
-    @observable _mounted = false; // turn off all pointer events if component isn't yet mounted (enables nested Docs in alternate UI textboxes that appear on hover which otherwise would grab focus from the text box, reverting to the original UI )
 
     componentDidMount() {
         runInAction(() => (this._mounted = true));
@@ -380,7 +250,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             { fireImmediately: true }
         );
         this._disposers.pointerevents = reaction(
-            () => this._props.styleProvider?.(this.Document, this.styleProps, StyleProp.PointerEvents),
+            () => this.style(this.Document, StyleProp.PointerEvents),
             pointerevents => (this._pointerEvents = pointerevents),
             { fireImmediately: true }
         );
@@ -406,18 +276,19 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
 
     startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
-        if (this._mainCont.current) {
+        const docView = this._docView;
+        if (this._mainCont.current && docView) {
             const views = SelectionManager.Views.filter(dv => dv.ContentDiv);
-            const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [this.DocumentView()];
+            const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [docView];
             const dragData = new DragManager.DocumentDragData(selected.map(dv => dv.Document));
-            const screenXf = this.DocumentView().screenToViewTransform();
+            const screenXf = docView.screenToViewTransform();
             const [left, top] = screenXf.inverse().transformPoint(0, 0);
             dragData.offset = screenXf.transformDirection(x - left, y - top);
             dragData.dropAction = dropAction;
             dragData.treeViewDoc = this._props.treeViewDoc;
             dragData.removeDocument = this._props.removeDocument;
             dragData.moveDocument = this._props.moveDocument;
-            dragData.draggedViews = [this.DocumentView()];
+            dragData.draggedViews = [docView];
             dragData.canEmbed = this.Document.dragAction ?? this._props.dragAction ? true : false;
             DragManager.StartDocumentDrag(
                 selected.map(dv => dv.ContentDiv!),
@@ -434,12 +305,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (!StrCast(this.layoutDoc._layout_showTitle)) this.layoutDoc._layout_showTitle = 'title';
         setTimeout(() => this._titleRef.current?.setIsFocused(true)); // use timeout in case title wasn't shown to allow re-render so that titleref will be defined
     };
-
-    public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse;
-
     onClick = action((e: React.MouseEvent | React.PointerEvent) => {
         if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
-        if (!this.Document.ignoreClick && this._props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
+        const documentView = this._docView;
+        if (documentView && !this.Document.ignoreClick && this._props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
             let stopPropagate = true;
             let preventDefault = true;
             !this.layoutDoc._keepZWhenDragged && this._props.bringToFront(this.Document);
@@ -451,7 +320,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     const func = () => this.onDoubleClickHandler.script.run( {
                             this: this.Document,
                             scriptContext: this._props.scriptContext,
-                            documentView: this.DocumentView(),
+                            documentView,
                             clientX, clientY,  altKey,  shiftKey,  ctrlKey,
                             value: undefined,
                         }, console.log  );
@@ -482,7 +351,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                                 this: this.Document,
                                 _readOnly_: false,
                                 scriptContext: this._props.scriptContext,
-                                documentView: this.DocumentView(),
+                                documentView,
                                 clientX,
                                 clientY,
                                 shiftKey,
@@ -506,7 +375,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 const sendToBack = e.altKey;
                 this._singleClickFunc =
                     // prettier-ignore
-                    clickFunc ?? (() => (sendToBack ? this.DocumentView()._props.bringToFront(this.Document, true) : 
+                    clickFunc ?? (() => (sendToBack ? documentView._props.bringToFront(this.Document, true) : 
                                                       this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ?? 
                                         this._props.select(e.ctrlKey||e.shiftKey, e.metaKey)));
                 const waitFordblclick = this._props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
@@ -525,12 +394,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     onPointerDown = (e: React.PointerEvent): void => {
         if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
-        this._longPressSelector = setTimeout(() => {
-            if (DocumentView.LongPress) {
-                this._props.select(false);
-            }
-        }, 1000);
-        if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this.DocumentView();
+        this._longPressSelector = setTimeout(() => DocumentView.LongPress && this._props.select(false), 1000);
+        if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this._docView;
 
         this._downX = e.clientX;
         this._downY = e.clientY;
@@ -541,7 +406,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             if (
                 // prettier-ignore
                 (this._props.isDocumentActive?.() || this._props.isContentActive?.()) &&
-                !this._props.onBrowseClick?.() &&
+                !this._props.onBrowseClickScript?.() &&
                 !this.Document.ignoreClick &&
                 e.button === 0 &&
                 !Doc.IsInMyOverlay(this.layoutDoc)
@@ -587,38 +452,39 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (DocumentView.LongPress) e.preventDefault();
     };
 
-    @undoBatch
-    toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => {
+    toggleFollowLink = undoable((zoom?: boolean, setTargetToggle?: boolean): void => {
         const hadOnClick = this.Document.onClick;
         this.noOnClick();
         this.Document.onClick = hadOnClick ? undefined : FollowLinkScript();
         this.Document.waitForDoubleClickToClick = hadOnClick ? undefined : 'never';
-    };
-    @undoBatch
-    followLinkOnClick = () => {
+    }, 'toggle follow link');
+
+    followLinkOnClick = undoable(() => {
         this.Document.ignoreClick = false;
         this.Document.onClick = FollowLinkScript();
         this.Document.followLinkToggle = false;
         this.Document.followLinkZoom = false;
         this.Document.followLinkLocation = undefined;
-    };
-    @undoBatch
-    noOnClick = () => {
-        this.Document.ignoreClick = false;
-        this.Document.onClick = Doc.GetProto(this.Document).onClick = undefined;
-    };
-
-    @undoBatch deleteClicked = () => this._props.removeDocument?.(this.Document);
-    @undoBatch setToggleDetail = () =>
-        (this.Document.onClick = ScriptField.MakeScript(
-            `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
-                .replace('layout_', '')
-                .replace(/^layout$/, 'detail')}")`,
-            { documentView: 'any' }
-        ));
+    }, 'follow link on click');
 
-    @undoBatch
-    drop = (e: Event, de: DragManager.DropEvent) => {
+    noOnClick = undoable(() => {
+        this.Document.ignoreClick = false;
+        this.Document.onClick = this.Document[DocData].onClick = undefined;
+    }, 'default on click');
+
+    deleteClicked = undoable(() => this._props.removeDocument?.(this.Document), 'delete doc');
+    setToggleDetail = undoable(
+        () =>
+            (this.Document.onClick = ScriptField.MakeScript(
+                `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
+                    .replace('layout_', '')
+                    .replace(/^layout$/, 'detail')}")`,
+                { documentView: 'any' }
+            )),
+        'set toggle detail'
+    );
+
+    drop = undoable((e: Event, de: DragManager.DropEvent) => {
         if (this._props.dontRegisterView || this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false;
         if (this.Document === Doc.ActiveDashboard) {
             e.stopPropagation();
@@ -642,7 +508,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]);
                     if (de.complete.linkDocument) {
                         de.complete.linkDocument.layout_isSvg = true;
-                        this.DocumentView().CollectionFreeFormView?.addDocument(de.complete.linkDocument);
+                        this._docView?.CollectionFreeFormView?.addDocument(de.complete.linkDocument);
                     }
                 }
                 e.stopPropagation();
@@ -650,11 +516,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             }
         }
         return false;
-    };
+    }, 'drop doc');
 
-    @undoBatch
-    makeIntoPortal = () => {
-        const portalLink = this.allLinks.find(d => d.link_anchor_1 === this.Document && d.link_relationship === 'portal to:portal from');
+    makeIntoPortal = undoable(() => {
+        const portalLink = this._allLinks.find(d => d.link_anchor_1 === this.Document && d.link_relationship === 'portal to:portal from');
         if (!portalLink) {
             DocUtils.MakeLink(
                 this.Document,
@@ -670,7 +535,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
         this.Document.followLinkLocation = OpenWhere.lightbox;
         this.Document.onClick = FollowLinkScript();
-    };
+    }, 'make into portal');
 
     importDocument = () => {
         const input = document.createElement('input');
@@ -716,7 +581,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
         if (e && !(e.nativeEvent as any).dash) {
             const onDisplay = () => {
-                if (this.Document.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this._props.isSelected() && SelectionManager.SelectView(this.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
+                if (this.Document.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && this._props.select(false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
                 setTimeout(() => simulateMouseClick(document.elementFromPoint(e.clientX, e.clientY), e.clientX, e.clientY, e.screenX, e.screenY));
             };
             if (navigator.userAgent.includes('Macintosh')) {
@@ -811,7 +676,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const constantItems: ContextMenuProps[] = [];
         if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking) {
             constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this.Document) });
-            (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.DocumentView()), icon: 'users' });
+            (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this._docView), icon: 'users' });
             if (this._props.removeDocument && Doc.ActiveDashboard !== this.Document) {
                 // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
                 constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
@@ -877,63 +742,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15, undefined, undefined, undefined);
     };
 
-    @computed get _rootSelected() {
-        return this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.());
-    }
     rootSelected = () => this._rootSelected;
     panelHeight = () => this._props.PanelHeight() - this.headerMargin;
     screenToLocalContent = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
-    onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
+    onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHandler;
     setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = height);
-    setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
-    @observable _isContentActive: boolean | undefined = undefined;
-
+    setContentView = action((view: ViewBoxInterface) => (this._componentView = view));
     isContentActive = (): boolean | undefined => this._isContentActive;
     childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
 
-    /// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive
-    @computed get _contentPointerEvents() {
-        TraceMobx();
-        return this._props.contentPointerEvents ??
-            ((!this.disableClickScriptFunc && //
-                this.onClickHandler &&
-                !this._props.onBrowseClick?.() &&
-                this.isContentActive() !== true) ||
-                this.isContentActive() === false)
-            ? 'none'
-            : this.pointerEvents;
-    }
     contentPointerEvents = () => this._contentPointerEvents;
-    @computed get contents() {
-        TraceMobx();
-        const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
-        const noBackground = this.Document.isGroup && !this._props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
-        return (
-            <div
-                className="documentView-contentsView"
-                style={{
-                    pointerEvents: (isInk || noBackground ? 'none' : this.contentPointerEvents()) ?? (this._mounted ? 'all' : 'none'),
-                    height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
-                }}>
-                <DocumentContentsView
-                    key={1}
-                    {...this.styleProps}
-                    fieldKey={this.finalLayoutKey}
-                    pointerEvents={this.contentPointerEvents}
-                    setContentView={this.setContentView}
-                    childFilters={this.childFilters}
-                    PanelHeight={this.panelHeight}
-                    setHeight={this.setHeight}
-                    isContentActive={this.isContentActive}
-                    ScreenToLocalTransform={this.screenToLocalContent}
-                    rootSelected={this.rootSelected}
-                    onClick={this.onClickFunc}
-                    setTitleFocus={this.setTitleFocus}
-                />
-                {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints}
-            </div>
-        );
-    }
 
     anchorPanelWidth = () => this._props.PanelWidth() || 1;
     anchorPanelHeight = () => this._props.PanelHeight() || 1;
@@ -950,33 +768,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
         return this._props.styleProvider?.(doc, props, property);
     };
-    // We need to use allrelatedLinks to get not just links to the document as a whole, but links to
-    // anchors that are not rendered as DocumentViews (marked as 'layout_unrendered' with their 'annotationOn' set to this document).  e.g.,
-    //     - PDF text regions are rendered as an Annotations without generating a DocumentView, '
-    //     - RTF selections are rendered via Prosemirror and have a mark which contains the Document ID for the annotation link
-    //     - and links to PDF/Web docs at a certain scroll location never create an explicit view.
-    // For each of these, we create LinkAnchorBox's on the border of the DocumentView.
-    @computed get directLinks() {
-        TraceMobx();
-        return LinkManager.Instance.getAllRelatedLinks(this.Document).filter(
-            link =>
-                (link.link_matchEmbeddings ? link.link_anchor_1 === this.Document : Doc.AreProtosEqual(link.link_anchor_1 as Doc, this.Document)) ||
-                (link.link_matchEmbeddings ? link.link_anchor_2 === this.Document : Doc.AreProtosEqual(link.link_anchor_2 as Doc, this.Document)) ||
-                ((link.link_anchor_1 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_1 as Doc)?.annotationOn as Doc, this.Document)) ||
-                ((link.link_anchor_2 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_2 as Doc)?.annotationOn as Doc, this.Document))
-        );
-    }
-    @computed get allLinks() {
-        TraceMobx();
-        return LinkManager.Instance.getAllRelatedLinks(this.Document);
-    }
-    hideLink = computedFn((link: Doc) => () => (link.link_displayLine = false));
-    @computed get allLinkEndpoints() {
+
+    removeLinkByHiding = (link: Doc) => () => (link.link_displayLine = false);
+    allLinkEndpoints = () => {
         // the small blue dots that mark the endpoints of links
-        TraceMobx();
         if (this._componentView instanceof KeyValueBox || this._props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this._props.dontRegisterView || this.layoutDoc.layout_unrendered) return null;
-        const filtered = DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
-        return filtered.map(link => (
+        return this.filteredLinks.map(link => (
             <div className="documentView-anchorCont" key={link[Id]}>
                 <DocumentView
                     {...this._props}
@@ -990,107 +787,54 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     hideCaptions={true}
                     hideLinkAnchors={true}
                     layout_fitWidth={returnTrue}
-                    removeDocument={this.hideLink(link)}
+                    removeDocument={this.removeLinkByHiding(link)}
                     styleProvider={this.anchorStyleProvider}
                     LayoutTemplate={undefined}
                     LayoutTemplateString={LinkAnchorBox.LayoutString(`link_anchor_${LinkManager.anchorIndex(link, this.Document)}`)}
                 />
             </div>
         ));
-    }
-
-    static recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) {
-        let gumStream: any;
-        let recorder: any;
-        navigator.mediaDevices
-            .getUserMedia({
-                audio: true,
-            })
-            .then(function (stream) {
-                let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null);
-                if (audioTextAnnos) audioTextAnnos.push('');
-                else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List<string>(['']);
-                DictationManager.Controls.listen({
-                    interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value),
-                    continuous: { indefinite: false },
-                }).then(results => {
-                    if (results && [DictationManager.Controls.Infringed].includes(results)) {
-                        DictationManager.Controls.stop();
-                    }
-                    onEnd?.();
-                });
+    };
 
-                gumStream = stream;
-                recorder = new MediaRecorder(stream);
-                recorder.ondataavailable = async (e: any) => {
-                    const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
-                    if (!(result instanceof Error)) {
-                        const audioField = new AudioField(result.accessPaths.agnostic.client);
-                        const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null);
-                        if (audioAnnos === undefined) {
-                            dataDoc[field + '_audioAnnotations'] = new List([audioField]);
-                        } else {
-                            audioAnnos.push(audioField);
-                        }
-                    }
-                };
-                //runInAction(() => (dataDoc.audioAnnoState = 'recording'));
-                recorder.start();
-                const stopFunc = () => {
-                    recorder.stop();
-                    DictationManager.Controls.stop(false);
-                    runInAction(() => (dataDoc.audioAnnoState = 'stopped'));
-                    gumStream.getAudioTracks()[0].stop();
-                };
-                if (onRecording) onRecording(stopFunc);
-                else setTimeout(stopFunc, 5000);
-            });
-    }
-    playAnnotation = () => {
-        const self = this;
-        const audioAnnoState = this.dataDoc.audioAnnoState ?? 'stopped';
-        const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '_audioAnnotations'], listSpec(AudioField), null);
-        const anno = audioAnnos?.lastElement();
-        if (anno instanceof AudioField) {
-            switch (audioAnnoState) {
-                case 'stopped':
-                    this.dataDoc[AudioPlay] = new Howl({
-                        src: [anno.url.href],
-                        format: ['mp3'],
-                        autoplay: true,
-                        loop: false,
-                        volume: 0.5,
-                        onend: action(() => (self.dataDoc.audioAnnoState = 'stopped')),
-                    });
-                    this.dataDoc.audioAnnoState = 'playing';
-                    break;
-                case 'playing':
-                    this.dataDoc[AudioPlay]?.stop();
-                    this.dataDoc.audioAnnoState = 'stopped';
-                    break;
-            }
-        }
+    viewBoxContents = () => {
+        TraceMobx();
+        const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
+        const noBackground = this.Document.isGroup && !this._props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
+        return (
+            <div
+                className="documentView-contentsView"
+                style={{
+                    pointerEvents: (isInk || noBackground ? 'none' : this.contentPointerEvents()) ?? (this._mounted ? 'all' : 'none'),
+                    height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
+                }}>
+                <DocumentContentsView
+                    {...this._props}
+                    layoutFieldKey={StrCast(this.Document.layout_fieldKey, 'layout')}
+                    pointerEvents={this.contentPointerEvents}
+                    setContentViewBox={this.setContentView}
+                    childFilters={this.childFilters}
+                    PanelHeight={this.panelHeight}
+                    setHeight={this.setHeight}
+                    isContentActive={this.isContentActive}
+                    ScreenToLocalTransform={this.screenToLocalContent}
+                    rootSelected={this.rootSelected}
+                    onClickScript={this.onClickFunc}
+                    setTitleFocus={this.setTitleFocus}
+                />
+                {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints()}
+            </div>
+        );
     };
 
     captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
-    @observable _changingTitleField = false;
-    @observable _dropDownInnerWidth = 0;
-    fieldsDropdown = (inputOptions: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => {
-        const filteredOptions = new Set(inputOptions);
-        const scaling = this.titleHeight / 30; /* height of Dropdown */
-        Object.entries(DocOptions)
-            .filter(opts => opts[1].filterable)
-            .forEach((pair: [string, FInfo]) => filteredOptions.add(pair[0]));
-        filteredOptions.add(StrCast(this.layoutDoc.layout_showTitle));
-        const options = Array.from(filteredOptions)
-            .filter(f => f)
-            .map(facet => ({ val: facet, text: facet }));
+    fieldsDropdown = (reqdFields: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => {
+        const filteredFields = Object.entries(DocOptions).reduce((set, [field, opts]) => (opts.filterable ? set.add(field) : set), new Set(reqdFields));
         return (
             <div style={{ width: dropdownWidth }}>
                 <div
-                    ref={action((r: any) => r && (this._dropDownInnerWidth = Number(getComputedStyle(r).width.replace('px', ''))))}
+                    ref={action((r: any) => r && (this._titleDropDownInnerWidth = Number(getComputedStyle(r).width.replace('px', ''))))}
                     onPointerDown={action(e => (this._changingTitleField = true))}
-                    style={{ width: 'max-content', transformOrigin: 'left', transform: `scale(${scaling})` }}>
+                    style={{ width: 'max-content', transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}>
                     <Dropdown
                         activeChanged={action(isOpen => !isOpen && (this._changingTitleField = false))}
                         selectedVal={placeholder}
@@ -1100,7 +844,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         type={Type.TERT}
                         closeOnSelect={true}
                         dropdownType={DropdownType.SELECT}
-                        items={options}
+                        items={Array.from(filteredFields).map(facet => ({ val: facet, text: facet }))}
                         width={100}
                         fillWidth
                     />
@@ -1108,43 +852,24 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             </div>
         );
     };
-    @computed get innards() {
-        TraceMobx();
+    /**
+     * displays a 'title' at the top of a document. The title contents default to the 'title' field, but can be changed to one or more fields by
+     * setting layout_showTitle using the format:   field1[;field2[...][:hover]]
+     * from the UI, this is done by clicking the title field and prefixin the format with '#'.  eg.,  #field1[;field2;...][:hover]
+     **/
+    titleView = () => {
         const showTitle = this.layout_showTitle?.split(':')[0];
         const showTitleHover = this.layout_showTitle?.includes(':hover');
-        const captionView = !this.layout_showCaption ? null : (
-            <div
-                className="documentView-captionWrapper"
-                style={{
-                    pointerEvents: this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
-                    background: StrCast(this.layoutDoc._backgroundColor, 'rgba(0,0,0,0.2)'),
-                    color: lightOrDark(StrCast(this.layoutDoc._backgroundColor, 'black')),
-                }}>
-                <FormattedTextBox
-                    {...this._props}
-                    yPadding={10}
-                    xPadding={10}
-                    fieldKey={this.layout_showCaption}
-                    styleProvider={this.captionStyleProvider}
-                    dontRegisterView={true}
-                    noSidebar={true}
-                    dontScale={true}
-                    renderDepth={this._props.renderDepth}
-                    isContentActive={this.isContentActive}
-                />
-            </div>
-        );
-        const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.Document;
-        const background = StrCast(
-            this.layoutDoc.layout_headingColor,
-            StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor))
-        );
-        const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._dropDownInnerWidth * this.titleHeight) / 30) : 0;
-        const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', '');
-        // displays a 'title' at the top of a document. The title contents default to the 'title' field, but can be changed to one or more fields by
-        // setting layout_showTitle using the format:   field1[;field2[...][:hover]]
-        // from the UI, this is done by clicking the title field and prefixin the format with '#'.  eg.,  #field1[;field2;...][:hover]
-        const titleView = !showTitle ? null : (
+
+        const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.Document;
+        const background = StrCast(
+            this.layoutDoc.layout_headingColor,
+            StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor))
+        );
+        const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._titleDropDownInnerWidth * this.titleHeight) / 30) : 0;
+        const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', '');
+
+        return !showTitle ? null : (
             <div
                 className={`documentView-titleWrapper${showTitleHover ? '-hover' : ''}`}
                 key="title"
@@ -1160,7 +885,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 {!dropdownWidth
                     ? null
                     : this.fieldsDropdown(
-                          [],
+                          [StrCast(this.layoutDoc.layout_showTitle)],
                           dropdownWidth,
                           StrCast(this.layoutDoc.layout_showTitle).split(':')[0],
                           action((field: string | number) => {
@@ -1206,19 +931,36 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 </div>
             </div>
         );
-        return this._props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
-            this.contents
-        ) : (
-            <div className="documentView-styleWrapper">
-                {titleView}
-                {this.contents}
-                {captionView}
+    };
+
+    captionView = () => {
+        return !this.layout_showCaption ? null : (
+            <div
+                className="documentView-captionWrapper"
+                style={{
+                    pointerEvents: this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
+                    background: StrCast(this.layoutDoc._backgroundColor, 'rgba(0,0,0,0.2)'),
+                    color: lightOrDark(StrCast(this.layoutDoc._backgroundColor, 'black')),
+                }}>
+                <FormattedTextBox
+                    {...this._props}
+                    yPadding={10}
+                    xPadding={10}
+                    fieldKey={this.layout_showCaption}
+                    styleProvider={this.captionStyleProvider}
+                    dontRegisterView={true}
+                    noSidebar={true}
+                    dontScale={true}
+                    renderDepth={this._props.renderDepth}
+                    isContentActive={this.isContentActive}
+                />
             </div>
         );
-    }
+    };
 
     renderDoc = (style: object) => {
         TraceMobx();
+        const showTitle = this.layout_showTitle?.split(':')[0];
         return !DocCast(this.Document) || GetEffectiveAcl(this.dataDoc) === AclPrivate
             ? null
             : this.docContents ?? (
@@ -1234,49 +976,22 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                           fontFamily: StrCast(this.Document._text_fontFamily, 'inherit'),
                           fontSize: Cast(this.Document._text_fontSize, 'string', null),
                           transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
-                          transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this.animateScaleTime / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
+                          transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this.animateScaleTime() / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
                       }}>
-                      {this.innards}
+                      {this._props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
+                          this.viewBoxContents()
+                      ) : (
+                          <div className="documentView-styleWrapper">
+                              {this.titleView()}
+                              {this.viewBoxContents()}
+                              {this.captionView()}
+                          </div>
+                      )}
                       {this.widgetDecorations ?? null}
                   </div>
               );
     };
 
-    /**
-     * returns an entrance animation effect function to wrap a JSX element
-     * @param presEffectDoc presentation effects document that specifies the animation effect parameters
-     * @returns a function that will wrap a JSX animation element wrapping any JSX element
-     */
-    public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt<Doc>, root: Doc) {
-        const dir = presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection;
-        const effectProps = {
-            left: dir === PresEffectDirection.Left,
-            right: dir === PresEffectDirection.Right,
-            top: dir === PresEffectDirection.Top,
-            bottom: dir === PresEffectDirection.Bottom,
-            opposite: true,
-            delay: 0,
-            duration: Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null)),
-        };
-        //prettier-ignore
-        switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) {
-            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 <JackInTheBox {...effectProps}>{renderDoc}</JackInTheBox>;
-        }
-    }
-    @computed get highlighting() {
-        return this._props.styleProvider?.(this.Document, { ...this._props, fieldKey: '' }, StyleProp.Highlighting);
-    }
-    @computed get borderPath() {
-        return this._props.styleProvider?.(this.Document, { ...this._props, fieldKey: '' }, StyleProp.BorderPath);
-    }
     render() {
         TraceMobx();
         const highlighting = this.highlighting;
@@ -1304,10 +1019,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 onClick={this.onClick}
                 onPointerEnter={e => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
                 onPointerOver={e => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
-                onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.Document)}
+                onPointerLeave={e => !isParentOf(this._contentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.Document)}
                 style={{
                     borderRadius: this.borderRounding,
-                    pointerEvents: this.pointerEvents === 'visiblePainted' ? 'none' : this.pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here)
+                    pointerEvents: this._pointerEvents === 'visiblePainted' ? 'none' : this._pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here)
                 }}>
                 <>
                     {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)}
@@ -1316,176 +1031,124 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             </div>
         );
     }
-}
 
-@observer
-export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
-    public static ROOT_DIV = 'documentView-effectsWrapper';
-
-    constructor(props: any) {
-        super(props);
-        makeObservable(this);
+    /**
+     * returns an entrance animation effect function to wrap a JSX element
+     * @param presEffectDoc presentation effects document that specifies the animation effect parameters
+     * @returns a function that will wrap a JSX animation element wrapping any JSX element
+     */
+    public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt<Doc>, root: Doc) {
+        const dir = presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection;
+        const effectProps = {
+            left: dir === PresEffectDirection.Left,
+            right: dir === PresEffectDirection.Right,
+            top: dir === PresEffectDirection.Top,
+            bottom: dir === PresEffectDirection.Bottom,
+            opposite: true,
+            delay: 0,
+            duration: Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null)),
+        };
+        //prettier-ignore
+        switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) {
+            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 <JackInTheBox {...effectProps}>{renderDoc}</JackInTheBox>;
+        }
     }
+    public static recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) {
+        let gumStream: any;
+        let recorder: any;
+        navigator.mediaDevices
+            .getUserMedia({
+                audio: true,
+            })
+            .then(function (stream) {
+                let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null);
+                if (audioTextAnnos) audioTextAnnos.push('');
+                else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List<string>(['']);
+                DictationManager.Controls.listen({
+                    interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value),
+                    continuous: { indefinite: false },
+                }).then(results => {
+                    if (results && [DictationManager.Controls.Infringed].includes(results)) {
+                        DictationManager.Controls.stop();
+                    }
+                    onEnd?.();
+                });
 
-    @observable _selected = false;
-    public get IsSelected() {
-        return this._selected;
-    }
-    public set IsSelected(val) {
-        runInAction(() => (this._selected = val));
-    }
-    @observable public static LongPress = false;
-    @computed public static get exploreMode() {
-        return () => (SnappingManager.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
+                gumStream = stream;
+                recorder = new MediaRecorder(stream);
+                recorder.ondataavailable = async (e: any) => {
+                    const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
+                    if (!(result instanceof Error)) {
+                        const audioField = new AudioField(result.accessPaths.agnostic.client);
+                        const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null);
+                        if (audioAnnos === undefined) {
+                            dataDoc[field + '_audioAnnotations'] = new List([audioField]);
+                        } else {
+                            audioAnnos.push(audioField);
+                        }
+                    }
+                };
+                //runInAction(() => (dataDoc.audioAnnoState = 'recording'));
+                recorder.start();
+                const stopFunc = () => {
+                    recorder.stop();
+                    DictationManager.Controls.stop(false);
+                    runInAction(() => (dataDoc.audioAnnoState = 'stopped'));
+                    gumStream.getAudioTracks()[0].stop();
+                };
+                if (onRecording) onRecording(stopFunc);
+                else setTimeout(stopFunc, 5000);
+            });
     }
-    @observable private _docViewInternal: DocumentViewInternal | undefined | null = undefined;
-    @observable private _htmlOverlayText: Opt<string> = undefined;
-    @observable private _isHovering = false;
-    private _htmlOverlayEffect: Opt<Doc>;
+}
 
-    public get displayName() {
-        return 'DocumentView(' + this.Document?.title + ')';
-    } // this makes mobx trace() statements more descriptive
+@observer
+export class DocumentView extends DocComponent<DocumentViewProps>() {
+    public static ROOT_DIV = 'documentView-effectsWrapper';
+    public get displayName() {  return 'DocumentView(' + this.Document?.title + ')';  } // prettier-ignore
     public ContentRef = React.createRef<HTMLDivElement>();
-    public ViewTimer: NodeJS.Timeout | undefined; // timer for res
-    public AnimEffectTimer: NodeJS.Timeout | undefined; // timer for res
+    private _htmlOverlayEffect: Opt<Doc>;
     private _disposers: { [name: string]: IReactionDisposer } = {};
-    public clearViewTransition = () => {
-        this.ViewTimer && clearTimeout(this.ViewTimer);
-        this.layoutDoc._viewTransition = undefined;
-    };
-    playAnnotation = () => this._docViewInternal?.playAnnotation();
-    noOnClick = () => this._docViewInternal?.noOnClick();
-    makeIntoPortal = () => this._docViewInternal?.makeIntoPortal();
-    setToggleDetail = () => this._docViewInternal?.setToggleDetail();
-    onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => this._docViewInternal?.onContextMenu?.(e, pageX, pageY);
-    cleanupPointerEvents = () => this._docViewInternal?.cleanupPointerEvents();
-    public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this._docViewInternal?.startDragging(x, y, dropAction, hideSource);
+    private _viewTimer: NodeJS.Timeout | undefined;
+    private _animEffectTimer: NodeJS.Timeout | undefined;
 
-    public showContextMenu = (pageX: number, pageY: number) => this._docViewInternal?.onContextMenu(undefined, pageX, pageY);
-
-    public setTextHtmlOverlay = action((text: string | undefined, effect?: Doc) => {
-        this._htmlOverlayText = text;
-        this._htmlOverlayEffect = effect;
-    });
-    public setAnimateScaling = action((scale: number, time?: number) => {
-        if (this._docViewInternal) {
-            this._docViewInternal._animateScalingTo = scale;
-            this._docViewInternal._animateScaleTime = time;
-        }
-    });
-    public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => {
-        this.AnimEffectTimer && clearTimeout(this.AnimEffectTimer);
-        this.Document[Animation] = presEffect;
-        this.AnimEffectTimer = setTimeout(() => (this.Document[Animation] = undefined), timeInMs);
-    };
-    public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => {
-        this.layoutDoc._viewTransition = `${transProp} ${timeInMs}ms`;
-        if (dataTrans) this.Document._dataTransition = `${transProp} ${timeInMs}ms`;
-        this.ViewTimer && clearTimeout(this.ViewTimer);
-        return (this.ViewTimer = setTimeout(() => {
-            this.layoutDoc._viewTransition = undefined;
-            this.Document._dataTransition = 'inherit';
-            afterTrans?.();
-        }, timeInMs + 10));
-    };
-    public static SetViewTransition(docs: Doc[], transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) {
-        docs.forEach(doc => {
-            doc._viewTransition = `${transProp} ${timeInMs}ms`;
-            dataTrans && (doc.dataTransition = `${transProp} ${timeInMs}ms`);
-        });
-        return setTimeout(
-            () =>
-                docs.forEach(doc => {
-                    doc._viewTransition = undefined;
-                    dataTrans && (doc.dataTransition = 'inherit');
-                    afterTrans?.();
-                }),
-            timeInMs + 10
-        );
+    @computed public static get exploreMode() {
+        return () => (SnappingManager.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
     }
 
-    // shows a stacking view collection (by default, but the user can change) of all documents linked to the source
-    public static showBackLinks(linkAnchor: Doc) {
-        const docId = Doc.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish';
-        // prettier-ignore
-        DocServer.GetRefField(docId).then(docx =>
-            LightboxView.Instance.SetLightboxDoc(
-                (docx as Doc) ?? // reuse existing pivot view of documents, or else create a new collection
-                Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, onViewMounted: ScriptField.MakeScript('updateLinkCollection(this, this.target)') }, docId)
-            )
-        );
+    constructor(props: DocumentViewProps) {
+        super(props);
+        makeObservable(this);
     }
-    toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => this._docViewInternal?.toggleFollowLink(zoom, setTargetToggle);
 
-    get Document() {
-        return this._props.Document;
-    }
-    get topMost() {
-        return this._props.renderDepth === 0;
-    }
-    get dataDoc() {
-        return this._docViewInternal?.dataDoc ?? this.Document;
-    }
-    get ContentDiv() {
-        return this._docViewInternal?.ContentDiv;
-    }
-    get ComponentView() {
-        return this._docViewInternal?._componentView;
-    }
-    get allLinks() {
-        return (this._docViewInternal?.allLinks || []).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document);
-    }
-    get LayoutFieldKey() {
-        return this._docViewInternal?.LayoutFieldKey || 'layout';
-    }
-    @computed get layout_fitWidth() {
-        return this._props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth;
-    }
-    @computed get anchorViewDoc() {
-        return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined;
-    }
-    @computed get hideLinkButton() {
-        return (
-            this._props.hideLinkButton ||
-            this._props.renderDepth === -1 || //
-            (this.IsSelected && this._props.renderDepth) ||
-            !this._isHovering ||
-            (!this.IsSelected && this.layoutDoc.layout_hideLinkButton) ||
-            SnappingManager.IsDragging ||
-            SnappingManager.IsResizing
-        );
-    }
-    hideLinkCount = () => (this.hideLinkButton ? true : false);
+    // want the htmloverlay to be able to fade in but we also want it to be display 'none' until it is needed.
+    // unfortunately, CSS can't transition animate any properties for something that is display 'none'.
+    // so we need to first activate the div, then, after a render timeout, start the opacity transition.
+    @observable private _enableHtmlOverlayTransitions: boolean = false;
+    @observable private _docViewInternal: DocumentViewInternal | undefined | null = undefined;
+    @observable private _htmlOverlayText: Opt<string> = undefined;
+    @observable private _isHovering = false;
+    @observable private _selected = false;
+    @observable public static LongPress = false;
 
-    @computed get linkCountView() {
-        return <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
-    }
-    /**
-     * path of DocumentViews hat contains this DocumentView (does not includes this DocumentView thouhg)
-     */
-    @computed get containerViewPath() {
-        return this._props.containerViewPath;
-    }
-    @computed get layoutDoc() {
-        return Doc.Layout(this.Document, this._props.LayoutTemplate?.());
-    }
-    @computed get nativeWidth() {
-        return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
-    }
-    @computed get nativeHeight() {
-        return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
-    }
-    @computed get shouldNotScale() {
+    @computed private get shouldNotScale() {
         return (this.layout_fitWidth && !this.nativeWidth) || this._props.LayoutTemplateString?.includes(KeyValueBox.name) || [CollectionViewType.Docking].includes(this.Document._type_collection as any);
     }
-    @computed get effectiveNativeWidth() {
+    @computed private get effectiveNativeWidth() {
         return this.shouldNotScale ? 0 : this.nativeWidth || NumCast(this.layoutDoc.width);
     }
-    @computed get effectiveNativeHeight() {
+    @computed private get effectiveNativeHeight() {
         return this.shouldNotScale ? 0 : this.nativeHeight || NumCast(this.layoutDoc.height);
     }
-    @computed get nativeScaling() {
+    @computed private get nativeScaling() {
         if (this.shouldNotScale) return 1;
         const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
         if (this.layout_fitWidth || this._props.PanelHeight() / (this.effectiveNativeHeight || 1) > this._props.PanelWidth() / (this.effectiveNativeWidth || 1)) {
@@ -1493,19 +1156,19 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
         }
         return Math.max(minTextScale, this._props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
     }
-    @computed get panelWidth() {
+    @computed private get panelWidth() {
         return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this._props.PanelWidth();
     }
-    @computed get panelHeight() {
+    @computed private get panelHeight() {
         if (this.effectiveNativeHeight && (!this.layout_fitWidth || !this.layoutDoc.layout_reflowVertical)) {
             return Math.min(this._props.PanelHeight(), this.effectiveNativeHeight * this.nativeScaling);
         }
         return this._props.PanelHeight();
     }
-    @computed get Xshift() {
+    @computed private get Xshift() {
         return this.effectiveNativeWidth ? Math.max(0, (this._props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2) : 0;
     }
-    @computed get Yshift() {
+    @computed private get Yshift() {
         return this.effectiveNativeWidth &&
             this.effectiveNativeHeight &&
             Math.abs(this.Xshift) < 0.001 &&
@@ -1513,41 +1176,99 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
             ? Math.max(0, (this._props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2)
             : 0;
     }
-    @computed get centeringX() {
-        return this._props.dontCenter?.includes('x') ? 0 : this.Xshift;
+    @computed private get hideLinkButton() {
+        return (
+            this._props.hideLinkButton ||
+            this._props.renderDepth === -1 || //
+            (this.IsSelected && this._props.renderDepth) ||
+            !this._isHovering ||
+            (!this.IsSelected && this.layoutDoc.layout_hideLinkButton) ||
+            SnappingManager.IsDragging ||
+            SnappingManager.IsResizing
+        );
+    }
+
+    componentDidMount() {
+        runInAction(() => this.Document[DocViews].add(this));
+        this._disposers.onViewMounted = reaction(() => ScriptCast(this.Document.onViewMounted)?.script?.run({ this: this.Document }).result, emptyFunction);
+        !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.AddView(this);
+    }
+
+    componentWillUnmount() {
+        runInAction(() => this.Document[DocViews].delete(this));
+        Object.values(this._disposers).forEach(disposer => disposer?.());
+        !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
     }
-    @computed get centeringY() {
-        return this._props.dontCenter?.includes('y') ? 0 : this.Yshift;
+
+    public set IsSelected(val)  { runInAction(() => (this._selected = val)); } // prettier-ignore
+    public get IsSelected()     { return this._selected; } // prettier-ignore
+    public get topMost()        { return this._props.renderDepth === 0; } // prettier-ignore
+    public get ContentDiv()     { return this._docViewInternal?._contentDiv; } // prettier-ignore
+    public get ComponentView()  { return this._docViewInternal?._componentView; } // prettier-ignore
+    public get allLinks()       { return this._docViewInternal?._allLinks ?? []; } // prettier-ignore
+
+    get LayoutFieldKey() {
+        return Doc.LayoutFieldKey(this.Document, this._props.LayoutTemplateString);
     }
 
-    @computed get CollectionFreeFormView() {
-        return this.CollectionFreeFormDocumentView?.CollectionFreeFormView;
+    @computed get layout_fitWidth() {
+        return this._props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth;
     }
-    @computed get CollectionFreeFormDocumentView() {
-        return this._props.CollectionFreeFormDocumentView?.();
+    @computed get anchorViewDoc() {
+        return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined;
     }
 
-    public toggleNativeDimensions = () => this._docViewInternal && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this._docViewInternal.NativeDimScaling, this._props.PanelWidth(), this._props.PanelHeight());
-    public getBounds = () => {
-        if (!this._docViewInternal?.ContentDiv || this._props.treeViewDoc || Doc.AreProtosEqual(this.Document, Doc.UserDoc())) {
+    @computed get getBounds() {
+        if (!this._docViewInternal?._contentDiv || this._props.treeViewDoc || Doc.AreProtosEqual(this.Document, Doc.UserDoc())) {
             return undefined;
         }
         if (this._docViewInternal._componentView?.screenBounds?.()) {
             return this._docViewInternal._componentView.screenBounds();
         }
-        const xf = this._docViewInternal._props.ScreenToLocalTransform().scale(this.nativeScaling).inverse();
+        const xf = this.screenToContentsTransform().scale(this.nativeScaling).inverse();
         const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
 
-        if (this._docViewInternal._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
-            const docuBox = this._docViewInternal.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
+        if (this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
+            const docuBox = this._docViewInternal._contentDiv.getElementsByClassName('linkAnchorBox-cont');
             if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
         }
         return { left, top, right, bottom };
+    }
+
+    @computed get nativeWidth() {
+        return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
+    }
+    @computed get nativeHeight() {
+        return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
+    }
+    @computed public get centeringX() { return this._props.dontCenter?.includes('x') ? 0 : this.Xshift; } // prettier-ignore
+    @computed public get centeringY() { return this._props.dontCenter?.includes('y') ? 0 : this.Yshift; } // prettier-ignore
+
+    /**
+     * path of DocumentViews hat contains this DocumentView (does not includes this DocumentView thouhg)
+     */
+    public get containerViewPath() { return this._props.containerViewPath; } // prettier-ignore
+    public get CollectionFreeFormView() { return this.CollectionFreeFormDocumentView?.CollectionFreeFormView; } // prettier-ignore
+    public get CollectionFreeFormDocumentView() { return this._props.CollectionFreeFormDocumentView?.(); } // prettier-ignore
+
+    public clearViewTransition = () => {
+        this._viewTimer && clearTimeout(this._viewTimer);
+        this.layoutDoc._viewTransition = undefined;
     };
+    public noOnClick = () => this._docViewInternal?.noOnClick();
+    public makeIntoPortal = () => this._docViewInternal?.makeIntoPortal();
+    public toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => this._docViewInternal?.toggleFollowLink(zoom, setTargetToggle);
+    public setToggleDetail = () => this._docViewInternal?.setToggleDetail();
+    public onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => this._docViewInternal?.onContextMenu?.(e, pageX, pageY);
+    public cleanupPointerEvents = () => this._docViewInternal?.cleanupPointerEvents();
+    public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this._docViewInternal?.startDragging(x, y, dropAction, hideSource);
+    public showContextMenu = (pageX: number, pageY: number) => this._docViewInternal?.onContextMenu(undefined, pageX, pageY);
+
+    public toggleNativeDimensions = () => this._docViewInternal && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.NativeDimScaling() ?? 1, this._props.PanelWidth(), this._props.PanelHeight());
 
     public iconify(finished?: () => void, animateTime?: number) {
         this.ComponentView?.updateIcon?.();
-        const animTime = this._docViewInternal?._animateScaleTime;
+        const animTime = this._docViewInternal?.animateScaleTime();
         runInAction(() => this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime));
         const finalFinished = action(() => {
             finished?.();
@@ -1564,13 +1285,58 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
             this._props.bringToFront(this.Document);
         }
     }
-    @undoBatch
-    setCustomView = (custom: boolean, layout: string): void => {
+
+    public playAnnotation = () => {
+        const self = this;
+        const audioAnnoState = this.dataDoc.audioAnnoState ?? 'stopped';
+        const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '_audioAnnotations'], listSpec(AudioField), null);
+        const anno = audioAnnos?.lastElement();
+        if (anno instanceof AudioField) {
+            switch (audioAnnoState) {
+                case 'stopped':
+                    this.dataDoc[AudioPlay] = new Howl({
+                        src: [anno.url.href],
+                        format: ['mp3'],
+                        autoplay: true,
+                        loop: false,
+                        volume: 0.5,
+                        onend: action(() => (self.dataDoc.audioAnnoState = 'stopped')),
+                    });
+                    this.dataDoc.audioAnnoState = 'playing';
+                    break;
+                case 'playing':
+                    this.dataDoc[AudioPlay]?.stop();
+                    this.dataDoc.audioAnnoState = 'stopped';
+                    break;
+            }
+        }
+    };
+
+    public setTextHtmlOverlay = action((text: string | undefined, effect?: Doc) => {
+        this._htmlOverlayText = text;
+        this._htmlOverlayEffect = effect;
+    });
+    public setAnimateScaling = action((scale: number, time?: number) => {
+        if (this._docViewInternal) {
+            this._docViewInternal._animateScalingTo = scale;
+            this._docViewInternal._animateScaleTime = time;
+        }
+    });
+    public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => {
+        this._animEffectTimer && clearTimeout(this._animEffectTimer);
+        this.Document[Animation] = presEffect;
+        this._animEffectTimer = setTimeout(() => (this.Document[Animation] = undefined), timeInMs);
+    };
+    public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => {
+        this._viewTimer = DocumentView.SetViewTransition([this.layoutDoc], transProp, timeInMs, this._viewTimer, afterTrans, dataTrans);
+    };
+
+    public setCustomView = undoable((custom: boolean, layout: string): void => {
         Doc.setNativeView(this.Document);
         custom && DocUtils.makeCustomViewClicked(this.Document, Docs.Create.StackingDocument, layout, undefined);
-    };
+    }, 'set custom view');
 
-    switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
+    public switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
         runInAction(() => this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1)); // shrink doc
         setTimeout(
             action(() => {
@@ -1585,16 +1351,19 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                         this._docViewInternal && (this._docViewInternal._animateScalingTo = 0);
                         finished?.();
                     }),
-                    this._docViewInternal ? Math.max(0, this._docViewInternal.animateScaleTime - 10) : 0
+                    Math.max(0, (this._docViewInternal?.animateScaleTime() ?? 0) - 10)
                 );
             }),
-            this._docViewInternal ? Math.max(0, this._docViewInternal?.animateScaleTime - 10) : 0
+            Math.max(0, (this._docViewInternal?.animateScaleTime() ?? 0) - 10)
         );
     };
+    /**
+     * @returns a hierarchy path through  the nested DocumentViews that display this view.  The last element of the path is this view.
+     */
+    public docViewPath = () => (this.containerViewPath ? [...this.containerViewPath(), this] : [this]);
 
     layout_fitWidthFunc = (doc: Doc) => BoolCast(this.layout_fitWidth);
     screenToLocalScale = () => this._props.ScreenToLocalTransform().Scale;
-    docViewPath = () => (this.containerViewPath ? [...this.containerViewPath(), this] : [this]);
     isSelected = () => this.IsSelected;
     select = (extendSelection: boolean, focusSelection?: boolean) => {
         if (this.IsSelected && SelectionManager.Views.length > 1) SelectionManager.DeselectView(this);
@@ -1615,6 +1384,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
     PanelWidth = () => this.panelWidth;
     PanelHeight = () => this.panelHeight;
     NativeDimScaling = () => this.nativeScaling;
+    hideLinkCount = () => (this.hideLinkButton ? true : false);
     selfView = () => this;
     /**
      * @returns Transform to the document view (in the coordinate system of whatever contains the DocumentView)
@@ -1629,34 +1399,19 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
             .translate(-this.centeringX, -this.centeringY)
             .scale(1 / this.nativeScaling);
 
-    componentDidMount() {
-        runInAction(() => this.Document[DocViews].add(this));
-        this._disposers.onViewMounted = reaction(() => ScriptCast(this.Document.onViewMounted)?.script?.run({ this: this.Document }).result, emptyFunction);
-        !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.AddView(this);
-    }
-
-    componentWillUnmount() {
-        runInAction(() => this.Document[DocViews].delete(this));
-        Object.values(this._disposers).forEach(disposer => disposer?.());
-        !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
-    }
-    // want the htmloverlay to be able to fade in but we also want it to be display 'none' until it is needed.
-    // unfortunately, CSS can't transition animate any properties for something that is display 'none'.
-    // so we need to first activate the div, then, after a render timeout, start the opacity transition.
-    @observable enableHtmlOverlayTransitions: boolean = false;
-    @computed get htmlOverlay() {
+    htmlOverlay = () => {
         const effect = StrCast(this._htmlOverlayEffect?.presentation_effect, StrCast(this._htmlOverlayEffect?.followLinkAnimEffect));
         return (
             <div
                 className="documentView-htmlOverlay"
                 ref={r => {
                     const val = r?.style.display !== 'none'; // if the outer overlay has been displayed, trigger the innner div to start it's opacity fade in transition
-                    if (r && val !== this.enableHtmlOverlayTransitions) {
-                        setTimeout(action(() => (this.enableHtmlOverlayTransitions = val)));
+                    if (r && val !== this._enableHtmlOverlayTransitions) {
+                        setTimeout(action(() => (this._enableHtmlOverlayTransitions = val)));
                     }
                 }}
                 style={{ display: !this._htmlOverlayText ? 'none' : undefined }}>
-                <div className="documentView-htmlOverlayInner" style={{ transition: `all 500ms`, opacity: this.enableHtmlOverlayTransitions ? 0.9 : 0 }}>
+                <div className="documentView-htmlOverlayInner" style={{ transition: `all 500ms`, opacity: this._enableHtmlOverlayTransitions ? 0.9 : 0 }}>
                     {DocumentViewInternal.AnimationEffect(
                         <div className="webBox-textHighlight">
                             <ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this._htmlOverlayText)} />
@@ -1667,11 +1422,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                 </div>
             </div>
         );
-    }
-
-    @computed get infoUI() {
-        return this.ComponentView?.infoUI?.();
-    }
+    };
 
     render() {
         TraceMobx();
@@ -1685,14 +1436,14 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                         className="contentFittingDocumentView-previewDoc"
                         ref={this.ContentRef}
                         style={{
-                            transition: 'inherit', // this._props.dataTransition,
                             transform: `translate(${this.centeringX}px, ${this.centeringY}px)`,
                             width: xshift ?? `${this._props.PanelWidth() - this.Xshift * 2}px`,
                             height: this._props.forceAutoHeight ? undefined : yshift ?? (this.layout_fitWidth ? `${this.panelHeight}px` : `${(this.effectiveNativeHeight / this.effectiveNativeWidth) * this._props.PanelWidth()}px`),
                         }}>
                         <DocumentViewInternal
                             {...this._props}
-                            docViewPublic={this.selfView}
+                            fieldKey={this.LayoutFieldKey}
+                            DocumentView={this.selfView}
                             docViewPath={this.docViewPath}
                             PanelWidth={this.PanelWidth}
                             PanelHeight={this.PanelHeight}
@@ -1706,15 +1457,44 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                             focus={this._props.focus || emptyFunction}
                             ref={action((r: DocumentViewInternal | null) => r && (this._docViewInternal = r))}
                         />
-                        {this.htmlOverlay}
-                        {this.infoUI}
+                        {this.htmlOverlay()}
+                        {this.ComponentView?.infoUI?.()}
                     </div>
                 )}
-
-                {this.linkCountView}
+                {/* display link count button */}
+                <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover={true} Bottom={this.topMost} ShowCount={true} />
             </div>
         );
     }
+
+    public static SetViewTransition(docs: Doc[], transProp: string, timeInMs: number, timer?: NodeJS.Timeout | undefined, afterTrans?: () => void, dataTrans = false) {
+        docs.forEach(doc => {
+            doc._viewTransition = `${transProp} ${timeInMs}ms`;
+            dataTrans && (doc.dataTransition = `${transProp} ${timeInMs}ms`);
+        });
+        timer && clearTimeout(timer);
+        return setTimeout(
+            () =>
+                docs.forEach(doc => {
+                    doc._viewTransition = undefined;
+                    dataTrans && (doc.dataTransition = 'inherit');
+                    afterTrans?.();
+                }),
+            timeInMs + 10
+        );
+    }
+
+    // shows a stacking view collection (by default, but the user can change) of all documents linked to the source
+    public static showBackLinks(linkAnchor: Doc) {
+        const docId = Doc.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish';
+        // prettier-ignore
+        DocServer.GetRefField(docId).then(docx =>
+            LightboxView.Instance.SetLightboxDoc(
+                (docx as Doc) ?? // reuse existing pivot view of documents, or else create a new collection
+                Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, onViewMounted: ScriptField.MakeScript('updateLinkCollection(this, this.target)') }, docId)
+            )
+        );
+    }
 }
 
 ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) {
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index cad76ddf7..2e03a766a 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -20,13 +20,13 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static SelectOnLoad: string = '';
     _ref: React.RefObject<EquationEditor> = React.createRef();
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
     }
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()))) {
             this._props.select(false);
 
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index b456807d7..5bb295565 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,26 +1,108 @@
-import { computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { DateField } from '../../../fields/DateField';
-import { Doc, Field, FieldResult, Opt } from '../../../fields/Doc';
+import { Doc, Field, Opt } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { ScriptField } from '../../../fields/ScriptField';
 import { WebField } from '../../../fields/URLField';
-import { DocumentView, DocumentViewInternalSharedProps, DocumentViewSharedProps } from './DocumentView';
+import { dropActionType } from '../../util/DragManager';
+import { Transform } from '../../util/Transform';
+import { ViewBoxInterface } from '../DocComponent';
+import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
+import { DocumentView, OpenWhere } from './DocumentView';
+import { PinProps } from './trails';
 
+export interface FocusViewOptions {
+    willPan?: boolean; // determines whether to pan to target document
+    willZoomCentered?: boolean; // determines whether to zoom in on target document.  if zoomScale is 0, this just centers the document
+    zoomScale?: number; // percent of containing frame to zoom into document
+    zoomTime?: number;
+    didMove?: boolean; // whether a document was changed during the showDocument process
+    docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom.  when this happens the docTransform must accumulate all the transforms of each level of the hierarchy
+    instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom)
+    preview?: boolean; // whether changes should be previewed by the componentView or written to the document
+    effect?: Doc; // animation effect for focus
+    noSelect?: boolean; // whether target should be selected after focusing
+    playAudio?: boolean; // whether to play audio annotation on focus
+    playMedia?: boolean; // whether to play start target videos
+    openLocation?: OpenWhere; // where to open a missing document
+    zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections
+    toggleTarget?: boolean; // whether to toggle target on and off
+    anchorDoc?: Doc; // doc containing anchor info to apply at end of focus to target doc
+    easeFunc?: 'linear' | 'ease'; // transition method for scrolling
+}
+export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt<number>;
+export type StyleProviderFuncType = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => any;
 //
 // these properties get assigned through the render() method of the DocumentView when it creates this node.
 // However, that only happens because the properties are "defined" in the markup for the field view.
 // See the LayoutString method on each field view :   ImageBox, FormattedTextBox, etc.
 //
-export interface FieldViewProps extends DocumentViewSharedProps, DocumentViewInternalSharedProps {
-    // FieldView specific props that are not part of DocumentView props
-    fieldKey: string;
-
-    setHeight?: (height: number) => void;
-    onBrowseClick?: () => ScriptField | undefined;
+export interface FieldViewSharedProps {
+    Document: Doc;
+    LayoutTemplateString?: string;
+    LayoutTemplate?: () => Opt<Doc>;
+    TemplateDataDocument?: Doc;
+    renderDepth: number;
+    scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
+    treeViewDoc?: Doc;
+    xPadding?: number;
+    yPadding?: number;
+    dontRegisterView?: boolean;
+    dropAction?: dropActionType;
+    dragAction?: dropActionType;
+    forceAutoHeight?: boolean;
+    ignoreAutoHeight?: boolean;
+    disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
+    CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
+    containerViewPath?: () => DocumentView[];
+    fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
+    isGroupActive?: () => string | undefined; // is this document part of a group that is active
+    setContentViewBox?: (view: ViewBoxInterface) => any; // called by rendered field's viewBox so that DocumentView can make direct calls to the viewBox
+    PanelWidth: () => number;
+    PanelHeight: () => number;
+    isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
+    isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
+    childFilters: () => string[];
+    childFiltersByRanges: () => string[];
+    styleProvider: Opt<StyleProviderFuncType>;
+    setTitleFocus?: () => void;
+    focus: FocusFuncType;
+    onClickScript?: () => ScriptField;
+    onDoubleClickScript?: () => ScriptField;
+    onPointerDownScript?: () => ScriptField;
+    onPointerUpScript?: () => ScriptField;
+    onBrowseClickScript?: () => ScriptField | undefined;
     onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
+    layout_fitWidth?: (doc: Doc) => boolean | undefined;
+    searchFilterDocs: () => Doc[];
+    layout_showTitle?: () => string;
+    whenChildContentsActiveChanged: (isActive: boolean) => void;
+    rootSelected?: () => boolean; // whether the root of a template has been selected
+    addDocTab: (doc: Doc, where: OpenWhere) => boolean;
+    filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
+    addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
+    removeDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
+    moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => boolean;
+    pinToPres: (document: Doc, pinProps: PinProps) => void;
+    ScreenToLocalTransform: () => Transform;
+    bringToFront: (doc: Doc, sendToBack?: boolean) => void;
+    waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
+    defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
     pointerEvents?: () => Opt<string>;
+}
+
+/**
+ * FieldView specific props that are not shared with DocumentView props
+ *  */
+export interface FieldViewProps extends FieldViewSharedProps {
+    DocumentView?: () => DocumentView;
+    fieldKey: string;
+    isSelected: () => boolean;
+    select: (ctrlPressed: boolean, shiftPress?: boolean) => void;
+    docViewPath: () => DocumentView[];
+    setHeight?: (height: number) => void;
+    NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
 
     // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
     // See currentUserUtils headerTemplate for examples of creating text boxes from html which set some of these fields
@@ -30,9 +112,9 @@ export interface FieldViewProps extends DocumentViewSharedProps, DocumentViewInt
     color?: string;
     height?: number;
     width?: number;
+    dontSelectOnLoad?: boolean; // suppress selecting (e.g.,. text box) when loaded (and mark as not being associated with scrollTop document field)
     noSidebar?: boolean;
     dontScale?: boolean;
-    dontSelectOnLoad?: boolean; // suppress selecting (e.g.,. text box) when loaded (and mark as not being associated with scrollTop document field)
 }
 
 @observer
@@ -41,13 +123,8 @@ export class FieldView extends React.Component<FieldViewProps> {
         return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "<ImageBox {...props} fieldKey={'data'} />"
     }
 
-    @computed
-    get field(): FieldResult {
-        const { Document, fieldKey: fieldKey } = this.props;
-        return Document[fieldKey];
-    }
     render() {
-        const field = this.field;
+        const field = this.props.Document[this.props.fieldKey];
         // prettier-ignore
         if (field instanceof Doc)  return <p> <b>{field.title?.toString()}</b></p>;
         if (field === undefined) return <p>{'<null>'}</p>;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 5a8665aaf..cf07d98be 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -46,7 +46,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         return FieldView.LayoutString(FontIconBox, fieldKey);
     }
 
-    constructor(props: any) {
+    constructor(props: ButtonProps) {
         super(props);
         makeObservable(this);
     }
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index c26579e66..2e7a2120e 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -31,7 +31,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
     }
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         reaction(
             () => [DocListCast(this.dataDoc[this.fieldKey]).map(doc => doc?.text), this.layoutDoc.width, this.layoutDoc.height, this.layoutDoc.xRange, this.layoutDoc.yRange],
             () => this.createGraph()
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 4483d12ce..2a10bd766 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -21,17 +21,17 @@ import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../../views/ContextMenu';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { StyleProp } from '../StyleProvider';
-import { DocFocusOptions, OpenWhere } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
+import { OpenWhere } from './DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
 import './ImageBox.scss';
 import { PinProps, PresBox } from './trails';
 
 @observer
-export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(ImageBox, fieldKey);
     }
@@ -55,10 +55,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     private _marqueeref = React.createRef<MarqueeAnnotator>();
     @observable _curSuffix = '';
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
 
     protected createDropTarget = (ele: HTMLDivElement) => {
@@ -186,9 +186,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     crop = (region: Doc | undefined, addCrop?: boolean) => {
         if (!region) return;
         const cropping = Doc.MakeCopy(region, true);
-        Doc.GetProto(region).lockedPosition = true;
-        Doc.GetProto(region).title = 'region:' + this.Document.title;
-        Doc.GetProto(region).followLinkToggle = true;
+        const regionData = region[DocData];
+        regionData.lockedPosition = true;
+        regionData.title = 'region:' + this.Document.title;
+        regionData.followLinkToggle = true;
         this.addDocument(region);
         const anchx = NumCast(cropping.x);
         const anchy = NumCast(cropping.y);
@@ -201,7 +202,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         cropping._width = anchw * (this._props.NativeDimScaling?.() || 1);
         cropping._height = anchh * (this._props.NativeDimScaling?.() || 1);
         cropping.onClick = undefined;
-        const croppingProto = Doc.GetProto(cropping);
+        const croppingProto = cropping[DocData];
         croppingProto.annotationOn = undefined;
         croppingProto.isDataDoc = true;
         croppingProto.backgroundColor = undefined;
@@ -389,7 +390,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         this._marqueeref.current?.onTerminateSelection();
         this._props.select(false);
     };
-    focus = (anchor: Doc, options: DocFocusOptions) => (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options));
+    focus = (anchor: Doc, options: FocusViewOptions) => (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options));
 
     _ffref = React.createRef<CollectionFreeFormView>();
     savedAnnotations = () => this._savedAnnotations;
@@ -421,7 +422,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 <CollectionFreeFormView
                     ref={this._ffref}
                     {...this._props}
-                    setContentView={emptyFunction}
+                    setContentViewBox={emptyFunction}
                     NativeWidth={returnZero}
                     NativeHeight={returnZero}
                     renderDepth={this._props.renderDepth + 1}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 73fdc3a23..39a45693e 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -44,7 +44,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
     private _valInput = React.createRef<HTMLInputElement>();
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
     isKeyValueBox = returnTrue;
     able = returnAlways;
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index b2aab2422..7c532f33f 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -12,8 +12,7 @@ import { ContextMenu } from '../ContextMenu';
 import { EditableView } from '../EditableView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DefaultStyleProvider } from '../StyleProvider';
-import { OpenWhere } from './DocumentView';
-import { FieldViewProps } from './FieldView';
+import { OpenWhere, returnEmptyDocViewList } from './DocumentView';
 import { KeyValueBox } from './KeyValueBox';
 import './KeyValueBox.scss';
 import './KeyValuePair.scss';
@@ -110,6 +109,7 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
                                 childFiltersByRanges: returnEmptyFilter,
                                 searchFilterDocs: returnEmptyDoclist,
                                 styleProvider: DefaultStyleProvider,
+                                docViewPath: returnEmptyDocViewList,
                                 fieldKey: this._props.keyName,
                                 isSelected: returnFalse,
                                 setHeight: returnFalse,
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index cc7c15a10..10eeff08d 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -14,13 +14,15 @@ import { StyleProp } from '../StyleProvider';
 import { FieldView, FieldViewProps } from './FieldView';
 import BigText from './LabelBigText';
 import './LabelBox.scss';
+import { PinProps, PresBox } from './trails';
+import { Docs } from '../../documents/Documents';
 
-export interface LabelBoxProps {
+export interface LabelBoxProps extends FieldViewProps {
     label?: string;
 }
 
 @observer
-export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProps>() {
+export class LabelBox extends ViewBoxBaseComponent<LabelBoxProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(LabelBox, fieldKey);
     }
@@ -30,13 +32,13 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
     private dropDisposer?: DragManager.DragDropDisposer;
     private _timeout: any;
 
-    constructor(props: any) {
+    constructor(props: LabelBoxProps) {
         super(props);
         makeObservable(this);
     }
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
     componentWillUnMount() {
         this._timeout && clearTimeout(this._timeout);
@@ -89,6 +91,19 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
         return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
     }
 
+    getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
+        if (!pinProps) return this.Document;
+        const anchor = Docs.Create.ConfigDocument({ title: StrCast(this.Document.title), annotationOn: this.Document });
+
+        if (anchor) {
+            if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
+            //  addAsAnnotation && this.addDocument(anchor);
+            PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}) } }, this.Document);
+            return anchor;
+        }
+        return anchor;
+    };
+
     fitTextToBox = (
         r: any
     ):
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 362a7def1..00e1f04c5 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -1,4 +1,5 @@
 import { action, computed, makeObservable } from 'mobx';
+import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Utils, emptyFunction, setupMoveUpEvents } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
@@ -7,13 +8,14 @@ import { TraceMobx } from '../../../fields/util';
 import { DragManager } from '../../util/DragManager';
 import { LinkFollower } from '../../util/LinkFollower';
 import { SelectionManager } from '../../util/SelectionManager';
-import { ViewBoxBaseComponent, ViewBoxBaseProps } from '../DocComponent';
+import { ViewBoxBaseComponent } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import { FieldView, FieldViewProps } from './FieldView';
 import './LinkAnchorBox.scss';
 import { LinkInfo } from './LinkDocPreview';
 const { default: { MEDIUM_GRAY }, } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
-export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps & ViewBoxBaseProps>() {
+@observer
+export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(LinkAnchorBox, fieldKey);
     }
@@ -23,13 +25,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps & ViewBox
     _isOpen = false;
     _timeout: NodeJS.Timeout | undefined;
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
     }
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
 
     @computed get linkSource() {
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index ba34255dc..8b6293806 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -20,7 +20,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return FieldView.LayoutString(LinkBox, fieldKey);
     }
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
     }
@@ -59,7 +59,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
     disposer: IReactionDisposer | undefined;
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         this.disposer = reaction(
             () => {
                 if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.CollectionFreeFormView) {
@@ -100,6 +100,9 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         this.layoutDoc._width = params.rx - params?.lx;
                         this.layoutDoc._height = params?.by - params?.ty;
                     }
+                } else {
+                    this.layoutDoc._width = Math.max(50, NumCast(this.layoutDoc._width));
+                    this.layoutDoc._height = Math.max(50, NumCast(this.layoutDoc._height));
                 }
             },
             { fireImmediately: true }
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 8ad0b7dde..13f0ac4fc 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -1,20 +1,27 @@
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
 import { LinkManager } from '../../util/LinkManager';
 import './LinkDescriptionPopup.scss';
 import { TaskCompletionBox } from './TaskCompletedBox';
 
 @observer
 export class LinkDescriptionPopup extends React.Component<{}> {
-    @observable public static descriptionPopup: boolean = false;
-    @observable public static showDescriptions: string = 'ON';
-    @observable public static popupX: number = 700;
-    @observable public static popupY: number = 350;
+    public static Instance: LinkDescriptionPopup;
+    @observable public display: boolean = false;
+    @observable public showDescriptions: string = 'ON';
+    @observable public popupX: number = 700;
+    @observable public popupY: number = 350;
     @observable description: string = '';
     @observable popupRef = React.createRef<HTMLDivElement>();
 
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+        LinkDescriptionPopup.Instance = this;
+    }
+
     @action
     descriptionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
         this.description = e.currentTarget.value;
@@ -22,16 +29,16 @@ export class LinkDescriptionPopup extends React.Component<{}> {
 
     @action
     onDismiss = (add: boolean) => {
-        LinkDescriptionPopup.descriptionPopup = false;
+        this.display = false;
         if (add) {
-            LinkManager.currentLink && (Doc.GetProto(LinkManager.currentLink).link_description = this.description);
+            LinkManager.currentLink && (LinkManager.currentLink[DocData].link_description = this.description);
         }
     };
 
     @action
     onClick = (e: PointerEvent) => {
         if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) {
-            LinkDescriptionPopup.descriptionPopup = false;
+            this.display = false;
             TaskCompletionBox.taskCompleted = false;
         }
     };
@@ -46,13 +53,13 @@ export class LinkDescriptionPopup extends React.Component<{}> {
     }
 
     render() {
-        return (
+        return !this.display ? null : (
             <div
                 className="linkDescriptionPopup"
                 ref={this.popupRef}
                 style={{
-                    left: LinkDescriptionPopup.popupX ? LinkDescriptionPopup.popupX : 700,
-                    top: LinkDescriptionPopup.popupY ? LinkDescriptionPopup.popupY : 350,
+                    left: this.popupX ? this.popupX : 700,
+                    top: this.popupY ? this.popupY : 350,
                 }}>
                 <input
                     className="linkDescriptionPopup-input"
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index ed4878d9d..2d54da0ed 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -18,9 +18,9 @@ import { SearchUtil } from '../../util/SearchUtil';
 import { SettingsManager } from '../../util/SettingsManager';
 import { Transform } from '../../util/Transform';
 import { ObservableReactComponent } from '../ObservableReactComponent';
-import { DocumentView, DocumentViewSharedProps, OpenWhere, StyleProviderFunc } from './DocumentView';
+import { DocumentView, OpenWhere } from './DocumentView';
+import { StyleProviderFuncType } from './FieldView';
 import './LinkDocPreview.scss';
-import { FieldViewProps } from './FieldView';
 
 export class LinkInfo {
     private static _instance: Opt<LinkInfo>;
@@ -45,7 +45,7 @@ interface LinkDocPreviewProps {
     linkDoc?: Doc;
     linkSrc?: Doc;
     DocumentView?: () => DocumentView;
-    styleProvider?: StyleProviderFunc;
+    styleProvider?: StyleProviderFuncType;
     location: number[];
     hrefs?: string[];
     showHeader?: boolean;
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 56fb157da..c185c66fc 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -23,12 +23,12 @@ import { DragManager } from '../../../util/DragManager';
 import { LinkManager } from '../../../util/LinkManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { UndoManager, undoable } from '../../../util/UndoManager';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
 import { Colors } from '../../global/globalEnums';
-import { DocFocusOptions, DocumentView } from '../DocumentView';
-import { FieldView, FieldViewProps } from '../FieldView';
+import { DocumentView } from '../DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../trails';
 import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
@@ -96,7 +96,7 @@ type MapMarker = {
 // });
 
 @observer
-export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(MapBox, fieldKey);
     }
@@ -107,7 +107,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     private _disposers: { [key: string]: IReactionDisposer } = {};
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
     }
@@ -248,7 +248,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     componentDidMount() {
         this._unmounting = false;
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
 
     _unmounting = false;
@@ -388,7 +388,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                 dragComplete: e => {
                     if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
-                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this._props.Document;
+                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.Document;
                         e.annoDragData.linkSourceDoc.followLinkZoom = false;
                     }
                 },
@@ -504,7 +504,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     };
 
-    getView = async (doc: Doc, options: DocFocusOptions) => {
+    getView = async (doc: Doc, options: FocusViewOptions) => {
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
             this.toggleSidebar();
             options.didMove = true;
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index 9734d9db1..9825824bd 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -12,7 +12,7 @@
 // import { SnappingManager } from '../../../util/SnappingManager';
 // import { UndoManager } from '../../../util/UndoManager';
 // import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
-// import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
+// import { ViewBoxAnnotatableComponent } from '../../DocComponent';
 // import { Colors } from '../../global/globalEnums';
 // import { AnchorMenu } from '../../pdf/AnchorMenu';
 // import { Annotation } from '../../pdf/Annotation';
@@ -83,7 +83,7 @@
 // } as google.maps.places.AutocompleteOptions;
 
 // @observer
-// export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps & Partial<GoogleMapProps>>() {
+// export class MapBox2 extends ViewBoxAnnotatableComponent<FieldViewProps & Partial<GoogleMapProps>>() {
 //     private _dropDisposer?: DragManager.DragDropDisposer;
 //     private _disposers: { [name: string]: IReactionDisposer } = {};
 //     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
@@ -137,7 +137,7 @@
 
 //     // iterate allMarkers to size, center, and zoom map to contain all markers
 //     private fitBounds = (map: google.maps.Map) => {
-//         const curBounds = map.getBounds() ?? new window.google.maps.LatLngBounds();
+//         const curBounds = map.getBounds ?? new window.google.maps.LatLngBounds();
 //         const isFitting = this.allMapMarkers.reduce((fits, place) => fits && curBounds?.contains({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), true as boolean);
 //         !isFitting && map.fitBounds(this.allMapMarkers.reduce((bounds, place) => bounds.extend({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), new window.google.maps.LatLngBounds()));
 //     };
@@ -257,7 +257,7 @@
 //         map.setZoom(NumCast(this.dataDoc.map_zoom, 2.5));
 //         map.setCenter(new google.maps.LatLng(NumCast(this.dataDoc.mapLat), NumCast(this.dataDoc.mapLng)));
 //         setTimeout(() => {
-//             if (this._loadPending && this._map.getBounds()) {
+//             if (this._loadPending && this._map.getBounds) {
 //                 this._loadPending = false;
 //                 this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map);
 //             }
@@ -272,7 +272,7 @@
 
 //     @action
 //     centered = () => {
-//         if (this._loadPending && this._map.getBounds()) {
+//         if (this._loadPending && this._map.getBounds) {
 //             this._loadPending = false;
 //             this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map);
 //         }
@@ -282,7 +282,7 @@
 
 //     @action
 //     zoomChanged = () => {
-//         if (this._loadPending && this._map.getBounds()) {
+//         if (this._loadPending && this._map.getBounds) {
 //             this._loadPending = false;
 //             this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map);
 //         }
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index a9c6ba22c..6ccbbbe1c 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -9,19 +9,18 @@
 // import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 // import { CollectionNoteTakingView } from '../../collections/CollectionNoteTakingView';
 // import { CollectionStackingView } from '../../collections/CollectionStackingView';
-// import { ViewBoxAnnotatableProps } from '../../DocComponent';
 // import { FieldViewProps } from '../FieldView';
 // import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 // import './MapBox.scss';
 
-// interface MapBoxInfoWindowProps {
+// interface MapBoxInfoWindowProps extends FieldViewProps {
 //     place: Doc;
 //     renderDepth: number;
 //     markerMap: { [id: string]: google.maps.Marker };
 //     isAnyChildContentActive: () => boolean;
 // }
 // @observer
-// export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & ViewBoxAnnotatableProps & FieldViewProps> {
+// export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps> {
 //     @action
 //     private handleInfoWindowClose = () => {
 //         if (this.props.place.infoWindowOpen) {
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index e079f7457..8a5bd7ce6 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Button, EditableText, IconButton, Type } from 'browndash-components';
-import { IReactionDisposer, ObservableMap, action, computed, observable, reaction, runInAction } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { MapProvider, Map as MapboxMap } from 'react-map-gl';
@@ -16,18 +16,17 @@ import { LinkManager } from '../../../util/LinkManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { UndoManager, undoable } from '../../../util/UndoManager';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../../DocComponent';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
 import { Colors } from '../../global/globalEnums';
-import { DocFocusOptions, DocumentView } from '../DocumentView';
-import { FieldView, FieldViewProps } from '../FieldView';
+import { DocumentView } from '../DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
 import { MapAnchorMenu } from '../MapBox/MapAnchorMenu';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../trails';
 import './MapBox.scss';
 
-// amongus
 /**
  * MapBox architecture:
  * Main component: MapBox.tsx
@@ -61,7 +60,7 @@ const bingApiKey = process.env.BING_MAPS; // if you're running local, get a Bing
 // });
 
 @observer
-export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(MapBoxContainer, fieldKey);
     }
@@ -71,6 +70,11 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
     private _disposers: { [key: string]: IReactionDisposer } = {};
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
 
+    constructor(props: FieldViewProps) {
+        super(props);
+        makeObservable(this);
+    }
+
     @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @computed get allSidebarDocs() {
         return DocListCast(this.dataDoc[this.SidebarKey]);
@@ -97,7 +101,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
 
     componentDidMount() {
         this._unmounting = false;
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
 
     _unmounting = false;
@@ -237,7 +241,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                 dragComplete: e => {
                     if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
-                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this._props.Document;
+                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.Document;
                         e.annoDragData.linkSourceDoc.followLinkZoom = false;
                     }
                 },
@@ -374,7 +378,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
         }
     };
 
-    getView = async (doc: Doc, options: DocFocusOptions) => {
+    getView = async (doc: Doc, options: FocusViewOptions) => {
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
             this.toggleSidebar();
             options.didMove = true;
@@ -787,8 +791,8 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
                                           NativeWidth={returnOne}
                                           NativeHeight={returnOne}
                                           onKey={undefined}
-                                          onDoubleClick={undefined}
-                                          onBrowseClick={undefined}
+                                          onDoubleClickScript={undefined}
+                                          onBrowseClickScript={undefined}
                                           childFilters={returnEmptyFilter}
                                           childFiltersByRanges={returnEmptyFilter}
                                           searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 7696a45a0..0eb3ffc3d 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -5,6 +5,7 @@ import * as Pdfjs from 'pdfjs-dist';
 import 'pdfjs-dist/web/pdf_viewer.css';
 import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { ComputedField } from '../../../fields/ScriptField';
@@ -22,19 +23,19 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 import { CollectionStackingView } from '../collections/CollectionStackingView';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { Colors } from '../global/globalEnums';
 import { CreateImage } from '../nodes/WebBoxRenderer';
 import { PDFViewer } from '../pdf/PDFViewer';
 import { SidebarAnnos } from '../SidebarAnnos';
-import { DocFocusOptions, DocumentView, OpenWhere } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
+import { DocumentView, OpenWhere } from './DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
 import { ImageBox } from './ImageBox';
 import './PDFBox.scss';
 import { PinProps, PresBox } from './trails';
 
 @observer
-export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(PDFBox, fieldKey);
     }
@@ -58,7 +59,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url;
     }
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
         const nw = Doc.NativeWidth(this.Document, this.dataDoc) || 927;
@@ -97,9 +98,10 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     crop = (region: Doc | undefined, addCrop?: boolean) => {
         if (!region) return;
         const cropping = Doc.MakeCopy(region, true);
-        Doc.GetProto(region).lockedPosition = true;
-        Doc.GetProto(region).title = 'region:' + this.Document.title;
-        Doc.GetProto(region).followLinkToggle = true;
+        const regionData = region[DocData];
+        regionData.lockedPosition = true;
+        regionData.title = 'region:' + this.Document.title;
+        regionData.followLinkToggle = true;
         this.addDocument(region);
 
         const docViewContent = this.DocumentView?.().ContentDiv!;
@@ -120,7 +122,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         cropping._width = anchw;
         cropping._height = anchh;
         cropping.onClick = undefined;
-        const croppingProto = Doc.GetProto(cropping);
+        const croppingProto = cropping[DocData];
         croppingProto.annotationOn = undefined;
         croppingProto.isDataDoc = true;
         croppingProto.proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
@@ -191,7 +193,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         this._disposers.select = reaction(
             () => this._props.isSelected(),
             () => {
@@ -219,12 +221,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
         return this._props.addDocTab(doc, where);
     };
-    focus = (anchor: Doc, options: DocFocusOptions) => {
+    focus = (anchor: Doc, options: FocusViewOptions) => {
         this._initialScrollTarget = anchor;
         return this._pdfViewer?.scrollFocus(anchor, NumCast(anchor.y, NumCast(anchor.config_scrollTop)), options);
     };
 
-    getView = async (doc: Doc, options: DocFocusOptions) => {
+    getView = async (doc: Doc, options: FocusViewOptions) => {
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
             options.didMove = true;
             this.toggleSidebar(false);
@@ -528,7 +530,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}>
                     <ComponentTag
                         {...this._props}
-                        setContentView={emptyFunction} // override setContentView to do nothing
+                        setContentViewBox={emptyFunction} // override setContentView to do nothing
                         NativeWidth={this.sidebarNativeWidthFunc}
                         NativeHeight={this.sidebarNativeHeightFunc}
                         PanelHeight={this._props.PanelHeight}
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index c04a81f7d..f6d94ce05 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -1,4 +1,4 @@
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { DateField } from '../../../../fields/DateField';
@@ -21,6 +21,7 @@ import { media_state } from '../AudioBox';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { VideoBox } from '../VideoBox';
 import { RecordingView } from './RecordingView';
+import { DocData } from '../../../../fields/DocSymbols';
 
 @observer
 export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -30,8 +31,13 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
 
+    constructor(props: FieldViewProps) {
+        super(props);
+        makeObservable(this);
+    }
+
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         Doc.SetNativeWidth(this.dataDoc, 1280);
         Doc.SetNativeHeight(this.dataDoc, 720);
     }
@@ -100,7 +106,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
                   });
         screengrabber.overlayX = 70; //was -400
         screengrabber.overlayY = 590; //was 0
-        Doc.GetProto(screengrabber)[Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
+        screengrabber[DocData][Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
         Doc.AddToMyOverlay(screengrabber); //just adds doc to overlay
         DocumentManager.Instance.AddViewRenderedCb(screengrabber, docView => {
             RecordingBox.screengrabber = docView.ComponentView as RecordingBox;
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index f74e6fb2b..1e3933ac3 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -21,12 +21,13 @@ import { TrackMovements } from '../../util/TrackMovements';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
 import { ContextMenu } from '../ContextMenu';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import { media_state } from './AudioBox';
 import { FieldView, FieldViewProps } from './FieldView';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import './ScreenshotBox.scss';
 import { VideoBox } from './VideoBox';
+import { DocData } from '../../../fields/DocSymbols';
 
 declare class MediaRecorder {
     constructor(e: any, options?: any); // whatever MediaRecorder has
@@ -109,7 +110,7 @@ declare class MediaRecorder {
 // }
 
 @observer
-export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(ScreenshotBox, fieldKey);
     }
@@ -121,7 +122,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         return Cast(this.dataDoc[this._props.fieldKey + '_recordingStart'], DateField)?.date.getTime();
     }
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
         this.setupDictation();
@@ -144,7 +145,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
 
     componentDidMount() {
         this.dataDoc.nativeWidth = this.dataDoc.nativeHeight = 0;
-        this._props.setContentView?.(this); // this tells the DocumentView that this ScreenshotBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
+        this._props.setContentViewBox?.(this); // this tells the DocumentView that this ScreenshotBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
         // this.layoutDoc.videoWall && reaction(() => ({ width: this._props.PanelWidth(), height: this._props.PanelHeight() }),
         //     ({ width, height }) => {
         //         if (this._camera) {
@@ -281,7 +282,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         const dictationText = DocUtils.GetNewTextDoc('dictation', NumCast(this.Document.x), NumCast(this.Document.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height));
         const textField = Doc.LayoutFieldKey(dictationText);
         dictationText._layout_autoHeight = false;
-        const dictationTextProto = Doc.GetProto(dictationText);
+        const dictationTextProto = dictationText[DocData];
         dictationTextProto[`${textField}_recordingSource`] = this.dataDoc;
         dictationTextProto[`${textField}_recordingStart`] = ComputedField.MakeFunction(`this.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
         dictationTextProto.mediaState = ComputedField.MakeFunction(`this.${textField}_recordingSource.mediaState`);
@@ -297,7 +298,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                     <div style={{ position: 'relative', height: this.videoPanelHeight() }}>
                         <CollectionFreeFormView
                             {...this._props}
-                            setContentView={emptyFunction}
+                            setContentViewBox={emptyFunction}
                             NativeWidth={returnZero}
                             NativeHeight={returnZero}
                             PanelHeight={this.videoPanelHeight}
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 73ad3a004..89650889d 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -14,7 +14,7 @@ import { ScriptManager } from '../../util/ScriptManager';
 import { CompileScript, ScriptParam } from '../../util/Scripting';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { ContextMenu } from '../ContextMenu';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import { EditableView } from '../EditableView';
 import { OverlayView } from '../OverlayView';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
@@ -23,7 +23,7 @@ import './ScriptingBox.scss';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
-export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     private dropDisposer?: DragManager.DragDropDisposer;
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(ScriptingBox, fieldStr);
@@ -56,7 +56,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
     @observable private _scriptSuggestedParams: any = '';
     @observable private _scriptParamsText: any = '';
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
         if (!this.compileParams.length) {
@@ -116,7 +116,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
 
     @action
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         this.rawText = this.rawScript;
         const observer = new _global.ResizeObserver(
             action((entries: any) => {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 1913ab032..a6c524881 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -4,10 +4,11 @@ import { observer } from 'mobx-react';
 import { basename } from 'path';
 import * as React from 'react';
 import { Doc, Opt, StrListCast } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { AudioField, ImageField, VideoField } from '../../../fields/URLField';
 import { emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -21,12 +22,12 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
 import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { StyleProp } from '../StyleProvider';
-import { DocFocusOptions, DocumentView } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
+import { DocumentView } from './DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
 import { RecordingBox } from './RecordingBox';
 import { PinProps, PresBox } from './trails';
 import './VideoBox.scss';
@@ -44,7 +45,7 @@ import './VideoBox.scss';
  */
 
 @observer
-export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(VideoBox, fieldKey);
     }
@@ -61,10 +62,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     private _playRegionTimer: any = null; // timeout for playback
     private _controlsFadeTimer: any = null; // timeout for controls fade
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
 
     @observable _stackedTimeline: CollectionStackedTimeline | undefined = undefined; // CollectionStackedTimeline ref
@@ -115,7 +116,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     componentDidMount() {
         this.unmounting = false;
-        this._props.setContentView?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link.
+        this._props.setContentViewBox?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link.
         this.player && this.setPlayheadTime(this.timeline?.clipStart || 0);
         document.addEventListener('keydown', this.keyEvents, true);
 
@@ -329,11 +330,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             _height: (height / width) * 150,
             title: '--snapshot' + NumCast(this.layoutDoc._layout_currentTimecode) + ' image-',
         });
-        Doc.SetNativeWidth(Doc.GetProto(imageSnapshot), Doc.NativeWidth(this.layoutDoc));
-        Doc.SetNativeHeight(Doc.GetProto(imageSnapshot), Doc.NativeHeight(this.layoutDoc));
+        Doc.SetNativeWidth(imageSnapshot[DocData], Doc.NativeWidth(this.layoutDoc));
+        Doc.SetNativeHeight(imageSnapshot[DocData], Doc.NativeHeight(this.layoutDoc));
         this._props.addDocument?.(imageSnapshot);
         const link = DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
-        link && (Doc.GetProto(link.link_anchor_2 as Doc).timecodeToHide = NumCast((link.link_anchor_2 as Doc).timecodeToShow) + 3);
+        link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3);
         setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, 'move', true));
     };
 
@@ -368,7 +369,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         !this.unmounting && this.player && (this.layoutDoc._layout_currentTimecode = this.player.currentTime);
     };
 
-    getView = (doc: Doc, options: DocFocusOptions) => {
+    getView = (doc: Doc, options: FocusViewOptions) => {
         if (this._stackedTimeline?.makeDocUnfiltered(doc)) {
             if (this.heightPercent === 100) {
                 this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
@@ -738,7 +739,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     zoom = (zoom: number) => this.timeline?.setZoom(zoom);
 
     // plays link
-    playLink = (doc: Doc, options: DocFocusOptions) => {
+    playLink = (doc: Doc, options: FocusViewOptions) => {
         const startTime = Math.max(0, NumCast(doc.config_clipStart, this._stackedTimeline?.anchorStart(doc) || 0));
         const endTime = this.timeline?.anchorEnd(doc);
         if (startTime !== undefined) {
@@ -885,10 +886,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     crop = (region: Doc | undefined, addCrop?: boolean) => {
         if (!region) return;
         const cropping = Doc.MakeCopy(region, true);
-        Doc.GetProto(region).backgroundColor = 'transparent';
-        Doc.GetProto(region).lockedPosition = true;
-        Doc.GetProto(region).title = 'region:' + this.Document.title;
-        Doc.GetProto(region).followLinkToggle = true;
+        const regionData = region[DocData];
+        regionData.backgroundColor = 'transparent';
+        regionData.lockedPosition = true;
+        regionData.title = 'region:' + this.Document.title;
+        regionData.followLinkToggle = true;
         region._timecodeToHide = NumCast(region._timecodeToShow) + 0.0001;
         this.addDocument(region);
         const anchx = NumCast(cropping.x);
@@ -904,7 +906,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         cropping.timecodeToHide = undefined;
         cropping.timecodeToShow = undefined;
         cropping.onClick = undefined;
-        const croppingProto = Doc.GetProto(cropping);
+        const croppingProto = cropping[DocData];
         croppingProto.annotationOn = undefined;
         croppingProto.isDataDoc = true;
         croppingProto.proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
@@ -955,7 +957,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         }}>
                         <CollectionFreeFormView
                             {...this._props}
-                            setContentView={emptyFunction}
+                            setContentViewBox={emptyFunction}
                             NativeWidth={returnZero}
                             NativeHeight={returnZero}
                             renderDepth={this._props.renderDepth + 1}
@@ -1004,7 +1006,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     }
 
     @computed get UIButtons() {
-        const bounds = this.DocumentView?.().getBounds();
+        const bounds = this.DocumentView?.().getBounds;
         const width = (bounds?.right || 0) - (bounds?.left || 0);
         const curTime = NumCast(this.layoutDoc._layout_currentTimecode);
         return (
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index b62d32503..5776cf6a6 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -24,7 +24,7 @@ import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { Colors } from '../global/globalEnums';
 import { LightboxView } from '../LightboxView';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
@@ -33,15 +33,15 @@ import { Annotation } from '../pdf/Annotation';
 import { GPTPopup } from '../pdf/GPTPopup/GPTPopup';
 import { SidebarAnnos } from '../SidebarAnnos';
 import { StyleProp } from '../StyleProvider';
-import { DocComponentView, DocFocusOptions, DocumentView, OpenWhere } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
+import { DocumentView, OpenWhere } from './DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
 import { LinkInfo } from './LinkDocPreview';
 import { PinProps, PresBox } from './trails';
 import './WebBox.scss';
 const { CreateImage } = require('./WebBoxRenderer');
 const _global = (window /* browser */ || global) /* node */ as any;
 @observer
-export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(WebBox, fieldKey);
     }
@@ -98,7 +98,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return Cast(this.Document[this._props.fieldKey], WebField)?.url;
     }
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
         this._webUrl = this._url; // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it.
@@ -172,7 +172,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
 
     componentDidMount() {
-        this._props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document.  this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons)
+        this._props.setContentViewBox?.(this); // this tells the DocumentView that this WebBox is the "content" of the document.  this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons)
 
         runInAction(() => {
             this._annotationKeySuffix = () => (this._urlHash ? this._urlHash + '_' : '') + 'annotations';
@@ -280,7 +280,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return this._savedAnnotations;
     };
 
-    focus = (anchor: Doc, options: DocFocusOptions) => {
+    focus = (anchor: Doc, options: FocusViewOptions) => {
         if (anchor !== this.Document && this._outerRef.current) {
             const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
             const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), NumCast(anchor._height), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + NumCast(anchor._height), this._scrollHeight));
@@ -297,7 +297,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
 
     @action
-    getView = (doc: Doc, options: DocFocusOptions) => {
+    getView = (doc: Doc, options: FocusViewOptions) => {
         if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this.DocumentView?.()));
         if (this.Document.layout_fieldKey === 'layout_icon') this.DocumentView?.().iconify();
         const webUrl = WebCast(doc.config_data)?.url;
@@ -924,7 +924,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
     _innerCollectionView: CollectionFreeFormView | undefined;
     zoomScaling = () => this._innerCollectionView?.zoomScaling() ?? 1;
-    setInnerContent = (component: DocComponentView) => (this._innerCollectionView = component as CollectionFreeFormView);
+    setInnerContent = (component: ViewBoxInterface) => (this._innerCollectionView = component as CollectionFreeFormView);
 
     @computed get content() {
         const interactive = this._props.isContentActive() && this._props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None;
@@ -966,7 +966,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     renderAnnotations = (childFilters: () => string[]) => (
         <CollectionFreeFormView
             {...this._props}
-            setContentView={this.setInnerContent}
+            setContentViewBox={this.setInnerContent}
             NativeWidth={returnZero}
             NativeHeight={returnZero}
             originTopLeft={false}
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index 4fb1a7f17..748c3322e 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -18,6 +18,11 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return FieldView.LayoutString(CalendarBox, fieldKey);
     }
 
+    constructor(props: FieldViewProps) {
+        super(props);
+        makeObservable(this);
+    }
+
     componentDidMount(): void {}
 
     componentWillUnmount(): void {}
@@ -105,11 +110,6 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
         });
     }
 
-    constructor(props: any) {
-        super(props);
-        makeObservable(this);
-    }
-
     render() {
         return (
             <div className="calendar-box-conatiner">
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 4ae12505e..bc49ed23d 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -6,12 +6,13 @@ import * as ReactDOM from 'react-dom/client';
 import { Doc } from '../../../../fields/Doc';
 import { Height, Width } from '../../../../fields/DocSymbols';
 import { NumCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnFalse, Utils } from '../../../../Utils';
+import { emptyFunction, returnFalse, Utils } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { Transform } from '../../../util/Transform';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { DocFocusOptions, DocumentView, returnEmptyDocViewList } from '../DocumentView';
+import { DocumentView } from '../DocumentView';
+import { FocusViewOptions } from '../FieldView';
 import { FormattedTextBox } from './FormattedTextBox';
 
 var horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side.
@@ -159,7 +160,7 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
         const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current);
         return new Transform(-translateX, -translateY, 1).scale(1 / scale);
     };
-    outerFocus = (target: Doc, options: DocFocusOptions) => this._textBox.focus(target, options); // ideally, this would scroll to show the focus target
+    outerFocus = (target: Doc, options: FocusViewOptions) => this._textBox.focus(target, options); // ideally, this would scroll to show the focus target
 
     onKeyDown = (e: any) => {
         e.stopPropagation();
@@ -178,9 +179,6 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
         ele && (ele.style.backgroundColor = 'orange');
     };
 
-    @computed get childContainerViewPath() {
-        return this._textBox.DocumentView?.().docViewPath ?? returnEmptyDocViewList;
-    }
     componentWillUnmount = () => Object.values(this._disposers).forEach(disposer => disposer?.());
     isContentActive = () => this._props.tbox._props.isContentActive() || this._props.tbox.isAnyChildContentActive?.();
 
@@ -210,7 +208,7 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
                     isDocumentActive={returnFalse}
                     isContentActive={this.isContentActive}
                     styleProvider={this._textBox._props.styleProvider}
-                    containerViewPath={this.childContainerViewPath}
+                    containerViewPath={this._textBox.DocumentView?.().docViewPath}
                     ScreenToLocalTransform={this.getDocTransform}
                     addDocTab={this._textBox._props.addDocTab}
                     pinToPres={returnFalse}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 65898d568..38400d2e7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -14,7 +14,7 @@ import * as React from 'react';
 import { BsMarkdownFill } from 'react-icons/bs';
 import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -42,7 +42,7 @@ import { CollectionStackingView } from '../../collections/CollectionStackingView
 import { CollectionTreeView } from '../../collections/CollectionTreeView';
 import { ContextMenu } from '../../ContextMenu';
 import { ContextMenuProps } from '../../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
 import { LightboxView } from '../../LightboxView';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
@@ -50,8 +50,8 @@ import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { StyleProp } from '../../StyleProvider';
 import { media_state } from '../AudioBox';
-import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
-import { FieldView, FieldViewProps } from '../FieldView';
+import { DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
 import { LinkInfo } from '../LinkDocPreview';
 import { PinProps, PresBox } from '../trails';
 import { DashDocCommentView } from './DashDocCommentView';
@@ -68,10 +68,8 @@ import { RichTextRules } from './RichTextRules';
 import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
 // import * as applyDevTools from 'prosemirror-dev-tools';
-
-export interface FormattedTextBoxProps {}
 @observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps & FormattedTextBoxProps>() {
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(FormattedTextBox, fieldStr);
     }
@@ -202,7 +200,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
         return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId
     }
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
         FormattedTextBox.Instance = this;
@@ -271,14 +269,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
                 if (target) {
                     anchor.followLinkAudio = true;
                     let stopFunc: any;
-                    Doc.GetProto(target).mediaState = media_state.Recording;
-                    Doc.GetProto(target).audioAnnoState = 'recording';
-                    DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target), stop => (stopFunc = stop));
+                    const targetData = target[DocData];
+                    targetData.mediaState = media_state.Recording;
+                    targetData.audioAnnoState = 'recording';
+                    DocumentViewInternal.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => (stopFunc = stop));
                     let reactionDisposer = reaction(
                         () => target.mediaState,
                         action(dictation => {
                             if (!dictation) {
-                                Doc.GetProto(target).audioAnnoState = 'stopped';
+                                targetData.audioAnnoState = 'stopped';
                                 stopFunc();
                                 reactionDisposer();
                             }
@@ -433,7 +432,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
 
     autoLink = () => {
         const newAutoLinks = new Set<Doc>();
-        const oldAutoLinks = LinkManager.Links(this._props.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords);
+        const oldAutoLinks = LinkManager.Links(this.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords);
         if (this._editorView?.state.doc.textContent) {
             const isNodeSel = this._editorView.state.selection instanceof NodeSelection;
             const f = this._editorView.state.selection.from;
@@ -451,7 +450,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
     updateTitle = () => {
         const title = StrCast(this.dataDoc.title, Cast(this.dataDoc.title, RichTextField, null)?.Text);
         if (
-            !this._props.dontRegisterView && // (this._props.Document.isTemplateForField === "text" || !this._props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
+            !this._props.dontRegisterView && // (this.Document.isTemplateForField === "text" || !this.Document.isTemplateForField) && // only update the title if the data document's data field is changing
             (title.startsWith('-') || title.startsWith('@')) &&
             this._editorView &&
             !this.dataDoc.title_custom &&
@@ -494,7 +493,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
                                     DocUtils.MakeLink(this.Document, target, { link_relationship: LinkManager.AutoKeywords })!);
                             newAutoLinks.add(alink);
                             // DocCast(alink.link_anchor_1).followLinkLocation = 'add:right';
-                            const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this._props.Document[Id] }];
+                            const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.Document[Id] }];
                             allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
                             const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term' });
                             tr = tr.addMark(pos, pos + node.nodeSize, link);
@@ -578,7 +577,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
                 // replace text contents when dragging with Alt
                 if (de.altKey) {
                     const fieldKey = Doc.LayoutFieldKey(draggedDoc);
-                    if (draggedDoc[fieldKey] instanceof RichTextField && !Doc.AreProtosEqual(draggedDoc, this._props.Document)) {
+                    if (draggedDoc[fieldKey] instanceof RichTextField && !Doc.AreProtosEqual(draggedDoc, this.Document)) {
                         Doc.GetProto(this.dataDoc)[this.fieldKey] = Field.Copy(draggedDoc[fieldKey]);
                     }
 
@@ -991,7 +990,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
                 };
                 const link = DocUtils.MakeLinkToActiveAudio(textanchorFunc, false).lastElement();
                 if (link) {
-                    Doc.GetProto(link).isDictation = true;
+                    link[DocData].isDictation = true;
                     const audioanchor = Cast(link.link_anchor_2, Doc, null);
                     const textanchor = Cast(link.link_anchor_1, Doc, null);
                     if (audioanchor) {
@@ -1001,7 +1000,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
                             audioId: audioanchor[Id],
                             textId: textanchor[Id],
                         });
-                        Doc.GetProto(textanchor).title = 'dictation:' + audiotag.attrs.timeCode;
+                        textanchor[DocData].title = 'dictation:' + audiotag.attrs.timeCode;
                         const tr = this._editorView.state.tr.insert(this._editorView.state.doc.content.size, audiotag);
                         const tr2 = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size));
                         this._editorView.dispatch(tr.setSelection(TextSelection.create(tr2.doc, tr2.doc.content.size)));
@@ -1056,7 +1055,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
         return anchorDoc ?? this.Document;
     }
 
-    getView = async (doc: Doc, options: DocFocusOptions) => {
+    getView = async (doc: Doc, options: FocusViewOptions) => {
         if (DocListCast(this.dataDoc[this.SidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
             if (!this.SidebarShown) {
                 this.toggleSidebar(false);
@@ -1066,7 +1065,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
         }
         return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
     };
-    focus = (textAnchor: Doc, options: DocFocusOptions) => {
+    focus = (textAnchor: Doc, options: FocusViewOptions) => {
         const focusSpeed = options.zoomTime ?? 500;
         const textAnchorId = textAnchor[Id];
         const findAnchorFrag = (frag: Fragment, editor: EditorView) => {
@@ -1141,7 +1140,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
         return Doc.NativeAspect(this.Document, this.dataDoc, false) ? this._props.NativeDimScaling?.() || 1 : 1;
     }
     componentDidMount() {
-        !this._props.dontSelectOnLoad && this._props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
+        !this._props.dontSelectOnLoad && this._props.setContentViewBox?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
         this._cachedLinks = LinkManager.Links(this.Document);
         this._disposers.breakupDictation = reaction(() => Doc.RecordingEvent, this.breakupDictation);
         this._disposers.layout_autoHeight = reaction(
@@ -1777,7 +1776,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
             }
         }
     };
-    fitContentsToBox = () => BoolCast(this._props.Document._freeform_fitContentsToBox);
+    fitContentsToBox = () => BoolCast(this.Document._freeform_fitContentsToBox);
     sidebarContentScaling = () => (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
     sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => {
         if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
@@ -2011,7 +2010,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
                         cursor: this._props.isContentActive() ? 'text' : undefined,
                         height: this._props.height ? 'max-content' : undefined,
                         overflow: this.layout_autoHeight ? 'hidden' : undefined,
-                        pointerEvents: Doc.ActiveTool === InkTool.None && !this._props.onBrowseClick?.() ? undefined : 'none',
+                        pointerEvents: Doc.ActiveTool === InkTool.None && !this._props.onBrowseClickScript?.() ? undefined : 'none',
                     }}
                     onContextMenu={this.specificContextMenu}
                     onKeyDown={this.onKeyDown}
diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx
index 7d0086c0c..6e7c3e612 100644
--- a/src/client/views/nodes/importBox/ImportElementBox.tsx
+++ b/src/client/views/nodes/importBox/ImportElementBox.tsx
@@ -1,4 +1,4 @@
-import { computed } from 'mobx';
+import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { returnFalse } from '../../../../Utils';
@@ -12,6 +12,10 @@ export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(ImportElementBox, fieldKey);
     }
+    constructor(props: FieldViewProps) {
+        super(props);
+        makeObservable(this);
+    }
 
     screenToLocalXf = () => this.ScreenToLocalBoxXf().scale(1 * (this._props.NativeDimScaling?.() || 1));
     @computed get mainItem() {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 645ac08e1..9e5ea9524 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, makeObservable, observable, Observ
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
-import { Animation } from '../../../../fields/DocSymbols';
+import { Animation, DocData } from '../../../../fields/DocSymbols';
 import { Copy, Id } from '../../../../fields/FieldSymbols';
 import { InkField } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -31,8 +31,8 @@ import { TreeView } from '../../collections/TreeView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
 import { LightboxView } from '../../LightboxView';
-import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView';
-import { FieldView, FieldViewProps } from '../FieldView';
+import { DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
 import { ScriptingBox } from '../ScriptingBox';
 import './PresBox.scss';
 import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
@@ -70,7 +70,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
     static navigateToDocScript: ScriptField;
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
         if (!PresBox.navigateToDocScript) {
@@ -184,7 +184,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             },
             { fireImmediately: true }
         );
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         this._unmounting = false;
         this.turnOffEdit(true);
         this._disposers.selection = reaction(
@@ -438,9 +438,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const setData = bestTargetView?.ComponentView?.setData;
             if (setData) setData(activeItem.config_data);
             else {
-                const current = Doc.GetProto(bestTarget)[fkey];
-                Doc.GetProto(bestTarget)[fkey + '_' + Date.now()] = current instanceof ObjectField ? current[Copy]() : current;
-                Doc.GetProto(bestTarget)[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
+                const bestTargetData = bestTarget[DocData];
+                const current = bestTargetData[fkey];
+                bestTargetData[fkey + '_' + Date.now()] = current instanceof ObjectField ? current[Copy]() : current;
+                bestTargetData[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
             }
             bestTarget[fkey + '_usePath'] = activeItem.config_usePath;
             setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10);
@@ -498,11 +499,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
         if (pinDataTypes?.inkable || (!pinDataTypes && (activeItem.config_fillColor !== undefined || activeItem.color !== undefined))) {
             if (bestTarget.fillColor !== activeItem.config_fillColor) {
-                Doc.GetProto(bestTarget).fillColor = StrCast(activeItem.config_fillColor, StrCast(bestTarget.fillColor));
+                bestTarget[DocData].fillColor = StrCast(activeItem.config_fillColor, StrCast(bestTarget.fillColor));
                 changed = true;
             }
             if (bestTarget.color !== activeItem.config_color) {
-                Doc.GetProto(bestTarget).color = StrCast(activeItem.config_color, StrCast(bestTarget.color));
+                bestTarget[DocData].color = StrCast(activeItem.config_color, StrCast(bestTarget.color));
                 changed = true;
             }
             if (bestTarget.width !== activeItem.width) {
@@ -559,7 +560,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     return doc;
                 });
             const newList = new List<Doc>([...oldItems, ...hiddenItems, ...newItems]);
-            Doc.GetProto(bestTarget)[fkey + '_annotations'] = newList;
+            bestTarget[DocData][fkey + '_annotations'] = newList;
         }
         if (pinDataTypes?.poslayoutview || (!pinDataTypes && activeItem.config_pinLayoutData !== undefined)) {
             changed = true;
@@ -580,9 +581,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         data.fill && (doc._fillColor = data.fill);
                         doc._width = data.w;
                         doc._height = data.h;
-                        data.data && (Doc.GetProto(doc).data = field);
-                        data.text && (Doc.GetProto(doc).text = tfield);
-                        Doc.AddDocToList(Doc.GetProto(bestTarget), layoutField, doc);
+                        data.data && (doc[DocData].data = field);
+                        data.text && (doc[DocData].text = tfield);
+                        Doc.AddDocToList(bestTarget[DocData], layoutField, doc);
                     }
                 });
             setTimeout(() => Array.from(transitioned).forEach(action(doc => (doc._dataTransition = undefined))), transTime + 10);
@@ -648,7 +649,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             }
             if (pinProps.pinData.dataannos) {
                 const fkey = Doc.LayoutFieldKey(targetDoc);
-                pinDoc.config_annotations = new List<Doc>(DocListCast(Doc.GetProto(targetDoc)[fkey + '_annotations']).filter(doc => !doc.layout_unrendered));
+                pinDoc.config_annotations = new List<Doc>(DocListCast(targetDoc[DocData][fkey + '_annotations']).filter(doc => !doc.layout_unrendered));
             }
             if (pinProps.pinData.inkable) {
                 pinDoc.config_fillColor = targetDoc.fillColor;
@@ -763,7 +764,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
         const effect = activeItem.presentation_effect && activeItem.presentation_effect !== PresEffect.None ? activeItem.presentation_effect : undefined;
         const presTime = NumCast(activeItem.presentation_transition, effect ? 750 : 500);
-        const options: DocFocusOptions = {
+        const options: FocusViewOptions = {
             willPan: activeItem.presentation_movement !== PresMovement.None,
             willZoomCentered: activeItem.presentation_movement === PresMovement.Zoom || activeItem.presentation_movement === PresMovement.Jump || activeItem.presentation_movement === PresMovement.Center,
             zoomScale: activeItem.presentation_movement === PresMovement.Center ? 0 : NumCast(activeItem.config_zoom, 1),
@@ -1115,7 +1116,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         presDocView && SelectionManager.SelectView(presDocView, false);
     };
 
-    focusElement = (doc: Doc, options: DocFocusOptions) => {
+    focusElement = (doc: Doc, options: FocusViewOptions) => {
         this.selectElement(doc);
         return undefined;
     };
@@ -2605,7 +2606,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 moveDocument={returnFalse}
                                 ignoreUnrendered={true}
                                 childDragAction="move"
-                                setContentView={emptyFunction}
+                                setContentViewBox={emptyFunction}
                                 //childLayoutFitWidth={returnTrue}
                                 childOpacity={returnOne}
                                 childClickScript={PresBox.navigateToDocScript}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 4bc79176e..7e68711fa 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -41,7 +41,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     readonly expandViewHeight = 100;
     readonly collapsedHeight = 35;
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
     }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 01590749e..ba7286111 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -17,16 +17,15 @@ import { SnappingManager } from '../../util/SnappingManager';
 import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
-import { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView';
-import { FieldViewProps } from '../nodes/FieldView';
+import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView';
 import { LinkInfo } from '../nodes/LinkDocPreview';
+import { PDFBox } from '../nodes/PDFBox';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { StyleProp } from '../StyleProvider';
 import { AnchorMenu } from './AnchorMenu';
 import { Annotation } from './Annotation';
 import { GPTPopup } from './GPTPopup/GPTPopup';
 import './PDFViewer.scss';
-import { PDFBox } from '../nodes/PDFBox';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 //pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
@@ -172,7 +171,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
 
     // scrolls to focus on a nested annotation document.  if this is part a link preview then it will jump to the scroll location,
     // otherwise it will scroll smoothly.
-    scrollFocus = (doc: Doc, scrollTop: number, options: DocFocusOptions) => {
+    scrollFocus = (doc: Doc, scrollTop: number, options: FocusViewOptions) => {
         const mainCont = this._mainCont.current;
         let focusSpeed: Opt<number>;
         if (doc !== this._props.Document && mainCont) {
@@ -521,7 +520,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                 {...this._props}
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
-                setContentView={emptyFunction} // override setContentView to do nothing
+                setContentViewBox={emptyFunction} // override setContentView to do nothing
                 pointerEvents={this._props.isContentActive() && (SnappingManager.IsDragging || Doc.ActiveTool !== InkTool.None) ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it.
                 childPointerEvents={this.childPointerEvents} // but freeform children need to get events to allow text editing, etc
                 renderDepth={this._props.renderDepth + 1}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 4d29573d4..5dc4f5550 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -3,7 +3,7 @@ import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCastAsync, Field } from '../../../fields/Doc';
-import { DirectLinks } from '../../../fields/DocSymbols';
+import { DirectLinks, DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, StrCast } from '../../../fields/Types';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -58,7 +58,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
     /**
      * This is the constructor for the SearchBox class.
      */
-    constructor(props: any) {
+    constructor(props: SearchBoxProps) {
         super(props);
         makeObservable(this);
         SearchBox.Instance = this;
@@ -207,7 +207,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
         this._results.forEach((_, doc) => {
             this._pageRanks.set(doc, 1.0 / this._results.size);
 
-            if (Doc.GetProto(doc)[DirectLinks].size === 0) {
+            if (doc[DocData][DirectLinks].size === 0) {
                 this._linkedDocsOut.set(doc, new Set(this._results.keys()));
 
                 this._results.forEach((_, linkedDoc) => {
@@ -216,7 +216,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
             } else {
                 const linkedDocSet = new Set<Doc>();
 
-                Doc.GetProto(doc)[DirectLinks].forEach(link => {
+                doc[DocData][DirectLinks].forEach(link => {
                     const d1 = link?.link_anchor_1 as Doc;
                     const d2 = link?.link_anchor_2 as Doc;
                     if (doc === d1 && this._results.has(d2)) {
diff --git a/src/client/views/selectedDoc/SelectedDocView.tsx b/src/client/views/selectedDoc/SelectedDocView.tsx
index 39e778b76..c9c01189e 100644
--- a/src/client/views/selectedDoc/SelectedDocView.tsx
+++ b/src/client/views/selectedDoc/SelectedDocView.tsx
@@ -1,14 +1,14 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { ListBox } from 'browndash-components';
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import { emptyFunction } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { StrCast } from '../../../fields/Types';
 import { DocumentManager } from '../../util/DocumentManager';
-import { DocFocusOptions } from '../nodes/DocumentView';
-import { emptyFunction } from '../../../Utils';
 import { SettingsManager } from '../../util/SettingsManager';
+import { FocusViewOptions } from '../nodes/FieldView';
 
 export interface SelectedDocViewProps {
     selectedDocs: Doc[];
@@ -25,7 +25,7 @@ export class SelectedDocView extends React.Component<SelectedDocViewProps> {
             <div className="selectedDocView-container">
                 <ListBox
                     items={this.selectedDocs.map(doc => {
-                        const options: DocFocusOptions = {
+                        const options: FocusViewOptions = {
                             playAudio: false,
                             playMedia: false,
                             willPan: true,
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
index 94458563e..4e984f3d6 100644
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ b/src/client/views/webcam/DashWebRTCVideo.tsx
@@ -8,7 +8,6 @@ import { Doc } from '../../../fields/Doc';
 import { InkTool } from '../../../fields/InkField';
 import { SnappingManager } from '../../util/SnappingManager';
 import '../../views/nodes/WebBox.scss';
-import { CollectionFreeFormDocumentViewProps } from '../nodes/CollectionFreeFormDocumentView';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import './DashWebRTCVideo.scss';
 import { hangup, initialize, refreshVideos } from './WebCamLogic';
@@ -17,14 +16,12 @@ import { hangup, initialize, refreshVideos } from './WebCamLogic';
  * This models the component that will be rendered, that can be used as a doc that will reflect the video cams.
  */
 @observer
-export class DashWebRTCVideo extends React.Component<CollectionFreeFormDocumentViewProps & FieldViewProps> {
+export class DashWebRTCVideo extends React.Component<FieldViewProps> {
     private roomText: HTMLInputElement | undefined;
     @observable remoteVideoAdded: boolean = false;
 
     @action
-    changeUILook = () => {
-        this.remoteVideoAdded = true;
-    };
+    changeUILook = () => (this.remoteVideoAdded = true);
 
     /**
      * Function that submits the title entered by user on enter press.
@@ -42,14 +39,9 @@ export class DashWebRTCVideo extends React.Component<CollectionFreeFormDocumentV
         return FieldView.LayoutString(DashWebRTCVideo, fieldKey);
     }
 
-    @action
-    onClickRefresh = () => {
-        refreshVideos();
-    };
+    onClickRefresh = () => refreshVideos();
 
-    onClickHangUp = () => {
-        hangup();
-    };
+    onClickHangUp = () => hangup();
 
     render() {
         const content = (
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 9c2350bd0..ff416bbe7 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -31,6 +31,7 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor }
 import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';
 import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, setter, SharingPermissions } from './util';
 import * as JSZip from 'jszip';
+import { FieldViewProps } from '../client/views/nodes/FieldView';
 export const LinkedTo = '-linkedTo';
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -423,7 +424,7 @@ export namespace Doc {
     //
     export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) {
         if (key.startsWith('_')) key = key.substring(1);
-        const hasProto = Doc.GetProto(doc) !== doc ? Doc.GetProto(doc) : undefined;
+        const hasProto = doc[DocData] !== doc ? doc[DocData] : undefined;
         const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1;
         const onProto = hasProto && Object.getOwnPropertyNames(hasProto).indexOf(key) !== -1;
         if (onDeleg || !hasProto || (!onProto && !defaultProto)) {
@@ -510,7 +511,7 @@ export namespace Doc {
     export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc) {
         const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc);
         if (listDoc[key] === undefined) {
-            Doc.GetProto(listDoc)[key] = new List<Doc>();
+            listDoc[DocData][key] = new List<Doc>();
         }
         const list = Cast(listDoc[key], listSpec(Doc));
         if (list) {
@@ -530,7 +531,7 @@ export namespace Doc {
     export function AddDocToList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean) {
         const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc);
         if (listDoc[key] === undefined) {
-            Doc.GetProto(listDoc)[key] = new List<Doc>();
+            listDoc[DocData][key] = new List<Doc>();
         }
         const list = Cast(listDoc[key], listSpec(Doc));
         if (list) {
@@ -564,7 +565,7 @@ export namespace Doc {
             Doc.SetLayout(embedding, Doc.MakeEmbedding(layout));
         }
         embedding.createdFrom = doc;
-        embedding.proto_embeddingId = Doc.GetProto(doc).proto_embeddingId = DocListCast(Doc.GetProto(doc).proto_embeddings).length - 1;
+        embedding.proto_embeddingId = doc[DocData].proto_embeddingId = DocListCast(doc[DocData].proto_embeddings).length - 1;
         !Doc.GetT(embedding, 'title', 'string', true) && (embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`));
         embedding.author = Doc.CurrentUserEmail;
 
@@ -1084,8 +1085,8 @@ export namespace Doc {
     export function LayoutField(doc: Doc) {
         return doc[StrCast(doc.layout_fieldKey, 'layout')];
     }
-    export function LayoutFieldKey(doc: Doc): string {
-        return StrCast(Doc.Layout(doc).layout).split("'")[1]; // bcz: TODO check on this .  used to always reference 'layout', now it uses the layout speicfied by the current layout_fieldKey
+    export function LayoutFieldKey(doc: Doc, templateLayoutString?: string): string {
+        return StrCast(templateLayoutString || Doc.Layout(doc).layout).split("'")[1]; // bcz: TODO check on this .  used to always reference 'layout', now it uses the layout speicfied by the current layout_fieldKey
     }
     export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) {
         return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
@@ -1100,10 +1101,10 @@ export namespace Doc {
         return NumCast(doc._nativeHeight, nheight || dheight);
     }
     export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) {
-        doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '_nativeWidth'] = width;
+        doc[(fieldKey || Doc.LayoutFieldKey(doc)) + '_nativeWidth'] = width;
     }
     export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) {
-        doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '_nativeHeight'] = height;
+        doc[(fieldKey || Doc.LayoutFieldKey(doc)) + '_nativeHeight'] = height;
     }
 
     const manager = new UserDocData();
@@ -1127,22 +1128,22 @@ export namespace Doc {
     }
 
     const isSearchMatchCache = computedFn(function IsSearchMatch(doc: Doc) {
-        return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
+        return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(doc[DocData]) ? brushManager.SearchMatchDoc.get(doc[DocData]) : undefined;
     });
     export function IsSearchMatch(doc: Doc) {
         return isSearchMatchCache(doc);
     }
     export function IsSearchMatchUnmemoized(doc: Doc) {
-        return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
+        return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(doc[DocData]) ? brushManager.SearchMatchDoc.get(doc[DocData]) : undefined;
     }
     export function SetSearchMatch(doc: Doc, results: { searchMatch: number }) {
-        if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) {
+        if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(doc[DocData]) !== AclPrivate) {
             brushManager.SearchMatchDoc.set(doc, results);
         }
         return doc;
     }
     export function SearchMatchNext(doc: Doc, backward: boolean) {
-        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
+        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(doc[DocData]) === AclPrivate) return doc;
         const result = brushManager.SearchMatchDoc.get(doc);
         const num = Math.abs(result?.searchMatch || 0) + 1;
         runInAction(() => result && brushManager.SearchMatchDoc.set(doc, { searchMatch: backward ? -num : num }));
@@ -1160,13 +1161,13 @@ export namespace Doc {
     }
     // returns 'how' a Doc has been brushed over - whether the document itself was brushed, it's prototype, or neither
     export function GetBrushStatus(doc: Doc) {
-        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return DocBrushStatus.unbrushed;
-        return doc[Brushed] ? DocBrushStatus.selfBrushed : Doc.GetProto(doc)[Brushed] ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
+        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(doc[DocData]) === AclPrivate || doc.opacity === 0) return DocBrushStatus.unbrushed;
+        return doc[Brushed] ? DocBrushStatus.selfBrushed : doc[DocData][Brushed] ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
     }
     export function BrushDoc(doc: Doc, unbrush = false) {
-        if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) {
+        if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(doc[DocData]) !== AclPrivate) {
             brushManager.brushDoc(doc, unbrush);
-            brushManager.brushDoc(Doc.GetProto(doc), unbrush);
+            brushManager.brushDoc(doc[DocData], unbrush);
         }
         return doc;
     }
@@ -1186,6 +1187,7 @@ export namespace Doc {
     }
     export function linkFollowUnhighlight() {
         clearTimeout(UnhighlightTimer);
+        UnhighlightTimer = 0;
         UnhighlightWatchers.forEach(watcher => watcher());
         UnhighlightWatchers.length = 0;
         highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc));
@@ -1205,8 +1207,8 @@ export namespace Doc {
 
     export var highlightedDocs = new ObservableSet<Doc>();
     export function IsHighlighted(doc: Doc) {
-        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return false;
-        return doc[Highlight] || Doc.GetProto(doc)[Highlight];
+        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(doc[DocData]) === AclPrivate || doc.opacity === 0) return false;
+        return doc[Highlight] || doc[DocData][Highlight];
     }
     export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presentation_effect?: Doc) {
         runInAction(() => {
@@ -1214,8 +1216,8 @@ export namespace Doc {
             doc[Highlight] = true;
             doc[Animation] = presentation_effect;
             if (dataAndDisplayDocs) {
-                highlightedDocs.add(Doc.GetProto(doc));
-                Doc.GetProto(doc)[Highlight] = true;
+                highlightedDocs.add(doc[DocData]);
+                doc[DocData][Highlight] = true;
             }
         });
     }
@@ -1224,8 +1226,8 @@ export namespace Doc {
         runInAction(() => {
             (doc ? [doc] : Array.from(highlightedDocs)).forEach(doc => {
                 highlightedDocs.delete(doc);
-                highlightedDocs.delete(Doc.GetProto(doc));
-                doc[Highlight] = Doc.GetProto(doc)[Highlight] = false;
+                highlightedDocs.delete(doc[DocData]);
+                doc[Highlight] = doc[DocData][Highlight] = false;
                 doc[Animation] = undefined;
             });
         });
@@ -1346,7 +1348,7 @@ export namespace Doc {
         });
     }
 
-    export function styleFromLayoutString(doc: Doc, props: any, scale: number) {
+    export function styleFromLayoutString(doc: Doc, props: FieldViewProps, scale: number) {
         const style: { [key: string]: any } = {};
         const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position'];
         const replacer = (match: any, expr: string, offset: any, string: any) => {
@@ -1354,7 +1356,7 @@ export namespace Doc {
             return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ this: doc, self: doc, scale }).result?.toString() ?? '';
         };
         divKeys.map((prop: string) => {
-            const p = props[prop];
+            const p = (props as any)[prop];
             typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
         });
         return style;
@@ -1593,7 +1595,7 @@ ScriptingGlobals.add(function idToDoc(id: string): any {
     return IdToDoc(id);
 });
 ScriptingGlobals.add(function renameEmbedding(doc: any) {
-    return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`;
+    return StrCast(doc[DocData].title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`;
 });
 ScriptingGlobals.add(function getProto(doc: any) {
     return Doc.GetProto(doc);
-- 
cgit v1.2.3-70-g09d2

