From f1ed1cff137c06afc4d4db8a8778f7827404889b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 9 Mar 2024 19:05:30 -0500
Subject: fixed up default text that uses a template to process an initial
 carriage return properly.  fixed text with inherited templates to be able to
 show fields with a default dashField value from template that can be
 overidden on instance.

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

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

diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index a4303c3aa..f59042b04 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -88,7 +88,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
 
         const textRegionAnno = Docs.Create.HTMLMarkerDocument([], {
             annotationOn: this.props.Document,
-            text: this.props.selectionText(),
+            text: this.props.selectionText() as any, // text want an RTFfield, but strings are acceptable, too.
             backgroundColor: 'transparent',
             presentation_duration: 2100,
             presentation_transition: 500,
@@ -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, 'yellow');
+                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
                 FormattedTextBox.SetSelectOnLoad(target);
                 return target;
             };
-- 
cgit v1.2.3-70-g09d2


From 409a72c8c2546851fc824877b27a71d101839e7e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 14 Mar 2024 10:55:29 -0400
Subject: cleaned up some audio recording  and annotating code

---
 src/client/views/MarqueeAnnotator.tsx              |  2 +-
 src/client/views/StyleProvider.tsx                 | 11 ++-
 src/client/views/nodes/DocumentView.tsx            | 90 ++++++++++------------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 21 ++---
 src/client/views/pdf/AnchorMenu.tsx                |  6 +-
 src/fields/Doc.ts                                  |  4 +-
 src/fields/DocSymbols.ts                           | 35 +++++----
 7 files changed, 81 insertions(+), 88 deletions(-)

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

diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index f59042b04..3c35b441b 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -184,7 +184,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
         };
         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.Highlight = (color: string) => this.highlight(color, false, undefined, true);
         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);
 
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index ab811858a..1adb0d9e5 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -50,6 +50,11 @@ export enum StyleProp {
     Highlighting = 'highlighting', // border highlighting
 }
 
+export enum AudioAnnoState {
+    stopped = 'stopped',
+    playing = 'playing',
+}
+
 function toggleLockedPosition(doc: Doc) {
     UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground');
 }
@@ -330,14 +335,14 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 );
             };
             const audio = () => {
-                const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, 'stopped');
+                const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, AudioAnnoState.stopped);
                 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' };
+                const audioIconColors: { [key: string]: string } = { playing: 'green', stopped: 'blue' };
                 return (
                     <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" />
+                            <FontAwesomeIcon className="documentView-audioFont" style={{ color: audioIconColors[audioAnnoState(doc)] }} icon='file-audio' size="sm" />
                         </div>
                     </Tooltip>
                 );
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 40592c2cd..bbeacef88 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -38,7 +38,7 @@ import { DocComponent, ViewBoxInterface } from '../DocComponent';
 import { EditableView } from '../EditableView';
 import { GestureOverlay } from '../GestureOverlay';
 import { LightboxView } from '../LightboxView';
-import { StyleProp } from '../StyleProvider';
+import { AudioAnnoState, StyleProp } from '../StyleProvider';
 import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView';
 import { DocumentLinksButton } from './DocumentLinksButton';
 import './DocumentView.scss';
@@ -1004,49 +1004,41 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     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?.();
-                });
-
-                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);
+        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) audioAnnos.push(audioField);
+                    else dataDoc[field + '_audioAnnotations'] = new List([audioField]);
+                }
+            };
+            recorder.start();
+            const stopFunc = () => {
+                recorder.stop();
+                DictationManager.Controls.stop(false);
+                dataDoc.audioAnnoState = AudioAnnoState.stopped;
+                gumStream.getAudioTracks()[0].stop();
+            };
+            if (onRecording) onRecording(stopFunc);
+            else setTimeout(stopFunc, 5000);
+        });
     }
 }
 
@@ -1231,25 +1223,25 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
 
     public playAnnotation = () => {
         const self = this;
-        const audioAnnoState = this.dataDoc.audioAnnoState ?? 'stopped';
+        const audioAnnoState = this.dataDoc.audioAnnoState ?? 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':
+                case AudioAnnoState.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')),
+                        onend: action(() => (self.dataDoc.audioAnnoState = AudioAnnoState.stopped)),
                     });
-                    this.dataDoc.audioAnnoState = 'playing';
+                    this.dataDoc.audioAnnoState = AudioAnnoState.playing;
                     break;
-                case 'playing':
+                case AudioAnnoState.playing:
                     this.dataDoc[AudioPlay]?.stop();
-                    this.dataDoc.audioAnnoState = 'stopped';
+                    this.dataDoc.audioAnnoState = AudioAnnoState.stopped;
                     break;
             }
         }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e80c869d3..fb709818c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -51,7 +51,7 @@ import { SidebarAnnos } from '../../SidebarAnnos';
 import { StyleProp } from '../../StyleProvider';
 import { media_state } from '../AudioBox';
 import { DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
+import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
 import { LinkInfo } from '../LinkDocPreview';
 import { PinProps, PresBox } from '../trails';
 import { DashDocCommentView } from './DashDocCommentView';
@@ -271,29 +271,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     let stopFunc: any;
                     const targetData = target[DocData];
                     targetData.mediaState = media_state.Recording;
-                    targetData.audioAnnoState = 'recording';
                     DocumentViewInternal.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => (stopFunc = stop));
-                    let reactionDisposer = reaction(
+                    const reactionDisposer = reaction(
                         () => target.mediaState,
-                        action(dictation => {
+                        dictation => {
                             if (!dictation) {
-                                targetData.audioAnnoState = 'stopped';
                                 stopFunc();
                                 reactionDisposer();
                             }
-                        })
+                        }
                     );
                     target.title = ComputedField.MakeFunction(`self["text_audioAnnotations_text"].lastElement()`);
                 }
             });
         };
