From d8f2a45a95a14151d3484c3c14f72d217a818786 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 22 Aug 2022 12:50:52 -0400
Subject: made inkMasks animate when they are turned on /off.    fixed
 inkStrokes to be interpolated when they have an activeFrame set.

---
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx    | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 0fd326091..1d518076a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -119,9 +119,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable ChildDrag: DocumentView | undefined; // child document view being dragged.  needed to update drop areas of groups when a group item is dragged.
 
     @computed get views() {
-        const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask).map(ele => ele.ele);
-        const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && !ele.inkMask).map(ele => ele.ele);
-        if (viewsMask.length) renderableEles.push(<div className="collectionfreeformview-mask">{viewsMask}</div>);
+        const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1).map(ele => ele.ele);
+        const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask === -1).map(ele => ele.ele);
+        if (viewsMask.length) renderableEles.push(<div className={`collectionfreeformview-mask${this._layoutElements.some(ele => (ele.inkMask ?? 0) > 0) ? '' : '-empty'}`}>{viewsMask}</div>);
         return renderableEles;
     }
     @computed get fitToContentVals() {
@@ -251,7 +251,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     isCurrent(doc: Doc) {
-        if (doc.isInkMask && doc.opacity === 0) return false; // bcz: hacky --- allows inkMasks to be "turned off" in a presentation without removing them from the collection.  otherwise, they still render a gray background.. need to come back to this and fix.
         const dispTime = NumCast(doc._timecodeToShow, -1);
         const endTime = NumCast(doc._timecodeToHide, dispTime + 1.5);
         const curTime = NumCast(this.Document._currentTimecode, -1);
@@ -1465,7 +1464,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 elements.push({
                     ele: this.getChildDocView(entry[1]),
                     bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica),
-                    inkMask: BoolCast(entry[1].pair.layout.isInkMask),
+                    inkMask: BoolCast(entry[1].pair.layout.isInkMask) ? NumCast(entry[1].pair.layout.opacity) : -1,
                 })
             );
 
-- 
cgit v1.2.3-70-g09d2


From 440f5c296a599d5c3f9ed781b58c5b00da6edb5a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 22 Aug 2022 17:18:10 -0400
Subject: added animation of background color, and streamlined/extended a
 variety of props-related things.

---
 src/client/util/CurrentUserUtils.ts                |  6 +--
 src/client/views/DocComponent.tsx                  | 14 -------
 src/client/views/StyleProvider.tsx                 |  9 +++--
 .../CollectionFreeFormLayoutEngines.tsx            |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 33 ++++++++--------
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 30 ++++++++++++---
 src/client/views/nodes/FieldView.tsx               | 11 ++++--
 .../views/nodes/formattedText/FormattedTextBox.tsx | 45 ++++++++++------------
 src/fields/Doc.ts                                  | 14 +++++++
 src/fields/ScriptField.ts                          | 12 +++++-
 10 files changed, 102 insertions(+), 74 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 20c57c617..17d58595c 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -235,9 +235,9 @@ export class CurrentUserUtils {
             const header = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "text", 
                 layout:
                 "<HTMLdiv transformOrigin='top left' width='{100/scale}%' height='{100/scale}%' transform='scale({scale})'>" +
-                `    <FormattedTextBox {...props} dontScale='true' fieldKey={'text'}  height='calc(100% - ${headerBtnHgt}px - {this._headerHeight||0}px)'/>` +
-                "    <FormattedTextBox {...props} dontScale='true' fieldKey={'header'}  dontSelectOnLoad='true' ignoreAutoHeight='true' fontSize='{this._headerFontSize||9}px' height='{(this._headerHeight||0)}px' background='{this._headerColor || MySharedDocs().userColor||`lightGray`}' />" +
-                `    <HTMLdiv fontSize='${headerBtnHgt - 1}px' height='${headerBtnHgt}px' background='yellow' onClick={‘(this._headerHeight=scale*Math.min(Math.max(0,this._height-30),this._headerHeight===0?50:0)) + (this._autoHeightMargins=this._headerHeight ? this._headerHeight+${headerBtnHgt}:0)’}  >Metadata</HTMLdiv>` +
+                `    <FormattedTextBox {...props} dontScale='true' fieldKey={'text'} height='calc(100% - ${headerBtnHgt}px - {this._headerHeight||0}px)'/>` +
+                "    <FormattedTextBox {...props} dontScale='true' fieldKey={'header'} dontSelectOnLoad='true' ignoreAutoHeight='true' fontSize='{this._headerFontSize||9}px' height='{(this._headerHeight||0)}px' backgroundColor='{this._headerColor || MySharedDocs().userColor||`lightGray`}' />" +
+                `    <HTMLdiv fontSize='${headerBtnHgt - 1}px' height='${headerBtnHgt}px' backgroundColor='yellow' onClick={‘(this._headerHeight=scale*Math.min(Math.max(0,this._height-30),this._headerHeight===0?50:0)) + (this._autoHeightMargins=this._headerHeight ? this._headerHeight+${headerBtnHgt}:0)’}  >Metadata</HTMLdiv>` +
                 "</HTMLdiv>"
             }, "header");
 
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 280ca8a8c..886dd974b 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -132,20 +132,6 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
 
         lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result;
 