-        AnchorMenu.Instance.Highlight = undoable(
-            action((color: string, isLinkButton: boolean) => {
-                this._editorView?.state && RichTextMenu.Instance.setHighlight(color);
-                return undefined;
-            }),
-            'highlght text'
-        );
+        AnchorMenu.Instance.Highlight = undoable((color: string) => {
+            this._editorView?.state && RichTextMenu.Instance.setHighlight(color);
+            return undefined;
+        }, 'highlght text');
         AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
         AnchorMenu.Instance.StartCropDrag = unimplementedFunction;
         /**
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index d0688c338..59f191af0 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -46,7 +46,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
     public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
     public StartCropDrag: (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 Highlight: (color: string) => Opt<Doc> = (color: string) => 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 PinToPres: () => void = unimplementedFunction;
@@ -118,8 +118,8 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     };
 
     @action
-    highlightClicked = (e: React.MouseEvent) => {
-        this.Highlight(this.highlightColor, false, undefined, true);
+    highlightClicked = () => {
+        this.Highlight(this.highlightColor);
         AnchorMenu.Instance.fadeOut(true);
     };
 
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index ea5411740..921d7aa5d 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -15,7 +15,7 @@ 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,
+    DocAcl, DocCss, DocData, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight,
     Initializing, Self, SelfProxy, TransitionTimer, UpdatingFromServer, Width
 } from './DocSymbols'; // prettier-ignore
 import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
@@ -227,7 +227,6 @@ export class Doc extends RefField {
                     DocAcl,
                     DocCss,
                     DocData,
-                    DocFields,
                     DocLayout,
                     DocViews,
                     FieldKeys,
@@ -308,7 +307,6 @@ export class Doc extends RefField {
             DocServer.UpdateField(this[Id], serverOp);
         }
     };
-    public [DocFields] = () => this[Self][FieldTuples]; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any);
     public [Width] = () => NumCast(this[SelfProxy]._width);
     public [Height] = () => NumCast(this[SelfProxy]._height);
     public [TransitionTimer]: any = undefined;
diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts
index 64d657e4f..837fcc90e 100644
--- a/src/fields/DocSymbols.ts
+++ b/src/fields/DocSymbols.ts
@@ -1,8 +1,26 @@
-export const DocUpdated = Symbol('DocUpdated');
+// Symbols for fundamental Doc operations such as: permissions, field and proxy access and server interactions
+export const AclPrivate = Symbol('DocAclOwnerOnly');
+export const AclReadonly = Symbol('DocAclReadOnly');
+export const AclAugment = Symbol('DocAclAugment');
+export const AclSelfEdit = Symbol('DocAclSelfEdit');
+export const AclEdit = Symbol('DocAclEdit');
+export const AclAdmin = Symbol('DocAclAdmin');
+export const DocAcl = Symbol('DocAcl');
+export const CachedUpdates = Symbol('DocCachedUpdates');
+export const UpdatingFromServer = Symbol('DocUpdatingFromServer');
+export const ForceServerWrite = Symbol('DocForceServerWrite');
 export const Self = Symbol('DocSelf');
 export const SelfProxy = Symbol('DocSelfProxy');
 export const FieldKeys = Symbol('DocFieldKeys');
 export const FieldTuples = Symbol('DocFieldTuples');
+export const Initializing = Symbol('DocInitializing');
+
+// Symbols for core Dash document model including data docs, layout docs, and links
+export const DocData = Symbol('DocData');
+export const DocLayout = Symbol('DocLayout');
+export const DirectLinks = Symbol('DocDirectLinks');
+
+// Symbols for view related operations for Documents
 export const AudioPlay = Symbol('DocAudioPlay');
 export const Width = Symbol('DocWidth');
 export const Height = Symbol('DocHeight');
@@ -10,22 +28,7 @@ export const Animation = Symbol('DocAnimation');
 export const Highlight = Symbol('DocHighlight');
 export const DocViews = Symbol('DocViews');
 export const Brushed = Symbol('DocBrushed');
-export const DocData = Symbol('DocData');
-export const DocLayout = Symbol('DocLayout');
-export const DocFields = Symbol('DocFields');
 export const DocCss = Symbol('DocCss');
-export const DocAcl = Symbol('DocAcl');
 export const TransitionTimer = Symbol('DocTransitionTimer');
-export const DirectLinks = Symbol('DocDirectLinks');
-export const AclPrivate = Symbol('DocAclOwnerOnly');
-export const AclReadonly = Symbol('DocAclReadOnly');
-export const AclAugment = Symbol('DocAclAugment');
-export const AclSelfEdit = Symbol('DocAclSelfEdit');
-export const AclEdit = Symbol('DocAclEdit');
-export const AclAdmin = Symbol('DocAclAdmin');
-export const UpdatingFromServer = Symbol('DocUpdatingFromServer');
-export const Initializing = Symbol('DocInitializing');
-export const ForceServerWrite = Symbol('DocForceServerWrite');
-export const CachedUpdates = Symbol('DocCachedUpdates');
 
 export const DashVersion = 'v0.8.0';
-- 
cgit v1.2.3-70-g09d2


From 38be41b6ae73e0b295245d96636db74cb6848d6c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 18 Mar 2024 10:29:17 -0400
Subject: changed pdf paste ref title

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

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

diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 3c35b441b..bd6be2519 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -93,7 +93,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
             presentation_duration: 2100,
             presentation_transition: 500,
             presentation_zoomText: true,
-            title: 'Selection on ' + this.props.Document.title,
+            title: '>' + this.props.Document.title,
         });
         let minX = Number.MAX_VALUE;
         let maxX = -Number.MAX_VALUE;
-- 
cgit v1.2.3-70-g09d2


From b420caf2c7ecd386cae2cc550904522474b541aa Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 26 Mar 2024 22:34:10 -0400
Subject: added empty image tool and click on empty image to select from
 filesystem.   fixed following links in lightbox and showing links to
 stackedTimelines.  fixed embedding docs into text.  fixed not resizing text
 boxes that also show up in pivot view.  prevent context menu from going off
 top of screen.   fixed freeform clustering colors and click to type. fixed
 links to stackedTimeline marks, and titles for marks.  made title editing
 from doc deco and header use same syntax as keyValue.  fixed marquee
 selection on webBoxes.  turn off transitions in freeformdocview after
 timeout. enabled iconifying templates to propagate to "offspring".  fixes
 images in templates.  don't show headr on schema views.

---
 src/client/documents/Documents.ts                  | 52 ++++++++--------
 src/client/util/DocumentManager.ts                 |  4 +-
 src/client/views/ContextMenu.tsx                   |  6 +-
 src/client/views/DocumentDecorations.tsx           | 71 ++++++++--------------
 src/client/views/FieldsDropdown.tsx                |  1 -
 src/client/views/LightboxView.tsx                  |  2 +
 src/client/views/MarqueeAnnotator.tsx              | 19 ++++--
 src/client/views/ObservableReactComponent.tsx      |  3 +-
 src/client/views/PropertiesView.tsx                |  2 +-
 src/client/views/StyleProvider.tsx                 |  6 +-
 .../collections/CollectionStackedTimeline.tsx      | 13 ++--
 .../CollectionFreeFormLayoutEngines.tsx            |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 11 ++--
 .../collections/collectionFreeForm/MarqueeView.tsx |  4 +-
 .../collectionSchema/SchemaTableCell.tsx           |  2 +-
 src/client/views/global/globalScripts.ts           |  4 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 24 ++++++--
 src/client/views/nodes/DocumentContentsView.tsx    |  7 ++-
 src/client/views/nodes/DocumentView.tsx            | 35 +++++++----
 src/client/views/nodes/FieldView.tsx               |  1 +
 src/client/views/nodes/ImageBox.tsx                | 55 +++++++++++++----
 src/client/views/nodes/KeyValueBox.tsx             |  2 +-
 src/client/views/nodes/LabelBox.tsx                |  2 +-
 src/client/views/nodes/LinkBox.tsx                 | 47 ++++++++++----
 src/client/views/nodes/VideoBox.tsx                |  5 +-
 src/client/views/nodes/WebBox.tsx                  | 65 ++++++++++++++------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 17 ++++--
 src/client/views/nodes/trails/PresBox.tsx          |  2 +-
 src/fields/Doc.ts                                  | 29 +++++----
 src/fields/PresField.ts                            |  6 --
 src/fields/RichTextField.ts                        |  7 ---
 31 files changed, 311 insertions(+), 195 deletions(-)
 delete mode 100644 src/fields/PresField.ts

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 17cb6fef8..b63c5e429 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1017,8 +1017,8 @@ export namespace Docs {
         }
 
         export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}, overwriteDoc?: Doc) {
-            const imgField = url instanceof ImageField ? url : new ImageField(url);
-            return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }, undefined, undefined, undefined, overwriteDoc);
+            const imgField = url instanceof ImageField ? url : url ? new ImageField(url) : undefined;
+            return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField?.url.href ?? '-no image-'), ...options }, undefined, undefined, undefined, overwriteDoc);
         }
 
         export function PresDocument(options: DocumentOptions = {}) {
@@ -1950,6 +1950,31 @@ export namespace DocUtils {
         return dd;
     }
 
+    export function assignImageInfo(result: Upload.FileInformation, proto: Doc) {
+        if (Upload.isImageInformation(result)) {
+            const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim);
+            const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase();
+            proto['data-nativeOrientation'] = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined);
+            proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+            proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+            if (NumCast(proto['data-nativeOrientation']) >= 5) {
+                proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+                proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+            }
+            proto.data_exif = JSON.stringify(result.exifData?.data);
+            proto.data_contentSize = result.contentSize;
+            // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates
+            const latitude = result.exifData?.data?.GPSLatitude;
+            const latitudeDirection = result.exifData?.data?.GPSLatitudeRef;
+            const longitude = result.exifData?.data?.GPSLongitude;
+            const longitudeDirection = result.exifData?.data?.GPSLongitudeRef;
+            if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) {
+                proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection);
+                proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection);
+            }
+        }
+    }
+
     async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions, overwriteDoc?: Doc) {
         if (result instanceof Error) {
             alert(`Upload failed: ${result.message}`);
@@ -1961,28 +1986,7 @@ export namespace DocUtils {
         if (doc) {
             const proto = Doc.GetProto(doc);
             proto.text = result.rawText;
-            if (Upload.isImageInformation(result)) {
-                const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim);
-                const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase();
-                proto['data-nativeOrientation'] = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined);
-                proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
-                proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
-                if (NumCast(proto['data-nativeOrientation']) >= 5) {
-                    proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
-                    proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
-                }
-                proto.data_exif = JSON.stringify(result.exifData?.data);
-                proto.data_contentSize = result.contentSize;
-                // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates
-                const latitude = result.exifData?.data?.GPSLatitude;
-                const latitudeDirection = result.exifData?.data?.GPSLatitudeRef;
-                const longitude = result.exifData?.data?.GPSLongitude;
-                const longitudeDirection = result.exifData?.data?.GPSLongitudeRef;
-                if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) {
-                    proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection);
-                    proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection);
-                }
-            }
+            !(result instanceof Error) && DocUtils.assignImageInfo(result, proto);
             if (Upload.isVideoInformation(result)) {
                 proto.data_duration = result.duration;
             }
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index a38a330da..40d28c690 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -59,7 +59,7 @@ export class DocumentManager {
     private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => any }[] = [];
     public AddViewRenderedCb = (doc: Opt<Doc>, func: (dv: DocumentView) => any) => {
         if (doc) {
-            const dv = this.getDocumentView(doc);
+            const dv = LightboxView.LightboxDoc ? this.getLightboxDocumentView(doc) : this.getDocumentView(doc);
             this._viewRenderedCbs.push({ doc, func });
             if (dv) {
                 this.callAddViewFuncs(dv);
@@ -262,7 +262,7 @@ export class DocumentManager {
                     return res(this.getDocumentView(docContextPath[0])!);
                 }
                 options.didMove = true;
-                docContextPath.some(doc => TabDocView.Activate(doc)) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
+                (!LightboxView.LightboxDoc && docContextPath.some(doc => TabDocView.Activate(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
                 this.AddViewRenderedCb(docContextPath[0], dv => res(dv));
             }));
         if (options.openLocation === OpenWhere.lightbox) {
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 8f4e43978..ca877b93e 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -218,11 +218,12 @@ export class ContextMenu extends ObservableReactComponent<{}> {
                         this._width = DivWidth(r);
                         this._height = DivHeight(r);
                     }
+                    this._searchRef.current?.focus();
                 })}
                 style={{
                     display: this._display ? '' : 'none',
                     left: this.pageX,
-                    ...(this._yRelativeToTop ? { top: this.pageY } : { bottom: this.pageY }),
+                    ...(this._yRelativeToTop ? { top: Math.max(0, this.pageY) } : { bottom: this.pageY }),
                     background: SettingsManager.userBackgroundColor,
                     color: SettingsManager.userColor,
                 }}>
@@ -265,7 +266,8 @@ export class ContextMenu extends ObservableReactComponent<{}> {
             const item = this.flatItems[this._selectedIndex];
             if (item) {
                 item.event({ x: this.pageX, y: this.pageY });
-            } else if (this._searchString.startsWith(this._defaultPrefix)) {
+            } else {
+                //if (this._searchString.startsWith(this._defaultPrefix)) {
                 this._defaultItem?.(this._searchString.substring(this._defaultPrefix.length));
             }
             this.closeMenu();
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2fb9f0fc1..4d9b93896 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -34,6 +34,7 @@ import { Colors } from './global/globalEnums';
 import { DocumentView, OpenWhereMod } from './nodes/DocumentView';
 import { ImageBox } from './nodes/ImageBox';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { KeyValueBox } from './nodes/KeyValueBox';
 
 interface DocumentDecorationsProps {
     PanelWidth: number;
@@ -57,7 +58,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
 
     @observable _showNothing = true;
     @observable private _accumulatedTitle = '';
-    @observable private _titleControlString: string = '#title';
+    @observable private _titleControlString: string = '$title';
     @observable private _editingTitle = false;
     @observable private _hidden = false;
     @observable private _isRotating: boolean = false;
@@ -111,9 +112,9 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
 
     @action
     titleBlur = () => {
-        if (this._accumulatedTitle.startsWith('#') || this._accumulatedTitle.startsWith('=')) {
+        if (this._accumulatedTitle.startsWith('$')) {
             this._titleControlString = this._accumulatedTitle;
-        } else if (this._titleControlString.startsWith('#')) {
+        } else if (this._titleControlString.startsWith('$')) {
             if (this._accumulatedTitle.startsWith('-->#')) {
                 SelectionManager.Docs.forEach(doc => (doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`)));
             }
@@ -131,26 +132,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                 Doc.AddToMyPublished(d.Document);
                             }
                         }
-                        //@ts-ignore
-                        const titleField = +this._accumulatedTitle == this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle;
-
-                        if (titleField.toString().startsWith('<this>')) {
-                            const title = titleField.toString().replace(/<this>\.?/, '');
-                            const curKey = Doc.LayoutFieldKey(d.Document);
-                            if (curKey !== title) {
-                                if (title) {
-                                    if (d.dataDoc[title] === undefined || d.dataDoc[title] instanceof RichTextField || typeof d.dataDoc[title] === 'string') {
-                                        d.Document.layout_fieldKey = `layout_${title}`;
-                                        d.Document[`layout_${title}`] = FormattedTextBox.LayoutString(title);
-                                        d.Document[`${title}_nativeWidth`] = d.Document[`${title}_nativeHeight`] = 0;
-                                    }
-                                } else {
-                                    d.Document.layout_fieldKey = undefined;
-                                }
-                            }
-                        } else {
-                            Doc.SetInPlace(d.Document, titleFieldKey, titleField, true);
-                        }
+                        KeyValueBox.SetField(d.Document, titleFieldKey, this._accumulatedTitle);
                     }),
                 'edit title'
             );
@@ -181,7 +163,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                 e => this.onBackgroundMove(true, e),
                 emptyFunction,
                 action(e => {
-                    !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('#') ? this.selectionTitle : this._titleControlString);
+                    const selected = SelectionManager.Views.length === 1 ? SelectionManager.Docs[0] : undefined;
+                    !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('$') ? (selected && Field.toKeyValueString(selected, this._titleControlString.substring(1))) || '-unset-' : this._titleControlString);
                     this._editingTitle = true;
                     this._keyinput.current && setTimeout(this._keyinput.current.focus);
                 })
@@ -622,11 +605,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     get selectionTitle(): string {
         if (SelectionManager.Views.length === 1) {
             const selected = SelectionManager.Views[0];
-            if (this._titleControlString.startsWith('=')) {
-                return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })?.script.run({ self: selected.Document, this: selected.layoutDoc }, console.log).result?.toString() || '';
-            }
-            if (this._titleControlString.startsWith('#')) {
-                return Field.toString(selected.Document[this._titleControlString.substring(1)] as Field) || '-unset-';
+            if (this._titleControlString.startsWith('$')) {
+                return Field.toJavascriptString(selected.Document[this._titleControlString.substring(1)] as Field) || '-unset-';
             }
             return this._accumulatedTitle;
         }
@@ -731,21 +711,24 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             ) : null;
 
         const titleArea = this._editingTitle ? (
-            <input
-                ref={this._keyinput}
-                className="documentDecorations-title"
-                type="text"
-                name="dynbox"
-                autoComplete="on"
-                value={hideTitle ? '' : this._accumulatedTitle}
-                onBlur={action((e: React.FocusEvent) => {
-                    this._editingTitle = false;
-                    !hideTitle && this.titleBlur();
-                })}
-                onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
-                onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
-                onPointerDown={e => e.stopPropagation()}
-            />
+            <>
+                {r - x < 150 ? null : <span>{this._titleControlString + ':'}</span>}
+                <input
+                    ref={this._keyinput}
+                    className="documentDecorations-title"
+                    type="text"
+                    name="dynbox"
+                    autoComplete="on"
+                    value={hideTitle ? '' : this._accumulatedTitle}
+                    onBlur={action((e: React.FocusEvent) => {
+                        this._editingTitle = false;
+                        !hideTitle && this.titleBlur();
+                    })}
+                    onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
+                    onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
+                    onPointerDown={e => e.stopPropagation()}
+                />
+            </>
         ) : (
             <div className="documentDecorations-title" key="title">
                 {hideTitle ? null : (
diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx
index 5638d34c6..6a5c2cb4c 100644
--- a/src/client/views/FieldsDropdown.tsx
+++ b/src/client/views/FieldsDropdown.tsx
@@ -61,7 +61,6 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps
             .forEach((pair: [string, FInfo]) => filteredOptions.push(pair[0]));
         const options = filteredOptions.sort().map(facet => ({ value: facet, label: facet }));
 
-        console.log(options);
         return (
             <Select
                 styles={{
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index c7ff7ce47..b9d805145 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -23,6 +23,7 @@ import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
 import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import { SnappingManager } from '../util/SnappingManager';
+import { Id } from '../../fields/FieldSymbols';
 
 interface LightboxViewProps {
     PanelWidth: number;
@@ -247,6 +248,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
                     }}>
                     <GestureOverlay isActive={true}>
                         <DocumentView
+                            key={this._doc.title + this._doc[Id]} // this makes a new DocumentView when the document changes which makes link following work, otherwise no DocView is registered for the new Doc
                             ref={action((r: DocumentView | null) => (this._docView = r !== null ? r : undefined))}
                             Document={this._doc}
                             PanelWidth={this.lightboxWidth}
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index bd6be2519..c29474fcd 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -34,7 +34,7 @@ export interface MarqueeAnnotatorProps {
     annotationLayer: HTMLDivElement;
     addDocument: (doc: Doc) => boolean;
     getPageFromScroll?: (top: number) => number;
-    finishMarquee: (x?: number, y?: number, PointerEvent?: PointerEvent) => void;
+    finishMarquee: (x?: number, y?: number) => void;
     anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
     anchorMenuCrop?: (anchor: Doc | undefined, addCrop: boolean) => Doc | undefined;
     highlightDragSrcColor?: string;
@@ -168,7 +168,6 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
 
     @action
     public onInitiateSelection(down: number[]) {
-        console.log('DOWN = ' + down[0] + ' ' + down[1]);
         this._width = this._height = 0;
         this._start = this.getTransformedScreenPt(down);
 
@@ -240,6 +239,13 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
         document.removeEventListener('pointerup', this.onSelectEnd);
     }
 
+    @action
+    onMove = (pt: number[]) => {
+        const movLoc = this.getTransformedScreenPt(pt);
+        this._width = movLoc.x - this._start.x;
+        this._height = movLoc.y - this._start.y;
+    };
+
     @action
     onSelectMove = (e: PointerEvent) => {
         const movLoc = this.getTransformedScreenPt([e.clientX, e.clientY]);
@@ -248,9 +254,12 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
         //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) => {
         e.stopPropagation();
+        this.onEnd(e.clientX, e.clientY);
+    };
+    @action
+    onEnd = (x: number, y: number) => {
         const marquees = this.props.marqueeContainer.getElementsByClassName('marqueeAnnotator-dragBox');
         const marqueeStyle = (Array.from(marquees).lastElement() as HTMLDivElement)?.style;
         if (!this.isEmpty && marqueeStyle) {
@@ -266,9 +275,9 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
             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);
+            AnchorMenu.Instance.jumpTo(x, y);
         }
-        this.props.finishMarquee(this.isEmpty ? e.clientX : undefined, this.isEmpty ? e.clientY : undefined, e);
+        this.props.finishMarquee(this.isEmpty ? x : undefined, this.isEmpty ? y : undefined);
         this._width = this._height = 0;
     };
 
diff --git a/src/client/views/ObservableReactComponent.tsx b/src/client/views/ObservableReactComponent.tsx
index 9b2b00903..394d1eae2 100644
--- a/src/client/views/ObservableReactComponent.tsx
+++ b/src/client/views/ObservableReactComponent.tsx
@@ -14,8 +14,7 @@ export abstract class ObservableReactComponent<T> extends React.Component<T, {}>
         makeObservable(this);
     }
     componentDidUpdate(prevProps: Readonly<T>): void {
-        Object.keys(prevProps).forEach(action(pkey =>
-            (prevProps as any)[pkey] !== (this.props as any)[pkey] &&
+        Object.keys(prevProps).filter(pkey => (prevProps as any)[pkey] !== (this.props as any)[pkey]).forEach(action(pkey =>
                 ((this._props as any)[pkey] = (this.props as any)[pkey]))); // prettier-ignore
     }
 }
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index cbd3ff358..195b1a04c 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1399,7 +1399,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         const zoom = Number((NumCast(this.sourceAnchor?.followLinkZoomScale, 1) * 100).toPrecision(3));
         const targZoom = this.sourceAnchor?.followLinkZoom;
         const indent = 30;
-        const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(this.selectedLink!);
+        const hasSelectedAnchor = this.selectedLink !== this.selectedDoc && LinkManager.Links(this.sourceAnchor).includes(this.selectedLink!);
 
         return (
             <>
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 1adb0d9e5..7a15e67e4 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -25,6 +25,7 @@ import { FieldViewProps } from './nodes/FieldView';
 import { KeyValueBox } from './nodes/KeyValueBox';
 import { PropertiesView } from './PropertiesView';
 import './StyleProvider.scss';
+import { CollectionSchemaView } from './collections/collectionSchema/CollectionSchemaView';
 
 export enum StyleProp {
     TreeViewIcon = 'treeView_Icon',
@@ -137,14 +138,15 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
         case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
         case StyleProp.Opacity:    return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 :  Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
         case StyleProp.FontSize:   return StrCast(doc?.[fieldKey + 'fontSize'],   StrCast(Doc.UserDoc().fontSize));
-        case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily));
+        case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamilyq'], StrCast(Doc.UserDoc().fontFamily));
         case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'],  StrCast(Doc.UserDoc().fontWeight));
         case StyleProp.FillColor:  return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent')));
         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.TitleHeight:return Math.min(4,(props?.DocumentView?.().screenToViewTransform().Scale ?? 1)) * NumCast(Doc.UserDoc().headerHeight,30);
         case StyleProp.ShowTitle:
             return (
                 (doc &&
+                    !(props?.DocumentView?.().ComponentView instanceof CollectionSchemaView) &&
                     !props?.LayoutTemplateString &&
                     !doc.presentation_targetDoc &&
                     !props?.LayoutTemplateString?.includes(KeyValueBox.name) &&
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 5c47d4b9e..656f850b3 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -594,6 +594,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                                     <StackedTimelineAnchor
                                         {...this._props}
                                         mark={d.anchor}
+                                        containerViewPath={this._props.containerViewPath}
                                         rangeClickScript={this.rangeClickScript}
                                         rangePlayScript={this.rangePlayScript}
                                         left={left - this._scroll}
@@ -701,6 +702,7 @@ interface StackedTimelineAnchorProps {
     layoutDoc: Doc;
     isDocumentActive?: () => boolean | undefined;
     ScreenToLocalTransform: () => Transform;
+    containerViewPath?: () => DocumentView[];
     _timeline: HTMLDivElement | null;
     focus: FocusFuncType;
     currentTimecode: () => number;
@@ -796,12 +798,15 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
         );
     };
 
+    resetTitle = () => {
+        this._props.mark[DocData].title = ComputedField.MakeFunction(
+            `["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"]`
+        );
+    };
     // context menu
     contextMenuItems = () => {
         const resetTitle = {
-            script: ScriptField.MakeFunction(
-                `this.title = this["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"])`
-            )!,
+            method: this.resetTitle,
             icon: 'folder-plus',
             label: 'Reset Title',
         };
@@ -826,7 +831,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
                     ref={action((r: DocumentView | null) => (anchor.view = r))}
                     Document={mark}
                     TemplateDataDocument={undefined}
-                    containerViewPath={returnEmptyDoclist}
+                    containerViewPath={this._props.containerViewPath}
                     pointerEvents={this.noEvents ? returnNone : undefined}
                     styleProvider={this._props.styleProvider}
                     renderDepth={this._props.renderDepth + 1}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index b8c0967c1..6484658e0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -388,7 +388,7 @@ function normalizeResults(
     minWidth: number,
     extras: ViewDefBounds[]
 ): ViewDefResult[] {
-    const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height } as ViewDefBounds));
+    const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
     const docEles = Array.from(docMap.entries()).map(ele => ele[1]);
     const aggBounds = aggregateBounds(
         extras.concat(grpEles.concat(docEles.map(de => ({ ...de, type: 'doc', payload: '' })))).filter(e => e.zIndex !== -99),
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1fd453e96..5495735ea 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -565,7 +565,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     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) {
+            switch (property.split(':')[0]) {
                 case StyleProp.BackgroundColor:
                     const cluster = NumCast(doc?.layout_cluster);
                     if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) {
@@ -590,7 +590,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     trySelectCluster = (addToSel: boolean) => {
-        if (this._hitCluster !== -1) {
+        if (addToSel && this._hitCluster !== -1) {
             !addToSel && SelectionManager.DeselectAll();
             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);
@@ -1288,15 +1288,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const rotation = Cast(_rotation,'number',
             !this.layoutDoc._rotation_jitter ? null
                 : NumCast(this.layoutDoc._rotation_jitter) * random(-1, 1, NumCast(x), NumCast(y)) );
+        const childProps = { ...this._props, styleProvider: this.clusterStyleProvider };
         return {
             x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x),
             y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y),
             z: Cast(z, 'number'),
             autoDim,
             rotation,
-            color: Cast(color, 'string') ? StrCast(color) : this._props.styleProvider?.(childDoc, this._props, StyleProp.Color),
-            backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, this._props, StyleProp.BackgroundColor),
-            opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this._props.styleProvider?.(childDoc, this._props, StyleProp.Opacity),
+            color: Cast(color, 'string') ? StrCast(color) : this.clusterStyleProvider(childDoc, childProps, StyleProp.Color),
+            backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, childProps, StyleProp.BackgroundColor),
+            opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.clusterStyleProvider?.(childDoc, childProps, StyleProp.Opacity),
             zIndex: Cast(zIndex, 'number'),
             width: _width,
             height: _height,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b913e05ad..cb593a993 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -107,7 +107,9 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
         const cm = ContextMenu.Instance;
         const [x, y] = this.Transform.transformPoint(this._downX, this._downY);
         if (e.key === '?') {
-            cm.setDefaultItem('?', (str: string) => this._props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', data_useCors: true }), OpenWhere.addRight));
+            cm.setDefaultItem('?', (str: string) =>
+                this._props.addDocTab(Docs.Create.WebDocument(`https://wikipedia.org/wiki/${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: `wiki:${str}`, data_useCors: true }), OpenWhere.addRight)
+            );
             cm.displayMenu(this._downX, this._downY, undefined, true);
             e.stopPropagation();
         } else if (e.key === 'u' && this._props.ungroup) {
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 67dc508d5..bda8afa41 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -213,7 +213,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
 
     get url() {
         const field = Cast(this._props.Document[this._props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
-        const alts = DocListCast(this._props.Document[this._props.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
+        const alts = DocListCast(this._props.Document[this._props.fieldKey + '_alternates']); // retrieve alternate documents that may be rendered as alternate images
         const altpaths = alts
             .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
             .filter(url => url)
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 3fdc9a488..d12639fc7 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -87,8 +87,8 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole
         });
     } else {
         Doc.SharingDoc().headingColor = undefined;
-        Doc.GetProto(Doc.SharingDoc()).headingColor = color;
-        Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'author_date');
+        Doc.GetProto(Doc.SharingDoc()).headingColor = color === 'transparent' ? undefined : color;
+        Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'title');
     }
 });
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 2800ea200..0d0a7c623 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { action, makeObservable, observable } from 'mobx';
+import { action, makeObservable, observable, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { OmitKeys, numberRange } from '../../../Utils';
@@ -17,6 +17,7 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
 import './CollectionFreeFormDocumentView.scss';
 import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
 import { FieldViewProps } from './FieldView';
+import { TransitionTimer } from '../../../fields/DocSymbols';
 
 /// Ugh, typescript has no run-time way of iterating through the keys of an interface.  so we need
 /// manaully keep this list of keys in synch wih the fields of the freeFormProps interface
@@ -91,6 +92,16 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @observable AutoDim = this.props.autoDim;
     @observable Transition = this.props.transition;
 
+    componentDidMount(): void {
+        if (this.props.transition && !this.Document[TransitionTimer]) {
+            const num = Number(this.props.transition.match(/([0-9.]+)s/)?.[1]) * 1000 || Number(this.props.transition.match(/([0-9.]+)ms/)?.[1]);
+            this.Document[TransitionTimer] = setTimeout(
+                action(() => (this.Document[TransitionTimer] = this.Transition = undefined)),
+                num
+            );
+        }
+    }
+
     componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<CollectionFreeFormDocumentViewProps & freeFormProps>>) {
         super.componentDidUpdate(prevProps);
         this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
@@ -98,14 +109,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
     // this way, downstream code only invalidates when it uses a specific prop, not when any prop changes
-    DataTransition = ()  => this._props.transition; // prettier-ignore
+    DataTransition = ()    => this.Transition || StrCast(this.Document.dataTransition); // prettier-ignore
     RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking
     PanelWidth = ()        => this._props.autoDim ? this._props.PanelWidth?.() : this.Width; // prettier-ignore
     PanelHeight = ()       => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
 
     styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
         if (doc === this.layoutDoc) {
-            switch (property) {
+            switch (property.split(':')[0]) {
                 case StyleProp.Opacity:         return this.Opacity; // only change the opacity for this specific document, not its children
                 case StyleProp.BackgroundColor: return this.BackgroundColor;
                 case StyleProp.Color:           return this.Color;
@@ -224,7 +235,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     //  undefined - this is not activated by a group
     isGroupActive = () => {
         if (this.CollectionFreeFormView.isAnyChildContentActive()) return undefined;
-        const isGroup = this.dataDoc.isGroup && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
+        const backColor = this.BackgroundColor;
+        const isGroup = this.dataDoc.isGroup && (!backColor || backColor === 'transparent');
         return isGroup ? (this._props.isDocumentActive?.() ? 'group' : this._props.isGroupActive?.() ? 'child' : 'inactive') : this._props.isGroupActive?.() ? 'child' : undefined;
     };
     render() {
@@ -237,7 +249,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     width: this.PanelWidth(),
                     height: this.PanelHeight(),
                     transform: `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Rotation)}deg)`,
-                    transition: this.Transition || StrCast(this.Document.dataTransition),
+                    transition: this.DataTransition(),
                     zIndex: this.ZIndex,
                     display: this.Width ? undefined : 'none',
                 }}>
@@ -251,6 +263,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                         styleProvider={this.styleProvider}
                         ScreenToLocalTransform={this.screenToLocalTransform}
                         isGroupActive={this.isGroupActive}
+                        PanelWidth={this.PanelWidth}
+                        PanelHeight={this.PanelHeight}
                     />
                 )}
             </div>
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 5a326ecb0..e729e2fa2 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -7,7 +7,7 @@ import { OmitKeys, Without, emptyPath } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { AclPrivate, DocData } from '../../../fields/DocSymbols';
 import { ScriptField } from '../../../fields/ScriptField';
-import { Cast, StrCast } from '../../../fields/Types';
+import { Cast, DocCast, StrCast } from '../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
 import { InkingStroke } from '../InkingStroke';
 import { ObservableReactComponent } from '../ObservableReactComponent';
@@ -128,7 +128,9 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
         if (this._props.LayoutTemplateString) return this._props.LayoutTemplateString;
         if (!this.layoutDoc) return '<p>awaiting layout</p>';
         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');
+        const tempLayout = DocCast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')]);
+        const layoutDoc = tempLayout ?? this.layoutDoc;
+        const layout = Cast(layoutDoc[layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(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>';
@@ -154,6 +156,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
             'LayoutTemplate',
             'layoutFieldKey',
             'dontCenter',
+            'DataTransition',
             'contextMenuItems',
             //'onClick', // don't need to omit this since it will be set
             'onDoubleClickScript',
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1044d6609..3154ee434 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { Howl } from 'howler';
-import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal';
@@ -104,12 +104,11 @@ export interface DocumentViewProps extends FieldViewSharedProps {
     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
     DataTransition?: () => string | undefined;
     NativeWidth?: () => number;
     NativeHeight?: () => number;
-    contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[];
+    contextMenuItems?: () => { script?: ScriptField; method?: () => void; filter?: ScriptField; label: string; icon: string }[];
     dragConfig?: (data: DragManager.DocumentDragData) => void;
     dragStarting?: () => void;
     dragEnding?: () => void;
@@ -560,7 +559,13 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         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 }));
+        this._props
+            .contextMenuItems?.()
+            .forEach(
+                item =>
+                    item.label &&
+                    cm.addItem({ description: item.label, event: () => (item.method ? item.method() : item.script?.script.run({ this: this.Document, documentView: this, scriptContext: this._props.scriptContext })), icon: item.icon as IconProp })
+            );
 
         if (!this.Document.isFolder) {
             const templateDoc = Cast(this.Document[StrCast(this.Document.layout_fieldKey)], Doc, null);
@@ -597,7 +602,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 if (!this.Document.annotationOn) {
                     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.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
-                    cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
+                    !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' });
                     onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' });
@@ -810,8 +815,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     };
     /**
      * 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]
+     * setting layout_showTitle using the format:   field1[:hover]
      **/
     @computed get titleView() {
         const showTitle = this.layout_showTitle?.split(':')[0];
@@ -851,21 +855,26 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                         contents={
                             showTitle
                                 .split(';')
-                                .map(field => Field.toString(targetDoc[field.trim()] as Field))
+                                .map(field => Field.toJavascriptString(this.Document[field] as Field))
                                 .join(' \\ ') || '-unset-'
                         }
                         display="block"
                         oneLine={true}
                         fontSize={(this.titleHeight / 15) * 10}
-                        GetValue={() => (showTitle.split(';').length !== 1 ? '#' + showTitle : Field.toKeyValueString(this.Document, showTitle.split(';')[0]))}
+                        GetValue={() =>
+                            showTitle
+                                .split(';')
+                                .map(field => Field.toKeyValueString(this.Document, field))
+                                .join('\\')
+                        }
                         SetValue={undoBatch((input: string) => {
-                            if (input?.startsWith('#')) {
+                            if (input?.startsWith('$')) {
                                 if (this.layoutDoc.layout_showTitle) {
-                                    this.layoutDoc._layout_showTitle = input?.substring(1);
+                                    this.layoutDoc._layout_showTitle = input?.substring(1) ? input.substring(1) : undefined;
                                 } else if (!this._props.layout_showTitle) {
-                                    Doc.UserDoc().layout_showTitle = input?.substring(1) ?? 'author_date';
+                                    Doc.UserDoc().layout_showTitle = input?.substring(1) ? input.substring(1) : 'title';
                                 }
-                            } else if (showTitle && !showTitle.includes('Date') && showTitle !== 'author') {
+                            } else if (showTitle && !showTitle.includes(';') && !showTitle.includes('Date') && showTitle !== 'author') {
                                 KeyValueBox.SetField(targetDoc, showTitle, input);
                             }
                             return true;
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 5b47dd91d..771856788 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -92,6 +92,7 @@ export interface FieldViewSharedProps {
     waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
     defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
     pointerEvents?: () => Opt<string>;
+    suppressSetHeight?: boolean;
 }
 
 /**
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 251235b93..86e8ed60a 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -9,7 +9,7 @@ import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { ObjectField } from '../../../fields/ObjectField';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
 import { DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
@@ -29,6 +29,12 @@ import { OpenWhere } from './DocumentView';
 import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
 import './ImageBox.scss';
 import { PinProps, PresBox } from './trails';
+import { Colors } from 'browndash-components';
+import { listSpec } from '../../../fields/Schema';
+import { List } from '../../../fields/List';
+import { url } from 'inspector';
+import { OverlayView } from '../OverlayView';
+import { Networking } from '../../Network';
 
 @observer
 export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -134,7 +140,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             if (de.metaKey || targetIsBullseye(e.target as HTMLElement)) {
                 added = de.complete.docDragData.droppedDocuments.reduce((last: boolean, drop: Doc) => {
                     this.layoutDoc[this.fieldKey + '_usePath'] = 'alternate:hover';
-                    return last && Doc.AddDocToList(this.dataDoc, this.fieldKey + '-alternates', drop);
+                    return last && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', drop);
                 }, true);
             } else if (de.altKey || !this.dataDoc[this.fieldKey]) {
                 const layoutDoc = de.complete.docDragData?.draggedDocuments[0];
@@ -259,7 +265,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         const lower = url.href.toLowerCase();
         if (url.protocol === 'data') return url.href;
         if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return Utils.CorsProxy(url.href);
-        if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return `/assets/unknown-file-icon-hi.png`;
+        if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower) || lower.endsWith('/assets/unknown-file-icon-hi.png')) return `/assets/unknown-file-icon-hi.png`;
 
         const ext = extname(url.href);
         return url.href.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
@@ -297,7 +303,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                     ref={this._overlayIconRef}
                     onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))}
                     style={{
-                        display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none',
+                        display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || this.dataDoc[this.fieldKey + '_alternates'] ? 'block' : 'none',
                         width: 'min(10%, 25px)',
                         height: 'min(10%, 25px)',
                         background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
@@ -311,13 +317,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
 
     @computed get paths() {
         const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
-        const alts = DocListCast(this.dataDoc[this.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
-        const altpaths = alts
-            .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
-            .filter(url => url)
-            .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
+        const alts = this.dataDoc[this.fieldKey + '_alternates'] as any as List<Doc>; // retrieve alternate documents that may be rendered as alternate images
+        const defaultUrl = new URL(Utils.prepend('/assets/unknown-file-icon-hi.png'));
+        const altpaths =
+            alts
+                ?.map(doc => (doc instanceof Doc ? ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url ?? defaultUrl : defaultUrl))
+                .filter(url => url)
+                .map(url => this.choosePath(url)) ?? []; // acc  ess the primary layout data of the alternate documents
         const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
-        return paths.length ? paths : [Utils.CorsProxy('https://cs.brown.edu/~bcz/noImage.png')];
+        return paths.length ? paths : [defaultUrl.href];
     }
 
     @observable _error = '';
@@ -326,7 +334,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     @computed get content() {
         TraceMobx();
 
-        const backColor = DashColor(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor));
+        const backColor = DashColor(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) ?? Colors.WHITE);
         const backAlpha = backColor.red() === 0 && backColor.green() === 0 && backColor.blue() === 0 ? backColor.alpha() : 1;
         const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0];
         const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement();
@@ -370,6 +378,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     }
     screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.ScreenToLocalBoxXf().Scale);
     marqueeDown = (e: React.PointerEvent) => {
+        if (!this.dataDoc[this.fieldKey]) return this.chooseImage();
         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,
@@ -468,4 +477,28 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             </div>
         );
     }
+
+    public chooseImage = () => {
+        const input = document.createElement('input');
+        input.type = 'file';
+        input.multiple = true;
+        input.accept = 'image/*';
+        input.onchange = async _e => {
+            const file = input.files?.[0];
+            if (file) {
+                const disposer = OverlayView.ShowSpinner();
+                const [{ result }] = await Networking.UploadFilesToServer({ file });
+                if (result instanceof Error) {
+                    alert('Error uploading files - possibly due to unsupported file types');
+                } else {
+                    this.dataDoc[this.fieldKey] = new ImageField(result.accessPaths.agnostic.client);
+                    !(result instanceof Error) && DocUtils.assignImageInfo(result, this.dataDoc);
+                }
+                disposer();
+            } else {
+                console.log('No file selected');
+            }
+        };
+        input.click();
+    };
 }
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 5394c7dbb..78e4435ce 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -113,7 +113,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
                     if (key) target[key] = script.originalScript;
                     return false;
                 }
-                field === undefined && (field = res.result);
+                field === undefined && (field = res.result instanceof Array ? new List<any>(res.result) : res.result);
             }
         }
         if (!key) return false;
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index be20b5934..74e78c671 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -41,7 +41,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @computed get Title() {
-        return Field.toString(this.dataDoc[this.fieldKey] as Field);
+        return Field.toString(this.dataDoc[this.fieldKey] as Field) || StrCast(this.Document.title);
     }
 
     protected createDropTarget = (ele: HTMLDivElement) => {
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 6e4d0e92a..0809e2ad6 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -2,12 +2,13 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import Xarrow from 'react-xarrows';
+import { FieldResult } from '../../../fields/Doc';
 import { DocCss, DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, NumCast, StrCast } from '../../../fields/Types';
+import { TraceMobx } from '../../../fields/util';
 import { DashColor, emptyFunction, lightOrDark, returnFalse } from '../../../Utils';
 import { DocumentManager } from '../../util/DocumentManager';
-import { LinkManager } from '../../util/LinkManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
 import { EditableView } from '../EditableView';
@@ -22,8 +23,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string = 'link') {
         return FieldView.LayoutString(LinkBox, fieldKey);
     }
-    disposer: IReactionDisposer | undefined;
-    @observable _forceAnimate = 0; // forces xArrow to animate when a transition animation is detected on something that affects an anchor
+    _disposer: IReactionDisposer | undefined;
+    @observable _forceAnimate: number = 0; // forces xArrow to animate when a transition animation is detected on something that affects an anchor
     @observable _hide = false; // don't render if anchor is not visible since that breaks xAnchor
 
     constructor(props: FieldViewProps) {
@@ -39,11 +40,11 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return DocumentManager.Instance.getDocumentView(anchor, this.DocumentView?.().containerViewPath?.().lastElement());
     };
     componentWillUnmount() {
-        this.disposer?.();
+        this._disposer?.();
     }
     componentDidMount() {
         this._props.setContentViewBox?.(this);
-        this.disposer = reaction(
+        this._disposer = reaction(
             () => ({ drag: SnappingManager.IsDragging }),
             ({ drag }) => {
                 !LightboxView.Contains(this.DocumentView?.()) &&
@@ -64,12 +65,13 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             }
                         })
                     );
-            },
-            { fireImmediately: true }
+            }
         );
     }
 
     render() {
+        TraceMobx();
+
         if (this._hide) return null;
         const a = this.anchor1;
         const b = this.anchor2;
@@ -92,18 +94,34 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const at = a.getBounds?.transition; // these force re-render when a or b change size and at the end of an animated transition
             const bt = b.getBounds?.transition; //    inquring getBounds() also causes text anchors to update whether or not they reflow (any size change triggers an invalidation)
 
+            var foundParent = false;
+            const getAnchor = (field: FieldResult): Element[] => {
+                const doc = DocCast(field);
+                const ele = document.getElementById(doc[Id]);
+                if (ele?.getBoundingClientRect().width) return [ele];
+                const eles = Array.from(document.getElementsByClassName(doc[Id])).filter(ele => ele?.getBoundingClientRect().width);
+                const annoOn = DocCast(doc.annotationOn);
+                if (eles.length || !annoOn) return eles;
+                const pareles = getAnchor(annoOn);
+                foundParent = pareles.length ? true : false;
+                return pareles;
+            };
             //   if there's an element in the DOM with a classname containing a link anchor's id (eg a hypertext <a>),
             //   then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
             //   otherwise, we just use the computed nearest point on the document boundary to the target Document
-            const targetAhyperlink = Array.from(document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_1)[Id])).lastElement();
-            const targetBhyperlink = Array.from(document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_2)[Id])).lastElement();
+            const targetAhyperlinks = getAnchor(this.dataDoc.link_anchor_1);
+            const targetBhyperlinks = getAnchor(this.dataDoc.link_anchor_2);
 
-            const aid = targetAhyperlink?.id || a.Document[Id];
-            const bid = targetBhyperlink?.id || b.Document[Id];
-            if (!document.getElementById(aid) || !document.getElementById(bid)) {
+            const container = this.DocumentView?.().containerViewPath?.().lastElement().ContentDiv;
+            const aid = targetAhyperlinks?.find(alink => container?.contains(alink))?.id ?? targetAhyperlinks?.lastElement()?.id;
+            const bid = targetBhyperlinks?.find(blink => container?.contains(blink))?.id ?? targetBhyperlinks?.lastElement()?.id;
+            if (!aid || !bid) {
                 setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01)));
                 return null;
             }
+            if (foundParent) {
+                setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 1)));
+            }
 
             if (at || bt) setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); // this forces an update during a transition animation
             const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting);
@@ -192,6 +210,11 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 </>
             );
         }
+
+        setTimeout(
+            action(() => (this._forceAnimate = this._forceAnimate + 1)),
+            2
+        );
         return (
             <div className={`linkBox-container${this._props.isContentActive() ? '-interactive' : ''}`} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }}>
                 <ComparisonBox
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index b2ae7201c..0af0751bf 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -335,7 +335,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         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 && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3);
+        // link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3);  // do we need to set an end time? should default to +0.1
         setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true));
     };
 
@@ -373,7 +373,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     getView = (doc: Doc, options: FocusViewOptions) => {
         if (this._stackedTimeline?.makeDocUnfiltered(doc)) {
             if (this.heightPercent === 100) {
-                this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
+                // do we want to always open up the timeline when followin a link? kind of clunky visually
+                //this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
                 options.didMove = true;
             }
             return this._stackedTimeline.getView(doc, options);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c9340edc0..f4d5eef05 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { htmlToText } from 'html-to-text';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as WebRequest from 'web-request';
@@ -340,8 +340,18 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     _textAnnotationCreator: (() => ObservableMap<number, HTMLDivElement[]>) | undefined;
     savedAnnotationsCreator: () => ObservableMap<number, HTMLDivElement[]> = () => this._textAnnotationCreator?.() || this._savedAnnotations;
 
+    @action
+    iframeMove = (e: PointerEvent) => {
+        const theclick = this.props
+            .ScreenToLocalTransform()
+            .inverse()
+            .transformPoint(e.clientX, e.clientY - NumCast(this.layoutDoc.layout_scrollTop));
+        this._marqueeref.current?.onMove(theclick);
+    };
     @action
     iframeUp = (e: PointerEvent) => {
+        this._iframe?.contentDocument?.removeEventListener('pointermove', this.iframeMove);
+        this.marqueeing = undefined;
         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.DocumentView?.()?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
@@ -358,6 +368,29 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 GPTPopup.Instance.setSidebarId(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
                 GPTPopup.Instance.addDoc = this.sidebarAddDocument;
             }
+        } else {
+            const theclick = this.props
+                .ScreenToLocalTransform()
+                .inverse()
+                .transformPoint(e.clientX, e.clientY - NumCast(this.layoutDoc.layout_scrollTop));
+            if (!this._marqueeref.current?.isEmpty) this._marqueeref.current?.onEnd(theclick[0], theclick[1]);
+            else {
+                if (!(e.target as any)?.tagName?.includes('INPUT')) this.finishMarquee(theclick[0], theclick[1]);
+                this._getAnchor = AnchorMenu.Instance?.GetAnchor;
+                this.marqueeing = undefined;
+            }
+
+            ContextMenu.Instance.closeMenu();
+            ContextMenu.Instance.setIgnoreEvents(false);
+            if (e?.button === 2 || e?.altKey) {
+                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.DocumentView?.().onContextMenu(undefined, theclick[0], theclick[1]);
+                });
+            }
         }
     };
     @action
@@ -400,6 +433,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     };
     @action
     iframeDown = (e: PointerEvent) => {
+        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._props.select(false);
         const theclick = this.props
             .ScreenToLocalTransform()
@@ -409,6 +448,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         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;
+            this._marqueeref.current?.onInitiateSelection(this.marqueeing);
+            this._iframe?.contentDocument?.addEventListener('pointermove', this.iframeMove);
             e.preventDefault();
         }
     };
@@ -739,28 +780,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             this.marqueeing = undefined;
         }
     };
-    @action finishMarquee = (x?: number, y?: number, e?: PointerEvent) => {
+    @action finishMarquee = (x?: number, y?: number) => {
         this._getAnchor = AnchorMenu.Instance?.GetAnchor;
         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.Document);
-        if (x !== undefined && y !== undefined) {
-            ContextMenu.Instance.closeMenu();
-            ContextMenu.Instance.setIgnoreEvents(false);
-            if (e?.button === 2 || e?.altKey) {
-                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.DocumentView?.().onContextMenu(undefined, x, y);
-                });
-            }
-        }
     };
 
     @observable lighttext = false;
@@ -992,6 +1015,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     }
     childPointerEvents = () => (this._props.isContentActive() ? 'all' : undefined);
     @computed get webpage() {
+        TraceMobx();
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
         const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as any);
         const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
@@ -1071,6 +1095,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             : 'none';
     annotationPointerEvents = () => (this._props.isContentActive() && (SnappingManager.IsDragging || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none');
     render() {
+        TraceMobx();
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
         const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as any);
         const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 793595694..eb7293054 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -450,16 +450,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     autoLink = () => {
         const newAutoLinks = new Set<Doc>();
-        const oldAutoLinks = LinkManager.Links(this.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords);
+        const oldAutoLinks = LinkManager.Links(this.Document).filter(
+            link => 
+                ((!Doc.isTemplateForField(this.Document) &&
+                    (!Doc.isTemplateForField(DocCast(link.link_anchor_1)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_1), this.Document)) &&
+                    (!Doc.isTemplateForField(DocCast(link.link_anchor_2)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_2), this.Document))) ||
+                 (Doc.isTemplateForField(this.Document) && (link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document))) &&
+                link.link_relationship === LinkManager.AutoKeywords
+        ); // prettier-ignore
         if (this._editorView?.state.doc.textContent) {
-            const isNodeSel = this._editorView.state.selection instanceof NodeSelection;
             const f = this._editorView.state.selection.from;
+
             const t = this._editorView.state.selection.to;
             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);
             Doc.MyPublishedDocs.filter(term => term.title).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)));
+            tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
             this._editorView?.dispatch(tr);
         }
         oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(LinkManager.Instance.deleteLink);
@@ -624,7 +631,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                         docId: draggedDoc[Id],
                         float: 'unset',
                     });
-                    if (![dropActionType.embed, dropActionType.copy].includes(dropAction ?? dropActionType.move)) {
+                    if (!de.embedKey && ![dropActionType.embed, dropActionType.copy].includes(dropAction ?? dropActionType.move)) {
                         added = dragData.removeDocument?.(draggedDoc) ? true : false;
                     } else {
                         added = true;
@@ -1814,7 +1821,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             };
             const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + toHgt(child), margins);
             const scrollHeight = this.ProseRef && proseHeight;
-            if (this._props.setHeight && scrollHeight && !this._props.dontRegisterView) {
+            if (this._props.setHeight && !this._props.suppressSetHeight && 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);
 
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index e34144fae..ae6da8fb0 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -703,7 +703,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             if (pinProps.pinData.temporal) {
                 pinDoc.config_clipStart = targetDoc._layout_currentTimecode;
                 const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], NumCast(targetDoc.config_clipStart) + 0.1);
-                pinDoc.config_clipEnd = NumCast(targetDoc.clipEnd, duration);
+                pinDoc.config_clipEnd = NumCast(pinDoc.config_clipStart) + NumCast(targetDoc.clipEnd, duration);
             }
         }
         if (pinProps?.pinViewport) {
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 246828709..1bd49cf3f 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -45,18 +45,23 @@ export namespace Field {
     export function toKeyValueString(doc: Doc, key: string, showComputedValue?: boolean): string {
         const onDelegate = !Doc.IsDataProto(doc) && Object.keys(doc).includes(key.replace(/^_/, ''));
         const field = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
-        return !Field.IsField(field)
-            ? key.startsWith('_')
-                ? '='
-                : ''
-            : (onDelegate ? '=' : '') +
-                  (field instanceof ComputedField && showComputedValue
-                      ? field._lastComputedResult
-                      : field instanceof ComputedField
-                        ? `:=${field.script.originalScript.replace(/dashCallChat\(_setCacheResult_, this, `(.*)`\)/, '(($1))')}`
-                        : field instanceof ScriptField
-                          ? `$=${field.script.originalScript}`
-                          : Field.toScriptString(field));
+        const valFunc = (field: Field): string => {
+            const res =
+                field instanceof ComputedField && showComputedValue
+                    ? field._lastComputedResult
+                    : field instanceof ComputedField
+                      ? `:=${field.script.originalScript.replace(/dashCallChat\(_setCacheResult_, this, `(.*)`\)/, '(($1))')}`
+                      : field instanceof ScriptField
+                        ? `$=${field.script.originalScript}`
+                        : Field.toScriptString(field);
+            const resStr = (res + '').replace(/^`(.*)`$/, '$1');
+            return typeof field === 'string' && (+resStr).toString() !== resStr && !Array.from('+-*/.').some(k => Array.from(resStr).includes(k))
+                ? resStr
+                : (res + '') // adjust the key value string to be easier to enter: represent any initial list as an array with []
+                      .trim()
+                      .replace(/^new List\((.*)\)$/, '$1');
+        };
+        return !Field.IsField(field) ? (key.startsWith('_') ? '=' : '') : (onDelegate ? '=' : '') + valFunc(field);
     }
     export function toScriptString(field: Field) {
         switch (typeof field) {
diff --git a/src/fields/PresField.ts b/src/fields/PresField.ts
deleted file mode 100644
index f236a04fd..000000000
--- a/src/fields/PresField.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-//insert code here
-import { ObjectField } from "./ObjectField";
-
-export abstract class PresField extends ObjectField {
-
-}
\ No newline at end of file
diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts
index f5801de73..d0a149506 100644
--- a/src/fields/RichTextField.ts
+++ b/src/fields/RichTextField.ts
@@ -36,11 +36,4 @@ export class RichTextField extends ObjectField {
     [ToString]() {
         return this.Text;
     }
-
-    public static DashField(fieldKey: string) {
-        return new RichTextField(
-            `{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"${fieldKey}","docId":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}`,
-            ''
-        );
-    }
 }
-- 
cgit v1.2.3-70-g09d2


From 82a74549a6b1c942fabaf8396c450b70d65e81dd Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 28 Mar 2024 23:28:11 -0400
Subject: fixed following link to pdf text selection with highlights. fixed
 hide key/value in dashfieldView. fixed selecting dashFieldView nodes.

---
 src/client/documents/Documents.ts                  |  25 +-
 src/client/util/CurrentUserUtils.ts                |   4 +-
 src/client/views/MarqueeAnnotator.tsx              |   1 +
 src/client/views/SidebarAnnos.tsx                  |   4 +-
 .../CollectionFreeFormLayoutEngines.tsx            |   2 +-
 src/client/views/nodes/MapBox/MapBox.tsx           | 791 +++++----------------
 src/client/views/nodes/MapBox/MapPushpinBox.tsx    |   4 +-
 .../views/nodes/RecordingBox/RecordingView.scss    | 322 ++++-----
 .../views/nodes/formattedText/DashFieldView.tsx    |  70 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  32 +-
 .../views/nodes/formattedText/RichTextRules.ts     |   3 +-
 src/client/views/nodes/formattedText/nodes_rts.ts  |   1 +
 12 files changed, 445 insertions(+), 814 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b63c5e429..b3d14bc22 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,6 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { action, reaction, runInAction } from 'mobx';
 import { basename } from 'path';
+import { OmitKeys, Utils } from '../../Utils';
 import { DateField } from '../../fields/DateField';
 import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc';
 import { DocData, Initializing } from '../../fields/DocSymbols';
@@ -13,30 +14,29 @@ import { SchemaHeaderField } from '../../fields/SchemaHeaderField';
 import { ComputedField, ScriptField } from '../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types';
 import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField, YoutubeField } from '../../fields/URLField';
-import { inheritParentAcls, SharingPermissions } from '../../fields/util';
+import { SharingPermissions, inheritParentAcls } from '../../fields/util';
 import { Upload } from '../../server/SharedMediaTypes';
-import { OmitKeys, Utils } from '../../Utils';
-import { YoutubeBox } from '../apis/youtube/YoutubeBox';
 import { DocServer } from '../DocServer';
 import { Networking } from '../Network';
+import { YoutubeBox } from '../apis/youtube/YoutubeBox';
 import { DragManager, dropActionType } from '../util/DragManager';
 import { FollowLinkScript } from '../util/LinkFollower';
 import { LinkManager } from '../util/LinkManager';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
-import { undoable, UndoManager } from '../util/UndoManager';
-import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView';
-import { CollectionView } from '../views/collections/CollectionView';
+import { UndoManager, undoable } from '../util/UndoManager';
 import { ContextMenu } from '../views/ContextMenu';
 import { ContextMenuProps } from '../views/ContextMenuItem';
 import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke';
+import { CollectionDockingView } from '../views/collections/CollectionDockingView';
+import { CollectionView } from '../views/collections/CollectionView';
+import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView';
 import { AudioBox, media_state } from '../views/nodes/AudioBox';
 import { ComparisonBox } from '../views/nodes/ComparisonBox';
 import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox';
+import { OpenWhere } from '../views/nodes/DocumentView';
 import { EquationBox } from '../views/nodes/EquationBox';
 import { FieldViewProps } from '../views/nodes/FieldView';
 import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox';
-import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
 import { FunctionPlotBox } from '../views/nodes/FunctionPlotBox';
 import { ImageBox } from '../views/nodes/ImageBox';
 import { KeyValueBox } from '../views/nodes/KeyValueBox';
@@ -52,14 +52,14 @@ import { RecordingBox } from '../views/nodes/RecordingBox/RecordingBox';
 import { ScreenshotBox } from '../views/nodes/ScreenshotBox';
 import { ScriptingBox } from '../views/nodes/ScriptingBox';
 import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
-import { PresBox } from '../views/nodes/trails/PresBox';
-import { PresElementBox } from '../views/nodes/trails/PresElementBox';
 import { VideoBox } from '../views/nodes/VideoBox';
 import { WebBox } from '../views/nodes/WebBox';
+import { CalendarBox } from '../views/nodes/calendarBox/CalendarBox';
+import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
+import { PresBox } from '../views/nodes/trails/PresBox';
+import { PresElementBox } from '../views/nodes/trails/PresElementBox';
 import { SearchBox } from '../views/search/SearchBox';
 import { CollectionViewType, DocumentType } from './DocumentTypes';
-import { CalendarBox } from '../views/nodes/calendarBox/CalendarBox';
-import { OpenWhere } from '../views/nodes/DocumentView';
 const { default: { DFLT_IMAGE_NATIVE_DIM } } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
 
 const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', ''));
@@ -204,6 +204,7 @@ export class DocumentOptions {
     overlayX?: NUMt = new NumInfo('horizontal coordinate in overlay view', false);
     overlayY?: NUMt = new NumInfo('vertical coordinate in overlay view', false);
     text?: RTFt = new RtfInfo('plain or rich text', true);
+    text_html?: STRt = new StrInfo('plain text or html', true);
     _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height", false);
     _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', false);
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 11f6c82ec..f4599f04a 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -321,9 +321,9 @@ export class CurrentUserUtils {
                         {type:"text",text:" "},
                         {type:"text",text:"Minerals in my tap water"},
                         {type:"text",text:"\n    \"Calcium\" : "},
-                        {type:"dashField",attrs:{fieldKey:"calcium",docId:"","hideKey":false,editable:true}},
+                        {type:"dashField",attrs:{fieldKey:"calcium",docId:"",hideKey:false,hideValue:false,editable:true}},
                         {type:"text",text:"\n    \"Potassium\" : "},
-                        {type:"dashField",attrs:{fieldKey:"pot",docId:"",hideKey:false,editable:true}},
+                        {type:"dashField",attrs:{fieldKey:"pot",docId:"",hideKey:false,hideValue:false,editable:true}},
                         {type:"text",text:"\n    \"Magnesium\" : 10.01"}
                     ]}
                 ]},
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index c29474fcd..0b68fd59e 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -89,6 +89,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
         const textRegionAnno = Docs.Create.HTMLMarkerDocument([], {
             annotationOn: this.props.Document,
             text: this.props.selectionText() as any, // text want an RTFfield, but strings are acceptable, too.
+            text_html: this.props.selectionText() as any, 
             backgroundColor: 'transparent',
             presentation_duration: 2100,
             presentation_transition: 500,
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 1e9272e93..3ad3c92da 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -94,7 +94,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
                 target[DocData][key] = val;
                 return {
                     type: 'dashField',
-                    attrs: { fieldKey: key, docId: '', hideKey: false, editable: true },
+                    attrs: { fieldKey: key, docId: '', hideKey: false, hideValue: false, editable: true },
                     marks: [{ type: 'pFontSize', attrs: { fontSize: '12px' } }, { type: 'strong' }, { type: 'user_mark', attrs: { userid: Doc.CurrentUserEmail, modified: 0 } }],
                 };
             });
@@ -121,7 +121,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
                             { type: 'pFontSize', attrs: { fontSize: '8px' } },
                             { type: 'em' },
                         ],
-                        attrs: { fieldKey: 'text', docId: anchor[Id], hideKey: true, editable: false },
+                        attrs: { fieldKey: 'text', docId: anchor[Id], hideKey: true, hideValue: false, editable: false },
                     },
                 ],
             },
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 6484658e0..c83c26509 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -5,7 +5,6 @@ import { RefField } from '../../../../fields/RefField';
 import { listSpec } from '../../../../fields/Schema';
 import { Cast, NumCast, StrCast } from '../../../../fields/Types';
 import { aggregateBounds } from '../../../../Utils';