-        styleFromLayoutString = (scale: number) => {
-            const style: { [key: string]: any } = {};
-            const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'background', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position'];
-            const replacer = (match: any, expr: string, offset: any, string: any) => {
-                // bcz: this executes a script to convert a property expression string:  { script }  into a value
-                return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.rootDoc, this: this.layoutDoc, scale }).result?.toString() ?? '';
-            };
-            divKeys.map((prop: string) => {
-                const p = (this.props as any)[prop];
-                typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
-            });
-            return style;
-        };
-
         protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
         @computed public get annotationKey() {
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 3bd4f5152..c0ba170c6 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -40,7 +40,8 @@ export enum StyleProp {
     JitterRotation = 'jitterRotation', // whether documents should be randomly rotated
     BorderPath = 'customBorder', // border path for document view
     FontSize = 'fontSize', // size of text font
-    FontFamily = 'fontFamily', // size of text font
+    FontFamily = 'fontFamily', // font family of text
+    FontWeight = 'fontWeight', // font weight of text
 }
 
 function darkScheme() {
@@ -117,9 +118,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.HideLinkButton:
             return props?.hideLinkButton || (!selected && (doc?.isLinkButton || doc?.hideLinkButton));
         case StyleProp.FontSize:
-            return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(doc?.fontSize, StrCast(Doc.UserDoc().fontSize)));
+            return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(doc?._fontSize, StrCast(Doc.UserDoc().fontSize)));
         case StyleProp.FontFamily:
-            return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?.fontFamily, StrCast(Doc.UserDoc().fontFamily)));
+            return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?._fontFamily, StrCast(Doc.UserDoc().fontFamily)));
+        case StyleProp.FontWeight:
+            return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(doc?._fontWeight, StrCast(Doc.UserDoc().fontWeight)));
         case StyleProp.ShowTitle:
             return (
                 (doc &&
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index b210e7d9a..d84717b95 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -34,7 +34,7 @@ export interface PoolData {
     zIndex?: number;
     width?: number;
     height?: number;
-    color?: string;
+    backgroundColor?: string;
     opacity?: number;
     transition?: string;
     highlight?: boolean;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1d518076a..a807ba4ea 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -115,7 +115,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable _timelineRef = React.createRef<Timeline>();
     @observable _marqueeRef = React.createRef<HTMLDivElement>();
     @observable _marqueeViewRef = React.createRef<MarqueeView>();
-    @observable _keyframeEditing = false;
     @observable ChildDrag: DocumentView | undefined; // child document view being dragged.  needed to update drop areas of groups when a group item is dragged.
 
     @computed get views() {
@@ -185,8 +184,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame));
         }
     };
-    @action setKeyFrameEditing = (set: boolean) => (this._keyframeEditing = set);
-    getKeyFrameEditing = () => this._keyframeEditing;
     onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick);
     onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
     onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
@@ -272,15 +269,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             .sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
         zsorted.forEach((doc, index) => (doc.zIndex = doc.isInkMask ? 5000 : index + 1));
         const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000));