-import * as React from 'react';
 
 export interface ViewDefBounds {
     type: string;
@@ -240,6 +239,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
                 y: -y + (pivotAxisWidth - hgt) / 2,
                 width: wid,
                 height: hgt,
+                backgroundColor: StrCast(layoutDoc.backgroundColor),
                 pair: { layout: doc },
                 replica: val.replicas[i],
             });
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 927e6fad4..36dad2747 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -6,7 +6,7 @@ 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 { IReactionDisposer, ObservableMap, action, autorun, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { CirclePicker, ColorResult } from 'react-color';
@@ -14,7 +14,6 @@ import { Layer, MapProvider, MapRef, Map as MapboxMap, Marker, Source, ViewState
 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';
 import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { DocUtils, Docs } from '../../../documents/Documents';
@@ -28,7 +27,7 @@ import { SidebarAnnos } from '../../SidebarAnnos';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
 import { Colors } from '../../global/globalEnums';
 import { DocumentView } from '../DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
+import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../trails';
 import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
@@ -53,7 +52,6 @@ import { MarkerIcons } from './MarkerIcons';
  * 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 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/';
 
@@ -66,72 +64,69 @@ type PopupInfo = {
     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
- */
-// 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 MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(MapBox, fieldKey);
     }
-    private _dragRef = React.createRef<HTMLDivElement>();
+    private _unmounting = false;
     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);
 
     constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
     }
 