-        const dropPos = this.Document._currentFrame !== undefined ? [dvals.x || 0, dvals.y || 0] : [NumCast(refDoc.x), NumCast(refDoc.y)];
+        const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)];
         for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
             const d = docDragData.droppedDocuments[i];
             const layoutDoc = Doc.Layout(d);
             if (this.Document._currentFrame !== undefined) {
                 CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false);
                 const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000));
-                vals.x = x + (vals.x || 0) - dropPos[0];
-                vals.y = y + (vals.y || 0) - dropPos[1];
+                vals.x = x + NumCast(vals.x) - dropPos[0];
+                vals.y = y + NumCast(vals.y) - dropPos[1];
                 vals._scrollTop = this.Document.editScrollProgressivize ? vals._scrollTop : undefined;
                 CollectionFreeFormDocumentView.setValues(NumCast(this.Document._currentFrame), d, vals);
             } else {
@@ -1317,22 +1314,22 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     });
 
     getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData {
-        const layoutDoc = Doc.Layout(params.pair.layout);
-        const { z, color, zIndex } = params.pair.layout;
-        const { x, y, opacity } =
-            this.Document._currentFrame === undefined
-                ? { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.childOpacity ? this.props.childOpacity() : this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) }
-                : CollectionFreeFormDocumentView.getValues(params.pair.layout, NumCast(this.Document._currentFrame));
+        const curFrame = NumCast(this.Document._currentFrame);
+        const childDoc = params.pair.layout;
+        const childDocLayout = Doc.Layout(childDoc);
+        const { z, zIndex } = childDoc;
+        const { backgroundColor } = curFrame === undefined ? { backgroundColor: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, curFrame);
+        const { x, y, opacity } = curFrame === undefined ? { x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } : CollectionFreeFormDocumentView.getValues(childDoc, curFrame);
         return {
             x: NumCast(x),
             y: NumCast(y),
             z: Cast(z, 'number'),
-            color: StrCast(color),
+            backgroundColor: Cast(backgroundColor, 'string') ?? this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.BackgroundColor),
+            opacity: Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
             zIndex: Cast(zIndex, 'number'),
-            transition: StrCast(layoutDoc.dataTransition),
-            opacity: this._keyframeEditing ? 1 : Cast(opacity, 'number', null),
-            width: Cast(layoutDoc._width, 'number'),
-            height: Cast(layoutDoc._height, 'number'),
+            width: Cast(childDocLayout._width, 'number'),
+            height: Cast(childDocLayout._height, 'number'),
+            transition: StrCast(childDocLayout.dataTransition),
             pair: params.pair,
             replica: '',
         };