-    @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
-    @computed get allSidebarDocs() {
-        return DocListCast(this.dataDoc[this.SidebarKey]);
-    }
+    @observable _featuresFromGeocodeResults: any[] = [];
+    @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
+    @observable _selectedPinOrRoute: Doc | undefined = undefined; // The pin that is selected
+    @observable _mapReady = false;
+    @observable _isAnimating: boolean = false;
+    @observable _routeToAnimate: Doc | undefined = undefined;
+    @observable _animationPhase: number = 0;
+    @observable _finishedFlyTo: boolean = false;
+    @observable _frameId: number | null = null;
+    @observable _animationUtility: AnimationUtility | null = null;
+    @observable _settingsOpen: boolean = false;
+    @observable _mapStyle: string = 'mapbox://styles/mapbox/standard';
+    @observable _showTerrain: boolean = true;
+    @observable _currentPopup: PopupInfo | undefined = undefined;
+    @observable _isStreetViewAnimation: boolean = false;
+    @observable _animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM;
+    @observable _animationLineColor: string = '#ffff00';
+    @observable _temporaryRouteSource: FeatureCollection = { type: 'FeatureCollection', features: [] };
+    @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: {},
+    };
+
     // 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 allRoutes() {
-        return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE);
+    @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } //prettier-ignore
+    @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } //prettier-ignore
+    @computed get allPushpins() { return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN); } //prettier-ignore
+    @computed get allRoutes() { return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE); } //prettier-ignore
+    @computed get SidebarShown() { return this.layoutDoc._layout_showSidebar ? true : false; } //prettier-ignore
+    @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } //prettier-ignore
+    @computed get SidebarKey() {  return this.fieldKey + '_sidebar'; } //prettier-ignore
+    @computed get sidebarColor() {
+        return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
     }
     @computed get updatedRouteCoordinates(): Feature<Geometry, GeoJsonProperties> {
-        if (this.routeToAnimate?.routeCoordinates) {
-            const originalCoordinates: Position[] = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates));
+        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
-            console.log('Animation phase', this.animationPhase);
+            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>;
@@ -147,7 +142,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 feature = {
                     type: 'Feature',
                     properties: {
-                        routeTitle: StrCast(this.routeToAnimate.title),
+                        routeTitle: StrCast(this._routeToAnimate.title),
                     },
                     geometry: geometry,
                 };
@@ -158,9 +153,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 const fraction = index - startIndex;
 
                 const interpolator = d3.interpolateArray(startCoord, endCoord);
-
                 const interpolatedCoord = interpolator(fraction);
-
                 const coordinates = originalCoordinates.slice(0, startIndex + 1).concat([interpolatedCoord]);
 
                 geometry = {
@@ -170,14 +163,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 feature = {
                     type: 'Feature',
                     properties: {
-                        routeTitle: StrCast(this.routeToAnimate.title),
+                        routeTitle: StrCast(this._routeToAnimate.title),
                     },
                     geometry: geometry,
                 };
             }
 
             autorun(() => {
-                const animationUtil = this.animationUtility;
+                const animationUtil = this._animationUtility;
                 const concattedCoordinates = geometry.coordinates.concat(originalCoordinates.slice(endIndex));
                 const newFeature: Feature<LineString, turf.Properties> = {
                     type: 'Feature',
@@ -204,11 +197,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         };
     }
     @computed get selectedRouteCoordinates(): Position[] {
-        let coordinates: Position[] = [];
-        if (this.routeToAnimate?.routeCoordinates) {
-            coordinates = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates));
-        }
-        return coordinates;
+        return !this._routeToAnimate?.routeCoordinates ? [] : JSON.parse(StrCast(this._routeToAnimate.routeCoordinates));
     }
 
     @computed get allRoutesGeoJson(): FeatureCollection {
@@ -233,29 +222,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         };
     }
 
-    @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.setContentViewBox?.(this);
     }
 
-    _unmounting = false;
-    componentWillUnmount(): void {
+    componentWillUnmount() {
         this._unmounting = true;
         this.deselectPinOrRoute();
-        this._rerenderTimeout && clearTimeout(this._rerenderTimeout);
         Object.keys(this._disposers).forEach(key => this._disposers[key]?.());
     }
 
@@ -269,7 +243,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         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.selectedPinOrRoute;
+            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));
             }