@@ -1447,7 +1444,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             for (const entry of array) {
                 const lastPos = this._cachedPool.get(entry[0]); // last computed pos
                 const newPos = entry[1];
-                if (!lastPos || newPos.opacity !== lastPos.opacity || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) {
+                if (!lastPos || newPos.backgroundColor !== lastPos.backgroundColor || newPos.opacity !== lastPos.opacity || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) {
                     this._layoutPoolData.set(entry[0], newPos);
                 }
                 if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index e19e2d525..7dc8a7fbb 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -18,7 +18,7 @@ import { DocumentView, DocumentViewProps } from './DocumentView';
 import React = require('react');
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
-    dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined;
+    dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined;
     sizeProvider?: (doc: Doc, replica: string) => { width: number; height: number } | undefined;
     renderCutoffProvider: (doc: Doc) => boolean;
     zIndex?: number;
@@ -32,6 +32,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
 @observer
 export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps>() {
     public static animFields = ['_height', '_width', 'x', 'y', '_scrollTop', 'opacity']; // fields that are configured to be animatable using animation frames
+    public static animStringFields = ['backgroundColor']; // fields that are configured to be animatable using animation frames
     @observable _animPos: number[] | undefined = undefined;
     @observable _contentView: DocumentView | undefined | null;
     get displayName() {
@@ -41,16 +42,19 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`;
     }
     get X() {
-        return this.dataProvider ? this.dataProvider.x : NumCast(this.Document.x);
+        return this.dataProvider?.x ?? NumCast(this.Document.x);
     }
     get Y() {
-        return this.dataProvider ? this.dataProvider.y : NumCast(this.Document.y);
+        return this.dataProvider?.y ?? NumCast(this.Document.y);
     }
     get ZInd() {
-        return this.dataProvider ? this.dataProvider.zIndex : NumCast(this.Document.zIndex);
+        return this.dataProvider?.zIndex ?? NumCast(this.Document.zIndex);
     }
     get Opacity() {
-        return this.dataProvider ? this.dataProvider.opacity : undefined;
+        return this.dataProvider?.opacity;
+    }
+    get BackgroundColor() {
+        return this.dataProvider?.backgroundColor;
     }
     get Highlight() {
         return this.dataProvider?.highlight;
@@ -67,6 +71,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
         if (property === StyleProp.Opacity && doc === this.layoutDoc) return this.Opacity; // only change the opacity for this specific document, not its children
+        if (property === StyleProp.BackgroundColor) {
+            return this.BackgroundColor; // only change the opacity for this specific document, not its children
+        }
         return this.props.styleProvider?.(doc, props, property);
     };
 
@@ -77,6 +84,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         }, {} as { [val: string]: Opt<number> });
     }
 
+    public static getStringValues(doc: Doc, time: number) {
+        return CollectionFreeFormDocumentView.animStringFields.reduce((p, val) => {
+            p[val] = Cast(`${val}-indexed`, listSpec('string'), [StrCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as string);
+            return p;
+        }, {} as { [val: string]: Opt<string> });
+    }
+
     public static setValues(time: number, d: Doc, vals: { [val: string]: Opt<number> }) {
         const timecode = Math.round(time);
         Object.keys(vals).forEach(val => {
@@ -95,6 +109,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     const findexed = Cast(doc[`${val}-indexed`], listSpec('number'), null);
                     findexed?.length <= timecode + 1 && findexed.push(undefined as any as number);
                 });
+                CollectionFreeFormDocumentView.animStringFields.forEach(val => {
+                    const findexed = Cast(doc[`${val}-indexed`], listSpec('string'), null);
+                    findexed?.length <= timecode + 1 && findexed.push(undefined as any as string);
+                });
             })
         );
         setTimeout(
@@ -142,6 +160,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 doc['opacity-indexed'] = new List<number>(numberRange(currTimecode + 1).map(t => (!doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1)));
             }
             CollectionFreeFormDocumentView.animFields.forEach(val => (doc[val] = ComputedField.MakeInterpolated(val, 'activeFrame', doc, currTimecode)));
+            CollectionFreeFormDocumentView.animStringFields.forEach(val => (doc[val] = ComputedField.MakeInterpolatedString(val, 'activeFrame', doc, currTimecode)));
             doc.activeFrame = ComputedField.MakeFunction('self.context?._currentFrame||0');
             doc.dataTransition = 'inherit';
         });
@@ -189,7 +208,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             PanelWidth: this.panelWidth,
             PanelHeight: this.panelHeight,
         };
-        const background = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
         const mixBlendMode = undefined; // (StrCast(this.layoutDoc.mixBlendMode) as any) || (typeof background === 'string' && background && !background.startsWith('linear') && DashColor(background).alpha() !== 1 ? 'multiply' : undefined);
         return (
             <div
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index dd2c13391..77aaa4441 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -26,16 +26,21 @@ export interface FieldViewProps extends DocumentViewSharedProps {
     NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to DocumentViewInternalsProps
     onBrowseClick?: () => ScriptField | undefined;
     onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
+    pointerEvents?: () => Opt<string>;
 
     // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
-    pointerEvents?: () => Opt<string>;
+    // See currentUserUtils headerTemplate for examples of creating text boxes from html which set some of these fields
+    // Also, see InkingStroke for examples of creating text boxes from render() methods which set some of these fields
+    backgroundColor?: string;
+    color?: string;
     fontSize?: number;
     height?: number;
     width?: number;
-    background?: string;
-    color?: string;
     xPadding?: number;
     yPadding?: number;
+    noSidebar?: boolean;
+    dontScale?: boolean;
+    dontSelectOnLoad?: boolean; // suppress selecting (e.g.,. text box) when loaded (and mark as not being associated with scrollTop document field)
 }
 
 @observer
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f61533619..81ac45521 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -11,7 +11,7 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
 import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
 import { EditorView } from 'prosemirror-view';
 import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclAugment, AclEdit, AclReadonly, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { PrefetchProxy } from '../../../../fields/Proxy';
@@ -35,6 +35,7 @@ import { SnappingManager } from '../../../util/SnappingManager';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
 import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
 import { CollectionStackingView } from '../../collections/CollectionStackingView';
+import { CollectionTreeView } from '../../collections/CollectionTreeView';
 import { ContextMenu } from '../../ContextMenu';
 import { ContextMenuProps } from '../../ContextMenuItem';
 import { ViewBoxAnnotatableComponent } from '../../DocComponent';
@@ -44,6 +45,7 @@ import { LightboxView } from '../../LightboxView';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { StyleProp } from '../../StyleProvider';
+import { DocumentViewInternal } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { LinkDocPreview } from '../LinkDocPreview';
 import { DashDocCommentView } from './DashDocCommentView';
@@ -61,25 +63,14 @@ import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
 import applyDevTools = require('prosemirror-dev-tools');
 import React = require('react');
-import { text } from 'body-parser';
-import { CollectionTreeView } from '../../collections/CollectionTreeView';
-import { DocumentViewInternal } from '../DocumentView';
 const translateGoogleApi = require('translate-google-api');
 
-export interface FormattedTextBoxProps {
-    makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link.  allows the text doc to react and setup a hyeprlink for any selected text
-    xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView
-    yPadding?: number;
-    noSidebar?: boolean;
-    dontScale?: boolean;
-    dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded (and mark as not being associated with scrollTop document field)
-}
 export const GoogleRef = 'googleDocId';
 
 type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
 
 @observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps & FormattedTextBoxProps>() {
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(FormattedTextBox, fieldStr);
     }
@@ -1843,18 +1834,27 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
         const selPad = (selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0;
         const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? '-selected' : '';
-        const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
-        return styleFromString?.height === '0px' ? null : (
+        const styleFromLayoutString = Doc.styleFromLayoutString(this.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
+        return styleFromLayoutString?.height === '0px' ? null : (
             <div
                 className="formattedTextBox-cont"
                 onWheel={e => this.props.isContentActive() && e.stopPropagation()}
                 style={{
-                    transform: this.props.dontScale ? undefined : `scale(${scale})`,
-                    transformOrigin: this.props.dontScale ? undefined : 'top left',
-                    width: this.props.dontScale ? undefined : `${100 / scale}%`,
-                    height: this.props.dontScale ? undefined : `${100 / scale}%`,
+                    ...(this.props.dontScale
+                        ? {}
+                        : {
+                              transform: `scale(${scale})`,
+                              transformOrigin: 'top left',
+                              width: `${100 / scale}%`,
+                              height: `${100 / scale}%`,
+                          }),
+                    transition: 'inherit',
                     // overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined,
-                    ...styleFromString,
+                    color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
+                    fontSize: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize),
+                    fontFamily: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily),
+                    fontWeight: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight),
+                    ...styleFromLayoutString,
                 }}>
                 <div
                     className={`formattedTextBox-cont`}
@@ -1862,11 +1862,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     style={{
                         overflow: this.autoHeight ? 'hidden' : undefined,
                         height: this.props.height || (this.autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined),
-                        background: this.props.background ? this.props.background : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
-                        color: this.props.color ? this.props.color : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
-                        fontSize: this.props.fontSize ? this.props.fontSize : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize),
-                        fontWeight: Cast(this.layoutDoc._fontWeight, 'string', null) as any,
-                        fontFamily: StrCast(this.layoutDoc._fontFamily, 'inherit'),
                         pointerEvents: interactive ? undefined : 'none',
                     }}
                     onContextMenu={this.specificContextMenu}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 8d56ebf8c..612fc7fb8 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1389,6 +1389,20 @@ export namespace Doc {
         return !curPres ? false : DocListCast(curPres.data).findIndex(val => Doc.AreProtosEqual(val, doc)) !== -1;
     }
 
+    export function styleFromLayoutString(rootDoc: Doc, layoutDoc: Doc, props: any, scale: number) {
+        const style: { [key: string]: any } = {};
+        const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position'];
+        const replacer = (match: any, expr: string, offset: any, string: any) => {
+            // bcz: this executes a script to convert a property expression string:  { script }  into a value
+            return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: rootDoc, this: layoutDoc, scale }).result?.toString() ?? '';
+        };
+        divKeys.map((prop: string) => {
+            const p = props[prop];
+            typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
+        });
+        return style;
+    }
+
     // prettier-ignore
     export function toIcon(doc?: Doc, isOpen?: boolean) {
         switch (StrCast(doc?.type)) {
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 68fb45987..0fd992d3b 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -9,7 +9,7 @@ import { Doc, Field, Opt } from './Doc';
 import { Copy, Id, ToScriptString, ToString } from './FieldSymbols';
 import { List } from './List';
 import { ObjectField } from './ObjectField';
-import { Cast, NumCast } from './Types';
+import { Cast, NumCast, StrCast } from './Types';
 import { Plugins } from './util';
 
 function optional(propSchema: PropSchema) {
@@ -199,6 +199,16 @@ export class ComputedField extends ScriptField {
         const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: 'any' }, true, {});
         return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
     }
+    public static MakeInterpolatedString(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number) {
+        if (!doc[`${fieldKey}-indexed`]) {
+            const flist = new List<string>(numberRange(curTimecode + 1).map(i => undefined) as any as string[]);
+            flist[curTimecode] = StrCast(doc[fieldKey]);
+            doc[`${fieldKey}-indexed`] = flist;
+        }
+        const getField = ScriptField.CompileScript(`getIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey})`, {}, true, {});
+        const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: 'any' }, true, {});
+        return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
+    }
 }
 export namespace ComputedField {
     let useComputed = true;
-- 
cgit v1.2.3-70-g09d2


From 653afba6635d676ec4fcdfa649360ca26c91cb88 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 22 Aug 2022 17:23:21 -0400
Subject: from last

---
 .../collectionFreeForm/CollectionFreeFormView.tsx       |  4 ++--
 src/client/views/nodes/DocumentContentsView.tsx         | 17 ++++++++---------
 2 files changed, 10 insertions(+), 11 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a807ba4ea..b34dad226 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1314,7 +1314,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     });
 
     getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData {
-        const curFrame = NumCast(this.Document._currentFrame);
+        const curFrame = Cast(this.Document._currentFrame, 'number');
         const childDoc = params.pair.layout;
         const childDocLayout = Doc.Layout(childDoc);
         const { z, zIndex } = childDoc;
@@ -1324,7 +1324,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             x: NumCast(x),
             y: NumCast(y),
             z: Cast(z, 'number'),
-            backgroundColor: Cast(backgroundColor, 'string') ?? this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.BackgroundColor),
+            backgroundColor: Cast(backgroundColor, 'string') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.BackgroundColor),
             opacity: Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
             zIndex: Cast(zIndex, 'number'),
             width: Cast(childDocLayout._width, 'number'),
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 381436a56..d065c62fb 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -25,7 +25,7 @@ import './DocumentView.scss';
 import { EquationBox } from './EquationBox';
 import { FieldView, FieldViewProps } from './FieldView';
 import { FilterBox } from './FilterBox';
-import { FormattedTextBox, FormattedTextBoxProps } from './formattedText/FormattedTextBox';
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { FunctionPlotBox } from './FunctionPlotBox';
 import { ImageBox } from './ImageBox';
 import { KeyValueBox } from './KeyValueBox';
@@ -114,14 +114,13 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
 
 @observer
 export class DocumentContentsView extends React.Component<
-    DocumentViewProps &
-        FormattedTextBoxProps & {
-            isSelected: (outsideReaction: boolean) => boolean;
-            select: (ctrl: boolean) => void;
-            NativeDimScaling?: () => number;
-            setHeight?: (height: number) => void;
-            layoutKey: string;
-        }
+    DocumentViewProps & {
+        isSelected: (outsideReaction: boolean) => boolean;
+        select: (ctrl: boolean) => void;
+        NativeDimScaling?: () => number;
+        setHeight?: (height: number) => void;
+        layoutKey: string;
+    }
 > {
     @computed get layout(): string {
         TraceMobx();
-- 
cgit v1.2.3-70-g09d2


From cfcff13b6a5acfd1299102716a3c9747b32a7e7a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 22 Aug 2022 17:28:45 -0400
Subject: from last

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

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b34dad226..0234d303f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1324,7 +1324,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             x: NumCast(x),
             y: NumCast(y),
             z: Cast(z, 'number'),
-            backgroundColor: Cast(backgroundColor, 'string') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.BackgroundColor),
+            backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.props.styleProvider?.(childDoc, this.props, StyleProp.BackgroundColor),
             opacity: Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
             zIndex: Cast(zIndex, 'number'),
             width: Cast(childDocLayout._width, 'number'),
-- 
cgit v1.2.3-70-g09d2


From 267bb35a555ac0f8b67041346213d9a06386785f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 23 Aug 2022 11:57:58 -0400
Subject: added color to animated properties.  changed doc decorations to stop
 before menu bar.  changed color of tab bar so that doc decorations is visible
 when overlapping.

---
 src/client/views/InkingStroke.tsx                  |  3 ++-
 src/client/views/MainView.tsx                      |  4 ++--
 src/client/views/_nodeModuleOverrides.scss         |  3 ++-
 .../CollectionFreeFormLayoutEngines.tsx            |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 14 ++++++++++--
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 25 +++++++++++++---------
 6 files changed, 34 insertions(+), 16 deletions(-)

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

diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 520d40abf..ceaabd0e1 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -48,6 +48,7 @@ import Color = require('color');
 import { ComputedField } from '../../fields/ScriptField';
 import { listSpec } from '../../fields/Schema';
 import { List } from '../../fields/List';
+import { StyleProp } from './StyleProvider';
 
 @observer
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -370,7 +371,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const closed = InkingStroke.IsClosed(inkData);
         const isInkMask = BoolCast(this.layoutDoc.isInkMask);
         const fillColor = isInkMask ? '#aaaaaa' : StrCast(this.layoutDoc.fillColor, 'transparent');
-        const strokeColor = !closed && fillColor && fillColor !== 'transparent' ? fillColor : StrCast(this.layoutDoc.color);
+        const strokeColor = !closed && fillColor && fillColor !== 'transparent' ? fillColor : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color) ?? StrCast(this.layoutDoc.color);
 
         // bcz: Hack!! Not really sure why, but having fractional values for width/height of mask ink strokes causes the dragging clone (see DragManager) to be offset from where it should be.
         if (isInkMask && (this.layoutDoc[WidthSym]() !== Math.round(this.layoutDoc[WidthSym]()) || this.layoutDoc[HeightSym]() !== Math.round(this.layoutDoc[HeightSym]()))) {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d2b0e10c1..06be4d194 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -981,7 +981,7 @@ export class MainView extends React.Component {
                 <CaptureManager />
                 <GroupManager />
                 <GoogleAuthenticationManager />
-                <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfHeaderBarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
+                <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfSidebarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
                 <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
                 {this._hideUI ? null : <TopBar />}
                 {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
@@ -994,7 +994,7 @@ export class MainView extends React.Component {
                         default:
                             return (
                                 <>
-                                    <div style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 1999 }}>
+                                    <div style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 2001 }}>
                                         <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
                                     </div>
                                     {this.mainDashboardArea}
diff --git a/src/client/views/_nodeModuleOverrides.scss b/src/client/views/_nodeModuleOverrides.scss
index 17eff022f..b1cce8705 100644
--- a/src/client/views/_nodeModuleOverrides.scss
+++ b/src/client/views/_nodeModuleOverrides.scss
@@ -44,7 +44,8 @@ div .lm_header {
     position: absolute;
     width: calc(100% - 60px);
     overflow: scroll;
-    background: $dark-gray;
+    background: #6b6b6b6b; //$dark-gray;
+    border-radius: 5px;
 }
 
 .lm_tab {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index d84717b95..89cc22d07 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -35,6 +35,7 @@ export interface PoolData {
     width?: number;
     height?: number;
     backgroundColor?: string;
+    color?: string;
     opacity?: number;
     transition?: string;
     highlight?: boolean;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 0234d303f..03beaf65e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1318,12 +1318,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const childDoc = params.pair.layout;
         const childDocLayout = Doc.Layout(childDoc);
         const { z, zIndex } = childDoc;
-        const { backgroundColor } = curFrame === undefined ? { backgroundColor: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, curFrame);
+        const { backgroundColor, color } = curFrame === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, curFrame);
         const { x, y, opacity } = curFrame === undefined ? { x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } : CollectionFreeFormDocumentView.getValues(childDoc, curFrame);
         return {
             x: NumCast(x),
             y: NumCast(y),
             z: Cast(z, 'number'),
+            color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color),
             backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.props.styleProvider?.(childDoc, this.props, StyleProp.BackgroundColor),
             opacity: Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
             zIndex: Cast(zIndex, 'number'),
@@ -1444,7 +1445,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             for (const entry of array) {
                 const lastPos = this._cachedPool.get(entry[0]); // last computed pos
                 const newPos = entry[1];
-                if (!lastPos || newPos.backgroundColor !== lastPos.backgroundColor || newPos.opacity !== lastPos.opacity || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) {
+                if (
+                    !lastPos ||
+                    newPos.color !== lastPos.color ||
+                    newPos.backgroundColor !== lastPos.backgroundColor ||
+                    newPos.opacity !== lastPos.opacity ||
+                    newPos.x !== lastPos.x ||
+                    newPos.y !== lastPos.y ||
+                    newPos.z !== lastPos.z ||
+                    newPos.zIndex !== lastPos.zIndex
+                ) {
                     this._layoutPoolData.set(entry[0], newPos);
                 }
                 if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 7dc8a7fbb..45f68e0f0 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -18,7 +18,7 @@ import { DocumentView, DocumentViewProps } from './DocumentView';
 import React = require('react');
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
-    dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined;
+    dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; color?: string; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined;
     sizeProvider?: (doc: Doc, replica: string) => { width: number; height: number } | undefined;
     renderCutoffProvider: (doc: Doc) => boolean;
     zIndex?: number;
@@ -32,12 +32,14 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
 @observer
 export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps>() {
     public static animFields = ['_height', '_width', 'x', 'y', '_scrollTop', 'opacity']; // fields that are configured to be animatable using animation frames
-    public static animStringFields = ['backgroundColor']; // fields that are configured to be animatable using animation frames
+    public static animStringFields = ['backgroundColor', 'color']; // fields that are configured to be animatable using animation frames
     @observable _animPos: number[] | undefined = undefined;
     @observable _contentView: DocumentView | undefined | null;
     get displayName() {
+        // this makes mobx trace() statements more descriptive
         return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')';
-    } // this makes mobx trace() statements more descriptive
+    }
+
     get transform() {
         return `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`;
     }
@@ -56,12 +58,12 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     get BackgroundColor() {
         return this.dataProvider?.backgroundColor;
     }
+    get Color() {
+        return this.dataProvider?.color;
+    }
     get Highlight() {
         return this.dataProvider?.highlight;
     }
-    @computed get ShowTitle() {
-        return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as Opt<string>;
-    }
     @computed get dataProvider() {
         return this.props.dataProvider?.(this.props.Document, this.props.replica);
     }
@@ -70,10 +72,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
-        if (property === StyleProp.Opacity && doc === this.layoutDoc) return this.Opacity; // only change the opacity for this specific document, not its children
-        if (property === StyleProp.BackgroundColor) {
-            return this.BackgroundColor; // only change the opacity for this specific document, not its children
-        }
+        if (doc === this.layoutDoc)
+            // prettier-ignore
+            switch (property) {
+                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;
+            }
         return this.props.styleProvider?.(doc, props, property);
     };
 
-- 
cgit v1.2.3-70-g09d2


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

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

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

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