@@ -370,10 +344,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 
         const sourceAnchorCreator = action(() => {
             const note = this.getAnchor(true);
-            if (note && this.selectedPinOrRoute) {
-                note.latitude = this.selectedPinOrRoute.latitude;
-                note.longitude = this.selectedPinOrRoute.longitude;
-                note.map = this.selectedPinOrRoute.map;
+            if (note && this._selectedPinOrRoute) {
+                note.latitude = this._selectedPinOrRoute.latitude;
+                note.longitude = this._selectedPinOrRoute.longitude;
+                note.map = this._selectedPinOrRoute.map;
             }
             return note as Doc;
         });
@@ -399,10 +373,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         const createFunc = undoable(
             action(() => {
                 const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', LinkedTo]);
-                if (note && this.selectedPinOrRoute) {
-                    note.latitude = this.selectedPinOrRoute.latitude;
-                    note.longitude = this.selectedPinOrRoute.longitude;
-                    note.map = this.selectedPinOrRoute.map;
+                if (note && this._selectedPinOrRoute) {
+                    note.latitude = this._selectedPinOrRoute.latitude;
+                    note.longitude = this._selectedPinOrRoute.longitude;
+                    note.map = this._selectedPinOrRoute.map;
                 }
             }),
             'create note annotation'
@@ -423,12 +397,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         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.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
@@ -439,52 +408,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     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.Document.map; // For Bing Maps: The contents of the Bing search bar (string)
-
-    geoDataRequestOptions = {
-        entityType: 'PopulatedPlace',
-    };
-
-    // The pin that is selected
-    @observable selectedPinOrRoute: Doc | undefined = undefined;
-
     @action
     deselectPinOrRoute = () => {
-        if (this.selectedPinOrRoute) {
+        if (this._selectedPinOrRoute) {
             // // Removes filter
             // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'remove');
             // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'remove');
@@ -511,79 +437,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         }
         return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
     };
-    /*
-     * Pushpin onclick
-     */
-    @action
-    pushpinClicked = (pinDoc: Doc) => {
-        this.deselectPinOrRoute();
-        this.selectedPinOrRoute = pinDoc;
-        this.bingSearchBarContents = pinDoc.map;
-
-        // 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');
-
-        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.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.ScreenToLocalBoxXf().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.deselectPinOrRoute();
-    };
-    /*
-     * 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
@@ -592,14 +445,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         /// this should use SELECTED pushpin for lat/long  if there is a selection, otherwise CENTER
         const anchor = Docs.Create.ConfigDocument({
             title: 'MapAnchor:' + this.Document.title,
-            text: (StrCast(this.selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location') as any,
-            config_latitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.latitude ?? this.dataDoc.latitude),
-            config_longitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.longitude ?? this.dataDoc.longitude),
+            text: (StrCast(this._selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location') as any,
+            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.selectedPinOrRoute)?.map) || StrCast(this.dataDoc.map),
+            config_map: StrCast((existingPin ?? this._selectedPinOrRoute)?.map) || StrCast(this.dataDoc.map),
             layout_unrendered: true,
-            mapPin: existingPin ?? this.selectedPinOrRoute,
+            mapPin: existingPin ?? this._selectedPinOrRoute,
             annotationOn: this.Document,
         });
         if (anchor) {
@@ -613,25 +466,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 
     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
@@ -640,27 +474,16 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     @action
     removePushpinOrRoute = (pinOrRouteDoc: Doc) => this.removeMapDocument(pinOrRouteDoc, 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.selectedPinOrRoute = undefined;
-    };
-
     @action
     deleteSelectedPinOrRoute = undoable(() => {
         console.log('deleting');
-        if (this.selectedPinOrRoute) {
+        if (this._selectedPinOrRoute) {
             // Removes filter
-            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');
+            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);
+            this.removePushpinOrRoute(this._selectedPinOrRoute);
         }
         MapAnchorMenu.Instance.fadeOut(true);
         document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
@@ -677,7 +500,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         e.preventDefault();
         MapAnchorMenu.Instance.fadeOut(true);
         runInAction(() => {
-            this.temporaryRouteSource = {
+            this._temporaryRouteSource = {
                 type: 'FeatureCollection',
                 features: [],
             };
@@ -688,9 +511,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 
     @action
     centerOnSelectedPin = () => {
-        if (this.selectedPinOrRoute) {
+        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) {
@@ -703,33 +526,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         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,
-        },
-    };
-
     recolorPin = (pin: Doc, color?: string) => {
         // this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin));
         // this.map_docToPinMap.delete(pin);
@@ -739,109 +535,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         // 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.Document.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.Document.latitude, lng: this.Document.longitude, zoom: this.Document.map_zoom, mapType: this.Document.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 => {
-                // move event
-                if (!dragClone) {
-                    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
-                }
-                dragClone.style.transform = `translate(${e.clientX - 15}px, ${e.clientY - 15}px)`;
-                return false;
-            },
-            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.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));
-                        this.createPushpin(location.latitude, location.longitude);
-                        break;
-                    }
-                    target = target.parentElement;
-                }
-            },
-            e => {
-                const createPin = () => this.createPushpin(this.Document.latitude, this.Document.longitude, this.Document.map);
-                if (this.bingSearchBarContents) {
-                    this.bingSearch().then(createPin);
-                } else createPin();
-            }
-        );
-    };
-
     // incrementer: number = 0;
     /*
      * Creates Pushpin doc and adds it to the list of annotations
@@ -880,7 +573,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             if (createPinForDestination) {
                 this.createPushpin(destination.center[1], destination.center[0], destination.place_name);
             }
-            this.temporaryRouteSource = {
+            this._temporaryRouteSource = {
                 type: 'FeatureCollection',
                 features: [],
             };
@@ -890,10 +583,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         // TODO: Display error that can't create route to same location
     }, 'createmaproute');
 
-    searchbarKeyDown = (e: any) => e.key === 'Enter' && this.bingSearch();
-
-    @observable
-    featuresFromGeocodeResults: any[] = [];
+    @action
+    searchbarKeyDown = (e: any) => {
+        if (e.key === 'Enter' && this._featuresFromGeocodeResults) {
+            const center = this._featuresFromGeocodeResults[0]?.center;
+            this._featuresFromGeocodeResults = [];
+            setTimeout(() => center && this._mapRef.current?.flyTo({ center }));
+        }
+    };
 
     @action
     addMarkerForFeature = (feature: any) => {
@@ -910,7 +607,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                     center: feature.center,
                 });
             }
-            this.featuresFromGeocodeResults = [];
+            this._featuresFromGeocodeResults = [];
         } else {
             // TODO: handle error
         }
@@ -922,11 +619,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
      */
     handleSearchChange = async (searchText: string) => {
         const features = await MapboxApiUtility.forwardGeocodeForFeatures(searchText);
-        if (features && !this.isAnimating) {
+        if (features && !this._isAnimating) {
             runInAction(() => {
-                this.settingsOpen = false;
-                this.featuresFromGeocodeResults = features;
-                this.routeToAnimate = undefined;
+                this._settingsOpen = false;
+                this._featuresFromGeocodeResults = features;
+                this._routeToAnimate = undefined;
             });
         }
         // try {
@@ -946,8 +643,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 
     @action
     handleMapClick = (e: MapLayerMouseEvent) => {
-        this.featuresFromGeocodeResults = [];
-        this.settingsOpen = false;
+        this._featuresFromGeocodeResults = [];
+        this._settingsOpen = false;
         if (this._mapRef.current) {
             const features = this._mapRef.current.queryRenderedFeatures(e.point, {
                 layers: ['map-routes-layer'],
@@ -960,8 +657,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 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.Document, LinkedTo, `mapRoute=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
+                    this._selectedPinOrRoute = routeDoc;
+                    Doc.setDocFilter(this.Document, LinkedTo, `mapRoute=${Field.toScriptString(this._selectedPinOrRoute)}`, 'check');
 
                     // TODO: Recolor route
 
@@ -1008,7 +705,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         const features = await MapboxApiUtility.reverseGeocodeForFeatures(longitude, latitude);
         if (features) {
             runInAction(() => {
-                this.featuresFromGeocodeResults = features;
+                this._featuresFromGeocodeResults = features;
             });
         }
 
@@ -1028,21 +725,18 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         // }
     };
 
-    @observable
-    currentPopup: PopupInfo | undefined = undefined;
-
     @action
     handleMarkerClick = (e: MarkerEvent<mapboxgl.Marker, MouseEvent>, pinDoc: Doc) => {
-        this.featuresFromGeocodeResults = [];
+        this._featuresFromGeocodeResults = [];
         this.deselectPinOrRoute(); // TODO: check this method
-        this.selectedPinOrRoute = pinDoc;
+        this._selectedPinOrRoute = pinDoc;
         // this.bingSearchBarContents = pinDoc.map;
 
         // 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');
+        Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this._selectedPinOrRoute)}`, 'check');
 
-        this.recolorPin(this.selectedPinOrRoute, 'green'); // TODO: check this method
+        this.recolorPin(this._selectedPinOrRoute, 'green'); // TODO: check this method
 
         MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute;
         MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
@@ -1072,12 +766,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         // })
     };
 
-    @observable
-    temporaryRouteSource: FeatureCollection = {
-        type: 'FeatureCollection',
-        features: [],
-    };
-
     @action
     displayRoute = (routeInfoMap: Record<TransportationType, any> | undefined, type: TransportationType) => {
         if (routeInfoMap) {
@@ -1096,41 +784,23 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             };
             // TODO: Create pin for destination
             // TODO: Fly to point where full route will be shown
-            this.temporaryRouteSource = newTempRouteSource;
+            this._temporaryRouteSource = newTempRouteSource;
         }
     };
 
-    @observable
-    isAnimating: boolean = false;
-
-    @observable
-    routeToAnimate: Doc | undefined = undefined;
-
-    @observable
-    animationPhase: number = 0;
-
-    @observable
-    finishedFlyTo: boolean = false;
-
     @action
     setAnimationPhase = (newValue: number) => {
-        this.animationPhase = newValue;
+        this._animationPhase = newValue;
     };
 
-    @observable
-    frameId: number | null = null;
-
     @action
     setFrameId = (frameId: number) => {
-        this.frameId = frameId;
+        this._frameId = frameId;
     };
 
-    @observable
-    animationUtility: AnimationUtility | null = null;
-
     @action
     setAnimationUtility = (util: AnimationUtility) => {
-        this.animationUtility = util;
+        this._animationUtility = util;
     };
 
     @action
@@ -1138,8 +808,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         if (routeDoc) {
             MapAnchorMenu.Instance.fadeOut(true);
             document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
-            this.featuresFromGeocodeResults = [];
-            this.routeToAnimate = routeDoc;
+            this._featuresFromGeocodeResults = [];
+            this._routeToAnimate = routeDoc;
         }
     };
 
@@ -1161,29 +831,20 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 
     @computed
     get preAnimationViewState() {
-        if (!this.isAnimating) {
+        if (!this._isAnimating) {
             return this.mapboxMapViewState;
         }
     }
 
-    @observable
-    isStreetViewAnimation: boolean = false;
-
-    @observable
-    animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM;
-
-    @observable
-    animationLineColor: string = '#ffff00';
-
     @action
     setAnimationLineColor = (color: ColorResult) => {
-        this.animationLineColor = color.hex;
+        this._animationLineColor = color.hex;
     };
 
     @action
     updateAnimationSpeed = () => {
         let newAnimationSpeed: AnimationSpeed;
-        switch (this.animationSpeed) {
+        switch (this._animationSpeed) {
             case AnimationSpeed.SLOW:
                 newAnimationSpeed = AnimationSpeed.MEDIUM;
                 break;
@@ -1197,63 +858,33 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 newAnimationSpeed = AnimationSpeed.MEDIUM;
                 break;
         }
-        this.animationSpeed = newAnimationSpeed;
-        if (this.animationUtility) {
-            this.animationUtility.updateAnimationSpeed(newAnimationSpeed);
+        this._animationSpeed = newAnimationSpeed;
+        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';
-        }
+        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';
+        } // prettier-ignore
     }
     @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;
-        }
+        switch (this._animationSpeed) {
+            case AnimationSpeed.SLOW:   return slowSpeedIcon;
+            case AnimationSpeed.MEDIUM: return mediumSpeedIcon;
+            case AnimationSpeed.FAST:   return fastSpeedIcon;
+            default:                    return mediumSpeedIcon;
+        } // prettier-ignore
     }
 
     @action
     toggleIsStreetViewAnimation = () => {
-        const newVal = !this.isStreetViewAnimation;
-        this.isStreetViewAnimation = newVal;
-        if (this.animationUtility) {
-            this.animationUtility.updateIsStreetViewAnimation(newVal);
-        }
-    };
-
-    @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: {},
+        const newVal = !this._isStreetViewAnimation;
+        this._isStreetViewAnimation = newVal;
+        this._animationUtility?.updateIsStreetViewAnimation(newVal);
     };
 
     getFeatureFromRouteDoc = (routeDoc: Doc): Feature<Geometry, GeoJsonProperties> => {
@@ -1272,19 +903,19 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 
     @action
     playAnimation = (status: AnimationStatus) => {
-        if (!this._mapRef.current || !this.routeToAnimate) {
+        if (!this._mapRef.current || !this._routeToAnimate) {
             return;
         }
 
-        this.animationPhase = status === AnimationStatus.RESUME ? this.animationPhase : 0;
-        this.frameId = AnimationStatus.RESUME ? this.frameId : null;
-        this.finishedFlyTo = AnimationStatus.RESUME ? this.finishedFlyTo : false;
+        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._settingsOpen = false;
         this.path = path;
-        this.isAnimating = true;
+        this._isAnimating = true;
 
         runInAction(() => {
             return new Promise<void>(async resolve => {
@@ -1293,18 +924,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                     lat: this.selectedRouteCoordinates[0][1],
                 };
 
-                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 animationUtil = new AnimationUtility(targetLngLat, this.selectedRouteCoordinates, this._isStreetViewAnimation, this._animationSpeed, this._showTerrain, this._mapRef.current);
+                runInAction(() => this.setAnimationUtility(animationUtil));
 
-                const updateAnimationPhase = (newAnimationPhase: number) => {
-                    this.setAnimationPhase(newAnimationPhase);
-                };
+                const updateFrameId = (newFrameId: number) => this.setFrameId(newFrameId);
+                const updateAnimationPhase = (newAnimationPhase: number) => this.setAnimationPhase(newAnimationPhase);
 
                 if (status !== AnimationStatus.RESUME) {
                     const result = await animationUtil.flyInAndRotate({
@@ -1324,9 +948,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                     console.log('Altitude: ', result.altitude);
                 }
 
-                runInAction(() => {
-                    this.finishedFlyTo = true;
-                });
+                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({
@@ -1335,7 +957,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                     // startBearing: -20,
                     // startAltitude: this.isStreetViewAnimation ? 80 : 12000,
                     // pitch: this.isStreetViewAnimation ? 80: 50,
-                    currentAnimationPhase: this.animationPhase,
+                    currentAnimationPhase: this._animationPhase,
                     updateAnimationPhase,
                     updateFrameId,
                 });
@@ -1353,7 +975,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 });
 
                 setTimeout(() => {
-                    this.isStreetViewAnimation = false;
+                    this._isStreetViewAnimation = false;
                     resolve();
                 }, 10000);
             });
@@ -1362,27 +984,27 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 
     @action
     pauseAnimation = () => {
-        if (this.frameId && this.animationPhase > 0) {
-            window.cancelAnimationFrame(this.frameId);
-            this.frameId = null;
-            this.isAnimating = false;
+        if (this._frameId && this._animationPhase > 0) {
+            window.cancelAnimationFrame(this._frameId);
+            this._frameId = null;
+            this._isAnimating = false;
         }
     };
 
     @action
     stopAnimation = (close: boolean) => {
-        if (this.frameId) {
-            window.cancelAnimationFrame(this.frameId);
+        if (this._frameId) {
+            window.cancelAnimationFrame(this._frameId);
         }
-        this.animationPhase = 0;
-        this.frameId = null;
-        this.finishedFlyTo = false;
-        this.isAnimating = false;
+        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.animationUtility = null;
+            this._animationSpeed = AnimationSpeed.MEDIUM;
+            this._isStreetViewAnimation = false;
+            this._routeToAnimate = undefined;
+            this._animationUtility = null;
         }
     };
 
@@ -1390,21 +1012,21 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         return (
             <>
                 <IconButton
-                    tooltip={this.isAnimating && this.finishedFlyTo ? 'Pause Animation' : 'Play Animation'}
+                    tooltip={this._isAnimating && this._finishedFlyTo ? 'Pause Animation' : 'Play Animation'}
                     onPointerDown={() => {
-                        if (this.isAnimating && this.finishedFlyTo) {
+                        if (this._isAnimating && this._finishedFlyTo) {
                             this.pauseAnimation();
-                        } else if (this.animationPhase > 0) {
+                        } 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} />}
+                    icon={this._isAnimating && this._finishedFlyTo ? <FontAwesomeIcon icon={faPause as IconLookup} /> : <FontAwesomeIcon icon={faPlay as IconLookup} />}
                     color="black"
                     size={Size.MEDIUM}
                 />
-                {this.isAnimating && this.finishedFlyTo && (
+                {this._isAnimating && this._finishedFlyTo && (
                     <IconButton
                         tooltip="Restart animation"
                         onPointerDown={() => {
@@ -1420,7 +1042,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 <>
                     <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>
@@ -1436,26 +1058,17 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 
     @action
     hideRoute = () => {
-        this.temporaryRouteSource = {
+        this._temporaryRouteSource = {
             type: 'FeatureCollection',
             features: [],
         };
     };
 
-    @observable
-    settingsOpen: boolean = false;
-
-    @observable
-    mapStyle: string = 'mapbox://styles/mapbox/standard';
-
-    @observable
-    showTerrain: boolean = true;
-
     @action
     toggleSettings = () => {
-        if (!this.isAnimating && this.animationPhase == 0) {
-            this.featuresFromGeocodeResults = [];
-            this.settingsOpen = !this.settingsOpen;
+        if (!this._isAnimating && this._animationPhase == 0) {
+            this._featuresFromGeocodeResults = [];
+            this._settingsOpen = !this._settingsOpen;
         }
     };
 
@@ -1500,14 +1113,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     @action
     onStepZoomChange = (increment: boolean) => {
         if (this._mapRef.current) {
-            let newZoom: number;
-            if (increment) {
-                console.log('inc');
-                newZoom = Math.min(16, this.mapboxMapViewState.zoom + 1);
-            } else {
-                console.log('dec');
-                newZoom = Math.max(0, this.mapboxMapViewState.zoom - 1);
-            }
+            const newZoom = increment //
+                ? Math.min(16, this.mapboxMapViewState.zoom + 1)
+                : Math.max(0, this.mapboxMapViewState.zoom - 1);
+
             this._mapRef.current.setZoom(newZoom);
             this.dataDoc.map_zoom = newZoom;
         }
@@ -1523,7 +1132,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     };
 
     @action
-    toggleShowTerrain = () => (this.showTerrain = !this.showTerrain);
+    toggleShowTerrain = () => (this._showTerrain = !this._showTerrain);
 
     getMarkerIcon = (pinDoc: Doc): JSX.Element | null => {
         const markerType = StrCast(pinDoc.markerType);
@@ -1532,25 +1141,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         return MarkerIcons.getFontAwesomeIcon(markerType, '2x', markerColor) ?? null;
     };
 
-    static _firstRender = true;
-    static _rerenderDelay = 500;
-    _rerenderTimeout: any;
+    _textRef = React.createRef<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.Document[DocCss];
-        if (MapBox._rerenderDelay) {
-            // prettier-ignore
-            this._rerenderTimeout = this._rerenderTimeout ??
-                setTimeout(action(() => {
-                    if ((window as any).Microsoft?.Maps?.Internal._WorkDispatcher) {
-                        MapBox._rerenderDelay = 0;
-                    }
-                    this._rerenderTimeout = undefined;
-                    this.Document[DocCss] = this.Document[DocCss] + 1;
-                }), MapBox._rerenderDelay);
-            return null;
-        }
         const scale = this._props.NativeDimScaling?.() || 1;
         const parscale = scale === 1 ? 1 : this.ScreenToLocalBoxXf().Scale ?? 1;
 
@@ -1560,20 +1152,18 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 <div
                     className="mapBox-wrapper"
                     onWheel={e => e.stopPropagation()}
-                    onPointerDown={async e => {
-                        e.button === 0 && !e.ctrlKey && e.stopPropagation();
-                    }}
+                    onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
                     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.IsDragging ? null : renderAnnotations()}
-                    {!this.routeToAnimate && (
+                    {!this._routeToAnimate && (
                         <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)} />
+                            <TextField ref={this._textRef} fullWidth placeholder="Enter a location" onKeyDown={this.searchbarKeyDown} 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 && (
+                    {this._settingsOpen && !this._routeToAnimate && (
                         <div className="mapbox-settings-panel" style={{ right: `${0 + this.sidebarWidth()}px` }}>
                             <div className="mapbox-style-select">
                                 <div>Map Style:</div>
@@ -1605,21 +1195,21 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                             </div>
                             <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 && (
+                    {this._routeToAnimate && (
                         <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>
+                            <div id="route-to-animate-title">{StrCast(this._routeToAnimate.title)}</div>
                             <div className="route-animation-options">{this.getRouteAnimationOptions()}</div>
                         </div>
                     )}
-                    {this.featuresFromGeocodeResults.length > 0 && (
+                    {this._featuresFromGeocodeResults.length > 0 && (
                         <div className="mapbox-geocoding-search-results">
-                            <React.Fragment>
+                            <>
                                 <h4>Choose a location for your pin: </h4>
-                                {this.featuresFromGeocodeResults
+                                {this._featuresFromGeocodeResults
                                     .filter(feature => feature.place_name)
                                     .map((feature, idx) => (
                                         <div
@@ -1632,14 +1222,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                                             <div className="search-result-place-name">{feature.place_name}</div>
                                         </div>
                                     ))}
-                            </React.Fragment>
+                            </>
                         </div>
                     )}
                     <MapProvider>
                         <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',
@@ -1649,20 +1239,22 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                                 width: NumCast(this.layoutDoc._width) * parscale,
                                 height: NumCast(this.layoutDoc._height) * parscale,
                             }}
-                            initialViewState={this.isAnimating ? undefined : this.mapboxMapViewState}
+                            initialViewState={this._isAnimating ? undefined : this.mapboxMapViewState}
                             onZoom={this.onMapZoom}
                             onMove={this.onMapMove}
                             onClick={this.handleMapClick}
                             onDblClick={this.handleMapDblClick}
-                            terrain={this.showTerrain ? { source: 'mapbox-dem', exaggeration: 2.0 } : undefined}>
+                            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="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._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
@@ -1670,7 +1262,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                                                 type="line"
                                                 source="animated-route"
                                                 paint={{
-                                                    'line-color': this.animationLineColor,
+                                                    'line-color': this._animationLineColor,
                                                     'line-width': 5,
                                                 }}
                                             />
@@ -1722,10 +1314,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                             )}
 
                             <>
-                                {!this.isAnimating &&
-                                    this.animationPhase == 0 &&
-                                    this.allPushpins
-                                        // .filter(anno => !anno.layout_unrendered)
+                                {!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)}
diff --git a/src/client/views/nodes/MapBox/MapPushpinBox.tsx b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
index fc5b4dd18..8ebc90157 100644
--- a/src/client/views/nodes/MapBox/MapPushpinBox.tsx
+++ b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
@@ -1,7 +1,7 @@
 import * as React from 'react';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { FieldView, FieldViewProps } from '../FieldView';
-import { MapBox } from './MapBox';
+import { MapBoxContainer } from '../MapboxMapBox/MapboxContainer';
 
 /**
  * Map Pushpin doc class
@@ -18,7 +18,7 @@ export class MapPushpinBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     get mapBoxView() {
-        return this.DocumentView?.()?.containerViewPath?.().lastElement()?.ComponentView as MapBox;
+        return this.DocumentView?.()?.containerViewPath?.().lastElement()?.ComponentView as MapBoxContainer;
     }
     get mapBox() {
         return this.DocumentView?.().containerViewPath?.().lastElement()?.Document;
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.scss b/src/client/views/nodes/RecordingBox/RecordingView.scss
index 287cccd8f..f2d5a980d 100644
--- a/src/client/views/nodes/RecordingBox/RecordingView.scss
+++ b/src/client/views/nodes/RecordingBox/RecordingView.scss
@@ -1,208 +1,200 @@
 video {
-      // flex: 100%;
-      width: 100%;
-      // min-height: 400px;
-      //height: auto;
-      height: 100%;
-      //display: block;
-      object-fit: cover;
-      background-color: black;
-}
-
-button {
-      margin: 0 .5rem
+    // flex: 100%;
+    width: 100%;
+    // min-height: 400px;
+    //height: auto;
+    height: 100%;
+    //display: block;
+    object-fit: cover;
+    background-color: black;
 }
 
 .recording-container {
-      height: 100%;
-      width: 100%;
-      // display: flex;
-      pointer-events: all;
-      background-color: black;
+    height: 100%;
+    width: 100%;
+    // display: flex;
+    pointer-events: all;
+    background-color: black;
+    button {
+        margin: 0 0.5rem;
+    }
 }
 
 .video-wrapper {
-      //   max-width: 600px;
-      //   max-width: 700px;
-      // position: relative;
-      display: flex;
-      justify-content: center;
-      //   overflow: hidden;
-      border-radius: 10px;
-      margin: 0;
+    //   max-width: 600px;
+    //   max-width: 700px;
+    // position: relative;
+    display: flex;
+    justify-content: center;
+    //   overflow: hidden;
+    border-radius: 10px;
+    margin: 0;
 }
 
 .video-wrapper:hover .controls {
-      bottom: 34.5px;
-      transform: translateY(0%);
-      opacity: 100%;
+    bottom: 34.5px;
+    transform: translateY(0%);
+    opacity: 100%;
 }
 
 .controls {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      position: absolute;
-      width: 100%;
-      flex-wrap: wrap;
-      background: rgba(255, 255, 255, 0.25);
-      box-shadow: 0 8px 32px 0 rgba(255, 255, 255, 0.1);
-      // backdrop-filter: blur(4px);
-      border-radius: 10px;
-      border: 1px solid rgba(255, 255, 255, 0.18);
-      transition: all 0.3s ease-in-out;
-      bottom: 34.5px;
-      height: 60px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    position: absolute;
+    width: 100%;
+    flex-wrap: wrap;
+    background: rgba(255, 255, 255, 0.25);
+    box-shadow: 0 8px 32px 0 rgba(255, 255, 255, 0.1);
+    // backdrop-filter: blur(4px);
+    border-radius: 10px;
+    border: 1px solid rgba(255, 255, 255, 0.18);
+    transition: all 0.3s ease-in-out;
+    bottom: 34.5px;
+    height: 60px;
 }
 
 .controls:active {
-      bottom: 40px;
+    bottom: 40px;
 }
 
 .actions button {
-      background: none;
-      border: none;
-      outline: none;
-      cursor: pointer;
+    background: none;
+    border: none;
+    outline: none;
+    cursor: pointer;
 }
 
 .actions button i {
-      background-color: none;
-      color: white;
-      font-size: 30px;
+    background-color: none;
+    color: white;
+    font-size: 30px;
 }
 
-
 .velocity {
-      appearance: none;
-      background: none;
-      color: white;
-      outline: none;
-      border: none;
-      text-align: center;
-      font-size: 16px;
+    appearance: none;
+    background: none;
+    color: white;
+    outline: none;
+    border: none;
+    text-align: center;
+    font-size: 16px;
 }
 
 .mute-btn {
-      background: none;
-      border: none;
-      outline: none;
-      cursor: pointer;
+    background: none;
+    border: none;
+    outline: none;
+    cursor: pointer;
 }
 
 .mute-btn i {
-      background-color: none;
-      color: white;
-      font-size: 20px;
+    background-color: none;
+    color: white;
+    font-size: 20px;
 }
 
 .recording-sign {
-      height: 20px;
-      width: auto;
-      display: flex;
-      flex-direction: row;
-      position: absolute;
-      top: 10px;
-      right: 15px;
-      align-items: center;
-      justify-content: center;
-
-      .timer {
-            font-size: 15px;
-            color: white;
-            margin: 0;
-      }
-
-      .dot {
-            height: 15px;
-            width: 15px;
-            margin: 5px;
-            background-color: red;
-            border-radius: 50%;
-            display: inline-block;
-      }
+    height: 20px;
+    width: auto;
+    display: flex;
+    flex-direction: row;
+    position: absolute;
+    top: 10px;
+    right: 15px;
+    align-items: center;
+    justify-content: center;
+
+    .timer {
+        font-size: 15px;
+        color: white;
+        margin: 0;
+    }
+
+    .dot {
+        height: 15px;
+        width: 15px;
+        margin: 5px;
+        background-color: red;
+        border-radius: 50%;
+        display: inline-block;
+    }
 }
 
 .controls-inner-container {
-      display: flex;
-      flex-direction: row;
-      position: relative;
-      width: 100%;
-      align-items: center;
-      justify-content: center;
+    display: flex;
+    flex-direction: row;
+    position: relative;
+    width: 100%;
+    align-items: center;
+    justify-content: center;
 }
 
 .record-button-wrapper {
-      width: 35px;
-      height: 35px;
-      font-size: 0;
-      background-color: grey;
-      border: 0px;
-      border-radius: 35px;
-      margin: 10px;
-      display: flex;
-      justify-content: center;
-
-      .record-button {
-            background-color: red;
-            border: 0px;
-            border-radius: 50%;
-            height: 80%;
-            width: 80%;
-            align-self: center;
-            margin: 0;
-
-            &:hover {
-                  height: 85%;
-                  width: 85%;
-            }
-      }
-
-      .stop-button {
-            background-color: red;
-            border: 0px;
-            border-radius: 10%;
-            height: 70%;
-            width: 70%;
-            align-self: center;
-            margin: 0;
-
-
-            // &:hover {
-            //   width: 40px;
-            //   height: 40px
-            // }
-      }
-
+    width: 35px;
+    height: 35px;
+    font-size: 0;
+    background-color: grey;
+    border: 0px;
+    border-radius: 35px;
+    margin: 10px;
+    display: flex;
+    justify-content: center;
+
+    .record-button {
+        background-color: red;
+        border: 0px;
+        border-radius: 50%;
+        height: 80%;
+        width: 80%;
+        align-self: center;
+        margin: 0;
+
+        &:hover {
+            height: 85%;
+            width: 85%;
+        }
+    }
+
+    .stop-button {
+        background-color: red;
+        border: 0px;
+        border-radius: 10%;
+        height: 70%;
+        width: 70%;
+        align-self: center;
+        margin: 0;
+
+        // &:hover {
+        //   width: 40px;
+        //   height: 40px
+        // }
+    }
 }
 
 .options-wrapper {
-      height: 100%;
-      display: flex;
-      flex-direction: row;
-      align-content: center;
-      position: relative;
-      top: 0;
-      bottom: 0;
-
-      &.video-edit-wrapper {
-
-            // right: 50% - 15;
-
-            .track-screen {
-                  font-weight: 200;
-            }
-
-      }
-
-      &.track-screen-wrapper {
-
-            // right: 50% - 30;
-
-            .track-screen {
-                  font-weight: 200;
-                  color: aqua;
-            }
-
-      }
-}
\ No newline at end of file
+    height: 100%;
+    display: flex;
+    flex-direction: row;
+    align-content: center;
+    position: relative;
+    top: 0;
+    bottom: 0;
+
+    &.video-edit-wrapper {
+        // right: 50% - 15;
+
+        .track-screen {
+            font-weight: 200;
+        }
+    }
+
+    &.track-screen-wrapper {
+        // right: 50% - 30;
+
+        .track-screen {
+            font-weight: 200;
+            color: aqua;
+        }
+    }
+}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 8802a032f..cdfeebe66 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -22,7 +22,7 @@ import { OpenWhere } from '../DocumentView';
 import './DashFieldView.scss';
 import { FormattedTextBox } from './FormattedTextBox';
 import { DocData } from '../../../../fields/DocSymbols';
-import { NodeSelection, TextSelection } from 'prosemirror-state';
+import { NodeSelection } from 'prosemirror-state';
 
 export class DashFieldView {
     dom: HTMLDivElement; // container for label and value
@@ -60,7 +60,7 @@ export class DashFieldView {
                             return;
                         }
                     }
-                    tBox.setFocus(state.selection.to + 1);
+                    tBox.setFocus(state.selection.to);
                 }
             }
         };
@@ -84,6 +84,7 @@ export class DashFieldView {
                 width={node.attrs.width}
                 height={node.attrs.height}
                 hideKey={node.attrs.hideKey}
+                hideValue={node.attrs.hideValue}
                 editable={node.attrs.editable}
                 expanded={this.Expanded}
                 dataDoc={node.attrs.dataDoc}
@@ -112,6 +113,7 @@ interface IDashFieldViewInternal {
     fieldKey: string;
     docId: string;
     hideKey: boolean;
+    hideValue: boolean;
     tbox: FormattedTextBox;
     width: number;
     height: number;
@@ -217,12 +219,28 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     };
 
     toggleFieldHide = undoable(
-        action(() => this._dashDoc && (this._dashDoc[this._fieldKey + '_hideKey'] = !this._dashDoc[this._fieldKey + '_hideKey'])),
+        action(() => {
+            const editor = this._props.tbox.EditorView!;
+            editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideKey: !this._props.node.attrs.hideKey ? true : false }));
+        }),
         'hideKey'
     );
 
+    toggleValueHide = undoable(
+        action(() => {
+            const editor = this._props.tbox.EditorView!;
+            editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideValue: !this._props.node.attrs.hideValue ? true : false }));
+        }),
+        'hideValue'
+    );
+
+    @observable _showValue = false;
     @computed get _hideKey() {
-        return this._dashDoc?.[this._fieldKey + '_hideKey'] && !this._expanded;
+        return this._props.hideKey && !this._expanded;
+    }
+
+    @computed get _hideValue() {
+        return !this._showValue && this._props.hideValue && !this._expanded;
     }
 
     // clicking on the label creates a pivot view collection of all documents
@@ -231,7 +249,11 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
         setupMoveUpEvents(this, e, returnFalse, returnFalse, e => {
             DashFieldViewMenu.createFieldView = this.createPivotForField;
             DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide;
+            DashFieldViewMenu.toggleValueHide = this.toggleValueHide;
             DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16, this._fieldKey);
+            this._dashDoc?.[this._fieldKey + '_hideValue'] && runInAction(() => (this._showValue = !this._showValue));
+            const editor = this._props.tbox.EditorView!;
+            setTimeout(() => editor.dispatch(editor.state.tr.setSelection(new NodeSelection(editor.state.doc.resolve(this._props.getPos())))), 100);
         });
     };
 
@@ -257,12 +279,12 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                     height: this._props.height,
                     pointerEvents: this._props.tbox._props.rootSelected?.() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
                 }}>
-                {this._props.hideKey || this._hideKey ? null : (
+                {this._hideKey ? null : (
                     <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
                         {(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : this._dashDoc?.title + ':') + this._fieldKey}
                     </span>
                 )}
-                {this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
+                {this._props.fieldKey.startsWith('#') || this._hideValue ? null : this.fieldValueContent}
                 {/* {!this.values.length ? null : (
                     <select className="dashFieldView-select" tabIndex={-1} onChange={this.selectVal}>
                         {this.values.map(val => (
@@ -279,6 +301,7 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
     static Instance: DashFieldViewMenu;
     static createFieldView: (e: React.MouseEvent) => void = emptyFunction;
     static toggleFieldHide: () => void = emptyFunction;
+    static toggleValueHide: () => void = emptyFunction;
     constructor(props: any) {
         super(props);
         DashFieldViewMenu.Instance = this;
@@ -292,6 +315,10 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
         DashFieldViewMenu.toggleFieldHide();
         DashFieldViewMenu.Instance.fadeOut(true);
     };
+    toggleValueHide = (e: React.MouseEvent) => {
+        DashFieldViewMenu.toggleValueHide();
+        DashFieldViewMenu.Instance.fadeOut(true);
+    };
 
     @observable _fieldKey = '';
 
@@ -308,16 +335,27 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
     render() {
         return this.getElement(
             <>
-                <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}>
-                    <button className="antimodeMenu-button" onPointerDown={this.showFields}>
-                        <FontAwesomeIcon icon="eye" size="lg" />
-                    </button>
-                </Tooltip>
-                <Tooltip key="trash" title={<div className="dash-tooltip">Toggle view of field key</div>}>
-                    <button className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}>
-                        <FontAwesomeIcon icon="bullseye" size="lg" />
-                    </button>
-                </Tooltip>
+                {!this._fieldKey.startsWith('#') ? null : (
+                    <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}>
+                        <button className="antimodeMenu-button" onPointerDown={this.showFields}>
+                            <FontAwesomeIcon icon="eye" size="sm" />
+                        </button>
+                    </Tooltip>
+                )}
+                {this._fieldKey.startsWith('#') ? null : (
+                    <Tooltip key="key" title={<div className="dash-tooltip">Toggle view of field key</div>}>
+                        <button className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}>
+                            <FontAwesomeIcon icon="bullseye" size="sm" />
+                        </button>
+                    </Tooltip>
+                )}
+                {this._fieldKey.startsWith('#') ? null : (
+                    <Tooltip key="val" title={<div className="dash-tooltip">Toggle view of field value</div>}>
+                        <button className="antimodeMenu-button" onPointerDown={this.toggleValueHide}>
+                            <FontAwesomeIcon icon="hashtag" size="sm" />
+                        </button>
+                    </Tooltip>
+                )}
             </>
         );
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f06e5fad0..39d4893ab 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -104,7 +104,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _recordingStart: number = 0;
     private _ignoreScroll = false;
-    private _hadDownFocus = false;
     private _focusSpeed: Opt<number>;
     private _keymap: any = undefined;
     private _rules: RichTextRules | undefined;
@@ -328,7 +327,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         if (node.type === this._editorView?.state.schema.nodes.dashField) {
             const refDoc = !node.attrs.docId ? this.Document : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
             const fieldKey = StrCast(node.attrs.fieldKey);
-            return fieldKey + ':' + Field.toJavascriptString(refDoc[fieldKey] as Field);
+            return (
+                (node.attrs.hideKey ? '' : '"' + fieldKey + '"' + ':') + //
+                (node.attrs.hideValue ? '' : Field.toJavascriptString(refDoc[fieldKey] as Field))
+            );
         }
         return '';
     };
@@ -383,7 +385,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     } else {
                         // if we've deleted all the text in a note driven by a template, then restore the template data
                         dataDoc[this.fieldKey] = undefined;
-                        this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((layoutData ?? protoData ?? prevData).Data)));
+                        this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(((layoutData !== prevData ? layoutData : undefined) ?? protoData).Data)));
                         ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, text: newText });
                         unchanged = false;
                     }
@@ -1363,7 +1365,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             DocServer.GetRefField(pdfAnchorId).then(pdfAnchor => {
                 if (pdfAnchor instanceof Doc) {
                     const dashField = view.state.schema.nodes.paragraph.create({}, [
-                        view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, editable: false, expanded: true }, undefined, [
+                        view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, hideValue: false, editable: false, expanded: true }, undefined, [
                             view.state.schema.marks.linkAnchor.create({
                                 allAnchors: [{ href: `/doc/${this.Document[Id]}`, title: this.Document.title, anchorId: `${this.Document[Id]}` }],
                                 title: StrCast(pdfAnchor.title),
@@ -1577,7 +1579,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 (this.ProseRef?.children?.[0] as any).focus();
             }
         }
-        this._hadDownFocus = this.ProseRef?.children[0].className.includes('focused') ?? false;
         if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
             e.preventDefault();
         }
@@ -1588,9 +1589,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     onPointerUp = (e: React.PointerEvent): void => {
         const editor = this._editorView!;
         const state = editor?.state;
-        if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime) && !this._hadDownFocus) {
-            (this.ProseRef?.children[0] as HTMLElement)?.blur?.();
-        }
         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() && !e.button) {
@@ -1599,10 +1597,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             while (xpos > 0 && !state.doc.resolve(xpos).node()?.isTextblock) {
                 xpos = xpos - 1;
             }
-            editor.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(xpos))));
-            let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
-            while (target && !target.dataset?.targethrefs) target = target.parentElement;
-            FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
+            let node: any;
+            try {
+                node = state.doc.nodeAt(xpos);
+            } catch (e) {}
+            if (node?.type !== schema.nodes.dashFieldView) {
+                editor.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(xpos))));
+                let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
+                while (target && !target.dataset?.targethrefs) target = target.parentElement;
+                FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
+            } else {
+                editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(xpos))));
+            }
         }
     };
     @action
@@ -1800,7 +1806,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             default:
                 if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
             case ' ':
-                if (e.code !== 'Space') {
+                if (e.code !== 'Space' && e.code !== 'Backspace') {
                     [AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.Document)) &&
                         this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
                 }
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 7afe91e94..418ec92ef 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -137,6 +137,7 @@ export class RichTextRules {
                 textDocInline.title = inlineFieldKey; // give the annotation its own title
                 textDocInline.title_custom = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
                 textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
+                textDocInline.isDataDoc = true;
                 textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
                 textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
                 textDoc[inlineFieldKey] = ''; // set a default value for the annotation
@@ -349,7 +350,7 @@ export class RichTextRules {
                         if (fieldKey === this.TextBox.fieldKey) return this.TextBox.EditorView!.state.tr;
                     }
                     const target = docTitle ? getTitledDoc(docTitle) : undefined;
-                    const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false, dataDoc });
+                    const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false, hideValue: false, dataDoc });
                     return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
                 },
                 { inCode: true }
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 905146ee2..6862eecef 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -264,6 +264,7 @@ export const nodes: { [index: string]: NodeSpec } = {
             fieldKey: { default: '' },
             docId: { default: '' },
             hideKey: { default: false },
+            hideValue: { default: false },
             editable: { default: true },
             expanded: { default: null },
             dataDoc: { default: false },
-- 
cgit v1.2.3-70-g09d2


From 10e1a8c7edbc0c19fda4efd82d7ed802ccbf9b4f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 30 Mar 2024 17:08:03 -0400
Subject: got rid of HTMLMarkers in favor of Configs -- fixes issues with pdf
 and web annotations not scrolling to the right place.  Also got rid of
 default values for x/y which caused a similar problem for some configs which
 should not have x/y set.   fixed webBox to have its annotation  computedField
 set properly

---
 src/client/documents/Documents.ts                   | 7 +------
 src/client/views/MarqueeAnnotator.tsx               | 4 ++--
 src/client/views/collections/CollectionTimeView.tsx | 2 +-
 src/client/views/nodes/WebBox.tsx                   | 6 +++---
 4 files changed, 7 insertions(+), 12 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 6965ae0ce..8dccdeba9 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -919,8 +919,6 @@ export namespace Docs {
                 title,
                 type,
                 isBaseProto: true,
-                x: 0,
-                y: 0,
                 _width: 300,
                 'acl-Guest': SharingPermissions.View,
                 ...(template.options || {}),
@@ -1208,10 +1206,6 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id, '', undefined, undefined, true);
         }
 
-        export function HTMLMarkerDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Freeform }, id);
-        }
-
         export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
             return InstanceFromProto(
                 Prototypes.get(DocumentType.COL),
@@ -1536,6 +1530,7 @@ export namespace DocUtils {
                     link_relationship: linkSettings.link_relationship,
                     link_description: linkSettings.link_description,
                     link_autoMoveAnchors: true,
+                    _lockedPosition: true,
                     _layout_showCaption: '', // removed since they conflict with showing a link with a LinkBox (ie, line, not comparison box)
                     _layout_showTitle: '',
                     // _layout_showCaption: 'link_description',
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 0b68fd59e..9fa20f642 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -86,10 +86,10 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
             return marqueeAnno;
         }
 
-        const textRegionAnno = Docs.Create.HTMLMarkerDocument([], {
+        const textRegionAnno = Docs.Create.ConfigDocument({
             annotationOn: this.props.Document,
             text: this.props.selectionText() as any, // text want an RTFfield, but strings are acceptable, too.
-            text_html: this.props.selectionText() as any, 
+            text_html: this.props.selectionText() as any,
             backgroundColor: 'transparent',
             presentation_duration: 2100,
             presentation_transition: 500,
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index b92edd165..37452ddfb 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -49,7 +49,7 @@ export class CollectionTimeView extends CollectionSubView() {
     }
 
     getAnchor = (addAsAnnotation: boolean) => {
-        const anchor = Docs.Create.HTMLMarkerDocument([], {
+        const anchor = Docs.Create.ConfigDocument({
             title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any,
             annotationOn: this.Document,
         });
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 434415b96..033b01d24 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -179,9 +179,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             // bcz: need to make sure that doc.data_annotations points to the currently active web page's annotations (this could/should be when the doc is created)
             if (this._url) {
                 const reqdFuncs: { [key: string]: string } = {};
-                reqdFuncs[this.fieldKey + '_annotations'] = `copyField(this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"annotations"])`;
-                reqdFuncs[this.fieldKey + '_annotations-setter'] = `this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"annotations"] = value`;
-                reqdFuncs[this.fieldKey + '_sidebar'] = `copyField(this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"sidebar"])`;
+                reqdFuncs[this.fieldKey + '_annotations'] = `copyField(this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"_annotations"])`;
+                reqdFuncs[this.fieldKey + '_annotations-setter'] = `this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"_annotations"] = value`;
+                reqdFuncs[this.fieldKey + '_sidebar'] = `copyField(this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"_sidebar"])`;
                 DocUtils.AssignScripts(this.dataDoc, {}, reqdFuncs);
             }
         });
-- 
cgit v1.2.3-70-g09d2

